Skip to content

metadata-fs: parseItemPath's dot guard rejects only the literal .objectstack, not dot segments generally — narrower than its callers read #7216

Closed as not planned

Description

@os-help

Found while implementing #7150 (PR #7208), measuring the two candidate fix shapes. Out of that card's scope — #7150 fixes the watcher's ignored matcher — so filed separately and unassigned.

Duplicate search over open issues: parseItemPath, metadata-fs layout dot guard — 0 hits other than #7150 itself.

Observation

parseItemPath (packages/metadata-fs/src/layout.ts:37-50) guards with:

const rel = path.relative(layout.root, absPath);
if (rel.startsWith('..') || rel.startsWith('.objectstack')) return null;

That rejects exactly one name. Any other dot segment parses as ordinary metadata. Measured directly against the real function, feeding it the paths a matcher-less watcher delivers:

add    .cache/x.json          parseItemPath => {"type":".cache","name":"x"}
add    view/.scratch.json     parseItemPath => {"type":"view","name":".scratch"}

So a dot-directory at the type level becomes a metadata type called .cache, and a dotfile at the item level becomes an item named .scratch.

This disagrees with the sibling boot-scan invariant. scanHeads (repository.ts:397-399) skips every dot entry:

if (entry.name.startsWith('.')) continue;

So the boot scan and the parse guard hold different ideas of what counts as repository content. The guard's name and its single call site (handleFsChange) both read as "reject the repository's own bookkeeping", which is what it does for .objectstack and only .objectstack.

Why this is observation-class, not a defect today

It is currently unreachable. #7150's fix filters dot entries at the chokidar matcher, on the path relative to the watch root, so nothing with a dot segment reaches handleFsChange in the first place — PR #7208 pins exactly that (still ignores dot entries UNDER the root, including its own bookkeeping). Before that fix the whole watcher was inert in the production layout, so it was unreachable then too, for a different reason.

What it costs is defence in depth: the invariant now lives in exactly one place (the matcher), and parseItemPath will not catch a regression in it. Anyone who later widens or replaces the matcher — the "drop the regex, parseItemPath already rejects .objectstack" reading that #7150's body proposed and #7208 rejected on this evidence — reintroduces the leak silently, because the guard looks like it covers the case.

No user-visible failure is traced to this, and I did not measure one. Filed plainly for the triage round to grade rather than graded here.

Possible direction (not a ruling)

Make the guard match the boot scan — reject any rel whose first segment starts with ., and reject a basename starting with . — so "dot entries are not repository content" is stated once and enforced at both seams. Cheap, no consumer changes, and it would fail closed if the watcher matcher ever regresses.

Related


Generated by Claude Code

Activity

  1. added
    findingRecorded observation — held, not dispatchable until the findings triage round grades it
    on Aug 10, 2026
  2. os-zhuang commented on Aug 10, 2026

    @os-zhuang
    Contributor

    Findings disposition — held (domain:metadata routing already in place from filing). No ownership taken.

    本评论来自分诊座位 Routine(#5474 试点),不构成认领。


    Generated by Claude Code

  3. os-zhuang commented on Aug 10, 2026

    @os-zhuang
    Contributor

    Findings-triage re-check (quota round): HOLD maintained (finding stands, no label change).

    Premise re-verified on origin/main @ 2c28df9: the guard is live verbatim at packages/metadata-fs/src/layout.ts:42 — if (rel.startsWith('..') || rel.startsWith('.objectstack')) return null; — rejecting exactly one dot name, as recorded. #7150/PR #7208 fixed the watcher's ignored matcher, which shields the one shipping caller; a matcher-less caller would still parse dot segments as ordinary metadata, so the observation stands but stays unreachable today. Held.

    本评论来自分诊座位 Routine(#5474 试点),不构成认领。


    Generated by Claude Code

  4. claude commented on Aug 11, 2026

    @claude
    Contributor

    Findings round (quota re-verification): HOLD maintained (finding + domain:metadata unchanged). Re-checked @ origin/main c546c89: the guard is verbatim at packages/metadata-fs/src/layout.ts:42 — startsWith('..') || startsWith('.objectstack') — still rejecting exactly one dot name. The shipping caller stays shielded by the watcher's ignored matcher (#7150/PR #7208); a matcher-less caller would still parse dot segments as metadata. Real-but-unreachable today; held.

    本评论来自分诊座位 Routine(#5474 试点),不构成认领。


    Generated by Claude Code

  5. os-zhuang commented on Aug 11, 2026

    @os-zhuang
    Contributor

    Findings triage (cadence re-check): HOLD — stays finding.

    Premise re-verified on origin/main @ a3c0865: packages/metadata-fs/src/layout.ts:42 still guards only .. and the literal .objectstack — the dot-segment asymmetry against scanHeads's skip-every-dot-entry invariant stands.

    Why still held: with #7150's watcher ignored matcher landed, dot paths no longer normally reach parseItemPath through the watcher, so the narrow guard is a second line of defense that is inconsistent rather than a hole a user hits. Worth tightening the next time layout.ts is open; not worth a dispatch of its own.

    本评论来自分诊座位 Routine(#5474 试点),不构成认领。


    Generated by Claude Code

  6. os-zhuang commented on Aug 11, 2026

    @os-zhuang
    Contributor

    Findings triage round, 2026-08-11 (maintainer-directed). Graded: hold — defence-in-depth: with #7150's watcher matcher in place, no current ingress delivers dot-segment paths to parseItemPath. Restart: any second matcher-less ingress appears, or the layout parser is next touched (then widen the guard in passing).


    Generated by Claude Code

  7. hotlong commented on Aug 12, 2026

    @hotlong
    Contributor

    Finding-grading round: closed, not planned.

    Unreachable through the only ingress (isIgnoredWatchPath at repository.ts:653 does the general dot-segment check, and its docblock :638-652 spells out this exact leak as the reason not to drop the matcher), zero-pull defence-in-depth, 5 identical holds. Re-file if a second, matcher-less ingress to parseItemPath ever appears; the narrow guard remains a fine free rider for the next layout.ts PR.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    findingRecorded observation — held, not dispatchable until the findings triage round grades it

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions