Skip to content

Latest commit

 

History

History
124 lines (91 loc) · 4.6 KB

File metadata and controls

124 lines (91 loc) · 4.6 KB

agentsign

A tiny CLI that signs AI agent configuration files (CLAUDE.md, .cursor/rules, AGENTS.md, …) and verifies the signatures in CI. An unapproved change to a tracked config can't land on a protected branch, because the change invalidates a signature that only a trusted key can reissue.

It's the same idea as a signed lockfile or a signed git commit, but for the instruction files that steer a coding agent with shell and file-write access. It does not judge whether the rules are good — only whether the current content was signed by a key you trust.

Install / run

Zero dependencies, no build step (plain ESM, Node ≥ 18). Install globally straight from GitHub:

npm install -g github:0xjunger/agentsign
agentsign <command>

Or run a single command without installing:

npx github:0xjunger/agentsign <command>

Note: the bare name agentsign on npm is an unrelated package. Install from the GitHub spec above (or from a clone with node src/cli.js), not npx agentsign.

Quick start

# 1. Generate a keypair (do this once, keep the private key off CI).
agentsign keygen --out keys/
#   -> keys/agentsign.key  (private, PKCS8 PEM — never commit)
#   -> keys/agentsign.pub  (public — paste into the CI variable)

# 2. After a human reviews the config, sign it.
agentsign sign --key keys/agentsign.key --signer you@example.com
#   -> writes agentsign.lock  (commit this)

# 3. Verify (this is what CI runs).
AGENTSIGN_PUBKEY="$(cat keys/agentsign.pub)" agentsign verify --pubkey -

verify exits 0 if every tracked file matches its signed hash, the signature is valid, and no untracked config files have appeared. Otherwise it exits 1 with a readable list of what broke.

Commands

Command What it does
agentsign keygen [--out keys/] Generate an Ed25519 keypair.
agentsign sign --key <privkey> [--signer <label>] Build + sign agentsign.lock.
agentsign verify [--pubkey <key|->] CI gate: exit 0/1. Reads $AGENTSIGN_PUBKEY when the flag is - or absent. Multiple trusted keys may be comma/space separated.
agentsign status Human-readable summary: signer, signature validity, stale/missing/untracked files.

What it tracks

sign resolves these globs (override them in .agentsign.json):

CLAUDE.md  AGENTS.md  GEMINI.md
.cursorrules  .cursor/rules/**  .windsurfrules
.github/copilot-instructions.md  .claude/**

.agentsign.json is itself included in the signed manifest, so the tracked set can't be quietly narrowed (see Security below).

CI

Copy examples/agent-config.yml into your repo at .github/workflows/agent-config.yml and make the agent-config check required via branch protection. That required check is the product — the CLI just powers it.

- run: npx github:0xjunger/agentsign verify --pubkey -
  env:
    AGENTSIGN_PUBKEY: ${{ vars.AGENTSIGN_PUBKEY }}  # from a repo/org variable, NOT the tree

Also commit a .gitattributes with * text=auto (included) so line-ending normalization doesn't cause false verify failures — agentsign signs raw bytes and leaves normalization to git.

Security model

What verify defends against:

  • A tracked config edited without re-signing → hash mismatch.
  • A new config file matching the tracked globs but absent from the manifest → untracked.
  • Any tampering, backed by a real Ed25519 signature, not a forgeable local secret.

The trust anchor. The public key verify trusts must come from somewhere the PR author cannot edit — a repo/org variable or secret, never a file in the working tree. Otherwise an attacker who can edit the config can also swap the key and re-sign. The private key lives with reviewers; it is never on CI. agentsign stores no trusted keys in the repo for this reason.

Why .agentsign.json is signed: the untracked-file check re-resolves globs from that file. If it weren't signed, an attacker could drop a glob and smuggle in a matching config. Since it's in the manifest, verify recomputes its hash before trusting its globs — any edit fails as a normal hash mismatch.

Out of scope: a malicious signer (that's key management), instructions in an untrusted third-party repo you cloned, and private-key theft (mitigated later by Sigstore keyless signing).

Files at repo root

  • .agentsign.json — config: the tracked globs. Committed and signed.
  • agentsign.lock — the signed manifest: path → sha256 for each tracked file, the signer's public key, and one Ed25519 signature over the canonical manifest. Committed.

Develop

node --test