Metadata-Version: 2.4
Name: handcuff
Version: 0.2.0
Summary: A dashcam for your AI agents: local, tamper-evident recorder for process/file/network activity, with a LangChain/LangGraph drop-in prompt-injection circuit breaker.
Project-URL: Homepage, https://github.com/Mister2005/Handcuff
Project-URL: Repository, https://github.com/Mister2005/Handcuff
Project-URL: Issues, https://github.com/Mister2005/Handcuff/issues
Author: Varun Gupta
License: MIT
Keywords: agents,langchain,langgraph,observability,prompt-injection,security
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: Microsoft :: Windows
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Security
Classifier: Topic :: Software Development :: Libraries
Requires-Python: >=3.10
Requires-Dist: cryptography>=42.0
Requires-Dist: httpx>=0.27
Requires-Dist: pillow>=10.0
Requires-Dist: psutil>=5.9
Requires-Dist: pyfiglet>=1.0
Requires-Dist: python-ulid>=2.2
Requires-Dist: rich-pixels>=3.0
Requires-Dist: ruamel-yaml>=0.18
Requires-Dist: textual>=0.58
Requires-Dist: typer>=0.12
Requires-Dist: watchdog>=4.0
Provides-Extra: dev
Requires-Dist: mypy>=1.10; extra == 'dev'
Requires-Dist: pip-audit>=2.7; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest>=8.2; extra == 'dev'
Requires-Dist: ruff>=0.5; extra == 'dev'
Provides-Extra: langchain
Requires-Dist: langchain-core>=0.2; extra == 'langchain'
Description-Content-Type: text/markdown

# Handcuff

**A dashcam for your AI agents.** Handcuff records the OS-level side effects of an AI agent — processes spawned, files written, network connections opened — into a local, tamper-evident, hash-chained SQLite timeline you can replay, search, export, and verify. Real-time policy alerts flag risky behavior (sudo, mass deletes, writes outside the workspace, unknown domains, force pushes) while the agent runs.

**Local-only by design:** nothing ever leaves your machine except an optional, user-configured, content-light webhook (HTTPS required). This is enforced by a `test_no_egress` release-gate test.

> **Platform note:** the current implementation targets **Windows** (psutil + watchdog capture, Windows ACL/Firewall enforcement, PyInstaller binary). The original planning docs describe a Linux-first design; that pivot is deliberate. Most capture code is psutil-generic, so Linux/macOS support is feasible but currently untested.

## Install

```powershell
# From PyPI (Python 3.11+) — note the distribution name; the import
# name and CLI command are still `handcuff` either way.
pip install handcuff
# with the LangChain/LangGraph integration:
pip install "handcuff[langchain]"

# From source, for development
pip install .

# Or use the prebuilt binary
dist\handcuff.exe --help
```

## Quickstart

```powershell
# Record an agent (or any command) and everything it does
handcuff watch -- python my_agent.py

# List recorded sessions, then open the live dashboard
handcuff sessions
handcuff tui

# Replay a past session, jumping between alerts
handcuff replay <SESSION_ID>

# Export a report (json | md | html) and verify its hash chain
handcuff export <SESSION_ID> --format html
handcuff verify <SESSION_ID>

# Attach to something already running
handcuff attach <PID>

# Environment diagnostics
handcuff doctor
```

## Key features

