Cryptographic provenance for AI agent instructions and packages
Sign SKILLS.md, CLAUDE.md and related artifacts with Sigstore and Nono keyless attestation, or publish full nono packages to the registry with the same GitHub Actions identity.
Quick Start | How It Works | Package Publishing | Inputs | Verification | nono CLI
AI agents read instruction files to determine what they can do. If those files are tampered with, the agent follows malicious instructions. nono packages raise the stakes further by distributing profiles, hooks, trust policy, and project instructions together. This action creates a cryptographic chain of trust for both cases: files can be signed in CI, and full nono packages can be signed and published with the same workflow identity.
The result is a Sigstore bundle containing a DSSE envelope with an in-toto statement, a Fulcio certificate (binding GitHub Actions OIDC identity to the signature), and a Rekor transparency log inclusion proof. No private keys involved — identity is derived from the CI workflow itself.
name: Sign files
on:
push:
branches: [main]
paths:
- 'SKILL.md'
- 'scripts/some-script.py'
permissions:
id-token: write
contents: write
jobs:
sign:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: nolabs-ai/agent-sign@v0.0.2That's it. This signs all files matching the trust policy's includes patterns, commits the .bundle sidecars, and verifies the signatures as a smoke test.
agent-sign also supports publishing full nono packages to the registry. In publish mode it:
- Installs the
nonoCLI - Signs each package artifact with a per-file
.bundle - Exchanges the GitHub OIDC token for a short-lived registry upload token
- Uploads the artifacts and matching bundle sidecars
Example:
name: Publish nono package
on:
push:
tags:
- "v*"
permissions:
contents: read
id-token: write
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: nolabs-ai/agent-sign@main
with:
publish: "true"
package-name: claude-code
package-version: ${{ github.ref_name }}
nono-version: latest
package-path: .
files: |
package.json
claude-code.profile.json
CLAUDE.md
hooks/nono-hook.sh
groups.json
trust-policy.jsonIf files is omitted in publish mode, the action recursively discovers non-hidden files under package-path, excluding README.md and existing *.bundle sidecars.
- Installs the nono CLI from GitHub releases
- Signs files using
nono trust sign --keyless - GitHub Actions OIDC provides the identity token automatically
- Fulcio issues a short-lived certificate binding the OIDC claims (repository, workflow, ref) to an ephemeral signing key
- The signature is submitted to Rekor for transparency logging
- The resulting bundle contains everything needed for offline verification
By default, all specified files are signed together into a single .nono-trust.bundle. One signature covers the entire set — modify any file and the whole bundle is invalidated. This is the recommended approach for related instruction files.
| Mode | Bundle Output | Use Case |
|---|---|---|
| Multi-subject (default) | .nono-trust.bundle |
Sign related files together. Atomic verification. Any change invalidates the bundle. |
Per-file (per-file: true) |
<file>.bundle for each |
Independent signatures. Files can be updated and verified separately. |
- uses: nolabs-ai/agent-sign@v0.0.2
with:
commit: "false"
upload-artifacts: "true"- uses: nolabs-ai/agent-sign@v0.0.2
with:
files: "SKILLS.md CLAUDE.md config/settings.json"- uses: nolabs-ai/agent-sign@v0.0.2
with:
files: "SKILLS.md CLAUDE.md"
per-file: "true"- uses: nolabs-ai/agent-sign@v0.0.2
with:
version: "v0.6.0-alpha.3"- uses: nolabs-ai/agent-sign@main
with:
publish: "true"
package-name: claude-code
package-version: ${{ github.ref_name }}
nono-version: latest
package-path: .
registry-url: "http://localhost:3001/api/v1"- uses: nolabs-ai/agent-sign@v0.0.2
with:
trust-policy: "trust-policy.json"| Input | Default | Description |
|---|---|---|
nono-version |
latest |
nono CLI version to install |
files |
(empty) | Whitespace-separated list of files to sign. Empty = --all (matches trust policy includes) |
per-file |
false |
Sign each file separately instead of a single multi-subject bundle |
commit |
true |
Commit bundle files back to the repository |
upload-artifacts |
false |
Upload bundle files as workflow artifacts |
verify |
true |
Run verification after signing |
trust-policy |
(empty) | Path to trust-policy.json for verification |
working-directory |
. |
Working directory for signing |
commit-message |
chore: update attestation bundles [skip ci] |
Commit message |
publish |
false |
Publish a nono package version to the registry |
package-version |
(empty) | Registry package version for publish mode |
package-name |
(empty) | Registry package name for publish mode |
package-namespace |
(empty) | Registry namespace for publish mode. Defaults to the owner of GITHUB_REPOSITORY |
registry-url |
https://registry.nono.sh/api/v1 |
Registry API base URL for publish mode |
package-path |
. |
Package directory for publish mode |
The workflow must have id-token: write permission for Sigstore keyless signing and package publishing. If commit: true (the default for instruction mode), it also needs contents: write.
permissions:
id-token: write
contents: writeFor package publishing, contents: read is enough unless your workflow also commits changes:
permissions:
id-token: write
contents: readConsumers verify bundles using a trust-policy.json that defines trusted publishers:
{
"version": 1,
"includes": ["SKILLS*", "CLAUDE*", "AGENT*", ".claude/**/*.md"],
"publishers": [
{
"name": "my-org CI",
"issuer": "https://token.actions.githubusercontent.com",
"repository": "my-org/my-repo",
"workflow": ".github/workflows/sign-instruction-files.yml",
"ref_pattern": "refs/heads/main"
}
],
"blocklist": { "digests": [] },
"enforcement": "deny"
}Verify locally:
nono trust verify --policy trust-policy.json --allOr enforce at runtime — nono's pre-exec scan verifies all files matching the trust policy before the agent can read them:
nono run --profile claude-code -- claudeFor package publishing, consumers verify the downloaded artifacts during nono pull using the Sigstore bundle, Fulcio identity, Rekor inclusion proof, and namespace-bound publisher identity returned by the registry pull manifest.
This action is the source half of nono's attestation pipeline. The runtime half happens when an agent is launched through nono run. Together they form an unbroken cryptographic chain from CI to execution. For full technical details, see Signing and Attestation Internals.
Each signed file produces a three-layer cryptographic artifact:
For keyless signing (the default in CI), the signer identity is extracted from the GitHub Actions OIDC token and embedded in a short-lived Fulcio certificate. The certificate binds the OIDC claims -- repository, workflow file, branch ref -- to an ephemeral signing key. The signature is then logged in Rekor, providing a timestamp proof that the signature was created while the certificate was valid.
When an agent is launched through nono, the sandbox enforces attestation before the agent can read any file matching the trust policy:
The agent process never observes unverified content. On Linux, the seccomp supervisor mediates every file open. On macOS, kernel-level Seatbelt rules prevent reads entirely unless the file was verified at startup.
Without runtime enforcement, signing is advisory -- an attacker who modifies a file after it was signed can still trick the agent. nono closes this gap:
- No trust-on-first-use: A valid signature from a trusted publisher is required on first encounter. There is no grace period.
- Blocklist precedence: Known-malicious file digests are rejected regardless of whether they have a valid signature.
- TOCTOU protection: On Linux, the supervisor re-verifies the file descriptor's digest after opening, preventing file swaps between verification and read.
- Policy self-protection: The trust policy document itself requires a signed attestation. An attacker cannot modify
trust-policy.jsonto self-authorize without a valid signature from a trusted publisher.
Keyless bundle verification confirms four things:
| Check | What it proves |
|---|---|
| Fulcio certificate chain | The certificate was issued by Sigstore's CA |
| Rekor inclusion proof | The signature was logged in the transparency log within the certificate's validity window (10-20 minutes) |
| ECDSA signature validity | The DSSE envelope was signed by the certificate's key |
| OIDC claim matching | The Fulcio certificate's X.509 extensions (issuer, repository, workflow, ref) match the trust policy's publisher definition |
The relevant Fulcio certificate extensions:
| 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 |
Repository ref |
1.3.6.1.4.1.57264.1.11 |
Build config (workflow URI) |
| Option | Use Case |
|---|---|
commit: true (default) |
Bundles live alongside files in version control. Consumers get them on git clone. |
upload-artifacts: true |
Bundles available as downloadable workflow artifacts. Useful for release pipelines. |
| Both | Commit for development, artifacts for release automation. |
Instruction files often reference companion artifacts (scripts, configs, data files). Use multi-subject signing to attest them together:
- uses: nolabs-ai/agent-sign@v0.0.2
with:
files: "SKILLS.md lib/helper.py config/settings.json"This produces a single .nono-trust.bundle that attests all three files. If any file is modified, the entire bundle becomes invalid, ensuring the agent only receives a consistent, verified set of files.
Apache-2.0


