Skip to main content
Nono’s trust system verifies the provenance and integrity of files before they are consumed. Any file you choose to protect — instruction files, configs, scripts, templates — can be signed and verified. At sandbox startup, nono scans for files matching the trust policy and verifies their signatures. Unsigned or tampered files are hard-denied.

Quick Start

1. Generate a signing key

nono trust keygen
Creates an ECDSA P-256 key pair in the system keystore (macOS Keychain / Linux Secret Service).

2. Create and sign a trust policy

A trust policy declares which files must be verified and who is allowed to sign them. It lives in the project root as trust-policy.json — if a project you’re working on already has one, skip this step.
nono trust init --include "SKILLS.md" --include "CLAUDE.md" --include "src/config.py"
nono trust sign-policy
The first command creates a trust-policy.json with the specified include patterns and your signing key’s public key as a publisher. The second signs it — an unsigned policy could be modified by an attacker. Patterns use glob syntax and can be specific files or wildcards — SKILLS* matches SKILLS.md, SKILLS-ops.md, etc. Be specific with patterns: any file that matches includes must be signed or verification will fail.

3. Sign files

# Sign a single file
nono trust sign SKILLS.md

# Sign all files matching the trust policy's includes
nono trust sign --all
Each file gets its own .bundle sidecar (e.g., SKILLS.md.bundle). Use --multi-subject to produce a single .nono-trust.bundle instead. Commit bundles alongside the signed files. Signing works on any file — no trust policy is required for explicit file arguments. --all uses the policy to discover which files to sign.

4. Verify

nono trust verify --all
When you run nono run, the pre-exec scan automatically verifies all files matching the policy before the agent launches.

CLI Commands

nono trust init

Create a trust-policy.json in the current directory.
nono trust init --include "*.md"                    # single pattern
nono trust init --include "*.md" --include "*.py"   # multiple patterns
nono trust init --include "SKILLS*" --key my-key    # with specific signing key
nono trust init --user                              # user-level policy (~/.config/nono/)
nono trust init --force                             # overwrite existing policy
Note: the pre-exec trust scan and sign --all/verify --all do not respect .gitignore — adding a file to .gitignore cannot bypass trust verification.

nono trust sign

Sign files. Any file can be signed — no trust policy is required for explicit file arguments.
nono trust sign SKILLS.md                  # keyed (default key), creates SKILLS.md.bundle
nono trust sign SKILLS.md --key my-key     # keyed (specific key)
nono trust sign SKILLS.md CLAUDE.md        # per-file .bundle sidecars
nono trust sign --all                      # all files matching policy (per-file)
nono trust sign --all --multi-subject      # single .nono-trust.bundle for all files
Keyless signing (CI environments only):
nono trust sign SKILLS.md --keyless        # requires ambient OIDC (e.g., GitHub Actions)
Keyless signing requires a CI environment with ambient OIDC tokens, such as GitHub Actions with permissions: id-token: write. Interactive browser-based keyless signing is not yet supported. Use keyed signing for local development.

nono trust verify

nono trust verify SKILLS.md                # single file
nono trust verify --all                    # all files matching policy
nono trust verify SKILLS.md --policy path  # specific trust policy

nono trust list

List all files matching the policy and their verification status.
nono trust list
  File                               Status       Publisher
  -------------------------------------------------------------------
  SKILLS.md                          VERIFIED     local-dev (keyed)
  CLAUDE.md                          VERIFIED     local-dev (keyed)
  .claude/commands/deploy.md         UNSIGNED     no .bundle file found

nono trust keygen

Generate an ECDSA P-256 signing key pair.
nono trust keygen                          # default key ID ("default")
nono trust keygen --id my-signing-key      # named key

nono trust export-key

Export the public key in base64 DER format for use in trust policy public_key fields.
nono trust export-key                      # export default key
nono trust export-key --id my-signing-key  # export named key
nono trust export-key --pem                # output as PEM instead of base64 DER

