Evidence Locker
Status: Draft (2025-11-26) — DOCS-FORENSICS-53-001.
Audience: operators and compliance owners who need to capture, retain, and verify forensic evidence produced by Stella Ops.
Purpose
The Evidence Locker captures forensic artefacts (bundles, logs, attestations) in a WORM-oriented store and can request retention and legal-hold controls. Effective provider-enforced WORM behavior depends on the certified deployment profile described below.
Storage model and provider requirements
- Object storage is preferred. A production WORM profile must provide and prove:
- Bucket per tenant (or tenant prefix) and immutable retention policy.
- Server-side encryption (KMS) and optional client-side DSSE envelopes.
- Versioning enabled; deletion disabled during legal hold.
- Index (PostgreSQL) for metadata:
artifactId,tenant,type(bundle/attestation/log),sha256,size,createdAt,retentionUntil,legalHold.provenance: source service, job/run ID, DSSE envelope hash, signer.immutability:worm=true|false,legalHold=true|false,expiresAt.
RustFS/MinIO and other S3-compatible endpoints
EvidenceLocker uses the AWS S3 protocol adapter; it does not carry a dedicated RustFS or MinIO driver. Configure an external endpoint explicitly:
EvidenceLocker:
ObjectStore:
Kind: AmazonS3
EnforceWriteOnce: true
AmazonS3:
BucketName: stellaops-evidence
Region: eu-central-1 # SigV4 signing region
ServiceKind: S3Compatible
Endpoint: https://rustfs.internal:9000
ForcePathStyle: true # false requires wildcard/bucket DNS
Tls:
VerifyServerCertificate: true
CustomCaCertificatePath: /run/stellaops/trust/rustfs-ca.pem
ClientCertificatePath: /run/stellaops/mtls/evidencelocker-client.pfx
ClientCertificatePassword: null # inject via protected configuration
For a PEM client identity, set ClientCertificatePath to the certificate and ClientCertificateKeyPath to its private key. All configured certificate paths must be absolute and mounted into both EvidenceLocker hosts. Outside the Development/Testing local harness, plaintext endpoints and disabled server certificate verification fail startup. Access/secret keys continue to use the AWS SDK credential chain and must be supplied through protected runtime configuration.
The automated contract tests prove client transport construction without network access. A dated local interoperability run also proves basic bucket creation and object write/existence/read/delete behavior against SeaweedFS’s S3 API. That is not a RustFS or MinIO certification: a deployment must still verify its exact server version, HTTPS trust/mTLS, bucket provisioning, versioning, legal hold, and S3 Object Lock behavior before claiming WORM-grade operation.
To repeat the basic smoke proof, run S3CompatibleObjectStoreInteropTests with EVIDENCELOCKER_S3_COMPAT_ENDPOINT, EVIDENCELOCKER_S3_COMPAT_ACCESS_KEY, and EVIDENCELOCKER_S3_COMPAT_SECRET_KEY set to disposable test credentials. The test creates a uniquely named bucket, round-trips one object, and removes both; it deliberately does not exercise retention or Object Lock.
The local Compose service named rustfs currently runs SeaweedFS and uses a SeaweedFS-specific entrypoint, command, provisioning files, and health check. Do not substitute a RustFS or MinIO image through that legacy service variable and infer compatibility from container startup.
Scope note (2026-07-20): the project-level RustFS/MinIO certification effort was retired by owner decision (Decision B,
SPRINT_20260711_016). Stella Ops does not claim RustFS or MinIO WORM certification. The probe below remains supported as a per-deployment qualification tool: run it against your own approved, digest-pinned provider before claiming WORM-grade operation for that installation.
For a disposable provider that is provisioned with Object Lock support, the same test class has an explicit capability probe. In addition to the endpoint and credentials, set:
EVIDENCELOCKER_S3_COMPAT_PROVIDER=RustFS # or MinIO
EVIDENCELOCKER_S3_COMPAT_PROVIDER_VERSION=<exact-version>
EVIDENCELOCKER_S3_COMPAT_PROVIDER_IMAGE_DIGEST=sha256:<64-hex>
EVIDENCELOCKER_S3_COMPAT_OBJECT_LOCK_PROBE=true
EVIDENCELOCKER_S3_COMPAT_FORCE_PATH_STYLE=true
EVIDENCELOCKER_S3_COMPAT_TLS_PROFILE=https # or mtls
EVIDENCELOCKER_S3_COMPAT_VERIFY_SERVER_CERTIFICATE=true
EVIDENCELOCKER_S3_COMPAT_CUSTOM_CA_CERTIFICATE_PATH=<absolute-path-if-needed>
EVIDENCELOCKER_S3_COMPAT_CLIENT_CERTIFICATE_PATH=<absolute-pfx-or-pem-path-for-mtls>
EVIDENCELOCKER_S3_COMPAT_CLIENT_CERTIFICATE_KEY_PATH=<absolute-pem-key-path-if-needed>
EVIDENCELOCKER_S3_COMPAT_CLIENT_CERTIFICATE_PASSWORD=<protected-pfx-secret-if-needed>
The probe creates an Object-Lock-enabled bucket, writes through EvidenceLocker with one-day Governance retention and legal hold, reads both controls back, verifies that deleting the protected version is rejected, and uses privileged disposable-test credentials to turn off the hold and bypass Governance retention for cleanup. Never run it against a production bucket. The certification lane accepts only the exact provider identifiers RustFS and MinIO, rejects mutable latest/unknown versions, requires an immutable image digest, HTTPS, and server-certificate verification. Set the TLS profile to mtls only when the endpoint requires a client identity; that profile also requires the client-certificate path. A benchmark metadata image, a stale data volume, or a provider name supplied only through an environment variable is not provider provenance: record the approved image digest and independently verify that the endpoint was launched from that artifact. Configured legal-hold failure is fail-closed: the evidence write reports the provider error rather than silently succeeding without the requested hold.
Ingest rules
- Only append; no overwrite. Use content-addressed paths:
tenant/<type>/<sha256>/<filename>. - Every artefact must include:
- SHA256 (and size)
- DSSE or Sigstore bundle for attestations
- Source metadata (service, run ID)
- Retention/hold directives
- Reject uploads lacking tenant, hash, or provenance.
Retention & legal hold
- Default retention per tenant (e.g., 180d); configurable via policy.
- Legal hold flag prevents deletion/expiry; requires dual-approval to clear.
- Expiry job runs daily; deletes only items past
retentionUntiland not on hold; produce audit log.
Access & audit
- RBAC scopes:
evidence:read,evidence:create,evidence:hold(see the canonical scope catalog inStellaOpsScopes.cs). - All actions logged with tenant, actor, artefact ID, hash, and result.
- A manifest endpoint lists artefacts by tenant/time/type with pagination.
Verification
locker verify <artifactId>:- Recompute SHA256
- Verify DSSE/Sigstore signature and signer against trust roots
- Return status + provenance summary
- Periodic background verification: sample N artefacts/day; emit
locker.verify.failedevents on mismatch.
Bundle format (minimum)
- Manifest (
manifest.json):artifactId,tenant,type,hashes,createdAt,retentionUntil,legalHoldprovenance(source, signer, dsseEnvelopeHash)pathswith bytes and hashes
- Data files under
data/(gzip/zstd optional) - Signature under
signatures/if provided
Migration from legacy storage
- Export legacy artefacts with hashes; import via Locker API with retention/hold fields.
- Maintain mapping of legacy IDs → new
artifactIdin index. - Keep legacy store read-only until verification completes.
Operational runbook (abridged)
- On ingest failure: log and return 400 with reason; do not partially store.
- On verification failure: mark artefact
suspect=true, emit event, and investigate signer and storage integrity. - For legal hold requests: require ticket/approval metadata; set
legalHold=trueand extend retention.
References
- Evidence Locker architecture — full storage, retention, and verification design.
- Evidence Pack schema — the pack format, API, and CLI for replayable evidence bundles.
- DSSE and Sigstore bundle format: see the Attestor module (Provenance/Signer consolidated into Attestor), in particular
bundle-format.mdanddsse-roundtrip-verification.md. - Export Center mirror imports and policy attestations feed artefacts into the locker.
