Security model
Pied Kingfisher is designed for a read-only, least-privilege posture. It runs git commands on remote hosts over SSH and renders the output in a browser. This page documents every security mechanism that keeps those operations safe.
Allowlisted operations only
Only two git commands are ever executed:
-
git diff— show uncommitted working-tree changes -
git status --short— determine whether a repo is dirty
No writes.
No pushes.
No force-anything.
The code does not import subprocess with arbitrary user-supplied strings.
The allowed commands are assembled from discrete, validated parts.
shell=False everywhere
Every subprocess call — local git, remote SSH, repo discovery — is invoked with shell=False.
The command is always a Python list of strings, never a concatenated shell command string.
This prevents shell injection regardless of what appears in repo paths or machine names.
Input validation at startup and enrollment time
Target names
Target names are validated against ^[A-Za-z0-9][A-Za-z0-9._-]*$ at config-load time and at
/register time.
Unknown target names return 404 Not Found.
The name is never interpolated into a shell command.
Shell-aware quoting for remote commands
When difflab SSH-es into a remote host it must pass a command string over the SSH channel. That string is interpreted by the remote shell, so quoting must match the remote shell type.
-
POSIX hosts:
shlex.quote()— produces POSIX-compliant single-quoted strings. -
Windows hosts (PowerShell): double-quoted strings with internal
"characters rejected at enrollment time, since reliable escaping across PowerShell versions is not guaranteed.
The remote shell type is detected automatically during enrollment (POSIX find attempted first;
PowerShell fallback on failure) and stored in registry.yaml so subsequent diff fetches use the
correct quoting.
Token-gated enrollment
POST /register requires a bearer token that must match DIFFLAB_ENROLL_TOKEN.
If the environment variable is unset, the endpoint returns 503 Service Unavailable and
enrollment is disabled entirely.
Requests with a missing or wrong token return 401 Unauthorized.
Keep the token long (at least 32 random characters) and treat it like a password. Rotate it by restarting the container with a new value.
XSS prevention
All diff and status output from git — including file names, hunk headers, and changed lines —
is HTML-escaped before being inserted into the page.
Jinja2 autoescape is enabled globally; no template uses | safe on user-controlled data.
A <script> tag in a diff line renders as literal text, not executable code.
Forced-command gate
The SSH public key served by GET /pubkey includes a forced-command prefix that invokes the
git-gate.sh (POSIX) or git-gate.ps1 (Windows) script from the gate/ directory:
command="/usr/local/lib/difflab/git-gate.sh",no-pty,no-agent-forwarding,no-port-forwarding,no-X11-forwarding ssh-ed25519 AAAA... difflab
|
The |
What the gate allows
The gate tokenises $SSH_ORIGINAL_COMMAND and accepts only these three shapes:
-
git -C <path> --no-pager diff -
git -C <path> --no-pager diff --numstat -
git -C <path> status --short
Everything else — including id, cat, bash, any extra argument, any shell metacharacter
— is rejected with difflab: command not permitted on stderr and exit 1.
The path extracted from the command is passed to exec git with proper shell quoting, so no
secondary interpretation of the path occurs.
Installing the gate on a new host
Before adding the container’s public key to a host’s authorized_keys, install the gate:
Linux/macOS:
sudo mkdir -p /usr/local/lib/difflab
sudo cp gate/git-gate.sh /usr/local/lib/difflab/git-gate.sh
sudo chmod 755 /usr/local/lib/difflab/git-gate.sh
Then authorize with the gated key line from GET /pubkey (POSIX default):
curl -s http://difflab.example.com:8747/pubkey >> ~/.ssh/authorized_keys
Windows (elevated PowerShell):
New-Item -ItemType Directory -Force "C:\ProgramData\difflab"
Copy-Item gate\git-gate.ps1 "C:\ProgramData\difflab\git-gate.ps1"
Then authorize with the Windows gate line from GET /pubkey?os=windows:
(Invoke-WebRequest http://difflab.example.com:8747/pubkey?os=windows).Content |
Add-Content "$env:ProgramData\ssh\administrators_authorized_keys"
|
Because the gate only allows |
Restricted authorized_keys options
In addition to command=, the authorized_keys line includes:
no-pty,no-agent-forwarding,no-port-forwarding,no-X11-forwarding
These prevent interactive shell allocation, SSH agent forwarding, and port forwarding, providing defence in depth alongside the forced-command restriction.
SSH key storage
The container’s SSH keypair is generated with ssh-keygen -t ed25519 on first start and stored
in $DIFFLAB_DATA.
It is never transmitted over the network except as the public key on GET /pubkey.
Mount $DIFFLAB_DATA as a named Docker volume so the keypair persists across container upgrades.