Skip to content

Agent-Pattern-Labs/agent-proof

Repository files navigation

@razroo/actionproof

Action-bound, signed challenge-response verification for proving fresh autonomous AI-agent work.

This package creates short-lived tasks that are awkward for a human to complete quickly but straightforward for an AI agent with tool use: route finding, VM tracing, JSON state mutation, and constrained subset search. Each challenge is random, expiring, and signed, so static bots cannot replay or copy a previous answer.

ActionProof terminal demo

Important limitation: no npm package can mathematically prove that a responder is an AI. This package proves narrower properties: the responder completed fresh heterogeneous computational work within your configured time window, and, when runtime attestations are required, a trusted runtime or mediator signed a receipt for the same challenge and action.

Install

npm install @razroo/actionproof

Library usage

import {
  createChallenge,
  hashActionPayload,
  solvePublicTask,
  verifyCapability,
  verifyResponse
} from "@razroo/actionproof";

const secret = process.env.ACTIONPROOF_SECRET!;
const post = {
  title: "Agent update",
  body: "This exact post is what the agent is allowed to publish."
};

const binding = {
  subject: "user_123",
  action: "create_post",
  resource: "POST /posts",
  contentHash: hashActionPayload(post)
};

const challenge = createChallenge({
  secret,
  difficulty: "standard",
  ttlMs: 30_000,
  binding
});

// Send challenge.instructions, challenge.binding, and challenge.tasks to the agent.
// The agent must return:
// {"challengeId":"...","answers":{"task_id":"64-char lowercase sha256 hex"}}
// Trusted producer runtimes can call solvePublicTask(task) when they own the
// full autonomous path and need a deterministic local task solver.

const result = verifyResponse({
  secret,
  challenge,
  response: agentResponse,
  capabilityTtlMs: 15_000,
  consumeVerification: (consumeKeys) => reserveVerificationInDatabase(consumeKeys)
});

if (!result.ok || !result.capability) {
  throw new Error("not verified");
}

// On the actual POST /posts request, recompute the binding from the request.
// If content changed, this check fails.
const capabilityResult = verifyCapability({
  secret,
  capability: result.capability,
  binding
});

if (capabilityResult.ok) {
  // Atomically store capabilityResult.consumeKey as used, then publish the post.
}

Runtime attestations

ActionProof can require a signed runtime attestation in addition to challenge answers. Use this when the agent runtime, MCP mediator, or egress proxy has a signing secret that is not available to the agent process or to users. The attestation is bound to the same challenge id, action binding, and content hash.

This changes the gate from "anyone with the API token and proof flow can post" to "posting also requires a receipt from an approved runtime path." It can block stolen posting tokens and direct manual API calls that do not have access to the runtime attestation key.

import {
  assessAutonomousPostingAssurance,
  createAutonomousActionGrant,
  createAutonomousAgentSessionReceipt,
  createAutonomousPolicyDecision,
  createAutonomousRequestProof,
  createAutonomousRuntimePolicy,
  createSignerCustodyCertificate,
  createStrictAutonomousDeploymentCertificate,
  createStrictAutonomousDeploymentPolicy,
  createStrictAutonomousActionPolicy,
  createRuntimeAttestation,
  createTriggerOriginProof,
  hashActionPayload,
  hashAutonomousPolicyDecision,
  hashStrictAutonomousDeploymentManifest,
  hashChallengeBinding,
  auditStrictAutonomousDeploymentManifest,
  verifyAutonomousAction,
  verifyAutonomousPolicyDecision,
  verifyAutonomousAgentSessionReceipt,
  verifyAutonomousRequestProof,
  verifyResponse,
  verifyStrictAutonomousDeploymentCertificate,
  verifyStrictAutonomousAction,
  verifyStrictAutonomousDeploymentAction,
  verifyStrictAutonomousDeploymentResponse,
  createStrictGrantedAutonomousPostingProof,
  verifyStrictGrantedAutonomousPosting,
  verifyStrictGrantedAutonomousDeploymentAction,
  verifyStrictGrantedAutonomousAction,
  verifyStrictAutonomousResponse
} from "@razroo/actionproof";

const runtimeSecret = process.env.ACTIONPROOF_RUNTIME_SECRET!;

// Issued by the trusted runtime or MCP mediator, not by the agent process.
const runtimeAttestation = createRuntimeAttestation({
  secret: runtimeSecret,
  issuer: "profilescribe-runtime",
  runtimeId: "mcp-worker-1",
  challengeId: challenge.id,
  binding: challenge.binding!,
  mode: "autonomous",
  humanInteractive: false,
  trigger: {
    kind: "source_event",
    id: "github-release-42",
    source: "github"
  },
  policyHash: hashActionPayload("posting-policy-v1")
});

const result = verifyResponse({
  secret,
  challenge,
  response: agentResponse,
  runtimeAttestation,
  runtimeAttestationSecret: runtimeSecret,
  runtimeAttestationPolicy: {
    allowedIssuers: ["profilescribe-runtime"],
    allowedRuntimeIds: ["mcp-worker-1"],
    allowedTriggerKinds: ["source_event", "scheduled", "autonomous_policy"],
    deniedTriggerKinds: ["human_request", "unknown"],
    requiredMode: "autonomous",
    requiredPolicyHash: hashActionPayload("posting-policy-v1")
  },
  capabilityTtlMs: 15_000,
  consumeVerification: (consumeKeys) => reserveVerificationInDatabase(consumeKeys)
});

For stronger key separation, sign runtime attestations with an Ed25519 private key and verify them with only the public key in your API:

const runtimeAttestation = createRuntimeAttestation({
  privateKey: runtimeSigningPrivateKey,
  keyId: "runtime-key-2026-05",
  keyCustody: "external_mediator",
  keyExportable: false,
  issuer: "profilescribe-runtime",
  audience: "profilescribe-api",
  runtimeId: "mcp-worker-1",
  challengeId: challenge.id,
  binding: challenge.binding!,
  mode: "autonomous",
  humanInteractive: false,
  trigger: { kind: "scheduled", id: "daily-profile-check" }
});

const result = verifyResponse({
  secret,
  challenge,
  response: agentResponse,
  runtimeAttestation,
  runtimeAttestationPublicKey: runtimeSigningPublicKey,
  runtimeAttestationPolicy: createAutonomousRuntimePolicy({
    allowedIssuers: ["profilescribe-runtime"],
    allowedAudiences: ["profilescribe-api"],
    allowedKeyIds: ["runtime-key-2026-05"],
  })
});

If the runtime should only sign autonomous work that came from a durable automation source, bind the attestation to a separate signed trigger receipt. That lets your API require evidence from a scheduler, webhook mediator, or policy engine instead of trusting the runtime's bare trigger.kind claim:

