Skip to content

fix(export): exclude symlinks from the build export archive (Closes #104) - #166

Merged
harsharajkumar-273 merged 1 commit into
harsharajkumar-273:mainfrom
SakethSumanBathini:fix/104-export-zip-skip-symlinks
Jul 29, 2026
Merged

fix(export): exclude symlinks from the build export archive (Closes #104)#166
harsharajkumar-273 merged 1 commit into
harsharajkumar-273:mainfrom
SakethSumanBathini:fix/104-export-zip-skip-symlinks

Conversation

@SakethSumanBathini

Copy link
Copy Markdown
Contributor

Closes #104

I've left a comment on #104 with the detail. Short version: the remediation the issue asks for is correct, but I could not reproduce the impact as stated, so this PR describes what I could actually demonstrate.

What the issue predicts, and what actually happens

The issue expects archiver to follow symlinks and pull host file contents into the zip. I installed archiver@7.0.1 — the version pinned in backend/package.json — and reproduced exportZip exactly, same constructor options and same archive.directory(outputPath, false) call, against an output directory containing a link to a file outside it and a link to /etc.

Archiver 7.0.1 does not dereference symlinks. It stored both as symlink entries (mode 0o120000) and rewrote the absolute targets into relative ones (/etc became ../../../../etc). The archive contained the link paths, not the contents of the linked files. I don't believe the backend arbitrary-file-read described in the issue is reachable on this version.

The real exposure points the other way

Extracting that archive with unzip recreated both symlinks, still resolving outside the extraction directory — reading through one returned content from outside the extraction root.

So the risk isn't the backend host; it's whoever downloads the export. A cloned repository's contents are attacker-controlled and git records symlinks, so a malicious PreTeXt repo can commit links that survive into the exported zip. Anyone extracting it with a standard tool (unzip, bsdtar, macOS Archive Utility) materialises links pointing outside their extraction directory — the zip symlink traversal pattern (CWE-59), landing on the user rather than the server.

Change

archive.directory() now takes an entry filter that drops symlinks:

archive.directory(outputPath, false, (entry: any) => (
  entry?.stats?.isSymbolicLink?.() ? false : entry
));

entry.stats comes from an lstat, so this identifies the link itself rather than whatever it points at.

Verification

  • npx tsc --noEmit in backend/ clean.
  • Against the same fixture, the filter excluded both symlink entries and archived the regular files normally.

An alternative, if you'd prefer it

This drops symlinks outright. If you'd rather resolve and validate them — keeping links whose targets stay inside outputPathresolveContainedPath in backend/src/utils/pathContainment.ts already performs exactly that realpath-based containment check, and updateFile uses it for the same class of problem. Say the word and I'll switch this to reuse it.

Note: npm test is red on clean main independently of this branch.

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@SakethSumanBathini, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 2 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 51227017-6196-4306-984f-a1a2e40ddde3

📥 Commits

Reviewing files that changed from the base of the PR and between 2f06e6e and 7f4ed1b.

📒 Files selected for processing (1)
  • backend/src/services/buildExecutor.ts
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@harsharajkumar-273 harsharajkumar-273 added ELUSOC Required Tracking VETERAN Advanced (50 pts) labels Jul 29, 2026
@harsharajkumar-273
harsharajkumar-273 merged commit 4c403cd into harsharajkumar-273:main Jul 29, 2026
1 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ELUSOC Required Tracking VETERAN Advanced (50 pts)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Security: Zip Symlink Traversal & Arbitrary System File Leak via exportZip

2 participants