[AVAILABLE], and wired into the default server as of commit
651497a (2026-07-11).
Earlier revisions of this page said the opposite, see the correction below.The problem: check-vs-use (TOCTOU)
A system that authorizes an action by checking a payload, then executes a different payload under the same authorization, has a Time-Of-Check-To-Time-Of-Use gap. An authorization that only names an ID (businessTransactionId: "tx-001"), without binding
to the actual content, cannot detect this: anything can be substituted under a
previously-approved ID.
The real mechanism
Parmana’sSignedExecutionAuthorization binds a canonical content hash into the signed
payload, not just an ID:
ExecutionGateway.verify() is where the check-vs-use gap actually closes: it recomputes
the hash of the content it is about to forward, and compares it to the signed
businessTransactionHash:
ExecutionGateway.ts:86-90):
ExecutionGateway.ts:170-180).
ExecutableContentHasher delegates to the same TrustRecordHasher used elsewhere in the
system (packages/crypto/src/ExecutableContentHasher.ts), the signing side and verifying
side run identical canonical serialization and hashing, never two parallel
implementations of the same computation.
What this proves, precisely scoped
From CLAIMS.md 3.1 (Conditional Claim, load-bearing scope): “For any system running the
Parmana envelope verifier, execution requests not authorized by Parmana are
cryptographically impossible to accept. This claim holds only for a receiving system that
(a) runs
@parmana/envelope-verifier and (b) gates every execution-triggering code path
behind its verification result. Parmana enforces nothing at the network level.”Confirmed: the default local server now enforces this
This was traced directly, not assumed.packages/api/src/bootstrap/createExecutionSystem.ts
unconditionally returns createExecutionGateway() (commit 651497a, 2026-07-11), and
packages/api/src/server.ts always passes that gateway into createApplication(). There is
no code path where the server skips gateway verification. See The gateway
for the current, wired mechanism and its one real caveat: only one connector is registered
by default, an action with no matching connector fails closed rather than skipping checks.
Demonstrated, at the library level
The REST API generates and verifies an authorization within the same request today, so a black-box HTTP client can’t provoke a hash mismatch through the public API alone, both sides of the check run inside the same process, on the same content, in the same call. The tamper-and-verify demonstration therefore has to happen where an authorization is a first-class object a caller can mutate before re-checking it, which is exactly whatexamples/tutorials/36-parameter-tampering/run.ts does: build a Business Transaction, get
its real businessTransactionHash back from RuntimeBuilder, independently recompute the
hash of a tampered copy of the same content (payment amount changed) using the same
ExecutableContentHasher, and compare:
ExecutableContentHasher and the same comparison
ExecutionGateway.verify() runs internally, exercised directly rather than through HTTP.
Tutorials 35 through 46 (examples/tutorials/) cover the same negative-path pattern for
replay, substitution, forgery, and expiry: mutate, re-verify, watch it fail. See Detect
tampering for three of these run live with real output.
Connector SDK: the same content, unmodified, all the way to the connector
@parmana/connector-sdk’s SdkConnectorExecutor sits downstream of every check above, it
only ever runs after ExecutionGateway.verify() has already passed and the content is
already deep-frozen (ExecutionGateway.ts’s deepFreeze(executableContent)). From there:
SdkConnectorExecutor.execute(content, credential)buildsConnectorRequestby copyingbusinessTransactionId,action,target, andparametersstraight off the verified, frozencontent, never re-deriving or re-interpreting any of them.- The same
ConnectorRequestobject is what aConnector(HttpConnector,MockConnector, or a future one) receives; nothing in between constructs a second copy. capabilityiscontent.actionitself, so “capability” cannot silently diverge from the authorizedaction, there is only one field, not two that could disagree.
ExecutionGateway.verify() hash comparison, this is a
permanent architectural invariant, not something connector-sdk re-implements or could
weaken. connector-sdk adds capability-declaration and version/health checks in front of
a connector call (SdkConnectorExecutor, CapabilityConnectorPolicy), all of which fail
closed (throw, never a partial or guessed success) before a connector is ever invoked.
Related, and also worth knowing
- Nonce single-use is scoped to whichever
NonceStoreinstance checks it. Multiple independent gateway instances each using their ownMemoryNonceStorecan each accept the same authorization once. Fleet-wide single-use requires one shared store (CLAIMS.md 3.2). - ML-DSA-65 (post-quantum) signatures are randomized, not deterministic. Signing the same message twice with the same key produces two different, both-valid signatures, only verification is deterministic. Don’t build tooling that assumes identical input produces an identical PQ signature (CLAIMS.md §5).
- Every envelope carries a bounded TTL specifically so the exposure window from either of the above gaps is bounded, not unlimited.