The origin proof should name the verification method and bind to durable evidence. Strict policies should allow methods such as webhook_signature, scheduler_lock, policy_engine_decision, queue_claim, or external_attestation, and reject manual or self-reported evidence.

const originEvidenceHash = hashActionPayload({
  deliveryId: githubWebhookEvent.deliveryId,
  signature: githubWebhookSignature
});

const originProof = createTriggerOriginProof({
  privateKey: sourceVerifierPrivateKey,
  keyId: "github-source-key",
  issuer: "profilescribe-source-verifier",
  audience: "profilescribe-trigger-mediator",
  kind: "source_event",
  source: "github",
  verificationMethod: "webhook_signature",
  eventId: githubWebhookEvent.id,
  evidenceId: githubWebhookEvent.deliveryId,
  payloadHash: hashActionPayload(githubWebhookEvent),
  bindingHash: hashChallengeBinding(challenge.binding!),
  evidenceHash: originEvidenceHash
});

const triggerReceipt = createTriggerReceipt({
  privateKey: triggerSigningPrivateKey,
  keyId: "github-webhook-key",
  issuer: "profilescribe-trigger-mediator",
  audience: "profilescribe-runtime",
  kind: "source_event",
  payloadHash: hashActionPayload(githubWebhookEvent),
  bindingHash: hashChallengeBinding(challenge.binding!),
  humanInitiated: false,
  source: "github",
  eventId: githubWebhookEvent.id,
  originProof,
  originProofPublicKey: sourceVerifierPublicKey,
  originProofPolicy: {
    allowedIssuers: ["profilescribe-source-verifier"],
    allowedAudiences: ["profilescribe-trigger-mediator"],
    allowedKeyIds: ["github-source-key"],
    allowedKinds: ["source_event"],
    allowedSources: ["github"],
    allowedVerificationMethods: ["webhook_signature"],
    requireEventId: true,
    requireEvidenceId: true,
    requiredPayloadHash: hashActionPayload(githubWebhookEvent),
    requiredBindingHash: hashChallengeBinding(challenge.binding!),
    requiredEvidenceHash: originEvidenceHash
  }
});

const runtimeAttestation = createRuntimeAttestation({
  privateKey: runtimeSigningPrivateKey,
  keyId: "runtime-key-2026-05",
  keyCustody: "external_mediator",
  keyExportable: false,
  issuer: "profilescribe-runtime",
  audience: "profilescribe-api",
  runtimeId: "mcp-worker-1",
  challengeId: challenge.id,
  binding: challenge.binding!,
  mode: "autonomous",
  humanInteractive: false,
  trigger: { kind: "source_event", id: githubWebhookEvent.id, source: "github" },
  triggerReceipt,
  triggerReceiptPublicKey: triggerSigningPublicKey,
  triggerReceiptOriginProofPublicKey: sourceVerifierPublicKey,
  triggerReceiptPolicy: {
    allowedIssuers: ["profilescribe-trigger-mediator"],
    allowedAudiences: ["profilescribe-runtime"],
    allowedKeyIds: ["github-webhook-key"],
    allowedKinds: ["source_event"],
    allowedSources: ["github"],
    requireEventId: true,
    requiredPayloadHash: hashActionPayload(githubWebhookEvent),
    requiredBindingHash: hashChallengeBinding(challenge.binding!),
    requireOriginProof: true,
    requireVerifiedOriginProof: true,
    originProofPolicy: {
      allowedIssuers: ["profilescribe-source-verifier"],
      allowedAudiences: ["profilescribe-trigger-mediator"],
      allowedKeyIds: ["github-source-key"],
      allowedKinds: ["source_event"],
      allowedSources: ["github"],
      allowedVerificationMethods: ["webhook_signature"],
      requireEventId: true,
      requireEvidenceId: true,
      requiredPayloadHash: hashActionPayload(githubWebhookEvent),
      requiredBindingHash: hashChallengeBinding(challenge.binding!),
      requiredEvidenceHash: originEvidenceHash
    }
  },
  policyHash: hashActionPayload("posting-policy-v1"),
  environmentHash: hashActionPayload("worker-image-v1")
});

const result = verifyStrictAutonomousResponse({
  secret,
  challenge,
  response: agentResponse,
  runtimeAttestation,
  runtimeAttestationPublicKey: runtimeSigningPublicKey,
  runtimeAttestationTriggerReceiptPublicKey: triggerSigningPublicKey,
  runtimeAttestationTriggerOriginProofPublicKey: sourceVerifierPublicKey,
  runtimePolicy: {
    allowedIssuers: ["profilescribe-runtime"],
    allowedAudiences: ["profilescribe-api"],
    allowedKeyIds: ["runtime-key-2026-05"],
    maxAttestationAgeMs: 15_000,
    requiredPolicyHash: hashActionPayload("posting-policy-v1"),
    requiredEnvironmentHash: hashActionPayload("worker-image-v1"),
    triggerReceiptPolicy: {
      allowedIssuers: ["profilescribe-trigger-mediator"],
      allowedKeyIds: ["github-webhook-key"],
      allowedKinds: ["source_event"],
      allowedSources: ["github"],
      requireEventId: true,
      requiredBindingHash: hashChallengeBinding(challenge.binding!),
      maxReceiptAgeMs: 15_000,
      requireOriginProof: true,
      requireVerifiedOriginProof: true,
      originProofPolicy: {
        allowedIssuers: ["profilescribe-source-verifier"],
        allowedKeyIds: ["github-source-key"],
        allowedKinds: ["source_event"],
        allowedSources: ["github"],
        allowedVerificationMethods: ["webhook_signature"],
        requireEventId: true,
        requireEvidenceId: true,
        requiredBindingHash: hashChallengeBinding(challenge.binding!),
        requiredEvidenceHash: originEvidenceHash,
        maxProofAgeMs: 15_000
      }
    }
  },
  signerCertificatePolicy: {
    allowedIssuers: ["profilescribe-key-registry"],
    allowedAudiences: ["profilescribe-api"],
    allowedKeyIds: ["key-registry-key"],
    allowedKeyCustody: ["external_mediator"],
    requireNonExportableKey: true,
    denyAgentAccessible: true,
    denyUserAccessible: true,
    requireSubjectPublicKeyHash: true
  },
  maxChallengeAgeMs: 15_000,
  capabilityTtlMs: 15_000,
  consumeVerification: (consumeKeys) => reserveVerificationInDatabase(consumeKeys)
});

When a runtime attestation is present, the signed capability carries a runtime summary. For posting gates, verify the action with the strict autonomous helper and consume the capability in the same database transaction that publishes:

