Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Lint: AuditLogger.Emit inside per-invocation scope should be EmitFromContext #175

Closed
Labels
enhancementNew feature or request forge-cliAffects the forge-cli command-line tool (init, run, build, mcp commands) forge-coreAffects the forge-core library (runtime, security, types, llm, mcp, auth)

Description

Context

PR #173 fixed three audit-emit sites that were using `AuditLogger.Emit(...)` inside per-A2A-invocation scope, where they should have been using `AuditLogger.EmitFromContext(ctx, ...)`. The bug was operator-visible: the emitted events silently dropped `seq`, `trace_id`, `span_id`, and workflow-correlation fields the FWS-8 / FWS-4-Phase-4 contracts promise.

This issue is about catching the drift at PR time, not after a user reports a missing `seq` field in production.

The pattern to catch

```go
// BAD when ctx is in scope:
auditLogger.Emit(coreruntime.AuditEvent{
Event: coreruntime.AuditToolExec,
CorrelationID: hctx.CorrelationID,
TaskID: hctx.TaskID,
Fields: fields,
})

// GOOD:
auditLogger.EmitFromContext(ctx, coreruntime.AuditEvent{
Event: coreruntime.AuditToolExec,
CorrelationID: hctx.CorrelationID,
TaskID: hctx.TaskID,
Fields: fields,
})
```

The `Emit` form is genuinely the right call outside an invocation scope (auth callback, scheduler tick, MCP startup, subprocess egress proxy). The catch is: inside a function that takes a `context.Context` parameter (or has a ctx local in scope), `Emit` is almost always a bug.

Three options, increasing complexity

Option A — custom analyzer using `go/analysis` framework

Write a small `golang.org/x/tools/go/analysis` Analyzer that flags any call to `*coreruntime.AuditLogger.Emit` inside a function where a `context.Context`-typed value is in scope. Plug into the existing `golangci-lint` config via the `gocritic` or `custom` mechanism.

Pros: catches the exact pattern. Author-time feedback.
Cons: ~50-100 lines of analyzer code. New maintenance surface.

Option B — runtime assertion in dev / test mode

Add a debug-build assertion: `AuditLogger.Emit` panics (or logs a loud warning) when called inside a goroutine that holds a context with a `SequenceCounter` installed. Build tag `-tags=auditstrict` or env `FORGE_AUDIT_STRICT=1`.

Pros: no analyzer to maintain. Catches at test time.
Cons: only catches code paths that the test suite exercises. The user's specific `tool_exec` bug WAS reachable via the test suite (BeforeToolExec hook is fired in existing tests) but the test suite didn't assert on `seq`.

Option C — review checklist + grep guard in CI

Add a pre-commit/CI grep that flags `auditLogger.Emit\(` (note: not `EmitFromContext`) and requires a `//nolint:audit-emit` annotation with justification. Match the four documented out-of-scope sites in PR #173 by annotating them inline.

```bash

.github/workflows/lint.yaml (or scripts/lint-audit-emit.sh)

matches=$(grep -rn 'auditLogger.Emit(' forge-cli/ forge-core/ \
| grep -v 'EmitFromContext' \
| grep -v '//nolint:audit-emit')
if [ -n "$matches" ]; then
echo "audit emit without justification:"
echo "$matches"
exit 1
fi
```

Pros: ~10 lines. Zero new tooling. Forces every `Emit` call to be justified in code.
Cons: false positives (the four legit sites need annotations). Lower precision than a real analyzer.

Recommendation: Option C first, then Option A if it pays off

Start with Option C — annotate the four documented out-of-scope sites with `//nolint:audit-emit` + a brief comment explaining why, add the grep guard to CI. That gets us the regression protection with ~10 lines of CI + 4 inline annotations.

If the grep grows brittle (e.g., when channel plugins or forge-skills start emitting audit events with their own AuditLogger embedding), revisit Option A and write the real analyzer.

Files that would change with Option C

File Change
`scripts/lint-audit-emit.sh` (new) or `.github/workflows/lint.yaml` The grep guard
`forge-cli/runtime/runner.go` Inline annotations on the four legit `Emit` call sites (auth callback, egress proxy, MCP conflict, scheduler dispatcher)
`docs/security/audit-logging.md` One-paragraph note describing the contract ("Emit inside invocation scope is a bug — use EmitFromContext")

Annotation format

```go
//nolint:audit-emit // Auth middleware runs before the seq counter is
// installed on ctx; using EmitFromContext here would still produce a
// seq-less event. See issue #174 for the planned fix.
auditLogger.Emit(coreruntime.AuditEvent{
Event: coreruntime.EventAuthVerify,
...
})
```

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request forge-cliAffects the forge-cli command-line tool (init, run, build, mcp commands) forge-coreAffects the forge-core library (runtime, security, types, llm, mcp, auth)

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

        AltStyle によって変換されたページ (->オリジナル) /