{"record":{"id":"17eb425804484918","repo":"musistudio/claude-code-router","slug":"artifact-url-contains-an-invalid-access-token","errorCode":null,"errorMessage":"Artifact URL contains an invalid access token.","messagePattern":"Artifact URL contains an invalid access token\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/core/src/agents/codex/media-preview-bridge.ts","lineNumber":288,"sourceCode":"  return url.startsWith(\"app://codex\") || url.startsWith(\"app://chatgpt\") || /\\b(codex|chatgpt)\\b/i.test(target.title || \"\");\n}\n\nfunction validateCodexMediaArtifactUrl(value: string, endpoint: string): ValidatedArtifactUrl {\n  const expected = new URL(endpoint);\n  const url = new URL(value);\n  if (url.protocol !== \"http:\" || url.origin !== expected.origin) throw new Error(\"Artifact origin is not the configured CCR gateway.\");\n  if (url.username || url.password || url.hash) throw new Error(\"Artifact URL contains unsupported credentials or fragments.\");\n  if (!url.pathname.startsWith(MEDIA_ARTIFACT_PATH_PREFIX)) throw new Error(\"Artifact URL does not use the CCR media artifact path.\");\n  const encodedId = url.pathname.slice(MEDIA_ARTIFACT_PATH_PREFIX.length);\n  if (!encodedId || encodedId.includes(\"/\")) throw new Error(\"Artifact URL contains an invalid identifier.\");\n  const artifactId = decodeURIComponent(encodedId);\n  if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(artifactId)) {\n    throw new Error(\"Artifact URL contains an invalid identifier.\");\n  }\n  const keys = [...url.searchParams.keys()];\n  const token = url.searchParams.get(\"token\") || \"\";\n  if (keys.length !== 1 || keys[0] !== \"token\" || !/^[A-Za-z0-9_-]{32}$/.test(token)) {\n    throw new Error(\"Artifact URL contains an invalid access token.\");\n  }\n  return { artifactId, url };\n}\n\nasync function loadCodexMediaArtifact(validated: ValidatedArtifactUrl, signal: AbortSignal): Promise<LoadedMediaArtifact> {\n  let response: Response;\n  try {\n    response = await fetch(validated.url, {\n      headers: { accept: \"image/*, video/*\" },\n      redirect: \"error\",\n      signal\n    });\n  } catch {\n    throw new Error(\"The CCR artifact request failed.\");\n  }\n  if (!response.ok) throw new Error(`The CCR artifact endpoint returned HTTP ${response.status}.`);\n  if (response.redirected) throw new Error(\"The CCR artifact endpoint attempted a redirect.\");\n  const declaredMimeType = (response.headers.get(\"content-type\") || \"\").split(\";\", 1)[0].trim().toLowerCase();","sourceCodeStart":270,"sourceCodeEnd":306,"githubUrl":"https://github.com/musistudio/claude-code-router/blob/99f24806c6a2c660b16e53e95211c517448a6c90/packages/core/src/agents/codex/media-preview-bridge.ts#L270-L306","documentation":"The final check in validateCodexMediaArtifactUrl: the URL must carry exactly one query parameter named token whose value matches ^[A-Za-z0-9_-]{32}$ (a 32-char base64url token). Extra params, a missing token, a differently-named param, or wrong length/charset all throw this.","triggerScenarios":"Artifact URL with no ?token=, with additional query params (?token=...&foo=1), a token shorter/longer than 32 chars, or containing characters outside A-Za-z0-9_- (e.g. '+', '=' from standard base64).","commonSituations":"Token generation changed to standard base64 (with + and =) or a different length; querystring parsers/gateways appending extra params like utm_ or sig; tokens stripped or re-encoded by proxies.","solutions":["Ensure the gateway issues the token as exactly 32 base64url characters (no padding) in the sole token query param","Strip any extra query parameters before validating / stop appending them upstream","If the token scheme legitimately changed, align this package's regex or pin compatible versions"],"exampleFix":"// before\nvalidateCodexMediaArtifactUrl(\"http://gw/media/artifact/<uuid>?token=abc&src=cli\", endpoint);\n// after\nvalidateCodexMediaArtifactUrl(\"http://gw/media/artifact/<uuid>?token=AbCdEfGhIjKlMnOpQrStUvWxYz012345\", endpoint);","handlingStrategy":"validation","validationCode":"const u = new URL(artifactUrl);\nconst keys = [...u.searchParams.keys()];\nconst token = u.searchParams.get('token') || '';\nif (keys.length !== 1 || keys[0] !== 'token' || !/^[A-Za-z0-9_-]{32}$/.test(token)) {\n  throw new Error('rejecting artifact with bad token');\n}","typeGuard":"function hasValidArtifactToken(value: string): boolean {\n  try { const u = new URL(value); const k = [...u.searchParams.keys()]; const t = u.searchParams.get('token') || ''; return k.length === 1 && k[0] === 'token' && /^[A-Za-z0-9_-]{32}$/.test(t); } catch { return false; }\n}","tryCatchPattern":null,"preventionTips":["Ensure tokens are 32-char base64url with no padding","Don't append extra query params to artifact links","Validate token shape before calling the artifact loader"],"tags":["url-validation","token","security"],"backgroundTag":"invalid-access-token","analyzedSha":"99f24806c6a2c660b16e53e95211c517448a6c90","analyzedAt":"2026-08-27T04:11:01.184Z","schemaVersion":2},"datasetVersion":"2026-08-27T08:17:20.692Z"}