const capabilityResult = verifyStrictAutonomousAction({
  secret,
  capability: result.capability,
  binding,
  runtimePolicy: {
    allowedIssuers: ["profilescribe-runtime"],
    allowedAudiences: ["profilescribe-api"],
    allowedKeyIds: ["runtime-key-2026-05"],
    maxAttestationAgeMs: 15_000,
    requiredPolicyHash: hashActionPayload("posting-policy-v1"),
    requiredEnvironmentHash: hashActionPayload("worker-image-v1"),
    triggerReceiptPolicy: {
      allowedIssuers: ["profilescribe-trigger-mediator"],
      allowedKeyIds: ["github-webhook-key"],
      allowedKinds: ["source_event"],
      allowedSources: ["github"],
      requireEventId: true,
      requiredBindingHash: hashChallengeBinding(result.capability.binding),
      maxReceiptAgeMs: 15_000,
      requireOriginProof: true,
      requireVerifiedOriginProof: true,
      originProofPolicy: {
        allowedIssuers: ["profilescribe-source-verifier"],
        allowedKeyIds: ["github-source-key"],
        allowedKinds: ["source_event"],
        allowedSources: ["github"],
        allowedVerificationMethods: ["webhook_signature"],
        requireEventId: true,
        requireEvidenceId: true,
        requiredBindingHash: hashChallengeBinding(result.capability.binding),
        requiredEvidenceHash: originEvidenceHash,
        maxProofAgeMs: 15_000
      }
    }
  },
  maxCapabilityAgeMs: 15_000,
  consumeCapability: (consumeKey) => reserveCapabilityInDatabase(consumeKey)
});

verifyStrictAutonomousResponse and verifyStrictAutonomousAction are the fail-closed helpers for production posting gates. They require strict runtime policy, full trigger receipt verification from a separate trigger issuer/key, a separately verified origin proof for the scheduler/webhook/policy event, explicit challenge and capability freshness limits, consumed challenge verification, and final capability consumption. The lower-level verifyAutonomousAction also requires capabilities minted from a consumed verification by default; set requireConsumedVerification: false only for local experiments or compatibility migrations.

For production, keep a strict deployment manifest next to the endpoint configuration and fail deployment if it audits with any issues. This catches the non-cryptographic gaps that a signed challenge cannot see, such as direct user posting, bearer-token-only posting, human access to signer keys, user-invoked runtime posting, manually minted origin events, and missing single-use database consumption:

const deploymentCertificatePolicy = {
  allowedIssuers: ["profilescribe-deployment-auditor"],
  allowedAudiences: ["profilescribe-api"],
  allowedKeyIds: ["deployment-auditor-key"],
  allowedDeploymentIds: ["profilescribe-posting-prod"],
  maxCertificateAgeMs: 86_400_000
};

const agentSessionReceiptPolicy = {
  allowedIssuers: ["profilescribe-session-controller"],
  allowedAudiences: ["profilescribe-api"],
  allowedKeyIds: ["agent-session-key"],
  allowedRuntimeIds: ["mcp-worker-1"],
  allowedInputKinds: ["source_event"],
  deniedInputKinds: ["human_prompt", "manual_shell", "unknown"],
  requireChallengeId: true,
  requireBindingHash: true,
  requireNoHumanPrompt: true,
  requireNoHumanOperator: true,
  denyUserShellAccess: true,
  denyUserAccessibleAgentEnvironment: true,
  allowedKeyCustody: ["external_mediator"],
  requireNonExportableKey: true,
  requiredPolicyHash: hashActionPayload("posting-policy-v1"),
  requiredEnvironmentHash: hashActionPayload("worker-image-v1"),
  maxReceiptAgeMs: 15_000
};

const requestProofPolicy = {
  allowedIssuers: ["profilescribe-request-mediator"],
  allowedAudiences: ["profilescribe-api"],
  allowedKeyIds: ["posting-request-proof-key"],
  allowedKeyCustody: ["external_mediator"],
  requireNonExportableKey: true,
  requireBindingHash: true,
  requireCapabilityId: true,
  maxProofAgeMs: 15_000
};

const deployment = {
  version: "1.0",
  deploymentId: "profilescribe-posting-prod",
  runtimePolicy: {
    allowedIssuers: ["profilescribe-runtime"],
    allowedAudiences: ["profilescribe-api"],
    allowedKeyIds: ["runtime-key-2026-05"],
    maxAttestationAgeMs: 15_000,
    requiredPolicyHash: hashActionPayload("posting-policy-v1"),
    requiredEnvironmentHash: hashActionPayload("worker-image-v1"),
    triggerReceiptPolicy: {
      allowedIssuers: ["profilescribe-trigger-mediator"],
      allowedKeyIds: ["github-webhook-key"],
      allowedKinds: ["source_event"],
      allowedSources: ["github"],
      requireEventId: true,
      requiredBindingHash: hashChallengeBinding(challenge.binding!),
      maxReceiptAgeMs: 15_000,
      requireOriginProof: true,
      requireVerifiedOriginProof: true,
      originProofPolicy: {
        allowedIssuers: ["profilescribe-source-verifier"],
        allowedKeyIds: ["github-source-key"],
        allowedKinds: ["source_event"],
        allowedSources: ["github"],
        allowedVerificationMethods: ["webhook_signature"],
        requireEventId: true,
        requireEvidenceId: true,
        requiredBindingHash: hashChallengeBinding(challenge.binding!),
        requiredEvidenceHash: originEvidenceHash,
        maxProofAgeMs: 15_000
      }
    }
  },
  deploymentCertificatePolicy,
  agentSessionReceiptPolicy,
  requestProofPolicy,
  maxChallengeAgeMs: 15_000,
  capabilityTtlMs: 15_000,
  maxCapabilityAgeMs: 15_000,
  controls: {
    proofRequiredForPost: true,
    bearerTokenAloneCanPost: false,
    directUserPostingAllowed: false,
    deploymentCertificateRequired: true,
    agentSessionReceiptRequiredForPost: true,
    agentSessionSignerAccessibleToAgent: false,
    agentSessionSignerAccessibleToUsers: false,
    agentSessionAcceptsHumanPrompts: false,
    agentSessionAllowsHumanOperators: false,
    agentSessionAllowsUserShellAccess: false,
    agentEnvironmentAccessibleToUsers: false,
    agentSessionCanBeMintedByRuntimeSigner: false,
    requestProofRequiredForPost: true,
    accessTokenSenderConstrained: true,
    requestProofSignerAccessibleToAgent: false,
    requestProofSignerAccessibleToUsers: false,
    requestProofCanBeMintedByRuntimeSigner: false,
    requestProofCanBeMintedByGrantSigner: false,
    requestProofCanBeMintedByPolicyDecisionSigner: false,
    runtimeSignerAccessibleToAgent: false,
    runtimeSignerAccessibleToUsers: false,
    runtimeAcceptsInteractiveRequests: false,
    runtimeCanBeInvokedByUsersForPosting: false,
    triggerSignerAccessibleToAgent: false,
    triggerSignerAccessibleToUsers: false,
    triggerAcceptsHumanInitiatedEvents: false,
    triggerCanBeMintedByRuntimeSigner: false,
    originProofSignerAccessibleToAgent: false,
    originProofSignerAccessibleToUsers: false,
    originProofAcceptsManualEvents: false,
    originProofCanBeMintedByTriggerSigner: false,
    originProofCanBeMintedByRuntimeSigner: false,
    signerCustodyCertificatesRequired: true,
    policyDecisionRequiredForPost: true,
    grantRequiresPolicyDecision: true,
    policyDecisionSignerAccessibleToAgent: false,
    policyDecisionSignerAccessibleToUsers: false,
    policyDecisionAcceptsHumanInitiatedRequests: false,
    policyDecisionCanBeMintedByRuntimeSigner: false,
    policyDecisionCanBeMintedByTriggerSigner: false,
    policyDecisionCanBeMintedByGrantSigner: false,
    consumeVerification: true,
    consumeCapability: true,
    atomicConsumptionWithPost: true
  }
};

