Your security tools already know if your controls work. Nobody can verify that. Until now.
Website · Documentation · CPOE Spec · Verify a CPOE · Generate trust.txt
Compliance trust today is exchanged via PDF. SOC 2 reports, pentest results, ISO 27001 certificates — emailed as attachments, stored in shared drives, re-requested every quarter. They are machine-unreadable, unverifiable, and impossible to validate without trusting the sender.
CORSAIR signs tool output as a CPOE (Certificate of Proof of Operational Effectiveness) — a W3C Verifiable Credential with an Ed25519 signature. Your scanner says PASS, Corsair signs "the scanner said PASS." The tool's finding, signed, verifiable.
A CPOE is:
- Machine-readable — structured JSON, not a PDF
- Cryptographically verifiable — Ed25519 signature, anyone can check
- Provenance-tracked — records who produced the evidence, not just what it says
Anyone can verify a CPOE. Free to check. No account required. Four steps with any JWT library.
# Install (pick one)
npm install -g @grcorsair/cli # npm
brew install grcorsair/corsair/corsair # homebrew
# Runtime
# Bun is required to run the CLI. Homebrew installs Bun automatically via the `oven-sh/bun` tap; npm does not.
npx skills add grcorsair/corsair # AI agent skill (Claude Code, Cursor, 25+ agents)
# Initialize a project (generates keys + example evidence)
corsair init
# Sign your tool output as a CPOE (keys auto-generate on first use)
corsair sign --file evidence.json
# Keyless sign via API (OIDC or API key)
corsair sign --file evidence.json --auth-token $OIDC_TOKEN --api-url https://api.grcorsair.com
# Verify any CPOE (always free, no account needed)
corsair verify --file cpoe.jwt
# Verify by domain (resolves trust.txt + catalog)
corsair verify --domain acme.com
# Compare two CPOEs over time (like git diff)
corsair diff --current q2.jwt --previous q1.jwt| Variable | Purpose | Required |
|---|---|---|
DATABASE_URL |
Postgres connection string for persistence | Yes (server) |
CORSAIR_KEY_ENCRYPTION_SECRET |
32-byte key for AES-256-GCM signing key encryption (64 hex chars or base64) | Yes (server) |
CORSAIR_API_KEYS |
Comma-separated API keys for authenticated endpoints | Yes (prod, unless OIDC is configured) |
CORSAIR_OIDC_CONFIG |
JSON config for OIDC issuers (keyless signing) | No |
CORSAIR_DOMAIN |
Public domain for DID:web and trust.txt generation | Recommended |
CORSAIR_TRUST_HOST |
Hostname for hosted trust.txt URLs (defaults to trust.<CORSAIR_DOMAIN>) |
Optional |
CORSAIR_ALLOWED_ORIGINS |
Comma-separated CORS origins for authenticated routes | Optional |
CORSAIR_MAPPING_PACK_PUBKEY |
Ed25519 public key PEM to verify signed mapping packs | Optional |
OPENROUTER_API_KEY |
Enables multi-model GRC JSON translator endpoint (POST /grc/translate) |
Optional |
OPENROUTER_HTTP_REFERER |
HTTP Referer sent to OpenRouter (defaults to https://grcorsair.com) |
Optional |
GRC_TRANSLATOR_ENABLED |
Enable/disable translator endpoint (default true) |
Optional |
GRC_TRANSLATOR_MAX_INPUT_BYTES |
Max translator input size in bytes (default 131072) |
Optional |
GRC_TRANSLATOR_MAX_MODELS |
Max models per translator request (default 10) |
Optional |
GRC_TRANSLATOR_MODEL_TIMEOUT_MS |
Per-model timeout in milliseconds (default 20000) |
Optional |
GRC_TRANSLATOR_MAX_OUTPUT_TOKENS |
Max model output tokens (default 500) |
Optional |
ENABLE_DELIVERY_WORKER |
Enables SSF delivery worker loop (true to enable) |
Optional |
DELIVERY_WORKER_INTERVAL |
SSF delivery worker poll interval ms (default 30000) |
Optional |
CORSAIR_OIDC_CONFIG example:
{
"providers": [
{
"issuer": "https://accounts.google.com",
"audiences": ["corsair-sign"],
"requireJti": true,
"claimMapping": {
"subject": "sub",
"email": "email",
"organization": "hd"
}
}
]
}- CLI / local use: file-based, no database required.
- Hosted API / production: Postgres is required via
DATABASE_URLfor keys, SCITT, and audit trails. - Migrations are applied automatically on API startup (idempotent).
src/db/migrations/014_data_capture_foundation.sql adds the non-ephemeral metadata base:
event_journal(append-only): internal telemetry emitted by hosted protocol operationsssf_streams.owner_type/owner_id: per-owner isolation for SSF stream CRUDscitt_entries.tree_sizeuniqueness constraint: continuity hardening for append order
Current emitted event_journal.event_type values:
sign.success,sign.failureissue.success,issue.failureverify.success,verify.failurescitt.entry.registeredssf.stream.created,ssf.stream.read,ssf.stream.updated,ssf.stream.deletedtrusttxt.hosted.upsert,trusttxt.hosted.verified,trusttxt.hosted.public_fetch
event_journal is intentionally write-only in normal operation (UPDATE/DELETE are blocked by DB rules).
Read access is exposed via authenticated API: GET /intelligence/events (actor-scoped).
Example query:
SELECT event_type, date_trunc('day', occurred_at) AS day, count(*) AS events
FROM event_journal
GROUP BY event_type, day
ORDER BY day DESC, events DESC;Corsair ships with a simple, shareable surface that maps directly to how people verify compliance in the real world.
- trust.txt — publish discoverable proofs at
/.well-known/trust.txtor via delegated DNS - 4-line verification — verify any CPOE with standard JWT libs (see
CPOE_SPEC.md) - corsair diff — drift detection that reads like
git diff
corsair diff --current q2.jwt --previous q1.jwtCorsair does six things. Like git.
| Primitive | Command | What It Does | Analogy |
|---|---|---|---|
| SIGN | corsair sign --file <path> |
Parse tool output, record provenance, sign JWT-VC | git commit |
| LOG | corsair log |
List CPOEs from local files or a SCITT log | git log |
| PUBLISH | corsair trust-txt generate |
Generate trust.txt for proof discovery | git push |
| VERIFY | corsair verify --file <cpoe.jwt> |
Verify Ed25519 signature, apply policy checks | git verify-commit |
| DIFF | corsair diff --current <new> --previous <old> |
Compare two CPOEs, detect regressions | git diff |
| SIGNAL | corsair signal generate |
Generate FLAGSHIP SETs for real-time notifications | git webhooks |
corsair did generate --domain example.com --output did.json
corsair did jwks --domain example.com --output jwks.jsoncorsair sign --file evidence.json # Auto-detect mapping pack, sign
corsair sign --file evidence.json --json # Structured JSON output
corsair sign --file evidence.json --dry-run # Preview without signing
corsair sign --file evidence.json --strict # Enforce minimum ingestion contract
corsair sign --file evidence.json --sd-jwt # SD-JWT selective disclosure
corsair sign --file evidence.json --sd-jwt --sd-fields scope # Disclose only scope
corsair sign --file evidence.json --mapping ./mappings/toolx.json # Apply mapping file
corsair sign --file evidence.json --mapping ./mappings/ # Apply mapping directory
corsair sign --file evidence.json --dependency https://vendor.com/cpoe.jwt # Attach dependency proof
corsair sign --file evidence.json --source tool # Override provenance source
corsair sign --file evidence.json --baseline baseline.cpoe.jwt --gate # Fail on regression vs baseline
corsair sign --file evidence.json --auth-token $OIDC_TOKEN --api-url https://api.grcorsair.com # Keyless sign
corsair sign --file - < data.json # Sign from stdincorsair mappings list # Show loaded mappings
corsair mappings list --json # Machine-readable output
corsair mappings validate --json # Validate mappings
corsair mappings add https://example.com/pack.json # Add a mapping pack
corsair mappings pack --id wiz --version 1.1.7 --mapping ./mappings # Build a pack
corsair mappings sign --file pack.json --key ./keys/mapping-pack.key # Sign a packMapping packs can be signed. If a pack includes a signature, set
CORSAIR_MAPPING_PACK_PUBKEY to the Ed25519 public key PEM to enforce verification.
corsair verify --file cpoe.jwt --json # Structured JSON output
corsair verify --file cpoe.jwt --did # Verify via DID:web
corsair verify --url https://acme.com/cpoe.jwt # Verify remote CPOE
corsair verify --domain acme.com # Resolve trust.txt + catalog
corsair verify --domain acme.com --all # Verify all published CPOEs
corsair verify --file cpoe.jwt --require-issuer did:web:acme.com
corsair verify --file cpoe.jwt --require-framework SOC2,ISO27001
corsair verify --file cpoe.jwt --max-age 30 --min-score 90
corsair verify --file cpoe.jwt --receipts receipts.json
corsair verify --file cpoe.jwt --evidence evidence.jsonl
corsair verify --file cpoe.jwt --require-source tool --require-source-identity "Scanner v1.2"
corsair verify --file cpoe.jwt --require-tool-attestation --require-receipts --receipts receipts.json
corsair verify --file cpoe.jwt --require-evidence-chain --evidence evidence.jsonl
corsair verify --file cpoe.jwt --require-input-binding --source-document raw-evidence.json
corsair verify --file cpoe.jwt --require-scitt --receipts receipts.json
corsair verify --file cpoe.jwt --policy policy.json
corsair verify --file cpoe.jwt --dependencies
corsair verify --file cpoe.jwt --dependencies --dependency-depth 2--source-document computes a canonical JSON hash (sorted keys) and compares it to provenance.sourceDocument.
Policies encode acceptance criteria as portable JSON files:
corsair policy validate --file policy.json
corsair verify --file cpoe.jwt --policy policy.jsonEvidence receipts prove that a specific evidence record exists in the evidence chain without revealing the record itself.
corsair receipts generate --evidence evidence.jsonl --index 0 --output receipt.json
corsair receipts verify --file receipt.json --cpoe cpoe.jwtcorsair diff --current new.jwt --previous old.jwt
corsair diff --current new.jwt --previous old.jwt --verify
corsair diff --current new.jwt --previous old.jwt --json
corsair diff --domain acme.com --verifycorsair signal stream create --auth-token $API_KEY --api-url https://api.grcorsair.com \\
--delivery push --endpoint https://receiver.example.com/ssf \\
--events colors-changed,compliance-change --audience did:web:buyer.comCorsair supports a discovery layer modeled after security.txt. Organizations publish
/.well-known/trust.txt so verifiers can discover DID identity, current CPOEs,
SCITT log endpoints, optional catalog snapshots, policy artifacts, and FLAGSHIP streams.
For large numbers of proofs, keep trust.txt tiny and point to SCITT + catalog.
If you don’t run your own SCITT log, you can use the hosted Corsair log at
https://api.grcorsair.com/scitt/entries.
If you can’t host at the root domain, delegate discovery via DNS:
- TXT:
_corsair.example.com TXT "corsair-trusttxt=https://trust.example.com/trust.txt" - TXT (optional hash pin):
_corsair.example.com TXT "corsair-trusttxt-sha256=<sha256>" - CNAME:
trust.example.com CNAME trust.your-host.com
corsair did generate --domain acme.com --output did.json
corsair did jwks --domain acme.com --output jwks.json
corsair trust-txt generate --did did:web:acme.com --scitt https://api.grcorsair.com/scitt/entries?issuer=did:web:acme.com
corsair trust-txt generate --did did:web:acme.com --catalog https://acme.com/compliance/catalog.json
corsair trust-txt generate --did did:web:acme.com --policy https://acme.com/.well-known/policy.json
corsair trust-txt generate --did did:web:acme.com --cpoe-url https://acme.com/soc2.jwt
corsair trust-txt discover acme.com --verifyHosted option: use POST /trust-txt/host on the API to generate a hosted trust.txt URL plus DNS TXT records for delegation.
Corsair auto-detects evidence via mapping packs or falls back to the generic format.
Use the mapping registry to extract controls or passthrough fields without code changes
(see --mapping and CORSAIR_MAPPING_DIR). Mappings are evaluated by priority (higher wins),
then filename order.
Mappings may set sourceTier (native|tool|platform|human) to override tier classification.
| Format | Purpose | Detection |
|---|---|---|
mapping-pack |
Tool-specific mappings (config-driven) | Auto-detected via --mapping or CORSAIR_MAPPING_* |
generic |
Any JSON with { metadata, controls[] } |
Default fallback |
Minimum ingestion contract: evidence must include an issuer (or auditor), assessment date, and scope.
Use --strict to fail fast when any of these are missing; otherwise Corsair returns warnings.
Source tiers (deterministic): Corsair derives a source tier from the document source.
Tool outputs (tool, json) map to the tool tier, while audits and manual evidence
(soc2, iso27001, pentest, manual) map to the human tier.
A CPOE is a JWT with three base64url-encoded segments: header.payload.signature
┌──────────────────────────────────────────────────────────────────┐
│ HEADER { "alg": "EdDSA", "typ": "vc+jwt", "kid": "did:web:..." } │
├──────────────────────────────────────────────────────────────────┤
│ PAYLOAD { "iss": "did:web:...", "vc": { ... CPOE ... }, "parley": "2.1" }│
├──────────────────────────────────────────────────────────────────┤
│ SIGNATURE Ed25519 │
└──────────────────────────────────────────────────────────────────┘
The credential subject records provenance and summary — who produced the evidence, what they found:
{
"type": "CorsairCPOE",
"scope": "SOC 2 Type II — Acme Cloud Platform",
"provenance": {
"source": "tool",
"sourceIdentity": "Cloud Scanner v1.2",
"sourceDate": "2026-01-15"
},
"summary": {
"controlsTested": 46,
"controlsPassed": 42,
"controlsFailed": 4,
"overallScore": 91
},
"evidenceChain": {
"chainType": "hash-linked",
"algorithm": "sha256",
"canonicalization": "sorted-json-v1",
"recordCount": 128,
"chainVerified": true,
"chainDigest": "f4c1..."
},
"frameworks": {
"SOC2": { "controlsMapped": 24, "passed": 22, "failed": 2 },
"NIST-800-53": { "controlsMapped": 22, "passed": 20, "failed": 2 }
},
"extensions": {
"mapping": { "id": "toolx-evidence-only", "evidenceOnly": true },
"passthrough": { "summary": { "passed": 12, "failed": 2 } }
},
"processProvenance": {
"chainDigest": "a7f3e2...",
"receiptCount": 4,
"chainVerified": true,
"format": "in-toto/v1+cose-sign1"
}
}1. Decode ─── Parse JWT header + payload (base64url)
2. Resolve ─── Fetch issuer's DID document via HTTPS
3. Extract ─── Find the public key matching header.kid
4. Verify ─── Check Ed25519 signature
Anyone can do this. No Corsair account needed.
Corsair records where evidence came from and lets buyers decide what's sufficient.
| Provenance | Source | Example |
|---|---|---|
| Self | Organization self-reports | Policy documents, manual attestation |
| Tool | Automated scanning tools | CSPM, SAST, vuln scanners |
| Auditor | Independent third party | SOC 2 auditor, ISO 27001 certification body |
The CPOE is a signed fact: "The scanner said PASS on Jan 15." Not an opinion. Not a score. A verifiable record of what a tool found.
Companies fear publishing detailed control data. Corsair solves this with three privacy layers — share proof, not secrets.
| Layer | What It Does | How |
|---|---|---|
| Summary-Only CPOEs | Aggregate pass/fail counts, no raw evidence | Default CPOE omits control details and configuration data |
| Evidence Sanitization | Strip sensitive identifiers before signing | ARNs, IPs, file paths, account IDs, API keys removed recursively |
| SD-JWT Selective Disclosure | Reveal only chosen claims per verifier | IETF SD-JWT — holder controls which fields are disclosed |
Register a CPOE in the transparency log without storing the credential itself — only a SHA-256 hash and COSE receipt. The CPOE is shared bilaterally while the log proves it was registered at a specific time.
corsair log register --file cpoe.jwt --scitt https://log.example.com/scitt/entries --proof-onlyAttach other issuers’ CPOEs as dependency proofs to build a composable trust graph. Each dependency stores the issuer, scope, and a hash of the referenced CPOE:
corsair sign --file evidence.json --dependency https://vendor.com/cpoe.jwt
corsair verify --file cpoe.jwt --dependenciescorsair sign --file evidence.json --sd-jwt # SD-JWT with default disclosable fields
corsair sign --file evidence.json --sd-jwt --sd-fields scope # Only scope is disclosable ┌─────────────────────┐
│ Tool / Platform │ CSPM, SAST, vuln scanners,
│ Evidence Output │ API exports, tool outputs
└──────────┬──────────┘
│
┌──────────▼──────────┐
01 │ SIGN │ Parse → Provenance → Sign JWT-VC (Ed25519)
└──────────┬──────────┘
│
┌──────────▼──────────┐
02 │ LOG │ Register in SCITT transparency log
└──────────┬──────────┘
│
┌──────────▼──────────┐
03 │ VERIFY │ Anyone verifies (free, no account)
└──────────┬──────────┘
│
┌──────────▼──────────┐
04 │ DIFF │ Compare CPOEs, detect regressions
└──────────┘
That's it. Tool output goes in, signed proof comes out.
The protocol composing Corsair is called Parley. It composes open standards so any JWT library can verify a CPOE. Zero vendor lock-in.
| Standard | Role | Implementation |
|---|---|---|
| JWT-VC | Attestation envelope | CPOE as W3C Verifiable Credential, Ed25519-signed |
| DID:web | Issuer identity | DNS-based decentralized identifiers |
| SCITT | Transparency log | Append-only registry with COSE receipts + Merkle proofs |
| SSF/CAEP | Real-time signals | Compliance change notifications via signed SETs |
| Ed25519 | Signatures | Curve25519 — fast, compact, no weak keys |
| in-toto/SLSA | Process provenance | COSE-signed pipeline receipts with Merkle root chain |
| SD-JWT | Selective disclosure | Prove specific claims without revealing the full CPOE |
Organizations are identified via did:web DIDs. The DID document at /.well-known/did.json contains the Ed25519 public key for CPOE verification.
did:web:grcorsair.com → https://grcorsair.com/.well-known/did.json
did:web:acme.com → https://acme.com/.well-known/did.json
Real-time compliance signals via OpenID SSF/CAEP:
| Event | CAEP Type | Trigger |
|---|---|---|
FLEET_ALERT |
compliance-change |
Drift detected |
PAPERS_CHANGED |
credential-change |
CPOE issued, renewed, or revoked |
MARQUE_REVOKED |
session-revoked |
Emergency revocation |
npx skills add grcorsair/corsairWorks with Claude Code, Cursor, GitHub Copilot, and 25+ AI agents. Your agent can then sign evidence, verify CPOEs, detect compliance drift, and autonomously assess vendor compliance via trust.txt.
bun run bin/corsair-mcp.tsTools: corsair_sign, corsair_verify, corsair_diff, corsair_formats
{ "corsair": { "command": "bun", "args": ["run", "bin/corsair-mcp.ts"], "env": { "CORSAIR_KEY_DIR": "./keys" } } }- uses: grcorsair/corsair@main
with:
file: evidence.json
id: sign# Sign (requires auth: API key or OIDC token)
curl -X POST https://api.grcorsair.com/sign \
-H "Authorization: Bearer $AUTH_TOKEN" \
-d '{"evidence": {...}, "registerScitt": true}'
# Onboard (generate did.json, jwks.json, trust.txt)
curl -X POST https://api.grcorsair.com/onboard \
-H "Authorization: Bearer $AUTH_TOKEN" \
-d '{"contact":"security@acme.com","frameworks":["SOC2"]}'
# Verify (no auth required)
curl -X POST https://api.grcorsair.com/verify \
-d '{"cpoe": "eyJ..."}'
# Multi-model GRC JSON translator (no auth required)
curl -X POST https://api.grcorsair.com/grc/translate \
-H "Content-Type: application/json" \
-d '{"payload":{"framework":"SOC2","controls":[{"id":"CC6.1","status":"pass"}]},"mode":"quick","redact":true}'
# Issue a CPOE via API
curl -X POST https://api.grcorsair.com/issue \
-H "Authorization: Bearer $AUTH_TOKEN" \
-H "Content-Type: application/json" \
-d '{"source":"tool","metadata":{"issuer":"Acme","assessmentDate":"2026-02-20","scope":"Production"},"controls":[{"id":"CC6.1","title":"MFA","status":"pass"}]}'
# Intelligence events (auth required; actor-scoped)
curl "https://api.grcorsair.com/intelligence/events?limit=25&status=failure" \
-H "Authorization: Bearer $AUTH_TOKEN"The SDK is coming soon and not actively maintained. It is not published to npm. If you need a packaged dependency today, use the CLI or API.
For internal development only, you can use packages/sdk as a workspace after cloning the repo.
bun test # 1210 tests, 85 files — all passing| Component | Technology |
|---|---|
| Runtime | Bun — TypeScript, no build step |
| Crypto | Ed25519 via Node.js crypto + jose |
| Database | Postgres via Bun.sql — zero-dep driver |
| Web | Next.js 16 + Tailwind 4 + shadcn/ui |
| Standards | W3C VC 2.0, IETF SCITT, OpenID SSF/CAEP |
Dependencies: 1 runtime dep —
jose(JWT/JWK). Everything else is hand-rolled.
Lost in the acronyms? Here's every term in plain English.
| Term | What It Means |
|---|---|
| CPOE | Certificate of Proof of Operational Effectiveness — a signed compliance proof. Think "digitally signed SOC 2 result." |
| Parley | The open protocol behind Corsair. Like SMTP is for email, Parley is for compliance proofs. |
| MARQUE | A signed CPOE — the actual JWT you hand to a verifier. Named after letters of marque (pirate commissions). |
| FLAGSHIP | Real-time compliance change notifications. If your controls drift, subscribers know immediately. |
| Term | What It Means |
|---|---|
| JWT-VC | JSON Web Token — Verifiable Credential. A W3C standard for digitally signed claims. The envelope a CPOE lives in. |
| DID:web | Decentralized Identifier anchored to a domain. did:web:acme.com means "look up acme.com's public key at /.well-known/did.json." |
| Ed25519 | A modern digital signature algorithm. Fast, small, no weak keys. What Corsair signs with. |
| SCITT | Supply Chain Integrity, Transparency, and Trust — an IETF draft for append-only transparency logs. Corsair's audit trail. |
| SSF/CAEP | Shared Signals Framework / Continuous Access Evaluation Protocol — OpenID standards for real-time security events. Powers FLAGSHIP. |
| SD-JWT | Selective Disclosure JWT — prove specific claims without revealing the full document. Share your SOC 2 score without exposing every control. |
| in-toto/SLSA | Supply chain provenance standards. Records the full pipeline that produced a CPOE — who ran what, when, in what order. |
| COSE | CBOR Object Signing and Encryption — a compact binary signing format. Used in SCITT receipts. |
| Term | What It Means |
|---|---|
| GRC | Governance, Risk, and Compliance — the industry Corsair operates in. |
| SOC 2 | A trust framework for service organizations. The most common compliance report in SaaS. |
| NIST 800-53 | A US government catalog of security controls. One of many frameworks Corsair maps evidence to. |
Project Structure
corsair.ts # CLI entry point
src/
types.ts # Core type definitions
evidence.ts # JSONL evidence engine with SHA-256 hash chain
sign/ # Sign engine
ingestion/ # Evidence parsing (mapping packs + generic)
parley/ # Parley protocol (JWT-VC, SCITT, DID, COSE, CBOR, Merkle)
flagship/ # FLAGSHIP real-time signals (SSF/CAEP)
security/ # URL validation for DID resolver
middleware/ # HTTP auth, rate-limit, security headers
db/ # Postgres via Bun.sql + migrations
api/ # Versioned API router
mcp/ # MCP server
bin/ # Standalone CLIs (verify, DID, MCP)
functions/ # Railway API endpoints
apps/web/ # grcorsair.com (Next.js 16)
packages/sdk/ # @grcorsair/sdk (coming soon, not actively maintained)
tests/ # Test suite
- SCITT entries are append-only by design. Entries cannot be deleted or modified after registration.
- Event journal rows are append-only by design. Signal metadata is retained for longitudinal analysis.
- Signing keys are encrypted at rest (AES-256-GCM). Retired keys are preserved for historical CPOE verification.
See SECURITY.md for vulnerability reporting.
Network access notice: the CLI and libraries perform outbound HTTPS requests for DID:web resolution, trust.txt discovery, SCITT, and SSF/CAEP endpoints. Offline usage is supported by providing local CPOEs and public keys and avoiding discovery calls.
See CONTRIBUTING.md for the Pirate's Code.
Code is licensed under Apache 2.0. Specifications (CPOE_SPEC.md) are licensed under CC BY 4.0. See NOTICE for the full licensing architecture.