Skip to content
INTYGAStart free

INTYGA · Technical Whitepaper

Signing the Letter, Not the Envelope

A minimal architecture for proving that a human authorized a specific irreversible action — and for letting anyone verify that proof without trusting the vendor who issued it.

Version 1.0September 2026ECDSA P-256 · WebAuthn · Merkle
Contents

§0Abstract

Automation now executes actions that used to require a person: dropping databases, moving money, rotating credentials, applying infrastructure changes — executed by autonomous scripts, scheduled background services, CI/CD pipelines, and increasingly by AI agents acting from a natural-language instruction. Authorization did not keep up. The industry's response has been to build progressively more elaborate machinery around identity — who the actor is, which credentials it holds, how long they live, which broker issued them.

Identity and access policy establish who may act. Per-action approval establishes who approved this particular operation. An audit can require both; a standing permission alone does not establish a contemporaneous human decision.

INTYGA reduces the problem to a single primitive: a human signs a canonical representation of the intended action, and the executing service verifies it against its own parameters and trusted approver keys. This paper describes that primitive, the architecture around it, and its trust boundaries.

§1The gap automation opened

A CI pipeline holds a static token. A backend service holds an API key. An AI agent holds a scoped credential. All three can execute irreversible operations, and in every case the authorization decision was made once, in advance, by someone who was not looking at the specific action when it ran.

That was tolerable when automation was predictable. It is not tolerable when the caller is a model executing tool calls via protocols like the Model Context Protocol (MCP) that can be prompt-injected, or a script whose blast radius nobody re-evaluated after the last refactor.

For high-risk AI systems, Article 14 of the EU AI Act requires design that enables effective human oversight, with measures proportionate to the risks, autonomy and context of use. This includes the ability to interpret outputs, override decisions or stop the system where appropriate. It does not mandate cryptographic proof. An action-bound signature can support evidence of an approval; it does not establish compliance by itself.

The conventional answer is an audit log. A database row can be useful operational evidence, but a mutable record controlled by the party under review does not independently establish that a human approved the action. External retention, integrity proofs and approver signatures address different parts of that trust problem.

Can you prove that a specific named human agreed to this specific operation, with these exact parameters — and can a third party verify that without trusting you, your vendor, or your cloud?

The rest of this paper describes the conditions under which INTYGA can provide that evidence.

§2Four ways the industry gets this wrong

We want to be fair here. The systems below are well engineered and solve real problems. They just solve a different problem than the one above, and the difference is usually obscured rather than stated.

2.1 · Solving identity and calling it authorization

The dominant pattern is to make credentials more granular and shorter-lived: scoped session tokens, ephemeral credentials, workload identity, agent registries, identity brokers in front of tool servers. All of this genuinely reduces blast radius.

None of it produces evidence about intent. A perfectly scoped ten-minute credential still executed because a policy written months ago said it could. When the auditor asks who approved the production wipe, “an appropriately scoped machine identity” is not an answer.

Scoped credentials constrain what an agent may do. They say nothing about who decided it should.

2.2 · Vendor-attested audit dressed as non-repudiation

Several platforms describe their audit logs as non-repudiable because entries are hashed and the digests signed. Read carefully who holds the signing key. If the platform signs its own log, you have tamper-evidence against third parties — real and useful — but not non-repudiation. The attesting party is the party holding the record.

Cryptography applied at the wrong point adds cost without changing the trust model. The test is simple: can the vendor forge an entry? If yes, the signature provides internal tamper-evidence, but it does not alter the trust model or establish independent non-repudiation.

2.3 · Transaction detail is no longer the differentiator

The most direct competitors do put a human in the loop, typically by pushing a confirmation to a second device. The weakness is not what the approver is shown — it is what the approval is bound to, and when that binding is checked.