const deploymentIssues = auditStrictAutonomousDeploymentManifest(deployment);
if (deploymentIssues.length > 0) {
  throw new Error(`Unsafe agent posting deployment: ${deploymentIssues.join(", ")}`);
}

const strictPostingPolicy = createStrictAutonomousDeploymentPolicy(deployment);
const deploymentHash = hashStrictAutonomousDeploymentManifest(deployment);

const deploymentCertificate = createStrictAutonomousDeploymentCertificate({
  privateKey: deploymentAuditorPrivateKey,
  keyId: "deployment-auditor-key",
  issuer: "profilescribe-deployment-auditor",
  audience: "profilescribe-api",
  deployment
});

const deploymentCertificateResult = verifyStrictAutonomousDeploymentCertificate({
  publicKey: deploymentAuditorPublicKey,
  certificate: deploymentCertificate,
  deployment,
  policy: deploymentCertificatePolicy
});

if (!deploymentCertificateResult.ok) {
  throw new Error(`Unsafe deployment certificate: ${deploymentCertificateResult.reason}`);
}

The returned strictPostingPolicy contains the exact runtimePolicy, signerCertificatePolicy, deploymentCertificatePolicy, agentSessionReceiptPolicy, requestProofPolicy, maxChallengeAgeMs, capabilityTtlMs, and maxCapabilityAgeMs values to pass into strict verification helpers. deploymentHash is useful for release evidence and deployment logs; deploymentCertificate turns that local manifest hash into a signed control-plane claim from an auditor key your API pins.

You can also pass the manifest directly to the deployment wrappers so the audit must pass before either verification step runs:

const signerCertificatePolicy = {
  allowedIssuers: ["profilescribe-key-registry"],
  allowedAudiences: ["profilescribe-api"],
  allowedKeyIds: ["key-registry-key"],
  allowedKeyCustody: ["external_mediator"],
  requireNonExportableKey: true,
  denyAgentAccessible: true,
  denyUserAccessible: true,
  requireSubjectPublicKeyHash: true
};

const runtimeSignerCertificate = createSignerCustodyCertificate({
  privateKey: keyRegistrySigningPrivateKey,
  keyId: "key-registry-key",
  issuer: "profilescribe-key-registry",
  audience: "profilescribe-api",
  subjectIssuer: "profilescribe-runtime",
  subjectKeyId: "runtime-key-2026-05",
  subjectPublicKey: runtimeSigningPublicKey,
  purposes: ["runtime_attestation"],
  keyCustody: "external_mediator",
  keyExportable: false,
  accessibleToAgent: false,
  accessibleToUsers: false
});

const triggerSignerCertificate = createSignerCustodyCertificate({
  privateKey: keyRegistrySigningPrivateKey,
  keyId: "key-registry-key",
  issuer: "profilescribe-key-registry",
  audience: "profilescribe-api",
  subjectIssuer: "profilescribe-trigger-mediator",
  subjectKeyId: "github-webhook-key",
  subjectPublicKey: triggerSigningPublicKey,
  purposes: ["trigger_receipt"],
  keyCustody: "external_mediator",
  keyExportable: false,
  accessibleToAgent: false,
  accessibleToUsers: false
});

const originSignerCertificate = createSignerCustodyCertificate({
  privateKey: keyRegistrySigningPrivateKey,
  keyId: "key-registry-key",
  issuer: "profilescribe-key-registry",
  audience: "profilescribe-api",
  subjectIssuer: "profilescribe-source-verifier",
  subjectKeyId: "github-source-key",
  subjectPublicKey: sourceVerifierPublicKey,
  purposes: ["trigger_origin_proof"],
  keyCustody: "external_mediator",
  keyExportable: false,
  accessibleToAgent: false,
  accessibleToUsers: false
});

const requestProofSignerCertificate = createSignerCustodyCertificate({
  privateKey: keyRegistrySigningPrivateKey,
  keyId: "key-registry-key",
  issuer: "profilescribe-key-registry",
  audience: "profilescribe-api",
  subjectIssuer: "profilescribe-request-mediator",
  subjectKeyId: "posting-request-proof-key",
  subjectPublicKey: requestProofSigningPublicKey,
  purposes: ["request_proof"],
  keyCustody: "external_mediator",
  keyExportable: false,
  accessibleToAgent: false,
  accessibleToUsers: false
});

const agentSessionSignerCertificate = createSignerCustodyCertificate({
  privateKey: keyRegistrySigningPrivateKey,
  keyId: "key-registry-key",
  issuer: "profilescribe-key-registry",
  audience: "profilescribe-api",
  subjectIssuer: "profilescribe-session-controller",
  subjectKeyId: "agent-session-key",
  subjectPublicKey: agentSessionSigningPublicKey,
  purposes: ["agent_session"],
  keyCustody: "external_mediator",
  keyExportable: false,
  accessibleToAgent: false,
  accessibleToUsers: false
});

const agentSessionReceipt = createAutonomousAgentSessionReceipt({
  privateKey: agentSessionSigningPrivateKey,
  keyId: "agent-session-key",
  keyCustody: "external_mediator",
  keyExportable: false,
  issuer: "profilescribe-session-controller",
  audience: "profilescribe-api",
  sessionId: agentRun.sessionId,
  runtimeId: "mcp-worker-1",
  challengeId: challenge.id,
  bindingHash: hashChallengeBinding(challenge.binding!),
  inputKind: "source_event",
  inputSource: "github",
  eventId: githubWebhookEvent.id,
  humanPromptPresent: false,
  humanOperatorPresent: false,
  userShellAccess: false,
  agentEnvironmentUserAccessible: false,
  transcriptHash: agentRun.transcriptHash,
  policyHash: hashActionPayload("posting-policy-v1"),
  environmentHash: hashActionPayload("worker-image-v1")
});

const agentSessionResult = verifyAutonomousAgentSessionReceipt({
  publicKey: agentSessionSigningPublicKey,
  receipt: agentSessionReceipt,
  challengeId: challenge.id,
  bindingHash: hashChallengeBinding(challenge.binding!),
  signerCertificate: agentSessionSignerCertificate,
  signerCertificatePublicKey: keyRegistrySigningPublicKey,
  signerCertificatePolicy,
  requireSignerCertificate: true,
  policy: agentSessionReceiptPolicy
});

if (!agentSessionResult.ok) {
  throw new Error(`Unsafe agent session: ${agentSessionResult.reason}`);
}

const responseResult = verifyStrictAutonomousDeploymentResponse({
  secret,
  challenge,
  response: agentResponse,
  runtimeAttestation,
  runtimeAttestationPublicKey: runtimeSigningPublicKey,
  runtimeAttestationSignerCertificate: runtimeSignerCertificate,
  runtimeAttestationSignerCertificatePublicKey: keyRegistrySigningPublicKey,
  runtimeAttestationSignerCertificatePolicy: signerCertificatePolicy,
  runtimeAttestationTriggerReceiptPublicKey: triggerSigningPublicKey,
  runtimeAttestationTriggerReceiptSignerCertificate: triggerSignerCertificate,
  runtimeAttestationTriggerReceiptSignerCertificatePublicKey:
    keyRegistrySigningPublicKey,
  runtimeAttestationTriggerReceiptSignerCertificatePolicy: signerCertificatePolicy,
  runtimeAttestationTriggerOriginProofPublicKey: sourceVerifierPublicKey,
  runtimeAttestationTriggerOriginProofSignerCertificate: originSignerCertificate,
  runtimeAttestationTriggerOriginProofSignerCertificatePublicKey:
    keyRegistrySigningPublicKey,
  runtimeAttestationTriggerOriginProofSignerCertificatePolicy:
    signerCertificatePolicy,
  deployment,
  deploymentCertificate,
  deploymentCertificatePublicKey: deploymentAuditorPublicKey,
  deploymentCertificatePolicy,
  agentSessionReceipt,
  agentSessionReceiptPublicKey: agentSessionSigningPublicKey,
  agentSessionReceiptPolicy,
  agentSessionReceiptSignerCertificate: agentSessionSignerCertificate,
  agentSessionReceiptSignerCertificatePublicKey: keyRegistrySigningPublicKey,
  agentSessionReceiptSignerCertificatePolicy: signerCertificatePolicy,
  consumeVerification: (consumeKeys) => reserveVerificationInDatabase(consumeKeys)
});

const accessTokenHash = hashActionPayload(apiAccessToken);
const requestProof = createAutonomousRequestProof({
  privateKey: requestProofSigningPrivateKey,
  keyId: "posting-request-proof-key",
  keyCustody: "external_mediator",
  keyExportable: false,
  issuer: "profilescribe-request-mediator",
  audience: "profilescribe-api",
  subject: "agent:poster",
  action: "create_post",
  resource: "POST /posts",
  contentHash: binding.contentHash,
  bindingHash: hashChallengeBinding(responseResult.capability.binding),
  capabilityId: responseResult.capability.id,
  accessTokenHash
});

const requestProofResult = verifyAutonomousRequestProof({
  publicKey: requestProofSigningPublicKey,
  proof: requestProof,
  binding,
  bindingHash: hashChallengeBinding(responseResult.capability.binding),
  capabilityId: responseResult.capability.id,
  accessTokenHash,
  signerCertificate: requestProofSignerCertificate,
  signerCertificatePublicKey: keyRegistrySigningPublicKey,
  signerCertificatePolicy,
  requireSignerCertificate: true,
  policy: {
    allowedIssuers: ["profilescribe-request-mediator"],
    allowedAudiences: ["profilescribe-api"],
    allowedKeyIds: ["posting-request-proof-key"],
    allowedKeyCustody: ["external_mediator"],
    requireNonExportableKey: true,
    requireBindingHash: true,
    requireCapabilityId: true
  }
});

if (!requestProofResult.ok) {
  throw new Error(`Unsafe request proof: ${requestProofResult.reason}`);
}

const actionResult = verifyStrictAutonomousDeploymentAction({
  secret,
  capability: responseResult.capability,
  binding,
  deployment,
  deploymentCertificate,
  deploymentCertificatePublicKey: deploymentAuditorPublicKey,
  deploymentCertificatePolicy,
  requestProof,
  requestProofPublicKey: requestProofSigningPublicKey,
  requestProofPolicy: {
    allowedIssuers: ["profilescribe-request-mediator"],
    allowedAudiences: ["profilescribe-api"],
    allowedKeyIds: ["posting-request-proof-key"]
  },
  requestProofSignerCertificate,
  requestProofSignerCertificatePublicKey: keyRegistrySigningPublicKey,
  requestProofSignerCertificatePolicy: signerCertificatePolicy,
  accessTokenHash,
  consumeCapability: (consumeKey) => reserveCapabilityInDatabase(consumeKey)
});

For user-scoped posting, add a separate signed grant from your authorization service. This is distinct from the proof chain: the proof says the autonomous path was followed, while the grant says this subject/action/resource/runtime path is allowed to post.

const policyDecisionSignerCertificate = createSignerCustodyCertificate({
  privateKey: keyRegistrySigningPrivateKey,
  keyId: "key-registry-key",
  issuer: "profilescribe-key-registry",
  audience: "profilescribe-api",
  subjectIssuer: "profilescribe-policy",
  subjectKeyId: "posting-policy-decision-key",
  subjectPublicKey: policyDecisionSigningPublicKey,
  purposes: ["policy_decision"],
  keyCustody: "external_mediator",
  keyExportable: false,
  accessibleToAgent: false,
  accessibleToUsers: false
});

const grantSignerCertificate = createSignerCustodyCertificate({
  privateKey: keyRegistrySigningPrivateKey,
  keyId: "key-registry-key",
  issuer: "profilescribe-key-registry",
  audience: "profilescribe-api",
  subjectIssuer: "profilescribe-authz",
  subjectKeyId: "posting-grant-key",
  subjectPublicKey: grantSigningPublicKey,
  purposes: ["action_grant"],
  keyCustody: "external_mediator",
  keyExportable: false,
  accessibleToAgent: false,
  accessibleToUsers: false
});

const policyDecision = createAutonomousPolicyDecision({
  privateKey: policyDecisionSigningPrivateKey,
  keyId: "posting-policy-decision-key",
  keyCustody: "external_mediator",
  keyExportable: false,
  issuer: "profilescribe-policy",
  audience: "profilescribe-api",
  subject: "agent:poster",
  action: "create_post",
  resource: "POST /posts",
  contentHash: binding.contentHash,
  bindingHash: hashChallengeBinding(responseResult.capability.binding),
  challengeId: responseResult.capability.challengeId,
  runtimeId: "mcp-worker-1",
  triggerKind: "source_event",
  triggerSource: "github",
  triggerEventId: githubWebhookEvent.id,
  originVerificationMethod: "webhook_signature",
  originEvidenceId: githubWebhookEvent.deliveryId,
  originEvidenceHash,
  policyHash: hashActionPayload("posting-policy-v1"),
  environmentHash: hashActionPayload("worker-image-v1"),
  humanInitiated: false
});

const grant = createAutonomousActionGrant({
  privateKey: grantSigningPrivateKey,
  keyId: "posting-grant-key",
  keyCustody: "external_mediator",
  keyExportable: false,
  issuer: "profilescribe-authz",
  audience: "profilescribe-api",
  subject: "agent:poster",
  actions: ["create_post"],
  resources: ["POST /posts"],
  runtimeIds: ["mcp-worker-1"],
  triggerSources: ["github"],
  triggerEventIds: [githubWebhookEvent.id],
  contentHashes: [binding.contentHash],
  bindingHashes: [hashChallengeBinding(responseResult.capability.binding)],
  policyDecisionIds: [policyDecision.id],
  policyDecisionHashes: [hashAutonomousPolicyDecision(policyDecision)!],
  originVerificationMethods: ["webhook_signature"],
  originEvidenceIds: [githubWebhookEvent.deliveryId],
  originEvidenceHashes: [originEvidenceHash],
  policyHash: hashActionPayload("posting-policy-v1"),
  environmentHash: hashActionPayload("worker-image-v1")
});

const grantedActionResult = verifyStrictGrantedAutonomousDeploymentAction({
  secret,
  capability: responseResult.capability,
  binding,
  deployment,
  deploymentCertificate,
  deploymentCertificatePublicKey: deploymentAuditorPublicKey,
  deploymentCertificatePolicy,
  requestProof,
  requestProofPublicKey: requestProofSigningPublicKey,
  requestProofPolicy: {
    allowedIssuers: ["profilescribe-request-mediator"],
    allowedAudiences: ["profilescribe-api"],
    allowedKeyIds: ["posting-request-proof-key"]
  },
  requestProofSignerCertificate,
  requestProofSignerCertificatePublicKey: keyRegistrySigningPublicKey,
  requestProofSignerCertificatePolicy: signerCertificatePolicy,
  accessTokenHash,
  grant,
  grantPublicKey: grantSigningPublicKey,
  grantPolicy: {
    allowedIssuers: ["profilescribe-authz"],
    allowedAudiences: ["profilescribe-api"],
    allowedKeyIds: ["posting-grant-key"]
  },
  policyDecision,
  policyDecisionPublicKey: policyDecisionSigningPublicKey,
  policyDecisionPolicy: {
    allowedIssuers: ["profilescribe-policy"],
    allowedAudiences: ["profilescribe-api"],
    allowedKeyIds: ["posting-policy-decision-key"]
  },
  grantSignerCertificate,
  grantSignerCertificatePublicKey: keyRegistrySigningPublicKey,
  grantSignerCertificatePolicy: {
    allowedIssuers: ["profilescribe-key-registry"],
    allowedAudiences: ["profilescribe-api"],
    allowedKeyIds: ["key-registry-key"],
    allowedKeyCustody: ["external_mediator"],
    requireNonExportableKey: true,
    denyAgentAccessible: true,
    denyUserAccessible: true,
    requireSubjectPublicKeyHash: true
  },
  policyDecisionSignerCertificate,
  policyDecisionSignerCertificatePublicKey: keyRegistrySigningPublicKey,
  policyDecisionSignerCertificatePolicy: {
    allowedIssuers: ["profilescribe-key-registry"],
    allowedAudiences: ["profilescribe-api"],
    allowedKeyIds: ["key-registry-key"],
    allowedKeyCustody: ["external_mediator"],
    requireNonExportableKey: true,
    denyAgentAccessible: true,
    denyUserAccessible: true,
    requireSubjectPublicKeyHash: true
  },
  consumeCapability: (consumeKey) => reserveCapabilityInDatabase(consumeKey)
});

const assurance = assessAutonomousPostingAssurance({
  deployment,
  verification: responseResult,
  action: grantedActionResult
});

if (!assurance.ok || assurance.claim !== "controlled_autonomous_path") {
  throw new Error(`Insufficient autonomous posting assurance: ${assurance.issues.join(", ")}`);
}

// This is intentionally false even when assurance.ok is true.
// The claim is controlled autonomous path evidence, not non-human identity proof.
console.assert(assurance.nonHumanIdentityProven === false);

For new posting gates, verifyStrictGrantedAutonomousPosting composes those last steps so the API cannot forget the final assurance check. It verifies the strict deployment response, calls createRequestProof after the fresh capability exists, verifies the granted deployment action, and returns ok: true only for the controlled_autonomous_path assurance claim.

Producer-side runtimes can use createStrictGrantedAutonomousPostingProof to assemble the envelope that a posting API verifies. It creates or forwards the deployment certificate, trigger origin proof, trigger receipt, runtime attestation, agent-session receipt, policy decision, grant, and signer custody certificates. It intentionally does not solve the challenge and does not create the final sender-constrained request proof for the API bearer token; the runtime must supply the agent's challenge response, and the protected API mediator should still create the request proof at the final posting boundary.

