Skip to content

[sec-check] loader: symlinked project .prompts directory bypasses the #285 symlink-escape check #294

Closed

Description

@hivecommons-hive

Security Finding

Severity: low
Type: unsafe-pattern (incomplete fix of #274 / #285)

#285 made loadFromDir refuse .prompts/*.md symlinks whose real target escapes the owning .prompts/ directory. The directory boundary it enforces, however, is realpathSync(dir) — the resolved location of .prompts itself. If the project .prompts entry is a symlink (git stores symlinks as mode 120000 and checks them out by default on Linux/macOS, so a cloned repository can commit one), the attacker-chosen target becomes the trusted root and every *.md inside it is loaded as a project template.

Reproduced at c10c5dd:

mkdir -p secret repo; echo 'private {{note}}' > secret/journal.md
ln -s "$PWD/secret" repo/.prompts
cd repo && promptargs list
# Available templates:
#   journal
#     vars: {{note}}

src/loader.ts loadFromDir() (realDir = realpathSync(dir) then target.startsWith(realDirPrefix)) — the per-file check is correct, but the root it compares against is attacker-controlled when dir is itself a link.

Impact

A cloned repository can point .prompts at a directory on the victim's machine (e.g. ~/.prompts, an Obsidian/notes vault, ~/Documents) and have its *.md files listed, shown and expanded by promptargs <name> as if they were the repo's own templates — feeding private note content into a prompt the user then pastes into an AI tool. Exposure is limited to *.md files in a guessable path, hence low, but it is the same class #285 set out to close ("a cloned repo must not be able to make promptargs x read" files outside the repo).

Recommendation

In loadFromDir, for the project source, lstatSync(dir) and refuse the directory when isSymbolicLink() (emit a loader_dir_unreadable-style diag and return []). The user-level ~/.prompts is the user's own and may remain a symlink. Mirror the #285 test in src/loader.test.ts with a symlinked .prompts directory, add a changelog.d/fixed-<n>-prompts-dir-symlink.md fragment, and rebuild dist/ (CI dist-sync gate).


Filed by sec-check agent (ACMM L6 — full mode)


🐝 Hive Agent: security | Instance: hosted-available-oke-11-placeholder-r05x | SHA: unknown

— hive: agent=sec-check backend=copilot model=claude-fable-5.1 copilot=1.0.88

Activity

  1. added
    securityCreated by Hive for agent-filed issue provenance
    agent/sec-checkCreated by Hive for agent-filed issue provenance
    on Oct 20, 2026
  2. added
    help wantedDenotes an issue that needs help from a contributor. Must meet "help wanted" guidelines.
    on Oct 20, 2026
  3. hivecommons-hive commented on Oct 20, 2026

    @hivecommons-hive
    ContributorAuthor

    Folded finding. This was filed as a separate issue, but 1 open agent-filed issue (plus 3 closed as completed in the last 14 days) already cite src/loader.ts, so it was folded into the oldest of them instead of opening another. Treat this issue as the tracker for that component: prefer one structural fix that covers every folded variant over a PR per variant.

    Review backlog (style): Dangling project .prompts symlink is skipped silently without the new diagnostic

    Filed from an out-of-scope finding in the Hive review of PR #295.

    PR: #295 (#295)
    Perspective: style
    Severity: info (P3)
    Evidence: src/loader.ts:25 (

    if (!existsSync(dir)) return [];
    )
    Finding: Dangling project .prompts symlink is skipped silently without the new diagnostic

    existsSync(dir) at src/loader.ts:25 follows symlinks and returns false for a dangling project .prompts link, so execution never reaches the lstatSync guard at :33 and no loader_dir_symlink diagnostic is emitted. Harmless (nothing is loaded) but inconsistent with the PR's stated intent of explaining why the dir was refused; could reorder the lstat before existsSync for project source.

    Fingerprint: 6303d5d5d0338cc39849840c9cc85d475966ed6877a429aeecba4d3d379e7e6e

    filed by Hive review backlog

  4. hivecommons-hive commented on Oct 20, 2026

    @hivecommons-hive
    ContributorAuthor

    Linked again from the Hive review of PR #295.

  5. added a commit that references this issue on Oct 20, 2026
    c0fe085
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

    agent/reviewerCreated by Hive for agent-filed issue provenanceagent/sec-checkCreated by Hive for agent-filed issue provenancehelp wantedDenotes an issue that needs help from a contributor. Must meet "help wanted" guidelines.hive/claimed-by-sec-checksecurityCreated by Hive for agent-filed issue provenancestage/040-covered-by-prAn open PR covers this issue

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions