Skip to content

fix(audit): flush pending audit logs before exit - #215

Open
a-palamarchuk wants to merge 1 commit into
coder:mainfrom
a-palamarchuk:fix/audit-flush-on-exit
Open

a-palamarchuk wants to merge 1 commit into
coder:mainfrom
a-palamarchuk:fix/audit-flush-on-exit

Conversation

@a-palamarchuk

Copy link
Copy Markdown

Fixes coder/registry#1019 (filed in the registry repo, but the bug is here).

Problem

SetupAuditor started SocketAuditor.Loop with go socketAuditor.Loop(ctx) on the CLI context (inv.Context()). That context is never canceled before the process exits, and nothing waited for the loop.
When the wrapped command exits, run.Run returns and the process ends. On a non-zero child exit this goes through os.Exit in cli/cli.go.
Any logs still queued for the next batch (up to 5 seconds or 10 requests) are lost. The local log keeps them, but they never reach the workspace agent or coderd.
For short commands, which are the main Agent Firewall use case, every record is lost.

Loop already drains and flushes when its context is canceled (TestSocketAuditor_Loop_FlushesOnContextCancel). Nothing triggered that path at exit.

Change

  • SetupAuditor now also returns a close function. It cancels the loop and waits up to 5 seconds for the final drain and flush, so an unresponsive agent cannot block exit.
  • The wait uses its own deadline (context.WithoutCancel), so it still works when the caller's context is already canceled.
  • A small runSocketAuditor helper owns the goroutine and its done channel. SocketAuditor itself is unchanged.
  • The nsjail and landjail RunParent functions defer the close. It runs after Run has stopped the proxy, so no audit events arrive after the final flush, and before the CLI exits.

SetupAuditor is exported and its signature changes. The only callers are the two parents here; coder/coder does not call it.

Testing

New tests:

  • TestSetupAuditor_CloseSendsQueuedLogs: a real unix-socket listener. One request is audited, then close is called; the log must already be on the socket when close returns.
  • TestRunSocketAuditor_StopIsBoundedByContext: a dial that never returns; stop returns context.DeadlineExceeded at the deadline, then nil once the agent responds.

Both fail if close does not wait (checked with a temporary mutation), and both pass 20 times under -race.

End to end with landjail, following the issue's Variant B (a denied and an allowed request back to back, then exit). The agent socket is a small listener that prints every record it decodes:

===== main (3 runs, identical)
  curl kubernetes.io (deny): 403
  curl example.com (allow): 200
  agent received 0 of 2 records

===== this branch (3 runs, identical)
  curl kubernetes.io (deny): 403
  curl example.com (allow): 200
  agent received 2 of 2 records
    agent received: allowed=false GET http://kubernetes.io/
    agent received: allowed=true GET http://example.com/

The non-zero exit path (sh -c '...; exit 3') behaves the same: main delivers 0 of 1 records, this branch 1 of 1, and the exit code is still 3. Wall time is 208ms vs 221ms.

AI disclosure

I used Claude Code to trace the bug and draft the fix and tests. I reviewed the diff and ran the tests and the reproduction above myself.

SetupAuditor started SocketAuditor.Loop on the CLI context, which is
never canceled before the process exits, and nothing waited for the
loop. When the wrapped command exits, boundary returns and the process
ends, so logs still queued for the next batch (up to 5 seconds or 10
requests) never reach the workspace agent or coderd, while the local
log keeps them. For short commands every record is lost.

SetupAuditor now also returns a close function. It cancels the loop,
which already drains and flushes on cancellation, and waits up to 5
seconds for it to finish so an unresponsive agent cannot block exit.
The nsjail and landjail parents defer it, so it runs after the proxy
has stopped and before the CLI exits.

Fixes coder/registry#1019.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Coder boundary: audit records in the final flush window are dropped from the coderd forward on shutdown (local log keeps them)

1 participant