const actionProof = createStrictGrantedAutonomousPostingProof({
  challenge,
  response: agentResponse,
  binding,
  deployment,
  createDeploymentCertificate: {
    privateKey: deploymentAuditorPrivateKey,
    keyId: "deployment-auditor-key",
    issuer: "profilescribe-deployment-auditor",
    audience: "profilescribe-api"
  },
  createTriggerOriginProof: {
    privateKey: sourceVerifierPrivateKey,
    keyId: "github-source-key",
    issuer: "profilescribe-source-verifier",
    audience: "profilescribe-trigger-mediator",
    kind: "source_event",
    source: "github",
    verificationMethod: "webhook_signature",
    eventId: githubWebhookEvent.id,
    evidenceId: githubWebhookEvent.deliveryId,
    payloadHash: hashActionPayload(githubWebhookEvent),
    evidenceHash: hashActionPayload(githubWebhookEvent.body)
  },
  createTriggerReceipt: {
    privateKey: triggerSigningPrivateKey,
    keyId: "github-webhook-key",
    issuer: "profilescribe-trigger-mediator",
    audience: "profilescribe-runtime",
    kind: "source_event",
    payloadHash: hashActionPayload(githubWebhookEvent),
    source: "github",
    eventId: githubWebhookEvent.id,
    originProofPublicKey: sourceVerifierPublicKey,
    originProofPolicy: deployment.runtimePolicy.triggerReceiptPolicy
      .originProofPolicy
  },
  createRuntimeAttestation: {
    privateKey: runtimeSigningPrivateKey,
    keyId: "runtime-key-2026-05",
    keyCustody: "external_mediator",
    keyExportable: false,
    issuer: "profilescribe-runtime",
    audience: "profilescribe-api",
    runtimeId: "mcp-worker-1",
    trigger: { kind: "source_event", id: githubWebhookEvent.id, source: "github" },
    triggerReceiptPublicKey: triggerSigningPublicKey,
    triggerReceiptOriginProofPublicKey: sourceVerifierPublicKey,
    triggerReceiptPolicy: deployment.runtimePolicy.triggerReceiptPolicy,
    policyHash: hashActionPayload("posting-policy-v1"),
    environmentHash: hashActionPayload("worker-image-v1")
  },
  runtimeAttestationSignerCertificate,
  runtimeAttestationTriggerReceiptSignerCertificate: triggerSignerCertificate,
  runtimeAttestationTriggerOriginProofSignerCertificate: originSignerCertificate,
  createAgentSessionReceipt: {
    privateKey: agentSessionSigningPrivateKey,
    keyId: "agent-session-key",
    keyCustody: "external_mediator",
    keyExportable: false,
    issuer: "profilescribe-session-controller",
    audience: "profilescribe-api",
    sessionId: agentRun.sessionId,
    runtimeId: "mcp-worker-1",
    inputKind: "source_event",
    inputSource: "github",
    eventId: githubWebhookEvent.id,
    humanPromptPresent: false,
    humanOperatorPresent: false,
    userShellAccess: false,
    agentEnvironmentUserAccessible: false,
    transcriptHash: agentRun.transcriptHash,
    policyHash: hashActionPayload("posting-policy-v1"),
    environmentHash: hashActionPayload("worker-image-v1")
  },
  agentSessionReceiptSignerCertificate,
  createPolicyDecision: {
    privateKey: policyDecisionSigningPrivateKey,
    keyId: "posting-policy-decision-key",
    keyCustody: "external_mediator",
    keyExportable: false,
    issuer: "profilescribe-policy",
    audience: "profilescribe-api"
  },
  policyDecisionSignerCertificate,
  createGrant: {
    privateKey: grantSigningPrivateKey,
    keyId: "posting-grant-key",
    keyCustody: "external_mediator",
    keyExportable: false,
    issuer: "profilescribe-authz",
    audience: "profilescribe-api"
  },
  grantSignerCertificate
});
const posting = await verifyStrictGrantedAutonomousPosting({
  secret,
  challenge,
  response,
  binding,
  deployment,
  deploymentCertificate,
  deploymentCertificatePublicKey: deploymentAuditorPublicKey,
  runtimeAttestation,
  runtimeAttestationPublicKey: runtimeSigningPublicKey,
  runtimeAttestationTriggerReceiptPublicKey: triggerSigningPublicKey,
  runtimeAttestationTriggerOriginProofPublicKey: originProofSigningPublicKey,
  agentSessionReceipt,
  agentSessionReceiptPublicKey: agentSessionSigningPublicKey,
  grant,
  grantPublicKey: grantSigningPublicKey,
  policyDecision,
  policyDecisionPublicKey: policyDecisionSigningPublicKey,
  createRequestProof: async ({ capability, bindingHash }) => ({
    requestProof: await requestProofMediator.sign({
      capabilityId: capability.id,
      bindingHash,
      accessTokenHash,
      subject: binding.subject,
      action: binding.action,
      resource: binding.resource,
      contentHash: binding.contentHash
    }),
    requestProofPublicKey: requestProofSigningPublicKey,
    requestProofSignerCertificate,
    requestProofSignerCertificatePublicKey: keyRegistrySigningPublicKey,
    accessTokenHash
  }),
  consumeVerification: (consumeKeys) => reserveVerificationInDatabase(consumeKeys),
  consumeCapability: (consumeKey) => reserveCapabilityInPostTransaction(consumeKey)
});

if (!posting.ok) {
  throw new Error(`Insufficient autonomous posting evidence: ${posting.reason}`);
}

Runtime attestations only mean something if the runtime signing secret is outside the agent environment and outside the user's shell. If a human can read the attestation secret, they can mint receipts. If a human can instruct the approved runtime to perform an allowed autonomous action, the runtime policy must reject that path before signing. Agent session receipts only mean something if the session controller truthfully records prompt ingress, operator presence, shell access, and environment access. Deployment certificates only mean something if the auditor private key is protected and the API pins the expected auditor public key and policy. Request proofs only protect stolen API tokens when the request-proof private key is sender-constrained and unavailable to the token thief. ActionProof verifies those signed claims; it does not independently know the user's intent.

Policy decisions add a final external control-plane check. verifyStrictGrantedAutonomousAction verifies the signed decision with a protected key, requires it to be non-human-initiated, binds it to the same runtime evidence and binding hash, and then only accepts grants scoped to that exact decision id/hash. Deployment certificates let an external auditor or control plane sign the exact strict manifest hash and controls hash. Agent session receipts bind capability issuance to a run that the session controller says had no human prompt, no human operator, no user shell access, and no user-accessible agent environment. Request proofs bind the final post to the API access-token hash, capability id, binding hash, and post content before the wrapper consumes the capability. Signer custody certificates let a separate key registry attest that grant, policy-decision, agent-session, and request-proof signing keys are external, non-exportable, inaccessible to agents and users, and purpose-scoped.

assessAutonomousPostingAssurance maps those artifacts back to the threat model. It returns controlled_autonomous_path only when the strict deployment audit, fresh-work verification, runtime trigger evidence, agent-session receipt, sender-constrained request proof, grant, and policy decision are all present. It always returns nonHumanIdentityProven: false; that field is a guardrail against treating signed path evidence as proof that no human was involved anywhere outside the trusted control points.

For posting gates, prefer Ed25519 runtime receipts, keep the private key only in the mediator, require keyCustody of external_mediator or hardware_backed, require non-exportable keys where your infrastructure can make that claim, and deny human_request and unknown trigger kinds. A trusted mediator should sign source_event, scheduled, or autonomous_policy only when the run came from its own durable automation queue rather than from an interactive user prompt.

createAutonomousRuntimePolicy builds those strict defaults for posting gates: autonomous mode only, no human interaction, denied human_request and unknown triggers, external or hardware-backed key custody, and non-exportable signing keys.

createStrictAutonomousActionPolicy adds production posting requirements on top: verifier audience allowlists, runtime key id allowlists, freshness limits, required runtime policy/environment hashes, a non-human-initiated trigger receipt claim, a required signed trigger receipt policy, and a required origin proof policy. Strict policies require the API to verify the full trigger receipt and the embedded origin proof, so the API is not only trusting a runtime-embedded summary. auditAutonomousActionPolicy returns issue codes when a hand-built policy is missing those safeguards.

