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
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions