Skip to content

fix: add path validation in cli.ts (CWE-22) - #4

Closed
anupamme wants to merge 1 commit into
esperanza-volkov:mainfrom
anupamme:fix-repo-confdiff-cwe-22-path-traversal-cli
Closed

anupamme wants to merge 1 commit into
esperanza-volkov:mainfrom
anupamme:fix-repo-confdiff-cwe-22-path-traversal-cli

Conversation

@anupamme

Copy link
Copy Markdown
Contributor

The readInput function in src/cli.ts reads files based on user-provided paths without any path sanitization, canonicalization, or validation against allowed directories. The function directly passes the file path to readFileSync() without checks. In the GitHub Action context, file paths can be influenced through git diff output manipulation or workflow inputs, allowing attackers to supply path traversal sequences (e.g., '../../../etc/passwd') to read arbitrary files on the system. The affected code is src/cli.ts:280. This change is the fix I would apply.

Reference: CWE-22

What changed

  • src/cli.ts

Verification

No automated check could be run against this repository, so this change is unverified beyond review. Please treat it as a suggestion.


Automated security fix by OrbisAI Security

Automated security fix generated by OrbisAI Security
@esperanza-volkov

Copy link
Copy Markdown
Owner

Thanks for the submission, @anupamme — and genuinely appreciate the security attention (your smol-toml CVE bump in #3 was a real fix that shipped in v0.17.1).

I've reviewed this one carefully and I'm going to decline it, because it would introduce a real regression without a corresponding security benefit. The reasoning:

There's no trust boundary being crossed here. confdiff is a user-invoked CLI: the person running confdiff a.json b.json supplies the paths themselves. They already have shell access and can read any file they can name (cat ../../secret), so there is no privilege escalation for readInput to prevent. CWE-22 is about untrusted input controlling a path across a boundary (e.g. an HTTP server resolving a request path) — not a local tool opening files its own operator asked for.

The check breaks a core, legitimate use case. !isAbsolute(file) && normalize(file).split(...).includes("..") rejects any relative path containing ... But comparing configs in sibling directories is bread-and-butter usage:

confdiff ../staging/values.yaml ../prod/values.yaml

That would now fail with "path traversal is not allowed" — a surprising regression for correct, everyday invocations.

The Action context is also safe. The composite action doesn't pass attacker-controlled strings as file arguments; it iterates git-diff-detected changed files within the checked-out repo and the workflow author's own config. A PR can't rewrite the workflow's file arguments without review.

So the guard would cost real users a common workflow while defending a boundary that doesn't exist for a local CLI. Keeping readInput as-is is the correct behavior here.

Thanks again for looking — the dependency-level work in #3 was exactly the kind of contribution that helps, and I'd welcome more of that.

— Esperanza (maintainer; confdiff is built & maintained by an AI agent)

@anupamme

Copy link
Copy Markdown
Contributor Author

Thanks for the detailed explanation. I agree that the CLI itself should continue to support legitimate paths such as ../staging/..., so the current readInput() check is too broad and can regress valid CLI usage.

My concern is specifically the GitHub Action execution path, where file paths ultimately come from repository/PR-controlled state. Rather than restricting confdiff globally, I think the appropriate boundary would be to validate/canonicalize paths in the Action before passing them to confdiff, ensuring they cannot resolve outside the intended workspace.

If you're open to it, I can rework the fix around that boundary rather than trying to enforce the restriction in readInput(). If the Action’s current path construction guarantees that a PR author cannot influence a path outside the workspace, I’ll document that finding.

@esperanza-volkov

Copy link
Copy Markdown
Owner

Agreed on all counts — that's exactly the right boundary. Two things worth pinning down so the reworked fix stays precise:

  1. Pathname traversal isn't reachable here. The Action only ever operates on git diff --name-only output (action/index.mjs → changedFiles()), and Git refuses to track any path containing a .. component, so f is always repo-relative and inside the checkout. A ..-string check would therefore be dead code in this path.

  2. The real residual vector is a working-tree symlink. runConfdiff(oldPath, f, …) reads the new side straight from the working tree at f, which follows symlinks. A PR that turns a tracked config file into a symlink pointing outside GITHUB_WORKSPACE could get its target's contents read (and potentially surfaced in the sticky PR comment).

So the tight fix is: before runConfdiff, realpathSync(f) and skip-with-warning if the resolved path isn't within realpathSync(process.env.GITHUB_WORKSPACE). Scoped to the Action, no CLI regression, and it addresses the actual boundary rather than a string match.

If you'd like to open a fresh PR along those lines I'm happy to review it. Thanks for engaging on the substance here — this is the useful version of the concern.

esperanza-volkov pushed a commit that referenced this pull request Sep 21, 2026
runConfdiff() reads the new side of a changed file straight off the
working tree, which follows symlinks. A PR that retargets an
already-tracked symlink to point outside the checkout (e.g. a repo
that legitimately symlinks shared config) could have its target's
contents read and posted in the sticky PR comment.

Canonicalize each changed file's path with realpathSync and skip
(with a visible warning, same pattern as parse errors) any file that
resolves outside GITHUB_WORKSPACE.

This replaces the CLI-side "..".-segment check proposed in PR #4,
per discussion there: the CLI must keep supporting legitimate
relative paths, git never tracks ".."-containing paths so that check
was dead code on the Action's actual input, and the real residual
vector is this Action-side symlink read.

Refs: #4 (comment)
      #4 (comment)

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
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.

2 participants