/** * Recurrence gate for celilo#1430: dns-int logged `internal: command not found` * on every boot. * * The unbound config was written through an UNQUOTED heredoc, so a backtick in * a comment ran as a command and the split-horizon block was passed as * printf's FORMAT STRING. Both are silent: unbound still started, so the only * symptom was noise in the middle of the resolver's readiness path. * * The check runs the real script's config-rendering half in bash and asserts * nothing reaches stderr — the one signal a shell-injected comment produces. */ import { afterAll, describe, expect, test } from 'bun:test'; import { spawnSync } from 'node:child_process'; import { mkdtempSync, readFileSync, rmSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; /** Scratch dirs removed at exit: the CI runner's PrivateTmp is never reclaimed (celilo#1469). */ const scratchDirs: string[] = []; function scratch(dir: string): string { scratchDirs.push(dir); return dir; } afterAll(() => { for (const dir of scratchDirs) rmSync(dir, { recursive: true, force: true }); }); const SCRIPT = join(import.meta.dir, '../config/routing/resolver-internal-routes.sh'); /** Run the script with `ip`/`unbound` stubbed and the config written to stdout. */ function render(domains: string): { config: string; stderr: string } { const harness = ` PATH="$PWD/.stub:$PATH" mkdir -p .stub printf '#!/bin/sh\\nexit 0\\n' > .stub/ip printf '#!/bin/sh\\nexit 0\\n' > .stub/unbound printf '#!/bin/sh\\nexit 0\\n' > .stub/sleep chmod +x .stub/ip .stub/unbound .stub/sleep mkdir -p etc/unbound sed -e 's#/etc/unbound/unbound.conf#etc/unbound/unbound.conf#' \ -e 's#/usr/sbin/unbound#unbound#' ${JSON.stringify(SCRIPT)} > run.sh bash run.sh > /dev/null cat etc/unbound/unbound.conf `; const r = spawnSync('bash', ['-c', harness], { cwd: scratch(mkdtempSync(join(tmpdir(), 'celilo-1430-'))), encoding: 'utf8', env: { ...process.env, DOMAINS: domains }, }); return { config: r.stdout ?? '', stderr: r.stderr ?? '' }; } describe('resolver-internal-routes.sh', () => { test('renders a multi-domain config with nothing on stderr', () => { const { config, stderr } = render('iamtheinternet.org,example.net'); expect(stderr).toBe(''); expect(config).toContain('local-zone: "iamtheinternet.org." redirect'); expect(config).toContain('local-zone: "example.net." redirect'); expect(config).not.toContain('\\n'); }); test('a single domain renders the same shape', () => { const { config, stderr } = render('iamtheinternet.org'); expect(stderr).toBe(''); expect(config).toContain('local-zone: "iamtheinternet.org." redirect'); expect(config).not.toContain('example.net'); }); test('the config heredocs are quoted, so a comment can never run', () => { const src = readFileSync(SCRIPT, 'utf8'); expect(src).not.toMatch(/<<\s*EOF/); }); });