Skip to content

Commit 4a2a6bf

Browse files
committed
fix(security): centralize full URL redaction
Signed-off-by: Apurv Kumaria <akumaria@nvidia.com>
1 parent 6172ecd commit 4a2a6bf

5 files changed

Lines changed: 40 additions & 21 deletions

File tree

src/lib/actions/sandbox/gateway-restart.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@
44
import { GATEWAY_RESTART_MARKERS as MARKERS } from "../../agent/gateway-restart-markers";
55
import * as agentRuntime from "../../agent/runtime";
66
import { G, R } from "../../cli/terminal-style";
7-
import { redactFull, redactUrl } from "../../security/redact";
8-
import { URL_TOKEN_PATTERN } from "../../security/redact-url";
7+
import { redactFullWithUrls } from "../../security/redact";
98
import { hermesMcpReconciliationRemediationLines } from "./mcp-bridge-hermes-reconciliation";
109
import { inspectHermesMcpReconciliationRefusal } from "./mcp-bridge-recovery";
1110
import { assertHermesPortableCommandUnavailable } from "../../onboard/experimental/portable-agent-lifecycle";
@@ -173,11 +172,7 @@ const ANSI_CONTROL_RE =
173172

174173
function sanitizeGatewayRestartFailureLine(line: string): string {
175174
const withoutControls = line.replace(ANSI_CONTROL_RE, "");
176-
const withRedactedUrls = withoutControls.replace(
177-
URL_TOKEN_PATTERN,
178-
(url) => redactUrl(url) ?? "<REDACTED>",
179-
);
180-
return redactFull(withRedactedUrls);
175+
return redactFullWithUrls(withoutControls);
181176
}
182177

183178
function sanitizeGatewayRestartFailureDetail(detail: string): string {

src/lib/diagnostics/debug.ts

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -56,19 +56,14 @@ function section(title: string): void {
5656
// Secret redaction — delegates to unified redact module (#2381).
5757
// ---------------------------------------------------------------------------
5858

59-
import { redactFull, redactUrl } from "../security/redact";
60-
import { URL_TOKEN_PATTERN } from "../security/redact-url";
59+
import { redactFullWithUrls } from "../security/redact";
6160

6261
/**
6362
* Redact collected diagnostics before they are written to the bundle or
64-
* echoed to the terminal. `redactFull` covers known secret shapes but leaves
65-
* URL credentials such as `http://user:password@proxy:3128` intact, so URL
66-
* tokens are redacted first. This is the order the other full-replacement
67-
* sinks already use — see `actions/sandbox/gateway-restart.ts` and
68-
* `policy/preset-scope-render.ts`.
63+
* echoed to the terminal.
6964
*/
7065
export function redact(text: string): string {
71-
return redactFull(text.replace(URL_TOKEN_PATTERN, (url) => redactUrl(url) ?? "<REDACTED>"));
66+
return redactFullWithUrls(text);
7267
}
7368

7469
// ---------------------------------------------------------------------------

src/lib/policy/preset-scope-render.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@
22
// SPDX-License-Identifier: Apache-2.0
33

44
import { isObjectRecord } from "../core/json-types";
5-
import { redactFull, redactUrl } from "../security/redact";
6-
import { URL_TOKEN_PATTERN } from "../security/redact-url";
5+
import { redactFullWithUrls } from "../security/redact";
76
import { type PolicyValue, parseNetworkPolicies } from "./preset-parsing";
87

98
type RuleScope = {
@@ -58,8 +57,7 @@ export function escapeTerminalText(value: string): string {
5857

5958
/** Redact credential-shaped content before rendering untrusted YAML scalars. */
6059
function renderTerminalText(value: string): string {
61-
const redactedUrls = value.replace(URL_TOKEN_PATTERN, (url) => redactUrl(url) ?? "<REDACTED>");
62-
return escapeTerminalText(redactFull(redactedUrls));
60+
return escapeTerminalText(redactFullWithUrls(value));
6361
}
6462

6563
function toStringOrUndefined(value: PolicyValue | undefined): string | undefined {

src/lib/security/redact.test.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,31 @@
33

44
import { describe, expect, it } from "vitest";
55

6-
import { redactForLog, redactFull, redactLogSequence, redactSensitiveText } from "./redact.js";
6+
import {
7+
redactForLog,
8+
redactFull,
9+
redactFullWithUrls,
10+
redactLogSequence,
11+
redactSensitiveText,
12+
} from "./redact.js";
13+
14+
describe("redactFullWithUrls", () => {
15+
it.each([
16+
[
17+
"username and password",
18+
"https://service-user:service-password@example.com/path",
19+
"https://example.com/path",
20+
],
21+
["userinfo only", "https://service-token@example.com/path", "https://example.com/path"],
22+
[
23+
"malformed URL fallback",
24+
"https://fallback-user:fallback-password@[not-an-ip/path",
25+
"https://[not-an-ip/path",
26+
],
27+
])("fully redacts URL credentials for %s", (_case, value, expected) => {
28+
expect(redactFullWithUrls(value)).toBe(expected);
29+
});
30+
});
731

832
describe("redactForLog", () => {
933
it("redacts pass aliases in structured keys", () => {

src/lib/security/redact.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ import type { StdioOptions } from "node:child_process";
1212
*
1313
* Two modes:
1414
* - `redact()` — partial (keep first 4 chars). Used by runner.ts for CLI output.
15-
* - `redactFull()` — full replacement. Used by debug.ts for diagnostic dumps.
15+
* - `redactFull()` — full replacement for known secret patterns.
16+
* - `redactFullWithUrls()` — full replacement for known patterns and URL credentials.
1617
* - `redactSensitiveText()` — full replacement + truncation. Used by onboard-session.ts.
1718
*
1819
* Ref: https://github.com/NVIDIA/NemoClaw/issues/2381
@@ -194,6 +195,12 @@ export function redactFull(text: string): string {
194195
return result;
195196
}
196197

198+
/** Fully redact secret patterns and credentials embedded in URL tokens. */
199+
export function redactFullWithUrls(text: string): string {
200+
const redactedUrls = text.replace(URL_TOKEN_PATTERN, (url) => redactUrl(url) ?? "<REDACTED>");
201+
return redactFull(redactedUrls);
202+
}
203+
197204
function redactStandaloneSecrets(text: string, replacement: string): string {
198205
let result = text;
199206
for (const pattern of [

0 commit comments

Comments
 (0)