A git command line has more ways in than it looks. A branch named
--upload-pack=touch /tmp/x is an option. A URL ext::sh -c ... runs a
command. GIT_DIR in the environment, as a hook sets it, makes -C point
somewhere else. A repository's hooks and core.fsmonitor are programs. The
deck closes each of these, so names, revisions, paths and URLs can come from
users.
- No shell. Every command is an argument list handed to the program.
- Names are checked as git checks them. Branch, tag and remote names
follow
git check-ref-format: no spaces,..,~ ^ : ? * [ \,@plus an opening brace, leading or trailing/or.,.lockendings, and no leading-. - Revisions and URLs may not start with
-, and revisions are passed after--end-of-optionswhere git supports it. Control characters are refused in both. - Paths come after
--and are literal (--literal-pathspecs):-rfis a file name and*.txtis not a glob, nor is:(top)magic. - Remotes are allow-listed.
protocol.allow=never, then onlyConfig.protocols(defaulthttpsandssh). Local paths andfile://needfilein the list: with it, a URL from a user can clone any repository the process can read.ext::,fd::and every othertransport::addressform is refused before git sees it, and ascheme://URL cannot reach agit-remote-<scheme>helper on thePATH.withProtocolschanges the list;httpandgitare unencrypted. - The environment cannot redirect a command.
GIT_DIR,GIT_WORK_TREE,GIT_INDEX_FILE,GIT_OBJECT_DIRECTORYand the other variables that beat-Care removed withenv -uon every command. - git does not look above the repository.
opentakes the top level:GIT_CEILING_DIRECTORIESstops a subdirectory of some other repository from being taken for it, andopencompares the top level git reports with the one asked for. - Programs from the repository do not run. Hooks are off
(
core.hooksPath=/dev/null) unlesswithHooksturns them on, the fsmonitor is off, and diffs and blame run no external diff or text conversion, which a.gitattributesin the repository could select. - Credentials stay out of error messages.
https://user:token@...is writtenhttps://***@...in the deck's messages; git strips it from its own. - Nothing waits for a person.
GIT_TERMINAL_PROMPT=0: a remote that wants a password fails instead of hanging. The locale isC, so messages are English and stable. - Every command has a timeout (
withTimeout, two minutes by default), and replaced objects (refs/replace/) are ignored, so an id names what it says.
- A repository's own configuration is still git's.
.git/configcan setcore.sshCommand, filters and aliases that run programs. git refuses repositories owned by another user (safe.directory); do not run the deck on a.gitdirectory that came from an upload or an archive. - The timeout kills git, not its children. An
sshorgit-remote-httpsit started can outlive it until its connection drops. A command killed while writing can leave.git/index.lockbehind; the next command that needs the index then fails until it is removed. - A URL can reach any host the machine can. Cloning a user's
https://orssh://URL connects to internal hosts as readily as to GitHub; filter hosts before callingcloneorlsRemoteif that matters. runis unchecked. It passes your arguments as they are, after the hardened prefix. Never build them from user input.- Credentials are git's. The deck never sees them: use an ssh agent, deploy keys, or a credential helper.