Skip to content

Threat model: review and close open attack surfaces before v0.9 RC #37

@tig

Description

@tig

Context

docs/threat-model.md was published with v0.5 (PR #17). It covers the attack surface at a sketch level — sufficient to tick the v0.5 exit criterion, not sufficient for a v0.9 RC sign-off. This issue tracked the original review work; concrete attack-path items have been spun out into their own issues. What remains here is housekeeping + the meta task of confirming the threat model reads cleanly cold.

Status of original items (verified 2026-05-06)

1. Input-size caps (#BR-8) — DONE (#40 closed)

  • Cap --initial at 64 KiB. Implemented in CommandLineRoot.cs line 138 with code: "input-too-large", exit 65.
  • Cap clet md stdin at 8 MiB. Implemented in MarkdownClet.cs line 80.
  • Documented in specs/clet-spec.md §4.7 (line 285) and Appendix A (line 469).
  • Smoke test in tests/Clet.SmokeTests/CletSmokeTests.cs (lines 104, 117) and unit tests in CommandLineRootTests.cs (lines 302, 316, 334) and ExitCodesTests.cs line 23.

2. Link safety verification (clet md) — moved to #91

Code mitigation is in place (MarkdownClet.cs lines 130–134, e.Handled = true, no Process.Start anywhere in src/). Missing integration test and threat-model doc update tracked in #91.

3. Terminal escape filter — moved to #92

Threat-model claim is aspirational; no filter exists in the code. Concrete attack paths and remediation tracked in #92.

4. File access boundary — moved to #93

Two surfaces (clet md as file-read primitive; pick-* --root not actually confining) detailed and tracked in #93.

5. Plugin loading — DONE (confirmed)

  • Assembly.Load* / AppDomain / LoadAssembly — no matches anywhere in src/. Confirmed 2026-05-06. Threat model claim holds.

What remains in this issue

Housekeeping (low severity, no concrete attacker)

  • --output follows symlinks and truncates on error. src/Clet/Hosting/OutputFormatter.cs opens new StreamWriter(outputPath, append: false) which follows symlinks and unconditionally truncates. Even when the clet result is error, the file is created/clobbered. Consider FileMode.CreateNew (or explicit overwrite confirmation) and skipping the file write on non-success status. Low severity in the trust model (CLI args are user-controlled), but the silent clobber on error path is a footgun.
  • Catch-all --<unknown> parser swallows the next token. src/Clet/Hosting/CommandLineRoot.cs ~lines 197-209 accept any unknown --foo and consume the next token as its value. So clet text --initial --output /tmp/x makes --initial equal the literal string --output and /tmp/x becomes a positional arg. Should validate cletOptions keys against the dispatched clet's Options and reject unknown options with UsageError (exit 2). Mostly UX / agent-safety, no concrete security attack path.
  • --initial size cap runs before alias resolution. CommandLineRoot.cs ~lines 132–146 emit a BoxedCletResult error before the alias is checked. This conflates parsing errors (should be exit 2) with runtime errors. Reorder: resolve alias first, then bound-check.

Threat-model doc cleanup (depends on #91 / #92 / #93 outcomes)

Meta — close-out criteria

A "reviewed" threat model means:

  • Every attack surface in Appendix A has a corresponding test or a documented "won't fix / accepted risk" entry.
  • A second set of eyes (another maintainer or a bar-raise pass) has read the document cold and found no unnamed surfaces.

This meta task should be the last box to tick on this issue, after #88 / #91 / #92 / #93 land.

  • Cold read-through of docs/threat-model.md by a second maintainer; surfaces any gaps as a follow-up or signs off.
  • Spec Appendix A re-synced with the doc after the cold read.

Out of scope (deliberately)

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions