The Docker Action creates schema-v3 audit envelopes for an exact repository snapshot. The recommended deployment separates untrusted pull-request analysis from privileged publication.
| Stage | Trigger | Authority | Executes PR code |
|---|---|---|---|
| Analyze | pull_request |
contents: read; no secrets or OIDC |
Yes |
| Verify | workflow_run |
Read-only GitHub metadata and artifact access | No |
| Attest | verified result | OIDC and attestations write only | No |
| Publish | verified result | Pull-request comment write only | No |
Start from
mastermind-audit-pr.yml and
mastermind-audit-publish.yml.
- Content integrity: the Mastermind Canonical JSON v1 bytes match their SHA-256 digest. Replacing both the manifest and digest can still create a different internally consistent envelope.
- Provenance authenticity: a detached Ed25519 signature validates under a trusted, non-revoked key ID. This proves control of that key, not signer identity, signing time, or whether signing preceded key compromise.
- Policy acceptance: every configured trust anchor passes. A repository
anchor requires exact
owner/repo, full baseline and head OIDs, trusted-root recomputation, andworktree_clean:true. A signature anchor requires the signature, public key, and a trusted non-revoked key-ID allowlist. Partial or empty policy fails withincomplete_trust_anchororno_trust_anchor. - GitHub artifact attestation: the publication workflow verified the deterministic archive and statement. This does not prove that PR analysis ran in a trusted environment or that its findings are correct.
--integrity-only is a diagnostic. It sets authenticity and policy to
not_evaluated; the Docker Action, privileged pr-comment, and publication
workflow do not use it.
The envelope digest covers the canonical manifest, not pretty-printed storage. Canonical JSON uses UTF-8, sorted object keys, array order, minimal JSON string escaping, explicit i64/u64 integers only, and no trailing newline. Strict readers reject duplicate or unknown fields, floats, and unknown schemas, algorithms, or canonicalization identifiers.
The manifest binds repository identity, full baseline and HEAD, clean-worktree state, tool/config/index metadata, spec and executor-report paths and digests, normalized name/status entries, binary diff digest, verdict, file scope, claims, discrepancies, snapshot drift, logical mmcg queries, recorded verify commands, and summary.
Detached signatures sign a domain-separated canonical statement containing:
domain = mastermind/audit-envelope-signature/v1
signature_schema = 1
signature_algorithm = ed25519
key_id = sha256:<public-key digest>
envelope_schema = 3
hash_algorithm = sha256
canonicalization = mastermind-cjson-v1
manifest_digest = sha256:<manifest digest>
Key files contain one base64 line. The private file encodes a 32-byte Ed25519
seed and must have mode 0600 on Unix; the public file encodes 32 bytes. Keep
trusted and revoked key-ID allowlists in independently reviewed policy. For
rotation, add the new trusted ID, deploy verifiers, rotate signing, then revoke
the old ID while retaining the policy needed to verify historical evidence.
mastermind audit-spec .mastermind/tasks/005-example/spec.md \
--since 1111111111111111111111111111111111111111 \
--root . --bundle audit.bundle.json
mastermind audit sign audit.bundle.json \
--private-key audit-ed25519.seed \
--signature audit.bundle.sig.json
mastermind audit verify audit.bundle.json \
--root . \
--expected-repository owner/repo \
--expected-baseline 1111111111111111111111111111111111111111 \
--expected-head 2222222222222222222222222222222222222222
mastermind audit verify audit.bundle.json \
--signature audit.bundle.sig.json \
--public-key audit-ed25519.pub \
--require-signature \
--trusted-key-id sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaSnapshot and signature modes can be supplied together; every supplied policy must pass. A present signature is never ignored.
The repository-root action.yml defines a Docker Action with these inputs:
root: repository-relative root contained belowGITHUB_WORKSPACE;sinceandexpected-baseline: the same full lowercase baseline OID;bundle-dir: a new, non-symlink repository-relative output directory;expected-repository: exact GitHubowner/repo;expected-head: full lowercase head OID;require-clean-worktree: must remaintruefor publication.
The Action outputs the verified bundle directory and aggregate result JSON path. Inputs are passed as data; they are not evaluated, sourced, or rendered into shell syntax.
The Action audits only canonical task folders changed between since and
HEAD. Every selected task must include spec.md and a valid
executor-report.md; missing evidence fails the run. Historical task folders
that were not changed in the pull request are not re-audited against the new
baseline. This keeps a PR scoped to the contract it introduces or updates and
prevents an empty report from producing publishable evidence.
The unprivileged PR workflow uses the Action from its checked-out PR tree. That executes untrusted code but receives no secrets, OIDC, or write permission. The privileged workflow contains its strict schema-v3 verifier inline, so the verifier implementation and identity are bound to the independently allowlisted trusted workflow blob. The examples contain no unresolved Action or verifier placeholder. External Actions remain pinned to audited 40-character commits.
The PR workflow triggers only on pull_request, checks out the exact head with
credentials disabled, and has only contents: read. It uploads one
attempt-specific artifact. Uploaded PR numbers, SHAs, workflow strings, and
digests remain hostile claims.
The publication workflow has no checkout. Its read-only verify job keys API lookups to the source run ID and attempt, then checks repository ID/name, event, conclusion, workflow path/blob, independent PR/base/head association, and one server-owned artifact ID/digest/size. Extraction is capped at 64 MiB total, 16 MiB per regular file, 256 files, and 240-byte relative paths. Links, devices, traversal, nested archives, and extra names are rejected. Only the trusted verify implementation runs before the deterministic statement/archive is created.
Only the attestation job has id-token: write and attestations: write. Only
the publication job has pull-requests: write; it rechecks PR head and artifact
identity, then updates at most one constant-marker comment owned by
github-actions[bot]. Neither job executes commands stored in an envelope.
Treat every workflow_run download as hostile until this chain completes. If
GitHub cannot independently return the workflow blob, PR association, server
artifact digest, or exact run attempt, fail closed.
For every pinned Action or OCI base:
- Read the upstream release notes and security advisories.
- Resolve the release tag from the authoritative upstream repository or registry.
- Verify the full commit or multi-architecture manifest digest independently.
- Review the diff from the old pin and update the allowlist in
scripts/validate.pyin the same change. - Run the full Rust tests, repository validator, YAML parser check, and Docker build.
Never shorten a commit or pin a mutable tag such as v7, main, or master.
Artifact attestation availability and verification behavior depend on repository visibility and the organization's GitHub plan and policy. Private/internal support and API access can differ from public repositories. Confirm current GitHub documentation and organization settings before making attestations a required release gate. The local schema-v3 verifier performs no network lookup; the trusted publication workflow and GitHub tooling remain responsible for issuer, workflow, repository, and ref verification.