Skip to content

fix(install): prevent traversal in parsed source subdirectories#225

Merged
runkids merged 4 commits into
runkids:mainfrom
jnhu76:fix/sec003-subdir-validation
Jun 15, 2026
Merged

fix(install): prevent traversal in parsed source subdirectories#225
runkids merged 4 commits into
runkids:mainfrom
jnhu76:fix/sec003-subdir-validation

Conversation

@jnhu76

@jnhu76 jnhu76 commented Jun 14, 2026

Copy link
Copy Markdown

Fix: #224

Summary

This PR fixes unsafe repository subdirectory parsing in ParseSourceWithOptions.

The production change is limited to internal/install/source.go. It adds parser-level validation for parsed source fields and applies it across the source parser branches.

What changed

Added validation helpers:

  • validateRepoSubdir

    • Rejects absolute paths.
    • Rejects . / .. path segments.
    • Rejects backslashes.
    • Rejects NUL and control characters.
    • Iteratively URL-decodes up to a bounded depth and rejects encoded traversal.
    • Rejects inputs that remain too deeply URL-encoded instead of accepting a partially decoded value.
  • checkSubdirSafety

    • Internal helper used by validateRepoSubdir.
  • validateSourceName

    • Rejects empty names.
    • Rejects NUL/control characters.
    • Rejects / and \.
    • Rejects . and ...
  • validateCloneURL

    • Rejects NUL/control characters.
    • Does not redefine Git URL policy or reject valid Git URL forms such as SSH/scp-like URLs.

Integrated validation into these parser branches:

  • parseGitHub
  • parseGitSSH
  • parseSSHURL
  • parseFileURL
  • parseAzureOnPrem
  • parseAzureSSH
  • parseGitHTTPS

Why

Several parser branches previously assigned regex-captured suffixes directly to Source.Subdir and derived Source.Name without validating the resulting fields.

For example, an input such as:

github.com/owner/repo/../../etc/passwd

could produce a Source whose Subdir contains traversal segments. If downstream install logic later joins that Subdir with a cloned repository root, the parsed value may escape the intended repository boundary.

Behavior after this change

Rejected examples:

github.com/owner/repo/../../etc/passwd
github.com/owner/repo/../../../etc/shadow
https://github.com/owner/repo/../../etc/passwd
github.com/owner/repo/..%2F..%2Fetc%2Fpasswd
github.com/owner/repo/%2e%2e/%2e%2e/etc/passwd
github.com/owner/repo/a/../b
github.com/owner/repo/path\x00/../../etc/passwd
/\x00

Still accepted examples:

github.com/owner/repo/skills/frontend
https://github.com/user/repo/tree/main/path/to/skill
https://gitlab.com/group/subgroup/project/-/tree/main/skills/foo
https://dev.azure.com/org/project/_git/repo/skills/bar
https://git.example.com/owner/repo/skills/foo
https://gitea.example.com/user/repo/sub/dir

Scope

This PR only changes source parsing and regression tests.

It does not change:

  • CNB/Gitea host config wiring.
  • Gitea download behavior.
  • Git command wrappers.
  • Server handlers.
  • Config parsing.
  • Skillignore logic.
  • Fuzz/reporting artifacts.

Validation

Ran:

go test ./internal/install/ -run TestSecurity_ParseSource -v
go test ./internal/install/
go test ./...

…Options

Reject unsafe subdirs that contain path traversal (..), NUL bytes,
control characters, backslashes, absolute paths, and URL-encoded
traversal (single, double, or overly deep encoding). Validate derived
source names and CloneURLs for NUL and control characters.

SEC003: github.com/owner/repo/../../etc/passwd previously produced a
Source with Subdir containing traversal segments. Now rejected.
@runkids runkids marked this pull request as ready for review June 15, 2026 02:55
runkids
runkids previously approved these changes Jun 15, 2026

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7fa1b5644f

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +389 to +390
if err := validateRepoSubdir(subdir); err != nil {
return nil, err

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Validate blob subdirs before trimming SKILL.md

When the input is a GitHub blob URL ending in SKILL.md, stripGitHubBranchPrefix calls trimSkillFileSuffix before this new validation runs; that helper uses filepath.Dir, which cleans traversal segments away. For example, https://github.com/user/repo/blob/main/skills/foo/../../SKILL.md reaches this check with an empty subdir and is accepted as the repo-root skill instead of being rejected like the other a/../b cases, leaving a traversal-shaped source able to bypass the new SEC003 parser validation. Validate the raw blob path before trimming, or trim without cleaning path segments.

Useful? React with 👍 / 👎.

Add SEC003 regression cases for SSH, file, Azure, and generic HTTPS source parser branches so traversal rejection stays covered beyond the GitHub parser.
Trim SKILL.md suffixes without filepath.Dir so traversal segments remain visible to SEC003 subdir validation. Add regression coverage for GitHub, GitLab, and Bitbucket blob-style skill URLs.
@runkids runkids merged commit c5544c5 into runkids:main Jun 15, 2026
7 checks passed
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.

Validate parsed source subdirectories in ParseSourceWithOptions

2 participants