Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
28 changes: 14 additions & 14 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,18 @@

GitHub Actions runs on every PR to `main` (`/.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`
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)

Expand Down Expand Up @@ -52,8 +52,8 @@ From repo root (`jakarta/`): `make check`, `make check-ci`, `make fix`, `make te

## Code conventions

- Import scanners from **`unplug.safeguards.*`** not `unplug.scanners.*` (deprecated shims)
- Fail closed: scanner/pipeline errors block, never allow silently
- 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
Expand Down
56 changes: 41 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,32 @@
# Unplug

**Agent runtime security for LLM applications.**
**Find the attack. Cut the attack. Keep the rest.**

Unplug tracks where text came from (user vs retrieved vs tool output), scans for prompt injection and destructive actions, and enforces tool-call policy with span-level redaction instead of binary blocking.
Unplug is agent runtime security for LLM applications. It tracks where text came from (user vs retrieved vs tool output), scans for prompt injection and destructive actions, and enforces tool-call policy, with span-level redaction instead of binary blocking.

**PyPI, Docker, and public release ship after the unplug-tiny model passes validation.** Install from source until then:
<p>
<a href="https://pypi.org/project/unplug-ai/"><img alt="PyPI" src="https://img.shields.io/pypi/v/unplug-ai"></a>
<a href="https://huggingface.co/spaces/Unplug-AI/unplug-tiny-demo"><img alt="Live demo" src="https://img.shields.io/badge/Live_demo-Hugging_Face_Space-22c55e"></a>
<a href="https://huggingface.co/Unplug-AI/unplug-tiny-v1"><img alt="Model" src="https://img.shields.io/badge/Model-unplug--tiny--v1-f59e0b"></a>
<a href="https://www.apache.org/licenses/LICENSE-2.0"><img alt="License" src="https://img.shields.io/badge/License-Apache_2.0-9ca3af"></a>
</p>

## Install

```bash
pip install unplug-ai # regex-only core, zero ML deps
pip install "unplug-ai[ml]" # add the ML span model
```

Or from source:

```bash
git clone https://github.com/UnplugAI/Unplug.git && cd Unplug/sdk && uv sync && uv pip install -e .
git clone https://github.com/UnplugAI/Unplug.git && cd Unplug/sdk
uv sync && uv pip install -e ".[ml]"
```

## Quickstart

```python
from unplug import Guard
from unplug.api.enums import Source
Expand All @@ -31,26 +48,35 @@ print(result.action) # review or block
print(result.findings) # evidence with span offsets
```

## What ships in 0.1.0
One line upgrades detection to the ML span model (downloads [unplug-tiny-v1](https://huggingface.co/Unplug-AI/unplug-tiny-v1) once, cached):

```python
guard = Guard.with_tiny()
```

Try it without installing anything: [live demo](https://huggingface.co/spaces/Unplug-AI/unplug-tiny-demo).

## What ships today

| Capability | Status |
|------------|--------|
| Regex + normalization injection detection | **Included** (fast, offline) |
| TaintedText provenance + session taint | **Included** |
| Tool-call enforcement (destructive block, tainted review) | **Included** |
| Span-level redaction | **Included** |
| DeBERTa span classifier (`pip install unplug-ai[ml]`) | **Preview in 0.2.0** |
| ML span model `Guard.with_tiny()` | **Preview** ([unplug-tiny-v1](https://huggingface.co/Unplug-AI/unplug-tiny-v1)) |
| Sliding-window long documents + streaming scan | **Included** |

Regex-only doc-level detection reaches roughly **F1 0.36 / recall 0.23** on held-out attacksfine as a first line, not sufficient alone. The span ML model (0.2.0) targets **~0.88 span F1** on internal holdout.
Regex-only doc-level detection reaches roughly **F1 0.36 / recall 0.23** on held-out attacks: fine as a first line, not sufficient alone. The ML span model's measured per-axis numbers (including failures) are on the [model card](https://huggingface.co/Unplug-AI/unplug-tiny-v1).

## Agent host checklist

1. Scan user input `guard.scan(text, source="user")`
2. Wrap untrusted content `guard.wrap_for_context(chunk, source="retrieved")`
3. After fetch tools `guard.notify_taint_source("web_fetch")`
4. Before every tool call `guard.check_tool_call(name, args)`
5. Scan agent output `guard.scan_output(text)`
6. Fresh user turn `guard.reset_session_taint()`
1. Scan user input: `guard.scan(text, source="user")`
2. Wrap untrusted content: `guard.wrap_for_context(chunk, source="retrieved")`
3. After fetch tools: `guard.notify_taint_source("web_fetch")`
4. Before every tool call: `guard.check_tool_call(name, args)`
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`), `unplug-audit`, and dev gates (`make check`, `make check-ci`).

Expand All @@ -63,8 +89,8 @@ make check-ci # lint + tests + exfil demo + security regression

## Related repos

- [unplug-mcp](https://github.com/UnplugAI/unplug-mcp) MCP server for Claude Code / Cursor
- [unplug-server](https://github.com/UnplugAI/unplug-server) self-hosted API (premium tiers, later)
- [unplug-mcp](https://github.com/UnplugAI/unplug-mcp): MCP server for Claude Code / Cursor
- [unplug-server](https://github.com/UnplugAI/unplug-server): self-hosted API (premium tiers, later)

## License

Expand Down
51 changes: 51 additions & 0 deletions assets/hf-org-card/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
---
title: README
emoji: 🔌
colorFrom: blue
colorTo: green
sdk: static
pinned: false
---

<p align="center">
<b style="font-size: 1.6em">Unplug - pull the plug on bad AI</b>
</p>

**Runtime defense layer for LLM apps and agents.** Unplug detects, localizes, and **redacts** prompt injection at the span level - instead of binary-blocking entire documents.

Untrusted text is everywhere in an LLM pipeline: user messages, RAG chunks, tool output, fetched web pages. One hidden instruction in any of them can hijack your agent. Unplug scans all of it, cuts out the attack, and keeps the rest usable.

## What we ship

| | |
| --- | --- |
| **[`unplug-ai` SDK](https://github.com/UnplugAI/Unplug)** | Guard pipeline: normalization, regex + ML scanners, taint tracking, tool-call gates, streaming scan, span redaction. Apache-2.0. |
| **[unplug-tiny-v1](https://huggingface.co/Unplug-AI/unplug-tiny-v1)** | Dual-head span detector (70M params): doc classifier decides *whether*, BIOES token head localizes *where*. Honest per-axis benchmarks on the card. |
| **[Live demo](https://huggingface.co/spaces/Unplug-AI/unplug-tiny-demo)** | Paste text, see span highlights + redacted output, compare against a regex-only baseline. |

## Why span-level?

Binary classifiers force a bad trade: block the whole document (lose the data) or allow it (eat the attack). Unplug's token head localizes the injected instruction to character offsets, so the pipeline redacts just that span - the rest of the document flows through.

## Get started

```bash
pip install "unplug-ai[ml]"
```

```python
from unplug import Guard

guard = Guard.with_tiny() # auto-downloads unplug-tiny-v1
result = guard.scan(untrusted_text)
if not result.safe:
use(result.redacted_text) # attack removed, content preserved
```

Agent kill-chain walkthrough: [hidden webpage injection -> tainted session -> blocked exfil tool call](https://github.com/UnplugAI/Unplug/blob/main/sdk/examples/agent_exfil_demo.py).

## Principles

- **Nothing enters as a raw string** - all text carries provenance and trust level.
- **Fail closed** - scanner errors block, never silently allow.
- **Honest numbers** - every published metric comes from a frozen eval harness on held-out data, including the axes we fail.
18 changes: 9 additions & 9 deletions sdk/PUBLISH.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
# Publish unplug-ai to PyPI

Package: **`unplug-ai`** · Import: **`from unplug import Guard`**
Package: **`unplug-ai`** | Import: **`from unplug import Guard`**

## One-time setup

1. Create a [PyPI account](https://pypi.org/account/register/) (org account recommended).
2. Create an API token with **Upload** scope for project `unplug-ai` (or entire account for first release).
3. In [UnplugAI/Unplug](https://github.com/UnplugAI/Unplug) **Settings → Secrets → Actions**, add:
2. Create an API token with **Upload** scope for project `unplug-ai`.
3. In [UnplugAI/Unplug](https://github.com/UnplugAI/Unplug) -> **Settings -> Environments -> `pypi`**, add:

| Secret | Value |
|------------------|--------------|
| `PYPI_API_TOKEN` | `pypi-...` |
| Secret | Value |
|--------------|--------------|
| `PYPI_TOKEN` | `pypi-...` |

## Publish

**CI (recommended):** Actions **Publish to PyPI** Run workflow
Or tag a GitHub Release workflow runs on `release: published`.
**CI (recommended):** Actions -> **Publish to PyPI** -> Run workflow
Or tag a GitHub Release - workflow runs on `release: published`.

**Local:**

Expand All @@ -29,5 +29,5 @@ UV_PUBLISH_TOKEN=pypi-... uv publish

## After publish

- Site links: `pip install unplug-ai` https://pypi.org/project/unplug-ai/
- Site links: `pip install unplug-ai` -> https://pypi.org/project/unplug-ai/
- Bump `sdkVersion` in `unplug-site/public/js/core/site-config.jsx` when releasing new versions.
Loading
Loading