maintainability-agent docs
Audits and registers

Security queue — 2026-08-23

Two independent audits of 897c3d7 / 20b2460, one numbered S and one numbered C, against seams three rounds of chat-surface auditing had never looked at: write primitives, read boundaries, git argv, supply chain, and analyzer trust.

Register entries are the record; this page is the working order. It exists because the audits arrived as prose and the operator left for a flight — a queue nobody can read is a queue that gets reordered by whoever touches it last.

The one decision that was not mine — answered 2026-08-25

Are repositories trusted? Both audits converge here (S R1, C 2), and it is genuinely product intent, so it stayed open until it was decided on 2026-08-25. The ruling is below; the two options are kept because a decision that lists only the chosen path is a rationalization.

Today the agent runs eslint such that it requires and executes the audited tree's own configuration, and mypy and pylint can load configured plugins. SECURITY.md says the agent does not execute scanned code. Both cannot be true.

Two coherent answers:

  1. Repositories are trusted. Correct SECURITY.md, the MCP annotations, and the architecture docs to say so plainly. Cheap, honest, and narrows who can safely run the tool.
  2. Repositories are untrusted. Invoke analyzers with agent-owned configuration, --no-eslintrc / --no-plugins, and a scrubbed environment. Costs fidelity — a repository's own lint rules stop being what gets reported — and is a real product change.

Child sandboxing stays refused and neither option reopens it.

Answered: neither, because the question is moot. The line is drawn at executing code rather than at trusting repositories — see Decision 9. The agent does not run the audited tree's code, and its configuration is code. Option 2's mechanism is what will ship (agent-owned configuration, plugin loading disabled, scrubbed environment); option 2's framing is not, because the guarantee is not "we distrust you" but "we never run it".

That mechanism now exists (D39, closed 2026-08-25). pylint is spawned with --rcfile, mypy with --config-file, eslint is refused by selection rather than run, and the analyzer child's environment has the code-loading variables removed. An earlier revision of this paragraph said the mechanism shipped while none of it had been written; it is kept here, corrected, rather than deleted.

Most of the exposure leaves by scope rather than by engineering: eslint is the analyzer that requires the tree's own flat config, and JavaScript is not a v1.0 language (Decision 10). What remains is mypy and pylint, which are Python and therefore in scope, and which merely may load configured plugins — they run with that disabled.

Order of work

Severity first, then blast radius. Everything here is decidable from documents that already exist — a stated promise the code contradicts — so none of it needs a ruling.

# Entry What State
1 D34 Config, history and baseline writes open by name; hardlink and symlink escape closed
2 D36 class_dirs, expand_files and the scan itself follow paths out of the root closed
3 D35 Repository config can enable tool acquisition against P1 closed
4 D40 Repository-configured regex is a trivial ReDoS closed
5 D42 requires-python claims 3.10; StrEnum needs 3.11 closed
6 D43 Composite action interpolates inputs into Bash closed
7 D41 Release authority rides mutable third-party Action tags closed
8 D45 SECURITY.md supports 0.1.x; the package is 0.9.1 closed
9 D37 CLI passes git options the MCP door rejects; no timeouts; swallowed errors closed
10 D38 A standing grant follows a renamed directory after restart closed
11 D46 XML parsers unbounded against analyzer output closed
12 D44 MCP annotations contradict behaviour closed
13 D39 Analyzer configuration executes closed

Why green CI missed almost all of it

The C audit's sharpest observation, and it deserves recording separately from any one defect: the security tests prove the package imports no HTTP client, while the package deliberately launches repository-programmable children holding the host environment. The abstraction under test was "does our code reach the network", when the risk was "what do we hand the tree's own code".

Every fix below ships a falsifier. Several ship a lint, because the defect classes here — open-by-name writes, unbounded configured paths — were each closed once already at one seam and left open at the others.

Follow-up that is not a defect

D42 raised the Python floor to 3.11. The honest check is a CI matrix entry that installs and imports on the floor version, because the defect was that nothing ever ran there — a test asserting the metadata matches the language features in use would restate a constant. That is a pipeline change and belongs to whoever next touches CI.