git-safe.sizeLimitBytes:
maximum repository content in bytes, checked before and after checkout.
Env PI_GIT_SAFE_SIZE_LIMIT_BYTES.
Default 524288000 (500 MiB).
git-safe.fileCountLimit:
maximum file count, checked before and after checkout.
Env PI_GIT_SAFE_FILE_COUNT_LIMIT.
Default 10000.
git-safe.cloneTimeoutMs:
milliseconds before git clone is killed.
Env PI_GIT_SAFE_CLONE_TIMEOUT_MS.
Default 60000.
Each value must be a positive integer; cloneTimeoutMs is at most 2147483647.
Settings files need JSON numbers; env vars accept numeric strings.
Precedence: env, trusted project .pi/settings.json, user ~/.pi/agent/settings.json, default.
Project settings apply only when the project is trusted.
Values resolve on each git_clone_safe call.
An invalid value fails the clone with an error naming the key and source; it never falls back to a lower source or the default.
The 30s checkout timeout is fixed.
A generated Git executable wrapper receives parsed arguments from normal Bash calls, command git, env git, xargs git, nested Bash, and unqualified child-process PATH lookup.
The first Bash call creates it with fixed Node and Git paths; later calls and reloads reuse it.
It resets the editor environment immediately before invoking real Git and delegates ordinary commands unchanged.
Bash git() and git.exe() functions route direct calls to that wrapper.
Windows paths are converted for Git Bash, and common git.exe shell forms route through the wrapper.
The layers are defense in depth, not a security boundary; unrestricted Bash can still address or construct alternate executable paths.
On Windows, a non-Bash child process that explicitly launches git.exe can bypass the script wrapper because the extension does not ship a native PE launcher.
git_clone_safe clones a repository with checks around hook execution,
symlinks, LFS filters, disk use, and credential prompts.
Operational behavior:
rejects root, home, paths with depth less than 2, exact /tmp, /var,
/etc, /usr, and paths starting with /sys or /proc
refuses an existing target dir unless it contains .pi-git-safe
removes and replaces prior .pi-git-safe clone dirs
recovers marked clone dirs on demand by checking ancestors of each read
path
wraps text read results from tracked clone dirs in <untrusted_external_data marker="..."> markers with a git-specific warning preamble
warns when .git/config contains include or includeIf
warns when symlinks are present; core.symlinks=false stores them as text
files
Debug
Opt in through debug contract.
Safe events: session.start, session.shutdown, git.policy.initialize.start, git.policy.initialize.finish, git.policy.initialize.error, clone.start, clone.finish, clone.error.
# git-safe
Hardened git clone tool for Pi.
## Install / load
Loaded through the root `pi-ext` package.
See [root README](../../README.md).
## Commands / tools / settings
- Commands:
none
- Tools:
- `git_clone_safe(url, path, branch?, sparse?)`
- `url`:
Git repository URL
- `path`:
local target directory, absolute or relative to cwd
- `branch`:
optional branch name
- `sparse`:
optional repository-relative sparse checkout paths
- Hooks:
- `session_start`
- `session_shutdown`
- `tool_call` for agent-issued `bash`
- `tool_result` for `read`
- Settings:
- `git-safe.sizeLimitBytes`:
maximum repository content in bytes, checked before and after checkout.
Env `PI_GIT_SAFE_SIZE_LIMIT_BYTES`.
Default `524288000` (500 MiB).
- `git-safe.fileCountLimit`:
maximum file count, checked before and after checkout.
Env `PI_GIT_SAFE_FILE_COUNT_LIMIT`.
Default `10000`.
- `git-safe.cloneTimeoutMs`:
milliseconds before `git clone` is killed.
Env `PI_GIT_SAFE_CLONE_TIMEOUT_MS`.
Default `60000`.
Each value must be a positive integer; `cloneTimeoutMs` is at most `2147483647`.
Settings files need JSON numbers; env vars accept numeric strings.
Precedence: env, trusted project `.pi/settings.json`, user `~/.pi/agent/settings.json`, default.
Project settings apply only when the project is trusted.
Values resolve on each `git_clone_safe` call.
An invalid value fails the clone with an error naming the key and source; it never falls back to a lower source or the default.
The 30s checkout timeout is fixed.
## Behavior
Agent-issued Bash commands receive noninteractive Git editor settings:
- `GIT_EDITOR=true`
- `GIT_SEQUENCE_EDITOR=true`
- `GIT_MERGE_AUTOEDIT=no`
A generated Git executable wrapper receives parsed arguments from normal Bash calls, `command git`, `env git`, `xargs git`, nested Bash, and unqualified child-process PATH lookup.
The first Bash call creates it with fixed Node and Git paths; later calls and reloads reuse it.
It resets the editor environment immediately before invoking real Git and delegates ordinary commands unchanged.
Bash `git()` and `git.exe()` functions route direct calls to that wrapper.
Windows paths are converted for Git Bash, and common `git.exe` shell forms route through the wrapper.
The layers are defense in depth, not a security boundary; unrestricted Bash can still address or construct alternate executable paths.
On Windows, a non-Bash child process that explicitly launches `git.exe` can bypass the script wrapper because the extension does not ship a native PE launcher.
`git_clone_safe` clones a repository with checks around hook execution,
symlinks, LFS filters, disk use, and credential prompts.
Operational behavior:
- rejects root, home, paths with depth less than 2, exact `/tmp`, `/var`,
`/etc`, `/usr`, and paths starting with `/sys` or `/proc`
- refuses an existing target dir unless it contains `.pi-git-safe`
- removes and replaces prior `.pi-git-safe` clone dirs
- runs `git clone --no-checkout --depth 1 --single-branch -c
core.symlinks=false`
- passes `--branch` when `branch` is provided
- when `sparse` paths are provided, adds `--filter=blob:none` and `--sparse`
to clone args
- rejects unsafe sparse paths: empty paths, absolute paths, `.`, `..`,
traversal, and null bytes
- disables credential prompts with `GIT_TERMINAL_PROMPT=0` and
`GIT_TERMINAL_PROMPT_AUTOCHECK=0`
- sets repo-local `core.hooksPath` to an empty hooks dir
- sets repo-local `lfs.skipSmudge=true`
- writes `.git/info/attributes` with `* -filter`
- removes `.git/hooks`
- estimates checkout size with `git ls-tree -r -l HEAD`
- rejects content over `git-safe.sizeLimitBytes`, default 500 MiB
- rejects trees over `git-safe.fileCountLimit`, default 10,000 files
- scans `git ls-files -s` for symlinks before checkout
- checks out with the empty hooks dir enforced
- kills clone after `git-safe.cloneTimeoutMs`, default 60s, and checkout after 30s
- writes `.pi-git-safe` marker after successful checkout
- returns canonical clone path, URL, optional branch, file count, total byte
count, symlink count, symlink paths, warnings, and optional sparse paths
- recovers marked clone dirs on demand by checking ancestors of each `read`
path
- wraps text `read` results from tracked clone dirs in `<untrusted_external_data
marker="...">` markers with a git-specific warning preamble
- warns when `.git/config` contains `include` or `includeIf`
- warns when symlinks are present; `core.symlinks=false` stores them as text
files
## Debug
Opt in through [debug contract](../DEBUG.md).
Safe events: `session.start`, `session.shutdown`, `git.policy.initialize.start`, `git.policy.initialize.finish`, `git.policy.initialize.error`, `clone.start`, `clone.finish`, `clone.error`.