Skip to content

Stage 1 — Security & Guardrail Hardening#30

Merged
atc964 merged 3 commits into
mainfrom
release/stage-1-hardening
Jul 14, 2026
Merged

Stage 1 — Security & Guardrail Hardening#30
atc964 merged 3 commits into
mainfrom
release/stage-1-hardening

Conversation

@atc964

@atc964 atc964 commented Jul 14, 2026

Copy link
Copy Markdown
Collaborator

Stage 1 — Security & Guardrail Hardening

First release in a staged re-architecture of the agent. Ships only safety-critical fixes; fully backward-compatible — no API, schema, or configuration changes.

  • Authentication: API-key and bearer credentials are now correctly read from request headers. Previously they were bound as query params and ignored, so authenticated requests were treated as anonymous (PUBLIC tier) and revoked keys were accepted.
  • Audit integrity: money-relevant events (bookings, negotiation concessions, approvals) can no longer be silently dropped — delivery is fail-closed with a durable fallback log.

Later stages introduce a shared contract library and structural consolidation (in progress on integration branches).

🤖 Generated with Claude Code

atc964 and others added 3 commits July 13, 2026 19:41
The _get_optional_api_key_record dependency in interfaces/api/main.py
declared `authorization` and `x_api_key` with plain `= None` defaults,
so FastAPI bound them as query parameters. Real `Authorization: Bearer`
and `X-Api-Key` request headers never reached the validator: every
buyer request on the ~26 endpoints routed through this wrapper was
treated as anonymous (PUBLIC-tier pricing), and invalid, revoked, or
expired keys were silently accepted as anonymous instead of rejected
with 401.

Fix: mirror the Header(None) / Header(None, alias="X-Api-Key")
defaults from ad_seller.auth.dependencies.get_api_key_record, which
the wrapper delegates to.

Adds tests/unit/test_auth_header_binding.py exercising the real app
via httpx.ASGITransport (no dependency override for the auth
dependency): valid X-Api-Key and Bearer credentials resolve to the
key's identity tier, invalid/revoked/expired keys return 401,
credential-less requests keep the anonymous PUBLIC path, and the
auth params are no longer exposed as query parameters.

bead: ar-70x0

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Event emission was fail-open across the board: emit_event swallowed
every exception, so audit-trail events for money decisions (proposal
accept/reject/counter, deal creation/registration, approval decisions,
negotiation rounds) could silently drop when the event bus failed.

Surgical hotfix, not a bus redesign:

- AUDIT_EVENT_TYPES (events/models.py): the money-relevant event types
  whose loss would break the audit trail.
- events/audit_fallback.py: append-only JSONL fallback writer with
  fsync-on-write; path from settings (audit_fallback_path, default
  data/audit_fallback.jsonl).
- emit_event (events/helpers.py): on bus failure for an audit-class
  event, write the event to the fallback JSONL and continue; if the
  fallback write also fails, re-raise so the transaction surfaces the
  error (fail-closed). Non-audit events keep the existing fail-open
  behavior unchanged.

bead: ar-z64h

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…origin/fix/audit-fail-closed' into release/stage-1-hardening
@atc964
atc964 merged commit aaeccb6 into main Jul 14, 2026
@atc964
atc964 deleted the release/stage-1-hardening branch July 14, 2026 16:33
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