mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-10 03:30:44 +00:00
fix(logging): redact http client secrets
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user