Attestation Format
Signatures use three nested layers, following the same standards as SLSA provenance.DSSE Envelope
The outermost layer is a DSSE (Dead Simple Signing Envelope):In-Toto Statement
The DSSE payload is an in-toto v1 attestation statement:subject binds the statement to a specific file by name and SHA-256 digest. The predicateType identifies this as a nono instruction file attestation. Trust policy signatures use a separate predicate type: https://nono.sh/attestation/trust-policy/v1.
For keyless signing, the predicate carries OIDC provenance:
Sigstore Bundle
The DSSE envelope is wrapped in a Sigstore bundle v0.3:verificationMaterial contains a publicKey hint instead of a certificate. For keyless signing, it contains the Fulcio certificate chain and Rekor transparency log inclusion proof, making the bundle fully self-contained for offline verification.
Verification Pipeline
When nono encounters an instruction file (pre-exec scan or runtime interception), verification proceeds through these stages:
Keyless Verification
For keyless bundles, cryptographic verification includes:- Fulcio certificate chain — The signing certificate was issued by the Sigstore Fulcio CA
- Certificate validity — The Rekor timestamp proves the signature was created while the short-lived certificate was valid (certificates typically expire after 10-20 minutes)
- Rekor inclusion proof — The signature was logged in the Rekor transparency log
- ECDSA signature — The signature over the DSSE PAE is valid under the certificate’s public key
| OID | Field |
|---|---|
1.3.6.1.4.1.57264.1.1 | OIDC Issuer |
1.3.6.1.4.1.57264.1.8 | Source Repository |
1.3.6.1.4.1.57264.1.9 | Source Repository Ref |
1.3.6.1.4.1.57264.1.11 | Build Config URI (workflow) |
Keyed Verification
For keyed bundles, the public key is loaded from the system keystore and the ECDSA P-256 signature is verified directly. No Fulcio or Rekor is involved. Signer identity is extracted from the predicate’skey_id field.
Interception Points
Pre-exec Scan
Before fork/exec, nono scans the working directory for files matchinginstruction_patterns. This is the baseline — it catches instruction files present at session start. If any file fails verification and enforcement is deny, the process never starts.
The pre-exec scan also verifies the trust policy’s own signature before trusting it.
Seccomp-Notify (Linux Runtime)
On Linux in Supervised mode, the seccomp-notify supervisor already trapsopenat/openat2 syscalls for capability expansion. When the target path matches an instruction file pattern, the trust interceptor runs verification before the normal approval flow.
The interceptor:
- Reads the file content from disk (the supervisor has unrestricted access)
- Computes the SHA-256 digest and checks the blocklist
- Locates and verifies the
.bundlefile - On success: injects the file descriptor via
SECCOMP_IOCTL_NOTIF_ADDFD - On failure: returns
EPERMto the sandboxed process
Seatbelt Deny Rules (macOS Runtime)
On macOS, instruction file patterns are enforced at the kernel level using Seatbelt rule specificity. During sandbox profile generation:- Each instruction pattern is converted to a Seatbelt regex (e.g.,
SKILLS*becomes#"/SKILLS[^/]*$") - A
(deny file-read-data (regex ...))rule is emitted for each pattern - For each file that passed the pre-exec trust scan, a
(allow file-read-data (literal "/full/path"))rule is emitted
| Glob | Seatbelt Regex |
|---|---|
SKILLS* | /SKILLS[^/]*$ |
CLAUDE* | /CLAUDE[^/]*$ |
AGENT.MD | /AGENT\.MD$ |
.claude/**/*.md | /\.claude/.*/[^/]*\.md$ |
Verification Cache
To avoid re-computing signatures on every file access:Signed Trust Policy
The trust policy itself must be signed to establish a root of trust. Without this, an attacker who can write to the project directory can modifytrust-policy.json to add themselves as a publisher or weaken enforcement.
Policy bundles use the predicate type https://nono.sh/attestation/trust-policy/v1 to distinguish them from instruction file bundles.
During pre-exec scan, policy signature verification runs first:
- Locate
trust-policy.jsonin the scan root - Check for
.bundlesidecar - Verify the bundle (keyed or keyless)
- Extract and validate the policy content
deny, the sandbox refuses to start.
Trust Model
Policy signature proves provenance and tamper-resistance, not signer allowlisting. There is no higher-level document that defines who may author trust policy — the policy itself is that document. Operator acceptance of the initial policy is the trust bootstrap step, analogous to SSH’sknown_hosts or TLS’s root CA store.
Policy Merging
Multiple trust policies merge with additive-only semantics:| Field | Merge Strategy |
|---|---|
instruction_patterns | Union |
publishers | Union |
blocklist.digests | Union |
enforcement | Strictest wins (deny > warn > audit) |
trust-policy.json can add publishers but cannot remove blocklist entries, remove instruction patterns, or downgrade enforcement.
Codebase Layout
Library (crates/nono/src/trust/)
Attestation primitives reusable by all language bindings.
| File | Contents |
|---|---|
types.rs | TrustPolicy, Publisher, Blocklist, Enforcement, SignerIdentity, VerificationOutcome |
digest.rs | SHA-256 digest computation (file and bytes) |
dsse.rs | DSSE envelope parsing/construction, PAE, in-toto statements |
bundle.rs | Sigstore bundle verification, Fulcio cert identity extraction |
policy.rs | Trust policy loading, merging, evaluation, instruction file discovery |
signing.rs | ECDSA P-256 key generation, keyed signing, bundle construction |
CLI (crates/nono-cli/src/)
| File | Contents |
|---|---|
trust_cmd.rs | nono trust subcommand handlers |
trust_scan.rs | Pre-exec scan, policy signature verification |
trust_intercept.rs | Runtime trust interceptor (cache, verification dispatch) |
instruction_deny.rs | macOS Seatbelt deny rule generation (glob-to-regex) |