Skip to content

๐Ÿ›ก๏ธ Sentinel: [CRITICAL] Gherkin export ๋ช…๋ น์˜ ๊ฒฝ๋กœ ํƒ์ƒ‰(Path Traversal) ์ทจ์•ฝ์  ์ˆ˜์ •#50

Open
seonghobae wants to merge 1 commit into
mainfrom
sentinel/fix-path-traversal-export-gherkin-25955486560852296
Open

๐Ÿ›ก๏ธ Sentinel: [CRITICAL] Gherkin export ๋ช…๋ น์˜ ๊ฒฝ๋กœ ํƒ์ƒ‰(Path Traversal) ์ทจ์•ฝ์  ์ˆ˜์ •#50
seonghobae wants to merge 1 commit into
mainfrom
sentinel/fix-path-traversal-export-gherkin-25955486560852296

Conversation

@seonghobae

Copy link
Copy Markdown

๐Ÿšจ Severity: CRITICAL

๐Ÿ’ก Vulnerability: Path traversal in exportGherkin command. The CLI concatenated the user-provided output path directly with the project root using path.join, allowing malicious agents to write files to arbitrary locations outside the project directory (e.g., using ../../../ or absolute paths like /etc/passwd).
๐ŸŽฏ Impact: An attacker or malicious AI could write files anywhere on the user's local filesystem (within their OS permissions), potentially overwriting critical configuration files, dropping malicious payloads, or corrupting data outside the intended scope.
๐Ÿ”ง Fix: Replaced path.join with path.resolve and added strict boundary checks using path.relative. The code now explicitly checks if the resolved path starts with .. or is an absolute path relative to the root. If a violation is detected, it throws a structured VspecError(INVALID_ARGUMENT) to fail securely without leaking internal file structures in stack traces.
โœ… Verification: Verified by a new unit test in tests/gherkin.test.ts that explicitly attempts path traversal using both absolute and relative (../) payloads. Tests pass successfully.

(Note: PR title and description requested in Korean as per memory instructions.)


PR created automatically by Jules for task 25955486560852296 started by @seonghobae

@google-labs-jules

Copy link
Copy Markdown

๐Ÿ‘‹ Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a ๐Ÿ‘€ emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown

Review Change Stack

๐Ÿ“ Walkthrough

๊ฐœ์š”

์‚ฌ์šฉ์ž ์ œ๊ณต ์ถœ๋ ฅ ๊ฒฝ๋กœ๋ฅผ ๊ฒ€์ฆํ•˜์—ฌ ํ”„๋กœ์ ํŠธ ๋ฃจํŠธ ๋ฐ–์œผ๋กœ์˜ ๊ฒฝ๋กœ ์ˆœํšŒ๋ฅผ ๋ฐฉ์ง€ํ•˜๋Š” ๋ณด์•ˆ ๊ฐœ์„ ์‚ฌํ•ญ์„ ๊ตฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ฒ€์ฆ ๋กœ์ง์€ path.resolve์™€ path.relative๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ตฌํ˜„ํ•˜๋ฉฐ, ํ…Œ์ŠคํŠธ ์ธํ”„๋ผ์™€ ๋ณด์•ˆ ๋ฌธ์„œ๊ฐ€ ํ•จ๊ป˜ ์ถ”๊ฐ€๋ฉ๋‹ˆ๋‹ค.

๋ณ€๊ฒฝ ์‚ฌํ•ญ

๊ฒฝ๋กœ ์ˆœํšŒ ๊ฒ€์ฆ ๋ฐ ํ…Œ์ŠคํŠธ

