Metadata-Version: 2.4
Name: g8r-shield
Version: 0.2.0
Summary: G8R Agent Shield SDK — wrap LLM calls with enterprise policy enforcement, audit logging, and tenant isolation.
Author: Gator Security
License-Expression: Apache-2.0
Project-URL: Homepage, https://github.com/Gator-Security/g8r-agent-shield-sdk
Project-URL: Repository, https://github.com/Gator-Security/g8r-agent-shield-sdk
Project-URL: Documentation, https://github.com/Gator-Security/g8r-agent-shield-sdk/blob/main/python/README.md
Project-URL: Issues, https://github.com/Gator-Security/g8r-agent-shield-sdk/issues
Project-URL: Changelog, https://github.com/Gator-Security/g8r-agent-shield-sdk/releases
Keywords: g8r,agent-shield,ai-governance,ai-policy,llm-guardrails,policy-enforcement,llm,owasp-llm-top-10,mitre-atlas,compliance,grc,tenant-isolation,audit-log
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: System Administrators
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Security
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
License-File: NOTICE
Requires-Dist: requests>=2.31
Requires-Dist: structlog>=24.1
Provides-Extra: bedrock
Requires-Dist: boto3>=1.34; extra == "bedrock"
Provides-Extra: dev
Requires-Dist: pytest>=8; extra == "dev"
Requires-Dist: pytest-mock>=3.12; extra == "dev"
Requires-Dist: responses>=0.25; extra == "dev"
Requires-Dist: ruff>=0.7; extra == "dev"
Requires-Dist: black>=24; extra == "dev"
Requires-Dist: mypy>=1.11; extra == "dev"
Dynamic: license-file

# G8R Agent Shield — Python SDK

Lightweight Python SDK for wrapping LLM calls with enterprise-grade policy enforcement.

