Metadata-Version: 2.5
Name: hardn-runtime
Version: 1.5.2
Summary: Hardn runtime protection for Python: label agent traffic, serve honeytokens, screen prompt injection, report to Hardn.
Project-URL: Homepage, https://hardn.si
Project-URL: Documentation, https://hardn.si/developers#runtime
Author-email: Hardn <support@hardn.si>
License: Proprietary
Keywords: hardn,honeytoken,middleware,prompt-injection,security
Classifier: License :: Other/Proprietary License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Security
Requires-Python: >=3.9
Description-Content-Type: text/markdown

# Hardn runtime protection (Python)

A small, **defensive** middleware you run inside your own app. It:

- **labels automated-agent traffic** from request headers and path (`score_agent`),
- **serves honeytokens** — decoy values and paths a real user never touches (`Honeytokens`; `is_token()` takes any
  value a visitor sent, non-ASCII text included, and never raises),
- **screens text for prompt-injection** patterns before it reaches an LLM (`scan_prompt`),
- **reports** these signals to Hardn (`Telemetry`) so you see them on your dashboard.

It never modifies your data and only calls out to Hardn's telemetry endpoint. Telemetry is fail-open: a
network problem drops a batch of signals, it never breaks or slows a request (`record()` only buffers; one
background thread sends). Client IPs are hashed before they leave your process: HMAC-SHA256 keyed with your
API key, so an address hashes the same way across your processes and hosts, and nobody without the key can test
addresses against the hashes (1.5.1; earlier versions used one fixed salt, so an address's hash changes once
when you upgrade, and again if you rotate the key).

```bash
pip install hardn-runtime
```

## Flask example

```python
from flask import Flask, request, abort
from hardn_runtime import Telemetry, Honeytokens, score_agent, scan_prompt

app = Flask(__name__)
tel = Telemetry(api_key="hk_live_…", source="web-1")
honey = Honeytokens()

@app.before_request
def guard():
    sig = score_agent(user_agent=request.headers.get("User-Agent"), path=request.path,
                      accept=request.headers.get("Accept"))
    if sig.is_agent:
        tel.record("agent_detected", ip=request.remote_addr, path=request.path, score=sig.score,
                   detail={"reasons": sig.reasons})
    if honey.is_decoy_path(request.path):
        tel.record("honeytoken_hit", ip=request.remote_addr, path=request.path, score=100)
        abort(404)  # behave like any other missing page

@app.post("/chat")
def chat():
    result = scan_prompt(request.json.get("message", ""))
    if result.flagged:
        tel.record("injection_blocked", score=result.score, detail={"tactics": result.tactics})
        # strip / review / reject as your policy requires
    ...
```

## FastAPI / Starlette

Use the same calls from an `http` middleware or dependency; `score_agent` and `scan_prompt` are plain
functions, and `Telemetry.record` is safe to call from async code: it only buffers, and never waits for the
network.

## What gets sent

Each signal is `{kind, source, ip_hash, path, score, detail}` where `kind` is `agent_detected`,
`honeytoken_hit`, `injection_blocked` or `heartbeat`. Every `flush_every` signals (default 20, at most 5,000) a
background thread sends the batch (at most 500 a request), and however quiet your app, whatever is buffered goes once
its oldest signal has waited a minute; what is still buffered when the process exits is sent on the way out. Where exit hooks don't run (a serverless handler, `os._exit`), call `tel.flush()` yourself: it sends
now and returns `True` once Hardn stored it. While Hardn can't be reached, at most 5,000 signals wait; newer ones
are dropped. Pre-fork servers (gunicorn, uWSGI) are fine: each worker sends its own.
