@workrr-trust/certify
v0.1.12
Published
Certify and verify AI agent artifacts (skills, MCP servers, rulesets) with publicly logged, independently verifiable certificates.
Downloads
2,448
Maintainers
Readme
workrr-trust/certify
Certify and verify AI agent artifacts (skills, MCP servers, rulesets, AGENTS.md)
with publicly logged certificates anyone can verify without trusting workrr.
| Get it | |
|---|---|
| TypeScript / Node | @workrr-trust/certify on npm, published with provenance · npx @workrr-trust/certify verify … |
| Go | github.com/workrr-trust/certify/go · go install github.com/workrr-trust/certify/go/cmd/workrr@latest |
| Binaries | GitHub releases, with Sigstore build attestations |
| GitHub Action | workrr verify — Marketplace consumer gate (verify only). Quickstart |
| Guide | How to use · instructions for AI agents |
| How it works | trust.workrr.ai/how-it-works · live ledger · spec |
- Certificate format: spec/README.md (in-toto Statement, predicate
https://trust.workrr.ai/certificate/v0.1) - Implementations: TypeScript (this directory) and Go (go/, single static binary), held byte-for-byte to each other by the shared test vectors
- Design rationale: docs/design.md
- Evidence packs for regulation and audits (draft): docs/evidence.md
- Security: SECURITY.md
- License: Apache-2.0
Status: v0.1, pre-release. The path of this repo's
.github/workflows/certify.ymlis the certifier's public signing identity, so changes to it are changes to what workrr signs.
Certify in CI (GitHub)
In the artifact's public repo:
# .github/workflows/workrr.yml
on: { push: { branches: [main], tags: ["v*"] }, workflow_dispatch: {} }
jobs:
certify:
permissions: { contents: read, id-token: write, attestations: write }
uses: workrr-trust/certify/.github/workflows/[email protected]
with:
path: skills/my-skillThe workflow:
resolvereads its own commit from its OIDC token, so the certifier code that runs is the code the signing identity names.testruns the artifact's declared suite (workrr.json→{"test":{"command":"..."}}). It has no signing permission, because it runs artifact code.certifyre-derives the digest, runs the provenance, policy, and known-pattern scan checks, and accepts the test report only if its digest matches. If everything passes, it signs with Sigstore viaactions/attestand logs to public Rekor. The certificate is uploaded as theworkrr-certificateartifact.
Annotated copy: examples/certify.yml and the
five-minute quickstart. Do not replace this job with the
root action. That action cannot certify. See GitHub Marketplace.
Public scans (nothing to install)
A maintainer who doesn't want an outside workflow in their repo can still have a
certificate. workrr runs scan.yml in this repo at a
release tag. It reads a public commit, never runs the project's code, and signs the
result. The level is Scanned: anyone can reproduce it, it is not signed by the
maintainer, and it ranks below Signed. See docs/public-scans.md.
A scan that fails a security check signs a finding instead: a reproducible,
factual record (rule, file, line, commit) that workrr verify-finding re-checks.
watch.yml re-scans each target when its path
changes. There is no opt-out from scanning public code. See docs/findings.md.
Levels
Named for how much scrutiny an artifact got. Signed predicates keep the v0.1 wire
values; verifiers print both (VERIFIED · Tested (silver)).
| Level | Wire value | What happened |
|---|---|---|
| Scanned | scanned | workrr read a public commit; checks passed; not signed by the maintainer |
| Signed | banded | The maintainer's own CI signed it; checks passed |
| Tested | silver | Signed, and the artifact's tests passed in an isolated job |
| Reproduced | gold | Two independent forges certified the same digest |
Flagged is outside the ladder: a signed finding for the newest commit.
GitHub Marketplace
The action GitHub Marketplace can list is workrr verify
(action.yml). It is a consumer gate: one step fails the job
unless an artifact already has a workrr certificate at the level and source you
name.
Why this action does not certify
A certificate is trusted only when its Fulcio identity is
https://github.com/workrr-trust/certify/.github/workflows/certify.yml@refs/tags/v0.1.0
or the same path at v0.1.1 to v0.1.5. Each released tag except the
newest is pinned to its annotated tag object, the Build Signer Digest Fulcio records
(9fbe3e45…, 78d8c1a9…, 7ed4c1de…, 7cf13e72…, 54b6c3a8…; full values in
src/trust-policy.ts). The newest release (v0.1.5) is
accepted by exact tag, because a release can't contain its own tag object; the next
release pins it. Release tags are protected, and the public ledger alerts if one moves.
Fulcio sets that identity from job_workflow_ref of the called reusable
workflow, not from the caller. A composite action.yml does not get its own
job. Its steps run inside the caller's job, so a signature minted there would
name the caller's workflow. The trust policy rejects that certificate.
Certification stays on .github/workflows/certify.yml
at a released tag (v0.1.5 today). Public scans and findings sign from
scan.yml instead (level Scanned; see above). It is
dispatched, not called, so Fulcio records the tagged commit for it, and older
releases of it are pinned to that commit (14466afa… for v0.1.4).
The action also has no input that replaces the trust policy. The signer set is
the built-in policy in the commit you pin. level: gold needs the GitLab
witness certificate next to the publisher's (see Verify).
Publishers: docs/quickstart.md. Consumers:
permissions:
contents: read
jobs:
verify:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: workrr-trust/certify@<40-character SHA>
with:
path: skills/my-skill
bundle: certificate.sigstore.json
source: owner/repo
level: silverReplace <40-character SHA> with the full commit you reviewed (from v0.1.2
on, a release tag's commit contains action.yml). v0.1.0 and v0.1.1 do not
contain it. Do not use a moving tag. Grant contents:
read only. id-token and attestations belong on the certify job, not here.
In this repository the same step is uses: ./ (examples/verify.yml).
Starter workflows live in .github/workflow-templates/.
The verifier is built from that commit's package-lock.json (npm ci), not
from an npm dist-tag. The install does not run dependency scripts, and the
step clears the OIDC request variables before it builds.
Publishing the listing
This repository does not publish itself. An owner does the following after review. Never move a release tag.
- Merge the change that adds
action.yml. Confirm the repository is public and thataction.ymlis the only action metadata file at the repository root. Marketplace lists that file. Workflows and the Go module stay in this repo so the verifier and the certifier are the same commit. - Confirm
nameisworkrr verify, withbranding.icon: shieldandbranding.color: blue. An organization namedWorkrralready exists, so the name must not beworkrr. GitHub also rejects a name that matches an existing Marketplace action or a Marketplace category. If it rejectsworkrr verify, renamenameand release again. Do not rename by moving a tag. - Accept the GitHub Marketplace Developer Agreement.
An organization owner has to do this. Open
action.ymlon GitHub; the publish banner links the agreement when it is still outstanding. The publish checkbox stays disabled until an owner accepts it. - Turn on two-factor authentication for the account that publishes. GitHub requires it to publish a release.
- Cut a new immutable tag.
release.ymlruns onv*and requirespackage.json,src/version.ts, andgo/certify.goto equal the tag. Bump those three to X.Y.Z and merge that first. Then, because tags are protected and the workflow cannot create them:git tag -a vX.Y.Z -m vX.Y.Z,git tag -a go/vX.Y.Z -m go/vX.Y.Zon the same commit, andgit push origin vX.Y.Z go/vX.Y.Z. - Before tagging, the release commit's trust policy must pin the previous
release's tag object (
git rev-parse vPREV, notvPREV^{commit}) and acceptcertify.yml@refs/tags/vX.Y.Zby exact tag. A release can't pin its own tag object; the next release does. Then regenerate the test vectors (node spec/test-vectors/generate.mjs && node spec/trust-feed/generate.mjs). - Let
release.ymlcreate the GitHub release and publish npm. It does not tick the Marketplace checkbox. If you create the release from theaction.ymlbanner first,gh release createinsiderelease.ymlcan fail because the release already exists. The npm job is separate; check it. - Edit the release. Check Publish this Action to the GitHub Marketplace. Fix any metadata warning until GitHub reports that everything looks good. Primary category: Security. Publish. That is the step that lists the action.
- Do not create a floating major tag (
v1,v0). A tag in this repository is never moved. Consumers pinworkrr verifyby full commit SHA. After the release exists,git rev-parse vX.Y.Z^{commit}is that SHA. A Marketplace "use latest" snippet that inserts@vX.Y.Zis a tag; replace it with the commit SHA. - Smoke the listing from a different public repository: one step, SHA pin,
contents: read, no OIDC permission. A missing bundle fails the job. A certificate from[email protected]passes atlevel: silverwhensourceis the publisher.
Verify
# npm (published with provenance)
npx @workrr-trust/certify verify ./my-skill --bundle certificate.sigstore.json
# Go
go install github.com/workrr-trust/certify/go/cmd/workrr@latest
workrr verify ./my-skill --bundle certificate.sigstore.json
# Machine-readable JSON output (ok, level, artifactDigest, source, policyName, policyDigest, policyStatus, bundles, notes)
npx @workrr-trust/certify verify ./my-skill --bundle certificate.sigstore.json --jsonPrebuilt binaries for Linux, macOS and Windows are attached to each
release with build attestations:
gh attestation verify <file> --repo workrr-trust/certify.
Checks the artifact digest, the Sigstore certificate chain and signature, Rekor
inclusion, that the signer is a released workrr certifier tag pinned in the trust
policy, and that the certificate is not revoked. The output names the publisher
repository and the policy name and digest. Silver, gold, and banded are reported
only when that digest is the known baseline. --source <repo> fails when the
certificate's publisher does not match. Gold (Reproduced) needs a second
bundle from the GitLab witness (docs/gitlab-certifier.md):
pass both --bundle files, and the publisher's Tested certificate and the
witness must name the same repository, commit, path and checks.
verify fetches the signed trust policy from the trust API (cached for one hour)
and the revocation feed on every run. --offline uses the policy embedded in this
build and does not check revocation. It prints
offline: using the embedded trust policy; revocation was not checked. A revoked
certificate can still verify in that mode. The policy and revocation formats are
spec §7. Until the trust API is deployed, fetch failure falls
back to the embedded policy (the released tags, as above) and says so.
Local use
npm ci && npm run build
node dist/cli.js digest <path>
node dist/cli.js certify <path> --run-tests # local only; never in a signing job
npm testWhat the checks claim
| Check | Claim |
|---|---|
| provenance | The artifact is committed and unchanged at the recorded source commit |
| policy | Conforms to the named policy, pinned by digest. The name workrr-baseline-v0.1 is reserved for that baseline; a verifier reports a baseline level only for its digest |
| scan | No high/critical findings from ruleset workrr-scan-v0.3 (v0.2 from 0.1.6, v0.1 before), pinned by digest. Not "safe" or "injection-free." |
| tests | The artifact's own declared suite passed. Without a suite the level is Signed (banded). |
Known limits (v0.1)
- The signed trust policy and revocation feed are implemented (spec §7,
trust API). No policy has been signed or deployed yet, so
verifyfalls back to the embedded policy and does not see revocations until that deploy. That fallback is the v0.1.5 trust set:certify.ymlatrefs/tags/v0.1.0tov0.1.4, each pinned to the Build Signer Digest Fulcio recorded for that annotated tag (the tag object, not the peeled commit), andv0.1.5by exact tag, plus thescan.ymlobserver atv0.1.4(pinned to its commit) andv0.1.5(by exact tag).refs/heads/mainis not accepted.EMBEDDED_POLICY_SEQUENCEis still 0. The policy-signer and revoke-signer Build Signer Digests are still empty. - The Marketplace action only verifies. It does not add a signer. A certificate
still has to come from
certify.ymlat a released tag. - A policy digest other than
workrr-baseline-v0.1does not verify at a baseline level. Reusing that name on a different document is rejected. --offlineskips revocation on purpose. See the warning it prints.- Workflow actions are pinned by commit SHA.
- Private repos sign with GitHub's private Sigstore instance (no public Rekor entry). The private tier uses its own trust root and the hash-only public log design.
- Gold's second forge is the GitLab witness, which workrr runs in
gitlab.com/workrr-trust/certify-gitlab. A publisher's own GitLab CI can't be a signer yet (GitLab can't sign from an included CI file).
