Skip to content

Audit Log

Sia edited this page May 31, 2026 · 2 revisions

Audit Log

Operational audit trail. Every important admin action is recorded with timestamp, user, IP, result, and an optional JSON detail field. View at /audit (left nav → "감사 로그").

What gets recorded

Action Trigger Result codes Detail JSON
auth.login POST /api/auth/login, POST /login (SSR) OK / FAIL `{"channel":"web
auth.setup First admin creation OK (none)
auth.logout POST /api/auth/logout, POST /logout OK (none)
auth.password.change POST /password OK / FAIL (none)
device.revoke POST /devices/{id}/revoke OK (none)
project.create POST /projects OK `{"sourceType":"empty
project.delete POST /projects/{id}/delete OK / FAIL (none)
build.enqueue POST /projects/{id}/builds OK (none)
build.cancel POST /projects/{id}/builds/{buildId}/cancel OK (none)
console.new POST /projects/{id}/console/new OK (none)
console.cancel POST /api/projects/{id}/claude/console/cancel OK (none)
mcp.install POST /env-setup/mcp/install OK {"selected":"id1,id2,..."}
mcp.unregister POST /env-setup/mcp/unregister OK (none)
settings.update POST /settings OK / FAIL {"error":"..."} on failure
git.token.register POST /settings/git-integrations OK (none)
git.token.delete POST /settings/git-integrations/delete OK / FAIL (none)

REST API hits that don't change state (GETs, console prompt streaming, env diagnostics) are not in the audit log — they go to the server log instead (docker compose logs -f vibe-coder-server). The audit log is strictly IAM-level, not a request log.

Schema

CREATE TABLE audit_log (
    id              VARCHAR(64)  PRIMARY KEY,
    ts              VARCHAR(64)  NOT NULL,   -- ISO-8601 UTC
    user_id         VARCHAR(64)             , -- nullable (e.g. failed login)
    device_id       VARCHAR(64)             , -- nullable
    ip              VARCHAR(64)             ,
    action          VARCHAR(64)  NOT NULL,
    resource_type   VARCHAR(64)             ,
    resource_id     VARCHAR(256)            ,
    result          VARCHAR(16)  NOT NULL,   -- OK / FAIL / DENIED
    detail          TEXT                    ,
    INDEX (ts), INDEX (action)
);

detail is always a JSON object built with kotlinx.serialization (no string interpolation — quote-safe even when input contains ").

Filtering (UI)

/audit exposes five filters as GET query parameters:

Param Example Notes
action auth.login dropdown auto-populated from distinct values
result FAIL OK / FAIL / DENIED
user <userId> the admin's internal id, not username
from 2026-05-24T00:00:00Z ISO ts; string ≥ comparison
to 2026-05-25T00:00:00Z ISO ts; string ≤ comparison

Pagination: 100 rows per page, ?p=<index> (0-based).

ISO-8601 lexicographic ordering means the simple ts text index supports range queries efficiently even though the column type is varchar.

Programmatic access (planned)

A JSON API endpoint (GET /api/audit) is on the roadmap — clients will be able to stream the log into SIEM / monitoring. For now use direct PostgreSQL access:

docker exec -it vibe-coder-postgres psql -U vibecoder -d vibecoder
\d audit_log
SELECT ts, action, result, ip, resource_id FROM audit_log ORDER BY ts DESC LIMIT 50;

Failure policy

Audit writes are wrapped in a try/catch. If the INSERT fails (e.g., PostgreSQL momentarily unavailable), the user-facing request still completes successfully and the failure is logged to stderr. Missing audit entries are a forensic gap, not a denial-of-service surface.

Rotation

No automatic rotation. The audit log accumulates indefinitely. For 1-person LAN tools this is fine for years. Manual cleanup when needed:

DELETE FROM audit_log WHERE ts < (now() - interval '180 days')::text;
VACUUM ANALYZE audit_log;

Future work: retention configuration in server.yml, optional archive table.

Privacy notes

  • No request bodies are recorded — only metadata (action / IDs).
  • Passwords / tokens are never written to detail.
  • IP is the value from RoutingCall.request.local.remoteHost — for reverse-proxy deployments this may be the proxy's IP unless the proxy strips/sets X-Forwarded-For. Honouring that header is future work.

Quick filters via URL

Useful one-liners — paste into the browser:

/audit?action=auth.login&result=FAIL                # all failed logins
/audit?action=project.delete                         # who deleted what
/audit?user=<userId>&from=2026-05-24T00:00:00Z       # one user, one day
/audit?action=git.token.register                     # PAT lifecycle

Clone this wiki locally