๊ณ„์ธต / ํŒŒ์ผ ์„ค๋ช…
๊ฒฝ๋กœ ๊ฒ€์ฆ ๋กœ์ง ๊ตฌํ˜„
src/export/gherkin.ts
Node path ์œ ํ‹ธ๋ฆฌํ‹ฐ(isAbsolute, relative, resolve)๋ฅผ ์ž„ํฌํŠธํ•˜๊ณ , ์‚ฌ์šฉ์ž input output์„ config.root์— ๋Œ€ํ•ด ํ•ด์„ํ•œ ํ›„ ์ƒ๋Œ€ ๊ฒฝ๋กœ๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ํ”„๋กœ์ ํŠธ ๋ฃจํŠธ ๋ฐ–์˜ ์ ˆ๋Œ€/์ƒ๋Œ€ ๊ฒฝ๋กœ๋ฅผ rejectํ•˜๋Š” VspecError๋ฅผ throwํ•จ.
ํ…Œ์ŠคํŠธ ์ธํ”„๋ผ ๋ฐ ์„ค์ •
tests/gherkin.test.ts
ํ•„์ˆ˜ ์ž„ํฌํŠธ(exportGherkin, VspecError, beforeEach/afterEach)๋ฅผ ์ถ”๊ฐ€ํ•˜๊ณ , tmpdir ํ•˜์œ„์— ์ž„์‹œ ์ž‘์—… ๋””๋ ‰ํ„ฐ๋ฆฌ๋ฅผ ์ƒ์„ฑ/์ •๋ฆฌํ•˜๋Š” beforeEach/afterEach ํ›…์„ ๊ตฌํ˜„ํ•จ.
๊ฒฝ๋กœ ์ˆœํšŒ ๊ฒ€์ฆ ํ…Œ์ŠคํŠธ
tests/gherkin.test.ts
ํ”„๋กœ์ ํŠธ ๋ฃจํŠธ ๋ฐ–์˜ ์ƒ๋Œ€/์ ˆ๋Œ€ ๊ฒฝ๋กœ ์ž…๋ ฅ์— ๋Œ€ํ•ด exportGherkin์ด INVALID_ARGUMENT VspecError๋ฅผ ํŠน์ • ๋ฉ”์‹œ์ง€์™€ ํ•จ๊ป˜ throwํ•˜๋Š”์ง€ ๋‘ ์ผ€์ด์Šค๋กœ ๊ฒ€์ฆํ•จ.
๋ณด์•ˆ ๊ฐœ์„  ๋ฌธ์„œํ™”
.jules/sentinel.md
exportGherkin์˜ ๊ฒฝ๋กœ ์ˆœํšŒ ์ทจ์•ฝ์ , path.join์˜ ํ•œ๊ณ„, path.resolve/path.relative ๊ธฐ๋ฐ˜ ๊ฒ€์ฆ ๋ฐฉ๋ฒ•์„ ์„ค๋ช…ํ•˜๋Š” ๋ณด์•ˆ ๋ฉ”๋ชจ๋ฅผ ์ถ”๊ฐ€ํ•จ.

์˜ˆ์ƒ ์ฝ”๋“œ ๋ฆฌ๋ทฐ ๋‚œ์ด๋„

๐ŸŽฏ 3 (์ค‘๊ฐ„) | โฑ๏ธ ~20๋ถ„

์ถ•ํ•˜ ์‹œ

๐Ÿฐ ๊ฒฝ๋กœ ๋„˜์–ด ์—ฌํ–‰ํ•  ์ˆ˜ ์—†๋„ค,
resolve์™€ relative๋กœ ๊ฒฝ๊ณ„๋ฅผ ์ง€ํ‚ค๋‹ˆ,
๋ฃจํŠธ ์•ˆ์—๋งŒ ํŒŒ์ผ์ด ๋จธ๋ฌผ๋Ÿฌ,
ํ…Œ์ŠคํŠธ๋Š” ๋“ ๋“ ํ•˜๊ณ  ๋ฌธ์„œ๋Š” ๋ฐ๋„ค! โœจ


Caution

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

  • Ignore

โŒ Failed checks (1 error)

