From 5c17d2d025f295ddef34b7f6df07d7ca9c2acd0a Mon Sep 17 00:00:00 2001 From: Moviw <1973559479@qq.com> Date: Sat, 20 Jun 2026 16:45:30 +0900 Subject: [PATCH] fix: redact credentials from agent status messages --- packages/agent-connector/src/adapters/base.js | 9 ++- .../agent-connector/src/adapters/utils.js | 12 ++++ .../test/adapter-utils.test.js | 70 +++++++++++++++++++ 3 files changed, 89 insertions(+), 2 deletions(-) create mode 100644 packages/agent-connector/test/adapter-utils.test.js diff --git a/packages/agent-connector/src/adapters/base.js b/packages/agent-connector/src/adapters/base.js index 18005fda8..9a379d63d 100644 --- a/packages/agent-connector/src/adapters/base.js +++ b/packages/agent-connector/src/adapters/base.js @@ -18,7 +18,11 @@ 'use strict'; const { WorkspaceClient, SessionRevokedError } = require('../workspace-client'); -const { generateSessionTitle, SESSION_DEFAULT_RE } = require('./utils'); +const { + generateSessionTitle, + redactSensitiveHeaders, + SESSION_DEFAULT_RE, +} = require('./utils'); const { defaultAgentWorkdir } = require('../paths'); const DEFAULT_ENDPOINT = 'https://workspace-endpoint.openagents.org'; @@ -697,7 +701,8 @@ class BaseAdapter { async sendStatus(channel, content, extraMeta) { try { - await this.client.sendMessage(this.workspaceId, channel, this.token, content, { + const safeContent = redactSensitiveHeaders(content); + await this.client.sendMessage(this.workspaceId, channel, this.token, safeContent, { senderType: 'agent', senderName: this.agentName, messageType: 'status', diff --git a/packages/agent-connector/src/adapters/utils.js b/packages/agent-connector/src/adapters/utils.js index b467faad5..5c2b78d19 100644 --- a/packages/agent-connector/src/adapters/utils.js +++ b/packages/agent-connector/src/adapters/utils.js @@ -100,8 +100,20 @@ function formatAttachmentsForPrompt(attachments, toolMode = 'mcp', isWindows = p return lines.join('\n'); } +/** + * Redact credentials from HTTP headers embedded in status text. + */ +function redactSensitiveHeaders(text) { + if (typeof text !== 'string' || !text) return text; + return text.replace( + /(\b(?:authorization|(?:[a-z0-9]+-)*(?:token|key))\s*:\s*)(?:(?:bearer|basic|token)\s+)?[^\s"'`]+/gi, + '$1[REDACTED]' + ); +} + module.exports = { SESSION_DEFAULT_RE, generateSessionTitle, formatAttachmentsForPrompt, + redactSensitiveHeaders, }; diff --git a/packages/agent-connector/test/adapter-utils.test.js b/packages/agent-connector/test/adapter-utils.test.js new file mode 100644 index 000000000..d1d70093c --- /dev/null +++ b/packages/agent-connector/test/adapter-utils.test.js @@ -0,0 +1,70 @@ +'use strict'; + +const { describe, it } = require('node:test'); +const assert = require('node:assert/strict'); + +const BaseAdapter = require('../src/adapters/base'); +const { redactSensitiveHeaders } = require('../src/adapters/utils'); + +describe('redactSensitiveHeaders', () => { + it('redacts workspace tokens in quoted curl headers', () => { + const command = 'curl -H "X-Workspace-Token: workspace-secret" https://example.test'; + assert.equal( + redactSensitiveHeaders(command), + 'curl -H "X-Workspace-Token: [REDACTED]" https://example.test' + ); + }); + + it('redacts authorization schemes and credentials', () => { + assert.equal( + redactSensitiveHeaders("curl -H 'Authorization: Bearer bearer-secret'"), + "curl -H 'Authorization: [REDACTED]'" + ); + assert.equal( + redactSensitiveHeaders('Authorization: Basic dXNlcjpwYXNz'), + 'Authorization: [REDACTED]' + ); + }); + + it('redacts generic token and key headers case-insensitively', () => { + assert.equal( + redactSensitiveHeaders('X-Auth-Token: abc X-API-Key: def'), + 'X-Auth-Token: [REDACTED] X-API-Key: [REDACTED]' + ); + }); + + it('leaves non-sensitive headers unchanged', () => { + const command = 'curl -H "Content-Type: application/json" -H "Accept: */*"'; + assert.equal(redactSensitiveHeaders(command), command); + }); + + it('passes through empty and non-string values', () => { + assert.equal(redactSensitiveHeaders(''), ''); + assert.equal(redactSensitiveHeaders(null), null); + }); + + it('redacts status content before sending it to the workspace', async () => { + const adapter = new BaseAdapter({ + workspaceId: 'ws', + channelName: 'main', + token: 'workspace-token', + agentName: 'codex', + }); + let sentContent; + adapter.client = { + sendMessage: async (_workspaceId, _channel, _token, content) => { + sentContent = content; + }, + }; + + await adapter.sendStatus( + 'main', + '**Running:** `curl -H "X-Workspace-Token: workspace-secret"`' + ); + + assert.equal( + sentContent, + '**Running:** `curl -H "X-Workspace-Token: [REDACTED]"`' + ); + }); +});