src/gitvow/. Security and the bypass surface is the companion: it says where
enforcement holds and where it does not. This page says what the tool does on your machine and to your
repository, so a reviewer can verify the claim rather than take it.
Network
gitvow makes no network call of its own. It has no runtime dependencies, no telemetry, no update check and no hosted API. Three things reach a network, each explicit and each yours:git pushof your branch, and thepre-pushhook’s push ofrefs/notes/gitvow/*to the same remote.gitvow sync, which delivers an export bundle to the sinks you configured in.gitvow/export.local.json(a directory, a git remote, or an HTTP store the customer runs). Nothing is configured by default; with no sink,syncbuilds the bundle and moves nothing.- A provider or external adapter you install may call whatever it likes; gitvow runs it as a subprocess and reads its stdout. The gate itself never waits on a network call: providers answer from a local cache or not at all.
gitvow serve, the record server, listens on nothing: it speaks MCP over its own stdin and stdout to the agent
that started it, answers read-only questions about one repository, and writes no file, no log line and no note.
record_check evaluates the policy and stops; it never runs the command it was asked about.
What it reads
- The hook payload the agent sends: session id, working directory, tool name and tool input. The tool input is inspected as text against the policy; a Bash command is read as a string, an edit as a path.
- Repository state:
git rev-parse,git log,git show,git notes, the working tree of a file the agent just edited (to store its blob for attribution), ownership and instruction files it manages. - Policy and rules:
.gitvow/policy.jsonin the repository,~/.gitvow/policy.json, the cached organisation pack under~/.gitvow/cache/packs/, and.gitvow/redact-rules.json. A policy that fails to load refuses every tool call rather than allowing them. - The agent’s transcript, on the machine, at commit and at session stop, to build the note and the ledger entry: tool names, a shortened and redacted argument per call, the last stated plan after redaction, token usage. Tool output is never copied anywhere. The transcript itself is never copied anywhere.
~/.gitvow/cache/brief/, the store’s brief for this repository, fetched by a previoussync. Nothing is fetched during a tool call.- The reach of a matched command, when a rule names a
target: the kubeconfig’scurrent-contextline (--kubeconfig,$KUBECONFIG, or~/.kube/config; the file is scanned for that one line and nothing else is kept),git remote get-urland the current branch, and.terraform/environmentunder-chdir. Read only after a{program, verbs}rule has matched, never for an ordinary command.
Credential-store paths
A path that names a credential store is excluded from everything gitvow stores, whatever its content: snapshots never contain it, no attribution blob is written for it, and the hook log recordsblob_skipped
with the path. The list (src/gitvow/paths.py) covers .env and .env.*, .envrc, .npmrc, .netrc,
.pgpass, .htpasswd, .pypirc, .dockercfg, .boto, .git-credentials, .docker/config.json,
.kube/config, .aws/credentials, credentials.tfrc.json, application_default_credentials.json, SSH
private keys, credentials.* and secrets.* configuration files, key material by suffix (.pem, .key,
.pfx, .p12, .pkcs12, .jks, .keystore, .truststore, .ppk, .kdbx, .asc, .gpg), and
configuration-shaped files under any directory named secrets/ or credentials/. Matching is by path and
case-insensitive. The list is loaded after the policy’s own snapshots.exclude, so a policy can add to it and
cannot remove from it.
Classification is by path, never by content. A secret inside deploy/prod-values.yaml is not covered here; the
redaction layer is best effort on values and says so. Public halves (.crt, .cer, .pub) are not excluded,
and source under internal/secrets/ stays visible.
What it writes
Inside the repository’s git directory, never in the tree:.git/gitvow-session.json: the session’s findings, decisions, pending-commit flag, attribution blob ids, edits that landed elsewhere, the repositories the session reached..git/gitvow-hooks.log: one line per hook event, redacted, appended.- Loose objects for attribution blobs (
git hash-object -w), unreachable from any ref, pruned bygit gc. refs/notes/gitvow/<session>: the session note per commit.refs/gitvow/snapshots/<session>/<n>: working-tree snapshots after agent edits, never pushed..git/info/exclude: one line so.gitvow/export.local.json(your sinks) is never committed.
install, rules, claims and policy, and only these paths:
.gitvow/policy.json,.gitvow/git-hooks/*,.gitvow/export.local.json.- The agent’s settings file for a per-repository install (
.claude/settings.json,.codex/hooks.json,.cursor/hooks.json,.github/hooks/gitvow.json,.factory/hooks.json,.gemini/settings.json): one managed entry per hook event, everything else in the file preserved. - The managed sections in instruction files (
CLAUDE.md,AGENTS.md,.github/copilot-instructions.md,.cursor/rules/*): between gitvow’s own markers, everything outside preserved.
src/gitvow/safewrite.py): the target must resolve inside the
repository, must not resolve into its git directory (found through git rev-parse --git-dir, so a relocated
one counts), must be a regular file or absent, and must not be a hard link, because an inode’s other names
cannot be read back and one of them may be .git/config. A refused write names the path and the reason and
writes nothing. A checked-in symlink at .claude/settings.json therefore cannot aim the install at another
file. Writes are also skipped when the bytes are already current, so a rerun leaves mtimes alone; the install
stamp that gitvow status uses to find ungated commits is a hook’s mtime, and a rewrite would have hidden them.
Outside the repository: ~/.gitvow/policy.json, ~/.gitvow/git-hooks/, ~/.gitvow/ledger/<session>.json,
~/.gitvow/cache/, ~/.gitvow/claims/ (your own preferences), and the user-scope agent settings files.
Caches
The pack and the brief under~/.gitvow/cache/ are copies of what a store published, keyed by the
repository’s source. Neither is derived from your policy or from gitvow’s version, so a policy change applies
on the next tool call with nothing to invalidate: the pack is validated and applied at load, every time, and a
pack of an unknown schema, or past its expires, changes nothing and says why. The brief carries the store’s
stale_after and is marked stale past it. There is no other cache.
What it executes
git, as a subprocess, for the commands named above. Never with-c core.hooksPathor--no-verify.python3, from the git hooks, to read the session state; the hooks are shell scripts that chain to the repository’s own hooks when present.- A provider or external adapter you configured, as a subprocess, with the hook payload on stdin.
- Nothing from the repository. gitvow never runs a build, a test, or a command suggested by repository
content.
gitvow check -- <command>evaluates a command against policy and does not run it.
What it sends
Once you push, two things go to your remote, because they are inside git: commit trailers, and session notes underrefs/notes/gitvow/*. gitvow sync sends an export bundle to the sinks you
configured, one file per consented data class, with a manifest that lists what was refused and what never
travels; the bundle is readable before it moves and contains no transcript text, no working-tree content, no
snapshot, no command line and no prompt. Everything else named on this page stays on the machine.
Determinism and what the gate cannot see
The same policy and the same tool call give the same verdict. The gate inspects the text of a tool call; a process that a command spawns is not seen separately, and a path check uses the path, not the diff. Regular expressions are a floor; providers and the classifier raise it, and both are yours to run.Reversals
Decisions this tool has reversed or corrected, so that the current behaviour is read with its history:- 0.15.1, 0.15.2:
statusstopped repeating install advice and started testing for the consequence, an agent-signed commit after the install with no session; then the one-second granularity ofgit log --sincewas corrected, because CI blamed the install for a commit made in the same second. - 0.17.1: command rules gained
programandverbsafter a replay of 15,549 real tool calls found everykubectl --context … deletewalking past a regex that only matched the verb directly after the program. - 0.25.0: the repository a hook acts on is resolved from the tool call, not the session’s working directory, after our own repositories were found to carry no decisions for that reason.
- 0.26.0: repository writes are preflighted and skipped when unchanged; credential-store paths are excluded by path; a policy can extend the snapshot exclusion list and no longer replace it.
- 0.26.1:
{program, verbs}rules match the program in command position only, after an issue whose body described a runbook was stopped as a cluster mutation. A quoted argument, a commit message and a heredoc body are text, not commands;sh -c,sudo,env,timeoutandxargsstill reach the program behind them. - 0.27.0: rules may name what a command reaches (
target), read from the environment; the default policy asks for production contexts and observes the rest. Commands whose program the gate cannot read (eval, a variable, a script run through a shell) stopped falling through to allow and became an observe tier. Both follow from the question “isn’t regex too risky”: as the only barrier it is; as the first tier of a recorder that reads the environment and records what it cannot read, it is a floor with a named ceiling.