{"record":{"id":"bf6210068a358dfe","repo":"can1357/oh-my-pi","slug":"provider-delete-url-must-not-embed-an-account-cred","errorCode":null,"errorMessage":"Provider delete URL must not embed an account credential","messagePattern":"Provider delete URL must not embed an account credential","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/blob-broker/provider-file-types.ts","lineNumber":183,"sourceCode":"\t}\n\treturn normalized;\n}\n\nfunction errorMessage(error: unknown): string {\n\treturn error instanceof Error ? error.message : String(error);\n}\n\nfunction containsCredential(value: string, credential: string): boolean {\n\treturn credential.length > 0 && value.includes(credential);\n}\n\nfunction sanitizeDeleteAction(action: RemoteDeleteAction, credential: string): RemoteDeleteAction {\n\tlet url: URL;\n\ttry {\n\t\turl = new URL(action.url);\n\t} catch {\n\t\tif (containsCredential(action.url, credential)) {\n\t\t\tthrow new Error(\"Provider delete URL must not embed an account credential\");\n\t\t}\n\t\turl = new URL(action.url, \"https://provider-file.invalid\");\n\t}\n\tif (containsCredential(url.origin + url.pathname + url.hash, credential)) {\n\t\tthrow new Error(\"Provider delete URL must not embed an account credential\");\n\t}\n\tfor (const name of [...url.searchParams.keys()]) {\n\t\tconst values = url.searchParams.getAll(name);\n\t\tif (\n\t\t\tSENSITIVE_QUERY_PARAMETERS[name.toLowerCase()] ||\n\t\t\tvalues.some(value => containsCredential(value, credential))\n\t\t) {\n\t\t\turl.searchParams.delete(name);\n\t\t}\n\t}\n\tlet headers: Record<string, string> | undefined;\n\tif (action.headers) {\n\t\tfor (const name in action.headers) {","sourceCodeStart":165,"sourceCodeEnd":201,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/blob-broker/provider-file-types.ts#L165-L201","documentation":"sanitizeDeleteAction scrubss a provider's remote delete action so the persisted handle never embeds the account credential. If the delete URL (origin, path, or hash — and even an unparseable raw string) contains the literal credential substring, this error is thrown instead of storing it. Query parameters that embed the credential are silently stripped; only origin/path/hash fragments are hard-rejected.","triggerScenarios":"A provider adapter builds RemoteDeleteAction.url with the API key/token in the URL — e.g. \"https://api.example.com/files/f1?key=<token>\" where the token also appears in the path, or a relative URL whose raw text contains the credential while also failing URL parsing.","commonSituations":"A custom/less common provider puts auth in the URL path or fragment; an adapter interpolates the token into the pathname by mistake; the credential accidentally equals a generic substring appearing in the URL (over-broad match).","solutions":["Change the provider adapter to pass credentials via headers or query parameters, never in origin/path/hash.","Use a token-redacting URL builder so the credential never appears in the URL string.","If the credential is a broad substring colliding innocently, ensure the credential value used for hashing/matching is the actual secret, not a derived or shared string.","Rotate the credential if it was ever persisted."],"exampleFix":"// before\n{ url: `https://api.p.com/v1/files/${id}/token=${credential}` }\n// after\n{ url: `https://api.p.com/v1/files/${id}`, headers: { Authorization: `Bearer ${credential}` } }","handlingStrategy":"validation","validationCode":"function urlEmbedsCredential(rawUrl: string, credential: string): boolean {\n  if (!credential) return false;\n  try {\n    const u = new URL(rawUrl);\n    return (u.origin + u.pathname + u.hash).includes(credential);\n  } catch {\n    return rawUrl.includes(credential);\n  }\n}\n// call before constructing the delete action; move the credential to headers if true","typeGuard":null,"tryCatchPattern":"try {\n  const handle = persistProviderFileHandle(rawHandle, credential);\n} catch (err) {\n  if (err instanceof Error && err.message.includes(\"must not embed an account credential\")) {\n    // rebuild delete action with Authorization header instead of URL credential\n    return persistProviderFileHandle({ ...rawHandle, delete: withHeaderAuth(rawHandle.delete, credential) }, credential);\n  }\n  throw err;\n}","preventionTips":["Never put API tokens in the URL origin, path, or fragment — use headers","Pass credentials via query parameters only if unavoidable (they get stripped, not rejected)","Review custom provider adapters for URL interpolation of secrets","Rotate any credential that may have been persisted in a handle"],"tags":["security","credential-leak","validation","url"],"backgroundTag":"credential-in-url","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}