Zwischen

Zwischen blocks git push the moment you're about to leak a secret — the
last point where a leaked credential is still a local problem instead of an
incident. It orchestrates Gitleaks
and Semgrep, normalizes their findings, and (optionally)
asks an AI provider — including fully local models via Ollama — to prioritize
the results, flag false positives, and suggest fixes.
One command sets everything up:
zwischen init # installs gitleaks if missing, creates config, installs the pre-push hook
Try it on a deliberately vulnerable app
The zwischen-demo repository is a small Express app seeded with fake secrets and real vulnerability patterns. Clone it and watch a push get blocked in under a minute.
Installation
gem install zwischen # canonical implementation (Ruby)
npm install -g zwischen # Node wrapper
pip install zwischen-cli # Python wrapper (command is still `zwischen`)
For local development from this repository:
bundle install
bundle exec ruby -Ilib bin/zwischen --help
Quick start
Run these from the project you want to protect:
zwischen init # config + pre-push hook + gitleaks auto-install
zwischen scan # manual scan with full report
zwischen doctor # check tool status
AI triage
Raw scanner output tells you what matched. The AI pass tells you what to fix first and how — see the real before/after in docs/triage-example.md, generated with a local model via Ollama.
| Provider | Flag | Setup |
|---|---|---|
| Claude | --ai claude |
Set ANTHROPIC_API_KEY or pass --api-key. |
| Ollama | --ai ollama |
Install Ollama and pull a model. Local-first: nothing leaves your machine. |
| OpenAI | --ai openai |
Set OPENAI_API_KEY or pass --api-key. |
Manual scans use AI when --ai is passed or config enables it. Pre-push
scans stay scanner-only unless ai.pre_push_enabled: true — blocking
decisions should be fast and deterministic. Annotation quality is
model-dependent: larger models give reliably structured, accurate triage,
while very small local models may fail to annotate. If AI is unavailable or
unparseable, scans always fall back to raw findings. The design rationale
is in docs/design.md.
Commands
| Command | What it does |
|---|---|
zwischen init |
Installs/checks tools, creates config, installs pre-push hook. An existing hook (husky, hand-written) is backed up and appended to — its checks keep running. |
zwischen scan |
Runs enabled scanners, prints a terminal report. |
zwischen scan --changed |
Scans only files changed since the default branch, including staged and untracked files. |
zwischen scan --only secrets,sast |
Limits to Gitleaks (secrets) and/or Semgrep (sast). |
zwischen scan --ai <provider> |
Adds AI prioritization, fix suggestions, false-positive detection. |
zwischen scan --format json |
Machine-readable summary + findings. |
zwischen scan --format sarif |
SARIF 2.1.0 for GitHub code scanning. |
zwischen scan --pre-push |
Quiet hook mode: changed files only, compact output only when blocking. |
zwischen doctor |
Shows Gitleaks and Semgrep status. |
zwischen uninstall |
Removes the hook (or just the appended block), optionally config/credentials. |
zwischen --version |
Prints the version. |
Escape hatches: git push --no-verify or ZWISCHEN_SKIP=1 git push.
GitHub Action
Run the same scan in CI and feed the GitHub Security tab:
- uses: cjordan223/zwischen@main
with:
sarif-file: zwischen.sarif
- uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: zwischen.sarif
Configuration
Create or edit .zwischen.yml in the scanned project:
ai:
enabled: true
pre_push_enabled: false
provider: claude # claude, ollama, or openai
ollama:
model: llama3
url: http://localhost:11434
timeout: 180 # seconds; local models can be slow to load
blocking:
severity: high # high, critical, or none
scanners:
gitleaks:
enabled: true
semgrep:
enabled: true
config: p/security-audit,p/expressjs # comma-separated rulesets
ignore: # findings under these globs are dropped
- "**/node_modules/**"
- "**/test/fixtures/**"
Boolean scanner entries (gitleaks: true) work too. Credentials are read
from environment variables first, then ~/.zwischen/credentials (written by
zwischen init when ANTHROPIC_API_KEY is set).
Architecture
flowchart LR
push[git push] --> hook[pre-push hook]
hook --> diff[GitDiff<br/>changed files]
diff --> orch[Orchestrator]
cli[zwischen scan] --> orch
orch --> gl[Gitleaks<br/>secrets]
orch --> sg[Semgrep<br/>SAST]
gl --> agg[Aggregator<br/>normalize + dedupe<br/>+ ignore globs]
sg --> agg
agg --> ai{AI enabled?}
ai -- yes --> llm[Claude / OpenAI / Ollama<br/>priority · fixes · false positives]
ai -- no --> rep
llm --> rep[Reporter]
rep --> term[terminal / compact]
rep --> mach[json / sarif]
term --> block{blocking<br/>finding?}
block -- yes --> deny[⛔ push blocked]
block -- no --> allow[✓ push proceeds]
The Ruby gem is the canonical implementation; the npm and pip packages are convenience wrappers with a smaller command surface.
Wrapper parity
| Capability | Ruby gem | npm / pip wrappers |
|---|---|---|
init / scan / doctor |
✓ | ✓ |
uninstall |
✓ | — |
--only scanner selection |
✓ | — |
--changed / changed-file pre-push filtering |
✓ | — (hook scans the project) |
--format json |
✓ | ✓ |
--format sarif |
✓ | — |
| AI providers | claude, ollama, openai | ollama, openai, anthropic |
The wrapper gaps are intentional scope decisions, not bugs: the wrappers
exist so npm install -g zwischen / pip install zwischen-cli work in
ecosystems where a Ruby gem is friction, and they track the core workflow
(init → hook → scan) rather than every flag.
Repository layout
bin/zwischen Ruby executable
lib/zwischen/ Ruby gem implementation
lib/zwischen/scanner/ Gitleaks and Semgrep adapters
lib/zwischen/ai/ Claude, Ollama, and OpenAI clients
lib/zwischen/reporter/ Terminal and SARIF reporters
packages/npm/ Node wrapper package
packages/pip/ Python wrapper package
spec/ RSpec suite
action.yml Composite GitHub Action
docs/ Design write-up, triage example, demo GIF
Development
bundle exec rspec # 216 examples
./scripts/test_as_gem.sh # install and exercise as a real gem
See DEVELOPMENT.md for architecture notes and the release process, and TESTING.md for the end-to-end test plan.
License
MIT