SSH-based pivoting is a core red team technique: you land on a box, find a private key in ~/.ssh/, and use it to jump to the next host — then the next. In a large environment this quickly becomes a maze. Which key unlocks which user on which host? Where was that key found? Who connected to whom, and when?
Lockpick is a shared web tool for tracking all of that during an operation. It ingests raw evidence — SSH private keys found on compromised hosts, authorized_keys files, auth.log entries, bash history, known_hosts — and builds a relationship graph across the environment. The graph shows you pivot paths: "this private key found on HostA as bob is authorized on HostB as root". That's a confirmed pivot, and Lockpick surfaces it so you don't miss it in the noise.
It is designed to run as a shared server in a trusted network. Every operator reads and writes to the same state, so information one person uncovers is immediately visible to the rest of the team. Deployment is a single docker compose up -d — no database server, no cloud dependencies, no configuration beyond standing up the container.
Core value: visualizing lateral movement opportunities by correlating SSH keys, connection logs, and host data across an engagement.
- One-click evidence collection — generate a sudo-free bash collector (
GET /ops/{op_id}/collection-script), run it on a host, and upload the resulting tarball (POST .../import-archive) for bulk ingest - 44 parsers across 49 evidence file types — SSH artifacts, system files (
passwd/shadow/sshd_config/…), command output (netstat/ss/iptables/…), and credential files (cloud, database, and app secrets) - Key-fingerprint pivot detection — correlates private keys,
authorized_keys, and connection logs into confirmed pivots - BFS pivot path-finding — shortest credential-backed path between two hosts (
POST /ops/{op_id}/graph/paths) - Host merge — collapse duplicate or placeholder hosts, manually or automatically
- Search, activity log, live updates — global search, an audit timeline, and WebSocket push so the whole team sees new data instantly
- Export / import — snapshot an operation and move it between servers
make runThis builds images, starts containers in the background, and attaches to logs. Ctrl+C stops log tailing — containers keep running.
- Frontend: http://localhost:3000
- Backend API: http://localhost:8000
buildx/bake error? If
make runfails with a Docker buildx or bake error, runcp .env.example .env(setsCOMPOSE_BAKE=false) and retry.
make up # start without rebuilding
make down # stop and remove containers
make logs # re-attach to logs
make backup # tar ./data/ with a timestamp
make test # run the pytest suite- Python 3.12+
- uv (Python package manager)
- Node.js 20+
cd backend
uv sync
uv run uvicorn main:app --reload --host 0.0.0.0 --port 8000
# or: make dev-backendAPI available at http://localhost:8000. Interactive docs at http://localhost:8000/docs.
cd frontend
npm install
npm run dev
# or: make dev-frontendDev server at http://localhost:5173 — proxies /api requests to localhost:8000.
make testAll persistent state lives in ./data/:
data/
├── tracker.db # SQLite database
└── uploads/ # Raw uploaded files (organized by op_id)
This directory is gitignored. To move the tool to another machine:
docker compose down
tar czf lockpick-backup.tar.gz data/
# Transfer and extract on new machine, then:
make runFull interactive docs (Swagger UI + ReDoc) are available at http://localhost:8000/docs when the backend is running.
See ARCHITECTURE.md for architecture rules and DATA_MODEL.md for the data model. See CLAUDE.md for build, test, and contribution conventions.
See CONTRIBUTING.md for the commit format and the pre-commit gate.
- Backend: Python 3.12, FastAPI, SQLAlchemy ORM, Alembic, uv
- Database: SQLite (single file in
./data/) - Frontend: React 18, Vite, TypeScript, react-force-graph-2d + d3-force
- Container: Docker Compose (multi-stage builds)
- Tests: pytest, httpx
This tool runs on a trusted network/VPN. The red team trusts each other. There is no login screen — open the URL and you're in.