paperclipai/paperclip · error
Cloud runtime identity previous or canonical origin is inval
Error message
Cloud runtime identity previous or canonical origin is invalid
What it means
verifyCloudRuntimeIdentityAssertion validates that both previousOrigin and canonicalOrigin are exact HTTPS origins and that previousOrigin equals the caller-supplied expectedPreviousOrigin. It throws when either origin is missing/malformed/not https, or when the previous origin on the assertion does not match the origin expected for this claim, blocking origin-rewrite or downgrade attacks during a runtime identity handoff.
Source
Thrown at server/src/services/cloud-runtime-identity.ts:331
}
/** Verify that an assertion is signed for this exact, still-unclaimed instance. */
export function verifyCloudRuntimeIdentityAssertion(input: {
compactJws: string;
env?: NodeJS.ProcessEnv;
now?: Date;
expectedPreviousOrigin: string | null;
}): RuntimeIdentityClaims {
const env = input.env ?? process.env;
const claims = verifyClaims({ compactJws: input.compactJws, env, now: input.now ?? new Date() });
const configuredStackId = nonEmpty(env.PAPERCLIP_CLOUD_STACK_ID);
if (!configuredStackId || claims.sub !== configuredStackId) {
throw new Error("Cloud runtime identity stack does not match this instance");
}
const previousOrigin = exactHttpsOrigin(claims.previousOrigin);
const canonicalOrigin = exactHttpsOrigin(claims.canonicalOrigin);
if (!previousOrigin || !canonicalOrigin || previousOrigin !== input.expectedPreviousOrigin) {
throw new Error("Cloud runtime identity previous or canonical origin is invalid");
}
if (
!STACK_SLUG_PATTERN.test(claims.stackSlug)
|| new URL(canonicalOrigin).hostname.split(".")[0] !== claims.stackSlug
|| claims.claimId.length > 256
|| claims.claimId.trim() !== claims.claimId
|| !claims.claimId
) {
throw new Error("Cloud runtime identity destination is invalid");
}
return claims;
}
function assertionsEqual(row: PersistedRuntimeIdentity, claims: RuntimeIdentityClaims): boolean {
const left = Buffer.from(JSON.stringify([
row.stackId,
row.claimId,
row.previousOrigin,View on GitHub (pinned to 01ad858492)
Solutions
- Re-mint the assertion with exact https origins matching the instance's actual previous/canonical origins
- Verify expectedPreviousOrigin passed by the caller matches the origin recorded when the claim flow started
- Normalize origin strings to scheme://host only (no path, trailing slash, or non-443 port)
- Check for replay of an assertion from an earlier handoff and start a fresh claim flow
Example fix
// before previousOrigin: "https://old.example.com/" // after previousOrigin: "https://old.example.com" // exact origin, no path or trailing slash
Defensive patterns
Strategy: validation
Validate before calling
const prev = new URL(claims.previousOrigin);
const canon = new URL(claims.canonicalOrigin);
if (prev.protocol !== 'https:' || canon.protocol !== 'https:' || prev.pathname !== '/' || prev.origin !== expectedPreviousOrigin) {
throw new Error('origin claims invalid before apply');
} Type guard
function isExactHttpsOrigin(v: unknown): v is string {
if (typeof v !== 'string') return false;
try { const u = new URL(v); return u.protocol === 'https:' && u.pathname === '/' && u.search === '' && u.hash === '' && v === u.origin; } catch { return false; }
} Try / catch
try {
await verifyAssertion({ compactJws: jws, expectedPreviousOrigin });
} catch (e) {
if (e.message.includes('origin is invalid')) throw new Error(`origin mismatch: expected ${expectedPreviousOrigin}; restart claim flow`);
throw e;
} Prevention
- Always mint exact origins (URL.origin, no path/trailing slash/port surprises)
- Store and reuse the same expectedPreviousOrigin throughout a claim flow
- Reject http:// origins at mint time
- Watch for handoff migrations that change the previous origin mid-flow
When it happens
Trigger: Assertion claims.previousOrigin or claims.canonicalOrigin is absent, not an exact https origin (has path/query/port surprises), or previousOrigin !== input.expectedPreviousOrigin passed by verifyAssertion (e.g. claim retried against a different previous origin than originally expected).
Common situations: Assertion minted with http:// origin; trailing-slash or path-bearing origin strings; an assertion from a prior migration/handoff replayed after the expected previous origin changed; hostile assertion attempting to rewrite canonical origin.
Understand the failure class
Background: "Invalid URL" errors: why new URL(), URI.parse, and reqwest::Url reject your string — missing scheme, whitespace, and bad path format — this error's family across 39 libraries.
Related errors
- Persisted Cloud runtime identity contains an invalid origin
- Cloud runtime identity stack does not match this instance
- Cloud runtime identity destination is invalid
- devUiUrl must use http or https protocol
- ${options.errorLabel} decision predicate failed: ${detail}
AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10).
Data as JSON: /api/errors/e35b6bae51580ce7.
Report an issue: GitHub.