@axtary/actionpass
v0.6.1
Published
Scoped, signed ActionPass artifacts for runtime-governed AI agent actions.
Maintainers
Readme
@axtary/actionpass
Scoped, short-lived, signed, and proof-of-possession-bound authorization for an exact AI-agent action and payload. Start with the five-verb SDK facade; use the lower-level status, delegation, and proof APIs when an integration needs them.
Early 0.x release: the runtime path is real and tested, but the API is not stable yet and may change between minor versions.
The source repository is currently private. Public product documentation and runnable guides are at axtary.com/docs.
npm install @axtary/actionpassThe base format lives in spec/actionpass-v0.md; the sender-constrained
cnf/DPoP and delegation profile is spec/actionpass-v1.md; authenticated
status distribution is spec/actionpass-v2.md.
What It Does
- Validates normalized agent actions at runtime.
- Produces canonical SHA-256 payload hashes.
- Produces payload-bound approval artifacts for exact human or policy override approvals.
- Issues signed ActionPass JWT/JWS artifacts for allowed actions.
- Issues ActionPass v1 bound to a holder key with RFC 7800
cnf.jkt. - Creates and verifies RFC 9449 DPoP proofs with method/URI/token binding, nonce, clock-window, and replay enforcement.
- Backs replay enforcement with a shared
DpopReplayStore: an in-processInMemoryDpopReplayStoreand a durable, lock-serializedFileDpopReplayStore(0600) that keeps a captured proof rejected across a verifier restart within its window and compacts itself to the in-flight proof window. Single-host state, not cross-host distribution. - Exchanges a holder-authorized v1 pass for an attenuated child pass, walks root-to-leaf delegation chains, enforces remaining depth, and binds the child to a distinct sub-agent key.
- Applies one revocation source across the full delegation chain, so revoking a root or intermediate parent invalidates every downstream child without enumerating descendants.
- Binds authority-owned budget reservation cost/limit/state into the pass so a presenter cannot alter metering fields.
- Produces durable local revocation records and rejects revoked passes during verification; a revocation-source error also fails closed.
- Verifies passes against a keyring by
kidso rotated keys can coexist. - Persists local public verification keys and revocations in a JSON trust store.
- Issues and verifies ActionPass v2 with a signed Token Status List reference, freshness-bounded caching, and fail-closed unavailable/stale/invalid status.
- Keeps status-list and remote SSF JWKS retrieval explicit: callers inject a
transport function and missing transports fail closed with
status_list_transport_requiredorssf_jwks_transport_required, so the package does not use ambient global network access on its own. - Uses the IESG-approved Token Status List revision-21 wire format through the
pinned, stricter
axtary.status-list.v1profile, so RFC number assignment is not a runtime dependency. - Validates
axtary.provenance.v0field/source bindings and binds their canonical hash into ActionPasses and ledger records. - Persists the issuer ES256 keyring, publishes public JWKS by
kid, and rotates with bounded retired-key overlap. - Verifies generic final SSF/CAEP
session-revokedSETs and maps their explicit subjects to live delegation roots without inventing provider support. Remote transmitter JWKS lookup uses only the caller-injected transport. - Verifies that a signed pass and any embedded approval evidence still match the exact action payload.
- Records ledger entries with hashable decision evidence and parent-to-child delegation edges.
- Owns the provider-neutral native-connector governance descriptors used to derive GitHub/Jira/Linear/Postgres/Google Drive capability metadata, normalized evidence dispatch, config defaults, and doctor scope/smoke metadata without importing secret-bearing adapter runtimes.
Current Status
0.x versions are early releases. Do not use them for production authorization yet.
Before production use, Axtary still needs:
- Stable schema versioning.
- External/HSM signing-key management.
- Hosted approval queue integration.
- External security review.
The package builds to dist/ and publishes JavaScript plus TypeScript declarations.
SDK facade
For most callers the axtary facade is the simplest entry point: five verbs —
authorize, verify, record, revoke, explain — over a flat request
shape. It delegates to the lower-level functions below and adds no new
authorization logic.
import { axtary } from "@axtary/actionpass";
const decision = await axtary.authorize({
agent: "codex-prod",
human: "[email protected]",
intent: "Open a PR for Linear issue AXT-418",
tool: "github.pull_requests.create",
resource: "repo:company/web-app",
payload,
});
if (decision.status === "allow") {
await github.createPullRequest(payload);
}With no signing key configured, the facade signs with a persistent local dev
key from a 0600 keyring file under .axtary/, so quickstart passes verify
across restarts with zero key code and no ambient environment-variable reads.
For CI/container dev keys, call devKeypair({ env: process.env }) explicitly.
Call devKeypair() to obtain the file-backed keypair directly, or
createAxtary({ issuer, signingKey, verificationKey }) to use your own issuer
key in production. See the SDK guide.
Quickstart (low-level functions)
This example runs as-is with Node 20+:
import { generateKeyPair } from "jose";
import {
authorize,
createApprovalArtifact,
demoAction,
verifyActionPass,
} from "@axtary/actionpass";
const { publicKey, privateKey } = await generateKeyPair("ES256");
// Bind a human approval to the exact payload hash.
const { artifact } = createApprovalArtifact({
action: demoAction,
mode: "human",
approvedBy: "user:[email protected]",
reason: "Reviewed the exact PR payload",
});
// Evaluate policy, issue a signed ActionPass, produce a ledger record.
const result = await authorize({
action: demoAction,
issuer: "https://axtary.local",
tenant: "org:example",
signingKey: privateKey,
approvalArtifact: artifact,
});
console.log(result.decision.decision, result.payloadHash);
// Verification fails closed on expiry, revocation, or payload mismatch.
const verified = await verifyActionPass({
token: result.actionPass.token,
action: demoAction,
verificationKey: publicKey,
issuer: "https://axtary.local",
});
console.log(verified.valid);Security Notes
ActionPass is designed to fail closed:
- Malformed actions fail schema validation.
- Denied and step-up actions do not receive passes.
- Verification rejects expired tokens.
- Verification rejects revoked pass IDs.
- Delegation verification checks every root-to-leaf member against the same revocation source; an ancestor revoke cascades to all descendants.
- Verification rejects when a configured revocation source cannot be queried; it never treats source failure as an empty set.
- Verification rejects payload hash mismatches.
- Pass issuance rejects approval artifacts that were created for a different action or payload.
- Keyring verification fails closed when the JWT
kidis unknown. - The local trust store is atomically written with mode
0600. It persists public verification JWKs and local revocation records only; signing keys should remain in KMS, env-managed dev secrets, or another controlled key custodian. - Local revocation records still rely on the trusted filesystem boundary. ActionPass v2 additionally publishes signed freshness-bounded status evidence for independent/multi-process verification.
- Verification binds agent, human owner, runtime, task, tool, resource, and payload hash.
- V1 verification additionally requires the holder key, fresh proof target,
exact ActionPass hash, and one-time proof
jti. - V2 verification additionally requires authenticated fresh status evidence; status-source failure or stale evidence blocks execution.
Signing currently defaults to ES256.
