Luigit
repositories / pi-ext

pi-ext

bugabingas pi extensions

owned by admin

extensions/git-safe/README.md

Raw
Rendered preview

git-safe

Hardened git clone tool for Pi.

Install / load

Loaded through the root pi-ext package. See root README.

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. 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`.