Skip to content

v0.5.12 — Coverage-blind check fixes + symlink write disclosure

Choose a tag to compare

@yottayoshida yottayoshida released this 09 Aug 06:17
· 30 commits to main since this release
af40455

Summary: Several checks reported healthy without covering what they claimed — an identifier collision made the wheel-asset gate blind rather than noisy, the MCP stdout-purity test only stayed green because it exercised one request shape, and the release checker's permissions block silently dropped the scope its own checkout needs. Each now establishes its coverage or fails, and a weekly dependency audit is held to the same standard. Separately, mpg setup and mpg uninstall now disclose every symlinked directory a run writes through; the traversal is unchanged and is still not confinement.

Added

  • A weekly dependency audit (.github/workflows/audit-dependencies.yml) runs uv audit over the dependencies this project actually resolves and opens an issue keyed on the advisory set — so a closed issue about old advisories cannot suppress a new one, and a repeat of the same set cannot open a duplicate. It does not run on pull requests: an advisory published against a transitive dependency has nothing to do with the change under review. The verdict is decided by scripts/check_dependency_audit.py, which fails rather than reporting "nothing found" when it cannot establish what the audit covered — a missing subcommand, an unrecognized JSON shape, or an implausibly small audited set. That guard exists because a clean verdict over the wrong corpus is exactly how this check would fail silently: while it was being designed, a bare pip-audit reported no vulnerabilities against its own dependencies rather than this project's. CONTRIBUTING documents how to run and triage it locally. (closes #165)

Fixed

  • The wheel-asset gate compared guide identifiers as bare filename stems, which is only correct while no two categories share one. Measured, the consequence runs the opposite way from what the issue anticipated: a collision does not make the gate noisy, it makes it blind. The expected set shrinks along with whatever the index dropped, so the two agree and the check passes — a guide missing from the shipped wheel would be reported as healthy. A guide moved between categories was invisible to it for the same reason. It now compares (category, id) pairs derived from the wheel's own paths, which removes the dependency on globally unique stems rather than documenting it. (closes #141)

  • The scheduled Python release checker files its follow-up issues with tier:4-extend and area:content alongside enhancement, so automatically created work lands in the same triage as everything else instead of outside the classification scheme. A test reads the --label arguments specifically; matching anywhere in the workflow text would have been satisfied by the issue body, which names the same words. Duplicate detection is unchanged. (closes #160)

  • The scheduled Python release checker declared only issues: write, and declaring permissions at all drops every scope not listed — including the contents: read its actions/checkout step needs. Public repositories allow the checkout regardless, which is why ten consecutive weekly runs succeeded and the omission never surfaced; it would have failed the moment this repository went private. Both scopes are now declared, and a test asserts the pair without admitting any other write scope. (closes #163)

  • The MCP stdout-purity test judged pollution by recomputing the expected byte count with json.dumps's default ensure_ascii=True, while the server serializes with ensure_ascii=False. Any non-ASCII character reaching stdout made the two counts diverge and failed the test with no stray output present at all — not a hypothetical, since guide_index.py composes BAD/GOOD summaries with a → and a real search_guides response measures 12 bytes "short" under the old comparison. The test only stayed green because it exercised tools/list alone. The check now judges the stream's structure directly — every line non-empty, free of surrounding whitespace, a standalone JSON-RPC object carrying a result/error/method body, and the stream terminated by a newline — so it no longer depends on the server's serialization settings, and the test additionally pins which response ids belong on stdout so that a well-formed but extra message is still caught. Falsification tests pin that every pollution shape the byte comparison used to catch still fails, and that a genuinely non-ASCII payload passes. (closes #173)

  • mpg setup and mpg uninstall now announce, once per run before writing anything, when .claude is a symlink and which directory the writes actually land in. Previously the per-file guards refused a symlinked settings.local.json but said nothing about a symlinked .claude directory, so the hook settings and the Skills/Rules symlinks all silently went to the link target. The symlink is still followed — refusing would break deliberate "config lives elsewhere" layouts — so this closes the silence, not the traversal; SECURITY.md states plainly that neither the per-file checks nor this disclosure confine the .claude tree. --mcp-only, which writes nothing under .claude, prints nothing. (closes #170)

  • The same disclosure now covers every directory a run writes through, not just .claude. A symlinked .claude/skills or .claude/rules was followed with no note at all — the case that motivated the boundary #170 had to document. mpg setup and mpg uninstall now walk each write target below the project root, report the outermost symlinked directory on the way, and de-duplicate: a symlinked .claude still yields one note covering all three targets, while skills and rules pointing at different trees yield two, because there genuinely are two destinations. The final path component is skipped — those are mpg's own symlinks, so including them would make a second mpg setup announce mpg's own links back. The write targets are supplied by the callers rather than duplicated in the settings module, so there is no second copy of the layout to drift, and a source scan pins that no fourth target has appeared unannounced. Traversal is still not refused, and SECURITY.md still says the disclosure is not confinement. (closes #192)