From d5f581fe6b02a8f49abe97d192e923c4b29087b5 Mon Sep 17 00:00:00 2001 From: Justin Wyer Date: Thu, 2 Apr 2026 14:03:34 +0200 Subject: [PATCH] test: add regression tests for #666 (fails on main, passes on fix) Two regression tests that prove the bug introduced by PR #666: 1. Non-default credential tool (sops) is silently blocked by the hardcoded SAFE_COMMAND_PREFIXES with no way to override. 2. Private IP URL is silently blocked by isBlockedUrl() with no way to allowlist. Both tests use dynamic import to check for the override functions, so they run cleanly on both main (where they fail) and this branch (where they pass). Verified in a git worktree of main. --- src/tests/regression-666.test.ts | 113 +++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 src/tests/regression-666.test.ts diff --git a/src/tests/regression-666.test.ts b/src/tests/regression-666.test.ts new file mode 100644 index 000000000..166323a0c --- /dev/null +++ b/src/tests/regression-666.test.ts @@ -0,0 +1,113 @@ +/** + * Regression tests for the bug introduced by PR #666. + * + * Bug: PR #666 added hardcoded SAFE_COMMAND_PREFIXES and isBlockedUrl() with + * no override mechanism. Users with non-default credential tools (sops, doppler, + * age, etc.) or needing to fetch from internal URLs were silently broken. + * + * These tests import only APIs that exist on both main and this branch. + * On main (before fix): they FAIL because the override functions don't exist. + * On this branch (after fix): they PASS because overrides work. + */ +import { describe, it, beforeEach, afterEach } from "node:test"; +import assert from "node:assert/strict"; +import { + resolveConfigValue, + clearConfigValueCache, + SAFE_COMMAND_PREFIXES, +} from "../../packages/pi-coding-agent/src/core/resolve-config-value.ts"; +import { + isBlockedUrl, +} from "../resources/extensions/search-the-web/url-utils.ts"; + +describe("REGRESSION #666: hardcoded security lists with no override", () => { + beforeEach(() => { + clearConfigValueCache(); + }); + + afterEach(() => { + // Restore defaults — setAllowedCommandPrefixes/setFetchAllowedUrls are + // dynamically imported so we can restore even if they exist. + import("../../packages/pi-coding-agent/src/core/resolve-config-value.ts").then((mod) => { + if (typeof mod.setAllowedCommandPrefixes === "function") { + mod.setAllowedCommandPrefixes(SAFE_COMMAND_PREFIXES); + } + mod.clearConfigValueCache(); + }); + import("../resources/extensions/search-the-web/url-utils.ts").then((mod) => { + if (typeof mod.setFetchAllowedUrls === "function") { + mod.setFetchAllowedUrls([]); + } + }); + }); + + it("non-default credential tool (sops) can be unblocked via override", async (t) => { + const stderrChunks: string[] = []; + const originalWrite = process.stderr.write.bind(process.stderr); + process.stderr.write = (chunk: string | Uint8Array, ...args: unknown[]) => { + stderrChunks.push(chunk.toString()); + return true; + }; + t.after(() => { + process.stderr.write = originalWrite; + }); + + // Confirm the bug: sops is not in the hardcoded allowlist, so it's blocked + const blocked = resolveConfigValue("!sops decrypt --output-type json secrets.enc.json"); + assert.equal(blocked, undefined, "sops is blocked by the hardcoded allowlist"); + assert.ok( + stderrChunks.some((line) => line.includes('Blocked disallowed command: "sops"')), + "should log a block message for sops", + ); + + stderrChunks.length = 0; + clearConfigValueCache(); + + // The fix: setAllowedCommandPrefixes must exist and must unblock sops + const mod = await import("../../packages/pi-coding-agent/src/core/resolve-config-value.ts"); + assert.equal( + typeof mod.setAllowedCommandPrefixes, + "function", + "setAllowedCommandPrefixes must be exported (missing = bug #666 not fixed)", + ); + + mod.setAllowedCommandPrefixes([...SAFE_COMMAND_PREFIXES, "sops"]); + resolveConfigValue("!sops decrypt --output-type json secrets.enc.json"); + + const blockedAfterOverride = stderrChunks.some((line) => + line.includes("Blocked disallowed command"), + ); + assert.equal( + blockedAfterOverride, + false, + "sops must not be blocked after adding it to the allowlist", + ); + }); + + it("internal company URL can be unblocked via override", async () => { + const internalUrl = "http://192.168.1.100/internal-docs/api-reference"; + + // Confirm the bug: private IP is blocked with no way to allowlist + assert.equal( + isBlockedUrl(internalUrl), + true, + "private IP is blocked by the hardcoded SSRF blocklist", + ); + + // The fix: setFetchAllowedUrls must exist and must unblock the host + const mod = await import("../resources/extensions/search-the-web/url-utils.ts"); + assert.equal( + typeof mod.setFetchAllowedUrls, + "function", + "setFetchAllowedUrls must be exported (missing = bug #666 not fixed)", + ); + + mod.setFetchAllowedUrls(["192.168.1.100"]); + + assert.equal( + isBlockedUrl(internalUrl), + false, + "private IP must not be blocked after adding it to the allowlist", + ); + }); +});