Skip to content

Commit fcd308b

Browse files
committed
fix(logging): redact http client secrets
1 parent d2d4728 commit fcd308b

4 files changed

Lines changed: 58 additions & 2 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ Docs: https://docs.openclaw.ai
77
### Changes
88

99
- Docker: run the runtime image under `tini` so long-lived containers reap orphaned child processes and forward signals correctly. (#77885) Thanks @VintageAyu.
10+
- Logging/redaction: redact quoted HTTP client secret fields and auth/cookie headers in shared log and formatted error output. Related #71211 and #65623. (#75033) Thanks @liaoandi.
1011
- Google/Gemini: normalize retired `google/gemini-3-pro-preview` and `google-gemini-cli/gemini-3-pro-preview` selections to `google/gemini-3.1-pro-preview` before they are written to model config.
1112
- Amazon Bedrock: support `serviceTier` parameter for Bedrock models, configurable via `agents.defaults.params.serviceTier` or per-model in `agents.defaults.models`. Valid values: `default`, `flex`, `priority`, `reserved`. (#64512) Thanks @mobilinkd.
1213
- Control UI: read the Quick Settings exec policy badge from `tools.exec.security` instead of the non-schema `agents.defaults.exec.security` path, so configured `full`/`deny` values render accurately. Fixes #78311. Thanks @FriedBack.

src/infra/errors.test.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,25 @@ describe("error helpers", () => {
9595
expect(formatted).not.toContain(token);
9696
});
9797

98+
it("redacts HTTP client config secrets from formatted error chains", () => {
99+
const appSecret = "feishu_app_secret_1234567890";
100+
const tenantToken = "feishu_tenant_access_abcdef123456";
101+
const rootCause = new Error(
102+
`request config: { appSecret: '${appSecret}', headers: { authorization: 'Bearer ${tenantToken}' } }`,
103+
);
104+
const httpError = Object.assign(new Error(`POST /auth/v3/tenant_access_token failed`), {
105+
cause: rootCause,
106+
});
107+
108+
const formatted = formatErrorMessage(httpError);
109+
110+
expect(formatted).toContain("POST /auth/v3/tenant_access_token failed");
111+
expect(formatted).toContain("appSecret:");
112+
expect(formatted).toContain("authorization:");
113+
expect(formatted).not.toContain(appSecret);
114+
expect(formatted).not.toContain(tenantToken);
115+
});
116+
98117
it.each([
99118
{
100119
value: new Error("Unhandled stop reason: refusal_policy"),

src/logging/redact.test.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,24 @@ describe("redactSensitiveText", () => {
110110
);
111111
});
112112

113+
it("masks HTTP client config secrets in JSON and object-inspection fields", () => {
114+
const appSecret = "feishu_app_secret_1234567890";
115+
const clientSecret = "oauth_client_secret_1234567890";
116+
const input = [
117+
`body: {"app_secret":"${appSecret}"}`,
118+
`config: { appSecret: '${appSecret}', client_secret: '${clientSecret}' }`,
119+
].join("\n");
120+
const output = redactSensitiveText(input, {
121+
mode: "tools",
122+
patterns: defaults,
123+
});
124+
expect(output).toContain('"app_secret":"feishu…7890"');
125+
expect(output).toContain("appSecret: 'feishu…7890'");
126+
expect(output).toContain("client_secret: 'oauth_…7890'");
127+
expect(output).not.toContain(appSecret);
128+
expect(output).not.toContain(clientSecret);
129+
});
130+
113131
it("masks payment credential assignments and flags", () => {
114132
const input = [
115133
"LINK_CARD_NUMBER=4242424242424242",
@@ -133,6 +151,20 @@ describe("redactSensitiveText", () => {
133151
expect(output).toContain("--card-number ***");
134152
});
135153

154+
it("masks quoted HTTP auth headers in object-inspection fields", () => {
155+
const bearer = "feishu_tenant_access_abcdef123456";
156+
const cookie = "session_cookie_value_abcdef123456";
157+
const input = `headers: { authorization: 'Bearer ${bearer}', cookie: '${cookie}' }`;
158+
const output = redactSensitiveText(input, {
159+
mode: "tools",
160+
patterns: defaults,
161+
});
162+
expect(output).toContain("authorization: 'Bearer…3456'");
163+
expect(output).toContain("cookie: 'sessio…3456'");
164+
expect(output).not.toContain(bearer);
165+
expect(output).not.toContain(cookie);
166+
});
167+
136168
it("masks payment credential URL query parameters", () => {
137169
const input =
138170
"POST /authorize?shared_payment_token=spt_abcdefghijklmnopqrstuvwxyz&card_number=4242424242424242&amount=4200";

src/logging/redact.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ const PAYMENT_CREDENTIAL_ENV_KEYS = String.raw`CARD[_-]?NUMBER|CARD[_-]?CVC|CARD
1414
const PAYMENT_CREDENTIAL_QUERY_KEYS = String.raw`card[-_]?number|card[-_]?cvc|card[-_]?cvv|cvc|cvv|security[-_]?code|payment[-_]?credential|shared[-_]?payment[-_]?token`;
1515
const PAYMENT_CREDENTIAL_JSON_KEYS = String.raw`cardNumber|card_number|cardCvc|card_cvc|cardCvv|card_cvv|cvc|cvv|securityCode|security_code|paymentCredential|payment_credential|sharedPaymentToken|shared_payment_token`;
1616
const STRUCTURED_SECRET_FIELD_RE = new RegExp(
17-
String.raw`^(?:api[-_]?key|apiKey|token|secret|password|passwd|access[-_]?token|accessToken|refresh[-_]?token|refreshToken|client[-_]?secret|clientSecret|${PAYMENT_CREDENTIAL_QUERY_KEYS}|${PAYMENT_CREDENTIAL_JSON_KEYS})$`,
17+
String.raw`^(?:api[-_]?key|apiKey|token|secret|password|passwd|access[-_]?token|accessToken|refresh[-_]?token|refreshToken|auth[-_]?token|authToken|client[-_]?secret|clientSecret|app[-_]?secret|appSecret|${PAYMENT_CREDENTIAL_QUERY_KEYS}|${PAYMENT_CREDENTIAL_JSON_KEYS})$`,
1818
"i",
1919
);
2020

@@ -27,14 +27,18 @@ const DEFAULT_REDACT_PATTERNS: string[] = [
2727
String.raw`/[?&](?:access[-_]?token|auth[-_]?token|hook[-_]?token|refresh[-_]?token|api[-_]?key|client[-_]?secret|token|key|secret|password|pass|passwd|auth|signature|${PAYMENT_CREDENTIAL_QUERY_KEYS})=([^&\s"'<>]+)/gi`,
2828
// JSON fields.
2929
String.raw`"(?:apiKey|token|secret|password|passwd|accessToken|refreshToken|${PAYMENT_CREDENTIAL_JSON_KEYS})"\s*:\s*"([^"]+)"`,
30+
// HTTP client diagnostics often stringify request config objects using
31+
// JSON or util.inspect-style fields rather than env/CLI syntax.
32+
String.raw`(^|[\s,{])["']?(?:api[-_]key|access[-_]token|refresh[-_]token|authToken|auth[-_]token|clientSecret|client[-_]secret|appSecret|app[-_]secret)["']?\s*[:=]\s*(["'])([^"'\r\n]+)\2`,
33+
String.raw`(^|[\s,{])["']?(?:authorization|proxy-authorization|cookie|set-cookie|x-api-key|x-auth-token)["']?\s*[:=]\s*(["'])([^"'\r\n]+)\2`,
3034
// CLI flags.
3135
String.raw`--(?:api[-_]?key|hook[-_]?token|token|secret|password|passwd|${PAYMENT_CREDENTIAL_QUERY_KEYS})\s+(["']?)([^\s"']+)\1`,
3236
// Authorization headers.
3337
String.raw`Authorization\s*[:=]\s*Bearer\s+([A-Za-z0-9._\-+=]+)`,
3438
String.raw`\bBearer\s+([A-Za-z0-9._\-+=]{18,})\b`,
3539
// Standalone token assignments in CLI or HTTP diagnostics. URL query params
3640
// are handled above so non-secret params survive and long values stay hinted.
37-
String.raw`(^|[\s,;])(?:access_token|refresh_token|api[-_]?key|token|secret|password|passwd|${PAYMENT_CREDENTIAL_QUERY_KEYS})=([^\s&#]+)`,
41+
String.raw`(^|[\s,;])(?:access_token|refresh_token|auth[-_]?token|api[-_]?key|client[-_]?secret|app[-_]?secret|token|secret|password|passwd|${PAYMENT_CREDENTIAL_QUERY_KEYS})=([^\s&#]+)`,
3842
// PEM blocks.
3943
String.raw`-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]+?-----END [A-Z ]*PRIVATE KEY-----`,
4044
// Common token prefixes.

0 commit comments

Comments
 (0)