Metadata-Version: 2.5
Name: flyyy-guard
Version: 0.2.0
Summary: FLYYY prompt-injection guard: blocks malicious prompts before they reach your LLM.
Author: FLYYY
License-Expression: MIT
License-File: LICENSE
Keywords: ai-governance,guardrails,langchain,llm,prompt-injection
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Security
Requires-Python: >=3.10
Requires-Dist: requests>=2.31
Provides-Extra: dev
Requires-Dist: build>=1.2; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Requires-Dist: twine>=5; extra == 'dev'
Provides-Extra: langchain
Requires-Dist: langchain-core<2,>=1.0; extra == 'langchain'
Requires-Dist: langchain<2,>=1.0; extra == 'langchain'
Description-Content-Type: text/markdown

# flyyy-guard

Block prompt injection **anywhere it can enter your agent**, using FLYYY's guardrail check.

FLYYY's `/api/v1/guardrails/check` endpoint, where an LLM judge decides whether the text is an
attack, is called at three points of the agent loop:

| When | What is checked | Catches |
|---|---|---|
| Before the agent starts | the user's prompt | direct injection, jailbreaks, system-prompt extraction, obfuscated or multilingual attacks |
| Before each model call | every new tool result | indirect / RAG / web / email / API / tool-output injection, memory poisoning, cross-agent messages |
| Before each tool runs | the tool name and arguments the model chose | tool-selection and argument manipulation, data exfiltration, goal hijacking |

If any check blocks, the agent stops before the next model or tool step and returns a refusal.
Each check is listed in FLYYY → AI Governance → GenAI Governance → your project → **Guardrails**
with its category and subtype, where it was caught, the content's origin and trust, the
severity, the affected tool and the action taken.

Attack categories: `direct_injection`, `indirect_injection`, `tool_output_injection`,
`memory_poisoning`, `cross_agent_injection`, `tool_argument_manipulation`, `goal_hijacking`,
each with subtypes (for example `rag_document`, `api_response`, `data_exfiltration`).

## Quickstart (LangChain `create_agent`)

1. Install:
   ```bash
   pip install "flyyy-guard[langchain]"
   # or, in a uv project
   uv add "flyyy-guard[langchain]"
   ```
2. Add `FLYYY_URL` to the `.env` that already holds your project's Langfuse keys. The guard
   authenticates with those same keys, so no extra key is needed:
   ```
   LANGFUSE_PUBLIC_KEY=pk-lf-...
   LANGFUSE_SECRET_KEY=sk-lf-...
   FLYYY_URL=https://<your-flyyy-backend>
   ```
   If you were given a guardrail key (`FLYYY_GUARDRAIL_KEY=fg_...`), it is used instead.
3. Add the middleware where you create the agent:
   ```python
   from flyyy_guard import FlyyyGuardMiddleware

   agent = create_agent(
       model=model,
       tools=tools,
       system_prompt=system_prompt,
       checkpointer=checkpointer,
       middleware=[FlyyyGuardMiddleware()],
   )
   ```

Nothing else changes: `agent.invoke(...)`, your Langfuse `CallbackHandler`, tools and prompts stay as they are.
The conversation's `thread_id` is sent as the session id automatically.

When something is blocked, `agent.invoke` returns normally and the last message is
`"Your request was blocked by policy."`. Its `response_metadata["flyyy_guard"]` holds the
category, subtype, severity, where it was caught, the affected tool, the risk score and the
FLYYY request id. A blocked prompt or tool result is replaced in the stored conversation so it
is never sent to the model on later turns; a blocked tool call never runs.

Tell FLYYY what each tool returns, so the dashboard shows where an attack came from:
```python
FlyyyGuardMiddleware(tool_sources={
    "search_web": "web_page",
    "kb_search": "retrieved_document",
    "read_email": "email",
    "recall_memory": "memory",
    "ask_billing_agent": "agent_message",
})
```

Upgrading from 0.1.x: tool results and tool calls are now checked by default (one extra check
per tool result and per tool call). Remove `FlyyyToolOutputGuardMiddleware`, which is deprecated,
or pass `check_tool_outputs=False, check_tool_calls=False` to keep checking only the prompt.

## Any other framework

```bash
pip install flyyy-guard
```
```python
from flyyy_guard import check

decision = check(user_prompt, session_id=session_id)
if not decision.allowed:
    return "Your request was blocked by policy."
response = llm.invoke(user_prompt)
```
`check_or_raise(...)` does the same but raises `PromptBlockedError`.

Check other content before your agent acts on it:
```python
from flyyy_guard import FlyyyGuardClient

guard = FlyyyGuardClient()
guard.check(chunk, source="context", origin="retrieved_document", user_task=user_prompt)  # RAG chunk
guard.check(memory_text, source="context", origin="memory")                               # before saving memory
guard.check(api_body, source="tool_output", tool_name="crm_lookup", origin="api_response")
guard.check_tool_call("send_email", {"to": to, "body": body}, user_task=user_prompt)      # before running a tool
```

## Options

| Option | Default | Meaning |
|---|---|---|
| `FlyyyGuardMiddleware(block_message=...)` | `"Your request was blocked by policy."` | Reply returned when blocked |
| `FlyyyGuardMiddleware(unavailable_message=...)` | `"The safety check is unavailable right now..."` | Reply when the check itself failed (FLYYY unreachable, timeout, server error) |
| `FlyyyGuardMiddleware(rejected_message=...)` | `"This assistant's safety check is not configured correctly..."` | Reply when FLYYY rejected the agent's keys (wrong, revoked, or from another Langfuse) |
| `FlyyyGuardMiddleware(check_tool_outputs=...)` | `True` | Check every tool result before the model reads it |
| `FlyyyGuardMiddleware(check_tool_calls=...)` | `True` | Check every tool call (name + arguments) before the tool runs |
| `FlyyyGuardMiddleware(tool_sources={...})` | none | What each tool returns (`web_page`, `retrieved_document`, `email`, `api_response`, `memory`, `agent_message`, ...) |
| `FlyyyGuardMiddleware(redact_blocked=False)` | `True` | Keep the blocked text in conversation history |
| `FLYYY_GUARD_FAIL_OPEN=true` / `fail_open=True` | off | If FLYYY is unreachable, allow instead of block |
| `FLYYY_GUARD_TIMEOUT=10` / `timeout=10` | 10 seconds | How long to wait for FLYYY |

A rejected or revoked guardrail key always blocks, whatever the fail-open setting, so a
misconfiguration is noticed instead of silently turning protection off.

## Privacy

The guard sends only the text being checked, the session id and, for tool results and tool calls, the tool name
and the user's latest message (so FLYYY can tell whether the agent is being steered away from it). It never logs your key
or the prompt text. FLYYY stores a short preview of each checked input for the Guardrails view.
