From 0397cad89becf4684a3c8c11162be3ae20766872 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Thu, 23 Apr 2026 18:27:39 +0100 Subject: [PATCH] test: normalize safe-bin docs parity indentation --- docs/tools/exec-approvals.md | 88 +++++++++++++------------- src/infra/exec-safe-bin-policy.test.ts | 22 ++++++- 2 files changed, 65 insertions(+), 45 deletions(-) diff --git a/docs/tools/exec-approvals.md b/docs/tools/exec-approvals.md index 7c973dbd13b..7550caf4318 100644 --- a/docs/tools/exec-approvals.md +++ b/docs/tools/exec-approvals.md @@ -316,60 +316,60 @@ so file operands cannot be smuggled as ambiguous positionals. [//]: # "SAFE_BIN_DENIED_FLAGS:START" -- `grep`: `--dereference-recursive`, `--directories`, `--exclude-from`, `--file`, `--recursive`, `-R`, `-d`, `-f`, `-r` -- `jq`: `--argfile`, `--from-file`, `--library-path`, `--rawfile`, `--slurpfile`, `-L`, `-f` -- `sort`: `--compress-program`, `--files0-from`, `--output`, `--random-source`, `--temporary-directory`, `-T`, `-o` -- `wc`: `--files0-from` + - `grep`: `--dereference-recursive`, `--directories`, `--exclude-from`, `--file`, `--recursive`, `-R`, `-d`, `-f`, `-r` + - `jq`: `--argfile`, `--from-file`, `--library-path`, `--rawfile`, `--slurpfile`, `-L`, `-f` + - `sort`: `--compress-program`, `--files0-from`, `--output`, `--random-source`, `--temporary-directory`, `-T`, `-o` + - `wc`: `--files0-from` - [//]: # "SAFE_BIN_DENIED_FLAGS:END" + [//]: # "SAFE_BIN_DENIED_FLAGS:END" - Safe bins also force argv tokens to be treated as **literal text** at - execution time (no globbing and no `$VARS` expansion) for stdin-only - segments, so patterns like `*` or `$HOME/...` cannot be used to smuggle - file reads. + Safe bins also force argv tokens to be treated as **literal text** at + execution time (no globbing and no `$VARS` expansion) for stdin-only + segments, so patterns like `*` or `$HOME/...` cannot be used to smuggle + file reads. - + - - Safe bins must resolve from trusted binary directories (system defaults - plus optional `tools.exec.safeBinTrustedDirs`). `PATH` entries are never - auto-trusted. Default trusted directories are intentionally minimal: - `/bin`, `/usr/bin`. If your safe-bin executable lives in - package-manager/user paths (for example `/opt/homebrew/bin`, - `/usr/local/bin`, `/opt/local/bin`, `/snap/bin`), add them explicitly to - `tools.exec.safeBinTrustedDirs`. - + + Safe bins must resolve from trusted binary directories (system defaults + plus optional `tools.exec.safeBinTrustedDirs`). `PATH` entries are never + auto-trusted. Default trusted directories are intentionally minimal: + `/bin`, `/usr/bin`. If your safe-bin executable lives in + package-manager/user paths (for example `/opt/homebrew/bin`, + `/usr/local/bin`, `/opt/local/bin`, `/snap/bin`), add them explicitly to + `tools.exec.safeBinTrustedDirs`. + - - Shell chaining (`&&`, `||`, `;`) is allowed when every top-level segment - satisfies the allowlist (including safe bins or skill auto-allow). - Redirections remain unsupported in allowlist mode. Command substitution - (`$()` / backticks) is rejected during allowlist parsing, including inside - double quotes; use single quotes if you need literal `$()` text. + + Shell chaining (`&&`, `||`, `;`) is allowed when every top-level segment + satisfies the allowlist (including safe bins or skill auto-allow). + Redirections remain unsupported in allowlist mode. Command substitution + (`$()` / backticks) is rejected during allowlist parsing, including inside + double quotes; use single quotes if you need literal `$()` text. - On macOS companion-app approvals, raw shell text containing shell control - or expansion syntax (`&&`, `||`, `;`, `|`, `` ` ``, `$`, `<`, `>`, `(`, - `)`) is treated as an allowlist miss unless the shell binary itself is - allowlisted. + On macOS companion-app approvals, raw shell text containing shell control + or expansion syntax (`&&`, `||`, `;`, `|`, `` ` ``, `$`, `<`, `>`, `(`, + `)`) is treated as an allowlist miss unless the shell binary itself is + allowlisted. - For shell wrappers (`bash|sh|zsh ... -c/-lc`), request-scoped env - overrides are reduced to a small explicit allowlist (`TERM`, `LANG`, - `LC_*`, `COLORTERM`, `NO_COLOR`, `FORCE_COLOR`). + For shell wrappers (`bash|sh|zsh ... -c/-lc`), request-scoped env + overrides are reduced to a small explicit allowlist (`TERM`, `LANG`, + `LC_*`, `COLORTERM`, `NO_COLOR`, `FORCE_COLOR`). - For `allow-always` decisions in allowlist mode, known dispatch wrappers - (`env`, `nice`, `nohup`, `stdbuf`, `timeout`) persist the inner executable - path instead of the wrapper path. Shell multiplexers (`busybox`, `toybox`) - are unwrapped for shell applets (`sh`, `ash`, etc.) the same way. If a - wrapper or multiplexer cannot be safely unwrapped, no allowlist entry is - persisted automatically. + For `allow-always` decisions in allowlist mode, known dispatch wrappers + (`env`, `nice`, `nohup`, `stdbuf`, `timeout`) persist the inner executable + path instead of the wrapper path. Shell multiplexers (`busybox`, `toybox`) + are unwrapped for shell applets (`sh`, `ash`, etc.) the same way. If a + wrapper or multiplexer cannot be safely unwrapped, no allowlist entry is + persisted automatically. - If you allowlist interpreters like `python3` or `node`, prefer - `tools.exec.strictInlineEval=true` so inline eval still requires an - explicit approval. In strict mode, `allow-always` can still persist benign - interpreter/script invocations, but inline-eval carriers are not persisted - automatically. + If you allowlist interpreters like `python3` or `node`, prefer + `tools.exec.strictInlineEval=true` so inline eval still requires an + explicit approval. In strict mode, `allow-always` can still persist benign + interpreter/script invocations, but inline-eval carriers are not persisted + automatically. - + ### Safe bins versus allowlist diff --git a/src/infra/exec-safe-bin-policy.test.ts b/src/infra/exec-safe-bin-policy.test.ts index 20ba7b5a479..110acff27d1 100644 --- a/src/infra/exec-safe-bin-policy.test.ts +++ b/src/infra/exec-safe-bin-policy.test.ts @@ -17,6 +17,24 @@ const SAFE_BIN_DOC_DEFAULTS_END = '[//]: # "SAFE_BIN_DEFAULTS:END"'; const SAFE_BIN_DOC_DENIED_FLAGS_START = '[//]: # "SAFE_BIN_DENIED_FLAGS:START"'; const SAFE_BIN_DOC_DENIED_FLAGS_END = '[//]: # "SAFE_BIN_DENIED_FLAGS:END"'; +function normalizeGeneratedDocBlock(block: string): string { + const lines = block.split("\n"); + while (lines[0]?.trim() === "") { + lines.shift(); + } + while (lines.at(-1)?.trim() === "") { + lines.pop(); + } + const indents = lines + .filter((line) => line.trim().length > 0) + .map((line) => line.match(/^ */)?.[0].length ?? 0); + const commonIndent = Math.min(...indents); + if (commonIndent <= 0) { + return lines.join("\n"); + } + return lines.map((line) => line.slice(Math.min(line.length, commonIndent))).join("\n"); +} + function buildDeniedFlagArgvVariants(flag: string): string[][] { const value = "blocked"; if (flag.startsWith("--")) { @@ -187,7 +205,9 @@ describe("exec safe bin policy docs parity", () => { const end = docs.indexOf(SAFE_BIN_DOC_DENIED_FLAGS_END); expect(start).toBeGreaterThanOrEqual(0); expect(end).toBeGreaterThan(start); - const actual = docs.slice(start + SAFE_BIN_DOC_DENIED_FLAGS_START.length, end).trim(); + const actual = normalizeGeneratedDocBlock( + docs.slice(start + SAFE_BIN_DOC_DENIED_FLAGS_START.length, end), + ); const expected = renderSafeBinDeniedFlagsDocBullets(); expect(actual).toBe(expected); });