Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 870cc68dc3 | |||
| 1099eefedd | |||
| f342808d2b | |||
| f554523378 | |||
| 69e82f0258 | |||
| 52ca641679 | |||
| 60d68d85f3 | |||
| 777005a42b | |||
| 8cdf0683a3 |
@@ -8,7 +8,7 @@
|
||||
|
||||
# Strix
|
||||
|
||||
### Open-source AI hackers to find and fix your app’s vulnerabilities.
|
||||
### The open-source AI pentesting tool. Autonomous AI hackers that find and fix your app’s vulnerabilities.
|
||||
|
||||
<br/>
|
||||
|
||||
@@ -40,15 +40,15 @@
|
||||
|
||||
## Strix Overview
|
||||
|
||||
Strix are autonomous AI agents that act just like real hackers - they run your code dynamically, find vulnerabilities, and validate them through actual proof-of-concepts. Built for developers and security teams who need fast, accurate security testing without the overhead of manual pentesting or the false positives of static analysis tools.
|
||||
Strix are autonomous AI penetration testing agents that act just like real hackers - they run your code dynamically, find vulnerabilities, and validate them through actual proof-of-concepts. Built for developers and security teams who need fast, accurate security testing without the overhead of manual pentesting or the false positives of static analysis tools.
|
||||
|
||||
**Key Capabilities:**
|
||||
|
||||
- **Full hacker toolkit** out of the box
|
||||
- **Teams of agents** that collaborate and scale
|
||||
- **Real validation** with PoCs, not false positives
|
||||
- **Developer‑first** CLI with actionable reports
|
||||
- **Auto‑fix & reporting** to accelerate remediation
|
||||
- **Full pentesting toolkit** - reconnaissance, exploitation, and validation out of the box
|
||||
- **Multi-agent orchestration** - teams of AI pentesters that collaborate and scale
|
||||
- **Real exploit validation** - working PoCs, not false positives like legacy vulnerability scanners
|
||||
- **Developer‑first CLI** - actionable findings with remediation guidance
|
||||
- **Auto‑fix & reporting** - generate patches and compliance-ready pentest reports
|
||||
|
||||
|
||||
<br>
|
||||
@@ -95,13 +95,13 @@ strix --target ./app-directory
|
||||
|
||||
## ☁️ Strix Platform
|
||||
|
||||
Try the Strix full-stack security platform at **[app.strix.ai](https://app.strix.ai)** — sign up for free, connect your repos and domains, and launch a pentest in minutes.
|
||||
Try the Strix full-stack penetration testing platform at **[app.strix.ai](https://app.strix.ai)** - sign up for free, connect your repos and domains, and launch a pentest in minutes.
|
||||
|
||||
- **Validated findings with PoCs** and reproduction steps
|
||||
- **One-click autofix** as ready-to-merge pull requests
|
||||
- **Continuous monitoring** across code, cloud, and infrastructure
|
||||
- **Integrations** with GitHub, Slack, Jira, Linear, and CI/CD pipelines
|
||||
- **Continuous learning** that builds on past findings and remediations
|
||||
- **Validated findings with PoCs** - every vulnerability includes a working proof-of-concept exploit and reproduction steps
|
||||
- **One-click autofix** - AI-generated security patches as ready-to-merge pull requests
|
||||
- **Continuous pentesting** - always-on vulnerability scanning that keeps pace with your deployments
|
||||
- **DevSecOps integrations** - GitHub, GitLab, Bitbucket, Slack, Jira, Linear, and CI/CD pipelines
|
||||
- **Continuous learning** - AI that builds on past findings, adapts to your codebase, and reduces false positives over time
|
||||
|
||||
[**Start your first pentest →**](https://app.strix.ai)
|
||||
|
||||
@@ -109,37 +109,38 @@ Try the Strix full-stack security platform at **[app.strix.ai](https://app.strix
|
||||
|
||||
## ✨ Features
|
||||
|
||||
### Agentic Security Tools
|
||||
### Agentic Pentesting Tools
|
||||
|
||||
Strix agents come equipped with a comprehensive security testing toolkit:
|
||||
Strix agents come equipped with a comprehensive offensive security toolkit - the same tools used by professional penetration testers and ethical hackers:
|
||||
|
||||
- **Full HTTP Proxy** - Full request/response manipulation and analysis
|
||||
- **Browser Automation** - Multi-tab browser for testing of XSS, CSRF, auth flows
|
||||
- **Terminal Environments** - Interactive shells for command execution and testing
|
||||
- **Python Runtime** - Custom exploit development and validation
|
||||
- **Reconnaissance** - Automated OSINT and attack surface mapping
|
||||
- **Code Analysis** - Static and dynamic analysis capabilities
|
||||
- **Knowledge Management** - Structured findings and attack documentation
|
||||
- **HTTP Interception Proxy** - Full request/response manipulation and analysis with Caido
|
||||
- **Browser Exploitation** - Automated browser for testing XSS, CSRF, clickjacking, and auth bypass flows
|
||||
- **Shell & Command Execution** - Interactive terminal for exploit development and post-exploitation
|
||||
- **Custom Exploit Runtime** - Python sandbox for writing and validating proof-of-concept exploits
|
||||
- **Reconnaissance & OSINT** - Automated attack surface mapping, subdomain enumeration, and fingerprinting
|
||||
- **Static & Dynamic Code Analysis** - SAST + DAST capabilities for comprehensive application security testing
|
||||
- **Vulnerability Knowledge Base** - Structured findings with CVSS scoring and OWASP classification
|
||||
|
||||
### Comprehensive Vulnerability Detection
|
||||
### Comprehensive Vulnerability Scanner
|
||||
|
||||
Strix can identify and validate a wide range of security vulnerabilities:
|
||||
Strix identifies, validates, and exploits a wide range of security vulnerabilities across the OWASP Top 10 and beyond:
|
||||
|
||||
- **Access Control** - IDOR, privilege escalation, auth bypass
|
||||
- **Injection Attacks** - SQL, NoSQL, command injection
|
||||
- **Server-Side** - SSRF, XXE, deserialization flaws
|
||||
- **Client-Side** - XSS, prototype pollution, DOM vulnerabilities
|
||||
- **Business Logic** - Race conditions, workflow manipulation
|
||||
- **Authentication** - JWT vulnerabilities, session management
|
||||
- **Infrastructure** - Misconfigurations, exposed services
|
||||
- **Broken Access Control** - IDOR, privilege escalation, auth bypass
|
||||
- **Injection Attacks** - SQL injection, NoSQL injection, OS command injection, SSTI
|
||||
- **Server-Side Vulnerabilities** - SSRF, XXE, insecure deserialization, RCE
|
||||
- **Client-Side Attacks** - XSS (stored/reflected/DOM), prototype pollution, CSRF
|
||||
- **Business Logic Flaws** - Race conditions, payment manipulation, workflow bypass
|
||||
- **Authentication & Session** - JWT attacks, session fixation, credential stuffing vectors
|
||||
- **Infrastructure & Cloud** - Misconfigurations, exposed services, cloud security issues
|
||||
- **API Security** - Broken authentication, mass assignment, rate limiting bypass
|
||||
|
||||
### Graph of Agents
|
||||
### Graph of Agents (Multi-Agent Pentesting)
|
||||
|
||||
Advanced multi-agent orchestration for comprehensive security testing:
|
||||
Advanced multi-agent orchestration for comprehensive automated penetration testing:
|
||||
|
||||
- **Distributed Workflows** - Specialized agents for different attacks and assets
|
||||
- **Scalable Testing** - Parallel execution for fast comprehensive coverage
|
||||
- **Dynamic Coordination** - Agents collaborate and share discoveries
|
||||
- **Distributed Pentesting** - Specialized AI agents for recon, exploitation, and post-exploitation
|
||||
- **Scalable Security Testing** - Parallel execution across multiple targets for fast, comprehensive coverage
|
||||
- **Dynamic Coordination** - Agents share discoveries, chain vulnerabilities, and collaborate like a red team
|
||||
|
||||
---
|
||||
|
||||
@@ -182,7 +183,7 @@ strix -n --target ./ --scan-mode quick --scope-mode diff --diff-base origin/main
|
||||
|
||||
### Headless Mode
|
||||
|
||||
Run Strix programmatically without interactive UI using the `-n/--non-interactive` flag—perfect for servers and automated jobs. The CLI prints real-time vulnerability findings, and the final report before exiting. Exits with non-zero code when vulnerabilities are found.
|
||||
Run Strix programmatically without interactive UI using the `-n/--non-interactive` flag - perfect for servers and automated jobs. The CLI prints real-time vulnerability findings, and the final report before exiting. Exits with non-zero code when vulnerabilities are found.
|
||||
|
||||
```bash
|
||||
strix -n --target https://your-app.com
|
||||
@@ -239,19 +240,19 @@ export STRIX_REASONING_EFFORT="high" # control thinking effort (default: high,
|
||||
|
||||
**Recommended models for best results:**
|
||||
|
||||
- [OpenAI GPT-5.4](https://openai.com/api/) — `openai/gpt-5.4`
|
||||
- [Anthropic Claude Sonnet 4.6](https://claude.com/platform/api) — `anthropic/claude-sonnet-4-6`
|
||||
- [Google Gemini 3 Pro Preview](https://cloud.google.com/vertex-ai) — `vertex_ai/gemini-3-pro-preview`
|
||||
- [OpenAI GPT-5.4](https://openai.com/api/) - `openai/gpt-5.4`
|
||||
- [Anthropic Claude Sonnet 4.6](https://claude.com/platform/api) - `anthropic/claude-sonnet-4-6`
|
||||
- [Google Gemini 3 Pro Preview](https://cloud.google.com/vertex-ai) - `vertex_ai/gemini-3-pro-preview`
|
||||
|
||||
See the [LLM Providers documentation](https://docs.strix.ai/llm-providers/overview) for all supported providers including Vertex AI, Bedrock, Azure, and local models.
|
||||
|
||||
## Enterprise
|
||||
## Enterprise Pentesting
|
||||
|
||||
Get the same Strix experience with [enterprise-grade](https://strix.ai/demo) controls: SSO (SAML/OIDC), custom compliance reports, dedicated support & SLA, custom deployment options (VPC/self-hosted), BYOK model support, and tailored agents optimized for your environment. [Learn more](https://strix.ai/demo).
|
||||
Get the same Strix experience with [enterprise-grade](https://strix.ai/demo) controls: SSO (SAML/OIDC), custom compliance-ready penetration testing reports (SOC 2, ISO 27001, PCI DSS), dedicated support & SLA, custom deployment options (VPC/self-hosted), BYOK model support, and tailored AI pentesting agents optimized for your environment. [Learn more](https://strix.ai/demo).
|
||||
|
||||
## Documentation
|
||||
|
||||
Full documentation is available at **[docs.strix.ai](https://docs.strix.ai)** — including detailed guides for usage, CI/CD integrations, skills, and advanced configuration.
|
||||
Full documentation is available at **[docs.strix.ai](https://docs.strix.ai)** - including detailed guides for usage, CI/CD integrations, skills, and advanced configuration.
|
||||
|
||||
## Contributing
|
||||
|
||||
@@ -274,3 +275,5 @@ Strix builds on the incredible work of open-source projects like [LiteLLM](https
|
||||
> Only test apps you own or have permission to test. You are responsible for using Strix ethically and legally.
|
||||
|
||||
</div>
|
||||
|
||||

|
||||
|
||||
@@ -3,6 +3,10 @@ title: "Configuration"
|
||||
description: "Environment variables for Strix"
|
||||
---
|
||||
|
||||
import { ScarfPixel } from "/snippets/scarf-pixel.mdx";
|
||||
|
||||
<ScarfPixel />
|
||||
|
||||
Configure Strix using environment variables or a config file.
|
||||
|
||||
## LLM Configuration
|
||||
|
||||
@@ -3,6 +3,10 @@ title: "Skills"
|
||||
description: "Specialized knowledge packages that enhance agent capabilities"
|
||||
---
|
||||
|
||||
import { ScarfPixel } from "/snippets/scarf-pixel.mdx";
|
||||
|
||||
<ScarfPixel />
|
||||
|
||||
Skills are structured knowledge packages that give Strix agents deep expertise in specific vulnerability types, technologies, and testing methodologies.
|
||||
|
||||
## The Idea
|
||||
|
||||
@@ -3,6 +3,10 @@ title: "Introduction"
|
||||
description: "Managed security testing without local setup"
|
||||
---
|
||||
|
||||
import { ScarfPixel } from "/snippets/scarf-pixel.mdx";
|
||||
|
||||
<ScarfPixel />
|
||||
|
||||
Skip the setup. Run Strix in the cloud at [app.strix.ai](https://app.strix.ai).
|
||||
|
||||
## Features
|
||||
|
||||
@@ -3,6 +3,10 @@ title: "Contributing"
|
||||
description: "Contribute to Strix development"
|
||||
---
|
||||
|
||||
import { ScarfPixel } from "/snippets/scarf-pixel.mdx";
|
||||
|
||||
<ScarfPixel />
|
||||
|
||||
## Development Setup
|
||||
|
||||
### Prerequisites
|
||||
|
||||
@@ -3,6 +3,10 @@ title: "Introduction"
|
||||
description: "Open-source AI hackers to secure your apps"
|
||||
---
|
||||
|
||||
import { ScarfPixel } from "/snippets/scarf-pixel.mdx";
|
||||
|
||||
<ScarfPixel />
|
||||
|
||||
Strix are autonomous AI agents that act like real hackers—they run your code dynamically, find vulnerabilities, and validate them with proof-of-concepts. Built for developers and security teams who need fast, accurate security testing without the overhead of manual pentesting or the false positives of static analysis tools.
|
||||
|
||||
<Frame>
|
||||
|
||||
@@ -3,6 +3,10 @@ title: "CI/CD Integration"
|
||||
description: "Run Strix in any CI/CD pipeline"
|
||||
---
|
||||
|
||||
import { ScarfPixel } from "/snippets/scarf-pixel.mdx";
|
||||
|
||||
<ScarfPixel />
|
||||
|
||||
Strix runs in headless mode for automated pipelines.
|
||||
|
||||
## Headless Mode
|
||||
|
||||
@@ -3,6 +3,10 @@ title: "GitHub Actions"
|
||||
description: "Run Strix security scans on every pull request"
|
||||
---
|
||||
|
||||
import { ScarfPixel } from "/snippets/scarf-pixel.mdx";
|
||||
|
||||
<ScarfPixel />
|
||||
|
||||
Integrate Strix into your GitHub workflow to catch vulnerabilities before they reach production.
|
||||
|
||||
## Basic Workflow
|
||||
|
||||
@@ -3,6 +3,10 @@ title: "Anthropic"
|
||||
description: "Configure Strix with Claude models"
|
||||
---
|
||||
|
||||
import { ScarfPixel } from "/snippets/scarf-pixel.mdx";
|
||||
|
||||
<ScarfPixel />
|
||||
|
||||
## Setup
|
||||
|
||||
```bash
|
||||
|
||||
@@ -3,6 +3,10 @@ title: "Azure OpenAI"
|
||||
description: "Configure Strix with OpenAI models via Azure"
|
||||
---
|
||||
|
||||
import { ScarfPixel } from "/snippets/scarf-pixel.mdx";
|
||||
|
||||
<ScarfPixel />
|
||||
|
||||
## Setup
|
||||
|
||||
```bash
|
||||
|
||||
@@ -3,6 +3,10 @@ title: "AWS Bedrock"
|
||||
description: "Configure Strix with models via AWS Bedrock"
|
||||
---
|
||||
|
||||
import { ScarfPixel } from "/snippets/scarf-pixel.mdx";
|
||||
|
||||
<ScarfPixel />
|
||||
|
||||
## Setup
|
||||
|
||||
```bash
|
||||
|
||||
@@ -3,6 +3,10 @@ title: "Local Models"
|
||||
description: "Run Strix with self-hosted LLMs for privacy and air-gapped testing"
|
||||
---
|
||||
|
||||
import { ScarfPixel } from "/snippets/scarf-pixel.mdx";
|
||||
|
||||
<ScarfPixel />
|
||||
|
||||
Running Strix with local models allows for completely offline, privacy-first security assessments. Data never leaves your machine, making this ideal for sensitive internal networks or air-gapped environments.
|
||||
|
||||
## Privacy vs Performance
|
||||
|
||||
@@ -3,6 +3,10 @@ title: "Novita AI"
|
||||
description: "Configure Strix with Novita AI models"
|
||||
---
|
||||
|
||||
import { ScarfPixel } from "/snippets/scarf-pixel.mdx";
|
||||
|
||||
<ScarfPixel />
|
||||
|
||||
[Novita AI](https://novita.ai) provides fast, cost-efficient inference for open-source models via an OpenAI-compatible API.
|
||||
|
||||
## Setup
|
||||
|
||||
@@ -3,6 +3,10 @@ title: "OpenAI"
|
||||
description: "Configure Strix with OpenAI models"
|
||||
---
|
||||
|
||||
import { ScarfPixel } from "/snippets/scarf-pixel.mdx";
|
||||
|
||||
<ScarfPixel />
|
||||
|
||||
## Setup
|
||||
|
||||
```bash
|
||||
|
||||
@@ -3,6 +3,10 @@ title: "OpenRouter"
|
||||
description: "Configure Strix with models via OpenRouter"
|
||||
---
|
||||
|
||||
import { ScarfPixel } from "/snippets/scarf-pixel.mdx";
|
||||
|
||||
<ScarfPixel />
|
||||
|
||||
[OpenRouter](https://openrouter.ai) provides access to 100+ models from multiple providers through a single API.
|
||||
|
||||
## Setup
|
||||
|
||||
@@ -3,6 +3,10 @@ title: "Overview"
|
||||
description: "Configure your AI model for Strix"
|
||||
---
|
||||
|
||||
import { ScarfPixel } from "/snippets/scarf-pixel.mdx";
|
||||
|
||||
<ScarfPixel />
|
||||
|
||||
Strix uses [LiteLLM](https://docs.litellm.ai/docs/providers) for model compatibility, supporting 100+ LLM providers.
|
||||
|
||||
## Configuration
|
||||
|
||||
@@ -3,6 +3,10 @@ title: "Google Vertex AI"
|
||||
description: "Configure Strix with Gemini models via Google Cloud"
|
||||
---
|
||||
|
||||
import { ScarfPixel } from "/snippets/scarf-pixel.mdx";
|
||||
|
||||
<ScarfPixel />
|
||||
|
||||
## Installation
|
||||
|
||||
Vertex AI requires the Google Cloud dependency. Install Strix with the vertex extra:
|
||||
|
||||
@@ -3,6 +3,10 @@ title: "Quick Start"
|
||||
description: "Install Strix and run your first security scan"
|
||||
---
|
||||
|
||||
import { ScarfPixel } from "/snippets/scarf-pixel.mdx";
|
||||
|
||||
<ScarfPixel />
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Docker (running)
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
export const ScarfPixel = () => (
|
||||
<img
|
||||
referrerPolicy="no-referrer-when-downgrade"
|
||||
src="https://static.scarf.sh/a.png?x-pxid=a0ba15dd-a205-4a54-95d6-7814e9ae6b61"
|
||||
alt=""
|
||||
width="1"
|
||||
height="1"
|
||||
style={{ position: "absolute", width: 0, height: 0, opacity: 0, pointerEvents: "none" }}
|
||||
/>
|
||||
);
|
||||
@@ -0,0 +1,34 @@
|
||||
agentic
|
||||
Caido
|
||||
deobfuscation
|
||||
deserialization
|
||||
Devstral
|
||||
[Dd]ocstrings
|
||||
exfiltration
|
||||
failover
|
||||
ffuf
|
||||
Firestore
|
||||
frontmatter
|
||||
fuzzer
|
||||
gcloud
|
||||
hardcoded
|
||||
Kimi
|
||||
Langfuse
|
||||
LLMs?
|
||||
[Mm]isconfigurations?
|
||||
Novita
|
||||
Ollama
|
||||
pentest(ers|ing)?
|
||||
pipx
|
||||
pull_request
|
||||
Pydantic
|
||||
spidering
|
||||
SQLi
|
||||
[Ss]trix
|
||||
Supabase
|
||||
traceback
|
||||
UIs
|
||||
untrusted
|
||||
uv
|
||||
vulns
|
||||
[Ww]ordlist
|
||||
@@ -3,6 +3,10 @@ title: "Browser"
|
||||
description: "Playwright-powered Chrome for web application testing"
|
||||
---
|
||||
|
||||
import { ScarfPixel } from "/snippets/scarf-pixel.mdx";
|
||||
|
||||
<ScarfPixel />
|
||||
|
||||
Strix uses a headless Chrome browser via Playwright to interact with web applications exactly like a real user would.
|
||||
|
||||
## How It Works
|
||||
|
||||
@@ -3,6 +3,10 @@ title: "Agent Tools"
|
||||
description: "How Strix agents interact with targets"
|
||||
---
|
||||
|
||||
import { ScarfPixel } from "/snippets/scarf-pixel.mdx";
|
||||
|
||||
<ScarfPixel />
|
||||
|
||||
Strix agents use specialized tools to test your applications like a real penetration tester would.
|
||||
|
||||
## Core Tools
|
||||
|
||||
@@ -3,6 +3,10 @@ title: "HTTP Proxy"
|
||||
description: "Caido-powered proxy for request interception and replay"
|
||||
---
|
||||
|
||||
import { ScarfPixel } from "/snippets/scarf-pixel.mdx";
|
||||
|
||||
<ScarfPixel />
|
||||
|
||||
Strix includes [Caido](https://caido.io), a modern HTTP proxy built for security testing. All browser traffic flows through Caido, giving the agent full control over requests and responses.
|
||||
|
||||
## Capabilities
|
||||
|
||||
@@ -3,6 +3,10 @@ title: "Sandbox Tools"
|
||||
description: "Pre-installed security tools in the Strix container"
|
||||
---
|
||||
|
||||
import { ScarfPixel } from "/snippets/scarf-pixel.mdx";
|
||||
|
||||
<ScarfPixel />
|
||||
|
||||
Strix runs inside a Kali Linux-based Docker container with a comprehensive set of security tools pre-installed. The agent can use any of these tools through the [terminal](/tools/terminal).
|
||||
|
||||
## Reconnaissance
|
||||
|
||||
@@ -3,6 +3,10 @@ title: "Terminal"
|
||||
description: "Bash shell for running commands and security tools"
|
||||
---
|
||||
|
||||
import { ScarfPixel } from "/snippets/scarf-pixel.mdx";
|
||||
|
||||
<ScarfPixel />
|
||||
|
||||
Strix has access to a persistent bash terminal running inside the Docker sandbox. This gives the agent access to all [pre-installed security tools](/tools/sandbox).
|
||||
|
||||
## Capabilities
|
||||
|
||||
@@ -3,6 +3,10 @@ title: "CLI Reference"
|
||||
description: "Command-line options for Strix"
|
||||
---
|
||||
|
||||
import { ScarfPixel } from "/snippets/scarf-pixel.mdx";
|
||||
|
||||
<ScarfPixel />
|
||||
|
||||
## Basic Usage
|
||||
|
||||
```bash
|
||||
|
||||
@@ -3,6 +3,10 @@ title: "Custom Instructions"
|
||||
description: "Guide Strix with custom testing instructions"
|
||||
---
|
||||
|
||||
import { ScarfPixel } from "/snippets/scarf-pixel.mdx";
|
||||
|
||||
<ScarfPixel />
|
||||
|
||||
Use instructions to provide context, credentials, or focus areas for your scan.
|
||||
|
||||
## Inline Instructions
|
||||
|
||||
@@ -3,6 +3,10 @@ title: "Scan Modes"
|
||||
description: "Choose the right scan depth for your use case"
|
||||
---
|
||||
|
||||
import { ScarfPixel } from "/snippets/scarf-pixel.mdx";
|
||||
|
||||
<ScarfPixel />
|
||||
|
||||
Strix offers three scan modes to balance speed and thoroughness.
|
||||
|
||||
## Quick
|
||||
|
||||
+20
-23
@@ -136,30 +136,27 @@ def child_initial_input(
|
||||
task: str,
|
||||
parent_history: list[Any],
|
||||
) -> list[dict[str, Any]]:
|
||||
initial_input: list[dict[str, Any]] = []
|
||||
"""Build the initial input for a child agent as a single user message.
|
||||
|
||||
Collapsing the inherited-context block, the identity line, and the task into
|
||||
one ``{"role": "user"}`` message keeps providers that require strictly
|
||||
alternating roles (e.g. Perplexity, llama.cpp) from rejecting consecutive
|
||||
user messages.
|
||||
"""
|
||||
parts: list[str] = []
|
||||
if parent_history:
|
||||
rendered = json.dumps(parent_history, ensure_ascii=False, default=str)
|
||||
initial_input.append(
|
||||
{
|
||||
"role": "user",
|
||||
"content": (
|
||||
"== Inherited context from parent (background only) ==\n"
|
||||
f"{rendered}\n"
|
||||
"== End of inherited context ==\n"
|
||||
"Use the above as background only; do not continue the "
|
||||
"parent's work. Your task follows."
|
||||
),
|
||||
},
|
||||
parts.append(
|
||||
"== Inherited context from parent (background only) ==\n"
|
||||
f"{rendered}\n"
|
||||
"== End of inherited context ==\n"
|
||||
"Use the above as background only; do not continue the "
|
||||
"parent's work. Your task follows.",
|
||||
)
|
||||
initial_input.append(
|
||||
{
|
||||
"role": "user",
|
||||
"content": (
|
||||
f"You are agent {name} ({child_id}); your parent is {parent_id}. "
|
||||
"Maintain your own identity. Call agent_finish when your task "
|
||||
"is complete."
|
||||
),
|
||||
}
|
||||
parts.append(
|
||||
f"You are agent {name} ({child_id}); your parent is {parent_id}. "
|
||||
"Maintain your own identity. Call agent_finish when your task "
|
||||
"is complete.",
|
||||
)
|
||||
initial_input.append({"role": "user", "content": task})
|
||||
return initial_input
|
||||
parts.append(task)
|
||||
return [{"role": "user", "content": "\n\n".join(parts)}]
|
||||
|
||||
@@ -11,6 +11,7 @@ from typing import TYPE_CHECKING, Any
|
||||
|
||||
from agents import RunConfig
|
||||
from agents.sandbox import SandboxRunConfig
|
||||
from openai import RateLimitError
|
||||
|
||||
from strix.agents.factory import build_strix_agent, make_child_factory
|
||||
from strix.config import load_settings
|
||||
@@ -308,6 +309,19 @@ async def run_strix_scan(
|
||||
with contextlib.suppress(Exception):
|
||||
await coordinator.set_status(root_id, "stopped")
|
||||
return None
|
||||
except RateLimitError as exc:
|
||||
logger.warning(
|
||||
"Scan %s stopped: persistent rate limit from the LLM provider (%s). "
|
||||
"Resume with 'strix --resume %s' once the limit clears.",
|
||||
scan_id,
|
||||
exc,
|
||||
scan_id,
|
||||
)
|
||||
if root_id is not None:
|
||||
await coordinator.cancel_descendants(root_id)
|
||||
with contextlib.suppress(Exception):
|
||||
await coordinator.set_status(root_id, "stopped")
|
||||
return None
|
||||
except BaseException:
|
||||
logger.exception("Strix scan %s failed", scan_id)
|
||||
if root_id is not None:
|
||||
|
||||
@@ -820,10 +820,10 @@ def main() -> None:
|
||||
asyncio.run(run_tui(args))
|
||||
except KeyboardInterrupt:
|
||||
exit_reason = "interrupted"
|
||||
except Exception as e:
|
||||
except Exception:
|
||||
exit_reason = "error"
|
||||
posthog.error("unhandled_exception", str(e))
|
||||
scarf.error("unhandled_exception", str(e))
|
||||
posthog.error("unhandled_exception")
|
||||
scarf.error("unhandled_exception")
|
||||
raise
|
||||
finally:
|
||||
report_state = get_global_report_state()
|
||||
|
||||
@@ -123,8 +123,6 @@ def end(report_state: "ReportState", exit_reason: str = "completed") -> None:
|
||||
)
|
||||
|
||||
|
||||
def error(error_type: str, error_msg: str | None = None) -> None:
|
||||
def error(error_type: str) -> None:
|
||||
props = {**base_props(), "error_type": error_type}
|
||||
if error_msg:
|
||||
props["error_msg"] = error_msg
|
||||
_send("error", props)
|
||||
|
||||
@@ -129,12 +129,10 @@ def end(report_state: ReportState, exit_reason: str = "completed") -> None:
|
||||
)
|
||||
|
||||
|
||||
def error(error_type: str, error_msg: str | None = None) -> None:
|
||||
def error(error_type: str) -> None:
|
||||
props: dict[str, Any] = {
|
||||
**base_props(),
|
||||
"session": SESSION_ID,
|
||||
"error_type": error_type,
|
||||
}
|
||||
if error_msg:
|
||||
props["error_msg"] = error_msg
|
||||
_send("error", props)
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
"""Tests for strix.config.loader: JSON overrides, alias resolution, persistence."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import pytest
|
||||
from pydantic import AliasChoices, Field
|
||||
from pydantic.fields import FieldInfo
|
||||
|
||||
from strix.config import loader
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
_LLM_ENV_KEYS = [
|
||||
"STRIX_LLM",
|
||||
"LLM_API_KEY",
|
||||
"OPENAI_API_KEY",
|
||||
"LLM_API_BASE",
|
||||
"OPENAI_API_BASE",
|
||||
"OPENAI_BASE_URL",
|
||||
"LITELLM_BASE_URL",
|
||||
"OLLAMA_API_BASE",
|
||||
"STRIX_REASONING_EFFORT",
|
||||
"LLM_TIMEOUT",
|
||||
"PERPLEXITY_API_KEY",
|
||||
# RuntimeSettings
|
||||
"STRIX_IMAGE",
|
||||
"STRIX_RUNTIME_BACKEND",
|
||||
"STRIX_MAX_LOCAL_COPY_MB",
|
||||
# TelemetrySettings
|
||||
"STRIX_TELEMETRY",
|
||||
]
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _reset_loader_state(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""Reset module globals and clear known env vars for deterministic runs."""
|
||||
for key in _LLM_ENV_KEYS:
|
||||
monkeypatch.delenv(key, raising=False)
|
||||
monkeypatch.setattr(loader, "_cached", None)
|
||||
monkeypatch.setattr(loader, "_override", None)
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# _read_json_overrides
|
||||
# --------------------------------------------------------------------------- #
|
||||
|
||||
|
||||
def test_read_json_overrides_missing_file(tmp_path: Path) -> None:
|
||||
assert loader._read_json_overrides(tmp_path / "nope.json") == {}
|
||||
|
||||
|
||||
def test_read_json_overrides_corrupt_json(tmp_path: Path) -> None:
|
||||
path = tmp_path / "cli-config.json"
|
||||
path.write_text("{not valid json", encoding="utf-8")
|
||||
assert loader._read_json_overrides(path) == {}
|
||||
|
||||
|
||||
def test_read_json_overrides_non_dict_env(tmp_path: Path) -> None:
|
||||
path = tmp_path / "cli-config.json"
|
||||
path.write_text(json.dumps({"env": ["not", "a", "dict"]}), encoding="utf-8")
|
||||
assert loader._read_json_overrides(path) == {}
|
||||
|
||||
|
||||
def test_read_json_overrides_maps_to_nested_settings(tmp_path: Path) -> None:
|
||||
path = tmp_path / "cli-config.json"
|
||||
path.write_text(
|
||||
json.dumps({"env": {"STRIX_LLM": "my-model", "PERPLEXITY_API_KEY": "pk"}}),
|
||||
encoding="utf-8",
|
||||
)
|
||||
assert loader._read_json_overrides(path) == {
|
||||
"llm": {"model": "my-model"},
|
||||
"integrations": {"perplexity_api_key": "pk"},
|
||||
}
|
||||
|
||||
|
||||
def test_read_json_overrides_skips_keys_already_in_environ(
|
||||
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
monkeypatch.setenv("STRIX_LLM", "from-env")
|
||||
path = tmp_path / "cli-config.json"
|
||||
path.write_text(json.dumps({"env": {"STRIX_LLM": "from-file"}}), encoding="utf-8")
|
||||
# env wins -> the JSON value is not surfaced as an init kwarg.
|
||||
assert loader._read_json_overrides(path) == {}
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# _aliases_for
|
||||
# --------------------------------------------------------------------------- #
|
||||
|
||||
|
||||
def test_aliases_for_simple_alias() -> None:
|
||||
finfo = FieldInfo(alias="SIMPLE_ALIAS")
|
||||
assert loader._aliases_for(finfo) == ["SIMPLE_ALIAS"]
|
||||
|
||||
|
||||
def test_aliases_for_alias_choices() -> None:
|
||||
finfo: FieldInfo = Field( # type: ignore[assignment]
|
||||
default=None,
|
||||
validation_alias=AliasChoices("FIRST", "SECOND"),
|
||||
)
|
||||
assert loader._aliases_for(finfo) == ["FIRST", "SECOND"]
|
||||
|
||||
|
||||
def test_aliases_for_string_validation_alias() -> None:
|
||||
finfo: FieldInfo = Field(default=None, validation_alias="STR_ALIAS") # type: ignore[assignment]
|
||||
assert loader._aliases_for(finfo) == ["STR_ALIAS"]
|
||||
|
||||
|
||||
def test_aliases_for_no_alias() -> None:
|
||||
assert loader._aliases_for(FieldInfo()) == []
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# apply_config_override + load_settings round-trip
|
||||
# --------------------------------------------------------------------------- #
|
||||
|
||||
|
||||
def test_apply_override_and_load_settings_round_trip(tmp_path: Path) -> None:
|
||||
path = tmp_path / "cli-config.json"
|
||||
path.write_text(
|
||||
json.dumps({"env": {"STRIX_LLM": "round-trip-model", "PERPLEXITY_API_KEY": "pk"}}),
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
loader.apply_config_override(path)
|
||||
settings = loader.load_settings()
|
||||
|
||||
assert settings.llm.model == "round-trip-model"
|
||||
assert settings.integrations.perplexity_api_key == "pk"
|
||||
# Second call is memoized -> same object.
|
||||
assert loader.load_settings() is settings
|
||||
|
||||
|
||||
def test_apply_config_override_invalidates_cache(tmp_path: Path) -> None:
|
||||
first = tmp_path / "first.json"
|
||||
first.write_text(json.dumps({"env": {"STRIX_LLM": "first-model"}}), encoding="utf-8")
|
||||
second = tmp_path / "second.json"
|
||||
second.write_text(json.dumps({"env": {"STRIX_LLM": "second-model"}}), encoding="utf-8")
|
||||
|
||||
loader.apply_config_override(first)
|
||||
assert loader.load_settings().llm.model == "first-model"
|
||||
|
||||
loader.apply_config_override(second)
|
||||
assert loader.load_settings().llm.model == "second-model"
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# persist_current
|
||||
# --------------------------------------------------------------------------- #
|
||||
|
||||
|
||||
def test_persist_current_writes_env_block(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.setenv("STRIX_LLM", "persisted-model")
|
||||
target = tmp_path / "sub" / "cli-config.json"
|
||||
loader.apply_config_override(target)
|
||||
|
||||
loader.persist_current()
|
||||
|
||||
assert target.exists()
|
||||
assert json.loads(target.read_text(encoding="utf-8")) == {
|
||||
"env": {"STRIX_LLM": "persisted-model"}
|
||||
}
|
||||
|
||||
|
||||
def test_persist_current_sets_0600_mode(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.setenv("STRIX_LLM", "persisted-model")
|
||||
target = tmp_path / "cli-config.json"
|
||||
loader.apply_config_override(target)
|
||||
|
||||
loader.persist_current()
|
||||
|
||||
assert target.stat().st_mode & 0o777 == 0o600
|
||||
@@ -0,0 +1,114 @@
|
||||
"""Tests for pure input builders in strix.core.inputs."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from itertools import pairwise
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
|
||||
from strix.core.inputs import build_root_task, child_initial_input
|
||||
|
||||
|
||||
def _child_kwargs(parent_history: list[Any]) -> dict[str, Any]:
|
||||
return {
|
||||
"name": "scout",
|
||||
"child_id": "agent-2",
|
||||
"parent_id": "agent-1",
|
||||
"task": "Audit the login flow.",
|
||||
"parent_history": parent_history,
|
||||
}
|
||||
|
||||
|
||||
def test_child_initial_input_single_message_without_history() -> None:
|
||||
result = child_initial_input(**_child_kwargs([]))
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0]["role"] == "user"
|
||||
content = result[0]["content"]
|
||||
assert "agent scout (agent-2)" in content
|
||||
assert "Audit the login flow." in content
|
||||
assert "Inherited context" not in content
|
||||
|
||||
|
||||
def test_child_initial_input_single_message_with_history() -> None:
|
||||
history = [{"role": "assistant", "content": "previous work"}]
|
||||
result = child_initial_input(**_child_kwargs(history))
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0]["role"] == "user"
|
||||
content = result[0]["content"]
|
||||
assert "Inherited context from parent" in content
|
||||
assert "previous work" in content
|
||||
assert "agent scout (agent-2)" in content
|
||||
assert "Audit the login flow." in content
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"parent_history",
|
||||
[[], [{"role": "assistant", "content": "previous work"}]],
|
||||
)
|
||||
def test_child_initial_input_no_consecutive_same_role(parent_history: list[Any]) -> None:
|
||||
result = child_initial_input(**_child_kwargs(parent_history))
|
||||
|
||||
roles = [msg["role"] for msg in result]
|
||||
assert all(prev != nxt for prev, nxt in pairwise(roles))
|
||||
|
||||
|
||||
def test_build_root_task_empty_config() -> None:
|
||||
assert build_root_task({}) == ""
|
||||
|
||||
|
||||
def test_build_root_task_repository_target() -> None:
|
||||
config = {
|
||||
"targets": [
|
||||
{
|
||||
"type": "repository",
|
||||
"details": {
|
||||
"target_repo": "https://example.com/repo.git",
|
||||
"cloned_repo_path": "/workspace/repo",
|
||||
"workspace_subdir": "repo",
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
task = build_root_task(config)
|
||||
|
||||
assert "Repositories:" in task
|
||||
assert "/workspace/repo" in task
|
||||
assert "https://example.com/repo.git" in task
|
||||
|
||||
|
||||
def test_build_root_task_web_application_with_instructions() -> None:
|
||||
config = {
|
||||
"targets": [
|
||||
{"type": "web_application", "details": {"target_url": "https://app.example.com"}},
|
||||
],
|
||||
"user_instructions": "Focus on auth.",
|
||||
}
|
||||
task = build_root_task(config)
|
||||
|
||||
assert "URLs:" in task
|
||||
assert "https://app.example.com" in task
|
||||
assert "Special instructions: Focus on auth." in task
|
||||
|
||||
|
||||
def test_build_root_task_diff_scope() -> None:
|
||||
config = {
|
||||
"targets": [],
|
||||
"diff_scope": {
|
||||
"active": True,
|
||||
"repos": [
|
||||
{
|
||||
"workspace_subdir": "repo",
|
||||
"analyzable_files_count": 3,
|
||||
"deleted_files_count": 2,
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
task = build_root_task(config)
|
||||
|
||||
assert "Scope Constraints:" in task
|
||||
assert "3 changed file(s)" in task
|
||||
assert "2 deleted file(s)" in task
|
||||
@@ -0,0 +1,84 @@
|
||||
"""Tests for graceful handling of persistent RateLimitError in run_strix_scan."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import types
|
||||
from typing import Any
|
||||
|
||||
import httpx
|
||||
import pytest
|
||||
from openai import RateLimitError
|
||||
|
||||
import strix.tools.notes.tools as notes_tools
|
||||
import strix.tools.todo.tools as todo_tools
|
||||
from strix.core import runner
|
||||
from strix.core.agents import AgentCoordinator
|
||||
|
||||
|
||||
def _make_rate_limit_error() -> RateLimitError:
|
||||
request = httpx.Request("POST", "https://api.openai.com/v1/responses")
|
||||
response = httpx.Response(status_code=429, request=request)
|
||||
return RateLimitError("rate limited", response=response, body=None)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_persistent_rate_limit_stops_gracefully(
|
||||
monkeypatch: pytest.MonkeyPatch, tmp_path: Any, caplog: pytest.LogCaptureFixture
|
||||
) -> None:
|
||||
"""A persistent RateLimitError stops the scan (root -> 'stopped') without raising."""
|
||||
monkeypatch.setattr(runner, "run_dir_for", lambda _scan_id: tmp_path)
|
||||
monkeypatch.setattr(runner, "runtime_state_dir", lambda _run_dir: tmp_path)
|
||||
monkeypatch.setattr(runner, "setup_scan_logging", lambda _run_dir: lambda: None)
|
||||
monkeypatch.setattr(runner, "set_scan_id", lambda _scan_id: None)
|
||||
|
||||
settings = types.SimpleNamespace(
|
||||
llm=types.SimpleNamespace(model="openai/gpt-4o", reasoning_effort="high")
|
||||
)
|
||||
monkeypatch.setattr(runner, "load_settings", lambda: settings)
|
||||
monkeypatch.setattr(runner, "configure_sdk_model_defaults", lambda _settings: None)
|
||||
monkeypatch.setattr(
|
||||
runner, "uses_chat_completions_tool_schema", lambda _model, _settings: False
|
||||
)
|
||||
|
||||
monkeypatch.setattr(todo_tools, "hydrate_todos_from_disk", lambda _state_dir: None)
|
||||
monkeypatch.setattr(notes_tools, "hydrate_notes_from_disk", lambda _state_dir: None)
|
||||
|
||||
async def _create_or_reuse(*_args: Any, **_kwargs: Any) -> dict[str, Any]:
|
||||
return {"client": object(), "session": object(), "caido_client": None}
|
||||
|
||||
async def _cleanup(*_args: Any, **_kwargs: Any) -> None:
|
||||
return None
|
||||
|
||||
monkeypatch.setattr(runner.session_manager, "create_or_reuse", _create_or_reuse)
|
||||
monkeypatch.setattr(runner.session_manager, "cleanup", _cleanup)
|
||||
|
||||
monkeypatch.setattr(runner, "build_root_task", lambda _scan_config: "task")
|
||||
monkeypatch.setattr(runner, "build_scope_context", lambda _scan_config: "")
|
||||
monkeypatch.setattr(runner, "make_model_settings", lambda *_args, **_kwargs: object())
|
||||
monkeypatch.setattr(runner, "build_strix_agent", lambda **_kwargs: object())
|
||||
monkeypatch.setattr(runner, "make_child_factory", lambda **_kwargs: lambda **_k: object())
|
||||
monkeypatch.setattr(runner, "open_agent_session", lambda _root_id, _db: object())
|
||||
|
||||
async def _raise_rate_limit(*_args: Any, **_kwargs: Any) -> None:
|
||||
raise _make_rate_limit_error()
|
||||
|
||||
monkeypatch.setattr(runner, "run_agent_loop", _raise_rate_limit)
|
||||
|
||||
coordinator = AgentCoordinator()
|
||||
|
||||
with caplog.at_level(logging.WARNING):
|
||||
result = await runner.run_strix_scan(
|
||||
scan_config={"targets": [], "scan_mode": "deep"},
|
||||
scan_id="scan-test",
|
||||
image="img",
|
||||
coordinator=coordinator,
|
||||
)
|
||||
|
||||
assert result is None
|
||||
root_ids = [aid for aid, parent in coordinator.parent_of.items() if parent is None]
|
||||
assert len(root_ids) == 1
|
||||
assert coordinator.statuses[root_ids[0]] == "stopped"
|
||||
# the resume hint must carry the real scan id, not a literal placeholder
|
||||
assert "strix --resume scan-test" in caplog.text
|
||||
assert "<run_name>" not in caplog.text
|
||||
Reference in New Issue
Block a user