감사 로그
포털의 모든 쓰기 작업은 추가 전용 감사 로그에 기록됩니다. 로그는 "누가 언제 무엇을 무엇에 했는가"의 진실의 원천 — 사고 조사·컴플라이언스 요청 응대 시 가장 먼저 보는 곳입니다.
/admin/audit 페이지는 툴바(actor / target table / action / 시간 범위 필터)와 라이브 데이터 위의 행 표를 노출합니다:

조직 단위 읽기는 super_admin; 팀 단위 읽기는 team_admin.
스키마
각 항목 필드:
| 필드 | 타입 | 설명 |
|---|---|---|
id | UUID | 기본 키. |
created_at | timestamptz | 작업 발생 시각(서버 시계, UTC). |
actor_user_id | UUID | 작업 수행 사용자(시스템 작업은 null). |
team_id | UUID | 해당 시 작업의 팀 범위(조직 단위 쓰기는 null). |
action | text | 동사만(create / update / delete). 테이블은 target_table에 별도 캡처. 예: target_table=projects&action=create 로 필터. |
target_table | text | 영향 받은 객체가 속한 테이블(projects, teams, users, vulnerability_findings 등). |
target_id | String(64) | 영향 받은 객체의 식별자. |
request_id | text | 구조화 로그(X-Request-ID)와 상관. |
diff | jsonb | 정제된 before/after diff. PII는 마스킹(mask_pii). |
ip | inet | 출처 IP. |
user_agent | text | 잘린 UA 문자열. |
추가 전용 계약은 두 계층에서 강제됩니다.
-
애플리케이션 — 감사 리스너는 insert 만 발신하며 API 가 update / delete 엔드포인트를 노출하지 않습니다.
-
데이터베이스 — 두 개의 트리거(마이그레이션
0012)가 모든 변경 시도에SQLSTATE 23000(integrity_constraint_violation)을 발생시킵니다.audit_logs_immutable_trigger— BEFORE UPDATE OR DELETE, FOR EACH ROW.audit_logs_immutable_truncate— BEFORE TRUNCATE, FOR EACH STATEMENT (PostgreSQL 은 row 트리거를 UPDATE / DELETE 에서만 발사 — TRUNCATE 는 BEFORE-row 를 우회하므로 테이블 wipe 경로에는 별도 statement-level 가드가 필요).
super-admin 이 raw
psql로UPDATE audit_logs ...,DELETE FROM audit_logs ...,TRUNCATE TABLE audit_logs를 실행하면ERROR: audit_logs is append-only (TG_OP=…)와 함께 트랜잭션이 abort 됩니다. INSERT 는 영향받지 않으므로 리스너 경로는 정상 동작합니다.
이 트리거들은 PR #44 가 로드맵으로 문서화했던 defense-in-depth 갭을 닫습니다. 알려진 잔여 우회: 기본 설치에서는 마이그레이션 role 과 런타임 앱 role 이 동일한 PostgreSQL role (trustedoss) 입니다. 이 role 이 함수와 트리거를 소유하므로 DROP TRIGGER / ALTER FUNCTION ... OWNER 를 통해 "DROP TRIGGER → mutate → re-CREATE TRIGGER" 우회가 가능합니다. Phase 7 / 8 강화 PR 에서 런타임 role 과 마이그레이션 role 분리(trustedoss_app 은 audit_logs 에 DML 만 + trustedoss_owner 는 마이그레이션) 가 예정되어 있으며, 분리 후에는 런타임 앱에서 우회 불가능. 그 전까지 우회는 관측 가능합니다 — DROP TRIGGER 는 DDL 문이라 pg_event_trigger (향후 audit-of-audit 강화) 와 두-운영자 retention purge 의 운영자 세션 로그가 포착합니다.
무엇이 기록되는가
인증된 모든 POST, PATCH, PUT, DELETE가 정확히 하나의 항목을 생성합니다. 읽기 엔드포인트(GET)는 기록하지 않습니다. SBOM 내보내기는 structlog sbom_exported 이벤트를 발신하지만 현재 릴리스에서는 audit_logs 행을 생성하지 않습니다 — 내보내기를 감사 테이블에 통합하는 것은 로드맵 항목입니다.