Files
openclaw/src/utils/normalize-secret-input.ts
lin-hongkuan bc7f0f1223 fix(secrets): strip control characters from secret input (#96444)
* fix(secrets): strip control characters from secret input

* chore: retrigger PR checks

* fix(web-content): strip controls from provider secrets

---------

Co-authored-by: lin-hongkuan <lin-hongkuan@users.noreply.github.com>
2026-06-29 10:51:08 -07:00

48 lines
1.8 KiB
TypeScript

/**
* Secret normalization for copy/pasted credentials.
*
* Common footgun: line breaks (especially `\r`) embedded in API keys/tokens.
* We strip line breaks anywhere, then trim whitespace at the ends.
*
* Another frequent source of runtime failures is rich-text/Unicode artifacts
* (smart punctuation, box-drawing chars, etc.) pasted into API keys. These can
* break HTTP header construction (`ByteString` violations). Drop non-Latin1
* code points so malformed keys fail as auth errors instead of crashing request
* setup.
*
* Intentionally does NOT remove ordinary spaces inside the string to avoid
* silently altering "Bearer <token>" style values.
*/
/**
* Normalizes a raw secret value from config, env, setup prompts, or plugin SDK callers.
* Returns an empty string for absent/invalid input so callers can keep boolean presence checks simple.
*/
export function normalizeSecretInput(value: unknown): string {
if (typeof value !== "string") {
return "";
}
const collapsed = value.replace(/[\r\n\u2028\u2029]+/g, "");
const chars: string[] = [];
for (const char of collapsed) {
const codePoint = char.codePointAt(0);
const isControl =
typeof codePoint === "number" &&
((codePoint >= 0x00 && codePoint <= 0x1f) ||
codePoint === 0x7f ||
(codePoint >= 0x80 && codePoint <= 0x9f));
if (typeof codePoint === "number" && codePoint <= 0xff && !isControl) {
chars.push(char);
}
}
return chars.join("").trim();
}
/**
* Normalizes a raw secret value and converts empty normalized output to `undefined`.
* Use this at optional config boundaries where "not configured" is clearer than an empty string.
*/
export function normalizeOptionalSecretInput(value: unknown): string | undefined {
const normalized = normalizeSecretInput(value);
return normalized || undefined;
}