| id | 2607031500 |
|---|---|
| title | Security hardening batch — 2026-07-03 post-audit diff review (low) |
| status | ✅ |
| summary | Two low-severity findings from the 2026-07-03 diff review: route Workspace.ReadFile through the same OpenRoot symlink containment as its sibling FS() view (F001), and cap block-quote recursion depth in the Layer-0 parse-skip scanner before either gating spike flag ships as a supported option (F002). |
| model | sonnet |
Address the two low-severity findings from the 2026-07-03 post-audit diff review. Neither is exploitable today under default configuration, but each closes a gap that would matter if the surrounding code evolves.
F001 (low, CWE-61). OSWorkspace.ReadFile in
pkg/mdsmith/workspace.go:72-74 reads a joined path with no
containment check. OverlayWorkspace.ReadFile in
pkg/mdsmith/overlay.go:57-66 has the same gap. A symlink escaping the
root is followed, not refused.
Their sibling FS() views got an OpenRootFS fix this window. This
direct seam did not. Session.frontMatterFor in
pkg/mdsmith/session.go:431 calls the unguarded seam. It backs the
public Session.Kinds API.
F002 (low, CWE-674). tryBlockquote in
internal/lint/layer0.go:224 recurses once per block-quote nesting
level. It has no depth cap. lineHasNonFenceCode in
internal/lint/layer0_para.go:204-209 treats any nested > marker as
code-capable. That triggers the recursion.
A deeply nested quote line can exhaust the stack. Go cannot recover a
stack overflow. That would bypass this window's new panic-recovery
work. The path needs the MDSMITH_LAYER0_SKIP or
MDSMITH_SPIKE_FLAT_L0 env var. Both are unwired and undocumented.
The gap is unreachable today, but worth a fix before either ships.
- Write a failing test for
OSWorkspace.ReadFileon a within-workspace symlink pointing outsideRoot, mirroring the existingFS()-containment test inpkg/mdsmith/workspace_test.go(around line 358) — assert the read is refused, not followed. - Write the matching failing test for
OverlayWorkspace.ReadFileinpkg/mdsmith/overlay_test.go(mirroring lines 61-72'sFS()containment test). - Make
OSWorkspace.ReadFileroute through a contained read whenRootis set, instead of callingos.ReadFiledirectly, so it shareslint.OpenRootFS's containment. Keep the existing absolute-path passthrough behavior. - Make
OverlayWorkspace.ReadFile's disk fall-through do the same, routing throughw.FS()(which reuses the cacheddiskFSinternally) instead of a freshos.ReadFilecall. - Remove the now-inapplicable
//nolint:goseccomments on both methods. - Run the two new tests to confirm both go green.
- Copilot review on PR #720 flagged that routing
OSWorkspace.ReadFilethroughw.FS()callslint.OpenRootFS(a freshos.OpenRootper call) with no matchingClose, leaking one directory handle per read. AddedreadFileRooted(build-tag split acrossworkspace_fs.go/workspace_fs_tinygo.go) that opens, reads, and closes theos.Roothandle per call instead of caching anfs.FS;OverlayWorkspace.ReadFilewas unaffected since itsFS()already memoizes the handle viasync.Once. AddedTestOSWorkspaceReadFileDoesNotLeakRootHandles(Linux-only,/proc/self/fdcount) as a red/green regression test. - Follow-up Copilot review flagged that
OverlayWorkspace.ReadFile's disk fall-through still calledw.FS(), which clones the whole overlay map on every call (O(len(open buffers)) per miss) even though the cacheddiskFSnever changes. ExtractedensureDiskFS(thesync.Once-cached disk viewFS()already built) and hadReadFileread through it directly, skipping the overlay snapshot. AddedTestOverlayWorkspaceReadFileDiskFallthroughSkipsOverlaySnapshot(testing.AllocsPerRunwith 500 open buffers) as a red/green regression test, plusTestOverlayWorkspaceFSEmptyRootfor the extracted method's empty-root branch.
- Write a failing test driving
scanLayer0with a line of enough nested>markers to exceed a proposed depth cap (e.g. 100), and assert it returns gracefully (e.g. treats the excess depth as plain text) rather than recursing further. - Add a
maxBlockquoteDepthconstant nearmaxIncludeDepth's existing pattern, and thread a depth counter throughtryBlockquote's recursion intoscanLayer0, refusing to recurse past the cap. - Confirm the depth-cap test passes and existing block-quote fixtures (nested code blocks, lazy continuations) are unaffected.
- A within-workspace symlink escaping the workspace root is refused
by
OSWorkspace.ReadFileandOverlayWorkspace.ReadFile, not just theirFS()views. -
Session.Kindson a symlinked path outside the workspace root returns an error instead of reading the external file's front matter (verified at the underlyingReadFileseamfrontMatterForcalls). - A line with a depth-cap-exceeding number of nested
>markers no longer recurses unbounded inscanLayer0. - All tests pass:
go test ./... -
go tool -modfile=tools/go.mod golangci-lint runreports no issues