shelling-out-to-git
Non-obvious considerations when writing logic that shells out to Git from an app/lib.
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
القائمة
Non-obvious considerations when writing logic that shells out to Git from an app/lib.
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
استنادا إلى تصنيف SOC المهني
How to make a good property-based test.
How to decompose a design into reviewable implementation stages. Use when planning how to implement multi-stage features, breaking down design documents into PRs, or reviewing implementation plans before starting work.
| name | shelling-out-to-git |
| description | Non-obvious considerations when writing logic that shells out to Git from an app/lib. |
It's hard to shell out to Git securely, so it's important to think carefully whenever you do.
Git reads .git/config from the local directory, so you need to know that that file doesn't exist/isn't malicious.
(For example, git commit with CWD set to an arbitrary attacker-controlled directory is unsafe if .git/config might already exist.)
Set GIT_CONFIG_NOSYSTEM=1, GIT_CONFIG_GLOBAL=/dev/null, and GIT_CONFIG_COUNT=0 and unset GIT_CONFIG_PARAMETERS, and set HOME=/dev/null, to prevent loading system/user config.
You're probably taking a URL as input to decide what to clone.
As early as possible, strip credentials (such as a username) from it, and preferably use the type system to identify that creds have been stripped: it's otherwise very easy to accidentally allow creds to appear in logs.
Default to only accepting http and https schemes unless you have evidence that other schemes will be required; leave the door open for other schemes, but it's generally easier to validate security when HTTP(S) are the only schemes implemented.
Always carefully control the environment of the Git process we're creating, and default to completely stripping it and also running from / to ensure we're not already in a Git repo when running.
Otherwise, e.g. if the user has set a custom Git config file path, important assumptions we make about Git's behaviour might break.
(Resolve git from $PATH before clearing the environment.)
Never embed credentials in a remote URL, because Git is liable to persist them to disk and to echo them in error messages.
For HTTPS, pass creds through the process env:
GIT_TOKEN=… \
GIT_TERMINAL_PROMPT=0 \
git \
-c credential.helper= \
-c credential.helper='!f() { test "$1" = get && printf "username=foo\npassword=%s\n" "$GIT_TOKEN"; }; f' \
clone https://github.com/foo/bar
credential.helper first, because otherwise they stack.GIT_TOKEN doesn't contain printf metacharacters like newlines.git binary lifecycleMake sure git has quit within some bounded scope.
(For example, if in Rust, tokio::process::Command needs kill_on_drop(true).)
Git permits -C <path>, but this will locate a .git directory that's been planted in <path> even if you intended <path> to be bare.
Use --git-dir <path> instead, to suppress discovery (remember you need --work-tree in that case too, for non-bare repos).
All of the following need to be true for the repo to be definitely a bare repo confined to that directory:
HEAD existscore.bare is configured trueobjects/ or refs/ directoriescommondir (linked worktree gitdir)Remember also that Git config has last-wins semantics: a first-match parser is wrong.
It's so confusing to allow usage of the Git alternates mechanism.
Don't do it, and if you want to be sure a write has taken place, you need to check that objects/info/alternates doesn't exist.
-F runs stripspace and silently mutates the bytes written. You should instead use --no-stripspace (and consider --allow-empty) unless you have some reason to use the default.--force-with-lease-equivalent-style flag to ensure you are replacing what you thought you were replacing. This is frankly incredible. You need to plan around this.GIT_AUTHOR_* and GIT_COMMITTER_* to be set (or some other way of obtaining identity, like config).Git supports extensions.objectformat of sha256, which your application might not; consider whether you need to check the object format before consuming objects.