fix(logging): redact http client secrets

This commit is contained in:
Andi Liao
2026-04-30 18:44:04 +08:00
parent d2d4728340
commit fcd308be71
4 changed files with 58 additions and 2 deletions

View File

@@ -7,6 +7,7 @@ Docs: https://docs.openclaw.ai
### Changes
- Docker: run the runtime image under `tini` so long-lived containers reap orphaned child processes and forward signals correctly. (#77885) Thanks @VintageAyu.
- 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.
- 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.
- 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.
- 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.

View File

@@ -95,6 +95,25 @@ describe("error helpers", () => {
expect(formatted).not.toContain(token);
});
it("redacts HTTP client config secrets from formatted error chains", () => {
const appSecret = "feishu_app_secret_1234567890";
const tenantToken = "feishu_tenant_access_abcdef123456";
const rootCause = new Error(
`request config: { appSecret: '${appSecret}', headers: { authorization: 'Bearer ${tenantToken}' } }`,
);
const httpError = Object.assign(new Error(`POST /auth/v3/tenant_access_token failed`), {
cause: rootCause,
});
const formatted = formatErrorMessage(httpError);
expect(formatted).toContain("POST /auth/v3/tenant_access_token failed");
expect(formatted).toContain("appSecret:");
expect(formatted).toContain("authorization:");
expect(formatted).not.toContain(appSecret);
expect(formatted).not.toContain(tenantToken);
});
it.each([
{
value: new Error("Unhandled stop reason: refusal_policy"),

View File

@@ -110,6 +110,24 @@ describe("redactSensitiveText", () => {
);
});
it("masks HTTP client config secrets in JSON and object-inspection fields", () => {
const appSecret = "feishu_app_secret_1234567890";
const clientSecret = "oauth_client_secret_1234567890";
const input = [
`body: {"app_secret":"${appSecret}"}`,
`config: { appSecret: '${appSecret}', client_secret: '${clientSecret}' }`,
].join("\n");
const output = redactSensitiveText(input, {
mode: "tools",
patterns: defaults,
});
expect(output).toContain('"app_secret":"feishu…7890"');
expect(output).toContain("appSecret: 'feishu…7890'");
expect(output).toContain("client_secret: 'oauth_…7890'");
expect(output).not.toContain(appSecret);
expect(output).not.toContain(clientSecret);
});
it("masks payment credential assignments and flags", () => {
const input = [
"LINK_CARD_NUMBER=4242424242424242",
@@ -133,6 +151,20 @@ describe("redactSensitiveText", () => {
expect(output).toContain("--card-number ***");
});
it("masks quoted HTTP auth headers in object-inspection fields", () => {
const bearer = "feishu_tenant_access_abcdef123456";
const cookie = "session_cookie_value_abcdef123456";
const input = `headers: { authorization: 'Bearer ${bearer}', cookie: '${cookie}' }`;
const output = redactSensitiveText(input, {
mode: "tools",
patterns: defaults,
});
expect(output).toContain("authorization: 'Bearer…3456'");
expect(output).toContain("cookie: 'sessio…3456'");
expect(output).not.toContain(bearer);
expect(output).not.toContain(cookie);
});
it("masks payment credential URL query parameters", () => {
const input =
"POST /authorize?shared_payment_token=spt_abcdefghijklmnopqrstuvwxyz&card_number=4242424242424242&amount=4200";

View File

@@ -14,7 +14,7 @@ const PAYMENT_CREDENTIAL_ENV_KEYS = String.raw`CARD[_-]?NUMBER|CARD[_-]?CVC|CARD
const PAYMENT_CREDENTIAL_QUERY_KEYS = String.raw`card[-_]?number|card[-_]?cvc|card[-_]?cvv|cvc|cvv|security[-_]?code|payment[-_]?credential|shared[-_]?payment[-_]?token`;
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`;
const STRUCTURED_SECRET_FIELD_RE = new RegExp(
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})$`,
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})$`,
"i",
);
@@ -27,6 +27,10 @@ const DEFAULT_REDACT_PATTERNS: string[] = [
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`,
// JSON fields.
String.raw`"(?:apiKey|token|secret|password|passwd|accessToken|refreshToken|${PAYMENT_CREDENTIAL_JSON_KEYS})"\s*:\s*"([^"]+)"`,
// HTTP client diagnostics often stringify request config objects using
// JSON or util.inspect-style fields rather than env/CLI syntax.
String.raw`(^|[\s,{])["']?(?:api[-_]key|access[-_]token|refresh[-_]token|authToken|auth[-_]token|clientSecret|client[-_]secret|appSecret|app[-_]secret)["']?\s*[:=]\s*(["'])([^"'\r\n]+)\2`,
String.raw`(^|[\s,{])["']?(?:authorization|proxy-authorization|cookie|set-cookie|x-api-key|x-auth-token)["']?\s*[:=]\s*(["'])([^"'\r\n]+)\2`,
// CLI flags.
String.raw`--(?:api[-_]?key|hook[-_]?token|token|secret|password|passwd|${PAYMENT_CREDENTIAL_QUERY_KEYS})\s+(["']?)([^\s"']+)\1`,
// Authorization headers.
@@ -34,7 +38,7 @@ const DEFAULT_REDACT_PATTERNS: string[] = [
String.raw`\bBearer\s+([A-Za-z0-9._\-+=]{18,})\b`,
// Standalone token assignments in CLI or HTTP diagnostics. URL query params
// are handled above so non-secret params survive and long values stay hinted.
String.raw`(^|[\s,;])(?:access_token|refresh_token|api[-_]?key|token|secret|password|passwd|${PAYMENT_CREDENTIAL_QUERY_KEYS})=([^\s&#]+)`,
String.raw`(^|[\s,;])(?:access_token|refresh_token|auth[-_]?token|api[-_]?key|client[-_]?secret|app[-_]?secret|token|secret|password|passwd|${PAYMENT_CREDENTIAL_QUERY_KEYS})=([^\s&#]+)`,
// PEM blocks.
String.raw`-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]+?-----END [A-Z ]*PRIVATE KEY-----`,
// Common token prefixes.