Check name Status Explanation Resolution
Docstring Coverage โŒ Error Docstring coverage is 0.00% which is insufficient. The required threshold is 100.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
โœ… Passed checks (4 passed)
Check name Status Explanation
Title check โœ… Passed PR ์ œ๋ชฉ์€ ๋ณ€๊ฒฝ ์‚ฌํ•ญ์˜ ํ•ต์‹ฌ์ธ ๊ฒฝ๋กœ ํƒ์ƒ‰(Path Traversal) ์ทจ์•ฝ์  ์ˆ˜์ •์„ ๋ช…ํ™•ํ•˜๊ฒŒ ์„ค๋ช…ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.
Description check โœ… Passed PR ์„ค๋ช…์€ ์ทจ์•ฝ์ ์˜ ์„ฑ์งˆ, ์˜ํ–ฅ, ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•, ๊ฒ€์ฆ ๋ฐฉ๋ฒ•์„ ์ƒ์„ธํžˆ ์„ค๋ช…ํ•˜๊ณ  ์žˆ์œผ๋ฉฐ ๋ณ€๊ฒฝ ์‚ฌํ•ญ๊ณผ ์ง์ ‘์ ์œผ๋กœ ๊ด€๋ จ์ด ์žˆ์Šต๋‹ˆ๋‹ค.
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 docstrings
  • Create stacked PR
  • Commit on current branch
๐Ÿงช Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch sentinel/fix-path-traversal-export-gherkin-25955486560852296
โœจ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch sentinel/fix-path-traversal-export-gherkin-25955486560852296

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

@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.

๐Ÿงน Nitpick comments (2)
tests/gherkin.test.ts (1)

25-50: โšก Quick win

๊ฒฝ๋กœ ์ˆœํšŒ ๊ฒ€์ฆ ํ…Œ์ŠคํŠธ๊ฐ€ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๊ตฌํ˜„๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

๋‘ ๊ฐ€์ง€ ๊ณต๊ฒฉ ๋ฒกํ„ฐ(์ƒ๋Œ€ ๊ฒฝ๋กœ ์ˆœํšŒ์™€ ์ ˆ๋Œ€ ๊ฒฝ๋กœ)๋ฅผ ์ •ํ™•ํžˆ ๊ฒ€์ฆํ•˜๋ฉฐ, VspecError์˜ ์—๋Ÿฌ ์ฝ”๋“œ์™€ ๋ฉ”์‹œ์ง€๊ฐ€ ๊ตฌํ˜„๊ณผ ์ผ์น˜ํ•ฉ๋‹ˆ๋‹ค.

ํ…Œ์ŠคํŠธ ์ปค๋ฒ„๋ฆฌ์ง€ ๊ฐœ์„ ์„ ์œ„ํ•ด ์œ ํšจํ•œ ๊ฒฝ๋กœ(ํ”„๋กœ์ ํŠธ ๋ฃจํŠธ ๋‚ด๋ถ€)๋กœ exportGherkin์„ ํ˜ธ์ถœํ•˜๋Š” ๊ธ์ • ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๋ฅผ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒƒ์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ๊ฒ€์ฆ ๋กœ์ง์ด ์ •์ƒ์ ์ธ ์‚ฌ์šฉ ์‚ฌ๋ก€๋ฅผ ๋ฐฉํ•ดํ•˜์ง€ ์•Š๋Š”์ง€ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

โœ… ๊ธ์ • ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค ์ œ์•ˆ
+  it("exports to valid path within project root", () => {
+    writeFileSync(
+      join(cwd, "specs/usecases/TEST-2-usecase.md"),
+      `---
+vspec_format: 1
+type: usecase
+key: TEST-2
+title: Valid Export
+level: SUMMARY
+format: CASUAL
+status: DRAFT
+priority: P1
+scope: test
+primary_actor: user
+---
+## Main Success
+`
+    );
+
+    expect(() => exportGherkin({ key: "TEST-2", output: "tests/valid.feature", cwd })).not.toThrow();
+    expect(readFileSync(join(cwd, "tests/valid.feature"), "utf8")).toContain("Feature: Valid Export");
+  });
๐Ÿค– 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 `@tests/gherkin.test.ts` around lines 25 - 50, Add a positive test that calls
exportGherkin with a valid output path inside the project root to ensure the
path-traversal guard doesn't block legitimate outputs: in tests/gherkin.test.ts
create a case that writes the same TEST-1 spec, then calls exportGherkin({ key:
"TEST-1", output: "tmp/ok.feature", cwd }) (or another relative path under cwd)
and asserts it does not throw (or returns the expected result); reference
exportGherkin and VspecError only to contrast the negative tests and ensure the
new test verifies successful behavior for in-root outputs.
src/export/gherkin.ts (1)

