From 0e226266b17d18fa58d206535171fe2610c91ecb Mon Sep 17 00:00:00 2001 From: BeiWay1145 Date: Sat, 31 Oct 2026 00:38:48 +0800 Subject: [PATCH] feat(download-guard): refuse disk-writing shell downloads when a forwarder is configured Closes #506. The model reaches for `curl -o` / `wget -O` / `Invoke-WebRequest -OutFile` because those commands work. They also write one connection at a time: on the reporting machine the same ISO moved at 0.13 MB/s that way and 11.78 MB/s through a downloader built for the job, roughly 90x. The rule that says "use the downloader" has only ever existed as prose, and prose did not stop it. This row moves the rule into the runtime, with one deliberate difference from the version that motivated the issue: it never names a downloader. The operator supplies a command template, and every part of the refusal is derived from it. An unconfigured install gets no listener at all. WHY THE DECISION IS SPLIT FROM THE DETECTION `src/download-guard/detect.ts` is a pure function over a command string, with no host dependency, so the rules can be tested exhaustively without mounting anything. `src/download-guard/policy.ts` turns a hit plus the operator's config into a decision. Keeping the refusal out of the detector is what makes "does this look like a download" a question that can be answered by evidence rather than by policy taste. FALSE NEGATIVES ARE THE SAFER ERROR Missing a download leaves the status quo. Refusing an ordinary command breaks a working session. So every rule requires a WRITE signal, and the suite of must-allow cases is deliberately larger than the must-refuse one. It includes the shapes that a naive rule gets wrong: `curl URL` printing to stdout, `-o -` and `wget -qO-` streaming into a pipe, `node download.cjs --help` (the word "download" proves nothing about where bytes go), redirecting *from* a file, and `Invoke-RestMethod` returning an object instead of writing one. SCOPE AND ITS LIMIT Shell tools only, and `script_path` is skipped outright: the file's contents are not in the call, and guessing at them would be the false positive this row is trying to avoid. WHAT IS VERIFIED node test/run.mjs download-guard download-guard-detect 56 checks download-guard-policy 15 checks download-guard-row 7 checks (real cordis waterfall) download-guard-mount 5 checks (through apply(), the composition root) The mount suite is the one that matters for integration: it proves the row is reached from `src/index.ts`, that the `download-guard` config section resolves, and that `enabled: false` is honoured even when a template is present. `node scripts/validate-manifest.mjs` - 84 checks. `node test/run.mjs manifest config host-metadata` - 8 suites. `node scripts/check-file-size.mjs` - largest new file 242 lines, total implementation 428. NOT VERIFIED, AND STATED SO RATHER THAN IMPLIED The full suite has 13 failing suites on this machine, none of them related to this change: they need a Rust worker (`npm run build:ptc`), a bash-ipc provenance receipt, or npm's default cache directory, which this machine denies (EPERM). None touches the download guard. --- .gitignore | 1 + cordis.patch.yml | 6 + docs/configuration-reference.md | 1 + ...15\347\275\256\345\217\202\350\200\203.md" | 1 + package.json | 6 + src/components/defaults.ts | 10 +- src/components/download-guard/icon.png | Bin 0 -> 119677 bytes src/components/download-guard/index.ts | 65 +++++ src/components/download-guard/locale/en.json | 6 + src/components/download-guard/locale/zh.json | 6 + src/components/download-guard/package.json | 6 + src/config.ts | 49 ++++ src/download-guard/detect.ts | 242 ++++++++++++++++++ src/download-guard/policy.ts | 121 +++++++++ src/index.ts | 2 + src/settings/merged.ts | 16 +- test/download-guard-detect.test.mjs | 127 +++++++++ test/download-guard-mount.test.mjs | 79 ++++++ test/download-guard-policy.test.mjs | 100 ++++++++ test/download-guard-row.test.mjs | 97 +++++++ 20 files changed, 935 insertions(+), 6 deletions(-) create mode 100644 src/components/download-guard/icon.png create mode 100644 src/components/download-guard/index.ts create mode 100644 src/components/download-guard/locale/en.json create mode 100644 src/components/download-guard/locale/zh.json create mode 100644 src/components/download-guard/package.json create mode 100644 src/download-guard/detect.ts create mode 100644 src/download-guard/policy.ts create mode 100644 test/download-guard-detect.test.mjs create mode 100644 test/download-guard-mount.test.mjs create mode 100644 test/download-guard-policy.test.mjs create mode 100644 test/download-guard-row.test.mjs diff --git a/.gitignore b/.gitignore index b4bf0160..6b1fca5e 100644 --- a/.gitignore +++ b/.gitignore @@ -44,3 +44,4 @@ bench/runs/ bench/llm.patch.yml # Update-lock sidecars the CLI leaves next to whatever it rewrites (also seen under test/). *.dsh-ops-update-*.lock +.npm-cache/ diff --git a/cordis.patch.yml b/cordis.patch.yml index 6f7590c9..e829917b 100644 --- a/cordis.patch.yml +++ b/cordis.patch.yml @@ -29,6 +29,12 @@ residentTools: true discovery: enabled: true + # Inert until template is set; off by default so an install that never + # opted in gets no listener at all. + download-guard: + enabled: false + downloadGuard: + template: "" panel: enabled: false tui: diff --git a/docs/configuration-reference.md b/docs/configuration-reference.md index 556f58ce..6c019ff2 100644 --- a/docs/configuration-reference.md +++ b/docs/configuration-reference.md @@ -83,6 +83,7 @@ falling back to the built-in surface; scoped diagnostics remain accessible. | `binaryPath` / `bashPath` | managed worker/Shell resolution unless explicitly selected | | `required` | false; missing required worker fails activation | | `denyCredentialReads` | true; Native-only override, PTC rejects overrides and retains its boundary | +| `downloadGuard.template` | empty; inert until set. A command with `{url}` (required) and optional `{file}` that a disk-writing shell download is redirected to | | `allowSystemShellFallback` | false; Windows automatic fallback is opt-in | `shell: false` retains explicit `bashPath`, `allowSystemShellFallback` and `hidePwsh` intent, diff --git "a/docs/\351\205\215\347\275\256\345\217\202\350\200\203.md" "b/docs/\351\205\215\347\275\256\345\217\202\350\200\203.md" index c8a8bf21..b279f302 100644 --- "a/docs/\351\205\215\347\275\256\345\217\202\350\200\203.md" +++ "b/docs/\351\205\215\347\275\256\345\217\202\350\200\203.md" @@ -68,6 +68,7 @@ presets: | `binaryPath`/`bashPath` | 默认受管理解析,可显式指定 | | `required` | false;所需 worker 缺失时拒绝激活 | | `denyCredentialReads` | true,Native 可显式覆盖;PTC 拒绝覆盖并持续执行边界 | +| `downloadGuard.template` | 空;未设置时完全不生效。一条含 `{url}`(必需)与可选 `{file}` 的命令模板,会落盘的 shell 下载将被重定向到它 | | `allowSystemShellFallback` | false,Windows 自动系统回退须主动选择 | `shell: false` 保留显式 `bashPath`、`allowSystemShellFallback` 与 `hidePwsh` 意图,但不发布 Bash、不隐藏宿主 Shell;重新启用时应用保存值。自定义 PTC 卡片锁定 `files` 开启,并提示用 `residentTools` 控制启动 API。PTC 必须 `files: true`,且宿主真实为 run_code-only 展示并有 PTC runtime;不接受 `residentNames`,不把外部 Native 提供者当类型化 SDK。[第三方 PTC 注册暂不支持](外部工具提供者.md)。 diff --git a/package.json b/package.json index a23f53f8..c9f8e65c 100644 --- a/package.json +++ b/package.json @@ -86,6 +86,12 @@ "./discovery/package.json": "./lib/components/discovery/package.json", "./discovery/locale/*.json": "./lib/components/discovery/locale/*.json", "./discovery/icon": "./lib/components/discovery/icon.png", + "./download-guard": { + "default": "./lib/components/download-guard/index.js" + }, + "./download-guard/package.json": "./lib/components/download-guard/package.json", + "./download-guard/locale/*.json": "./lib/components/download-guard/locale/*.json", + "./download-guard/icon": "./lib/components/download-guard/icon.png", "./hide-official": { "default": "./lib/components/hide-official/index.js" }, diff --git a/src/components/defaults.ts b/src/components/defaults.ts index 01ed3409..80aa0cbe 100644 --- a/src/components/defaults.ts +++ b/src/components/defaults.ts @@ -1,9 +1,11 @@ /** Package defaults only. Saved Loader overrides take precedence over this layer. */ -export const COMPONENTS = ['mode-minimal', 'mode-standard', 'mode-ptc', 'mode-cordis', 'discovery', 'panel', 'tui'] as const +export const COMPONENTS = ['mode-minimal', 'mode-standard', 'mode-ptc', 'mode-cordis', 'discovery', 'download-guard', 'panel', 'tui'] as const export type Component = typeof COMPONENTS[number] export function componentEnabled(component: Component, _platform: string): boolean { - // Enhanced presets and progressive discovery start enabled; data capture and - // the TUI integration are opt-in. - return !['panel', 'tui'].includes(component) + // Enhanced presets and progressive discovery start enabled. The download + // guard is opt-in: it only acts once an operator names a downloader, so + // enabling it by default would add a listener to installs that gain nothing. + // Data capture and the TUI integration are opt-in for their own reasons. + return !['download-guard', 'panel', 'tui'].includes(component) } export function componentEntry(component: Component): string { return `dsh-ops/${component}` } diff --git a/src/components/download-guard/icon.png b/src/components/download-guard/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..cb35b43ecb1fc6b24dc382ce5727c26778c1803b GIT binary patch literal 119677 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

