Skip to content

fix(security): sanitizer relaxes denylists by file extension only; Markdown with inline code passes (#32) - #73

Open
minerva-sky wants to merge 2 commits into
codenamev:mainfrom
minerva-sky:fix/sanitizer-scope-by-type
Open

minerva-sky wants to merge 2 commits into
codenamev:mainfrom
minerva-sky:fix/sanitizer-scope-by-type

Conversation

@minerva-sky

Copy link
Copy Markdown
Collaborator

Closes #32.

Security::Sanitizer.sanitize_file_content ran one union denylist over every artifact, so a README with a code span raised SecurityError, and because the raise is SecurityError it took the task down with it. .md is in Workspace::ALLOWED_EXTENSIONS and a README is the most likely thing an LLM file task produces, so the check was mostly rejecting the artifacts it was supposed to protect.

What changes

  • Relaxation is keyed on the file extension and nothing else. sanitize_file_content gains name: nil; .md and .txt skip the command- and code-injection lists, .sql skips the SQL list. Every other extension, extension-less files (Rakefile, Gemfile), and every call without a name run every list exactly as on main. The encoding check and the file-system list apply to all.
  • The declared type never relaxes anything. It comes straight from the model's JSON (FileGenerationCapability reads it), so {"name":"build.rb","type":"markdown"} is rejected as before, and so is a bare sanitize_file_content(content, :markdown) from any caller.
  • YAML and JSON keep every check: they are config that tools execute (ERB in database.yml, a workflow run: step, a package.json script).
  • Prose keeps one command check, !`cmd`: agent harnesses expand that in .claude/commands/*.md and SKILL.md at prompt time, so Markdown is not entirely inert.
  • Workspace#validate_artifact passes artifact.name (its name regex and extension allowlist already run first).
  • Specs: 14 examples covering the relaxations, the type spoof on .rb/.sh/Rakefile, the nameless path, the three config payloads, the two Markdown-directive payloads, and two Workspace end-to-end cases.

Tests: sanitizer + workspace specs 82/82; full suite 1404 examples with the one known event_dispatcher_integration_spec.rb:291 wall-clock flake that #36 de-flakes.

Follow-up already open as #67: Workspace#verify_artifact picks its verification strategy from the declared type by the same hole.

Intended labels: loop:drain, loop:security.

Panel

  • DHH: cleared (3/3). Frozen constants, one lookup, one fetch; nit that the Workspace examples belong in workspace_spec.rb.
  • Obie Fernandez: objected-then-cleared. Round 1 (3 seats): 2/3 objected on LLM walk-through holes: a call without name: still let the declared type skip the command lists, and .md is executable in agent harnesses via !`cmd` command and skill files. Both fixed in 6a58c04 (extension-only relaxation; prose directive check). Round 2 (3 seats): cleared, with the note that .txt is not fully inert either (CMakeLists.txt, requirements.txt), judged non-blocking because main never caught those payloads without backticks.
  • Vladimir Dementyev: cleared (3/3). Pure computation over frozen constants, every branch reached through the public method, no new flake vector.
  • The gem consumer: cleared (3/3). Additive keyword, strictly monotone loosening, nothing 0.2.0 accepted is rejected after upgrade; CHANGELOG wording tightened on their nit.

Earlier rounds (2026-10-14, recorded on #32): Obie objected on the type-spoof and on relaxed YAML/JSON; both were fixed on this branch before this session's panel.

🤖 Generated with Claude Code

minerva-sky and others added 2 commits October 14, 2026 21:54
…enamev#32)

Sanitizer.sanitize_file_content ran the command, code, SQL, and
file-system denylists over every artifact type, so a Markdown README
with an inline code span was rejected as command injection and the
SecurityError took the task down with it. Prose and data artifacts
(:markdown, :text, :json, :yaml) now skip the command- and
code-injection lists, :sql artifacts skip the SQL list. Code artifacts
and unknown types keep every check; the encoding and file-system checks
apply to all.

Closes codenamev#32

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…rective check for prose (codenamev#32)

Panel round 3 (Obie Fernandez, 2/3) found two holes in the type-scoped
version: a caller without a name: still let the model-declared type skip
the command lists, and .md is not inert when an agent harness expands
!`cmd` in command or skill files. Now only a known extension relaxes
anything (.md/.txt skip command+code lists, .sql skips the SQL list),
prose keeps a check for executable Markdown directives, and every call
without a name runs every list exactly as before.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

loop:drain Implemented from the improvements ledger by the drain loop loop:security Security analysis loop

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Sanitizer rejects any Markdown artifact with inline code as command injection

1 participant