_read gated on may_read alone and never called permits_tenant, so any reader credential could read every tenant through /v1/events, /v1/events/<id>, /v1/dead-letters and /v1/secret-findings. Deployment bounded the exposure -- the only production sender holds may_read: false -- but the boundary was not in the code, which is the difference between E2 and E1 on the tenancy posture enforcement ladder. Two rules, because the surfaces divide cleanly. Event reads are filtered to the tenants the credential may act for. Surfaces with no tenant key to filter on -- stats, integrity, dead letters, secret findings -- require full tenant scope and are refused rather than served instance-wide facts to a scoped reader. A cross-tenant fetch returns 404 rather than 403. A distinguishable forbidden would confirm that an event id exists and which tenant holds it, turning the read surface into an existence oracle. Correlation lookup is filtered rather than refused, since a correlation id legitimately spans tenants. _readable_by fails closed: a record with no tenant is readable only at full scope. Three existing tests read instance-wide surfaces with a scoped credential, which this makes a 403; bound_app now carries an unrestricted operator identity and those reads use it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|---|---|---|
| .claude/rules | ||
| .forgejo/workflows | ||
| audit_core | ||
| data/capability | ||
| deploy | ||
| docs | ||
| evidence | ||
| registry | ||
| scripts | ||
| spec | ||
| tests | ||
| workplans | ||
| .custodian-brief.md | ||
| .dockerignore | ||
| .gitignore | ||
| .repo-classification.yaml | ||
| AGENTS.md | ||
| CLAUDE.md | ||
| Containerfile | ||
| INTENT.md | ||
| LICENSE | ||
| Makefile | ||
| pyproject.toml | ||
| README.md | ||
| SCOPE.md | ||
| WORK-RECORDS.md | ||
Reliable multi-tenant auto setup audit capability
Production on railiance01 (AUDIT-WP-0005): PostgreSQL custody, digest-pinned
image, operator procedures in
docs/operator-runbook.md. Manifests live in
deploy/.
Backend contract
The pluggable backend interface, event schema (audit-core.event.v1alpha1),
retention policy, and migration path from the mock file backend are documented
in docs/audit-backend-contract.md.
Development Mock Backend
The first implementation is intentionally tiny: a replaceable audit interface with a mock file backend.
By default it writes JSONL audit events to:
/tmp/audit-core/audit-YYYYMMDDTHH.jsonl
Files older than 7 days are removed when the backend writes or when cleanup is run explicitly. This backend is for local integration and bootstrap wiring. It is not durable audit custody.
Example:
python3 -m audit_core emit \
--source openbao \
--action openbao.authenticated_readiness_proof \
--resource openbao/openbao-0 \
--outcome success \
--detail file_audit_visible=true \
--detail backend=mock-file
Cleanup:
python3 -m audit_core cleanup
Make targets:
make test
make mock-audit-smoke
make mock-audit-cleanup
Environment:
AUDIT_CORE_MOCK_DIR: override the output directory.AUDIT_CORE_MOCK_RETENTION_DAYS: override the default 7-day cleanup window.