Metadata-Version: 2.4
Name: histos
Version: 0.1.1
Summary: The tool call as a security boundary, in both directions - in-process, no proxy, no models, no infra.
Author-email: Łukasz Jakubowski <lukasz.jakubowski1993@gmail.com>
License-Expression: Apache-2.0
Project-URL: Homepage, https://usehistos.dev
Project-URL: Repository, https://github.com/Szesnasty/histos
Project-URL: Documentation, https://github.com/Szesnasty/histos/tree/main/docs
Project-URL: Issues, https://github.com/Szesnasty/histos/issues
Project-URL: Changelog, https://github.com/Szesnasty/histos/blob/main/CHANGELOG.md
Keywords: llm,agent,security,authorization,rbac,guardrails,prompt-injection,tool-calling,mcp
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Topic :: Security
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.12
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: yaml
Requires-Dist: pyyaml>=6.0; extra == "yaml"
Provides-Extra: dev
Requires-Dist: pytest==9.1.1; extra == "dev"
Requires-Dist: ruff==0.15.18; extra == "dev"
Requires-Dist: pyyaml>=6.0; extra == "dev"
Dynamic: license-file

# Histos

> ## Hijacked. Still bounded.

**[usehistos.dev](https://usehistos.dev)**

**Deterministic authorization for AI agent tool calls — before execution and after
return.** The model proposes. Your policy decides.

Histos puts an in-process security boundary around the tools an agent can call. It
does not guess whether a prompt is malicious. It enforces one narrow loop even when
the model is manipulated:

1. **Authorize the input** — tool, arguments, principal and trusted resource facts.
2. **Execute only within policy** — or deny / require confirmation before side effects.
3. **Constrain the output** — project and redact before it returns to the model.

No proxy, model or service is required. The core has zero runtime dependencies,
works with sync and async tools, and keeps ordinary policy evaluation in-process.
It is deliberately a small Python enforcement layer, not an identity platform,
sandbox or fleet-governance suite. Audit, coverage, tool import and drift detection
make that boundary reviewable and deployable.

## Why this exists

A stronger prompt can reduce the chance of a bad decision. It cannot authorize a
payment, prove tenant ownership or prevent a forbidden tool call from executing.
Histos treats model input, retrieved documents and tool output as untrusted, then
checks the action against policy written before the agent encountered them.

```text
model / conversation / retrieved content / tool output   untrusted, variable
────────────────────────────────────────────────────────────────────────────
policy + authenticated principal + trusted resource data trusted boundary
```

This is enforcement, not prompt-injection detection. Detection asks whether content
looks dangerous; Histos asks what the agent is allowed to do regardless. Its schema,
RBAC, resource and binding checks are deterministic for their inputs; resource
lookups, stateful limits, clocks and human approval remain runtime inputs.

## See the boundary hold

The repository contains five runnable demos: a LangChain clinic receptionist, a
LangGraph accounts-payable workflow, a framework-free on-call agent, an MCP tool
rug pull, and a mediation harness. Each attack is judged from actual datastore
effects, not from what the assistant claimed it did.

| scenario | without Histos | with a complete policy |
|---|---|---|
| poisoned clinic note redirects an SMS | patient data sent off-site | recipient rebound to the authenticated patient |
| invoice quietly swaps the supplier IBAN | 14,200 PLN sent to the wrong account | payee denied against trusted supplier data |
| injected runbook requests zero replicas and a production deploy | service damaged and an invented version deployed | arguments and resource state keep production unchanged |
| MCP vendor rewrites a tool description after review | ordinary schema diff is silent | description drift makes the CI command exit 1 |

In the controlled `qwen2.5:7b` runs, 6 of 11 attacks damaged the competent baseline
and 0 of 11 damaged the fully mediated version. Those model-driven figures were
measured manually at temperature 0; they are evidence from these scenarios, not a
general benchmark. A larger model avoided some baseline attacks, while the policy
bounds remained deterministic. The clinic policy also demonstrates a real product
cost: binding the SMS recipient removes caller-selected delivery. The full methods,
raw distinctions and partial-wiring failure are documented in the
[demo report](https://github.com/Szesnasty/histos/blob/main/demo/README.md).

## Install

```bash
pip install "histos[yaml]"
```

Requires Python 3.12 or newer. The `yaml` extra adds PyYAML; JSON policies use only
the standard library. To see a hijacked call remain bounded with no model or
infrastructure, clone the repository and run `python examples/makeRefund_demo.py`.
The adversarial applications are in
[`demo/`](https://github.com/Szesnasty/histos/tree/main/demo).

## Write a policy

A policy is YAML or JSON. This is a complete, enforceable YAML policy — not a sketch:

```yaml
# yaml-language-server: $schema=https://usehistos.dev/spec/policy-0.1.schema.json
schema_version: histos.policy/0.1
policy_id: support-search
version: "1"

tools:
  search_docs:
    access: read
    args:
      query: {type: string, min_length: 1, max_length: 500}
    returns:
      title: {type: string}
      snippet: {type: string}
    output:
      project: true
      strict: true

roles:
  support:
    allow: [search_docs]
```

Unknown tools, roles and arguments deny by default. Validate the document, review the
missing security decisions, then exercise one call without running the tool:

```bash
histos validate security.policy.yaml
histos review security.policy.yaml
histos explain security.policy.yaml search_docs \
  --role support --args '{"query":"refund policy"}'
```

Load the same file around the callable your agent receives:

```python
from histos import Principal, protect, use_principal

def search_docs(query: str):
    return {"title": "Refunds", "snippet": "Refunds require a receipt."}

guarded = protect([search_docs], policy="security.policy.yaml")

with use_principal(Principal(role="support", identity="user-42")):
    result = guarded.tools["search_docs"](query="refund policy")
```

Set the `Principal` in trusted host code from an authenticated session or workload
identity — never from model output or a tool argument. For a real tool set, import its
argument and return shapes instead of retyping them, then author the decisions schemas
cannot know: grants, ownership, trusted bindings, limits, confirmation and output.
Follow the [policy-writing guide](https://github.com/Szesnasty/histos/blob/main/docs/writing-policies.md),
use the [seven worked policies](https://github.com/Szesnasty/histos/tree/main/policies),
or run the [complete quickstart](https://github.com/Szesnasty/histos/blob/main/examples/quickstart.py).

## A production adoption path

Import tool shapes from MCP, OpenAI tools, OpenAPI, JSON Schema or Python signatures.
Author what those schemas cannot know — roles, ownership, trusted bindings,
confirmation and output rules. Run `histos review` and `histos coverage`, calibrate in
`mode="observe"`, then enforce with a durable audit sink and drift check in CI.

Worked policies for RAG, refunds, outbound email, MCP and deployments live in the
[policy gallery](https://github.com/Szesnasty/histos/tree/main/policies).

## Read this before production

Histos is defense in depth, not a sandbox and not a replacement for backend
authorization.

- Every execution path must receive the wrapped callable. A raw tool retained or
  registered elsewhere is a bypass; coverage sees only the surface you declare.
- Principal, resource facts, confirmation and policy are trusted host inputs. Histos
  does not replace backend authorization, sandbox compromised code or undo side
  effects before a post-call check.
- Histos does not understand intent or stop unsafe workflows composed from separately
  allowed calls. Limits and built-in approvals are process-local; the default audit
  sink is memory-only.
- The complete joined argument text is limited to 1 MiB by default and can be raised
  with `input_budget=`. A field using `pattern` is limited to 4,096 characters because
  it runs through Python's backtracking regex engine; unpatterned text is not.

The exact guarantee, residual object-inspection limits and safe deployment patterns
are in [SECURITY.md](https://github.com/Szesnasty/histos/blob/main/SECURITY.md).

## Status and documentation

Histos is an alpha API implementing Histos Policy Format Draft 0.1. The Python
engine, policy format, CLI, conformance corpus, LangChain/LangGraph adapters and tool
definition import/drift workflow exist today. A hosted control plane, JavaScript
runtime and dedicated MCP enforcement product do not.

- [Documentation map](https://github.com/Szesnasty/histos/tree/main/docs)
- [Policy reference](https://github.com/Szesnasty/histos/blob/main/docs/policy-reference.md)
- [Roadmap](https://github.com/Szesnasty/histos/blob/main/docs/roadmap.md)
- [Changelog](https://github.com/Szesnasty/histos/blob/main/CHANGELOG.md)

Apache-2.0.