For stolen-token and replay resistance, keep capabilityTtlMs, maxCapabilityAgeMs, maxAttestationAgeMs, and request-proof TTLs short. Use consumeVerification to atomically reserve the challenge id plus any runtime attestation and trigger receipt ids before a capability is issued, then atomically reserve the consumeKey returned by verifyAutonomousAction in the same database transaction that publishes. In strict deployment wrappers, the final consume happens only after the request proof verifies.

CLI

export ACTIONPROOF_SECRET="replace-this-with-a-long-random-secret"

actionproof generate --difficulty standard --pretty > challenge.json
actionproof verify --challenge challenge.json --response response.json --pretty

You can also pass --secret <value> directly.

For action-bound posting:

CONTENT_HASH=$(printf '%s' '{"title":"Agent update","body":"..."}' | actionproof hash-payload --json)

actionproof generate \
  --difficulty gauntlet \
  --ttl-ms 30000 \
  --subject user_123 \
  --action create_post \
  --resource "POST /posts" \
  --content-hash "$CONTENT_HASH" \
  --pretty > challenge.json

actionproof verify --challenge challenge.json --response response.json --pretty > verification.json

actionproof verify-capability \
  --capability verification.json \
  --subject user_123 \
  --action create_post \
  --resource "POST /posts" \
  --content-hash "$CONTENT_HASH" \
  --pretty

How it works

  • createChallenge generates a random task set and computes the expected answers server-side.
  • The public challenge includes only task inputs, instructions, and a signed verification token.
  • solvePublicTask is exported for trusted autonomous producer runtimes that need to answer public tasks locally before they sign runtime evidence.
  • The token contains challenge metadata, optional action binding, a task hash, answer salts, and salted answer hashes.
  • assessAutonomousPostingAssurance returns a threat-model report distinguishing fresh-work proof, controlled autonomous path evidence, and the explicit non-human-identity limitation.
  • verifyResponse checks the token signature, expiry, task integrity, and submitted answer hashes.
  • createRuntimeAttestation lets a trusted runtime or mediator sign the challenge, action binding, execution mode, and non-interactive status.
  • createTriggerOriginProof lets a separate scheduler, webhook verifier, or policy-event verifier sign the source event before the trigger mediator signs its receipt.
  • createTriggerReceipt lets a scheduler, webhook mediator, or policy engine sign the non-human trigger that caused the run.
  • createStrictAutonomousActionPolicy builds a fail-closed runtime policy for production posting gates; auditAutonomousActionPolicy can check hand-built policies for missing controls.
  • auditStrictAutonomousDeploymentManifest checks the endpoint-level deployment controls that cannot be proven from a challenge response alone.
  • createStrictAutonomousDeploymentCertificate lets an external auditor or control plane sign the exact strict deployment manifest hash and controls hash.
  • verifyStrictAutonomousDeploymentResponse and verifyStrictAutonomousDeploymentAction fail closed unless the deployment manifest audits cleanly and its required deployment certificate verifies before strict verification runs.
  • createSignerCustodyCertificate lets an external key registry sign the custody, exportability, purpose, and public-key hash for runtime, trigger, origin, grant, and policy-decision signers.
  • createAutonomousAgentSessionReceipt lets a session controller sign the run input/access record, including whether human prompts, human operators, user shells, or user-accessible agent environments were present.
  • createAutonomousRequestProof lets a protected request mediator sign the final post request against the access-token hash, binding hash, capability id, and content hash.
  • createAutonomousPolicyDecision lets an external policy engine sign the exact allow decision for the same binding, runtime evidence, and non-human trigger.
  • createAutonomousActionGrant lets your authorization service sign the subject/action/resource/runtime/source scope that is allowed to post.
  • createStrictGrantedAutonomousPostingProof assembles the producer-side posting envelope from protected runtime/session/trigger/policy/grant signers while leaving challenge solving and final API request-proof creation to their separate trust boundaries.
  • verifyStrictGrantedAutonomousPosting composes strict response verification, request-proof creation, granted action verification, and the assurance report; it only returns ok: true for a controlled autonomous posting path.
  • verifyStrictGrantedAutonomousDeploymentAction audits the deployment manifest and then requires a strict autonomous capability carrying an approved agent-session receipt, a matching signed grant, a scoped policy decision, and a sender-constrained request proof before it consumes the final post capability.
  • verifyRuntimeAttestation checks that receipt against your approved issuers, runtime ids, policy hash, environment hash, execution mode, human-interaction rules, and optional signed trigger receipt policy.
  • verifyResponse can call consumeVerification before issuing a capability so solved challenges, runtime attestations, and trigger receipts cannot be replayed within their TTL.
  • verifyStrictAutonomousResponse and verifyStrictAutonomousAction wire those checks together for the verification endpoint and final posting endpoint.
  • When a challenge has a binding, a successful response returns a signed capability. The capability records whether verification was consumed before issuance.
  • verifyCapability checks that the capability is unexpired and matches the exact subject, action, resource, and content hash being submitted.
  • When configured, verifyCapability also requires the capability to carry an approved runtime attestation summary.
  • Verification is stateless; you do not need to store the generated solution in a database.

Action-bound verification

Use action-bound verification when you want to stop a human from solving a challenge once and then manually posting something else. The verification is bound to one exact operation:

  • subject: the user, account, API key, or agent identity.
  • action: the operation, such as create_post.
  • resource: the endpoint or destination, such as POST /posts.
  • contentHash: SHA-256 of the normalized payload being authorized.
  • nonce: generated by the challenge and returned in the signed capability.

The capability is intentionally not a login session. It is a short-lived, single-action proof. Your application should consume capabilityResult.consumeKey exactly once in the same database transaction that performs the protected action.

Difficulty levels

  • lite: fewer, smaller tasks for quick local checks.
  • standard: default multi-task verification.
  • gauntlet: larger randomized work set for stricter agent gates.

All levels accept ttlMs and taskCount overrides:

const challenge = createChallenge({
  secret,
  difficulty: "gauntlet",
  ttlMs: 60_000,
  taskCount: 8
});

Security notes

Use a strong server-side secret and generate challenges immediately before asking the agent to solve them. Do not accept expired challenges. For high-value gates, rotate secrets, combine this with rate limiting and account reputation, consume capability keys exactly once, and consider adding private task packs that are not shipped to clients.

For human-resistant posting gates, require runtime attestations and keep the runtime attestation secret in a trusted mediator, hosted worker, hardware-backed key, or other environment the agent and user cannot read. A direct library integration cannot stop a human who controls both the agent runtime and the runtime signing key.