OIDC CIBA paired with OAuth Rich Authorization Requests (RFC 9396) can close that functional gap. The approver sees structured authorization_details, the authorization server binds approved details to an access token, and the resource server enforces them. Auth0 documents this flow. A correct integration can reject runtime parameters that differ from those approved.

The remaining distinction is the assurance and evidence model, not whether exact details can be enforced. In this OAuth flow, the authorization server attests the approved details in its token. In INTYGA, the WebAuthn assertion binds a digest of the canonical action payload, the relying party re-derives it before execution, and the resulting receipt is retained as durable evidence. If trusting the authorization server as attester is acceptable, CIBA + RAR may be the simpler solution. INTYGA is for cases where the approver's own key, server-authoritative quorum and platform-unforgeable evidence matter.

2.4 · Signing the envelope instead of the letter

A more sophisticated variant has the user cryptographically sign a scope object before a session begins — permitted actions, boundaries, a time window — after which agent actions are checked against that signed scope.

This is a real improvement and the cryptography is usually sound. But it is a standing grant. One signature is amortized across many later actions, and no human is present when any of them execute. It proves someone once authorized a class of behaviour. It cannot prove anyone authorized the particular irreversible thing that just happened.

2.5 · The common root: complexity concentrates trust

Every additional component — broker, vault, scoring engine, policy service, anomaly detector — is another thing that must be correct and another party you must trust. Complexity in a security system is not neutral. It expands the set of assumptions an auditor has to accept on faith.

Our design constraint was the opposite: minimise what you are required to trust, ideally down to elliptic-curve mathematics and a key you control.

§3The reduction

Strip the problem to its irreducible core and you get one sentence:

A human uses a private key that nobody else holds to sign a canonical representation of the exact action about to execute. Before executing, the relying party re-derives that representation from its own parameters and checks the signature itself.

Verification runs in your process, offline, with no INTYGA secret. It requires approver identities or keys pinned through a channel you trust, your own expected action, and a trusted execution path. Fetching both the receipt and its trust anchor from a compromised gateway would defeat the independence of the check.

We hold no approver signing key, so we cannot forge their signature against an independently pinned key. The user still relies on the approval browser to display the action corresponding to the signed bytes. Key custody does not make that display trustworthy after compromise.

This is not a marketing posture. It is a structural property that constrains what we are able to build — and it is why the verifier is open source under Apache-2.0. An offline guarantee validated by a closed binary is not a guarantee.

§4Architecture

4.1 · The flow

  caller (script / pipeline / service / AI agent)
      │  POST /authorize
      │       { target, actionType, actionDescription, params }
      ▼
  ┌─────────────┐   evaluates ApprovalRule (server-authoritative)
  │   Gateway   │───────────────────────────────────────────────┐
  └─────────────┘                                               │
      │  nonce                                                  │
      ▼                                                         ▼
  caller polls                                    human reads the exact structured
                                                  params and signs with a passkey
                                                  or security key (WebAuthn P-256)
      │                                                         │
      │◀───────────── receipt ──────────────────────────────────┘
      ▼
  verifyApprovalReceipt(receipt,
      { target, actionType, params, nonce, approvers },
      { expectedOrigin, expectedRpId })
      │   re-derives the canonical payload locally — offline
      │   and checks the signature against YOUR trusted keys
      ▼
  atomically redeem nonce (consume or own durable record)
      ▼
  execute (only now)

WebAuthn signs authenticator data and a hash of clientDataJSON; the challenge inside that client data is bound to the canonical action digest. Verification checks this binding, the signature, origin, RP ID and user-verification flags. The authenticator does not itself display or interpret the action parameters. The executor must prevent callers from bypassing the gate with direct execution credentials.

4.2 · The canonical payload