- **Tamper-evident timeline** — every event is SHA-256 hash-chained; session heads are Ed25519-signed. `handcuff verify` re-validates the chain from the DB or from a JSON export.
- **Capture** — process spawn/exit (psutil), file write/delete/rename (watchdog), network connects (psutil). Optional `--hash-content` records a SHA-256 of written files (≤1 MiB; hash only, never content).
- **Rules & alerts** — safe expression DSL (AST-evaluated, no `eval`), windowed rules, default ruleset, trust allowlist (`handcuff rules trust add domain example.com`), optional webhook on critical alerts.
- **Redaction** — secrets (API keys, tokens) are scrubbed before events are persisted.
- **Enforcement (opt-in, Windows)** — `watch --enforce` applies real OS-level blocking: Deny-ACLs on sensitive files (no admin needed) and, if elevated, a Windows Firewall outbound block with `--allow-domain` exceptions. Always reverted on exit.
- **Cooperative gateway + prompt-injection circuit breaker** — `handcuff.gateway.Gateway` is a validating proxy for agent frameworks that route their file/network tool calls through it (same trust model as an OpenAI/LangChain/MCP tool sandbox — see its docstring for exact scope, it cannot stop raw shell/socket access). Every fetched URL (and any file read marked `untrusted=True`) is scanned with a heuristic prompt-injection signature scanner (`handcuff.rules.injection`) covering instruction-override phrasing, fake system/assistant turn markers, jailbreak personas, data-exfiltration instructions, and obfuscation tells (hidden Unicode, suspicious base64 blobs). A match "taints" the session for a configurable window and the gateway then **blocks further network egress** — the deterministic mitigation for Simon Willison's "lethal trifecta" (private data access + untrusted content + external communication), rather than trying to out-guess the injection semantically. This is a regex/heuristic tripwire, not a semantic guarantee — a novel or paraphrased injection can still slip past it; treat a miss as "not a known pattern" rather than "safe."

  ```python
  from pathlib import Path
  from handcuff.gateway.gateway import Gateway, GatewayDenied

  with Gateway(workspace_root=Path("./agent_workspace")) as gw:
      page = gw.http_request("https://example.com/some-doc")  # scanned automatically
      if gw.is_tainted:
          print("suspected prompt injection:", gw.taint_info.reason)
      try:
          gw.http_request("https://wherever-the-doc-said-to-send-data.example")
      except GatewayDenied as e:
          print("blocked:", e)  # circuit breaker fired
  ```
- **TUI dashboard** — trust meter, flagged-event cards, live monitoring, recommendation panel (Textual).
- **Exports** — JSON (with embedded verification block), Markdown, and self-contained HTML reports.
- **Multi-agent framework integration (LangChain / LangGraph), one line of code** — `handcuff.integrations.langchain.HandcuffHandler` is a real `langchain-core` `BaseCallbackHandler`. Pass it as `callbacks=[handler]` to any chain, agent, or graph and it records every chain/tool/retriever call as a hash-chained event **and** runs the same injection scanner + taint circuit breaker as the Gateway, scoped per agent. It works for concurrent/parallel agents (multiple LangGraph nodes, `ThreadPoolExecutor`, `asyncio.gather`) because it keys state off LangChain's own `run_id`/`parent_run_id` run tree — the same mechanism LangChain uses internally to keep parallel runs from crossing wires — with all shared state protected by a lock for real cross-thread safety. `pip install handcuff[langchain]` to pull in `langchain-core`. Verified against the real package end-to-end (parallel `ThreadPoolExecutor` agents, real `@tool`s, real blocking) — see `tests/test_langchain_real.py`.

  ```python
  from handcuff.integrations.langchain import HandcuffHandler

  handler = HandcuffHandler()  # writes to the same DB `handcuff tui` watches
  result = my_graph.invoke(inputs, config={"callbacks": [handler]})
  handler.close()
  ```

  Run `handcuff tui` in another terminal while your agent(s) run — it polls the same DB and picks the session up live automatically, no extra wiring. Honest scope: this only sees what happens through LangChain's own abstractions (chains/tools/retrievers) — a tool implementation that shells out or calls `requests` directly with no LangChain wrapper around it never fires these callbacks, and taint is scoped to one run tree (one top-level `.invoke()`), not the whole process.

## Configuration

```powershell
handcuff config list
handcuff config set retention_days 14
handcuff config set webhook_url https://hooks.example.com/handcuff
```

Config lives at `~/.config/handcuff/config.yaml`; the DB at `~/.local/share/handcuff/handcuff.db`; the signing key at `~/.config/handcuff/ed25519.key`.

## Development

```powershell
python -m venv .venv && .venv\Scripts\pip install -e .[dev,langchain]
.venv\Scripts\python -m pytest tests -q     # ~180 tests
.venv\Scripts\ruff check handcuff tests
scripts\build_windows.ps1                    # PyInstaller binary
```

Security release gates (must always pass): no-egress, chain-tamper detection, redaction, DSL safety, watchdog reconcile.

## Documentation

Planning and design docs live in the repo root: [PRD](01_PRD.md), [TRD](02_TRD.md), [Security](03_Security_Access.md), [Implementation Plan](04_Implementation_Plan.md), [Tickets](05_Tickets.md), and the TUI spec (`Handcuff_SPEC_Part1-4.md`).
