From cd431f155a29433d244620db9b3cec3114efe1c0 Mon Sep 17 00:00:00 2001 From: SentienceDEV Date: Mon, 23 Feb 2026 18:45:22 -0800 Subject: [PATCH 1/4] Phase 4: policy references --- README.md | 93 ++++++++++++- examples/browser_use_checkout.py | 155 +++++++++++++++++++++ examples/langchain_tool_guard.py | 225 +++++++++++++++++++++++++++++++ examples/playwright_form_fill.py | 192 ++++++++++++++++++++++++++ 4 files changed, 664 insertions(+), 1 deletion(-) create mode 100644 examples/browser_use_checkout.py create mode 100644 examples/langchain_tool_guard.py create mode 100644 examples/playwright_form_fill.py diff --git a/README.md b/README.md index 22f18eb..d5c0a31 100644 --- a/README.md +++ b/README.md @@ -80,21 +80,112 @@ SecureAgent └── RuntimeAgent (orchestration, pre-action hook) ``` -## Policy Example +## Debug Mode + +Debug mode provides human-readable trace output for troubleshooting: + +```python +secure_agent = SecureAgent( + agent=agent, + policy="policy.yaml", + mode="debug", + trace_format="console", # or "json" + trace_file="trace.jsonl", # optional file output +) +``` + +Console output shows: +- Session start/end with framework and policy info +- Each step with action and resource +- Policy decisions (ALLOWED/DENIED) with reason codes +- Snapshot diffs (before/after state changes) +- Verification results (PASS/FAIL) + +For JSON trace output (machine-parseable): + +```python +secure_agent = SecureAgent( + agent=agent, + mode="debug", + trace_format="json", + trace_file="trace.jsonl", +) +``` + +## Policy Reference + +### Basic Structure + +```yaml +# policies/example.yaml +rules: + - action: "" + resource: "" + effect: allow | deny + require_verification: # optional + - +``` + +### Action Patterns + +| Pattern | Description | Example | +|---------|-------------|---------| +| `browser.*` | All browser actions | click, type, navigate | +| `browser.click` | Specific action | Only click events | +| `api.call` | API tool calls | HTTP requests | +| `*` | Wildcard (all actions) | Catch-all rules | + +### Resource Patterns + +| Pattern | Description | Example | +|---------|-------------|---------| +| `https://example.com/*` | URL prefix match | All pages on domain | +| `*checkout*` | Contains match | Any checkout-related URL | +| `button#submit` | CSS selector | Specific element | +| `*` | Wildcard (all resources) | Catch-all | + +### Verification Predicates + +```yaml +require_verification: + # URL checks + - url_contains: "/checkout" + - url_matches: "^https://.*\\.amazon\\.com/.*" + + # DOM state checks + - element_exists: "#cart-items" + - element_text_contains: + selector: ".total" + text: "$" + + # Custom predicates + - predicate: "cart_not_empty" +``` + +### Policy Example ```yaml # policies/shopping.yaml rules: + # Allow browsing Amazon - action: "browser.*" resource: "https://amazon.com/*" effect: allow + # Allow checkout with verification - action: "browser.click" resource: "*checkout*" effect: allow require_verification: - url_contains: "/checkout" + - element_exists: "#cart-items" + + # Block external links + - action: "browser.navigate" + resource: "https://external.com/*" + effect: deny + # Default deny - action: "*" resource: "*" effect: deny diff --git a/examples/browser_use_checkout.py b/examples/browser_use_checkout.py new file mode 100644 index 0000000..2ada9aa --- /dev/null +++ b/examples/browser_use_checkout.py @@ -0,0 +1,155 @@ +""" +E-commerce checkout flow with browser-use and predicate-secure. + +This example demonstrates: +- Wrapping a browser-use Agent with SecureAgent +- Policy-based authorization for browser actions +- Debug mode for troubleshooting +- Verification predicates for checkout flow + +Requirements: + pip install predicate-secure[browser-use] + # Set up your LLM (e.g., OpenAI API key) +""" + +import asyncio +import os + +# Uncomment to use with actual browser-use +# from browser_use import Agent +# from langchain_openai import ChatOpenAI + +from predicate_secure import SecureAgent + + +def create_shopping_policy() -> str: + """Create a shopping policy file and return the path.""" + policy_content = """ +# Shopping policy - controls what the agent can do during checkout +rules: + # Allow browsing and interacting with Amazon + - action: "browser.*" + resource: "https://*.amazon.com/*" + effect: allow + + # Allow clicking checkout buttons with verification + - action: "browser.click" + resource: "*checkout*" + effect: allow + require_verification: + - url_contains: "/checkout" + + # Allow clicking add-to-cart buttons + - action: "browser.click" + resource: "*add-to-cart*" + effect: allow + + # Allow form filling for shipping/payment + - action: "browser.fill" + resource: "*address*" + effect: allow + + # Block navigation to external sites + - action: "browser.navigate" + resource: "https://external-site.com/*" + effect: deny + + # Default: deny everything else + - action: "*" + resource: "*" + effect: deny +""" + policy_path = "/tmp/shopping_policy.yaml" + with open(policy_path, "w") as f: + f.write(policy_content) + return policy_path + + +async def run_secure_checkout(): + """Run a secure e-commerce checkout flow.""" + + # Create policy file + policy_path = create_shopping_policy() + + # Example with mock agent (for demonstration) + # In real usage, replace with actual browser-use Agent: + # + # llm = ChatOpenAI(model="gpt-4") + # agent = Agent( + # task="Buy wireless headphones under $50 on Amazon", + # llm=llm, + # ) + + class MockBrowserUseAgent: + """Mock agent for demonstration purposes.""" + + __module__ = "browser_use.agent" + + def __init__(self): + self.task = "Buy wireless headphones under $50 on Amazon" + self.llm = "mock_llm" + self.browser = MockBrowser() + + async def run(self): + print("Mock agent: Would browse Amazon and add item to cart") + return {"status": "completed", "item": "Sony WH-1000XM4"} + + class MockBrowser: + """Mock browser session.""" + + pass + + # Create mock agent + agent = MockBrowserUseAgent() + + # Wrap with SecureAgent for authorization + verification + secure_agent = SecureAgent( + agent=agent, + policy=policy_path, + mode="debug", # Enable debug output + trace_format="console", + principal_id="shopping-agent:checkout", + ) + + print(f"Created: {secure_agent}") + print(f"Framework: {secure_agent.framework}") + print(f"Policy: {policy_path}") + print() + + # Manual step tracing (for demonstration) + step = secure_agent.trace_step("navigate", "https://amazon.com") + secure_agent.trace_step_end(step, success=True) + + step = secure_agent.trace_step("search", "wireless headphones") + secure_agent.trace_step_end(step, success=True) + + step = secure_agent.trace_step("click", "add-to-cart-button") + secure_agent.trace_snapshot_diff( + before={"cart_count": 0}, + after={"cart_count": 1}, + label="Cart Update", + ) + secure_agent.trace_step_end(step, success=True) + + secure_agent.trace_verification( + predicate="cart_not_empty", + passed=True, + message="Cart has 1 item", + ) + + print("\nCheckout flow traced successfully!") + print("In production, use secure_agent.run() to execute the full flow.") + + +def main(): + """Main entry point.""" + print("=" * 60) + print("E-commerce Checkout with predicate-secure") + print("=" * 60) + print() + + asyncio.run(run_secure_checkout()) + + +if __name__ == "__main__": + main() diff --git a/examples/langchain_tool_guard.py b/examples/langchain_tool_guard.py new file mode 100644 index 0000000..a3738ec --- /dev/null +++ b/examples/langchain_tool_guard.py @@ -0,0 +1,225 @@ +""" +LangChain tool interception with predicate-secure. + +This example demonstrates: +- Wrapping a LangChain AgentExecutor with SecureAgent +- Policy-based authorization for tool calls +- Getting SentienceLangChainCore for tool interception +- Debug mode for troubleshooting + +Requirements: + pip install predicate-secure[langchain] + pip install langchain langchain-openai +""" + +# Uncomment for actual LangChain usage +# from langchain.agents import AgentExecutor, create_react_agent +# from langchain_openai import ChatOpenAI +# from langchain.tools import Tool + +from predicate_secure import SecureAgent + + +def create_tool_policy() -> str: + """Create a tool authorization policy.""" + policy_content = """ +# Tool authorization policy +rules: + # Allow search tool + - action: "tool.search" + resource: "*" + effect: allow + + # Allow calculator tool + - action: "tool.calculator" + resource: "*" + effect: allow + + # Allow file read (read-only) + - action: "tool.file_read" + resource: "/safe/path/*" + effect: allow + + # Block file write + - action: "tool.file_write" + resource: "*" + effect: deny + + # Block shell commands + - action: "tool.shell" + resource: "*" + effect: deny + + # Block network requests to untrusted domains + - action: "tool.http_request" + resource: "https://untrusted.com/*" + effect: deny + + # Allow API calls to trusted services + - action: "tool.http_request" + resource: "https://api.trusted.com/*" + effect: allow + + # Default: deny + - action: "*" + resource: "*" + effect: deny +""" + policy_path = "/tmp/tool_policy.yaml" + with open(policy_path, "w") as f: + f.write(policy_content) + return policy_path + + +def run_secure_tool_agent(): + """Run a secure LangChain agent with tool interception.""" + + policy_path = create_tool_policy() + + # Mock LangChain AgentExecutor for demonstration + # In real usage: + # + # llm = ChatOpenAI(model="gpt-4") + # tools = [search_tool, calculator_tool] + # agent = create_react_agent(llm, tools, prompt) + # agent_executor = AgentExecutor(agent=agent, tools=tools) + # + # secure_agent = SecureAgent( + # agent=agent_executor, + # policy=policy_path, + # mode="strict", + # ) + + class MockAgentExecutor: + """Mock LangChain AgentExecutor for demonstration.""" + + __module__ = "langchain.agents.executor" + + def __init__(self): + self.llm = "gpt-4" + self.tools = ["search", "calculator", "file_read"] + + def invoke(self, inputs: dict): + print(f"Mock: invoke({inputs})") + return {"output": "Mock result"} + + # Create mock executor + agent_executor = MockAgentExecutor() + + # Wrap with SecureAgent + secure_agent = SecureAgent( + agent=agent_executor, + policy=policy_path, + mode="debug", + trace_format="console", + principal_id="langchain-agent:tools", + ) + + print(f"Created: {secure_agent}") + print(f"Framework: {secure_agent.framework}") + print(f"Executor: {secure_agent.wrapped.executor}") + print() + + # Demonstrate tracing for tool calls + step = secure_agent.trace_step("tool.search", "query='AI news'") + secure_agent.trace_step_end(step, success=True) + + step = secure_agent.trace_step("tool.calculator", "2 + 2") + secure_agent.trace_step_end(step, success=True) + + step = secure_agent.trace_step("tool.file_write", "/etc/passwd") + secure_agent.trace_step_end(step, success=False, error="Action denied by policy") + + secure_agent.trace_verification( + predicate="no_dangerous_tools", + passed=True, + message="All tool calls were safe", + ) + + print("\nTool interception flow traced successfully!") + + +def demonstrate_langchain_core(): + """Demonstrate getting SentienceLangChainCore for advanced usage.""" + print("\n" + "=" * 60) + print("Advanced: Getting SentienceLangChainCore") + print("=" * 60 + "\n") + + class MockAgentExecutor: + __module__ = "langchain.agents.executor" + + def __init__(self): + self.llm = "gpt-4" + + agent_executor = MockAgentExecutor() + + secure_agent = SecureAgent( + agent=agent_executor, + mode="strict", + ) + + print(f"Framework: {secure_agent.framework}") + print() + + # Without browser, get_langchain_core returns None + core = secure_agent.get_langchain_core() + print(f"Without browser: core = {core}") + print() + + # With browser (for browser-enabled LangChain agents): + print("With browser (requires predicate SDK):") + print(" core = secure_agent.get_langchain_core(browser=browser)") + print(" # Use core for tool interception") + print() + + # In real usage with browser: + # from playwright.async_api import async_playwright + # async with async_playwright() as p: + # browser = await p.chromium.launch() + # core = secure_agent.get_langchain_core(browser=browser) + + +def demonstrate_adapter(): + """Demonstrate using the adapter API.""" + print("\n" + "=" * 60) + print("Advanced: Using Adapter API") + print("=" * 60 + "\n") + + class MockAgentExecutor: + __module__ = "langchain.agents.executor" + + def __init__(self): + self.llm = "gpt-4" + + agent_executor = MockAgentExecutor() + + secure_agent = SecureAgent( + agent=agent_executor, + mode="strict", + ) + + # Get adapter for the agent + # This provides access to all wired components + try: + adapter = secure_agent.get_adapter() + print(f"Adapter metadata: {adapter.metadata}") + print(f"Executor: {adapter.executor}") + print(f"Plugin: {adapter.plugin}") + except Exception as e: + print(f"Adapter requires predicate SDK: {e}") + + +def main(): + """Main entry point.""" + print("=" * 60) + print("LangChain Tool Guard with predicate-secure") + print("=" * 60) + print() + + run_secure_tool_agent() + demonstrate_langchain_core() + demonstrate_adapter() + + +if __name__ == "__main__": + main() diff --git a/examples/playwright_form_fill.py b/examples/playwright_form_fill.py new file mode 100644 index 0000000..850358a --- /dev/null +++ b/examples/playwright_form_fill.py @@ -0,0 +1,192 @@ +""" +Form automation with Playwright and predicate-secure. + +This example demonstrates: +- Wrapping a Playwright page with SecureAgent +- Policy-based authorization for form actions +- Debug mode tracing +- Getting AgentRuntime for advanced use cases + +Requirements: + pip install predicate-secure[playwright] + playwright install chromium +""" + +import asyncio + +# Uncomment for actual Playwright usage +# from playwright.async_api import async_playwright + +from predicate_secure import SecureAgent + + +def create_form_policy() -> str: + """Create a form automation policy file.""" + policy_content = """ +# Form automation policy +rules: + # Allow filling form fields + - action: "browser.fill" + resource: "input[type='text']" + effect: allow + + - action: "browser.fill" + resource: "input[type='email']" + effect: allow + + # Allow clicking submit buttons + - action: "browser.click" + resource: "button[type='submit']" + effect: allow + require_verification: + - element_exists: "form" + + # Allow navigation to form pages + - action: "browser.navigate" + resource: "https://example.com/form*" + effect: allow + + # Block sensitive actions + - action: "browser.fill" + resource: "input[type='password']" + effect: deny + + # Default deny + - action: "*" + resource: "*" + effect: deny +""" + policy_path = "/tmp/form_policy.yaml" + with open(policy_path, "w") as f: + f.write(policy_content) + return policy_path + + +async def run_secure_form_fill(): + """Run a secure form fill automation.""" + + policy_path = create_form_policy() + + # Mock Playwright page for demonstration + # In real usage: + # + # async with async_playwright() as p: + # browser = await p.chromium.launch() + # page = await browser.new_page() + # await page.goto("https://example.com/form") + # + # secure_agent = SecureAgent( + # agent=page, + # policy=policy_path, + # mode="debug", + # ) + + class MockPlaywrightPage: + """Mock Playwright page for demonstration.""" + + __module__ = "playwright.async_api._generated" + + async def fill(self, selector: str, value: str): + print(f"Mock: fill({selector}, {value})") + + async def click(self, selector: str): + print(f"Mock: click({selector})") + + async def goto(self, url: str): + print(f"Mock: goto({url})") + + # Create mock page + page = MockPlaywrightPage() + + # Wrap with SecureAgent + secure_agent = SecureAgent( + agent=page, + policy=policy_path, + mode="debug", + trace_format="console", + principal_id="form-agent:automation", + ) + + print(f"Created: {secure_agent}") + print(f"Framework: {secure_agent.framework}") + print() + + # Demonstrate tracing for form fill flow + step = secure_agent.trace_step("navigate", "https://example.com/form") + secure_agent.trace_step_end(step, success=True) + + step = secure_agent.trace_step("fill", "input[name='email']") + secure_agent.trace_snapshot_diff( + before={"email_field": ""}, + after={"email_field": "user@example.com"}, + label="Form Input", + ) + secure_agent.trace_step_end(step, success=True) + + step = secure_agent.trace_step("fill", "input[name='name']") + secure_agent.trace_snapshot_diff( + before={"name_field": ""}, + after={"name_field": "John Doe"}, + label="Form Input", + ) + secure_agent.trace_step_end(step, success=True) + + step = secure_agent.trace_step("click", "button[type='submit']") + secure_agent.trace_verification( + predicate="form_valid", + passed=True, + message="All required fields filled", + ) + secure_agent.trace_step_end(step, success=True) + + print("\nForm fill flow traced successfully!") + + +async def run_with_runtime(): + """Demonstrate getting AgentRuntime for advanced usage.""" + print("\n" + "=" * 60) + print("Advanced: Getting AgentRuntime") + print("=" * 60 + "\n") + + # This shows how to get the AgentRuntime for advanced use cases + # like using RuntimeAgent directly + + class MockPlaywrightPage: + __module__ = "playwright.async_api._generated" + + page = MockPlaywrightPage() + + secure_agent = SecureAgent( + agent=page, + mode="strict", + ) + + print(f"Framework: {secure_agent.framework}") + print("To get AgentRuntime (requires predicate SDK):") + print(" runtime = await secure_agent.get_runtime_async()") + print(" authorizer = secure_agent.get_pre_action_authorizer()") + print() + + # In real usage: + # runtime = await secure_agent.get_runtime_async() + # from predicate.runtime_agent import RuntimeAgent + # runtime_agent = RuntimeAgent( + # runtime=runtime, + # executor=my_llm, + # pre_action_authorizer=secure_agent.get_pre_action_authorizer(), + # ) + + +def main(): + """Main entry point.""" + print("=" * 60) + print("Form Automation with predicate-secure") + print("=" * 60) + print() + + asyncio.run(run_secure_form_fill()) + asyncio.run(run_with_runtime()) + + +if __name__ == "__main__": + main() From 94272bcafd08a3bda26642884913fefee008fe8a Mon Sep 17 00:00:00 2001 From: SentienceDEV Date: Mon, 23 Feb 2026 18:49:17 -0800 Subject: [PATCH 2/4] linted --- docs/user-manual.md | 840 +++++++++++++++++++++++++++++++ examples/browser_use_checkout.py | 13 +- examples/langchain_tool_guard.py | 9 +- examples/playwright_form_fill.py | 12 +- src/predicate_secure/tracing.py | 49 +- tests/test_tracing.py | 4 +- 6 files changed, 874 insertions(+), 53 deletions(-) create mode 100644 docs/user-manual.md diff --git a/docs/user-manual.md b/docs/user-manual.md new file mode 100644 index 0000000..2213412 --- /dev/null +++ b/docs/user-manual.md @@ -0,0 +1,840 @@ +# predicate-secure User Manual + +A comprehensive guide to securing your AI agents with predicate-secure. + +## Table of Contents + +1. [Introduction](#introduction) +2. [Installation](#installation) +3. [Quick Start](#quick-start) +4. [Framework Guides](#framework-guides) + - [browser-use](#browser-use) + - [Playwright](#playwright) + - [LangChain](#langchain) + - [PydanticAI](#pydanticai) +5. [Modes](#modes) +6. [Writing Policies](#writing-policies) +7. [Debug Mode](#debug-mode) +8. [Advanced Usage](#advanced-usage) +9. [Troubleshooting](#troubleshooting) + +--- + +## Introduction + +**predicate-secure** is a drop-in security wrapper that adds authorization, verification, and audit capabilities to any AI agent framework. Instead of rewriting your agent code, you simply wrap your existing agent with `SecureAgent` and define a policy file. + +### What it does + +- **Pre-action authorization** - Every action is checked against your policy before execution +- **Post-execution verification** - Deterministic checks ensure the expected outcome occurred +- **Cryptographic audit** - All decisions are logged with tamper-proof receipts +- **Zero refactoring** - Works with your existing agent code + +### How it works + +``` +Your Agent Code + │ + ▼ +┌─────────────────┐ +│ SecureAgent │ +│ ┌───────────┐ │ +│ │ Policy │◀─── Your rules (YAML) +│ │ Engine │ │ +│ └───────────┘ │ +│ ┌───────────┐ │ +│ │ Snapshot │◀─── Before/after state +│ │ Engine │ │ +│ └───────────┘ │ +│ ┌───────────┐ │ +│ │ Audit │◀─── Decision log +│ │ Log │ │ +│ └───────────┘ │ +└─────────────────┘ + │ + ▼ + Execution +``` + +--- + +## Installation + +### Basic installation + +```bash +pip install predicate-secure +``` + +### With framework-specific extras + +```bash +# For browser-use agents +pip install predicate-secure[browser-use] + +# For Playwright automation +pip install predicate-secure[playwright] + +# For LangChain agents +pip install predicate-secure[langchain] + +# Install all extras +pip install predicate-secure[all] +``` + +### Development installation + +```bash +git clone https://github.com/PredicateSystems/py-predicate-secure.git +cd py-predicate-secure +pip install -e ".[dev]" +``` + +--- + +## Quick Start + +The simplest way to secure your agent is three lines of code: + +```python +from predicate_secure import SecureAgent + +# 1. Your existing agent (unchanged) +agent = YourAgent(task="Do something", llm=your_model) + +# 2. Wrap with SecureAgent +secure_agent = SecureAgent( + agent=agent, + policy="policy.yaml", # Your authorization rules + mode="strict", # Fail-closed mode +) + +# 3. Run with authorization +secure_agent.run() +``` + +That's it! Every action your agent attempts will now be checked against your policy. + +--- + +## Framework Guides + +### browser-use + +[browser-use](https://github.com/browser-use/browser-use) is an AI agent framework for browser automation. SecureAgent integrates seamlessly with browser-use agents. + +#### Basic Usage + +```python +from browser_use import Agent +from langchain_openai import ChatOpenAI +from predicate_secure import SecureAgent + +# Create your browser-use agent +llm = ChatOpenAI(model="gpt-4") +agent = Agent( + task="Buy wireless headphones under $50 on Amazon", + llm=llm, +) + +# Wrap with SecureAgent +secure_agent = SecureAgent( + agent=agent, + policy="policies/shopping.yaml", + mode="strict", +) + +# Run the agent +secure_agent.run() +``` + +#### Policy Example for browser-use + +```yaml +# policies/shopping.yaml +rules: + # Allow browsing Amazon + - action: "browser.*" + resource: "https://*.amazon.com/*" + effect: allow + + # Allow clicking checkout with verification + - action: "browser.click" + resource: "*checkout*" + effect: allow + require_verification: + - url_contains: "/checkout" + + # Block external sites + - action: "browser.navigate" + resource: "https://external.com/*" + effect: deny + + # Default deny + - action: "*" + resource: "*" + effect: deny +``` + +#### Using the Plugin API + +For more control, you can use the PredicateBrowserUsePlugin directly: + +```python +secure_agent = SecureAgent(agent=agent, policy="policy.yaml") + +# Get the plugin for lifecycle hooks +plugin = secure_agent.get_browser_use_plugin() + +# Run with lifecycle callbacks +result = await agent.run( + on_step_start=plugin.on_step_start, + on_step_end=plugin.on_step_end, +) +``` + +**Full example:** [examples/browser_use_checkout.py](../examples/browser_use_checkout.py) + +--- + +### Playwright + +[Playwright](https://playwright.dev/python/) is a browser automation library. SecureAgent can wrap Playwright pages to add authorization. + +#### Basic Usage + +```python +from playwright.async_api import async_playwright +from predicate_secure import SecureAgent + +async with async_playwright() as p: + browser = await p.chromium.launch() + page = await browser.new_page() + + # Wrap the page with SecureAgent + secure_agent = SecureAgent( + agent=page, + policy="policies/form.yaml", + mode="strict", + ) + + # Use the page as normal + await page.goto("https://example.com/form") + await page.fill("#email", "user@example.com") +``` + +#### Getting AgentRuntime + +For advanced use cases with the predicate SDK: + +```python +secure_agent = SecureAgent(agent=page, policy="policy.yaml") + +# Get the AgentRuntime +runtime = await secure_agent.get_runtime_async() + +# Get the pre-action authorizer +authorizer = secure_agent.get_pre_action_authorizer() + +# Use with RuntimeAgent +from predicate.runtime_agent import RuntimeAgent +runtime_agent = RuntimeAgent( + runtime=runtime, + executor=my_llm, + pre_action_authorizer=authorizer, +) +``` + +**Full example:** [examples/playwright_form_fill.py](../examples/playwright_form_fill.py) + +--- + +### LangChain + +[LangChain](https://www.langchain.com/) is a framework for building LLM applications. SecureAgent can wrap LangChain agents to authorize tool calls. + +#### Basic Usage + +```python +from langchain.agents import AgentExecutor, create_react_agent +from langchain_openai import ChatOpenAI +from predicate_secure import SecureAgent + +# Create your LangChain agent +llm = ChatOpenAI(model="gpt-4") +agent = create_react_agent(llm, tools, prompt) +agent_executor = AgentExecutor(agent=agent, tools=tools) + +# Wrap with SecureAgent +secure_agent = SecureAgent( + agent=agent_executor, + policy="policies/tools.yaml", + mode="strict", +) + +# Run the agent +result = agent_executor.invoke({"input": "Search for AI news"}) +``` + +#### Policy Example for LangChain + +```yaml +# policies/tools.yaml +rules: + # Allow search and calculator + - action: "tool.search" + resource: "*" + effect: allow + + - action: "tool.calculator" + resource: "*" + effect: allow + + # Block file operations + - action: "tool.file_write" + resource: "*" + effect: deny + + # Block shell commands + - action: "tool.shell" + resource: "*" + effect: deny + + # Default deny + - action: "*" + resource: "*" + effect: deny +``` + +#### Using SentienceLangChainCore + +For browser-enabled LangChain agents: + +```python +secure_agent = SecureAgent(agent=agent_executor, policy="policy.yaml") + +# Get the core with browser context +core = secure_agent.get_langchain_core(browser=browser) + +# Use core for tool interception +``` + +**Full example:** [examples/langchain_tool_guard.py](../examples/langchain_tool_guard.py) + +--- + +### PydanticAI + +[PydanticAI](https://github.com/pydantic/pydantic-ai) is a framework for building AI agents with Pydantic. SecureAgent supports PydanticAI agents. + +#### Basic Usage + +```python +from pydantic_ai import Agent +from predicate_secure import SecureAgent + +# Create your PydanticAI agent +agent = Agent(model="gpt-4") + +# Wrap with SecureAgent +secure_agent = SecureAgent( + agent=agent, + policy="policy.yaml", + mode="strict", +) +``` + +--- + +## Modes + +SecureAgent supports four execution modes: + +| Mode | Behavior | Use Case | +|------|----------|----------| +| `strict` | Fail-closed: deny action if policy check fails | Production deployments | +| `permissive` | Log but don't block unauthorized actions | Development/testing | +| `debug` | Human-readable trace output | Troubleshooting | +| `audit` | Record decisions without enforcement | Compliance monitoring | + +### Strict Mode (default) + +```python +secure_agent = SecureAgent( + agent=agent, + policy="policy.yaml", + mode="strict", # Actions denied by policy will raise an exception +) +``` + +If an action is denied, `AuthorizationDenied` is raised: + +```python +from predicate_secure import AuthorizationDenied + +try: + secure_agent.run() +except AuthorizationDenied as e: + print(f"Action blocked: {e}") + print(f"Decision: {e.decision}") +``` + +### Permissive Mode + +```python +secure_agent = SecureAgent( + agent=agent, + policy="policy.yaml", + mode="permissive", # Log unauthorized actions but don't block +) +``` + +### Debug Mode + +```python +secure_agent = SecureAgent( + agent=agent, + policy="policy.yaml", + mode="debug", # Show detailed trace output +) +``` + +### Audit Mode + +```python +secure_agent = SecureAgent( + agent=agent, + policy="policy.yaml", + mode="audit", # Record all decisions without blocking +) +``` + +--- + +## Writing Policies + +Policies are YAML files that define what actions your agent can perform. + +### Basic Structure + +```yaml +rules: + - action: "" + resource: "" + effect: allow | deny + require_verification: # optional + - +``` + +### Action Patterns + +Actions represent what the agent is trying to do: + +| Pattern | Matches | Example | +|---------|---------|---------| +| `browser.click` | Specific action | Only click events | +| `browser.*` | Action prefix | All browser actions | +| `tool.search` | Tool call | Search tool invocation | +| `api.call` | API request | HTTP API calls | +| `*` | Everything | Catch-all rule | + +### Resource Patterns + +Resources represent what the agent is acting on: + +| Pattern | Matches | Example | +|---------|---------|---------| +| `https://example.com/*` | URL prefix | All pages on domain | +| `*checkout*` | Contains text | Any checkout URL | +| `button#submit` | CSS selector | Specific element | +| `/safe/path/*` | File path prefix | Safe directory | +| `*` | Everything | Catch-all | + +### Verification Predicates + +Predicates ensure the action had the expected effect: + +```yaml +require_verification: + # URL checks + - url_contains: "/checkout" + - url_matches: "^https://.*\\.example\\.com/.*" + + # DOM checks + - element_exists: "#cart-items" + - element_text_contains: + selector: ".total" + text: "$" + + # Custom predicates + - predicate: "cart_not_empty" +``` + +### Rule Order + +Rules are evaluated top-to-bottom. The first matching rule wins: + +```yaml +rules: + # Specific rules first + - action: "browser.click" + resource: "*checkout*" + effect: allow + + # General rules after + - action: "browser.*" + resource: "https://example.com/*" + effect: allow + + # Default deny last + - action: "*" + resource: "*" + effect: deny +``` + +### Complete Policy Example + +```yaml +# policies/shopping.yaml +# +# Policy for an e-commerce shopping agent + +rules: + # Allow browsing the store + - action: "browser.navigate" + resource: "https://*.amazon.com/*" + effect: allow + + - action: "browser.click" + resource: "https://*.amazon.com/*" + effect: allow + + - action: "browser.fill" + resource: "https://*.amazon.com/*" + effect: allow + + # Allow checkout with verification + - action: "browser.click" + resource: "*place-order*" + effect: allow + require_verification: + - url_contains: "/checkout" + - element_exists: "#cart-items" + + # Block navigation to external sites + - action: "browser.navigate" + resource: "https://malicious.com/*" + effect: deny + + # Block sensitive actions + - action: "browser.fill" + resource: "*password*" + effect: deny + + # Default: deny everything else + - action: "*" + resource: "*" + effect: deny +``` + +--- + +## Debug Mode + +Debug mode provides detailed trace output for troubleshooting agent behavior. + +### Enabling Debug Mode + +```python +secure_agent = SecureAgent( + agent=agent, + policy="policy.yaml", + mode="debug", +) +``` + +### Trace Output Options + +```python +secure_agent = SecureAgent( + agent=agent, + mode="debug", + trace_format="console", # "console" or "json" + trace_file="trace.jsonl", # Optional file output + trace_colors=True, # ANSI colors (console only) + trace_verbose=True, # Verbose output +) +``` + +### Console Output Example + +``` +============================================================ +[predicate-secure] Session Start + Framework: browser_use + Mode: debug + Policy: policy.yaml + Principal: agent:default +============================================================ + +[Step 1] navigate → https://amazon.com + └─ OK (45ms) + +[Step 2] search → wireless headphones + └─ OK (120ms) + +[Step 3] click → add-to-cart-button + ├─ Policy: ALLOWED + │ Action: browser.click + │ Resource: add-to-cart-button + ├─ Cart Update: + │ + cart_item_1 + │ ~ cart_count + │ Before: 0 + │ After: 1 + ├─ Verification: PASS + │ Predicate: cart_not_empty + │ Message: Cart has 1 item + └─ OK (85ms) + +============================================================ +[predicate-secure] Session End: SUCCESS + Total Steps: 3 + Duration: 250ms +============================================================ +``` + +### JSON Trace Output + +```python +secure_agent = SecureAgent( + agent=agent, + mode="debug", + trace_format="json", + trace_file="trace.jsonl", +) +``` + +Output (one JSON object per line): + +```json +{"event_type": "session_start", "timestamp": "2024-01-01T00:00:00Z", "data": {"framework": "browser_use", "mode": "debug"}} +{"event_type": "step_start", "timestamp": "2024-01-01T00:00:01Z", "step_number": 1, "data": {"action": "navigate", "resource": "https://amazon.com"}} +{"event_type": "step_end", "timestamp": "2024-01-01T00:00:02Z", "step_number": 1, "duration_ms": 1000, "data": {"success": true}} +``` + +### Manual Tracing + +You can add custom trace points in your code: + +```python +# Trace a step +step = secure_agent.trace_step("custom_action", "custom_resource") +# ... do something ... +secure_agent.trace_step_end(step, success=True) + +# Trace a state change +secure_agent.trace_snapshot_diff( + before={"cart": []}, + after={"cart": ["item1"]}, + label="Cart Updated", +) + +# Trace a verification +secure_agent.trace_verification( + predicate="items_in_cart", + passed=True, + message="Cart has expected items", +) +``` + +--- + +## Advanced Usage + +### Getting the Pre-Action Authorizer + +For direct integration with RuntimeAgent: + +```python +secure_agent = SecureAgent(agent=agent, policy="policy.yaml") + +# Get the authorizer callback +authorizer = secure_agent.get_pre_action_authorizer() + +# Use with RuntimeAgent +from predicate.runtime_agent import RuntimeAgent +runtime_agent = RuntimeAgent( + runtime=runtime, + executor=my_llm, + pre_action_authorizer=authorizer, +) +``` + +### Getting Adapters + +Access the full adapter for any framework: + +```python +secure_agent = SecureAgent(agent=agent, policy="policy.yaml") + +# Get adapter with all wired components +adapter = secure_agent.get_adapter() + +print(adapter.agent_runtime) # AgentRuntime instance +print(adapter.backend) # Backend for DOM interaction +print(adapter.plugin) # Framework-specific plugin +print(adapter.executor) # LLM executor +print(adapter.metadata) # Framework info +``` + +### Attach Pattern + +Alternative way to create SecureAgent: + +```python +# Factory method +secure_agent = SecureAgent.attach( + agent, + policy="policy.yaml", + mode="strict", +) +``` + +### Environment Variables + +You can configure SecureAgent via environment variables: + +| Variable | Description | +|----------|-------------| +| `PREDICATE_AUTHORITY_POLICY_FILE` | Default policy file path | +| `PREDICATE_PRINCIPAL_ID` | Default agent principal ID | +| `PREDICATE_AUTHORITY_SIGNING_KEY` | Signing key for mandates | + +--- + +## Troubleshooting + +### Common Issues + +#### "AuthorizationDenied: Action denied" + +Your policy is blocking the action. Check: + +1. Is the action pattern correct? +2. Is the resource pattern matching? +3. Are rules in the right order (specific before general)? + +Debug with: + +```python +secure_agent = SecureAgent(agent=agent, policy="policy.yaml", mode="debug") +``` + +#### "UnsupportedFrameworkError" + +SecureAgent couldn't detect your agent's framework. Make sure you're using a supported framework: + +- browser-use Agent +- Playwright Page (sync or async) +- LangChain AgentExecutor +- PydanticAI Agent + +#### "PolicyLoadError" + +The policy file couldn't be loaded. Check: + +1. File path is correct +2. YAML syntax is valid +3. Required fields are present + +#### Import Errors + +Make sure you have the correct extras installed: + +```bash +pip install predicate-secure[browser-use] # For browser-use +pip install predicate-secure[playwright] # For Playwright +pip install predicate-secure[langchain] # For LangChain +``` + +### Getting Help + +- GitHub Issues: https://github.com/PredicateSystems/py-predicate-secure/issues +- Documentation: https://predicate.systems/docs + +--- + +## API Reference + +### SecureAgent + +```python +class SecureAgent: + def __init__( + self, + agent: Any, + policy: str | Path | None = None, + mode: str = "strict", + principal_id: str | None = None, + tenant_id: str | None = None, + session_id: str | None = None, + sidecar_url: str | None = None, + signing_key: str | None = None, + mandate_ttl_seconds: int = 300, + trace_format: str = "console", + trace_file: str | Path | None = None, + trace_colors: bool = True, + trace_verbose: bool = True, + ): ... + + # Properties + @property + def config(self) -> SecureAgentConfig: ... + @property + def wrapped(self) -> WrappedAgent: ... + @property + def framework(self) -> Framework: ... + @property + def tracer(self) -> DebugTracer | None: ... + + # Methods + def run(self, task: str | None = None) -> Any: ... + def get_pre_action_authorizer(self) -> Callable: ... + def get_adapter(self, **kwargs) -> AdapterResult: ... + async def get_runtime_async(self, **kwargs) -> AgentRuntime: ... + def get_browser_use_plugin(self, **kwargs) -> PredicateBrowserUsePlugin: ... + def get_langchain_core(self, **kwargs) -> SentienceLangChainCore: ... + + # Tracing + def trace_step(self, action: str, resource: str = "") -> int | None: ... + def trace_step_end(self, step_number: int, success: bool = True, **kwargs) -> None: ... + def trace_snapshot_diff(self, before: dict, after: dict, label: str = "") -> None: ... + def trace_verification(self, predicate: str, passed: bool, **kwargs) -> None: ... +``` + +### Exceptions + +```python +class AuthorizationDenied(Exception): + decision: Any # The authorization decision + +class VerificationFailed(Exception): + predicate: str # The failed predicate + +class PolicyLoadError(Exception): + pass + +class UnsupportedFrameworkError(Exception): + pass +``` + +### Modes + +```python +MODE_STRICT = "strict" +MODE_PERMISSIVE = "permissive" +MODE_DEBUG = "debug" +MODE_AUDIT = "audit" +``` diff --git a/examples/browser_use_checkout.py b/examples/browser_use_checkout.py index 2ada9aa..f2483fd 100644 --- a/examples/browser_use_checkout.py +++ b/examples/browser_use_checkout.py @@ -13,14 +13,14 @@ """ import asyncio -import os +import tempfile + +from predicate_secure import SecureAgent # Uncomment to use with actual browser-use # from browser_use import Agent # from langchain_openai import ChatOpenAI -from predicate_secure import SecureAgent - def create_shopping_policy() -> str: """Create a shopping policy file and return the path.""" @@ -59,10 +59,11 @@ def create_shopping_policy() -> str: resource: "*" effect: deny """ - policy_path = "/tmp/shopping_policy.yaml" - with open(policy_path, "w") as f: + with tempfile.NamedTemporaryFile( + mode="w", suffix="_shopping_policy.yaml", delete=False + ) as f: f.write(policy_content) - return policy_path + return f.name async def run_secure_checkout(): diff --git a/examples/langchain_tool_guard.py b/examples/langchain_tool_guard.py index a3738ec..89ab1f3 100644 --- a/examples/langchain_tool_guard.py +++ b/examples/langchain_tool_guard.py @@ -12,6 +12,8 @@ pip install langchain langchain-openai """ +import tempfile + # Uncomment for actual LangChain usage # from langchain.agents import AgentExecutor, create_react_agent # from langchain_openai import ChatOpenAI @@ -65,10 +67,11 @@ def create_tool_policy() -> str: resource: "*" effect: deny """ - policy_path = "/tmp/tool_policy.yaml" - with open(policy_path, "w") as f: + with tempfile.NamedTemporaryFile( + mode="w", suffix="_tool_policy.yaml", delete=False + ) as f: f.write(policy_content) - return policy_path + return f.name def run_secure_tool_agent(): diff --git a/examples/playwright_form_fill.py b/examples/playwright_form_fill.py index 850358a..5f69d37 100644 --- a/examples/playwright_form_fill.py +++ b/examples/playwright_form_fill.py @@ -13,12 +13,13 @@ """ import asyncio +import tempfile + +from predicate_secure import SecureAgent # Uncomment for actual Playwright usage # from playwright.async_api import async_playwright -from predicate_secure import SecureAgent - def create_form_policy() -> str: """Create a form automation policy file.""" @@ -56,10 +57,11 @@ def create_form_policy() -> str: resource: "*" effect: deny """ - policy_path = "/tmp/form_policy.yaml" - with open(policy_path, "w") as f: + with tempfile.NamedTemporaryFile( + mode="w", suffix="_form_policy.yaml", delete=False + ) as f: f.write(policy_content) - return policy_path + return f.name async def run_secure_form_fill(): diff --git a/src/predicate_secure/tracing.py b/src/predicate_secure/tracing.py index 910351a..2a4df6b 100644 --- a/src/predicate_secure/tracing.py +++ b/src/predicate_secure/tracing.py @@ -57,7 +57,9 @@ class SnapshotDiff: added: list[str] = field(default_factory=list) removed: list[str] = field(default_factory=list) - changed: list[dict] = field(default_factory=list) # {"element": str, "before": str, "after": str} + changed: list[dict] = field( + default_factory=list + ) # {"element": str, "before": str, "after": str} def is_empty(self) -> bool: """Check if diff is empty (no changes).""" @@ -215,10 +217,7 @@ def trace_session_start( if self.format == TraceFormat.CONSOLE: self.output.write("\n") self.output.write(self._color("=" * 60, "bold") + "\n") - self.output.write( - self._color("[predicate-secure]", "cyan") - + " Session Start\n" - ) + self.output.write(self._color("[predicate-secure]", "cyan") + " Session Start\n") self.output.write(f" Framework: {self._color(framework, 'blue')}\n") self.output.write(f" Mode: {self._color(mode, 'yellow')}\n") if policy: @@ -248,14 +247,9 @@ def trace_session_end(self, success: bool = True, error: str | None = None) -> N if self.format == TraceFormat.CONSOLE: self.output.write("\n") self.output.write(self._color("=" * 60, "bold") + "\n") - status = ( - self._color("SUCCESS", "green") - if success - else self._color("FAILED", "red") - ) + status = self._color("SUCCESS", "green") if success else self._color("FAILED", "red") self.output.write( - self._color("[predicate-secure]", "cyan") - + f" Session End: {status}\n" + self._color("[predicate-secure]", "cyan") + f" Session End: {status}\n" ) self.output.write(f" Total Steps: {self._step_count}\n") if duration_ms: @@ -305,8 +299,7 @@ def trace_step_start( if self.format == TraceFormat.CONSOLE: self.output.write( - self._color(f"[Step {step_number}]", "bold") - + f" {self._color(action, 'magenta')}" + self._color(f"[Step {step_number}]", "bold") + f" {self._color(action, 'magenta')}" ) if resource: self.output.write(f" → {self._color(resource, 'blue')}") @@ -341,11 +334,7 @@ def trace_step_end( self._emit(event) if self.format == TraceFormat.CONSOLE and self.verbose: - status = ( - self._color("OK", "green") - if success - else self._color("FAILED", "red") - ) + status = self._color("OK", "green") if success else self._color("FAILED", "red") duration_str = f" ({duration_ms:.1f}ms)" if duration_ms else "" self.output.write(f" └─ {status}{duration_str}\n") if error: @@ -379,9 +368,7 @@ def trace_policy_decision( if decision.reason: self.output.write(f" │ Reason: {decision.reason}\n") if decision.policy_rule: - self.output.write( - f" │ Rule: {self._color(decision.policy_rule, 'dim')}\n" - ) + self.output.write(f" │ Rule: {self._color(decision.policy_rule, 'dim')}\n") self.output.flush() def trace_snapshot_diff( @@ -404,19 +391,13 @@ def trace_snapshot_diff( if self.format == TraceFormat.CONSOLE: if diff.is_empty(): - self.output.write( - f" ├─ {label}: {self._color('(no changes)', 'dim')}\n" - ) + self.output.write(f" ├─ {label}: {self._color('(no changes)', 'dim')}\n") else: self.output.write(f" ├─ {label}:\n") for added in diff.added: - self.output.write( - f" │ {self._color('+', 'green')} {added}\n" - ) + self.output.write(f" │ {self._color('+', 'green')} {added}\n") for removed in diff.removed: - self.output.write( - f" │ {self._color('-', 'red')} {removed}\n" - ) + self.output.write(f" │ {self._color('-', 'red')} {removed}\n") for changed in diff.changed: self.output.write( f" │ {self._color('~', 'yellow')} {changed.get('element', 'unknown')}\n" @@ -449,11 +430,7 @@ def trace_verification_result( self._emit(event) if self.format == TraceFormat.CONSOLE: - status = ( - self._color("PASS", "green") - if result.passed - else self._color("FAIL", "red") - ) + status = self._color("PASS", "green") if result.passed else self._color("FAIL", "red") self.output.write(f" ├─ Verification: {status}\n") self.output.write(f" │ Predicate: {result.predicate}\n") if result.message: diff --git a/tests/test_tracing.py b/tests/test_tracing.py index 74d8733..b59cad4 100644 --- a/tests/test_tracing.py +++ b/tests/test_tracing.py @@ -294,9 +294,7 @@ def test_tracer_verification_pass(self): output = io.StringIO() tracer = DebugTracer(format="console", output=output, use_colors=False) - tracer.trace_verification_result( - VerificationResult(predicate="item_in_cart", passed=True) - ) + tracer.trace_verification_result(VerificationResult(predicate="item_in_cart", passed=True)) out = output.getvalue() assert "PASS" in out From 15c8f2f462dd91ac13a24ecde0cd807b6717d93b Mon Sep 17 00:00:00 2001 From: SentienceDEV Date: Mon, 23 Feb 2026 18:49:32 -0800 Subject: [PATCH 3/4] linted --- examples/browser_use_checkout.py | 4 +--- examples/langchain_tool_guard.py | 8 +++----- examples/playwright_form_fill.py | 4 +--- 3 files changed, 5 insertions(+), 11 deletions(-) diff --git a/examples/browser_use_checkout.py b/examples/browser_use_checkout.py index f2483fd..30a89b4 100644 --- a/examples/browser_use_checkout.py +++ b/examples/browser_use_checkout.py @@ -59,9 +59,7 @@ def create_shopping_policy() -> str: resource: "*" effect: deny """ - with tempfile.NamedTemporaryFile( - mode="w", suffix="_shopping_policy.yaml", delete=False - ) as f: + with tempfile.NamedTemporaryFile(mode="w", suffix="_shopping_policy.yaml", delete=False) as f: f.write(policy_content) return f.name diff --git a/examples/langchain_tool_guard.py b/examples/langchain_tool_guard.py index 89ab1f3..5c11126 100644 --- a/examples/langchain_tool_guard.py +++ b/examples/langchain_tool_guard.py @@ -14,13 +14,13 @@ import tempfile +from predicate_secure import SecureAgent + # Uncomment for actual LangChain usage # from langchain.agents import AgentExecutor, create_react_agent # from langchain_openai import ChatOpenAI # from langchain.tools import Tool -from predicate_secure import SecureAgent - def create_tool_policy() -> str: """Create a tool authorization policy.""" @@ -67,9 +67,7 @@ def create_tool_policy() -> str: resource: "*" effect: deny """ - with tempfile.NamedTemporaryFile( - mode="w", suffix="_tool_policy.yaml", delete=False - ) as f: + with tempfile.NamedTemporaryFile(mode="w", suffix="_tool_policy.yaml", delete=False) as f: f.write(policy_content) return f.name diff --git a/examples/playwright_form_fill.py b/examples/playwright_form_fill.py index 5f69d37..1dfe6ff 100644 --- a/examples/playwright_form_fill.py +++ b/examples/playwright_form_fill.py @@ -57,9 +57,7 @@ def create_form_policy() -> str: resource: "*" effect: deny """ - with tempfile.NamedTemporaryFile( - mode="w", suffix="_form_policy.yaml", delete=False - ) as f: + with tempfile.NamedTemporaryFile(mode="w", suffix="_form_policy.yaml", delete=False) as f: f.write(policy_content) return f.name From a676b567cf6d52341ece184b3385a799a9a7ed31 Mon Sep 17 00:00:00 2001 From: SentienceDEV Date: Mon, 23 Feb 2026 18:55:20 -0800 Subject: [PATCH 4/4] optionality for pre- and post- --- README.md | 80 ++++++++++++++++++++++++++ docs/user-manual.md | 135 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 215 insertions(+) diff --git a/README.md b/README.md index d5c0a31..d40453a 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,86 @@ SecureAgent └── RuntimeAgent (orchestration, pre-action hook) ``` +## Sidecar Prerequisite (Optional) + +The [Predicate Authority Sidecar](https://github.com/PredicateSystems/predicate-authority-sidecar) is **only required if you need pre-action authorization**—real-time policy evaluation that blocks unauthorized actions before they execute. + +| Feature | Sidecar Required? | +|---------|-------------------| +| Pre-action authorization (`strict`/`permissive` modes) | **Yes** | +| Debug tracing (`debug` mode) | No | +| Audit logging (`audit` mode) | No | +| Policy development & testing | No | + +If you only need debug tracing or audit logging, you can skip the sidecar entirely. + +### Starting the Sidecar + +**Docker (Recommended):** + +```bash +docker run -d -p 8787:8787 ghcr.io/predicatesystems/predicate-authorityd:latest +``` + +**Or download binary:** + +```bash +# macOS (Apple Silicon) +curl -fsSL https://github.com/PredicateSystems/predicate-authority-sidecar/releases/latest/download/predicate-authorityd-darwin-arm64.tar.gz | tar -xz +./predicate-authorityd --port 8787 + +# Linux x64 +curl -fsSL https://github.com/PredicateSystems/predicate-authority-sidecar/releases/latest/download/predicate-authorityd-linux-x64.tar.gz | tar -xz +./predicate-authorityd --port 8787 +``` + +**Verify:** + +```bash +curl http://localhost:8787/health +# {"status":"ok"} +``` + +The sidecar handles policy evaluation in <25ms with zero egress—no data leaves your infrastructure. + +## Flexible Verification + +Use pre-execution authorization and post-execution verification **independently or together**: + +| Pattern | Use Case | Sidecar? | +|---------|----------|----------| +| Pre-execution only | Block unauthorized actions | Yes | +| Post-execution only | Verify outcomes after completion | No | +| Both (full loop) | Block + verify for max safety | Yes | + +**Pre-execution only** (policy without `require_verification`): + +```yaml +rules: + - action: "browser.*" + resource: "https://amazon.com/*" + effect: allow +``` + +**Post-execution only** (debug mode, no sidecar): + +```python +secure_agent = SecureAgent(agent=agent, mode="debug") +secure_agent.run() +secure_agent.trace_verification("cart_not_empty", passed=True) +``` + +**Both** (policy with `require_verification`): + +```yaml +rules: + - action: "browser.click" + resource: "*checkout*" + effect: allow + require_verification: + - element_exists: "#order-confirmation" +``` + ## Debug Mode Debug mode provides human-readable trace output for troubleshooting: diff --git a/docs/user-manual.md b/docs/user-manual.md index 2213412..a569a7a 100644 --- a/docs/user-manual.md +++ b/docs/user-manual.md @@ -31,6 +31,141 @@ A comprehensive guide to securing your AI agents with predicate-secure. - **Cryptographic audit** - All decisions are logged with tamper-proof receipts - **Zero refactoring** - Works with your existing agent code +### Sidecar Prerequisite (Optional) + +The [Predicate Authority Sidecar](https://github.com/PredicateSystems/predicate-authority-sidecar) is **only required if you need pre-action authorization**—real-time policy evaluation that blocks unauthorized actions before they execute. + +| Feature | Sidecar Required? | +|---------|-------------------| +| Pre-action authorization (`strict`/`permissive` modes) | **Yes** | +| Debug tracing (`debug` mode) | No | +| Audit logging (`audit` mode) | No | +| Policy development & testing | No | + +**If you only need debug tracing, audit logging, or policy development, you can skip the sidecar entirely.** + +#### Starting the Sidecar + +| Resource | Link | +|----------|------| +| Sidecar Repository | [predicate-authority-sidecar](https://github.com/PredicateSystems/predicate-authority-sidecar) | +| Download Binaries | [Latest Releases](https://github.com/PredicateSystems/predicate-authority-sidecar/releases) | + +**Option A: Docker (Recommended)** + +```bash +docker run -d -p 8787:8787 ghcr.io/predicatesystems/predicate-authorityd:latest +``` + +**Option B: Download Binary** + +```bash +# macOS (Apple Silicon) +curl -fsSL https://github.com/PredicateSystems/predicate-authority-sidecar/releases/latest/download/predicate-authorityd-darwin-arm64.tar.gz | tar -xz +chmod +x predicate-authorityd +./predicate-authorityd --port 8787 + +# Linux x64 +curl -fsSL https://github.com/PredicateSystems/predicate-authority-sidecar/releases/latest/download/predicate-authorityd-linux-x64.tar.gz | tar -xz +chmod +x predicate-authorityd +./predicate-authorityd --port 8787 +``` + +See [all platform binaries](https://github.com/PredicateSystems/predicate-authority-sidecar/releases) for Linux ARM64, macOS Intel, and Windows. + +**Verify it's running:** + +```bash +curl http://localhost:8787/health +# {"status":"ok"} +``` + +The sidecar handles policy evaluation in <25ms with zero egress—no data leaves your infrastructure. + +### Flexible Verification Options + +You can use pre-execution authorization and post-execution verification **independently or together**: + +| Usage Pattern | Description | Sidecar Required? | +|---------------|-------------|-------------------| +| Pre-execution only | Block unauthorized actions before they run | Yes | +| Post-execution only | Verify outcomes after actions complete | No | +| Both (full loop) | Block + verify for maximum safety | Yes | + +#### Pre-Execution Authorization Only + +Use `strict` or `permissive` mode with a policy that has no `require_verification` predicates: + +```python +secure_agent = SecureAgent( + agent=agent, + policy="policy.yaml", + mode="strict", # Requires sidecar +) +``` + +```yaml +# policy.yaml - authorization only, no verification +rules: + - action: "browser.*" + resource: "https://amazon.com/*" + effect: allow + + - action: "*" + resource: "*" + effect: deny +``` + +#### Post-Execution Verification Only + +Use `debug` or `audit` mode and manually verify outcomes—no sidecar needed: + +```python +secure_agent = SecureAgent( + agent=agent, + mode="debug", # No sidecar required +) + +# Run agent +result = secure_agent.run() + +# Verify outcomes after execution +secure_agent.trace_verification( + predicate="cart_not_empty", + passed=check_cart_has_items(), + message="Verified cart contains expected items", +) + +secure_agent.trace_verification( + predicate="order_confirmed", + passed=check_order_confirmation(), + message="Order confirmation page displayed", +) +``` + +#### Both: Full Closed-Loop Verification + +Use `strict` mode with `require_verification` predicates for maximum safety: + +```python +secure_agent = SecureAgent( + agent=agent, + policy="policy.yaml", + mode="strict", # Requires sidecar +) +``` + +```yaml +# policy.yaml - authorization + verification +rules: + - action: "browser.click" + resource: "*checkout*" + effect: allow + require_verification: # Post-execution check + - url_contains: "/order-confirmation" + - element_exists: "#order-number" +``` + ### How it works ```