nono trust sign-policy

Sign the trust policy file. Required after every modification to trust-policy.json.
nono trust sign-policy                     # sign trust-policy.json in CWD
nono trust sign-policy --user              # sign user-level trust policy (~/.config/nono/)
nono trust sign-policy --key my-key        # specific key

Development Override

For development and testing, disable attestation enforcement:
nono run --profile claude-code --trust-override -- claude
Verification still runs and results are logged, but failures produce warnings instead of hard denies. This flag is CLI-only — it cannot be set in a profile or trust policy. The NONO_TRUST_OVERRIDE=1 environment variable is also supported.

Trust Policy Reference

Trust policy is defined in trust-policy.json, separate from the sandbox policy (policy.json).
{
  "version": 1,
  "includes": [
    "SKILLS*",
    "CLAUDE*",
    "*.py",
    ".claude/**/*.md"
  ],
  "publishers": [
    {
      "name": "local-dev",
      "key_id": "default",
      "public_key": "<BASE64_PUBLIC_KEY>"
    }
  ],
  "blocklist": {
    "digests": [],
    "publishers": []
  },
  "enforcement": "deny"
}
To get the public key in base64 format for a manual policy:
nono trust export-key --id default

includes

Glob patterns identifying files under attestation. Any file matching these patterns is subject to verification.
{
  "includes": [
    "SKILLS*",
    "CLAUDE*",
    "*.py",
    ".claude/**/*.md"
  ]
}

publishers