The signed artifact is the DIV Intent Payload: a deterministically serialised JSON object following RFC 8785 (JSON Canonicalization Scheme / JCS), with DIV's additional numeric restrictions and sorting of set-valued fields — every key sorted recursively — binding the intended execution target, action type, human-readable display summary, the full structured parameters, the requester, the approval requirement (quorum, four-eyes, hardware class, authenticator allowlist — so a 3-of-3 hardware-pinned receipt is not byte-identical to a 1-of-1 one), a one-time nonce, and an ISO expiration timestamp (expiresAt). The target is what makes an approval non-transferable across services (Target Isolation), and the requester binds which workload asked and whether it was third-party attested (OIDC / SPIFFE) or merely holding a credential — that null is signed too, so a receipt distinguishes “an attested CI workload asked” from “something with an API key asked”.

AI-agent requests add signed reversibility, an agent configuration digest, an optional parent-authority receipt hash, and a session sequence, predecessor and decimal running amount. Their short-lived validity uses nbf and exp. The approval screen shows the agent identity, configuration digest and running amount. A digest is an RP assertion, not proof that the agent is intact: the executing service must recompute it from the live model, tools and prompt, then reject drift. It must also atomically check the session chain and budget before executing. The raw prompt and personal data do not belong in these new receipt fields.

Shared vectors test canonicalization across the gateway, approval UI, and the TypeScript, Python, Go, Rust and Java verifiers. They are not a proof for every possible input. Keep integer parameters within the safe-integer range (±(253 − 1)) and encode larger values as decimal strings: parsers can disagree above that range even within DIV's current numeric limits.

The signed requirement records quorum, four-eyes, hardware constraints and the required signer class. Identity-bound trust is needed to count people and enforce four-eyes; a key-only anchor counts credentials and is refused when requesterCannotApprove is set. A WebAuthn assertion cannot independently establish the authenticator model or its registration AAGUID. Those checks depend on enrollment and gateway enforcement. The reserved evidence field must be null, and the only recognized signerClass is human; unknown values fail closed.

4.3 · Verification, and why it takes your parameters

const check = verifyApprovalReceipt(receipt, {
  target: "prod-db-cluster-01",                           // YOUR execution environment (Target Isolation)
  actionType: "wipe_production",
  params: { host: "prod-db-1", region: "eu-north-1" },    // what you are ACTUALLY about to run
  nonce,                                                   // the challenge YOU issued
  approvers: { dids, resolveKey },                         // YOUR trust anchor — see below
}, {
  expectedOrigin: process.env.INTYGA_WEBAUTHN_ORIGIN!,
  expectedRpId: process.env.INTYGA_WEBAUTHN_RP_ID!,
});
if (!check.ok) throw new Error(check.reason);  // expired proofs are rejected fail-closed by default

You re-pass what you are about to execute. The library recomputes the canonical payload from your values and compares it to what was signed. One byte of drift — a swapped target, an appended region — and verification fails.

approvers is the load-bearing argument, and it has no default on purpose. Without it the only key available to check the signature is the one carried inside the receipt, which would establish nothing beyond internal consistency: under the DIV threat model the party handing you a receipt may be the untrusted agent itself, and it could mint a keypair, sign a payload over the nonce you just issued and the parameters you are about to run, put any string in signerDid, and be told a human approved. Resolving the approver key from your own key management — a pinned enrollment record, a directory lookup, an allowlist — is what makes the check mean anything. DIV §3 Invariant 3 requires it, and the library refuses to run without it rather than accepting a weaker default.

Parameter drift · what the human signed vs. what the code runs
signed { actionType: "wipe_production", params: { db: "staging" } }
executing { actionType: "wipe_production", params: { db: "prod-1" } }
ok: falsereason: “target/params/actionType do not match what was approved” — execution refused

This is defence-in-depth against a compromised INTYGA. Even if our gateway were fully controlled by an attacker, it could not cause you to execute an action a human did not sign, because the check runs in your process against your parameters using the approver's public key.

Consider a concrete operational example:

  • Human approves: deleteDatabase({ environment: "staging" })
  • Attacker attempts execution: deleteDatabase({ environment: "production" })

