Get signing in five minutes
Sign the skills, agents, and instruction files you publish, and verify everything you install — from the command line, or right in the browser with the Sign and Verify tools. Both produce and read the exact same signed bundle.
Install
Download the CLI for your platform, unpack it, and put promptsign somewhere on your PATH. It's a single native binary — no runtime to install.
Save each signature beside its archive, keeping both original filenames — that pairing is how verification finds it. A combined SHA256SUMS covers every archive in the release.
A checksum tells you the file arrived intact; the signature tells you who built it, which is the part that still holds if this page is ever tampered with. On a machine that doesn't have a trusted promptsign yet, check the download with a tool that isn't ours — gh attestation verify <archive> --repo PromptSign/promptsign-cli. Once you have one you trust, it verifies the next release itself: promptsign verify <archive>.
Sign as a login you already have
The whole point of signing a skill is proving who published it. The recommended way to sign is keyless signing: log in with GitHub, Google, or your company account, and your signature carries that verified identity. There is no key file to create, protect, or lose. If you publish from GitHub Actions, it is a single workflow step.
Any signature can prove files haven't changed since signing. The harder question is who signed them: a bare signing key just carries a name typed next to it, and nothing stops anyone from typing someone else's name. Keyless signing removes the guesswork.
How it works behind the scenes
- Familiar Sign-In: You use the same “Sign in with GitHub” or “Sign in with Google” buttons you use everywhere else. PromptSign never sees your password.
- Short-Lived Proof: PromptSign receives a temporary login token that says, in effect, “this really is alice@example.com, confirmed by Google.”
- Zero Maintenance: A free service called Sigstore issues a certificate tying your signature to that identity for roughly ten minutes, records it in a public log, and throws the private key away. There is nothing for you to store, rotate, or leak.
promptsign login
promptsign sign ./my-skill --version 1.2.0The sections below cover signing automatically from GitHub, from your own computer, and right in the browser.
Which account can vouch for you?
Any identity provider the public Sigstore service trusts. The common choices:
GitHubYour GitHub login, or — best of all — a GitHub Actions workflow that signs releases automatically. Your identity shows up as the repository and workflow that produced the release.GoogleA personal or Google Workspace account. Your identity shows up as your email address.MicrosoftA personal or work/school account. Your identity shows up as your email address.GitLab CIA GitLab pipeline can vouch for itself the way GitHub Actions does — the identity is the project and pipeline that built the release.Company SSOSign as your own directory identities, against a trust root your company controls rather than the public one. That's the enterprise tier — talk to us.Whichever you pick, anyone verifying the bundle sees the confirmed identity — an email address or a CI workflow — never a name that was simply typed in.
Sign automatically from GitHub (easiest)
If you publish from a GitHub repository, this is the smoothest path — no account setup, no key, and no token to handle. GitHub itself vouches for the release: add one step to a GitHub Actions workflow and every build is signed as your repository's verified identity.
promptsign sign ./my-skill --name my-org/my-skill --version 1.2.0Give the job permission to prove its identity with permissions: id-token: write; GitHub then supplies the token automatically, with no secret to configure.
Prefer a maintained action over hand-rolling this step? PromptSign/promptsign-sign wraps it, installs the CLI, and re-verifies its own output before finishing. Pair it with PromptSign/promptsign-verify to gate pull requests, including ones from forks. See the Integrate page for both.
Sign from your own computer
The simplest path: log in once from the terminal, then sign. promptsign login opens your browser, you sign in with GitHub, Google, or your company account, and it caches a short-lived token so the next keyless sign just works — nothing to copy or paste.
promptsign login
promptsign sign ./my-skill --version 1.2.0Prefer no terminal? The Sign tool's Verified login tab does the same in the browser (see In the browser).
Other ways to get a token
In CI you don't log in at all — inside a GitHub Actions job (with permissions: id-token: write), GitHub mints a token for the sigstore audience on request:
TOKEN=$(curl -sSL \
-H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
"$ACTIONS_ID_TOKEN_REQUEST_URL&audience=sigstore" | jq -r .value)
echo "$TOKEN"Or grab a raw token from an interactive login — handy for setting as an environment variable, or passing to a script:
promptsign login --print-tokenHowever you obtained a token, you can also hand it to promptsign directly:
promptsign sign ./my-skill --identity-token "$(cat token.jwt)" --version 1.2.0export SIGSTORE_ID_TOKEN=… # then run promptsign signTokens are short-lived (minutes) and single-purpose, so grab one right before you sign.
What that token is
It is an identity token, not an access token. It says who you are. It does not grant anyone the ability to act as you.
It is an ordinary OpenID Connect ID token — a JWT your login provider signed. PromptSign asks for the openid email scope and nothing else, so the whole payload is this:
{
"iss": "https://accounts.google.com",
"aud": "sigstore",
"sub": "1078…41",
"email": "you@example.com",
"email_verified": true,
"iat": 1756100000,
"exp": 1756100300
}The aud field says the token is meant for sigstore and nobody else. Google, GitHub and Microsoft all refuse a token presented back to their own APIs with the wrong audience. So it cannot read your mail, list your repositories, or open a session anywhere. It proves an identity to one recipient, for a few minutes.
That one recipient is Fulcio Certificate Authority. Fulcio checks the provider's signature and issues the short-lived certificate that carries your identity into the signature. PromptSign never sends the token anywhere else. Signing in the browser routes the same request through our CORS proxy, because a web page cannot call Fulcio directly.
In CI GitHub mints the token itself and its subject is the workflow, which is why a bundle signed in Actions names a workflow as its publisher.
Two things worth knowing before you sign. promptsign login caches the token at ~/.promptsign/session.json so promptsign sign can reuse it, in the clear, until it expires a few minutes later. And the identity inside it does not stay private: it is copied into the certificate, and the certificate is published to Rekor, so the email you sign with becomes permanently public. Sign with an address you are willing to publish, or sign from CI, where the identity is a workflow.
In the browser
This site has browser keyless signing turned on. Open the Sign tool — the Verified login (keyless) tab is the default. Identity comes from one place:
Sign in with your providerClick the button and approve the login on the page it points you to (it shows a short code to confirm). The token is fetched and used automatically. This uses the OAuth device flow, which needs no redirect setup — so it works from a plain hosted page, unlike a normal “sign in” redirect.The page never asks you to paste a token, and you should never paste one into a page that does — a login token is a signing credential, and anything holding it can sign as you. Your browser then generates a one-time key, gets a certificate from Sigstore, records the signature in the public log, and throws the key away — the same steps the CLI runs, all in this tab.
Which accounts can I sign in with?
The button uses public Sigstore's login service, so you get the accounts it accepts:
GitHubSigns as the verified email on your GitHub account.GoogleA personal or Google Workspace account. Signs as your email address.MicrosoftA personal or work/school Microsoft account. Signs as your email address.All three providers support the browser-based device login. However, network reachability can still prevent a signature; your browser must be able to access both the login service and Sigstore. If this fails on a restricted network, PromptSign surfaces a explicit network error instead of failing silently. Sign from the CLI instead — it isn't bound by the browser's cross-site restrictions, and it reaches hosts a page in a tab often can't.
To verify your setup, perform an end-to-end test: sign in, sign a test skill, and confirm the output lists your verified email as the signer. PromptSign locally validates the signature and transparency log entry before confirming, ensuring a successful green status is genuinely valid.
Automated CI workflows bypass this button entirely. For example, GitHub Actions generates its own token automatically (see Sign automatically from GitHub). Signing against your own trust root and company SSO is available on the enterprise tier — talk to us.
Sign & verify
The signing unit is always a whole directory (a skill bundle) or a standalone file — never a single file inside a directory. A directory signature lands in .promptsign/bundle.json; a file gets a <file>.psig.json sidecar.
promptsign sign ./my-skill --name my-org/my-skill --version 1.2.0To verify keyless bundles, fetch the Sigstore trust roots once, then verify entirely offline — the certificate chain, the log timestamp, and the identity are all validated locally.
promptsign trust fetchpromptsign verify ./my-skillpromptsign verify-tree ./skills ./agentsVerification recomputes every file hash, rejects any unlisted file in a signed directory, and hard-fails if a known name is suddenly signed by a different identity (trust-on-first-use pinning). Integrity failures can never be waived by policy.
Verifying in the browser vs. the CLI
The Verify tool and promptsign verify run the same cryptographic checks on the same bundle, and neither needs a network connection to do it. What differs is everything around those checks — whose rules get applied, what the verifier can see, and what it can remember.
promptsign trust fetch and refreshed whenever you choose.promptsign verify-tree sweeps entire directories in one pass.Short version: the browser answers “is this bundle genuine?” on any machine, with nothing to install. The CLI answers that too, and adds “…and does it meet my rules, on my files, against a trust root I keep current?” — which is what you want in CI and in agent hooks.
The policy file: whose signature counts
A valid signature on its own means very little, because anyone can validly sign as themselves. An attacker who publishes a malicious skill and signs it honestly produces a bundle that verifies. What turns a signature into a decision is policy: which identities are allowed to sign which names.
You do not need to write one to start. With no policy file anywhere, PromptSign uses this:
{
"schema": "promptsign/policy/v1",
"default": "warn",
"rules": [
{ "pattern": "*", "action": "warn", "tofu": true }
]
}Which reads: every artifact matches, nothing is required of the signer, problems are reported rather than enforced, and the first signer seen for a name is remembered. That last flag is the one doing real work on day one, and it is covered below.
Where the file lives
The first of these that exists wins. A path you pass explicitly must exist, and PromptSign stops with an error rather than quietly falling through to a weaker policy:
--policy <path>Passed to verify or verify-tree, for one command.PROMPTSIGN_POLICYA path in the environment, for a shell session or a CI job..promptsign/policy.jsonIn the project being verified. Checked in, so a team shares one policy.~/.promptsign/policy.jsonYours, covering every project on the machine. Written by policy init --global.promptsign policy init --global
promptsign policy showpolicy show prints the effective policy and the file it came from, which is the fastest way to find out that the policy you have been editing is not the one being used.
Rules
Rules are matched against the artifact's name, and the first one that matches wins — so order them specific to general, and keep a * rule last as the catch-all. A rule can require any combination of:
patternGlob matched against the artifact name. Required.identityGlob the signer's identity must match — an email, or a workflow URL.issuerGlob the OIDC issuer must match. Locally-keyed bundles have no issuer, so any issuer rule also means “must be signed keyless.”keyidAn exact key. For local-key signing only, since keyless keys are ephemeral.actionenforce fails verification, warn reports it, off stays silent.tofuRemember the first signer for this name and require it thereafter.An allowlist is a specific rule ahead of an enforcing catch-all:
{
"schema": "promptsign/policy/v1",
"default": "enforce",
"rules": [
{
"pattern": "acme-*",
"identity": "https://github.com/acme/*",
"issuer": "https://token.actions.githubusercontent.com",
"action": "enforce"
},
{ "pattern": "*", "action": "enforce", "tofu": true }
]
}Anything named acme-* must now be signed by a GitHub Actions workflow in the acme organisation. Everything else must at least be signed by someone, consistently.
What fails, and what policy can change
Not every failure is a policy decision. Two of them are settled before policy is consulted at all, which is what makes the default useful on a machine that has no policy file on it.
"action": "off".off softens it, to a warning. A broken signature is never silently ignored.identity or issuer rule ships by default, so any valid signer is accepted — you are told who they are.enforce fails a signer outside the rule, warn reports it, off stays silent.tofu, so the first signer of a name is pinned and a different one is a mismatch.Only turning tofu off. While it is on, a pin mismatch fails regardless of the rule's action.warn, and the hooks print a report only when something actually fails — so an unsigned tree starts a session with no output.enforce makes unsigned a failure, which blocks it at the hook. off drops the warning from promptsign verify too.Rule actions are matched exactly. Anything that is not enforce or off is treated as warn, a misspelled "Enforce" included, and policy show prints it back as you wrote it — so check the effective policy after editing rather than assuming a value took.
Trust on first use
An identity rule answers “who may sign this?”. It cannot answer “is this the same publisher as last time?”, because you have to know the identity in advance to write it down, and because identity rules are globs: https://github.com/acme/* permits every workflow in the organisation.
tofu answers the second question. The first time a name verifies, its signer is written to ~/.promptsign/pins.json. Every later verification must present that same signer. A pin that stops matching is a hard failure regardless of the rule's action, so even the warn-only default turns a changed publisher into a real error. That is the signal for an account takeover or a transferred repository, and it is not something to report politely.
Keyless pins bind the identity and issuer but not the key, because keyless signing generates a fresh key every time and pinning it would fail on the publisher's next release. Use --no-pin-updates to verify without recording anything new, which is what you want in CI.
Enforce inside your agents
A hook is a small check your AI tool runs on its own at a certain moment. Claude Code and Codex can run one right before they load an instruction file — a skill, an agent, or a CLAUDE.md — and let that check decide whether loading is allowed at all.
promptsign hook is that check. Your tool hands it the file that's about to load, PromptSign verifies the signature, and it answers with an exit code — the single number every command-line program returns when it finishes. Think of it like a bouncer at a door: 0 means “all good, come in,” and 2 means “stop, don't let this in.” Claude Code and Codex watch for that 2 and refuse to load the file when they see it.
Before wiring up either tool, run this once to cache the Sigstore trust roots. Without trust roots the hook's first verification has nothing to check the certificate against and errors out:
promptsign trust fetchClaude Code
Add this to .claude/settings.json in your project, or to ~/.claude/settings.json to protect every project on your machine. Already have a settings file? Merge the hooks key into it — a second top-level { makes the whole file invalid JSON, and Claude Code skips it entirely.
{
"hooks": {
"SessionStart": [
{ "hooks": [{ "type": "command", "command": "promptsign hook" }] }
],
"PreToolUse": [
{ "matcher": "Skill", "hooks": [{ "type": "command", "command": "promptsign hook" }] }
]
}
}Codex
Add this to ~/.codex/config.toml (Codex uses the same event names as Claude Code, so the same command works unchanged):
[[hooks.SessionStart]]
type = "command"
command = "promptsign hook"
[[hooks.PreToolUse]]
matcher = "*"
type = "command"
command = "promptsign hook"Here is what that means in practice:
promptsign hookexit 0 → loadsA skill that is signed and hasn't changed since signing. The tool loads it and carries on as normal — you may never notice the check ran.exit 2 → blockedA skill that was never signed, or whose files or scripts were edited after signing. The tool stops before loading it and tells you why, so a tampered or unknown instruction never reaches the model.You add it once, per tool. Both tools already have the promptsign command from the install step above, so the setup is just wiring that command into two moments: when a session starts (it checks every instruction file up front) and just before a skill runs (it re-checks that one skill — the backstop in case a file was edited mid-session).
By default an unsigned file still loads — most skills in the wild aren't signed yet, so blocking every one would be unusable. Blocking unsigned is a policy decision: give the rule "action": "enforce" once your own files are signed. A signed file that fails verification already blocks, with no configuration at all.
PROMPTSIGN_STRICT=1 is a narrower lever. It leaves unsigned files unchanged, but makes verification failures fail closed: a failure found at session start ends the session rather than being reported into it, while a skill that cannot be found on disk or whose verification errors out is blocked rather than allowed to run.
OpenClaw / ClawPilot
OpenClaw (and ClawPilot desktop apps, which run OpenClaw underneath) uses a different hook model, but the same promptsign command covers it. Its install policy checks every skill or plugin at install time, so a tampered skill is blocked before it ever lands on disk. Add this to openclaw.json, pointing at the full path of your promptsign binary:
{
"security": {
"installPolicy": {
"enabled": true,
"exec": {
"command": "/usr/local/bin/promptsign",
"args": ["hook", "openclaw-install-policy"],
"passEnv": ["HOME", "USERPROFILE"]
}
}
}
}One difference from the two tools above: OpenClaw scrubs the environment of the program it calls, so PROMPTSIGN_STRICT never reaches it. Make unsigned skills a block by giving your policy an enforce rule instead. Pass PROMPTSIGN_HOME through security.installPolicy.exec.env so it can find that policy at all.
Environment
PROMPTSIGN_HOMEKey, pins, and policy directory (default ~/.promptsign).PROMPTSIGN_IDENTITYOverride the signing identity for a single command.SIGSTORE_ID_TOKENOIDC token used for keyless signing when present.