Trusted publisher identities. A file’s signature must match at least one publisher. Keyed publishers match by key ID and verify with the embedded public key:
{
  "name": "local-dev",
  "key_id": "default",
  "public_key": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE..."
}
The public_key field contains the base64-encoded DER SPKI public key. Keyless (OIDC) publishers match by identity claims from the Fulcio certificate:
{
  "name": "my-org-ci",
  "issuer": "https://token.actions.githubusercontent.com",
  "repository": "my-org/my-repo",
  "workflow": ".github/workflows/sign-skills.yml",
  "ref_pattern": "refs/heads/main"
}
These fields are matched against claims in the Fulcio certificate that Sigstore issues during keyless signing. The certificate embeds the OIDC identity of the CI environment that performed the signing.
FieldDescriptionWildcards
nameHuman-readable label for this publisher (not matched against anything)No
issuerThe OIDC provider URL. For GitHub Actions this is always https://token.actions.githubusercontent.comNo
repositoryThe owner/repo that ran the signing workflow (maps to the GitHub OIDC repository claim). Use org/* to trust all repos in an orgYes (org/*)
workflowPath to the workflow file that performed the signing, relative to the repo rootYes (*)
ref_patternThe git ref that triggered the workflow (e.g., refs/heads/main, refs/tags/v1.0). Use wildcards to trust a range of refsYes (refs/tags/v*)

blocklist

Known-malicious file digests. Checked before any other verification. A file matching a blocklist digest is hard-denied regardless of signature validity.
{
  "blocklist": {
    "digests": [
      {
        "sha256": "a1b2c3d4...",
        "description": "Known malicious SKILLS.md variant",
        "added": "2026-02-20"
      }
    ]
  }
}
Blocklist entries are always hard-denied, even in warn or audit enforcement modes.

enforcement

ModeBehavior
denyHard deny unsigned/invalid/untrusted files. The agent does not start.
warnLog warnings but allow the agent to proceed.
auditAllow access, log verification results for post-hoc review.

GitHub Actions Integration

Keyless signing integrates with GitHub Actions OIDC for automated CI/CD signing.

Signing Workflow

name: Sign files
on:
  push:
    branches: [main]
    paths:
      - 'SKILLS.md'
      - 'CLAUDE.md'
      - 'AGENT*'

permissions:
  id-token: write
  contents: write

jobs:
  sign:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install nono
        run: cargo install nono-cli

      - name: Sign files
        run: |
          for f in SKILLS.md CLAUDE.md; do
            [ -f "$f" ] && nono trust sign "$f" --keyless
          done

      - name: Commit bundles
        run: |
          git config user.name "github-actions[bot]"
          git config user.email "github-actions[bot]@users.noreply.github.com"
          git add '*.bundle'
          git diff --staged --quiet || git commit -m "Update file signatures"
          git push

Trust Policy for CI-Signed Files

{
  "version": 1,
  "includes": ["SKILLS*", "CLAUDE*", "AGENT*"],
  "publishers": [
    {
      "name": "my-org-ci",
      "issuer": "https://token.actions.githubusercontent.com",
      "repository": "my-org/my-repo",
      "workflow": ".github/workflows/sign-skills.yml",
      "ref_pattern": "refs/heads/main"
    }
  ],
  "blocklist": { "digests": [] },
  "enforcement": "deny"
}
This policy declares: only trust files signed by a GitHub Actions workflow in my-org/my-repo, from the main branch.

Signing Modes

There are two signing modes: Keyed signing — A private key stored in the system keystore (macOS Keychain / Linux Secret Service). Suitable for individual developers and local workflows. Keyless signing — Ephemeral key + OIDC identity + Fulcio certificate + Rekor transparency log. Suitable for CI/CD pipelines with ambient OIDC tokens (e.g., GitHub Actions with permissions: id-token: write). The signer’s identity is cryptographically bound to the signature via the OIDC token. Both modes produce Sigstore bundle v0.3 files (.bundle extension).

What Signing Proves (and What It Does Not)

Signing a file proves two things:
  1. Provenance — The file was signed by a specific identity (a key you control, or a CI/CD pipeline you trust).
  2. Integrity — The file has not been modified since it was signed. Any tampering breaks the signature.
Signing does not prove that the content is safe or correct. This is the same trust model as code signing or package signing: you decide which signers you trust (via publishers), and the cryptography guarantees only those signers can produce files that pass verification.

Signature Storage

Per-file bundles (default): <filename>.bundle
project/
  SKILLS.md
  SKILLS.md.bundle
  trust-policy.json
  trust-policy.json.bundle
Multi-subject bundles (with --multi-subject): .nono-trust.bundle
project/
  SKILLS.md
  CLAUDE.md
  helper.py
  .nono-trust.bundle           # contains all subjects
  trust-policy.json
  trust-policy.json.bundle
Bundles are checked into version control. A missing bundle triggers verification failure. For most projects, use a single .nono-trust.bundle via --multi-subject rather than per-file sidecars. This keeps the tree clean and avoids bundle proliferation as the number of signed files grows.
project/
  SKILLS.md
  CLAUDE.md
  src/
    config.py
  .nono-trust.bundle             # single bundle covering all signed files
  trust-policy.json
  trust-policy.json.bundle
  • trust-policy.json lives at the project root
  • trust-policy.json.bundle is its signature sidecar (always per-file)
  • .nono-trust.bundle contains attestations for all files signed with --multi-subject
  • Bundle files for signed files in subdirectories (e.g., src/config.py) are also stored at the depth of the file when using per-file signing (src/config.py.bundle)
To set this up:
nono trust init --include "SKILLS.md" --include "CLAUDE.md" --include "src/config.py"
nono trust sign-policy
nono trust sign --all --multi-subject
Commit trust-policy.json, trust-policy.json.bundle, and .nono-trust.bundle alongside your source files.

Policy Composition

Trust policies compose across three levels:
LevelLocationPurpose
EmbeddedBuilt into nono binaryBaseline include patterns
User$XDG_CONFIG_HOME/nono/trust-policy.jsonPersonal trusted publishers
Project<project-root>/trust-policy.jsonProject-specific publishers
Merging rules:
  • Publishers: union (all levels combined)
  • Blocklist digests: union (all levels combined)
  • Include patterns: union (all levels combined)
  • Enforcement: strictest wins (deny > warn > audit)
Project-level policy cannot weaken user-level or embedded policy.

User-Level Policy as Trust Anchor

The user-level policy (~/.config/nono/trust-policy.json) defines who you trust — your publishers, enforcement floor, and blocklist. Project-level policies define what gets verified (include patterns). This separation means a malicious project can’t weaken your trust decisions. When only a project-level policy exists with no user-level policy, nono warns:
Warning: project-level trust-policy.json found but no user-level policy exists.
Project policies are not authoritative without a user-level policy to anchor trust.
Create a signed policy at ~/.config/nono/trust-policy.json to enforce verification.
Create a user-level policy:
nono trust init --user
This creates ~/.config/nono/trust-policy.json with empty includes and your signing key as a publisher. Edit it to add your trusted OIDC publishers:
{
  "version": 1,
  "includes": [],
  "publishers": [
    {
      "name": "my-org-ci",
      "issuer": "https://token.actions.githubusercontent.com",
      "repository": "my-org/*",
      "workflow": "*",
      "ref_pattern": "refs/heads/main"
    }
  ],
  "blocklist": { "digests": [] },
  "enforcement": "deny"
}
The includes list is empty — the project-level policy declares which files to verify. The user-level policy establishes who you trust: in this example, any GitHub Actions workflow in your organization signing from main. Projects can add additional publishers but can’t remove yours, and enforcement of deny can’t be downgraded. You can also add keyed publishers for local development keys, though in practice your own key is already trusted implicitly — the user-level policy is more useful for declaring trust in CI/CD identities and other team members.
nono trust sign-policy --user

Pre-exec Scanning

When nono run launches a command, it scans the working directory for files matching includes before applying the sandbox. File discovery does not respect .gitignore — this prevents hiding files from verification. To keep startup latency bounded in large repositories, the scan prunes a small built-in set of regenerable heavy directories such as .git, node_modules, target, dist, and common cache directories. Hidden directories are otherwise still scanned. You can extend the skip set for a session with --skip-dir <name> or persist it in a profile with skipdirs.
nono run --profile claude-code -- claude
  -> Scan CWD for files matching includes patterns
  -> For each match:
     -> Compute SHA-256 digest
     -> Check blocklist
     -> Load and verify .bundle
     -> Check publisher identity against trust policy
  -> All pass: proceed with sandbox and exec
  -> Any fail (enforcement=deny): abort with diagnostic

Runtime Interception

The pre-exec scan catches files present at startup. Runtime behavior differs by platform.

Linux (seccomp-notify)

In Supervised mode, the seccomp-notify supervisor traps every openat() syscall. When the target path matches an include pattern, the supervisor verifies the file’s bundle before allowing the read. Unsigned, tampered, or blocklisted files are denied with EPERM. The verification result is cached by (path, inode, mtime, size). If any metadata changes, the cache entry is invalidated. Files that appear mid-session (e.g., via curl or git pull) are verified on first open. Linux provides true runtime interception.

macOS (Seatbelt)

On macOS, trust verification is startup-only. For each verified file, a literal (deny file-write-data ...) rule is emitted in the Seatbelt profile, making the file structurally immutable for the session. macOS does not have syscall-level file-open interception equivalent to Linux’s seccomp-notify. A file matching includes that appears after the sandbox is applied will be readable if its parent directory has read access granted. The macOS enforcement model provides:
  • Integrity of verified files — kernel-level write-protection prevents tampering
  • Startup gating — if any existing file fails verification, the sandbox refuses to start
  • No runtime interception — file existence and verification are evaluated once at startup

Why Glob Patterns?

Pattern matching keeps the system practical: it avoids requiring every file in the working directory to be signed, and lets you define the verification boundary to match your needs. Common use cases include agent instruction files (SKILLS*, CLAUDE*), configuration files (*.cfg, *.ini), scripts (*.py, *.sh), or any other files your workflow depends on.