Because exact parameters are canonicalized and signed into the challenge payload, the verification step immediately rejects the execution: the signature over "staging" does not verify against "production".

Three deliberate decisions in that call:

  • target and nonce are required, not read from the receipt. You name the target you are executing against and the challenge you are redeeming, so cross-service replay and reuse become your explicit assertions rather than silent assumptions. The signed expiresAt is enforced fail-closed by default (with a small clock-skew tolerance); an allowExpired opt-in exists only for audit re-verification. Single-use enforcement still lives in the gateway's consume step, which atomically marks the challenge consumed. If you skip that call, your executor must maintain its own durable, atomic record of redeemed nonces before performing the action.
  • WebAuthn receipts require a pinned origin and RP ID. An assertion proves a credential signed some bytes; it does not say which relying party asked. We refuse to verify unless you pin both, and we check user presence and verification.
  • Policy auto-approvals are refused by default. A scheduled pre-approval window can produce a receipt with sigAlg: "AUTO_APPROVED" and no human signature. Without the explicit allowAutoApproved opt-in, success requires a signature under a trusted approver key. WebAuthn additionally checks user presence and, by default, user verification; a raw ES256 signature alone cannot prove a human gesture. The signed Offline Approval procedure is a separate path.

When the honest answer is “nobody signed this”, the API says no.

4.4 · Governance the caller cannot rewrite

Quorum is not a client argument. There is no requiredApprovals parameter in the SDK, by design. Rules live server-side and are evaluated by the gateway:

The caller supplies an action ID, which the gateway matches exactly; the description does not select a rule. The tenant baseline (*) supplies minimum requirements that each specific rule must retain. New tenants deny unknown action IDs unless the administrator explicitly chooses baseline approval. Migrated tenants retain recorded older semantics until signed activation. These checks do not prove that the caller’s ID describes the real operation: executing services must verify receipts against their actual action and parameters.

ControlBehaviour
requiredApprovalsM-of-N distinct approver identities, each contributing a valid signature, before the challenge resolves — two signatures from one approver's two registered credentials are one approval (DIV §4.4.2)
approverDids / groupsEligibility, snapshotted at challenge creation so later membership edits never widen an in-flight approval
requesterCannotApproveFour-eyes — the approver must differ from the requester
requireHardwareKey / allowedAaguidsThe approving credential must be device-bound, optionally a specific authenticator model
requireAttestedRequesterThe requesting workload must have proved identity via OIDC or SPIFFE
escalateAfterSecondsAdds approvers and re-notifies — never lowers quorum, bypasses four-eyes, or extends expiry

The caller cannot rewrite the requirement frozen into a challenge. When required by the governing rule, hardware and requester-attestation constraints are enforced at signing time. A leaked requester key alone cannot satisfy a requirement for an attested workload.

4.5 · Completeness: a two-tier Merkle log

Approval receipts bind trusted approver signatures to an action. Transparency checks detect modification or omission of committed events within a verified sequence range, relative to a trusted checkpoint.

Events are sealed into block roots on a short cycle, block roots fold into a daily root, and checkpoints can be anchored to independent witnesses. An exported proof can be checked offline against a root obtained through an independently trusted channel. The file below is your retained root list, not a promise that a public discovery repository is live:

npx -p @intyga/sdk intyga audit-verify ./proof.json --roots roots/roots.jsonl
# exit 0 = verified

To let a tenant detect that something of theirs was omitted (not merely altered), each tenant-scoped audit event carries a contiguous per-tenant sequence counter (tenantSeq) committed directly into the Merkle leaf preimage — the commitment, not the human signature, is what binds it (DEWP §4.6). Verification confirms that exported tenant audit sequences are strictly gapless. This is scoped to committed events: a gap proves something was removed after commitment, but no log can prove that a producer never withheld an event from commitment in the first place.

4.6 · Policies we cannot read

Organisational policy manifests are encrypted client-side (AES-256-GCM + RSA-OAEP) and published to us as opaque ciphertext plus a hash. We enforce version freshness and relay the blob. We never hold the decryption key.

This encryption applies to the policy relay. ApprovalRules remain gateway-readable so the gateway can enforce quorum and eligibility. Hosted action parameters and any connection-level policy explicitly supplied in plaintext are also visible to the gateway. The relay's confidentiality does not make the whole service zero-knowledge.

4.7 · Operationalizing the primitive

While the core primitive is minimal — signing the letter rather than the envelope — turning it into production infrastructure requires careful engineering across four core themes:

  • Deterministic canonicalization: Payloads use a canonical JSON representation across implementations. Producers reject non-portable numbers; shared vectors test the supported cases and §4.2 states the integer-precision limit.
  • Governance and replay protection: Gateway-owned rules freeze each challenge's requirement. Atomic nonce redemption prevents that approval from being reused; offline signature verification alone does not.
  • Independent verification: Implementations in TypeScript (@intyga/verify), Python (intyga-verify / intyga-sdk), Go (github.com/intyga-dev/verify-go), Rust (intyga-verify) and Java (com.intyga:intyga-verify) verify ordinary, offline and delegation receipts, Agent Authority seals (DIV §5b), and platform receipts (DIV §5c). All five also support DEWP single- and multi-event bundles, checkpoint continuity and caller-policy anchor quorum with ES256, Ed25519, RSA-PSS and pinned-key Rekor evidence. Shared vectors exercise these features; helper APIs and limits remain documented per implementation.
  • Transparency and completeness: Per-tenant sequence counters (tenantSeq) are committed directly into Merkle leaf preimages, letting a relying party verify the authenticity and gapless ordering of committed events up to a checkpoint watermark. It cannot prove that an event was never withheld from commitment in the first place.

§5Enterprise deployment patterns

INTYGA is designed to integrate into existing enterprise architectures as a specialized authorization primitive:

  • Infrastructure automation: Gating high-risk infrastructure changes (terraform apply, production K8s deployment) behind developer passkey biometrics via @intyga/sdk or POST /authorize.
  • Database operations: Requiring parameter-bound human signatures over administrative DDL/DML statements (TRUNCATE, production schema migrations).
  • IAM and key management: Authorizing KMS key rotations, production break-glass access, and OAuth client secret provisioning.
  • AI agent tool execution: Intercepting high-risk MCP tool calls (execute_sql, transfer_funds, deploy_service) over secure control tunnels and enforcing step-up policies (require_approval).
  • Multi-signature approvals: Enforcing M-of-N quorum escalation for critical enterprise actions (e.g., 2-of-3 approvers required for wire transfers or production wipe).

§6Threat model

What an attacker gains by compromising each component:

CompromisedConsequence
The calling script or AI agentIt can request approvals and choose action labels that influence rule selection (§4.4). It cannot forge trusted approver signatures. Execution remains protected only when an independently controlled executor verifies its own parameters and the caller has no bypass credentials.
The INTYGA gateway and databaseCannot forge signatures under independently pinned approver keys. Can deny service, manipulate online policy or enrollment records, and withhold evidence. Trusted checkpoints and verified sequence ranges expose changes after commitment; events withheld before commitment leave no detectable gap. A receipt proves the requirement that was signed, not that a compromised gateway selected the organisation's intended rule.
The approval UI or its delivery pathCan misrepresent the action shown to the human or solicit a signature over different bytes. WebAuthn protects the signing key and origin binding; it does not provide a trusted display of transaction parameters.
The relying party's own processFull compromise. Nothing helps; the attacker is already where execution happens. We do not claim otherwise.
The approver's deviceApprovals can be made. This is why requireHardwareKey, AAGUID pinning and M-of-N exist — to raise the cost from one compromised laptop to several device-bound authenticators.

We fail closed for authorization: no approval means no action. That is the correct default for an irreversible operation, and it does mean we become a dependency in your critical path. We would rather state that plainly than discover it together during an incident.

§7What we did not build

No trust scores or behavioural anomaly detection.Anomaly detection can identify suspicious activity, but a score does not establish that a human approved exact parameters. INTYGA records and verifies that narrower decision.
No general semantic safety engine.We do not decide whether an arbitrary natural-language action is safe or appropriate. Connection-level policies and scheduled pre-approval rules do exist, but their unsigned outcomes do not establish human approval. The per-action proof applies where a human signature is required and verified.
No distributed ledger.Tamper-evidence needs a hash chain and a published root, not consensus. Merkle trees have done this since 1979.
No proprietary mobile app, no MDM enrolment, no app-store dependency.Modern browsers support WebAuthn with platform passkeys and hardware security keys. Synced passkeys are allowed by default; a relying party can require a hardware-bound credential for higher-risk actions. Approvers are reached by browser web push, email, or a chat webhook. The notification carries a nonce, a message kind and a deep link, omitting action text and parameters; the approver's browser fetches the authoritative item over TLS. The individually-addressed channels (email, encrypted web push) additionally carry a short-lived, per-approver link token that opens the approval page without a console login. It is a capability to READ one challenge, never to sign it.
No npm runtime dependencies in the TypeScript verifier.@intyga/verify uses Node's built-in cryptography and requires Node.js 18 or later. Optional RFC 3161 verification additionally uses an installed OpenSSL 3 executable. Its source includes receipt, ledger, anchor and bundle verification. It is available for inspection under Apache-2.0; it is not a browser-portable verifier. Other language implementations have their own declared dependencies.
No workload or agent identity product.Others do workload identity well. We consume their attestations (OIDC, SPIFFE) and bind them into the signed payload. Rebuilding that layer would add surface without adding proof.

§8Honest limitations

  • Shared feature coverage has explicit limits. The five implementations cover the receipt and audit features in §4.7, with shared test vectors rather than a formal proof of conformance. All five offer offline RFC 3161 timestamp verification through optional OpenSSL 3 adapters, with caller-configured TSA trust, signer certificate pins and revocation policy. Timestamp evidence proves existence by the TSA time, not the time of the underlying action. None implements NDJSON evidence streaming; WEBHOOK anchors do not count toward quorum. An audit leaf alone cannot verify a WebAuthn signature: retain the full DIV receipt. Offline Agent Authority verification checks the seal, not later online revocation. There is no .NET verifier. Package publication and installation status are recorded in each package's README; source implementation does not by itself establish registry availability. Compare verifier capabilities.
  • Approval fatigue is unsolved in general. Integrators choose which operations require a human signature. Scheduled pre-approval and other unsigned outcomes are refused by ordinary verification unless explicitly accepted; they cannot be represented as human-approved actions. If you prompt a human 200 times a day, they will stop reading, and no cryptography repairs that.
  • We are a dependency in your critical path, and we fail closed. The DIV §5a Offline Approval procedure uses a previously exported Trust Bundle and a new human signature at incident time. Verify and redeem that approval before execution, then reconcile its record afterwards. A bounded delegation is needed only when named local operators must substitute for unavailable approvers. Self-hosting is another deployment option.
  • We hold no security attestation yet. We publish a control mapping for SOC 2 CC6.1 and EU AI Act Article 14. A mapping is not an attestation and we do not present it as one.
  • The document-signing flow is parked and cannot be completed by a passkey user. It remains in the codebase; it is not a shipping surface.

§9Conclusion

The authorization space for autonomous scripts, services, and AI agents is accumulating machinery quickly: brokers, registries, scoped credential services, scoring engines, delegation frameworks. Most of it is competent, and most of it is answering who can act.

This architecture answers who approved this action, and it has a small answer. A human signs the exact bytes. Anyone can check the signature. The vendor cannot forge it, and the proof outlives the vendor.

We think the interesting engineering is not in adding to that, but in refusing to.