feat(audit): stamp org_id / workspace_id on every event from env + headers (closes #157)#158
Merged
Merged
Conversation
…aders (closes #157) Two-layer tenancy stamp so SIEM consumers can filter the audit stream by tenancy without joining against the auth_verify row. Same posture as the existing FWS-2 workflow correlation system. Layer 1 (deployment-time): FORGE_ORG_ID / FORGE_WORKSPACE_ID env vars → AuditLogger.WithTenancy. Read once at runner startup. Stamps every emit, including startup banners (agent_card_published, policy_loaded, audit_export_status) and per-invocation events. Layer 2 (per-request override): X-Forge-Org-ID / X-Forge-Workspace-ID headers → TenancyContext in ctx via TenancyContextFromHTTPHeaders. Per-request override for multi-tenant routing — one Forge agent serves many workspaces, the orchestrator picks per request. Inside the request scope, ctx beats the static stamp. Precedence at emit (highest first): 1. Explicit OrgID/WorkspaceID set on the AuditEvent. 2. TenancyContext from ctx (header override). 3. AuditLogger's static stamp (env). Both fields independent: setting only one header lets the static stamp fill in the other. Files: - forge-core/runtime/tenancy.go (new) — TenancyContext, header helpers, context plumbing. Mirrors workflow.go. - forge-core/runtime/audit.go — top-level OrgID/WorkspaceID with omitempty; WithTenancy method; ctx-first / logger-fallback in EmitFromContext; deployment-stamp pass in plain Emit so startup banners participate. - forge-cli/runtime/runner.go — read env at startup and call WithTenancy; pick up override headers in REST handlers and auth callback. - forge-cli/server/a2a_server.go — pick up override headers at the JSON-RPC dispatch boundary alongside the workflow context. - docs/security/tenancy.md (new) — full reference: layers, precedence, examples, backwards compat, distinction from auth-derived org_id. - docs/security/audit-logging.md — new Tenancy stamping section. - forge-core/runtime/tenancy_test.go (new) — covers header parsing, IsZero, static stamp on plain Emit, header beats static, partial header uses static for other field, explicit event value beats all fallbacks, ApplyToHTTPHeaders for outbound propagation, omitempty back-compat. No schema version bump — additive optional fields are schema- compatible per the documented policy.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two-layer tenancy stamp so SIEM consumers can filter the Forge audit stream by tenancy without joining against the
auth_verifyrow. Same posture as the existing FWS-2 workflow correlation system — vendor-neutral headers + deployment env vars.Layers
AuditEvent.OrgID/AuditEvent.WorkspaceIDX-Forge-Org-ID/X-Forge-Workspace-IDheadersFORGE_ORG_ID/FORGE_WORKSPACE_IDenv varsEach field is resolved independently. A request that overrides only
X-Forge-Org-IDstill lets the env stamp fill inworkspace_id.Deployment example
Every event — startup banners (
agent_card_published,policy_loaded,audit_export_status) AND per-invocation events (session_start,llm_call,guardrail_check,invocation_complete) — gets\"org_id\":\"org_abc123\",\"workspace_id\":\"ws_xyz789\"at the top level.SIEM filter: `org_id = "org_abc123" AND workspace_id = "ws_xyz789"`.
Multi-tenant routing example
The orchestrator picks per request; the override headers shadow the env stamp inside the request scope.
Distinct from
auth_verify.fields.org_idThe auth provider chain resolves
Identity.OrgIDfrom the bearer token (user's federated identity) and continues to stamp it on `auth_verify.fields.org_id` for back-compat. The new top-level `org_id` is the operator's declared deployment tenancy — trusted because the deployment / orchestrator set it. Both can coexist on the same `auth_verify` event when they're different identifiers.Files
Test plan
Unit tests in `forge-core/runtime/tenancy_test.go`:
End-to-end (manual):
Local checks:
Schema impact
Additive only — both keys use `omitempty`. No `AuditSchemaVersion` bump. Existing consumers that ignore unknown keys keep working unchanged.
Out of scope
Closes #157