Repository navigation
Conversation
…real threat model Audit of the reported command injection attack surface, done against the current tree rather than the 2023 reports, which cite a `src/` layout that no longer exists. Findings: - Every external command now runs through an argument vector. `os.exec` is called with an array in all four sites in `git/git.v`, and `Git.exec_in_dir_with_env` sets `p.set_args(...)` directly. Nothing concatenates user input into a string that is later handed to a shell. - `split_command` is purely lexical: it word-splits and unquotes, and does no command substitution, variable expansion, or globbing. Its result is passed as argv, so it cannot introduce shell metacharacters. - `Git.exec_shell` was the one remaining `/bin/sh -c` wrapper and had zero callers anywhere in the repository. Removed, with a note in `git/git.v` telling future authors to add an argument-array helper instead of a string one, so the footgun does not get reintroduced. - `Repo.git()` guards against `&` and `;`, which suggests a shell is involved. It is not. Replaced with a comment stating the guard is not a security control, and naming the actual residual risk: argument injection. A ref beginning with `-` can still be read by git as an option, and `split_command` breaks a ref containing a space into several arguments. The callers that interpolate a caller-supplied ref are in `pr_routes.v` and `repo/branch.v`. Behaviour is deliberately unchanged here; fixing it means moving those to `Git.exec_in_dir_with_env` with a `--` separator, which is a larger change. Private-repository visibility (vlang#247) is already enforced. `App.can_read_repo` in `repo/repo.v` returns 404 for a private repo unless the requester is logged in and has at least reporter access, and every repo-scoped read route calls it before running a git command or returning repo data. `repo/repo_routes.v` `tree()` is one of those callers. Regression tests already exist in `api/endpoints_test.v` and `pr_authorization_test.v`. SECURITY.md is new. The reporter of vlang#246 asked for a security policy in 2023 and the repository still has none, so this is the part of that report that is still open. It covers private disclosure, the supported-versions position, the scope of untrusted input, and the known boundaries that are deliberate design decisions rather than oversights, so a future report can be triaged against them instead of rediscovering them.
medvednikov
left a comment
There was a problem hiding this comment.
The unused shell-wrapper removal looks narrowly scoped, but SECURITY.md has a broken fallback disclosure channel: it tells reporters who cannot use GitHub private reporting to email maintainers listed in .github/FUNDING.yml. That file contains only Patreon/GitHub Sponsors entries (patreon: vlang, github: [medvednikov])—no email addresses. Please provide a confirmed private security contact or remove that fallback; otherwise reporters have no actionable second channel. I'd also have maintainers explicitly confirm the promised 14-day acknowledgement SLA, and clarify in Hardening Notes that argv avoids shell injection but does not by itself prevent the Git option/argument injection already called out in repo/repo.v.
|
Done - addressed in the latest push. |
|
Follow-up on the revised security policy: the incorrect |
…ce (address review)
|
Done - fixed in fc9d56a: repaired the malformed repo/repo.v reference, removed the draft-advisory workaround, softened the 14-day promise to best-effort, and clarified that -- only separates path operands while revision arguments need validation via is_safe_ref/rev-parse. v check-md is clean. |
Summary
Audit of the reported command-injection surface, done against the current tree rather than the 2023 reports, which cite a
src/layout that no longer exists.Plus
SECURITY.md, which the reporter of #246 asked for in 2023 and which the repository still does not have.Evidence
The 2023 report's cited files do not exist.
src/repo_routes.v,src/commit_routes.v, andsrc/repo_service.vare all gone; the tree was restructured and the helpers are now ingit/git.vandrepo/repo.v. So the report cannot be acted on as written and has to be re-derived.Every external command runs through argv. All four
os.execcall sites ingit/git.vpass an array:split_command(git/git.v:218) is purely lexical — word splitting and unquoting, with no command substitution, variable expansion, or globbing — and its result goes to argv.exec_shellhad zero callers. A repository-wide search forexec_shellreturns exactly one match, its own definition:Private-repository visibility (#247) is already fixed.
repo/repo.v:1796:That check is called at 43 sites across every repo-scoped read route, including
tree()inrepo/repo_routes.v:753, which is the route the report described (/:username/:repo_name). Regression tests already exist inapi/endpoints_test.v:919(test_api_v1_private_repo_visibility_from_other_user, which asserts 404 for a private repo from another authenticated user, and 200 for the owner) andpr_authorization_test.v.Build still passes after the removal:
Merge Danger
Door: Two-way.
exec_shellhad no callers, so removing it cannot change behaviour; the rest is comments and a new file. Reverting is clean.Blast Radius: Small, but one thing deserves scrutiny.
I deliberately did not fix the residual issue I found, and named it in the code instead.
Repo.git()guards against∧, which reads like a shell guard but is not one, because there is no shell. The actual residual risk is argument injection: a ref name beginning with-can still be interpreted by git as an option, andsplit_commandsplits on whitespace, so a ref containing a space becomes several arguments. The callers that interpolate a caller-supplied ref arepr_routes.v:859/pr_routes.v:886(log ${base}..${head},diff ${base}...${head}) andrepo/branch.v:67.Fixing that properly means moving those callers onto
Git.exec_in_dir_with_envwith an argument array and a--separator, which is a behavioural change I cannot test here beyond a compile. It is a separate change and should be reviewed on its own merits, not smuggled into a cleanup.