Skip to content

harden: add path validation in shuji.js - #243

Open
anupamme wants to merge 1 commit into
paazmaya:mainfrom
anupamme:fix-repo-shuji-v-001-bin-shuji.js
Open

harden: add path validation in shuji.js#243
anupamme wants to merge 1 commit into
paazmaya:mainfrom
anupamme:fix-repo-shuji-v-001-bin-shuji.js

Conversation

@anupamme

@anupamme anupamme commented Aug 3, 2026

Copy link
Copy Markdown

Summary

Harden input handling in bin/shuji.js (flagged by multi_agent_ai).

Vulnerability

Field Value
ID V-001
Severity HIGH
Scanner multi_agent_ai
Rule V-001
File bin/shuji.js:107
Assessment Defensive hardening
CWE CWE-22

Description: The application constructs output directories and writes files based on user-supplied input file paths and filenames extracted from source map content. If a malicious source map contains path traversal sequences (e.g., '../../../etc/cron.d/backdoor') in its 'sources' field, the writeSources function may write files outside the intended output directory. The lib/write-sources.js file was not available for review, so full path sanitization cannot be confirmed.

Threat Model Context

This is a Node.js library - vulnerabilities affect downstream consumers who use this package.

Changes

  • lib/write-sources.js

Behavior Preservation

The change is scoped to 1 file on the vulnerable path; it only tightens handling of untrusted input and leaves valid inputs unaffected.


This patch removes an exploit primitive — a code pattern that, while not independently exploitable today, could be chained with other weaknesses by automated exploit-development tooling. Proactive removal of such primitives raises the bar against increasingly capable automated attack tools.


Automated security fix by OrbisAI Security

Summary by CodeRabbit

  • Bug Fixes
    • Prevented files from being written outside the designated output directory.
    • Unsafe output paths are now skipped and reported with an error.

The application constructs output directories and writes files based on user-supplied input file paths and filenames extracted from source map content
@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

writeSources now resolves the output directory and target path before writing. It skips and logs unsafe targets that escape the output directory. Removed commented-out path sanitization code is no longer present.

Changes

Safe source writing

Layer / File(s) Summary
Path containment validation
lib/write-sources.js
writeSources validates resolved target paths against the resolved output directory. It logs and skips unsafe filenames before writing.

Estimated code review effort: 2 (Simple) | ~10 minutes

Poem

A rabbit checks each path with care,
No stray file may wander there.
Safe targets hop into the nest,
Unsafe ones are logged and rest.
Clean writes bloom beneath the moon.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: hardening path handling to prevent unsafe writes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Warning

⚠️ This pull request has been flagged as potential spam (promotional) by CodeRabbit slop detection and should be reviewed carefully.

@sonarqubecloud

