fix: stop deploy packer from following out-of-root symlinks - #257
Merged
Conversation
When deploy_to_heroku packaged a workspace, it listed files via git and then called stat() and readFile() on each one. Both follow symlinks, so a repository containing a symlink that points outside the selected root would have its target read and uploaded inside the source tarball. This let a malicious repo pull files from the deploying developer's machine into the build. getSourceFilePaths() and walkDirectory() now detect symlinks with lstat() and skip any whose resolved (realpath) target escapes the workspace root. Symlinks that stay inside the root are still followed, so existing behavior is preserved. Adds regression tests covering both the git ls-files path and the directory-walk path. W-23510157
jdodson
approved these changes
Sep 3, 2026
jdodson
left a comment
Contributor
There was a problem hiding this comment.
Approved.
- Visually looked over the PR and lined it up with the description
- Ran a ChatGPT review and it found a thing, it's saying it found a possible edge case in your solution below.
High — Intermediate symlinks bypass containment
tarball.ts:91 checks only when the final path component is a symlink. If Git tracks leak/credentials but leak becomes a symlink to an external directory, lstat("leak/credentials") reports a regular file and its external contents are packed. I reproduced this behavior.
Validate escapesRoot(realRoot, fullPath) for every candidate, not only leaf symlinks, before adding or traversing it.
tlowrimore-heroku
pushed a commit
that referenced
this pull request
Sep 3, 2026
🤖 I have created a release *beep* *boop* --- ## [1.2.7](mcp-server-v1.2.6...mcp-server-v1.2.7) (2026-09-03) ### Bug Fixes * stop deploy packer from following out-of-root symlinks ([#257](#257)) ([75ec26a](75ec26a)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). Co-authored-by: heroku-devtools-release-workflows[bot] <261039447+heroku-devtools-release-workflows[bot]@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
deploy_to_herokucould read files from outside the folder you asked it to deploy and upload them to Heroku inside your source tarball.When packaging a workspace, the tool lists files with
git ls-filesand then callsstat()andreadFile()on each one. Both of those follow symlinks. So if a repository contains a symlink that points somewhere outside the selected root (for example, a link namedconfig.txtthat actually points to/proc/self/environ), the packer would quietly read that external file and pack its contents into the tarball sent to Heroku. Build code in the repo could then read those bytes.This means a repository you cloned from someone else could pull files off your machine the moment you deploy it — without any prompt-injection or special access. It was reported through the bug bounty program (W-23510157, CWE-59).
The fix
getSourceFilePaths()andwalkDirectory()now:lstat()to notice when a path is a symlink.realpath()and check whether it stays inside the workspace root.Symlinks that point to files inside the root are still followed, so normal deploys are unaffected.
Type of Change
Breaking Changes (major semver update)
!after your change type to denote a change that breaks current behaviorFeature Additions (minor semver update)
Patch Updates (patch semver update)
Testing
Notes:
Added regression tests in
src/utils/tarball.spec.ts. They fail against the old code (the out-of-root file lands in the tarball) and pass with this fix. They cover both code paths that walk the workspace: thegit ls-filespath and the plain directory-walk fallback. A test also confirms that a legitimate symlink pointing inside the root is still included.Steps:
npm test— full suite passes (207 tests).npx mocha src/utils/tarball.spec.ts --grep "symlink handling"to run just the new tests.Screenshots (if applicable)
N/A
Related Issues
GUS work item: W-23510157