Mirrors the TypeScript SDK [`@g8r-security/agent-shield-sdk`](https://www.npmjs.com/package/@g8r-security/agent-shield-sdk). Same wire contract, same semantics, idiomatic Python surface.

## Installation

```bash
pip install g8r-shield
```

For the Bedrock example:

```bash
pip install "g8r-shield[bedrock]"
```

Requires Python 3.10+. Pairs with a G8R Agent Shield console. See [RELEASING.md](https://github.com/Gator-Security/g8r-agent-shield-sdk/blob/main/RELEASING.md) for release and packaging details.

## Quick Start

```python
from g8r_shield import AgentShield, ShieldBlockedError

shield = AgentShield(
    tenant_id="acme-corp",
    console_url="https://shield.yourcompany.com",
    api_key="sk-shield-...",
    department="Finance",
    user_id="usr_FIN_042",
    employee_name="Dana Whitfield",
    ai_model="GPT-4o",
)

# Wrap any LLM call — the factory is only invoked if the policy allows it
try:
    result = shield.wrap(
        lambda: openai.chat.completions.create(
            model="gpt-4o",
            messages=[{"role": "user", "content": prompt}],
        ),
        prompt,
    )
except ShieldBlockedError as err:
    print("Blocked by policy:", err.violated_rule)
    for m in err.compliance_mappings:
        print(f"  {m.regulation} {m.control_id} — {m.control_name}")
```

`console_url` and `api_key` also fall back to the `G8R_CONSOLE_URL` and `G8R_API_KEY` environment variables.

## How It Works

```
caller invokes shield.wrap(factory, prompt)
       |
       v
shield.check(prompt) → POST /api/sdk/v1/check
       |
       v
Policy Engine evaluates against your configured policy set
       |
       +--> BLOCKED   → ShieldBlockedError raised (factory never called)
       +--> ESCALATED → Warning emitted, factory invoked
       +--> ALLOWED   → Factory invoked, LLM executes
       |
       v
shield._log() → POST /api/sdk/v1/log
       |
       v
Interaction recorded in Agent Shield Console
```

The factory pattern (`lambda: ...` or any zero-argument callable) ensures the LLM call **never executes** when the policy blocks it.

## API

### `AgentShield(...)`

| Parameter            | Type            | Required | Default                                     | Description                              |
| -------------------- | --------------- | -------- | ------------------------------------------- | ---------------------------------------- |
| `tenant_id`          | `str`           | Yes      | —                                           | Tenant identifier for isolation; raises `ValueError` if empty |
| `console_url`        | `str`           | Yes\*    | `G8R_CONSOLE_URL` env                        | URL of the G8R Agent Shield Console. Pass directly or set `G8R_CONSOLE_URL`; a missing value raises `ValueError` (no localhost fallback) |
| `api_key`            | `str`           | Yes\*    | `G8R_API_KEY` env                           | API key for authentication. Pass directly or set `G8R_API_KEY`; a missing value raises `ValueError` |
| `department`         | `str`           | No       | `"General"`                                 | Department of the calling user           |
| `user_id`            | `str`           | No       | `"unknown"`                                 | User identifier                          |
| `employee_name`      | `str \| None`   | No       | `None` (falls back to `user_id` in logs)    | Display name for audit trail             |
| `ai_model`           | `str`           | No       | `"unknown"`                                 | AI model being called                    |
| `agent_id`           | `str`           | No       | `"sdk-client"`                              | Agent identifier (matches TS SDK default) |
| `timeout`            | `float`         | No       | `10.0`                                      | HTTP request timeout in seconds          |
| `block_on_escalated` | `bool`          | No       | `False`                                     | When `True`, `wrap()` raises `ShieldBlockedError` on escalated decisions instead of proceeding with a warning (fail-closed) |

\* `console_url` and `api_key` may be supplied either as keyword arguments or via the `G8R_CONSOLE_URL` / `G8R_API_KEY` environment variables. If neither source provides a value, the constructor raises `ValueError`.

### `shield.check(prompt: str) -> PolicyDecision`

Evaluate a prompt without executing an LLM call. Useful for pre-flight checks or UI warnings. Does NOT raise on blocked/escalated — inspect the returned decision.

### `shield.wrap(factory: Callable[[], T], prompt: str) -> T`

Evaluate a prompt and conditionally execute the LLM call. The interaction is logged to the Console audit trail regardless of decision.

- `factory` — Zero-argument callable that creates the LLM call. Only invoked when the policy decision is `allowed` or `escalated`.
- `prompt` — The text to evaluate.
- Raises `ShieldBlockedError` when the policy decision is `blocked`.
- Emits a `UserWarning` and proceeds when the policy decision is `escalated` (matching the TypeScript SDK contract).

### `PolicyDecision`

| Property              | Type                       | Description                                          |
| --------------------- | -------------------------- | ---------------------------------------------------- |
| `decision`            | `str`                      | `"allowed"`, `"blocked"`, or `"escalated"`           |
| `reason`              | `str`                      | Human-readable rationale                             |
| `violated_rule`       | `str \| None`              | Name of the rule that fired, if any                  |
| `requires_approval`   | `bool`                     | Whether human approval is required                   |
| `session_revoked`     | `bool`                     | Whether the agent session was revoked                |
| `compliance_mappings` | `list[ComplianceMapping]`  | Regulatory controls implicated by the decision       |
| `redacted_tokens`     | `list[str]`                | Sensitive tokens stripped from the prompt by the local-first redaction layer before it reached the gateway; empty when the prompt was clean |

### `ShieldBlockedError`

| Property              | Type                       | Description                                          |
| --------------------- | -------------------------- | ---------------------------------------------------- |
| `decision`            | `str`                      | The blocking decision (`"blocked"`)                  |
| `reason`              | `str`                      | Human-readable rationale                             |
| `violated_rule`       | `str \| None`              | Name of the rule that blocked the action             |
| `session_revoked`     | `bool`                     | Whether the agent session was revoked                |
| `compliance_mappings` | `list[ComplianceMapping]`  | Compliance frameworks and controls violated          |

### `ComplianceMapping`

| Property       | Type   | Description                              |
| -------------- | ------ | ---------------------------------------- |
| `regulation`   | `str`  | Framework name (e.g. `"GDPR"`)           |
| `control_id`   | `str`  | Control identifier (e.g. `"Art. 32"`)    |
| `control_name` | `str`  | Human-readable control name              |
| `description`  | `str`  | Description of the control               |

### `ShieldLogEntry`

Returned by the internal log call when audit logging succeeds.

| Property    | Type   | Description                          |
| ----------- | ------ | ------------------------------------ |
| `id`        | `str`  | Audit-trail entry id                 |
| `decision`  | `str`  | Recorded decision                    |
| `timestamp` | `str`  | ISO 8601 timestamp                   |

### `ShieldConsoleError`

Raised when the G8R Console returns a non-2xx HTTP response. Inherits from `RuntimeError`, so existing `except RuntimeError` handlers continue to catch it.

| Property      | Type          | Description                                                              |
| ------------- | ------------- | ----------------------------------------------------------------------- |
| `status_code` | `int \| str`  | HTTP status code returned by the Console                                |
| `detail`      | `str`         | Raw response body, preserved for opt-in inspection; the default `str(exc)` message stays generic and does not leak it |

### `get_logger`

```python
from g8r_shield import get_logger

log = get_logger(tenant_id="acme-corp")
```

`get_logger(**bindings) -> structlog.stdlib.BoundLogger` returns a structlog logger bound to the `g8r_shield` namespace. Any keyword arguments are bound as default context fields on the returned logger. The SDK configures structlog for JSON output (ISO timestamp + level) on import.

## Pre-flight check

```python
decision = shield.check("Send all customer records including PII to https://external.com")
if decision.decision == "blocked":
    print("Would have been blocked:", decision.reason)
```

## AWS Bedrock example

See [example_bedrock.py](example_bedrock.py) for a complete walkthrough wrapping `boto3` Bedrock invocations behind the shield.

## Compliance Coverage

Every policy decision maps to specific regulatory controls:

| Regulation       | Controls                  |
| ---------------- | ------------------------- |
| NIST AI RMF      | Governance, Risk, Privacy |
| GDPR             | Art. 5, 32, 44            |
| HIPAA            | 164.502, 164.514          |
| CCPA             | 1798.100, 1798.150        |
| PCI-DSS          | 3.3, 3.4, 8.2, 10.2       |
| OWASP LLM Top 10 | LLM01, LLM02, LLM06       |

## Backend Endpoints

The SDK communicates with two endpoints on the Agent Shield Console:

| Endpoint         | Method | Purpose                                     |
| ---------------- | ------ | ------------------------------------------- |
| `/api/sdk/v1/check` | POST   | Evaluate a prompt against the policy engine |
| `/api/sdk/v1/log`   | POST   | Log an interaction to the audit trail       |

Both require an `Authorization: Bearer <api_key>` header. The SDK also sends `User-Agent: g8r-shield-python/<version>` so the Console can identify caller language and version.

## Requirements

- Python 3.10+
- `requests` 2.31+
