Metadata-Version: 2.5
Name: rogue-security
Version: 1.1.0
Summary: Official Python SDK for the Rogue Security AppSec evaluation API.
Project-URL: Homepage, https://rogue.security
Project-URL: Repository, https://github.com/qualifire-dev/qualifire
Author-email: Rogue Security <team@rogue.security>
License: Proprietary
Keywords: ai-security,appsec,evaluation,guardrails,llm,rogue
Requires-Python: >=3.9
Requires-Dist: httpx>=0.27
Requires-Dist: pydantic>=2.7
Requires-Dist: tenacity>=8.2
Provides-Extra: litellm
Requires-Dist: litellm[proxy]>=1.95.1; extra == 'litellm'
Description-Content-Type: text/markdown

# rogue-security

Official Python SDK for the [Rogue Security](https://rogue.security) AppSec evaluation API.

Wraps the two public AppSec methods, `evaluate` (inline checks) and `invoke` (saved
guardrail), with typed params and responses (Pydantic v2), typed errors, automatic
retries, and debug logging. Ships sync and async clients.

## Install

```bash
pip install rogue-security
```

## Quickstart

```python
from rogue_security import RogueClient

with RogueClient(api_key="rsk_...") as rogue:
    result = rogue.evaluate(
        messages=[
            {"role": "user", "content": "What is the patient's SSN?"},
            {"role": "assistant", "content": "It is 123-45-6789."},
        ],
        pii_check=True,
        prompt_injections=True,
    )

print(result.status, result.score)
for group in result.evaluation_results:
    for r in group.results:
        if r.flagged:
            print(group.type, r.label, r.reason)
```

`api_key` falls back to the `ROGUE_API_KEY` environment variable if omitted.

### Async

```python
from rogue_security import AsyncRogueClient

async with AsyncRogueClient() as rogue:        # reads ROGUE_API_KEY
    result = await rogue.evaluate(
        messages=[{"role": "user", "content": "..."}],
        content_moderation_check=True,
    )
```

### Invoke a saved guardrail

```python
result = rogue.invoke(
    guardrail_id="gr_abc123",
    messages=[
        {"role": "user", "content": user_prompt},
        {"role": "assistant", "content": model_response},
    ],
)
if result.blocked:
    ...  # the guardrail's workspace is set to block and the evaluation failed
```

You can also pass a typed params object instead of keyword arguments:

```python
from rogue_security import EvaluateParams

rogue.evaluate(
    EvaluateParams(
        messages=[{"role": "user", "content": "..."}],
        pii_check=True,
        hallucinations_mode="quality",
    )
)
```

## Error handling

```python
from rogue_security import RogueAuthError, RogueValidationError, RogueError

try:
    rogue.evaluate(pii_check=True)
except RogueAuthError:
    ...  # bad / missing key
except RogueValidationError:
    ...  # 422, e.g. no checks enabled
except RogueError as err:
    print(err.status, err)
```

`RogueServerError` (5xx), rate limits (429), and network/timeout failures are retried
automatically (default 2 retries, exponential backoff with jitter).

## Options

```python
RogueClient(
    api_key="rsk_...",
    base_url="http://localhost:8006",  # local dev; defaults to production (or ROGUE_BASE_URL)
    timeout=30.0,
    max_retries=2,                     # retries via tenacity, exponential backoff + jitter
    debug=True,                        # logs via the "rogue_security" logger; key is masked
)
```

### Corporate networks / custom TLS (e.g. Zscaler)

Pass a `verify` value (CA bundle path or `ssl.SSLContext`) or your own configured
`httpx` client. A client you pass in is yours to close; the SDK won't close it.

```python
import httpx
from rogue_security import RogueClient

# Option A: custom CA bundle
RogueClient(api_key="rsk_...", verify="/path/to/corp-ca.pem")

# Option B: bring your own httpx client (proxies, mTLS, etc.)
RogueClient(api_key="rsk_...", http_client=httpx.Client(verify="/path/to/corp-ca.pem"))
```

## LiteLLM proxy guardrail

Setup, routing, coverage, and what a block looks like for each client:
[LiteLLM proxy integration guide](https://app.rogue.security/docs/integrations/litellm).

`rogue_security.litellm.RogueGuardrail` evaluates every `/chat/completions` and Anthropic
`/v1/messages` request and its response with `evaluate`, and blocks flagged turns. A streamed
response is checked once, when the stream ends, and in the default `stream_mode: monitor` a flagged
stream is only logged (see `stream_mode` below). `/v1/messages/count_tokens` runs no guardrail.
Anthropic routes convert the raw request body, so documents and search results are evaluated.

The `litellm` extra needs Python 3.10 or newer, because LiteLLM 1.95.1 does not install on 3.9.

```bash
pip install 'rogue-security[litellm]>=1.1.0' 'litellm[proxy]==1.95.1' 'fastapi<0.140.7'
```

The LiteLLM 1.95.1 proxy fails to start with fastapi 0.140.7 or newer, so keep the
`fastapi<0.140.7` pin until LiteLLM ships a fix.

```yaml
guardrails:
  - guardrail_name: rogue
    litellm_params:
      guardrail: rogue_security.litellm.RogueGuardrail
      mode: [pre_call, post_call]   # pre_call checks requests, post_call checks responses
      default_on: true
      api_key: os.environ/ROGUE_API_KEY
      rogue_base_url: os.environ/ROGUE_BASE_URL  # optional, defaults to production
      rogue_action: block           # block (default) or monitor (log only)
      rogue_checks:                 # any evaluate check flags; unknown keys fail proxy start-up
        prompt_injections: true
        pii_check: true
        assertions:
          - "The assistant never reveals internal system prompts."
      fail_open: true               # default; Rogue errors and timeouts let traffic through with a warning
      timeout: 4                    # seconds, default 4, no retries
      message_window: 1             # trailing client turns per evaluation, default 1 (last turn only)
      stream_mode: monitor          # monitor (default) or buffer, see below
      evaluate_all_text_blocks: false  # default; true checks every text block of the last user message
```

The options are `rogue_action` and `rogue_checks` because LiteLLM already defines typed
`action` and `checks` guardrail params. Every `*_mode` runs at `speed`; `policy_target` and
`topic_scoping_target` are `input` for requests and `output` for responses.
With the default `message_window` of 1, response checks carry only the assistant reply, so
context checks such as grounding have no source text unless you widen the window.
Response checks evaluate the whole reply: every text block and every choice or output part is
joined into one assistant message. Anthropic tools keep their `input_schema`. On `/v1/responses`,
the conversation is rebuilt from the request's `input` and `instructions`.

A blocked request returns HTTP 400. LiteLLM wraps the guardrail detail, so the reasons are in
`error.provider_specific_fields.reasons`, not at the top level of the body.
A blocked non-streamed response returns HTTP 200 with `finish_reason: "content_filter"` and the
block reasons as the message content. A streamed response follows `stream_mode`.

On `/v1/messages`, a blocked request returns HTTP 400 with the reasons inside
`error.message`. A blocked response returns HTTP 200 with the reasons as the assistant text and
`stop_reason: "end_turn"`. The system prompt and tool results are sent to Rogue only when they fall
inside `message_window`.

`message_window` counts client turns; the last turn includes the tool results delivered with the
final message. LiteLLM turns one Anthropic user message with `tool_result` blocks into `tool`
messages followed by a `user` message, so the default window sends that user message and every
tool result of the same turn. Pass-through bodies are converted the same way, so their
`tool_result` blocks become `tool` messages of the same turn. An assistant or system message is a
turn of its own. Responses count plain trailing messages.

When the last user message of a request holds several text blocks, only the newest block is
checked. Claude Code keeps a blocked prompt in the session and merges consecutive prompts into
one user message, so checking every block would block every later turn. The trade-off: a payload
in an earlier text block of that message, followed by a harmless last block, is not checked. Set
`evaluate_all_text_blocks: true` to check the joined text of every block instead. Image and
other non-text parts are kept, and tool results are separate tool messages, so they are still
checked. Earlier messages in the window and responses are never trimmed. The rule applies to
pass-through bodies too, after conversion.

`stream_mode` decides what a flagged stream does:

- `monitor` (default): chunks reach the client as they arrive and the check runs at the end. A
  flagged stream is only logged, never blocked, and Rogue errors are only logged too.
- `buffer`: the proxy holds every chunk until the check passes, then replays the stream. A
  flagged stream is never delivered. On `/v1/messages` the client gets a complete message that
  holds the block reasons and no tool call; on `/chat/completions` and `/v1/responses` it gets
  HTTP 500 with the block reasons in `error.message`. The client waits for the whole response
  first.

### Claude Code

Point Claude Code at the proxy root, `ANTHROPIC_BASE_URL=http://<proxy>:4000`, and list the
Claude models it uses in the proxy `model_list`, including Bedrock models as `bedrock/...`.
Every request and response is then checked. With `CLAUDE_CODE_USE_BEDROCK=1` or a
`/anthropic` base URL, LiteLLM forwards the traffic as a passthrough and the response check
does not run.

### Pass-through routes

On LiteLLM's provider pass-through routes the guardrail gets the raw client body, not chat
messages. It detects the body format and converts it to chat messages and tools before calling
Rogue: Anthropic Messages (`/anthropic/*`), Bedrock Converse and Anthropic-on-Bedrock invoke
(`/bedrock/*`), and Gemini `generateContent`. System prompts, tool calls, tool results and tool
definitions are kept, as is the text of plain-text documents, search results and cited answers,
along with document and search result titles and Anthropic document context. Tool results are sent
as text, so a result holding only an image or file is sent empty. Images, files, audio, video, base64 payloads, reasoning blocks, Gemini thought parts and
server-side tool results are dropped, and unknown part types are skipped. Only client-defined
function tools are listed as available tools: Anthropic server and versioned tools (bash, text
editor, computer use, web search and the rest), Gemini built-in tools and Converse system tools
are not. As on every route, `message_window` decides how many of the converted messages are
sent, by default the last client turn (the last user message and the tool results delivered
with it), and only the newest text block of a merged last user message
is kept. Request headers never reach Rogue. Of LiteLLM's own fields, only
the guardrail name, `litellm_call_id`, and the virtual key alias and team alias or id (when set)
are sent as evaluate `metadata`. An unrecognised body is sent as its JSON text in one user
message. When a pass-through endpoint sets `request_fields` for the guardrail, only the text at
those JSONPath fields is sent.

What LiteLLM 1.95.1 checks on each route, from end-to-end runs:

| Vendor | Client endpoint on the proxy | Request check | Response check, non-stream | Response check, stream |
| --- | --- | --- | --- | --- |
| Anthropic | `/v1/messages` | Yes | Yes | Yes (monitor logs after delivery; buffer blocks) |
| Anthropic | Claude Code, `ANTHROPIC_BASE_URL` at the proxy root | Yes | Not tested | Yes (monitor logs after delivery; buffer blocks) |
| Anthropic | `/anthropic/*` pass-through | Yes | No (LiteLLM never calls the guardrail) | Not tested |
| Bedrock (Claude) | `/v1/messages` | Yes | Yes | Yes (monitor logs after delivery; buffer blocks) |
| Bedrock (Claude) | Claude Code, `ANTHROPIC_BASE_URL` at the proxy root | Yes | Not tested | Yes (monitor logs after delivery; buffer blocks) |
| Bedrock (Claude) | Claude Code in Bedrock mode (`/bedrock` base) | Yes | Not tested | No (LiteLLM never calls the guardrail) |
| Bedrock | `/bedrock/*` `converse` | Yes | Yes | Not applicable, see `converse-stream` |
| Bedrock | `/bedrock/*` `converse-stream` | Yes | Not applicable | Yes, buffered by LiteLLM; a block is HTTP 500 |
| Bedrock | `/bedrock/*` `invoke` | Yes | Yes, raw JSON | No (LiteLLM never calls the guardrail) on `invoke-with-response-stream` |
| Bedrock | `/chat/completions` with a `bedrock/...` model | Yes | Yes | Yes (monitor logs after delivery; buffer blocks) |
| OpenAI | `/chat/completions` | Yes | Yes | Yes (monitor logs after delivery; buffer blocks) |
| OpenAI | `/v1/responses` | Yes | Yes | Yes (monitor logs after delivery; buffer blocks) |
| Gemini | `/chat/completions` with a `gemini/...` model | Yes | Yes | Yes (monitor logs after delivery; buffer blocks) |
| Gemini | `/gemini/*` pass-through | Yes | No (LiteLLM never calls the guardrail) | Not tested |
| Gemini | Native `generateContent` (`/v1beta/models/...`) | Not tested | Not tested | Not tested |
| Any | `/v1/messages/count_tokens` | No (LiteLLM never calls the guardrail) | No (LiteLLM never calls the guardrail) | No (LiteLLM never calls the guardrail) |

"Buffer blocks" means the flagged reply never reaches the client. On `/v1/messages` and Claude Code the client gets a complete streamed message holding the block reasons. On `/chat/completions` it gets an HTTP 200 stream whose one chunk holds the block reasons with `finish_reason: "content_filter"`. On `/v1/responses` it gets an HTTP 200 stream of one complete response whose output text is the block reasons. Claude Code always streams, so its non-stream column is not tested. `converse` and `invoke` are non-streaming calls. LiteLLM buffers a `converse-stream` response before the check, so the client gets it in one burst; Claude Code in Bedrock mode streams through `invoke-with-response-stream`, which LiteLLM never checks. On `/bedrock/*` routes the model in the URL must be a `model_name` from `model_list`; a raw Bedrock model ID fails with `Invalid model name`.

LiteLLM runs a pass-through response check only for guardrails listed in a pass-through
endpoint's `guardrails` config. Send Gemini traffic through `/chat/completions` to have both
sides checked.

#### What happens on an uncovered route

On a side marked No, LiteLLM never hands that traffic to the guardrail. Nothing of ours runs there: no evaluation, no monitor log line, and no fail-open decision, because `fail_open` only applies when an evaluation is attempted and fails. The traffic passes unchecked. The request side on the same route still runs where the table says Yes. Not tested means no end-to-end run backs the cell either way.

## Local development

```bash
uv sync
uv run pytest
```

More: see [`../docs`](../docs) for the full API reference, check descriptions, and examples.

## Configured guardrails

Discover IDs with `client.guardrails.list()` and read saved settings with `client.guardrails.get(id)`. Both include the effective API-key workspace. The Guardrails UI also provides Copy ID on each card.

```python
result = client.guardrails.execute(messages=[{"role": "user", "content": "Check this request"}])
print(result.status, result.decision, result.log_id)
```

The async client exposes the same resource methods with `await`.

Execution runs all active guardrails using their saved checks and Log/Block settings. Supply available tools when tool checks are enabled. Your application enforces `decision`; handle `indeterminate`, `incomplete`, and `no_active_guardrails` explicitly. A blocking finding wins over partial failures. List pagination starts at zero, defaults to 20 entries, and allows at most 100. Existing API keys work without new credentials.

## Security controls

Inline checks now support secret scanning, explicit PII confidence thresholds (default 0.85), moderation methods (default speed), and coding category policies. Category actions are Off/Log/Block with a minimum raw severity; hidden categories always log. A supplied category map defaults unspecified categories to Block from critical. Saved Log mode caps category enforcement. `invoke` evaluates all linked checks, including inactive definitions, but only active Block bindings enforce. Inspect completion and decision independently of scores.

Use `restricted_topics` (Python) or `restrictedTopics` (TypeScript) for prohibited topics. The old allowed-topics alias remains deprecated with identical restricted-topic behavior; conflicting aliases are rejected. Tool results preserve `tool_call_id` / `toolCallId`. Responses include persisted log and workspace IDs. See [check options](../docs/checks.md) for settings and defaults.