GKkG*_}fx` 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 N002ovPDHLkV1i~bjZ^>t literal 0 HcmV?d00001 diff --git a/src/components/download-guard/index.ts b/src/components/download-guard/index.ts new file mode 100644 index 00000000..0b5e3eb5 --- /dev/null +++ b/src/components/download-guard/index.ts @@ -0,0 +1,65 @@ +/** + * `dsh-ops/download-guard` row: refuse a shell download that writes to disk and + * name the operator's faster downloader instead. + * + * WHY THIS IS A ROW AND NOT PART OF A MODE + * + * It changes what the model is allowed to do, so it must be switchable on its + * own. Mounting it as a row means disabling it in the profile tears its + * listener down through the normal fiber lifecycle, with no extra flag to keep + * in sync. + * + * WHY THE LISTENER ALLOWS BY DEFAULT + * + * With no `downloadGuard.template` configured this row registers nothing at + * all. That is deliberate: the plugin ships to installs that never asked for a + * download policy, and silently refusing their `curl -o` would be a regression + * they did not choose. The whole decision lives in `./policy.ts`, which returns + * `allow` until the operator names a downloader. + * + * @module dsh-ops/components/download-guard + */ +import type { Context } from '../../adapter/dsh/types.js' +import { resolveConfig } from '../../config.js' +import { inspectCommand } from '../../download-guard/detect.js' +import { decideDownload } from '../../download-guard/policy.js' + +export const name = 'dsh-ops-download-guard' + +/** Shell tools whose `command` argument is worth inspecting. */ +const SHELL_TOOLS = new Set(['ops_bash', 'bash', 'pwsh', 'pwsh_persistent', 'shell']) + +/** + * Mount the row. + * + * @param ctx - the row's cordis context. + * @param rawConfig - the row's config, validated by `resolveConfig`. + */ +export function apply(ctx: Context, rawConfig?: unknown): void { + const config = resolveConfig(rawConfig) + const guard = config.downloadGuard + + // Nothing configured: register nothing at all, so an install that never opted + // in pays no listener and no per-call work. + if (guard.template.trim() === '') return + + ctx.on('tools/pre-execute', async (exec, next) => { + try { + const toolName = typeof exec?.name === 'string' ? exec.name : '' + if (!SHELL_TOOLS.has(toolName)) return next() + const args = exec?.arguments as { command?: unknown, script_path?: unknown } | undefined + if (args === null || typeof args !== 'object') return next() + // `script_path` runs a file whose contents are not in this call, so there + // is nothing to inspect. Guessing at the file would be a false positive. + if (typeof args.script_path === 'string' && args.script_path !== '') return next() + const command = args.command + const hit = inspectCommand(command) + const decision = decideDownload(hit, guard, typeof command === 'string' ? command : '') + if (decision.kind === 'allow') return next() + return { kind: 'deny', reason: decision.reason, info: decision.info } + } catch { + // A detector bug must never block an unrelated command. + return next() + } + }) +} diff --git a/src/components/download-guard/locale/en.json b/src/components/download-guard/locale/en.json new file mode 100644 index 00000000..5df5ea2b --- /dev/null +++ b/src/components/download-guard/locale/en.json @@ -0,0 +1,6 @@ +{ + "meta": { + "title": "dsh-ops-download-guard", + "description": "Refuse shell downloads that write to disk, and name the faster downloader the operator configured. Does nothing until downloadGuard.template is set." + } +} diff --git a/src/components/download-guard/locale/zh.json b/src/components/download-guard/locale/zh.json new file mode 100644 index 00000000..695ffa50 --- /dev/null +++ b/src/components/download-guard/locale/zh.json @@ -0,0 +1,6 @@ +{ + "meta": { + "title": "dsh-ops-download-guard", + "description": "拦截会把文件写到磁盘的 shell 下载,并给出运维配置的更快下载器。未配置 downloadGuard.template 时完全不生效。" + } +} diff --git a/src/components/download-guard/package.json b/src/components/download-guard/package.json new file mode 100644 index 00000000..6f0c9869 --- /dev/null +++ b/src/components/download-guard/package.json @@ -0,0 +1,6 @@ +{ + "name": "dsh-ops-download-guard", + "description": "Refuse a shell download that writes to disk and name the operator's configured faster downloader instead. Inert until downloadGuard.template is set.", + "icon": "./icon.png", + "type": "module" +} diff --git a/src/config.ts b/src/config.ts index 5f08bda6..644808a5 100644 --- a/src/config.ts +++ b/src/config.ts @@ -22,6 +22,7 @@ export const LEGACY_FASTCTX_KEYS = Object.freeze(['serverName', 'enableShellTool export const CORE_CONFIG_KEYS = Object.freeze([ ...LEGACY_FASTCTX_KEYS, 'binaryPath', 'required', 'shellPolicy', 'deniedHostTools', 'promptPolicy', 'extraGuidance', 'bashPath', 'publishBashTool', 'allowSystemShellFallback', 'denyCredentialReads', 'configUpdateVersion', + 'downloadGuard', ]) /** Upper bound accepted for the legacy `toolCallTimeoutMs`, matching the loader's timer ceiling. */ @@ -99,6 +100,40 @@ function optionalStringList(raw: Record, key: string, fallback: return [...value] } +/** + * Validate the `downloadGuard` block. + * + * A malformed block fails loud and names the offending key, matching the rest + * of this file. An absent block is the normal state and resolves to "no + * forwarder configured", which leaves every download command alone. + * + * @param rawConfig - the raw `downloadGuard` value, or undefined. + * @returns the resolved configuration. + */ +function resolveDownloadGuard(rawConfig: unknown): DownloadGuardConfig { + if (rawConfig === undefined || rawConfig === null) return { template: '' } + if (typeof rawConfig !== 'object' || Array.isArray(rawConfig)) { + throw new ConfigError(`downloadGuard must be an object, got ${JSON.stringify(rawConfig)}`) + } + const raw = rawConfig as Record + rejectUnknownKeys(raw, ['template']) + // An empty string is how the shipped patch row spells "not set", so it must + // resolve to the same inert state as an absent key rather than failing the + // load of an install that never configured anything. + const value = raw.template + if (value !== undefined && value !== null && typeof value !== 'string') { + throw new ConfigError(`downloadGuard.template must be a string, got ${JSON.stringify(value)}`) + } + const template = typeof value === 'string' ? value.trim() : '' + if (template.trim() !== '' && !template.includes('{url}')) { + throw new ConfigError( + `downloadGuard.template must contain {url} so the suggested command can identify what to fetch, ` + + `got ${JSON.stringify(template)}`, + ) + } + return { template } +} + /** The resolved configuration, with every default applied. */ export interface ResolvedConfig { configUpdateVersion: number @@ -112,6 +147,19 @@ export interface ResolvedConfig { bashPath: string | undefined publishBashTool: boolean allowSystemShellFallback: boolean + downloadGuard: DownloadGuardConfig +} + +/** + * Operator configuration for the download guard. + * + * Empty by default: an unconfigured install must behave exactly as it did + * before this feature existed. The guard only refuses a call once the operator + * has named a downloader that is strictly better than the one the model picked. + */ +export interface DownloadGuardConfig { + /** Command template with `{url}` (required to act) and optional `{file}`. */ + template: string } /** @@ -171,6 +219,7 @@ export function resolveConfig(rawConfig?: unknown): ResolvedConfig { // needs one of them can turn the fence off for this row; the environment // variable only ever widens the list, so a model cannot open it. denyCredentialReads: optionalBoolean(raw, 'denyCredentialReads', true), + downloadGuard: resolveDownloadGuard(raw.downloadGuard), } } diff --git a/src/download-guard/detect.ts b/src/download-guard/detect.ts new file mode 100644 index 00000000..5f94409b --- /dev/null +++ b/src/download-guard/detect.ts @@ -0,0 +1,242 @@ +/** + * Detect a shell call that writes downloaded bytes to a local file. + * + * WHY THIS IS DETECTION ONLY + * + * Deciding what to DO about such a call is a policy question and lives in + * `./policy.ts`; this module answers one narrow, testable question: given a + * command string, does it look like a download that lands on disk? + * + * FALSE NEGATIVES ARE CHEAPER THAN FALSE POSITIVES + * + * Missing a download leaves the status quo: the command runs as it always did. + * Refusing an ordinary command breaks a working session and is visible to the + * user. Every rule below therefore requires a WRITE signal — a flag that names + * an output file — and never fires merely because the text mentions a + * downloader. That distinction is not theoretical: a rule matching the bare + * word `download` refused a legitimate `node download.cjs --help`, because in a + * shell `node download.cjs` and `aria2-dl.js something` are indistinguishable + * strings. + * + * @module dsh-ops/download-guard/detect + */ + +/** What matched, and where the bytes appear to be going. */ +export interface DownloadHit { + /** Short machine name of the matched rule, for tests and refusal text. */ + rule: string + /** The URL the command appears to fetch, when one is visible in the text. */ + url?: string + /** The local file the command appears to write, when one is visible. */ + target?: string +} + +/** + * Split a command line into sub-commands on unquoted `;`, `&&`, `||`, `|` and + * newlines, so a download buried after an unrelated `cd` is still examined on + * its own. + * + * Quotes are tracked so a separator inside them does not split: `echo "a;b"` + * is one sub-command. This is not a full shell parser and does not try to be — + * it only has to be conservative in the direction that avoids false positives. + * + * @param command - the raw command string. + * @returns one trimmed string per sub-command, separators removed. + */ +export function splitSubcommands(command: string): string[] { + const parts: string[] = [] + let current = '' + let quote: 'none' | 'single' | 'double' = 'none' + for (let index = 0; index < command.length; index += 1) { + const char = command[index] as string + if (quote === 'single') { + current += char + if (char === "'") quote = 'none' + continue + } + if (quote === 'double') { + current += char + if (char === '"') quote = 'none' + else if (char === '\\' && index + 1 < command.length) { + current += command[index + 1] as string + index += 1 + } + continue + } + if (char === "'") { quote = 'single'; current += char; continue } + if (char === '"') { quote = 'double'; current += char; continue } + if (char === '\\' && index + 1 < command.length) { + current += char + (command[index + 1] as string) + index += 1 + continue + } + if (char === ';' || char === '\n') { parts.push(current); current = ''; continue } + if (char === '&' && command[index + 1] === '&') { parts.push(current); current = ''; index += 1; continue } + if (char === '|') { + parts.push(current) + current = '' + if (command[index + 1] === '|') index += 1 + continue + } + current += char + } + parts.push(current) + return parts.map(part => part.trim()).filter(part => part.length > 0) +} + +/** Strip a leading `VAR=value` assignment run, which is not the command word. */ +function commandWordOf(segment: string): { word: string, rest: string } { + let rest = segment.trim() + for (;;) { + const assignment = /^[A-Za-z_][A-Za-z0-9_]*=\S*\s+/.exec(rest) + if (assignment === null) break + rest = rest.slice(assignment[0].length) + } + const match = /^(\S+)(?:\s+([\s\S]*))?$/.exec(rest) + if (match === null) return { word: '', rest: '' } + return { word: match[1] ?? '', rest: match[2] ?? '' } +} + +/** + * Extract the argument that follows exactly one of `flags`. + * + * Both `-o FILE` and `-oFILE` spellings are accepted, and `--long=FILE` too, + * because all three appear in real usage. A repeated flag must resolve to the + * LAST occurrence, because that is the one the tool honours. + * + * A value of `-` is returned as-is: callers decide what it means, because for + * curl and wget it means stdout rather than a file. + * + * @param args - the argument tail of a sub-command. + * @param flags - short and long spellings to look for, e.g. `['-o', '--output']`. + * @returns the value when one is found. + */ +function flagValue(args: string[], flags: readonly string[]): string | undefined { + let found: string | undefined + for (let index = 0; index < args.length; index += 1) { + const arg = args[index] as string + for (const flag of flags) { + if (arg === flag) { + const next = args[index + 1] + // A leading `-` normally means the next option, not a value; `-` alone + // is the documented stdout sentinel and must be taken literally. + if (next !== undefined && (next === '-' || !next.startsWith('-'))) found = next + continue + } + if (arg.startsWith(`${flag}=`)) { found = arg.slice(flag.length + 1); continue } + // A short flag may carry its value with no separator: -oFILE. + if (flag.length === 2 && arg.startsWith(flag) && arg.length > 2) found = arg.slice(2) + } + } + return found !== undefined && found !== '' ? found : undefined +} + +/** First `http://` or `https://` token in the segment, if any. */ +function urlOf(segment: string): string | undefined { + const match = /(https?:\/\/[^\s"'`]+)/i.exec(segment) + return match?.[1] +} + +/** A trailing `> FILE` / `>> FILE` redirect target, when present. */ +function redirectTargetOf(segment: string): string | undefined { + const match = /(?:^|[^>\d])>{1,2}\s*(?![&|])([^\s;|&]+)/.exec(segment) + const target = match?.[1] + if (target === undefined) return undefined + return target === '/dev/null' || target === 'nul' || target === '$null' ? undefined : target +} + +/** + * Whether the sub-command fetches over the network at all. + * + * Used only to decide whether a redirect target is worth reporting; the + * refusal itself is driven by the presence of a write flag. + * + * @param segment - one sub-command. + * @returns true when a URL or a known downloader appears. + */ +function fetchesNetwork(segment: string): boolean { + return urlOf(segment) !== undefined || /\b(curl|wget)\b/i.test(segment) +} + +/** Quote-aware split of an argument tail into tokens. */ +function argumentsOf(rest: string): string[] { + const match = rest.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g) + return match ?? [] +} + +/** + * Inspect one sub-command. + * + * @param segment - one sub-command from {@link splitSubcommands}. + * @returns the hit, or undefined when the command does not write a download. + */ +export function inspectSegment(segment: string): DownloadHit | undefined { + const { word, rest } = commandWordOf(segment) + if (word === '') return undefined + const base = word.replace(/^.*[\\/]/, '').replace(/\.exe$/i, '').toLowerCase() + const args = argumentsOf(rest) + + // curl: -o / --output names a file; -O / --remote-name derives the name from + // the URL but still lands one. A bare `curl URL` prints to stdout, so it is + // NOT a download and must stay allowed. + if (base === 'curl') { + const output = flagValue(args, ['-o', '--output']) + // `-o -` streams to stdout; nothing lands on disk. + if (output !== undefined && output !== '-') return { rule: 'curl-output', url: urlOf(segment), target: output } + const remoteName = args.some(arg => arg === '-O' || arg === '--remote-name' + || arg === '--remote-name-all' || /^-[A-Za-z]*O[A-Za-z]*$/.test(arg)) + if (remoteName) return { rule: 'curl-remote-name', url: urlOf(segment) } + const redirect = redirectTargetOf(segment) + if (redirect !== undefined && fetchesNetwork(segment)) { + return { rule: 'redirect', url: urlOf(segment), target: redirect } + } + return undefined + } + + // wget writes to a file by design; -O names it, -q only silences progress. + if (base === 'wget') { + const output = flagValue(args, ['-O', '--output-document']) + if (output !== undefined) return { rule: 'wget', url: urlOf(segment), target: output } + // `-O -` means stdout, not a file. It is the documented way to stream + // wget into a pipe (`wget -qO- URL | tar xz`), so it must stay allowed. + if (args.some(arg => arg === '-O-' || arg === '--output-document=-' + || /^-[A-Za-z]*O-$/.test(arg))) return undefined + const redirect = redirectTargetOf(segment) + if (redirect !== undefined) return { rule: 'wget', url: urlOf(segment), target: redirect } + // A bare `wget URL` still lands a file in the working directory. + if (urlOf(segment) !== undefined) return { rule: 'wget', url: urlOf(segment) } + return undefined + } + + // PowerShell downloaders: only the *OutFile / -Destination spellings write a + // named file. `Invoke-RestMethod` without one returns a parsed object, which + // is not a download. + if (base === 'invoke-webrequest' || base === 'iwr') { + const output = flagValue(args, ['-OutFile']) + return output === undefined ? undefined : { rule: 'Invoke-WebRequest', url: urlOf(segment), target: output } + } + if (base === 'invoke-restmethod' || base === 'irm') { + const output = flagValue(args, ['-OutFile']) + return output === undefined ? undefined : { rule: 'Invoke-RestMethod', url: urlOf(segment), target: output } + } + if (base === 'start-bitstransfer') { + const output = flagValue(args, ['-Destination']) + return output === undefined ? undefined : { rule: 'Start-BitsTransfer', url: urlOf(segment), target: output } + } + return undefined +} + +/** + * Inspect a whole command line, sub-command by sub-command. + * + * @param command - the raw `command` argument of a shell tool call. + * @returns the first download hit, or undefined. + */ +export function inspectCommand(command: unknown): DownloadHit | undefined { + if (typeof command !== 'string' || command.trim() === '') return undefined + for (const segment of splitSubcommands(command)) { + const hit = inspectSegment(segment) + if (hit !== undefined) return hit + } + return undefined +} diff --git a/src/download-guard/policy.ts b/src/download-guard/policy.ts new file mode 100644 index 00000000..01fa9e4d --- /dev/null +++ b/src/download-guard/policy.ts @@ -0,0 +1,121 @@ +/** + * Turn a detection result into a decision, using operator configuration. + * + * WHY THE FORWARDER IS CONFIGURATION, NOT CODE + * + * A guard that hardcodes one downloader is only useful on the machine it was + * written for. The plugin therefore never names a downloader: the operator + * supplies a command template, and every part of the refusal text is derived + * from it. With no template configured the guard stays out of the way + * entirely — a default that refuses work the operator never asked to change + * would be worse than the slow download it prevents. + * + * @module dsh-ops/download-guard/policy + */ +import type { DownloadHit } from './detect.js' + +/** + * One configured forwarder. + * + * `template` is the whole command, with `{url}` and `{file}` replaced by the + * detected values. A template is used verbatim otherwise, so it can carry any + * flags the operator's downloader needs. + */ +export interface ForwarderConfig { + /** The command to suggest, containing `{url}` and optionally `{file}`. */ + template: string +} + +/** What the guard decided for one call. */ +export type DownloadDecision = + | { kind: 'allow' } + | { kind: 'deny', reason: string, info: { name: string, code: string } } + +/** Quote one path for a POSIX shell only when it would otherwise split. */ +function shellQuote(value: string): string { + if (value === '') return "''" + if (/^[A-Za-z0-9_\-./:=@%+,]+$/.test(value)) return value + return `'${value.replace(/'/g, `'\\''`)}'` +} + +/** + * Render the forwarder command for one hit. + * + * `{url}` is required because a template without it cannot identify what to + * download. `{file}` is optional: a downloader that derives the name from the + * URL does not need it. When the hit has no visible target but the template + * asks for one, the placeholder is left intact rather than replaced with a + * guess, so the operator can see why it was not filled. + * + * @param template - the configured command template. + * @param hit - the detection result. + * @returns the command to show the model. + */ +export function renderForwarder(template: string, hit: DownloadHit): string { + let rendered = template + if (hit.url !== undefined) rendered = rendered.split('{url}').join(shellQuote(hit.url)) + if (hit.target !== undefined) rendered = rendered.split('{file}').join(shellQuote(hit.target)) + return rendered +} + +/** + * Build the refusal text. + * + * The shape follows the host's own refusals: what was refused, why it matters, + * and the exact command that does it better. A refusal that does not name a + * working alternative just moves the problem to the model. + * + * @param hit - the detection result. + * @param template - the configured forwarder template. + * @param command - the original command, for context. + * @returns the refusal reason. + */ +export function refusalReason(hit: DownloadHit, template: string, command: string): string { + const shown = command.length > 240 ? `${command.slice(0, 240)}...` : command + const lines = [ + `This command downloads to disk through ${hit.rule}, which writes one connection at a time.`, + hit.target === undefined + ? 'The bytes land in a local file either way, so a downloader built for the job is strictly faster.' + : `The bytes go to ${hit.target} either way, so a downloader built for the job is strictly faster.`, + '', + 'Use the configured downloader instead:', + ` ${renderForwarder(template, hit)}`, + ] + if (hit.url !== undefined) lines.push('', `URL: ${hit.url}`) + lines.push( + '', + 'If that downloader is not what you want here, say so instead of retrying this command:', + 'the guard refuses rather than silently rewriting your command.', + '', + `Blocked command: ${shown}`, + ) + return lines.join('\n') +} + +/** + * Decide what to do with one inspected call. + * + * @param hit - the detection result, when the command looks like a download. + * @param config - the operator's forwarder configuration, when configured. + * @param command - the original command text. + * @returns the decision. + */ +export function decideDownload( + hit: DownloadHit | undefined, + config: ForwarderConfig | undefined, + command: string, +): DownloadDecision { + // No detection: nothing to say. + if (hit === undefined) return { kind: 'allow' } + // No configured forwarder: the operator has not opted in, so the command runs + // exactly as it always did. This is the difference between a plugin that + // improves a workflow and one that breaks an unconfigured install. + if (config === undefined || config.template.trim() === '') return { kind: 'allow' } + // A template that cannot name the URL cannot produce a runnable alternative. + if (!config.template.includes('{url}') || hit.url === undefined) return { kind: 'allow' } + return { + kind: 'deny', + reason: refusalReason(hit, config.template, command), + info: { name: 'DownloadGuardError', code: 'DOWNLOAD_TO_FILE' }, + } +} diff --git a/src/index.ts b/src/index.ts index d8b69f98..a1f27a23 100644 --- a/src/index.ts +++ b/src/index.ts @@ -11,6 +11,7 @@ import { mountRuntimeSettings } from './settings/runtime-host.js' import { companionVersion } from './settings/companion.js' import * as panelModule from './components/panel/index.js' import * as tuiModule from './components/tui/index.js' +import * as downloadGuardModule from './components/download-guard/index.js' export { Config } from './settings/merged.js' export { applyComponent } from './legacy/host.js' export const name = 'dsh-ops' @@ -58,6 +59,7 @@ export async function apply(root: Context, rawConfig: unknown): Promise { const effective = gateConsumer(resolved, config.global, linked) await host.plugin({ name: `dsh-ops/custom-${resolved.preset}`, apply(ctx: Context) { return mountIntegration(ctx, effective) } }) } + await mount(downloadGuardModule, config['download-guard']) await mount(panelModule, config.panel) await mount(tuiModule, config.tui) } finally { endMergedRow() } diff --git a/src/settings/merged.ts b/src/settings/merged.ts index c19653c3..0ba0f412 100644 --- a/src/settings/merged.ts +++ b/src/settings/merged.ts @@ -15,6 +15,7 @@ import { ConfigError } from '../config.js' import { resolveModeConfig, type ModeConfig } from '../presets/config.js' import { MODE_IDS } from '../presets/types.js' import { resolveDiscoveryConfig } from '../discovery/config.js' +import { resolveConfig, type DownloadGuardConfig } from '../config.js' import { resolvePanelConfig } from '../panel/collect.js' import { resolveActivityConfig } from '../tui/activity.js' import { PRESET_SETTINGS } from './preset-schema.js' @@ -29,7 +30,7 @@ type ActivityConfig = ReturnType export const MODES = MODE_IDS export type ModeKey = typeof MODES[number] /** Every section the row accepts. */ -export const FEATURES = [...MODES, 'discovery', 'panel', 'tui', 'other'] as const +export const FEATURES = [...MODES, 'discovery', 'download-guard', 'panel', 'tui', 'other'] as const export type Feature = typeof FEATURES[number] /** Bundle-wide switches; resident publication is deliberately mode-local. */ @@ -57,6 +58,7 @@ export interface MergedConfig { readonly ptc: FeatureState readonly cordis: FeatureState readonly discovery: FeatureState + readonly 'download-guard': FeatureState readonly panel: FeatureState readonly tui: FeatureState readonly other: FeatureState<{ pwsh7Link: boolean }> @@ -64,7 +66,9 @@ export interface MergedConfig { const ENABLED_BY_DEFAULT: Readonly> = Object.freeze({ minimal: true, standard: true, ptc: true, cordis: true, discovery: true, - panel: false, tui: false, other: false, + // The download guard is off until an operator names a downloader: mounting it + // against an empty template would register a listener that can never act. + 'download-guard': false, panel: false, tui: false, other: false, }) function object(name: string, value: unknown): Record { @@ -135,6 +139,7 @@ export function resolveMergedConfig(raw?: unknown): MergedConfig { ptc: modeState('ptc', input.ptc), cordis: modeState('cordis', input.cordis), discovery: discoveryState(input.discovery), + 'download-guard': downloadGuardState(input['download-guard']), panel: panelState(input.panel), tui: tuiState(input.tui), other: otherState(input.other), @@ -155,6 +160,13 @@ function discoveryState(value: unknown): FeatureState { : { enabled, config: resolved as DiscoveryConfig } } +function downloadGuardState(value: unknown): FeatureState { + const { enabled, rest, resolved } = split('download-guard', value) + return resolved === undefined + ? { enabled, config: resolveConfig(rest).downloadGuard, raw: rest } + : { enabled, config: resolved as DownloadGuardConfig } +} + function panelState(value: unknown): FeatureState { const { enabled, rest, resolved } = split('panel', value) return resolved === undefined diff --git a/test/download-guard-detect.test.mjs b/test/download-guard-detect.test.mjs new file mode 100644 index 00000000..e68de035 --- /dev/null +++ b/test/download-guard-detect.test.mjs @@ -0,0 +1,127 @@ +/** + * Detection rules for "this shell call writes a download to disk". + * + * The cases split into two groups and the second one matters more: + * + * - downloads that must be recognised, including the spellings that are easy + * to miss (`-oFILE` with no space, `--output=FILE`, a redirect after a `;`) + * - ordinary commands that must stay ALLOWED, because a refusal here breaks a + * working session. This group is deliberately larger than the first. + * + * Every assertion runs the real detector; there is no mocking, so removing a + * rule or loosening a guard turns this suite red. + */ +import { strict as assert } from 'node:assert' +import { inspectCommand, inspectSegment, splitSubcommands } from '../lib/download-guard/detect.js' + +let checks = 0 + +/** Assert one command line is recognised as a download with the given rule. */ +function fires(command, rule, extra = {}) { + const hit = inspectCommand(command) + assert.notEqual(hit, undefined, `expected a hit for ${JSON.stringify(command)}`) + assert.equal(hit.rule, rule, `rule for ${JSON.stringify(command)}`) + for (const [key, value] of Object.entries(extra)) { + assert.equal(hit[key], value, `${key} for ${JSON.stringify(command)}`) + } + checks += 1 +} + +/** Assert one command line is left alone. */ +function allows(command) { + const hit = inspectCommand(command) + assert.equal(hit, undefined, `expected no hit for ${JSON.stringify(command)}, got ${JSON.stringify(hit)}`) + checks += 1 +} + +// --- recognised downloads --------------------------------------------------- + +fires('curl -o out.iso https://example.test/a.iso', 'curl-output', { + url: 'https://example.test/a.iso', + target: 'out.iso', +}) +fires('curl -oFILE https://example.test/a.iso', 'curl-output', { target: 'FILE' }) +fires('curl --output out.iso https://example.test/a.iso', 'curl-output', { target: 'out.iso' }) +fires('curl --output=out.iso https://example.test/a.iso', 'curl-output', { target: 'out.iso' }) +fires('curl -sSL -o out.iso https://example.test/a.iso', 'curl-output', { target: 'out.iso' }) +// A repeated -o resolves to the last one, which is the file curl writes. +fires('curl -o first.iso -o second.iso https://example.test/a.iso', 'curl-output', { target: 'second.iso' }) +fires('curl -O https://example.test/a.iso', 'curl-remote-name') +fires('curl -sSLO https://example.test/a.iso', 'curl-remote-name') +fires('curl https://example.test/a.iso > out.iso', 'redirect', { target: 'out.iso' }) +fires('curl https://example.test/a.iso >> out.iso', 'redirect', { target: 'out.iso' }) + +fires('wget https://example.test/a.iso', 'wget', { url: 'https://example.test/a.iso' }) +fires('wget -O out.iso https://example.test/a.iso', 'wget', { target: 'out.iso' }) +fires('wget --output-document=out.iso https://example.test/a.iso', 'wget', { target: 'out.iso' }) +fires('wget -q https://example.test/a.iso', 'wget') + +fires('Invoke-WebRequest -Uri https://example.test/a.iso -OutFile out.iso', 'Invoke-WebRequest', { target: 'out.iso' }) +fires('iwr https://example.test/a.iso -OutFile out.iso', 'Invoke-WebRequest', { target: 'out.iso' }) +fires('Invoke-RestMethod https://example.test/a.iso -OutFile out.iso', 'Invoke-RestMethod', { target: 'out.iso' }) +fires('Start-BitsTransfer -Source https://example.test/a.iso -Destination out.iso', 'Start-BitsTransfer', { target: 'out.iso' }) + +// A download after an unrelated command is still found, and a Windows path to +// the executable is recognised by its base name. +fires('cd /tmp && curl -o out.iso https://example.test/a.iso', 'curl-output') +fires('echo start; wget https://example.test/a.iso', 'wget') +fires('C:\\Windows\\System32\\curl.exe -o out.iso https://example.test/a.iso', 'curl-output') +fires('/usr/bin/wget https://example.test/a.iso', 'wget') +// A leading environment assignment is not the command word. +fires('FOO=1 BAR=2 wget https://example.test/a.iso', 'wget') + +// --- ordinary commands that must stay allowed ------------------------------- + +allows('ls -la') +allows('git status') +allows('npm run build') +allows('cat package.json') +allows('grep -rn TODO src/') +allows('node scripts/build.mjs') + +// curl that prints to stdout is not a download: no file receives the bytes. +allows('curl https://example.test/api') +allows('curl -s https://example.test/api') +allows('curl -I https://example.test/api') +allows('curl -X POST -d @body.json https://example.test/api') + +// The historic false positive: the word "download" in an argument proves +// nothing about whether bytes land on disk. +allows('node download.cjs --help') +allows('node download.cjs') +allows('./download.sh --dry-run') +allows('echo download') +allows('grep -rn "wget" docs/') + +// Redirecting *from* is a read, not a write. +allows('sort < input.txt') +allows('cat < input.txt > /dev/null') +allows('command > /dev/null') +allows('node tool.js 2>&1 | tee log.txt') + +// PowerShell cmdlets that return objects rather than writing files. +allows('Invoke-RestMethod https://example.test/api') +allows('Invoke-WebRequest https://example.test/api') +allows('iwr https://example.test/api') + +// A pipe into something that is not a downloader. +allows('curl https://example.test/api | jq .') +allows('wget -qO- https://example.test/api | head -5') + +// --- argument shapes -------------------------------------------------------- + +assert.deepEqual(splitSubcommands('a; b && c || d | e'), ['a', 'b', 'c', 'd', 'e']) +assert.deepEqual(splitSubcommands('echo "a;b"'), ['echo "a;b"']) +assert.deepEqual(splitSubcommands("echo 'x && y'"), ["echo 'x && y'"]) +assert.deepEqual(splitSubcommands(' \n '), []) +checks += 4 + +// The detector must not crash on the shapes a tool call can actually carry. +assert.equal(inspectCommand(''), undefined) +assert.equal(inspectCommand(undefined), undefined) +assert.equal(inspectCommand(null), undefined) +assert.equal(inspectCommand(42), undefined) +assert.equal(inspectSegment(''), undefined) +checks += 5 + +console.log(`PASS download-guard detection: ${checks} checks`) diff --git a/test/download-guard-mount.test.mjs b/test/download-guard-mount.test.mjs new file mode 100644 index 00000000..4f60b4e6 --- /dev/null +++ b/test/download-guard-mount.test.mjs @@ -0,0 +1,79 @@ +/** + * The download-guard row as the composition root actually mounts it. + * + * The sibling `download-guard-row` suite drives the component directly; this one + * goes through `apply()` with a real cordis context, so it fails if the row is + * dropped from the composition root, if the `download-guard` config section + * stops resolving, or if the enabled/disabled switch stops being honoured. + * + * Cost note: cordis only. No worker, no DSH host, no subprocess. + */ +import { Context } from '@deepseek-ai/cordis' +import { assert, test, report } from './lib/harness.mjs' +import { apply } from '../lib/index.js' + +const TEMPLATE = 'aria2c -x16 -s16 -o {file} {url}' +const CMD = 'curl -o out.iso https://example.test/a.iso' + +/** + * Boot the composition root with one row config and return a dispatcher for + * the pre-execute waterfall. + */ +async function boot(rowConfig) { + const ctx = new Context() + await apply(ctx, rowConfig) + return async (name, args) => ctx.waterfall( + 'tools/pre-execute', + { name, arguments: args }, + () => Promise.resolve({ kind: 'allow' }), + ) +} + +await test('the shipped defaults mount no refusing behaviour', async () => { + // `download-guard.enabled: false` is what the bundle patch ships; a default + // install must never refuse a download it was not told to police. + const call = await boot({}) + const decision = await call('ops_bash', { command: CMD }) + assert.equal(decision.kind, 'allow') +}) + +await test('an enabled row with a template refuses, through the whole root', async () => { + const call = await boot({ + 'download-guard': { enabled: true, downloadGuard: { template: TEMPLATE } }, + }) + const decision = await call('ops_bash', { command: CMD }) + assert.equal(decision.kind, 'deny') + assert.ok(decision.reason.includes('aria2c -x16 -s16 -o out.iso')) +}) + +await test('enabled but with an empty template stays inert', async () => { + // Enabling the row is not the same as configuring a downloader. The shipped + // patch spells "not set" as an empty string, and that must not refuse. + const call = await boot({ + 'download-guard': { enabled: true, downloadGuard: { template: '' } }, + }) + const decision = await call('ops_bash', { command: CMD }) + assert.equal(decision.kind, 'allow') +}) + +await test('a disabled row ignores a template that is present', async () => { + // An operator who filled the template and then switched the row off expects + // it off; the config value must not keep the listener alive. + const call = await boot({ + 'download-guard': { enabled: false, downloadGuard: { template: TEMPLATE } }, + }) + const decision = await call('ops_bash', { command: CMD }) + assert.equal(decision.kind, 'allow') +}) + +await test('ordinary commands pass through the enabled row', async () => { + const call = await boot({ + 'download-guard': { enabled: true, downloadGuard: { template: TEMPLATE } }, + }) + for (const command of ['ls -la', 'git status', 'curl https://example.test/api']) { + const decision = await call('ops_bash', { command }) + assert.equal(decision.kind, 'allow', `must allow ${JSON.stringify(command)}`) + } +}) + +await report('download-guard row via the composition root') diff --git a/test/download-guard-policy.test.mjs b/test/download-guard-policy.test.mjs new file mode 100644 index 00000000..248607d2 --- /dev/null +++ b/test/download-guard-policy.test.mjs @@ -0,0 +1,100 @@ +/** + * The decision layer: detection plus operator configuration becomes a refusal + * or an allow. + * + * The assertions that matter most are the ALLOW ones. A guard that refuses + * work the operator never asked to change is worse than the slow download it + * exists to prevent, so an unconfigured install, a template missing the URL + * placeholder, and a template that cannot fill the file name all have to + * resolve to "run the command". + */ +import { strict as assert } from 'node:assert' +import { decideDownload, refusalReason, renderForwarder } from '../lib/download-guard/policy.js' +import { inspectCommand } from '../lib/download-guard/detect.js' + +let checks = 0 + +const CMD = 'curl -o out.iso https://example.test/a.iso' +const HIT = inspectCommand(CMD) +const TEMPLATE = 'aria2c -x16 -s16 -o {file} {url}' + +assert.notEqual(HIT, undefined, 'precondition: the sample command is detected') + +/** Assert a decision kind. */ +function decides(hit, config, expected) { + const decision = decideDownload(hit, config, CMD) + assert.equal(decision.kind, expected, `expected ${expected}, got ${decision.kind}`) + checks += 1 +} + +// --- refuses when properly configured -------------------------------------- + +decides(HIT, { template: TEMPLATE }, 'deny') + +{ + const decision = decideDownload(HIT, { template: TEMPLATE }, CMD) + assert.equal(decision.info.code, 'DOWNLOAD_TO_FILE') + assert.equal(decision.info.name, 'DownloadGuardError') + // The refusal must carry a runnable command, not just a complaint. + assert.ok(decision.reason.includes('aria2c -x16 -s16 -o out.iso https://example.test/a.iso')) + // It must also name what was blocked, so the model can tell which call failed. + assert.ok(decision.reason.includes('out.iso')) + checks += 3 +} + +// --- allows in every ambiguous case ---------------------------------------- + +// No detection, no opinion. +decides(undefined, { template: TEMPLATE }, 'allow') +// No configuration: the operator has not opted in. +decides(HIT, undefined, 'allow') +// Blank template is the same as no configuration. +decides(HIT, { template: ' ' }, 'allow') +// A template without {url} cannot produce a runnable download command. +decides(HIT, { template: 'aria2c {file}' }, 'allow') +// No URL visible in the original command means the template cannot be filled. +decides({ rule: 'wget' }, { template: TEMPLATE }, 'allow') + +// --- rendering ------------------------------------------------------------- + +{ + const rendered = renderForwarder(TEMPLATE, HIT) + assert.equal(rendered, 'aria2c -x16 -s16 -o out.iso https://example.test/a.iso') + checks += 1 +} +{ + // No target: the file placeholder stays visible rather than being guessed. + const rendered = renderForwarder(TEMPLATE, { rule: 'curl-remote-name', url: 'https://example.test/a.iso' }) + assert.equal(rendered, 'aria2c -x16 -s16 -o {file} https://example.test/a.iso') + checks += 1 +} +{ + // Paths with spaces are quoted so the suggested command actually runs. + const rendered = renderForwarder('aria2c -o {file} {url}', { + rule: 'curl-output', + url: 'https://example.test/a.iso', + target: 'C:/Program Files/out.iso', + }) + assert.ok(rendered.includes("'C:/Program Files/out.iso'"), rendered) + checks += 1 +} +{ + // A URL containing a single quote must not break out of the quoting. + const rendered = renderForwarder('dl {url}', { rule: 'wget', url: "https://example.test/a'b" }) + assert.ok(!rendered.includes("a'b"), rendered) + checks += 1 +} + +// --- the refusal text itself ---------------------------------------------- + +{ + const longCommand = `curl -o out.iso https://example.test/${'x'.repeat(400)}` + const reason = refusalReason(HIT, TEMPLATE, longCommand) + // Long commands are truncated so the refusal stays readable. + assert.ok(reason.includes('...')) + // The rule name appears, so the model knows what pattern fired. + assert.ok(reason.includes('curl-output')) + checks += 2 +} + +console.log(`PASS download-guard policy: ${checks} checks`) diff --git a/test/download-guard-row.test.mjs b/test/download-guard-row.test.mjs new file mode 100644 index 00000000..f6a31120 --- /dev/null +++ b/test/download-guard-row.test.mjs @@ -0,0 +1,97 @@ +/** + * The download-guard row, driven through a real cordis context. + * + * This suite mounts the built component and fires the actual + * `tools/pre-execute` waterfall, so it fails if the listener is not registered, + * if the decision shape drifts from what the host expects, or if the row + * starts refusing before an operator configures a downloader. + * + * Cost note: it boots cordis only. No worker, no DSH host, no subprocess. + */ +import { Context } from '@deepseek-ai/cordis' +import { assert, test, report } from './lib/harness.mjs' +import { apply } from '../lib/components/download-guard/index.js' + +const TEMPLATE = 'aria2c -x16 -s16 -o {file} {url}' +const CMD = 'curl -o out.iso https://example.test/a.iso' + +/** + * Mount the row on a fresh context and return a function that dispatches one + * pre-execute call through the real waterfall. + */ +async function mount(config) { + const ctx = new Context() + apply(ctx, config) + return async (name, args) => ctx.waterfall( + 'tools/pre-execute', + { name, arguments: args }, + () => Promise.resolve({ kind: 'allow' }), + ) +} + +await test('an unconfigured row registers no refusing behaviour', async () => { + const call = await mount({}) + // Every download shape must pass straight through: an install that never set + // a template has not opted into a download policy. + for (const command of [CMD, 'wget https://example.test/a.iso', 'curl -o x https://example.test/x']) { + const decision = await call('ops_bash', { command }) + assert.equal(decision.kind, 'allow', `unconfigured row must allow ${JSON.stringify(command)}`) + } +}) + +await test('a configured row refuses a download and names the forwarder', async () => { + const call = await mount({ downloadGuard: { template: TEMPLATE } }) + const decision = await call('ops_bash', { command: CMD }) + assert.equal(decision.kind, 'deny') + assert.ok(decision.reason.includes('aria2c -x16 -s16 -o out.iso https://example.test/a.iso'), + 'the refusal must carry a runnable command') + assert.equal(decision.info.code, 'DOWNLOAD_TO_FILE') +}) + +await test('an ordinary command is allowed even when configured', async () => { + const call = await mount({ downloadGuard: { template: TEMPLATE } }) + for (const command of [ + 'ls -la', + 'git status', + 'npm run build', + 'curl https://example.test/api', + 'node download.cjs --help', + 'grep -rn wget docs/', + ]) { + const decision = await call('ops_bash', { command }) + assert.equal(decision.kind, 'allow', `must allow ${JSON.stringify(command)}`) + } +}) + +await test('a non-shell tool is never inspected', async () => { + const call = await mount({ downloadGuard: { template: TEMPLATE } }) + const decision = await call('ops_read_files', { command: CMD }) + assert.equal(decision.kind, 'allow', 'file tools carry no shell command') +}) + +await test('script_path is not guessed at', async () => { + const call = await mount({ downloadGuard: { template: TEMPLATE } }) + // The file's contents are not in this call; treating the path as a command + // would be a false positive. + const decision = await call('ops_bash', { script_path: 'scripts/fetch.sh' }) + assert.equal(decision.kind, 'allow') +}) + +await test('a malformed call cannot turn a refusal into a crash', async () => { + const call = await mount({ downloadGuard: { template: TEMPLATE } }) + for (const args of [undefined, null, {}, { command: 42 }, { command: '' }]) { + const decision = await call('ops_bash', args) + assert.equal(decision.kind, 'allow', `must not refuse ${JSON.stringify(args)}`) + } +}) + +await test('a template without {url} is rejected at load time', async () => { + const ctx = new Context() + assert.throws( + () => apply(ctx, { downloadGuard: { template: 'aria2c {file}' } }), + /must contain \{url\}/, + 'a template that cannot name the URL would produce an unactionable refusal', + ) +}) + +await report('download-guard row')