Skip to content

security: sign this service's own native audit:events payloads (HIPAA PR3b) - #8

Merged
man4ish merged 2 commits into
mainfrom
security/hipaa-audit-producer-signing
Aug 14, 2026
Merged

security: sign this service's own native audit:events payloads (HIPAA PR3b)#8
man4ish merged 2 commits into
mainfrom
security/hipaa-audit-producer-signing

Conversation

@man4ish

@man4ish man4ish commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator

Problem

This service's own native producer (AuditLogger.log(), used by api/routes_audit.py for platform-admin API actions) is the sixth and last of six real audit:events producers the HIPAA audit-integrity assessment found unsigned.

Fix

audit/logger.py::AuditLogger.log() now signs the exact data string before publishing:

data = json.dumps(payload)
fields = {"data": data}
service = payload.get("service")
if service:
    fields["sig"] = sign_audit_event(service, data, AuditConfig.EVENT_SIGNING_SECRET)
await self.redis.xadd(AuditConfig.STREAM_NAME, fields, ...)

Unlike the other five producers (separate deployables that each hand-port sign_audit_event), this one imports it directly from audit.signing — same repo, no drift risk. service is read from the already-serialized payload (not event.service) so the signature always covers the identity actually present in the signed bytes.

Secret: AuditConfig.EVENT_SIGNING_SECRET (audit/config.py) — defined by PR2 specifically for this eventual purpose. No new secret, no new config path.

Tests

All 10 pre-existing tests/test_logger.py tests pass unmodified. 7 new tests in tests/test_logger_signing.py: signs the exact wire string (verified against the real verify_audit_event, not a re-derived value), wrong-secret and tampered-data both correctly fail verification, missing-service still publishes unsigned rather than dropping the event, secret non-leakage on a forced xadd exception. Full suite: 249 passed (242 baseline + 7 new), 0 regressions.

Closes producer-side signing platform-wide

This is the sixth and final PR of HIPAA PR3b — every real audit:events producer now signs (omnibioai-api-gateway#10, omnibioai-tes#23, omnibioai-workflow-bundles#25, omnibioai-control-center#43, omnibioai-rag#21, and this PR).

Non-scope

No SDK redesign, no GHCR/image work, no consumer changes beyond this repo's own already-merged PR2, no audit schema changes, no unrelated fixes.

🤖 Generated with Claude Code

Co-Authored-By: Claude Sonnet 5 noreply@anthropic.com

man4ish and others added 2 commits August 14, 2026 14:09
HIPAA PR3b: closes producer-side signing for the sixth and final real
audit:events producer the HIPAA audit-integrity assessment found
unsigned -- this repo's own AuditLogger.log(), used by
api/routes_audit.py for platform-admin API actions.

Unlike every other producer (api-gateway, tes, workflow-bundles,
control-center, rag -- all separate deployables that hand-port
sign_audit_event()), AuditLogger.log() lives in the same repo as
audit.signing itself and imports sign_audit_event directly -- no drift
risk, no parallel copy to keep in sync.

data = json.dumps(payload) is computed exactly once, from the already-
model_dump()'d payload, and that exact string is both signed and
published as {"data": data, "sig": ...}. service is read from
payload.get("service") -- the identity actually present in the signed
bytes -- not event.service, so a hypothetical future divergence between
the AuditEvent object and its own serialization can never desync the
two. Secret reused unchanged: AuditConfig.EVENT_SIGNING_SECRET
(audit/config.py), defined by PR2 specifically for this eventual purpose
-- no new secret, no new config path.

Existing event schema, event types, event IDs, timestamps, and stream
name unchanged -- purely additive sig field. All 10 pre-existing
tests/test_logger.py tests pass unmodified.

7 new tests (tests/test_logger_signing.py): signs the exact wire string
(verified against the real verify_audit_event, not a re-derived value),
wrong-secret and tampered-data both correctly fail verification, missing-
service still publishes unsigned rather than dropping the event, and
secret non-leakage on a forced xadd exception. Full suite: 249 passed (242
baseline + 7 new), 0 regressions.

This closes producer-side signing across all six real audit:events
producers on the platform (api-gateway, tes, workflow-bundles,
control-center, rag, and this repo's own native producer) -- each in its
own separate PR per repo, this being the sixth and last.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Lint-only, no behavior change. Fixes the 3 ruff errors this PR's own CI
run reported once audit/logger.py entered the changed-file set (ruff
lints changed files whole, not just changed lines, so this also caught
two pre-existing issues the file already had before PR3b's signing
change touched it):

  - I001: import block un-sorted -- auto-fixed via `ruff check --fix`,
    grouping stdlib/third-party/first-party with blank lines between,
    matching this repo's implicit isort convention elsewhere.
  - F401: `typing.Optional` imported but unused -- pre-existing, never
    referenced anywhere in this file even before PR3b; removed.
  - BLE001: blind `except Exception` -- pre-existing, and deliberate:
    this is the same "NEVER break core system" fire-and-forget contract
    every audit-write call site in this platform already documents for
    itself (worker/main.py's three identical except-Exception-print
    blocks, audit_service.log_event elsewhere). Suppressed with a
    minimal inline `# noqa: BLE001` plus a one-line reason, matching
    this repo's existing noqa style (alembic/env.py, tests/conftest.py's
    `# noqa: CODE -- reason` comments) -- not a broad/file-level
    suppression, and no repo-wide BLE001 precedent existed to follow, so
    this establishes the narrowest one for this exact line only.

Zero signing-behavior change: data/sig construction, secret source,
event schema, and control flow are byte-for-byte identical to before
this commit -- confirmed by re-running tests/test_logger_signing.py (7
tests) and tests/test_logger.py (10 tests) unmodified, all still
passing. Full suite: 249 passed, 0 regressions (unchanged from the
commit this fixes).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@man4ish
man4ish merged commit ab2790f into main Aug 14, 2026
2 checks passed
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.

1 participant