Skip to content

security: canonicalize config paths to prevent symlink exploitation#138

Merged
jdx merged 2 commits intomainfrom
security/canonicalize-config-paths
Jan 19, 2026
Merged

security: canonicalize config paths to prevent symlink exploitation#138
jdx merged 2 commits intomainfrom
security/canonicalize-config-paths

Conversation

@jdx
Copy link
Owner

@jdx jdx commented Jan 19, 2026

Summary

  • Canonicalizes paths in the web config editor before comparing against allowed paths
  • Prevents symlink-based attacks to access/modify files outside allowed config locations
  • Handles both existing files (full canonicalization) and non-existing files (parent directory canonicalization)

Security Impact

Without canonicalization, an attacker could create a symlink in an allowed directory pointing to a sensitive file (e.g., /etc/passwd) and then access it through the config editor. This change resolves symlinks before comparison, ensuring only actual allowed files can be accessed.

Test plan

  • Editing existing config files still works
  • Creating new config files in allowed directories works
  • Symlinks to files outside allowed paths are rejected
  • Paths with .. segments are properly resolved and checked

🤖 Generated with Claude Code


Note

Strengthens path validation in the web config editor to prevent symlink/TOCTOU issues.

  • Adds safe_canonicalize and validate_path to canonicalize input and allowed paths (including non-existing paths via ancestor resolution) before comparison
  • Refactors edit and save to require a validated canonical_path and use it for all read/write and directory creation operations; returns clear errors on invalid paths
  • Minor lockfile change (libc added)

Written by Cursor Bugbot for commit 6048bd9. This will update automatically on new commits. Configure here.

The web config editor now canonicalizes paths before comparing them against
the allowed paths list. This prevents an attacker from using symlinks to
access or modify files outside the allowed config locations.

The safe_canonicalize function handles both existing and non-existing files:
- Existing files: uses std::fs::canonicalize() directly
- Non-existing files: canonicalizes parent directory and appends filename

Paths that cannot be resolved are rejected.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

Fixes two issues identified in PR feedback:

1. TOCTOU race condition (High): Previously, paths were validated but then
   file I/O used the original uncanonicalized path. An attacker could swap
   in a symlink between validation and file operation. Now the canonical
   path is returned from validation and used for all file operations.

2. Parent directory doesn't exist (Medium): The previous implementation
   couldn't canonicalize paths when parent directories didn't exist yet
   (e.g., first-time setup). Now walks up the path tree to find an
   existing ancestor, canonicalizes that, then rebuilds the full path.

Changes:
- Renamed is_path_allowed to validate_path, now returns Option<PathBuf>
- Updated safe_canonicalize to walk up path tree for non-existing paths
- edit() and save() now use the canonical path for file operations

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@jdx jdx merged commit b16dc81 into main Jan 19, 2026
4 checks passed
@jdx jdx deleted the security/canonicalize-config-paths branch January 19, 2026 04:03
@jdx jdx mentioned this pull request Jan 19, 2026
jdx added a commit that referenced this pull request Jan 19, 2026
## 🤖 New release

* `pitchfork-cli`: 0.3.0 -> 0.3.1

<details><summary><i><b>Changelog</b></i></summary><p>

<blockquote>

## [0.3.1](v0.3.0...v0.3.1) -
2026-01-19

### Added

- implement daemon dependency resolution
([#135](#135))
- add restart command to CLI
([#134](#134))

### Fixed

- restart command preserves daemon dependency configuration
([#142](#142))
- add missing depends field to restart command
([#136](#136))
- set IPC socket permissions to 0600 for security
([#133](#133))
- handle shell command parsing errors instead of silently failing
([#132](#132))

### Other

- reduce unnecessary daemon cloning in loops
([#144](#144))
- use periodic log flushing instead of per-line
([#139](#139))
- refresh only tracked PIDs instead of all processes
([#141](#141))
- cache compiled regex patterns
([#143](#143))

### Security

- add rate limiting to IPC server
([#137](#137))
- canonicalize config paths to prevent symlink exploitation
([#138](#138))
- add centralized daemon ID validation
([#140](#140))
</blockquote>


</p></details>

---
This PR was generated with
[release-plz](https://github.com/release-plz/release-plz/).

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> Prepares the 0.3.1 release and updates metadata/documentation
accordingly.
> 
> - **Changelog**: Adds `0.3.1` entry detailing added dependency
resolution, new `restart` command, fixes, performance tweaks, and
security hardening
> - **Version bumps**: Updates `version` to `0.3.1` in `Cargo.toml`,
`Cargo.lock`, `docs/cli/commands.json`, `docs/cli/index.md`, and
`pitchfork.usage.kdl`
> - **Docs regen**: Refreshes CLI docs/spec to reflect the new version
(no behavioral changes in this diff)
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
9f9d386. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
@jdx jdx mentioned this pull request Jan 19, 2026
jdx added a commit that referenced this pull request Jan 19, 2026
## 🤖 New release

* `pitchfork-cli`: 1.0.0

<details><summary><i><b>Changelog</b></i></summary><p>

<blockquote>

## [1.0.0](https://github.com/jdx/pitchfork/releases/tag/v1.0.0) -
2026-01-19

### Added

- implement daemon dependency resolution
([#135](#135))
- add restart command to CLI
([#134](#134))

### Fixed

- restart command preserves daemon dependency configuration
([#142](#142))
- add missing depends field to restart command
([#136](#136))
- set IPC socket permissions to 0600 for security
([#133](#133))
- handle shell command parsing errors instead of silently failing
([#132](#132))

### Other

- bump version to 1.0.0
([#147](#147))
- release v0.3.1 ([#121](#121))
- reduce unnecessary daemon cloning in loops
([#144](#144))
- use periodic log flushing instead of per-line
([#139](#139))
- refresh only tracked PIDs instead of all processes
([#141](#141))
- cache compiled regex patterns
([#143](#143))

### Security

- add rate limiting to IPC server
([#137](#137))
- canonicalize config paths to prevent symlink exploitation
([#138](#138))
- add centralized daemon ID validation
([#140](#140))
</blockquote>


</p></details>

---
This PR was generated with
[release-plz](https://github.com/release-plz/release-plz/).

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> Prepares the 1.0.0 release and updates `CHANGELOG.md` with the
finalized notes.
> 
> - Adds `1.0.0` section: daemon dependency resolution, new CLI
`restart` command, fixes for dependency preservation and shell parsing,
secure IPC socket perms, plus performance/maintenance updates
> - Documents security hardening: IPC rate limiting, config path
canonicalization, centralized daemon ID validation
> - Retains prior `0.3.1` notes for historical context
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
4182984. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
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