paperclipai/paperclip · error
Cloud control assertion request id is invalid
Error message
Cloud control assertion request id is invalid
What it means
verifyCloudControlAssertion validates the requestId claim for replay protection: it must be a non-empty string, contain no leading/trailing whitespace, and be at most 256 characters. The requestId is later consumed idempotently (see the consumeControlRequestId call after the expiry check), so it must be a stable, unique identifier.
Solutions
- Generate requestId as a short unique identifier — crypto.randomUUID() or an id ≤ 256 chars with no surrounding whitespace
- Trim or validate the id at the issuer before signing; never interpolate it into templates with extra spaces
- Fix fallback paths that stringify undefined/null values into the requestId claim
- Add an issuer-side assertion mirroring the verifier's checks so bad ids fail at mint time
Example fix
// before
const requestId = `control ${operation}-${Date.now()} `;
// after
const requestId = crypto.randomUUID();
if (!requestId || requestId.trim() !== requestId || requestId.length > 256) throw new Error("bad requestId"); Defensive patterns
Strategy: validation
Validate before calling
function isValidRequestId(id) {
return typeof id === "string" && id.length > 0 && id.trim() === id && id.length <= 256;
}
if (!isValidRequestId(requestId)) throw new Error("requestId must be non-empty, untrimmed-free, and <=256 chars"); Type guard
const isRequestId = (v: unknown): v is string => typeof v === "string" && v.length > 0 && v.trim() === v && v.length <= 256;
Prevention
- Always generate requestId via crypto.randomUUID() or equivalent bounded unique id
- Validate at mint time with the same rules the verifier enforces
- Never build request ids via string interpolation that can add whitespace or stringify undefined
When it happens
Trigger: A minted assertion with an empty requestId ("") or whitespace-only value; requestId built by naive string concatenation that introduced leading/trailing spaces; an oversized identifier (e.g. a full URL or concatenated trace id exceeding 256 chars) placed in the claim.
Common situations: Template interpolation like `req ${id}` leaving a trailing space; using a long JWT or URL as the requestId; a client generating undefined/stringified-object request ids ("undefined", "[object Object]") under error conditions.
Understand the failure class
Background: "invalid id" errors: invalid identifier format — why libraries reject IDs before lookup, and how to fix them — this error's family across 37 libraries.
Related errors
- Cloud control assertion is expired or has an invalid…
- Cloud control claims are incomplete
- Cloud control assertion does not authorize this action
- Cloud control assertion has already been used
- Cloud control assertion is not a compact JWS
AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18).
Data as JSON: /api/errors/5f8bd21fe44d5f87.
Report an issue: GitHub.
Appendix: source
Thrown at server/src/services/cloud-runtime-identity.ts:562
|| !Number.isInteger(payload.exp)
) {
throw new Error("Cloud control claims are incomplete");
}
if (!configuredStackId || payload.sub !== configuredStackId) {
throw new Error("Cloud control assertion stack does not match this instance");
}
if (
!(CLOUD_CONTROL_ACTIONS as readonly string[]).includes(payload.action)
|| payload.action !== input.expectedAction
) {
throw new Error("Cloud control assertion does not authorize this action");
}
if (
!payload.requestId
|| payload.requestId.trim() !== payload.requestId
|| payload.requestId.length > 256
) {
throw new Error("Cloud control assertion request id is invalid");
}
if (
payload.exp <= nowSeconds
|| payload.iat > nowSeconds + MAX_CLOCK_SKEW_SECONDS
|| payload.exp <= payload.iat
|| payload.exp - payload.iat > CLOUD_CONTROL_MAX_LIFETIME_SECONDS
) {
throw new Error("Cloud control assertion is expired or has an invalid lifetime");
}
// Consumed LAST, only after every other check passed: a rejected
// assertion must not burn its request id, or an attacker could deny a
// legitimate call by replaying a mangled copy of it first.
if (!consumeControlRequestId(payload.requestId, payload.exp + MAX_CLOCK_SKEW_SECONDS, now.getTime())) {
throw new Error("Cloud control assertion has already been used");
}
return payload as CloudControlClaims;
}
View on GitHub (pinned to 3f1d897a7c)