Skip to content

fix(preset): allow symlinked extends targets outside the config directory#346

Merged
fohte merged 4 commits into
mainfrom
fohte/fix-extends-path-validation
May 16, 2026
Merged

fix(preset): allow symlinked extends targets outside the config directory#346
fohte merged 4 commits into
mainfrom
fohte/fix-extends-path-validation

Conversation

@fohte
Copy link
Copy Markdown
Owner

@fohte fohte commented May 15, 2026

Purpose

  • A relative-path extends reference (./foo.yml, ...) whose symlink target points outside the directory of the config file fails with path traversal detected: './foo.yml' escapes the base directory.
    • Overlay patterns that drop a symlink into the config directory to pull in a preset from elsewhere do not work.
    • The same file referenced through an absolute ~/ path is accepted, so rejecting only the relative form has no consistent justification.

Reproduction steps

mkdir -p /tmp/runok-repro/config /tmp/runok-repro/overlay
echo 'rules: []' > /tmp/runok-repro/overlay/extra.yml
ln -s /tmp/runok-repro/overlay/extra.yml /tmp/runok-repro/config/extra.yml
cat > /tmp/runok-repro/config/runok.yml <<'YAML'
extends:
  - ./extra.yml
YAML
runok check --config /tmp/runok-repro/config/runok.yml 'echo hi'
# => config error: preset error: invalid reference:
#    path traversal detected: './extra.yml' escapes the base directory

Approach

  • Restrict the check on relative and ~/ extends references to a purely lexical evaluation of the reference string.
    • Decide on the path text alone whether .. segments escape the resolution root; do not follow the resolved path's symlinks.
    • A reference without .. is accepted regardless of where its symlink target points.
  • ..-based escapes such as ../../etc/passwd or ~/../../etc/passwd continue to be rejected.
Design decisions

Validation threat model

Decision Design Pros Cons
Chosen Reject only ..-based escapes by lexically evaluating the reference string extends: already accepts absolute paths without restriction, so a filesystem sandbox around symlink targets carries no meaning in the threat model (anyone who can author extends: can also write any absolute path). A ..-segment check is enough as a typo-detection safety guard. Does not prevent reads through a planted symlink, but this matches the existing acceptance of absolute paths.
Rejected Keep the canonicalize-based check and add an opt-out config flag Preserves the expectation that what extends: resolves to stays inside the root, even through symlinks. Inconsistent with extends: already accepting absolute paths. The flag only adds cognitive overhead for users.

fohte added 2 commits May 14, 2026 18:07
intent(preset): support overlay-style configurations where a relative
  extends reference is a symlink to a file managed in a separate
  repository, without forcing users to fall back to absolute `~/` paths
decision(preset): validate the reference text only — collapse `.` and `..`
  lexically and check that the result stays inside the resolution root,
  without canonicalizing symlinks
rejected(preset): keep canonicalize-based check and add a config flag to
  opt out — extends already accepts unrestricted absolute paths, so a
  filesystem-level sandbox on relative refs is incoherent rather than
  conservative
constraint(preset): cycle detection (`./runok.yml` vs `runok.yml` must
  hash to the same key) still needs symlink resolution, so
  canonicalize_best_effort is retained for normalize_reference_key only
…se notes

intent(preset): keep doc comments and release notes focused on observable
  behavior — threat-model rationale for the validation change belongs in
  the PR body, not in the codebase
@codecov
Copy link
Copy Markdown

codecov Bot commented May 15, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 89.06%. Comparing base (9dd0e7d) to head (62d9ad4).

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #346      +/-   ##
==========================================
- Coverage   89.07%   89.06%   -0.02%     
==========================================
  Files          53       53              
  Lines       12657    12661       +4     
==========================================
+ Hits        11274    11276       +2     
- Misses       1383     1385       +2     
Flag Coverage Δ
Linux 88.88% <100.00%> (-0.01%) ⬇️
macOS 90.15% <100.00%> (-0.05%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request updates the extends: path resolution to use lexical normalization instead of filesystem canonicalization, allowing symlinks to point outside the base directory while still preventing .. traversal. Documentation and tests have been updated to reflect this change. A security concern was raised regarding the validate_within function, which may incorrectly permit path traversal if the root path is empty; a specific code improvement was suggested to address this vulnerability.

Comment thread src/config/preset.rs
intent(preset): record the caller-side invariant so future readers do
  not introduce an empty-root call site, which would silently bypass
  the `..` traversal check via `Path::starts_with`'s empty-prefix
  semantics
intent(preset): keep docs focused on observable behavior (relative
  reference + symlink target outside the config directory) without
  framing it through a specific dotfiles-overlay workflow
@fohte fohte merged commit 76713c3 into main May 16, 2026
10 checks passed
@fohte fohte deleted the fohte/fix-extends-path-validation branch May 16, 2026 09:12
@fohte-bot fohte-bot Bot mentioned this pull request May 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant