Time-Bound Receipts: expires_at and the Receipt Nonce
Asqav stamps an expiry timestamp and a per-receipt nonce on every signed receipt, so a captured envelope cannot be re-presented forever. The expires_at field is computed by the cloud signer from the request's valid_seconds (or the org's default TTL of 86,400 seconds). The nonce is a fresh random value the signer mints for each receipt and binds into the signed bytes.
What are expires_at and nonce?
expires_at is a REQUIRED ISO-8601 timestamp the cloud computes as signed_at + valid_seconds. The signer never trusts a client-supplied expiry value. The client supplies a window length, the cloud stamps the absolute moment.
The nonce on a receipt is a 24-hex-character value (12 random bytes from secrets.token_hex) minted by the cloud on every signing call, which gives 2^96 collision resistance. A sign request may carry a nonce of its own, up to 128 characters, which the cloud stores on the record alongside the receipt it issues.
Together they implement the NSA recommendation that MCP messages cryptographically bind requests to time and context. A verifier checks now < expires_at to flag the receipt as expired, and the nonce inside the signed bytes makes two receipts over identical content distinguishable from each other.
How does Asqav implement Recommendation 5 with expires_at and nonce?
The NSA CSI states the case for time-bound and replay-protected messages in the recommendations chapter:
MCP messages should include expiration timestamps and replay protection metadata to guard against delayed or duplicated messages, which is a known risk in distributed or event driven systems. The recommendations from OWASP Application Security Verification Standard (ASVS) V7 Session Management, are readily applicable here. In alignment to the well-known OWASP guidance in application security, MCP messages should cryptographically bind requests to time and context to prevent tampering, intentional replay techniques, and unintended re-execution errors. NSA CSI U/OO/6030316-26, p.12
Asqav reads "expiration timestamps and replay protection metadata" literally. Every receipt carries expires_at as part of the signed envelope, so an out-of-band edit to the database row cannot widen the window. On the observation ingest path a unique index over (organization_id, body_sha256, signed_at) rejects an identical body submitted twice at the same wall-clock second from the same org, and the per-receipt nonce rides inside the signed bytes as the value a consumer keys its own duplicate check on.
How do I set the validity window?
The producer sets valid_seconds on the sign request. When unset, the cloud applies the org's valid_seconds_default (factory default 86,400 seconds, i.e. 24 hours). The cap is 31,536,000 seconds (1 year). The floor is 1 second.
JSON wire example
{
"action_type": "mcp:tool_call",
"valid_seconds": 3600
}
Python SDK
import asqav
asqav.init()
agent = asqav.Agent.create("trading-bot")
receipt = agent.sign(
"mcp:tool_call",
{"tool": "broker.place_order", "symbol": "ASQV", "qty": 100},
valid_seconds=3600,
)
print("expires_at:", receipt.expires_at)
curl
curl -X POST https://api.asqav.com/api/v1/agents/agt_bot_001/sign \
-H "X-API-Key: $ASQAV_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"action_type": "mcp:tool_call",
"valid_seconds": 3600
}'
How does a verifier flag an expired receipt?
POST /api/v1/verify returns verified=true whenever the cryptographic axes pass, whether or not the receipt's expires_at has elapsed. Once expires_at has passed the response keeps verified=true and reports validation_label='signature_expired' with the not_expired axis set false: expiry never folds into the verdict, it is reported as its own axis/label, and what it blocks is replaying the expired decision as fresh authorisation. An expired receipt remains a valid historical proof of the original action.
For audit packs, the offline verifier checks the same window. A receipt with expires_at six months in the past is still a valid historical proof that the agent did the thing on the original date. What validation_label='signature_expired' blocks is re-presenting the receipt as authorisation for a NEW action today.
Why both expires_at and nonce?
expires_at bounds how long a receipt can be honoured. The nonce does the other half of the job: it makes each receipt a distinct object, so two signing calls over identical content produce envelopes a verifier can tell apart.
Observation ingest
A Postgres unique index over (organization_id, body_sha256, signed_at) rejects an identical body submitted twice at the same wall-clock second from the same org. A network double-submit resolves to the receipt already on file instead of minting a second one.
The receipt nonce
The signer mints a fresh 24-hex nonce on every call and binds it into the signed bytes under compliance mode. Because two receipts over the same action carry different nonces, a consumer that records the nonces it has already honoured can recognise an envelope it has seen before.