mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-28 05:16:23 -06:00
Redact bare Fireworks API keys (#98226)
* Redact bare Fireworks API keys * fix(logging): harden Fireworks key redaction * fix(logging): harden Fireworks key redaction --------- Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
@@ -735,6 +735,11 @@ describe("redactSensitiveText", () => {
|
||||
});
|
||||
|
||||
it("masks expanded vendor-prefix token corpus", () => {
|
||||
const fireworksTokens = [
|
||||
{ token: `fw-${"C".repeat(40)}`, redacted: "fw-CCC…CCCC" },
|
||||
{ token: `fw_${"A".repeat(40)}`, redacted: "fw_AAA…AAAA" },
|
||||
{ token: `fpk_${"B".repeat(40)}`, redacted: "fpk_BB…BBBB" },
|
||||
];
|
||||
const tokens = [
|
||||
"sk-ant-abcdefghijklmnopqrstuvwxyz",
|
||||
"gho_abcdefghijklmnopqrstuvwxyz",
|
||||
@@ -797,6 +802,7 @@ describe("redactSensitiveText", () => {
|
||||
"mem0_abcdefghijklmnopqrstuvwxyz",
|
||||
"brv_abcdefghijklmnopqrstuvwxyz",
|
||||
"xai-abcdefghijklmnopqrstuvwxyzABCDE",
|
||||
...fireworksTokens.map(({ token }) => token),
|
||||
];
|
||||
// Redact each fixture alone so every vendor pattern proves it stays reachable through
|
||||
// DEFAULT_REDACT_PREFILTER_RE; a joined corpus would let one trigger unlock all others.
|
||||
@@ -817,15 +823,36 @@ describe("redactSensitiveText", () => {
|
||||
expect(redactSensitiveText("xai-abcdefghijklmnopqrstuvwxyzABCDE", { mode: "tools" })).toBe(
|
||||
"xai-ab…BCDE",
|
||||
);
|
||||
for (const { token, redacted } of fireworksTokens) {
|
||||
expect(redactSensitiveText(token, { mode: "tools" })).toBe(redacted);
|
||||
expect(redactSensitiveText(redacted, { mode: "tools" })).toBe(redacted);
|
||||
}
|
||||
});
|
||||
|
||||
it("does not redact ordinary identifiers containing short token-prefix substrings", () => {
|
||||
const input =
|
||||
"npm_telegram_package_spec ask_openclaw_query_patterns team_management risk_assessment glpat-docs dapi-example sbp_short nfp_site CCIPAT_docs ATATT-example";
|
||||
const input = [
|
||||
"npm_telegram_package_spec ask_openclaw_query_patterns team_management risk_assessment glpat-docs dapi-example sbp_short nfp_site CCIPAT_docs ATATT-example fw-tooshort fw_tooshort fpk_tooshort",
|
||||
`fixturefw-${"C".repeat(40)}`,
|
||||
`fixture_fw_${"A".repeat(40)}`,
|
||||
`fixture_fpk_${"B".repeat(40)}`,
|
||||
].join(" ");
|
||||
const output = redactSensitiveText(input, { mode: "tools" });
|
||||
expect(output).toBe(input);
|
||||
});
|
||||
|
||||
it("masks Fireworks tokens that cross bounded-replacement chunk boundaries", () => {
|
||||
const chunkSize = 16_384;
|
||||
const prefix = `${"x".repeat(chunkSize - 2)} `;
|
||||
const suffix = "y".repeat(chunkSize);
|
||||
const tokens = [`fw-${"C".repeat(40)}`, `fw_${"A".repeat(40)}`, `fpk_${"B".repeat(40)}`];
|
||||
|
||||
for (const token of tokens) {
|
||||
expect(redactSensitiveText(`${prefix}${token}${suffix}`, { mode: "tools" })).not.toContain(
|
||||
token,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it("does not corrupt base64 blobs that embed token-prefix shapes", () => {
|
||||
// Tiny-PNG base64 contains a gAAAA run from zero-filled IHDR bytes; pure-base64-alphabet
|
||||
// prefixes must not fire mid-blob or media payloads get mangled.
|
||||
|
||||
+12
-5
@@ -122,6 +122,7 @@ const STANDALONE_ASSIGNMENT_REDACT_PATTERN = String.raw`(^|[\s,;])(?:${STANDALON
|
||||
// delimiters like `/` and `=` still qualify) but skip explicit `;base64,` payload spans, so
|
||||
// data-URL media is never corrupted while tokens in URL paths or assignments still redact.
|
||||
const BASE64_SAFE_TOKEN_BOUNDARY = String.raw`(^|[^A-Za-z0-9])(?<!;base64,[A-Za-z0-9+/=]*)`;
|
||||
const IDENTIFIER_SAFE_TOKEN_BOUNDARY = String.raw`(^|[^A-Za-z0-9_])`;
|
||||
const SHELL_REFERENCE_PRESERVING_PATTERN_SOURCES = new Set([
|
||||
ENV_ASSIGNMENT_REDACT_PATTERN,
|
||||
ESCAPED_ENV_ASSIGNMENT_REDACT_PATTERN,
|
||||
@@ -129,9 +130,8 @@ const SHELL_REFERENCE_PRESERVING_PATTERN_SOURCES = new Set([
|
||||
STANDALONE_ASSIGNMENT_REDACT_PATTERN,
|
||||
]);
|
||||
const shellReferencePreservingPatterns = new WeakSet<RegExp>();
|
||||
// Patterns whose left-context assertions (BASE64_SAFE_TOKEN_BOUNDARY) break under chunked
|
||||
// replacement: a chunk start satisfies `^` and hides the `;base64,` container from the
|
||||
// lookbehind, so these must always run against the full string.
|
||||
// Patterns whose left-context assertions or complete token can cross a chunk boundary must run
|
||||
// against the full string; chunking can invent a `^` boundary or split the secret itself.
|
||||
const chunkUnsafePatterns = new WeakSet<RegExp>();
|
||||
|
||||
const DEFAULT_REDACT_PATTERNS: string[] = [
|
||||
@@ -235,6 +235,9 @@ const DEFAULT_REDACT_PATTERNS: string[] = [
|
||||
String.raw`(mem0_[A-Za-z0-9]{10,})`,
|
||||
String.raw`(brv_[A-Za-z0-9]{10,})`,
|
||||
String.raw`(xai-[A-Za-z0-9]{30,})`,
|
||||
String.raw`${IDENTIFIER_SAFE_TOKEN_BOUNDARY}(fw-[A-Za-z0-9]{30,})`,
|
||||
String.raw`${IDENTIFIER_SAFE_TOKEN_BOUNDARY}(fw_[A-Za-z0-9]{30,})`,
|
||||
String.raw`${IDENTIFIER_SAFE_TOKEN_BOUNDARY}(fpk_[A-Za-z0-9]{30,})`,
|
||||
// Additional access-key and token-style prefixes.
|
||||
String.raw`${BASE64_SAFE_TOKEN_BOUNDARY}(AKIA[A-Z0-9]{16})`,
|
||||
String.raw`${BASE64_SAFE_TOKEN_BOUNDARY}(ASIA[A-Z0-9]{16})`,
|
||||
@@ -261,7 +264,7 @@ const DEFAULT_REDACT_PREFILTER_SOURCES: string[] = [
|
||||
// URL userinfo and connection-string password slots (`scheme://user:pass@host`).
|
||||
String.raw`:\/\/[^\/\s:@]*:[^\/\s@]+@`,
|
||||
// Vendor token prefixes and webhook hosts, ordered like DEFAULT_REDACT_PATTERNS.
|
||||
String.raw`sk-|gh[opsur]_|github_pat_|glpat-|gloas-|xox[baprs]-|xapp-|hooks\.slack\.com|discord|gsk_|AIza|ya29\.|1\/\/0|eyJ|pplx-|fal_|fc-|bb_live_|gAAAA|[sr]k_(?:live|test)_|\bSG\.|npm_|pypi-|do[opr]_v1_|dp\.(?:ct|pt|sa|st|scim|audit)\.|dckr_|bkua_|CCIPAT_|sbp_|dapi[0-9a-f]|dd[pw]_|glsa_|nfp_|CFPAT-|ATCTT3|ATATT|ATBB|BBDC-|HRKU-|pat-(?:eu|na)1-|apify_api_|FlyV1|fio-u-|tvly-|exa_|syt_|retaindb_|mem0_|brv_|xai-`,
|
||||
String.raw`sk-|gh[opsur]_|github_pat_|glpat-|gloas-|xox[baprs]-|xapp-|hooks\.slack\.com|discord|gsk_|AIza|ya29\.|1\/\/0|eyJ|pplx-|fal_|fc-|bb_live_|gAAAA|[sr]k_(?:live|test)_|\bSG\.|npm_|pypi-|do[opr]_v1_|dp\.(?:ct|pt|sa|st|scim|audit)\.|dckr_|bkua_|CCIPAT_|sbp_|dapi[0-9a-f]|dd[pw]_|glsa_|nfp_|CFPAT-|ATCTT3|ATATT|ATBB|BBDC-|HRKU-|pat-(?:eu|na)1-|apify_api_|FlyV1|fio-u-|tvly-|exa_|syt_|retaindb_|mem0_|brv_|xai-|fw-|fw_|fpk_`,
|
||||
String.raw`(?:^|[^A-Za-z0-9_])(?:am_|sk_)`,
|
||||
String.raw`A[KS]IA[A-Z0-9]|AKID|LTAI|hf_|api_org_|r8_`,
|
||||
String.raw`\bbot\d{6,}:|\b\d{6,}:[A-Za-z0-9_-]{20,}`,
|
||||
@@ -313,7 +316,11 @@ function parsePattern(raw: RedactPattern): RegExp | null {
|
||||
if (pattern && typeof raw === "string" && SHELL_REFERENCE_PRESERVING_PATTERN_SOURCES.has(raw)) {
|
||||
shellReferencePreservingPatterns.add(pattern);
|
||||
}
|
||||
if (pattern && typeof raw === "string" && raw.startsWith(BASE64_SAFE_TOKEN_BOUNDARY)) {
|
||||
if (
|
||||
pattern &&
|
||||
typeof raw === "string" &&
|
||||
(raw.startsWith(BASE64_SAFE_TOKEN_BOUNDARY) || raw.startsWith(IDENTIFIER_SAFE_TOKEN_BOUNDARY))
|
||||
) {
|
||||
chunkUnsafePatterns.add(pattern);
|
||||
}
|
||||
return pattern;
|
||||
|
||||
@@ -10,6 +10,9 @@ describe("browser tool detail redaction", () => {
|
||||
"curl 'https://example.test?refresh_token=ya29.longOAuthRefreshTokenValue&ok=1'",
|
||||
"client_secret=clientSecretValueThatShouldNotRender",
|
||||
"AIzaSyDUMMYGoogleApiKeyValue1234567890",
|
||||
`bare Fireworks key fw-${"C".repeat(40)}`,
|
||||
`https://example.test?debug=fw_${"A".repeat(40)}&ok=1`,
|
||||
`X-Debug: fpk_${"B".repeat(40)}`,
|
||||
"-----BEGIN PRIVATE KEY-----\nabc123\n-----END PRIVATE KEY-----",
|
||||
'cookie: "sessionid=verySensitiveCookieValue"',
|
||||
].join("\n"),
|
||||
@@ -27,8 +30,24 @@ describe("browser tool detail redaction", () => {
|
||||
expect(redacted).not.toContain("longOAuthRefreshTokenValue");
|
||||
expect(redacted).not.toContain("clientSecretValueThatShouldNotRender");
|
||||
expect(redacted).not.toContain("DUMMYGoogleApiKeyValue1234567890");
|
||||
expect(redacted).toContain("bare Fireworks key fw-CCC...CCCC");
|
||||
expect(redacted).toContain("https://example.test?debug=fw_AAA...AAAA&ok=1");
|
||||
expect(redacted).toContain("X-Debug: fpk_BB...BBBB");
|
||||
expect(redacted).not.toContain("abc123");
|
||||
expect(redacted).not.toContain("verySensitiveCookieValue");
|
||||
for (const masked of ["fw-CCC...CCCC", "fw_AAA...AAAA", "fpk_BB...BBBB"]) {
|
||||
expect(redactToolDetail(masked)).toBe(masked);
|
||||
}
|
||||
});
|
||||
|
||||
it("preserves long non-token identifiers containing Fireworks prefixes", () => {
|
||||
const input = [
|
||||
`fixturefw-${"C".repeat(40)}`,
|
||||
`fixture_fw_${"A".repeat(40)}`,
|
||||
`fixture_fpk_${"B".repeat(40)}`,
|
||||
].join(" ");
|
||||
|
||||
expect(redactToolDetail(input)).toBe(input);
|
||||
});
|
||||
|
||||
it("exposes the tool payload redaction name used by shared display modules", () => {
|
||||
|
||||
@@ -36,6 +36,9 @@ const SECRET_DETAIL_PATTERNS: RegExp[] = [
|
||||
/\b(1\/\/0[0-9A-Za-z_\-./+=]{10,})\b/g,
|
||||
/\b(eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,})\b/g,
|
||||
/\b(pplx-[A-Za-z0-9_-]{10,})\b/g,
|
||||
/(^|[^A-Za-z0-9_])(fw-[A-Za-z0-9]{30,})/g,
|
||||
/(^|[^A-Za-z0-9_])(fw_[A-Za-z0-9]{30,})/g,
|
||||
/(^|[^A-Za-z0-9_])(fpk_[A-Za-z0-9]{30,})/g,
|
||||
/\b(npm_[A-Za-z0-9]{10,})\b/g,
|
||||
/\b(AKID[A-Za-z0-9]{10,})\b/g,
|
||||
/\b(LTAI[A-Za-z0-9]{10,})\b/g,
|
||||
|
||||
Reference in New Issue
Block a user