sonarqubecloud Bot commented Aug 3, 2026

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lib/write-sources.js`:
- Around line 24-30: The path traversal check using resolved lexical paths does
not prevent symlinked directories from redirecting writes outside outdir. Update
the output-writing flow around resolvedOutdir, resolvedFilepath,
fs.ensureDirSync, and fs.writeFileSync to validate real parent paths and reject
symlink targets escaping outdir, then use a no-follow or atomic write approach
to avoid check-to-write races. Add a regression test covering a symlinked output
subdirectory.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 00ef69ea-ace2-4f51-b898-d75da80a0f87

📥 Commits

Reviewing files that changed from the base of the PR and between 5d40d0d and 80d5732.

📒 Files selected for processing (1)
  • lib/write-sources.js

Comment thread lib/write-sources.js
Comment on lines +24 to +30
// Prevent path traversal: ensure the resolved output stays within outdir
const resolvedOutdir = path.resolve(outdir);
const resolvedFilepath = path.resolve(outputFilepath);
if (!resolvedFilepath.startsWith(resolvedOutdir + path.sep) && resolvedFilepath !== resolvedOutdir) {
console.error(`Skipping unsafe path "${filename}"`);
return;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT
mkdir -p "$tmp/out" "$tmp/outside"
ln -s "$tmp/outside" "$tmp/out/link"

OUTDIR="$tmp/out" OUTSIDE="$tmp/outside" node <<'NODE'
const fs = require('node:fs');
const path = require('node:path');

const outdir = process.env.OUTDIR;
const outside = process.env.OUTSIDE;
const outputFilepath = path.join(outdir, 'link', 'escaped.js');
const resolvedOutdir = path.resolve(outdir);
const resolvedFilepath = path.resolve(outputFilepath);

const accepted =
  resolvedFilepath.startsWith(resolvedOutdir + path.sep) ||
  resolvedFilepath === resolvedOutdir;

if (!accepted) throw new Error('Fixture did not pass the lexical check');

fs.writeFileSync(outputFilepath, 'escaped');

if (!fs.existsSync(path.join(outside, 'escaped.js'))) {
  throw new Error('Symlink escape was not reproduced');
}

console.log('Symlink escape reproduced');
NODE

Repository: paazmaya/shuji

Length of output: 178


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate files =="
git ls-files | rg '(^|/)(write-sources\.js|write-sources_test\.js)$' || true

echo "== lib/write-sources.js =="
if [ -f lib/write-sources.js ]; then
  cat -n lib/write-sources.js
fi

echo "== tests/lib/write-sources_test.js =="
if [ -f tests/lib/write-sources_test.js ]; then
  cat -n tests/lib/write-sources_test.js
fi

echo "== usages of writeSources/write-source path =="
rg -n "writeSources|write-sources|outputFilepath|outdir|fs.mkdtemp|mkdtemp|realpath|fs\\.writeFile|writeFileSync" . --glob '!node_modules' --glob '!dist' --glob '!build' || true

echo "== package node version hints =="
[ -f package.json ] && sed -n '1,120p' package.json | rg -n '"(engines|type|main|scripts)"|node:' || true

Repository: paazmaya/shuji

Length of output: 5201


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== package.json relevant fields =="
sed -n '1,80p' package.json | cat -n

echo "== bin/shuji.js relevant fields =="
sed -n '1,220p' bin/shuji.js | cat -n

echo "== fs-extra usage/import =="
rg -n "fs-extra|ensureDirSync|writeFileSync|realpath|mkdtemp" lib bin tests npm-shrinkwrap.json --glob '!node_modules' || true

echo "== local safe-path lexical check semantics probe =="
tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT
mkdir -p "$tmp/out" "$tmp/outside/subdir"

node <<'NODE'
const path = require('node:path');
const cases = [
  ['./out/subdir/escaped.js', './out/safe.js'],
  ['link/escaped.js', 'safe.js'],
  ['../outside/subdir/escaped.js', 'safe.js'],
  ['..', 'safe.js'],
];
for (const [base, expected] of cases) {
  const filepath = path.join(base);
  const resolvedFilepath = path.resolve(filepath);
  const resolvedOutdir = path.resolve(base.endsWith('/unsafe.log') ? base.slice(0, -'/unsafe.log'.length) : base);
  const accepted = resolvedFilepath.startsWith(resolvedOutdir + path.sep) || resolvedFilepath === resolvedOutdir;
  console.log(JSON.stringify({ input: filepath, resolvedFilepath, accepted, expected }));
}
NODE

echo "== fs.realpath built-in availability =="
node -e "const fs=require('node:fs'); console.log(typeof fs.realpath)"

Repository: paazmaya/shuji

Length of output: 8043


🌐 Web query:

Node.js path.resolve does not resolve symlinks documentation

💡 Result:

The Node.js path.resolve method does not resolve symlinks [1][2]. Its documentation defines the method as a utility that processes a sequence of paths or path segments into an absolute path by performing string manipulation and normalization (such as resolving.. and. segments) [3][4]. Because path.resolve performs these operations purely as string-based path transformations based on the current working directory, it does not interact with the actual filesystem to detect or expand symbolic links [1][2]. If you need to resolve a path while accounting for symlinks, you should use fs.realpath or fs.realpathSync [5][2], which are designed to return the canonicalized absolute path by resolving symlinks and filesystem references [2].

Citations:


Prevent symlink-based writing outside outdir.

path.resolve() only normalizes path segments, so filename = "link/escaped.js" can pass the check when outdir/link points outside outdir. The later fs.ensureDirSync() and fs.writeFileSync(outputFilepath, ...) can follow the link and write outside the output directory. Reject symlink targets that escape outdir and compare real-parent paths. If filesystem state can change between the check and the write, also require a no-follow or atomic write path. Add a regression test for a symlinked output subdirectory.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/write-sources.js` around lines 24 - 30, The path traversal check using
resolved lexical paths does not prevent symlinked directories from redirecting
writes outside outdir. Update the output-writing flow around resolvedOutdir,
resolvedFilepath, fs.ensureDirSync, and fs.writeFileSync to validate real parent
paths and reject symlink targets escaping outdir, then use a no-follow or atomic
write approach to avoid check-to-write races. Add a regression test covering a
symlinked output subdirectory.

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