Skip to content

fix: prevent Squid config injection via --allow-domains and --allow-urls#1517

Merged
lpcox merged 4 commits intomainfrom
fix/squid-config-injection
Apr 2, 2026
Merged

fix: prevent Squid config injection via --allow-domains and --allow-urls#1517
lpcox merged 4 commits intomainfrom
fix/squid-config-injection

Conversation

@Mossaka
Copy link
Copy Markdown
Collaborator

@Mossaka Mossaka commented Mar 31, 2026

Summary

  • Vulnerability: Malicious input to --allow-domains or --allow-urls containing whitespace, newlines, null bytes, hash characters (#), semicolons, or quotes could inject arbitrary Squid config directives (e.g., http_access allow all) or truncate ACL lines (via # comment injection) in the generated squid.conf, bypassing all domain filtering
  • Primary fix: Added character validation in validateDomainOrPattern() that rejects whitespace, null bytes, quotes, semicolons, backslashes, and hash characters — all of which are Squid config delimiters or metacharacters
  • Shared constant: Extracted SQUID_DANGEROUS_CHARS from domain-patterns.ts covering whitespace, null bytes, #, ;, quotes, and backticks — used consistently across all three validation sites. Backslash is intentionally excluded from this shared constant because URL regex patterns passed to --allow-urls legitimately use \ for regex escaping; domain names additionally reject \ via a local check
  • Defense-in-depth: assertSafeForSquidConfig() in squid-config.ts now uses SQUID_DANGEROUS_CHARS (expanded from the previous [\s\0] subset) and is applied to user-provided values only — internally-generated regex patterns derived from already-validated domain names are trusted and not re-checked
  • URL patterns: Upgraded --allow-urls validation in cli.ts from [\s\0] to SQUID_DANGEROUS_CHARS, now catching #, ;, and quote injection

Changes

File Change
src/domain-patterns.ts Exported SQUID_DANGEROUS_CHARS constant; added dangerous character rejection (including \) in validateDomainOrPattern()
src/squid-config.ts Added assertSafeForSquidConfig() helper using SQUID_DANGEROUS_CHARS; applied to user-provided interpolation points; removed from internally-generated regex patterns
src/cli.ts Upgraded --allow-urls validation to use SQUID_DANGEROUS_CHARS (catches #, ;, quotes)
src/domain-patterns.test.ts Tests for injection chars (LF, CR, null, tab, space, semicolon, etc.) and underscore DNS names
src/squid-config.test.ts Defense-in-depth tests for newline, space, #, and ; injection in config generation

Test plan

  • All 1228 existing unit tests pass
  • Lint passes with 0 errors
  • New tests verify rejection of: LF, CR, CRLF, null bytes, tabs, interior spaces, semicolons, hash chars, backslashes, single/double quotes
  • New tests verify acceptance of underscore DNS names (_dmarc.example.com, _acme-challenge.example.com)
  • New tests verify defense-in-depth catches # and ; injection at squid-config level via URL patterns
  • CI integration tests

🤖 Generated with Claude Code

Malicious input to --allow-domains or --allow-urls containing
whitespace, newlines, or null bytes could inject arbitrary
directives into the generated squid.conf. This adds:

1. Character validation in validateDomainOrPattern() rejects
   whitespace, null bytes, quotes, semicolons, backslashes
2. assertSafeForSquidConfig() in squid-config.ts validates
   every value before interpolation into squid.conf
3. URL pattern validation in cli.ts for --allow-urls

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 31, 2026 17:24
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 31, 2026

✅ Coverage Check Passed

Overall Coverage

Metric Base PR Delta
Lines 82.67% 85.91% 📈 +3.24%
Statements 82.34% 85.79% 📈 +3.45%
Functions 81.22% 86.71% 📈 +5.49%
Branches 75.94% 78.55% 📈 +2.61%
📁 Per-file Coverage Changes (6 files)
File Lines (Before → After) Statements (Before → After)
src/cli.ts 61.1% → 60.6% (-0.51%) 61.5% → 61.1% (-0.45%)
src/host-iptables.ts 91.9% → 91.9% (+0.04%) 91.6% → 91.6% (+0.04%)
src/squid-config.ts 96.7% → 96.7% (+0.08%) 96.7% → 96.8% (+0.08%)
src/domain-patterns.ts 97.4% → 97.6% (+0.25%) 97.4% → 97.7% (+0.24%)
src/docker-manager.ts 85.8% → 86.5% (+0.68%) 85.3% → 86.0% (+0.68%)
src/commands/logs-audit.ts 0.0% → 100.0% (+100.00%) 0.0% → 100.0% (+100.00%)
✨ New Files (1 files)
  • src/dns-resolver.ts: 100.0% lines

Coverage comparison generated by scripts/ci/compare-coverage.ts

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR hardens the generation of squid.conf against config-injection attacks by validating/guarding user-supplied allowlist inputs (--allow-domains and --allow-urls) before they are interpolated into Squid directives.

Changes:

  • Rejects dangerous characters in validateDomainOrPattern() to prevent directive/token injection via --allow-domains.
  • Adds a defense-in-depth assertSafeForSquidConfig() guard around Squid config interpolation points for domains/patterns/URL patterns.
  • Adds CLI validation for --allow-urls (whitespace / null bytes) and extends unit tests to cover injection attempts.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/domain-patterns.ts Adds rejection of whitespace/control/meta characters in domain/pattern inputs to prevent Squid config injection.
src/squid-config.ts Introduces assertSafeForSquidConfig() and applies it at domain/pattern/url interpolation points in generated squid.conf.
src/cli.ts Adds URL pattern validation to block whitespace/null bytes before config generation.
src/domain-patterns.test.ts Adds unit tests for rejected injection characters and acceptance of underscore-containing names.
src/squid-config.test.ts Adds defense-in-depth tests ensuring config generation rejects injected newline/space payloads.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +161 to +167
const charCode = match[0].charCodeAt(0);
const charDesc = charCode <= 0x20 || charCode === 0x7f
? `U+${charCode.toString(16).padStart(4, '0')}`
: `'${match[0]}'`;
throw new Error(
`Invalid domain '${trimmed}': contains invalid character ${charDesc}. ` +
`Domain names must not contain whitespace, quotes, semicolons, backslashes, or control characters.`
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The thrown error message interpolates trimmed directly (e.g., Invalid domain '${trimmed}'), which can include the very control characters you’re rejecting (LF/CR/Unicode whitespace). This can lead to multi-line / forged log output when the CLI logs error.message. Consider rendering the rejected input via JSON.stringify(trimmed) (and/or escaping) and also make the human-readable list of rejected characters accurate (the regex currently also rejects # and backticks).

Suggested change
const charCode = match[0].charCodeAt(0);
const charDesc = charCode <= 0x20 || charCode === 0x7f
? `U+${charCode.toString(16).padStart(4, '0')}`
: `'${match[0]}'`;
throw new Error(
`Invalid domain '${trimmed}': contains invalid character ${charDesc}. ` +
`Domain names must not contain whitespace, quotes, semicolons, backslashes, or control characters.`
const safeDomainForMessage = JSON.stringify(trimmed);
const charCode = match[0].charCodeAt(0);
const charDesc = charCode <= 0x20 || charCode === 0x7f
? `U+${charCode.toString(16).padStart(4, '0')}`
: `'${match[0]}'`;
throw new Error(
`Invalid domain ${safeDomainForMessage}: contains invalid character ${charDesc}. ` +
`Domain names must not contain whitespace, quotes, semicolons, backticks, hash characters, backslashes, or control characters.`

Copilot uses AI. Check for mistakes.
@github-actions

This comment has been minimized.

@github-actions
Copy link
Copy Markdown
Contributor

Smoke test results for @Mossaka's PR:

✅ GitHub MCP — Last 2 merged PRs: #1508: fix: copy get-claude-key.sh to chroot-accessible path, #1498: [WIP] Fix failing GitHub Actions workflow Audit Main Package
✅ Playwright — github.com title contains "GitHub"
✅ File write — /tmp/gh-aw/agent/smoke-test-copilot-23810570029.txt created and verified
✅ Bash tool — file read back successfully

Overall: PASS

📰 BREAKING: Report filed by Smoke Copilot for issue #1517

@github-actions
Copy link
Copy Markdown
Contributor

Smoke test results (run 23810570009)

Overall: PASS

💥 [THE END] — Illustrated by Smoke Claude for issue #1517

@github-actions
Copy link
Copy Markdown
Contributor

Chroot Version Comparison Results

Runtime Host Version Chroot Version Match?
Python Python 3.12.13 Python 3.12.3 ❌ NO
Node.js v24.14.0 v20.20.1 ❌ NO
Go go1.22.12 go1.22.12 ✅ YES

Result: ❌ Not all tests passed — Python and Node.js versions differ between host and chroot environments.

Tested by Smoke Chroot for issue #1517

@github-actions github-actions bot mentioned this pull request Mar 31, 2026
@github-actions

This comment has been minimized.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

Smoke Test Results

GitHub MCP: #1508 fix: copy get-claude-key.sh to chroot-accessible path | #1469 feat: add volume mount for ~/.copilot/session-state to persist events.jsonl
Playwright: GitHub page title contains "GitHub"
File Write: /tmp/gh-aw/agent/smoke-test-claude-23811674281.txt created
Bash: File verified via cat

Overall: PASS

💥 [THE END] — Illustrated by Smoke Claude for issue #1517

@github-actions
Copy link
Copy Markdown
Contributor

Smoke Test Results — Copilot Engine

Test Result
GitHub MCP (last 2 merged PRs) #1508 "fix: copy get-claude-key.sh to chroot-accessible path", #1498 "[WIP] Fix failing GitHub Actions workflow Audit Main Package"
Playwright (github.com title check) ✅ Title contains "GitHub"
File write /tmp/gh-aw/agent/smoke-test-copilot-23811674283.txt ✅ Created
Bash cat verification ✅ Content confirmed

Overall: PASS

PR author: @Mossaka | Assignees: none

📰 BREAKING: Report filed by Smoke Copilot for issue #1517

@github-actions
Copy link
Copy Markdown
Contributor

Chroot Version Comparison Results

Runtime Host Version Chroot Version Match?
Python Python 3.12.13 Python 3.12.3 ❌ NO
Node.js v24.14.0 v20.20.1 ❌ NO
Go go1.22.12 go1.22.12 ✅ YES

Overall: Tests did not fully pass — Python and Node.js versions differ between host and chroot environments. Go versions match.

Tested by Smoke Chroot for issue #1517

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@Mossaka
Copy link
Copy Markdown
Collaborator Author

Mossaka commented Mar 31, 2026

Applied Copilot's suggestion in 86db03b: using JSON.stringify(trimmed) to safely render the domain in the error message (preventing log injection from control characters) and updated the human-readable list to include backticks and hash characters.

Copy link
Copy Markdown
Collaborator

@lpcox lpcox left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: Squid Config Injection Fix

Nice work on the layered defense approach — validateDomainOrPattern() as primary validation + assertSafeForSquidConfig() as defense-in-depth is the right pattern.

🔴 Issue: Inconsistent character validation between domains and URLs

The DANGEROUS_CHARS regex in validateDomainOrPattern() is comprehensive:

const DANGEROUS_CHARS = /[\s\0"'`;#\\]/;

But both the URL validation in cli.ts and the defense-in-depth assertSafeForSquidConfig() only check a subset:

// cli.ts:1643 - URL validation
if (/[\s\0]/.test(url)) {

// squid-config.ts:61 - defense-in-depth
if (/[\s\0]/.test(value)) {

This means URL patterns can still contain # (Squid comment character), ;, quotes, backticks, and backslashes. For example, a # in a URL pattern would cause everything after it to be treated as a comment in the generated Squid config.

Fix: Both should use the same DANGEROUS_CHARS set. Consider extracting it as a shared constant:

// Shared constant (export from domain-patterns.ts)
export const SQUID_DANGEROUS_CHARS = /[\s\0"'`;#\\]/;

Then use it in all three locations: validateDomainOrPattern(), assertSafeForSquidConfig(), and the cli.ts URL validation.

The whole point of defense-in-depth is catching what the primary validation misses — so assertSafeForSquidConfig() should validate the full dangerous character set.

✅ What looks good

  • All interpolation points in squid-config.ts are coveredformatDomainForSquid() and every url_regex / dstdom_regex ACL line uses assertSafeForSquidConfig()
  • DNS servers and ports are validated through separate paths (IP parsing, integer range checks) and are not vulnerable
  • Test coverage is thorough — injection chars, underscore DNS names, and defense-in-depth tests all present
  • Error messages use JSON.stringify() to safely display the malicious input without risk of terminal escape sequences

@lpcox
Copy link
Copy Markdown
Collaborator

lpcox commented Apr 2, 2026

@copilot update the PR based on this review #1517 (review)

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 2, 2026

Review: Squid Config Injection Fix

Nice work on the layered defense approach — validateDomainOrPattern() as primary validation + assertSafeForSquidConfig() as defense-in-depth is the right p...

Done in 6c22710 and 441af68.

  • Exported SQUID_DANGEROUS_CHARS = /[\s\0"';#]/fromdomain-patterns.ts— covers whitespace, null bytes,#(comment char),;, quotes, backticks. Backslash is intentionally excluded since URL regex patterns passed to --allow-urlslegitimately use` for escaping (e.g., \\. or [^\\s]).
  • validateDomainOrPattern() uses a local DOMAIN_DANGEROUS_CHARS = /[\s\0"';#\]/that additionally rejects` (domain names never have backslashes).
  • assertSafeForSquidConfig() now uses SQUID_DANGEROUS_CHARS — expanded from [\s\0] to also catch #, ;, quotes.
  • URL validation in cli.ts upgraded from [\s\0] to SQUID_DANGEROUS_CHARS.
  • Removed assertSafeForSquidConfig() from internally-generated regex patterns (p.regex in dstdom_regex ACLs) — those are derived from already-validated domain names and legitimately contain \; applying the guard there caused false positives on patterns like ^api-[a-zA-Z0-9.-]*\\.example\\.com$.
  • Added defense-in-depth tests for # and ; injection via URL patterns.

@copilot update the PR based on this review #1517 (review)

Updated in 6c22710 and 441af68. See the reply above for details on the changes made.

Copilot AI changed the title fix: prevent Squid config injection via --allow-domains fix: prevent Squid config injection via --allow-domains and --allow-urls Apr 2, 2026
Copilot AI requested a review from lpcox April 2, 2026 17:16
Copilot finished work on behalf of lpcox April 2, 2026 17:16
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 2, 2026

🏗️ Build Test Suite Results

Ecosystem Project Build/Install Tests Status
Bun elysia 1/1 passed ✅ PASS
Bun hono 1/1 passed ✅ PASS
C++ fmt N/A ✅ PASS
C++ json N/A ✅ PASS
Deno oak N/A 1/1 passed ✅ PASS
Deno std N/A 1/1 passed ✅ PASS
.NET hello-world N/A ✅ PASS
.NET json-parse N/A ✅ PASS
Go color 1/1 passed ✅ PASS
Go env 1/1 passed ✅ PASS
Go uuid 1/1 passed ✅ PASS
Java gson 1/1 passed ✅ PASS
Java caffeine 1/1 passed ✅ PASS
Node.js clsx All passed ✅ PASS
Node.js execa All passed ✅ PASS
Node.js p-limit All passed ✅ PASS
Rust fd 1/1 passed ✅ PASS
Rust zoxide 1/1 passed ✅ PASS

Overall: 8/8 ecosystems passed — ✅ PASS

Generated by Build Test Suite for issue #1517 ·

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 2, 2026

Smoke test summary:

  • PR titles: "feat: add Daily Claude Token Usage Analyzer workflow"; "fix: extract OpenAI/Copilot cached_tokens from prompt_tokens_details"
  • GitHub MCP merged PR review: ✅
  • safeinputs-gh PR query: ❌ (tool unavailable)
  • Playwright title contains "GitHub": ✅
  • Tavily search result returned: ❌ (tool unavailable)
  • File write + bash cat verify: ✅
  • Build (npm ci && npm run build): ✅
    Overall status: FAIL

🔮 The oracle has spoken through Smoke Codex

@lpcox lpcox merged commit c990c6b into main Apr 2, 2026
63 of 66 checks passed
@lpcox lpcox deleted the fix/squid-config-injection branch April 2, 2026 17:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants