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

  1. Generate requestId as a short unique identifier — crypto.randomUUID() or an id ≤ 256 chars with no surrounding whitespace
  2. Trim or validate the id at the issuer before signing; never interpolate it into templates with extra spaces
  3. Fix fallback paths that stringify undefined/null values into the requestId claim
  4. 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

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


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)