43-48: ๊ฒฝ๋กœ ์ˆœํšŒ ๊ฒ€์ฆ ๋กœ์ง์ด ์ ์ ˆํ•ฉ๋‹ˆ๋‹ค

  • resolve()๋กœ ์ •๊ทœํ™”ํ•œ ๋’ค relative() ๊ฒฐ๊ณผ์— ๋Œ€ํ•ด startsWith("..") ๋ฐ isAbsolute(rel)๋ฅผ ๊ฒ€์‚ฌํ•ด ํ”„๋กœ์ ํŠธ ๋ฃจํŠธ ๋ฐ– ๊ฒฝ๋กœ(์ƒ์œ„ ๋””๋ ‰ํ„ฐ๋ฆฌ/์ ˆ๋Œ€๊ฒฝ๋กœ)๋ฅผ VspecError("INVALID_ARGUMENT", ...)๋กœ ์ฐจ๋‹จํ•ฉ๋‹ˆ๋‹ค.
  • ๋‹ค๋งŒ ํ˜„์žฌ๋Š” ๊ฒฝ๋กœ ๋ฌธ์ž์—ด ๊ธฐ๋ฐ˜์ด๋ผ ์‹ฌ๋ณผ๋ฆญ ๋งํฌ๋กœ ์™ธ๋ถ€ ์‹ค๊ฒฝ๋กœ๊ฐ€ ์—ฐ๊ฒฐ๋œ ๊ฒฝ์šฐ๊นŒ์ง€ ์™„์ „ํžˆ ๋ฐฉ์–ดํ•˜์ง„ ๋ชปํ•ฉ๋‹ˆ๋‹ค(์„ ํƒ์  ๊ฐœ์„ : fs.realpathSync๋กœ root์™€ output์˜ ์‹ค๊ฒฝ๋กœ ๋น„๊ต ๊ณ ๋ ค).
๐Ÿค– 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 `@src/export/gherkin.ts` around lines 43 - 48, The current path traversal check
using resolve(), relative() and rel.startsWith("..")/isAbsolute(rel)
(variables/outputPath, rel and functions resolve/relative) can be bypassed by
symbolic links; update the validation to compare real filesystem paths: call
fs.realpathSync on both config.root and outputPath (or use async equivalent),
resolve and normalize them, then ensure the real output path starts with the
real root path (or is equal) before proceeding โ€” if not, throw the same
VspecError("INVALID_ARGUMENT", ...) to block paths that resolve outside the
project root.
๐Ÿค– 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.

Nitpick comments:
In `@src/export/gherkin.ts`:
- Around line 43-48: The current path traversal check using resolve(),
relative() and rel.startsWith("..")/isAbsolute(rel) (variables/outputPath, rel
and functions resolve/relative) can be bypassed by symbolic links; update the
validation to compare real filesystem paths: call fs.realpathSync on both
config.root and outputPath (or use async equivalent), resolve and normalize
them, then ensure the real output path starts with the real root path (or is
equal) before proceeding โ€” if not, throw the same VspecError("INVALID_ARGUMENT",
...) to block paths that resolve outside the project root.

In `@tests/gherkin.test.ts`:
- Around line 25-50: Add a positive test that calls exportGherkin with a valid
output path inside the project root to ensure the path-traversal guard doesn't
block legitimate outputs: in tests/gherkin.test.ts create a case that writes the
same TEST-1 spec, then calls exportGherkin({ key: "TEST-1", output:
"tmp/ok.feature", cwd }) (or another relative path under cwd) and asserts it
does not throw (or returns the expected result); reference exportGherkin and
VspecError only to contrast the negative tests and ensure the new test verifies
successful behavior for in-root outputs.

โ„น๏ธ Review info
โš™๏ธ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2bb770ea-cf57-44a5-a026-8080a7425a9d

๐Ÿ“ฅ Commits

Reviewing files that changed from the base of the PR and between f4223cb and 7f57d39.

๐Ÿ“’ Files selected for processing (3)
  • .jules/sentinel.md
  • src/export/gherkin.ts
  • tests/gherkin.test.ts

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