Skip to content
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 36 additions & 31 deletions .cursorrules
Original file line number Diff line number Diff line change
Expand Up @@ -5,44 +5,47 @@ Runtime security SDK for LLM applications. Scans text for prompt injection, dest

## Repository Layout
```
sdk/ # Python SDK (pip install unplug) — THIS IS THE MAIN PACKAGE
sdk/ # Python SDK (pip install unplug-ai) — THIS IS THE MAIN PACKAGE
src/unplug/
guard.py # Entry point: Guard class
models.py # Shared Pydantic schemas (Finding, ScanResult, Action, Source)
client.py # HTTP client for server mode
exceptions.py # Exception hierarchy
safeguards/ # CANONICAL threat scanners + registry
base.py # BaseScanner, RegexScanner, ModelScanner, Scanner protocol
registry.py # SafeguardRegistry (ScannerRegistry alias)
injection/ # Regex injection + patterns
injection_ml.py # ML span scanner (dual-head DeBERTa)
destructive.py # SQL, shell, git, file ops
leakage.py # API keys, PII, prompt leak
harmful.py # dangerous, self-harm, illegal
financial.py # crypto, payments, amount thresholds
secrets.py # registry-based exact-match detection
scanners/ # DEPRECATION SHIMS → unplug.safeguards.*
audit/ # unplug-audit runner + probe batteries
ml/ # ModelStore, catalog, download-once cache
cli/ # unplug-audit, unplug-models CLIs
core/
taint.py # TaintedText, TrustLevel (6 levels), Tagger
context.py # ExecutionContext, ToolCall, session tracking
secrets.py # SecretsRegistry, SecretsSanitizer
normalize.py # 12-stage text normalizer with span mapping
config.py # GuardConfig, PipelineConfig, ScannerConfig (Pydantic)
config_loader.py # TOML file loading + env var overrides
model_runtime.py # active_model resolution, HF cache integration
logging.py # Correlation IDs via contextvars
models.py # ModelProvider ABC, ModelRegistry, ModelSpec (Pydantic)
stats.py # MetricsCollector, ScannerStats, PipelineStats (Pydantic)
judge.py # JudgeProvider protocol, CallableJudge (BYOLLM)
content.py # ContentProvider protocol, ScrapedContent
limits.py # LimitConfig — input length + tool permissions
scanners/
base.py # BaseScanner, RegexScanner, ModelScanner, Scanner protocol
injection.py # 6 regex patterns + 12-stage normalization
destructive.py # 8 patterns (SQL, shell, git, file ops)
leakage.py # 9 patterns (API keys, PII, prompt leak)
harmful.py # 3 patterns (dangerous, self-harm, illegal)
financial.py # Crypto, payments, amount thresholds
secrets.py # Registry-based exact-match detection
pipelines/
base.py # BasePipeline with fail-closed, timing, redaction
input.py # InputPipeline (taint → normalize → scan → decide)
output.py # OutputPipeline (secrets + leakage)
toolcall.py # ToolCallPipeline (destructive + financial + taint)
benchmarks/ # Dataset evaluation framework
loader.py # JSONL/CSV/Parquet loaders
evaluate.py # Precision/recall/F1 per category
builtin_samples.py # Built-in smoke test samples
run.py # CLI runner
tests/ # 317 tests, all passing
tests/ # 535+ tests, all passing
.context/ # Gitignored — strategy, architecture, research docs
.claude/rules/ # Behavioral rules for Claude Code
```
Expand All @@ -54,14 +57,17 @@ Server, MCP, and site live in separate repos:

## Commands
```bash
cd sdk && uv sync --all-extras # install
cd sdk && uv run pytest -v # test
cd sdk && uv run ruff check . # lint
cd sdk && uv run ruff format . # format
cd sdk && uv sync --all-extras --dev # install
cd sdk && make check # lint + format + pytest
cd sdk && make check-ci # CI parity (exfil demo + security subset)
cd sdk && make fix # auto-fix lint + format
cd sdk && uv run pytest -v # verbose tests
cd sdk && uv run unplug-audit # wiring + ML health checks
```

## Architecture Rules (STRICT)
- Guard → Pipelines → Scanners → Core (never skip layers)
- Guard → Pipelines → Safeguards → Core (never skip layers)
- Import scanners from `unplug.safeguards.*` (not `unplug.scanners.*`)
- Nothing enters as raw string — everything is TaintedText
- Scanners receive TaintedText + ExecutionContext, never raw str
- Fail closed: scanner/pipeline errors → block, never allow silently
Expand Down Expand Up @@ -96,13 +102,12 @@ except Exception as exc:
- Never expose: competitor names, model details, infrastructure, business strategy
- No "based on analysis", "after discussing", "AI-assisted" in any public output

## Current State (2026-05-20)
- Branch: v2-enforcement-layer (17 commits ahead of main, not merged)
- 317 tests passing, all lint clean
## Current State (2026-06-01)
- 535+ tests passing, ruff clean, `make check-ci` mirrors GitHub Actions
- Safeguards migration complete; `unplug.scanners.*` shims until major version
- Model catalog + download-once cache + `unplug-models` CLI + `unplug-audit` ML checks
- SDK refactor complete: Pydantic config, fail-closed, logging, TOML config
- Base classes added: JudgeProvider (BYOLLM), ContentProvider, LimitConfig
- Evaluation framework ready in benchmarks/
- Baseline eval on built-in samples: 92.3% F1, 0% FPR
- Agent hardening: boundaries, trajectory, intent gate, degradation, Hermes patterns

## Research Context (read before making design decisions)
All verified research lives in `.context/research/`:
Expand All @@ -116,9 +121,9 @@ All verified research lives in `.context/research/`:
Also read: `.context/design-next-phase.md` (synthesized design doc with all decisions)

## What's Next
1. Merge v2-enforcement-layer to main
2. Download real datasets (neuralchemy 22K, microsoft 208K) and run evaluation
3. Wire LimitConfig + JudgeProvider into Guard (base classes exist, need integration)
4. Expand regex patterns based on evaluation gaps
5. Server hardening (unplug-server repo): middleware, auth, Docker
6. MCP server implementation
1. Golden eval green → publish HF checkpoint + update catalog revision pin
2. Wire ABSTAIN band from training into SDK `Action` + `injection_ml` policy
3. Real dataset eval (neuralchemy, microsoft) and pattern expansion
4. Server hardening (unplug-server repo): middleware, auth, Docker
5. MCP server implementation
6. Remove `unplug.scanners.*` shims at major version bump
6 changes: 6 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ jobs:
- name: Tests
run: uv run pytest -q

- name: Exfil demo gate (killer scenario must block)
run: |
uv run pytest tests/test_exfil_demo_integration.py -q
uv run python examples/agent_exfil_demo.py

- name: Security regression
run: |
uv run pytest \
Expand All @@ -46,4 +51,5 @@ jobs:
tests/test_scan_policy.py \
tests/test_security_stress.py \
tests/test_sdk_coverage.py \
tests/test_agent_hardening.py \
-q
9 changes: 8 additions & 1 deletion .github/workflows/publish-pypi.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
name: Publish to PyPI

# v0.1.0 ships regex + tool enforcement as the default (no bundled model), so it can
# publish now. The optional ML extra later points at a validated DeBERTa-v3-xsmall
# dual-head checkpoint whose metrics are gated by the golden harness.
#
# Token: GitHub Environment "pypi", secret "PYPI_TOKEN".

on:
release:
types: [published]
Expand All @@ -11,6 +17,7 @@ permissions:
jobs:
publish:
runs-on: ubuntu-latest
environment: pypi
defaults:
run:
working-directory: sdk
Expand All @@ -35,5 +42,5 @@ jobs:

- name: Publish unplug-ai
env:
UV_PUBLISH_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
UV_PUBLISH_TOKEN: ${{ secrets.PYPI_TOKEN }}
run: uv publish
17 changes: 12 additions & 5 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,26 @@ LLM defense layer — SDK package.

```bash
make install # install SDK
make check # lint + format --check + full pytest
make check-ci # CI parity: check + exfil demo + security subset
make fix # auto-fix lint + format
make test # run all tests
make test-security # security regression subset (verbose)
make lint # ruff check
make format # ruff format

cd sdk && uv sync --all-extras
cd sdk && uv run pytest -v
cd sdk && uv run pytest tests/test_file.py -v
cd sdk && uv run ruff check . && uv run ruff format --check .
cd sdk && uv sync --all-extras --dev
cd sdk && make check-ci
```

## Structure

- `sdk/` — Python SDK (`pip install unplug`)
- `sdk/` — Python SDK (`pip install unplug-ai`, import `unplug`)
- `src/unplug/safeguards/` — **canonical** threat scanners + registry
- `src/unplug/scanners/` — deprecation shims only (remove at major version)
- `src/unplug/pipelines/` — input, output, toolcall pipelines
- `src/unplug/audit/` — `unplug-audit` wiring + probe batteries
- `src/unplug/ml/` — model cache, catalog, download-once store

Server, MCP, and site live in separate repos:
- [unplug-server](https://github.com/chiruu12/unplug-server)
Expand Down
42 changes: 38 additions & 4 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,53 @@

## CI

GitHub Actions runs on every PR to `main`:
GitHub Actions runs on every PR to `main` (`/.github/workflows/ci.yml`):

- `sdk/`: ruff + pytest (`/.github/workflows/ci.yml`)
1. **Ruff** — `ruff check .` + `ruff format --check .`
2. **Tests** — full pytest suite (`pytest -q`)
3. **Exfil demo gate** — `test_exfil_demo_integration.py` + `examples/agent_exfil_demo.py`
4. **Security regression** — explicit subset:
- `test_adversarial.py`
- `test_false_positives.py`
- `test_encodings.py`
- `test_secrets.py`
- `test_scan_policy.py`
- `test_security_stress.py`
- `test_sdk_coverage.py`
- `test_agent_hardening.py`

## Local checks (SDK)

```bash
cd sdk
uv sync --all-extras --dev
uv run ruff check . && uv run ruff format . # auto-fix locally; CI uses format --check
uv run pytest -q

# Fast local gate (lint + format + full pytest)
make check

# Exact CI parity before PR (includes exfil demo + security subset above)
make check-ci

# Auto-fix formatting and safe lint fixes
make fix # ruff check --fix + ruff format

# Individual targets
make lint # ruff check only
make format # ruff format only
make test # pytest -v
make test-security # security subset + test_financial (verbose)
make audit # unplug-audit wiring
make audit-ml # unplug-audit --require-ml
```

From repo root (`jakarta/`): `make check`, `make check-ci`, `make fix`, `make test`.

## Code conventions

- Import scanners from **`unplug.safeguards.*`** — not `unplug.scanners.*` (deprecated shims)
- Fail closed: scanner/pipeline errors → block, never allow silently
- All new modules: `from __future__ import annotations`, typed params/returns, Pydantic models

## Agent integration

When adding scanner or pipeline behavior, read the **agent host checklist** in [`sdk/README.md`](sdk/README.md) and run `unplug-audit` (plus `--probes` when touching detection).
Expand Down
25 changes: 22 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
.PHONY: install test test-security lint format
.PHONY: install test test-security lint format fix check check-ci

install:
cd sdk && uv sync --all-extras
cd sdk && uv sync --all-extras --dev

test:
cd sdk && uv run pytest -v

test-security:
cd sdk && uv run pytest tests/test_adversarial.py tests/test_false_positives.py tests/test_encodings.py tests/test_secrets.py tests/test_scan_policy.py tests/test_security_stress.py tests/test_sdk_coverage.py tests/test_financial.py -v
cd sdk && uv run pytest \
tests/test_adversarial.py \
tests/test_false_positives.py \
tests/test_encodings.py \
tests/test_secrets.py \
tests/test_scan_policy.py \
tests/test_security_stress.py \
tests/test_sdk_coverage.py \
tests/test_agent_hardening.py \
tests/test_financial.py \
-v
@if [ -f ../repos/unplug_exp/scripts/eval_sdk_security.py ]; then \
cd ../repos/unplug_exp && uv run python scripts/eval_sdk_security.py --sdk ../../jakarta/sdk; \
fi
Expand All @@ -17,3 +27,12 @@ lint:

format:
cd sdk && uv run ruff format .

fix:
cd sdk && uv run ruff check --fix . && uv run ruff format .

check:
cd sdk && uv run ruff check . && uv run ruff format --check . && uv run pytest -q

check-ci:
cd sdk && make check-ci
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,14 @@ Regex-only doc-level detection reaches roughly **F1 0.36 / recall 0.23** on held
5. Scan agent output — `guard.scan_output(text)`
6. Fresh user turn — `guard.reset_session_taint()`

See [sdk/README.md](sdk/README.md) for config (`unplug.toml`) and `unplug-audit`.
See [sdk/README.md](sdk/README.md) for config (`unplug.toml`), `unplug-audit`, and dev gates (`make check`, `make check-ci`).

## Development

```bash
cd sdk && uv sync --all-extras --dev
make check-ci # lint + tests + exfil demo + security regression
```

## Related repos

Expand Down
15 changes: 12 additions & 3 deletions context/product/decisions.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,18 @@
- **Session policy:** existing CaMeL-lite taint + tool profiles unchanged; now complemented by trajectory + intent gates
- **Crescendo:** `TrajectoryConfig` — escalating `risk_trajectory` slope → REVIEW/BLOCK findings on all pipelines
- **Intent:** `IntentConfig` — informational user intent + side-effect tool → REVIEW
- **Hermes/persona:** named persona + red-team regex patterns in injection scanner
- **Out of SDK scope:** container sandbox, channel pairing, filesystem/network egress (host responsibility)
- **Detail:** `.context/research/openclaw-agent-security.md`
- **Hermes Agent (NousResearch):** `scan_context_file()`, Hermes-aligned injection patterns, assembled-prompt scan guidance
- **Hermes jailbreak persona:** `named_persona_hermes` + red-team regex (attack pattern, not the agent product)
- **Out of SDK scope:** container sandbox, channel pairing, skills hub trust tiers, cron scheduler (host responsibility)
- **Detail:** `jakarta/sdk/docs/HERMES_AGENT_SECURITY.md`, `docs/AGENT_FLOW_SECURITY.md`

## 2026-06-01: SDK cleanup — safeguards, audit, CI parity
- **Safeguards migration:** canonical scanners in `unplug.safeguards.*`; `unplug.scanners.*` are deprecation shims until major version
- **Audit ML checks:** `unplug-audit` splits checkpoint found vs configured vs active (`ml_checkpoint`, `ml_configured`, `ml_active`); `--require-ml` gates all three
- **Path auto-wire:** `UNPLUG_MODEL_PATH` alone sets `active_model=tiny` in config loader
- **CI / local gates:** `make check` (lint + full pytest), `make check-ci` (CI parity incl. exfil demo + security subset with `test_agent_hardening`)
- **Lint:** Ruff with extended rules; `make fix` for auto-format
- **Detail:** `jakarta/sdk/docs/SDK_HARDENING_PLAN.md`, `CONTRIBUTING.md`

- **Question:** Use Microsoft Presidio to filter agent-exposed PII?
- **Answer:** Useful as optional server-side benchmark / interim `PrivacyFilterService` backend only — not a replacement for Unplug or the planned unplug-safeguard PF head.
Expand Down
24 changes: 13 additions & 11 deletions context/product/plans/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,25 @@ Building an LLM defense layer that stops prompt injection, destructive agent act

## 3-Stage Detection Pipeline

> Status legend: [implemented] ships today · [planned] designed, not built · [target] a goal with a current measured number, not a claim. All metrics come from the golden eval harness (`unplug_exp/scripts/golden_eval.py`) — see `BENCHMARKS.md`. No hand-typed numbers.

```
Stage 1: Regex + Heuristics (<1ms)
Stage 1: Regex + Heuristics (<1ms) [implemented]
├── 12 normalization stages (leetspeak, zero-width, homoglyphs, base64, etc.)
├── 245+ patterns across 15 languages
├── 29 injection patterns (English) + destructive/leakage/harmful/financial scanners
│ (regex alone is a sub-millisecond pre-filter, NOT a standalone product)
├── Produces span offsets of suspicious regions
└── High-confidence → short-circuit, return immediately

Stage 2: ML Classifier (5-15ms)
├── ONNX-quantized ModernBERT or DeBERTa
├── Loaded once in lifespan, reused across requests
├── CPU-optimized, runs via run_in_threadpool
└── Confidence > 0.8 → short-circuit
Stage 2: ML Classifier (5-15ms) [implemented: transformers · planned: ONNX export]
├── DeBERTa-v3-xsmall dual-head (doc classifier + token/BIOES span tagger)
├── Runs via the optional ml extra (transformers today; ONNX/INT8 export planned)
├── Loaded once, reused across requests
└── Doc head → detection recall; token head → span localization / redaction

Stage 3: LLM Judge (500ms-2s, ~5% of requests)
├── Local small model (Qwen-0.6B or similar via MLX)
├── Structured CoT reasoning
├── FAISS embedding similarity against attack corpus
Stage 3: LLM Judge (500ms-2s, ~5% of requests) [planned]
├── BYOLLM JudgeProvider (CallableJudge), disabled by default
├── Structured reasoning on the ~5% ambiguous middle band
└── Hard negative mining: confirmed benign → feed back to Stage 2
```

Expand Down
2 changes: 1 addition & 1 deletion context/product/plans/unplug-span-pipeline-spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
## Non-goals (v1)

- **Local ML in the pip package** (no transformers/onnxruntime required for default install).
- Generative SLM (Gemma/LFM) as primary redaction engine.
- Generative SLM (Gemma/LFM) as primary redaction engine — aspirational only; NOT implemented and not on the v1 path. The shipped model is the DeBERTa-v3-xsmall dual-head encoder.
- Training span models on pure encoding tricks (Base64, leet, etc.) — handled by normalize + Prompt Guard.
- Mandatory scan of all `retrieved` content — **callers choose** `source` and `scanners`.
- Session-level / crescendo classifier (infra only).
Expand Down
Loading
Loading