can1357/oh-my-pi · error

Provider delete URL must not embed an account credential

Error message

Provider delete URL must not embed an account credential

What it means

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.

Source

Thrown at packages/coding-agent/src/blob-broker/provider-file-types.ts:183

	}
	return normalized;
}

function errorMessage(error: unknown): string {
	return error instanceof Error ? error.message : String(error);
}

function containsCredential(value: string, credential: string): boolean {
	return credential.length > 0 && value.includes(credential);
}

function sanitizeDeleteAction(action: RemoteDeleteAction, credential: string): RemoteDeleteAction {
	let url: URL;
	try {
		url = new URL(action.url);
	} catch {
		if (containsCredential(action.url, credential)) {
			throw new Error("Provider delete URL must not embed an account credential");
		}
		url = new URL(action.url, "https://provider-file.invalid");
	}
	if (containsCredential(url.origin + url.pathname + url.hash, credential)) {
		throw new Error("Provider delete URL must not embed an account credential");
	}
	for (const name of [...url.searchParams.keys()]) {
		const values = url.searchParams.getAll(name);
		if (
			SENSITIVE_QUERY_PARAMETERS[name.toLowerCase()] ||
			values.some(value => containsCredential(value, credential))
		) {
			url.searchParams.delete(name);
		}
	}
	let headers: Record<string, string> | undefined;
	if (action.headers) {
		for (const name in action.headers) {

View on GitHub (pinned to 9690622007)

Solutions

  1. Change the provider adapter to pass credentials via headers or query parameters, never in origin/path/hash.
  2. Use a token-redacting URL builder so the credential never appears in the URL string.
  3. 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.
  4. Rotate the credential if it was ever persisted.

Example fix

// before
{ url: `https://api.p.com/v1/files/${id}/token=${credential}` }
// after
{ url: `https://api.p.com/v1/files/${id}`, headers: { Authorization: `Bearer ${credential}` } }
Defensive patterns

Strategy: validation

Validate before calling

function urlEmbedsCredential(rawUrl: string, credential: string): boolean {
  if (!credential) return false;
  try {
    const u = new URL(rawUrl);
    return (u.origin + u.pathname + u.hash).includes(credential);
  } catch {
    return rawUrl.includes(credential);
  }
}
// call before constructing the delete action; move the credential to headers if true

Try / catch

try {
  const handle = persistProviderFileHandle(rawHandle, credential);
} catch (err) {
  if (err instanceof Error && err.message.includes("must not embed an account credential")) {
    // rebuild delete action with Authorization header instead of URL credential
    return persistProviderFileHandle({ ...rawHandle, delete: withHeaderAuth(rawHandle.delete, credential) }, credential);
  }
  throw err;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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).

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/bf6210068a358dfe. Report an issue: GitHub.