Problem
The collectSubmoduleGitDirs helper added in #276 identifies a Git directory by a regular file named HEAD. Once it finds that file, it only descends into a child named modules and skips the remaining directory children. HEAD remains writable by root sandbox commands.
This allows an intermediate directory in a slash-containing submodule path to hide an existing submodule from the next command's deny scan:
- Create a real submodule whose Git directory is
.git/modules/group/project.
- In one sandbox command, plant a regular file at
.git/modules/group/HEAD.
- On the next command, the scan treats
.git/modules/group as the Git directory and never visits its project child.
- The real submodule's
config and hooks are omitted from explicit write denies and can then be modified. Those settings can affect a subsequent trusted Git invocation.
Removing an existing submodule's HEAD can also make its own config and hooks disappear from the collected set until the file is restored. The scan is recomputed per command, so writable discovery markers must not narrow traversal.
Suggested follow-up
Walk descendant directories even after discovering a HEAD, or otherwise ensure a writable marker cannot cause an existing protected Git directory to be skipped. Preserve support for nested submodules and slash-containing names.
Add regressions covering a planted intermediate HEAD, a removed real HEAD, and the resulting config/hooks denies across consecutive commands.
Bugbot reported this during the upstream import into ClickHouse/ai: https://github.com/ClickHouse/ai/pull/4153#discussion_r4160697007
Affected code: packages/code/src/native-sandbox.ts, collectSubmoduleGitDirs.
Problem
The
collectSubmoduleGitDirshelper added in #276 identifies a Git directory by a regular file namedHEAD. Once it finds that file, it only descends into a child namedmodulesand skips the remaining directory children.HEADremains writable by root sandbox commands.This allows an intermediate directory in a slash-containing submodule path to hide an existing submodule from the next command's deny scan:
.git/modules/group/project..git/modules/group/HEAD..git/modules/groupas the Git directory and never visits itsprojectchild.configandhooksare omitted from explicit write denies and can then be modified. Those settings can affect a subsequent trusted Git invocation.Removing an existing submodule's
HEADcan also make its own config and hooks disappear from the collected set until the file is restored. The scan is recomputed per command, so writable discovery markers must not narrow traversal.Suggested follow-up
Walk descendant directories even after discovering a
HEAD, or otherwise ensure a writable marker cannot cause an existing protected Git directory to be skipped. Preserve support for nested submodules and slash-containing names.Add regressions covering a planted intermediate
HEAD, a removed realHEAD, and the resulting config/hooks denies across consecutive commands.Bugbot reported this during the upstream import into ClickHouse/ai: https://github.com/ClickHouse/ai/pull/4153#discussion_r4160697007
Affected code:
packages/code/src/native-sandbox.ts,collectSubmoduleGitDirs.