resolve: merge conflict resolution, llm api base resolution
This commit is contained in:
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -27,7 +27,7 @@ If applicable, add screenshots to help explain your problem.
|
||||
- OS: [e.g. Ubuntu 22.04]
|
||||
- Strix Version or Commit: [e.g. 0.1.18]
|
||||
- Python Version: [e.g. 3.12]
|
||||
- LLM Used: [e.g. GPT-5, Claude Sonnet 4]
|
||||
- LLM Used: [e.g. GPT-5, Claude Sonnet 4.6]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
|
||||
@@ -30,7 +30,7 @@ Thank you for your interest in contributing to Strix! This guide will help you g
|
||||
|
||||
3. **Configure your LLM provider**
|
||||
```bash
|
||||
export STRIX_LLM="openai/gpt-5"
|
||||
export STRIX_LLM="anthropic/claude-sonnet-4-6"
|
||||
export LLM_API_KEY="your-api-key"
|
||||
```
|
||||
|
||||
|
||||
10
README.md
10
README.md
@@ -72,7 +72,9 @@ Strix are autonomous AI agents that act just like real hackers - they run your c
|
||||
|
||||
**Prerequisites:**
|
||||
- Docker (running)
|
||||
- An LLM provider key (e.g. [get OpenAI API key](https://platform.openai.com/api-keys) or use a local LLM)
|
||||
- An LLM API key:
|
||||
- Any [supported provider](https://docs.strix.ai/llm-providers/overview) (OpenAI, Anthropic, Google, etc.)
|
||||
- Or [Strix Router](https://models.strix.ai) — single API key for multiple providers with $10 free credit on signup
|
||||
|
||||
### Installation & First Scan
|
||||
|
||||
@@ -84,7 +86,7 @@ curl -sSL https://strix.ai/install | bash
|
||||
pipx install strix-agent
|
||||
|
||||
# Configure your AI provider
|
||||
export STRIX_LLM="openai/gpt-5"
|
||||
export STRIX_LLM="anthropic/claude-sonnet-4-6" # or "strix/claude-sonnet-4.6" via Strix Router (https://models.strix.ai)
|
||||
export LLM_API_KEY="your-api-key"
|
||||
|
||||
# Run your first security assessment
|
||||
@@ -201,7 +203,7 @@ jobs:
|
||||
### Configuration
|
||||
|
||||
```bash
|
||||
export STRIX_LLM="openai/gpt-5"
|
||||
export STRIX_LLM="anthropic/claude-sonnet-4-6"
|
||||
export LLM_API_KEY="your-api-key"
|
||||
|
||||
# Optional
|
||||
@@ -215,8 +217,8 @@ export STRIX_REASONING_EFFORT="high" # control thinking effort (default: high,
|
||||
|
||||
**Recommended models for best results:**
|
||||
|
||||
- [Anthropic Claude Sonnet 4.6](https://claude.com/platform/api) — `anthropic/claude-sonnet-4-6`
|
||||
- [OpenAI GPT-5](https://openai.com/api/) — `openai/gpt-5`
|
||||
- [Anthropic Claude Sonnet 4.5](https://claude.com/platform/api) — `anthropic/claude-sonnet-4-5`
|
||||
- [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.
|
||||
|
||||
@@ -8,7 +8,7 @@ Configure Strix using environment variables or a config file.
|
||||
## LLM Configuration
|
||||
|
||||
<ParamField path="STRIX_LLM" type="string" required>
|
||||
Model name in LiteLLM format (e.g., `openai/gpt-5`, `anthropic/claude-sonnet-4-5`).
|
||||
Model name in LiteLLM format (e.g., `anthropic/claude-sonnet-4-6`, `openai/gpt-5`).
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="LLM_API_KEY" type="string">
|
||||
@@ -86,7 +86,7 @@ strix --target ./app --config /path/to/config.json
|
||||
```json
|
||||
{
|
||||
"env": {
|
||||
"STRIX_LLM": "openai/gpt-5",
|
||||
"STRIX_LLM": "anthropic/claude-sonnet-4-6",
|
||||
"LLM_API_KEY": "sk-...",
|
||||
"STRIX_REASONING_EFFORT": "high"
|
||||
}
|
||||
@@ -97,7 +97,7 @@ strix --target ./app --config /path/to/config.json
|
||||
|
||||
```bash
|
||||
# Required
|
||||
export STRIX_LLM="openai/gpt-5"
|
||||
export STRIX_LLM="anthropic/claude-sonnet-4-6"
|
||||
export LLM_API_KEY="sk-..."
|
||||
|
||||
# Optional: Enable web search
|
||||
|
||||
@@ -32,7 +32,7 @@ description: "Contribute to Strix development"
|
||||
</Step>
|
||||
<Step title="Configure LLM">
|
||||
```bash
|
||||
export STRIX_LLM="openai/gpt-5"
|
||||
export STRIX_LLM="anthropic/claude-sonnet-4-6"
|
||||
export LLM_API_KEY="your-api-key"
|
||||
```
|
||||
</Step>
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"group": "LLM Providers",
|
||||
"pages": [
|
||||
"llm-providers/overview",
|
||||
"llm-providers/models",
|
||||
"llm-providers/openai",
|
||||
"llm-providers/anthropic",
|
||||
"llm-providers/openrouter",
|
||||
|
||||
@@ -78,7 +78,7 @@ Strix uses a graph of specialized agents for comprehensive security testing:
|
||||
curl -sSL https://strix.ai/install | bash
|
||||
|
||||
# Configure
|
||||
export STRIX_LLM="openai/gpt-5"
|
||||
export STRIX_LLM="anthropic/claude-sonnet-4-6"
|
||||
export LLM_API_KEY="your-api-key"
|
||||
|
||||
# Scan
|
||||
|
||||
@@ -35,7 +35,7 @@ Add these secrets to your repository:
|
||||
|
||||
| Secret | Description |
|
||||
|--------|-------------|
|
||||
| `STRIX_LLM` | Model name (e.g., `openai/gpt-5`) |
|
||||
| `STRIX_LLM` | Model name (e.g., `anthropic/claude-sonnet-4-6`) |
|
||||
| `LLM_API_KEY` | API key for your LLM provider |
|
||||
|
||||
## Exit Codes
|
||||
|
||||
@@ -6,7 +6,7 @@ description: "Configure Strix with Claude models"
|
||||
## Setup
|
||||
|
||||
```bash
|
||||
export STRIX_LLM="anthropic/claude-sonnet-4-5"
|
||||
export STRIX_LLM="anthropic/claude-sonnet-4-6"
|
||||
export LLM_API_KEY="sk-ant-..."
|
||||
```
|
||||
|
||||
@@ -14,8 +14,8 @@ export LLM_API_KEY="sk-ant-..."
|
||||
|
||||
| Model | Description |
|
||||
|-------|-------------|
|
||||
| `anthropic/claude-sonnet-4-5` | Best balance of intelligence and speed (recommended) |
|
||||
| `anthropic/claude-opus-4-5` | Maximum capability for deep analysis |
|
||||
| `anthropic/claude-sonnet-4-6` | Best balance of intelligence and speed (recommended) |
|
||||
| `anthropic/claude-opus-4-6` | Maximum capability for deep analysis |
|
||||
|
||||
## Get API Key
|
||||
|
||||
|
||||
80
docs/llm-providers/models.mdx
Normal file
80
docs/llm-providers/models.mdx
Normal file
@@ -0,0 +1,80 @@
|
||||
---
|
||||
title: "Strix Router"
|
||||
description: "Access top LLMs through a single API with high rate limits and zero data retention"
|
||||
---
|
||||
|
||||
Strix Router gives you access to the best LLMs through a single API key.
|
||||
|
||||
<Note>
|
||||
Strix Router is currently in **beta**. It's completely optional — Strix works with any [LiteLLM-compatible provider](/llm-providers/overview) using your own API keys, or with [local models](/llm-providers/local). Strix Router is just the setup we test and optimize for.
|
||||
</Note>
|
||||
|
||||
## Why Use Strix Router?
|
||||
|
||||
- **High rate limits** — No throttling during long-running scans
|
||||
- **Zero data retention** — Routes to providers with zero data retention policies enabled
|
||||
- **Failover & load balancing** — Automatic fallback across providers for reliability
|
||||
- **Simple setup** — One API key, one environment variable, no provider accounts needed
|
||||
- **No markup** — Same token pricing as the underlying providers, no extra fees
|
||||
- **$10 free credit** — Try it free on signup, no credit card required
|
||||
|
||||
## Quick Start
|
||||
|
||||
1. Get your API key at [models.strix.ai](https://models.strix.ai)
|
||||
2. Set your environment:
|
||||
|
||||
```bash
|
||||
export LLM_API_KEY='your-strix-api-key'
|
||||
export STRIX_LLM='strix/claude-sonnet-4.6'
|
||||
```
|
||||
|
||||
3. Run a scan:
|
||||
|
||||
```bash
|
||||
strix --target ./your-app
|
||||
```
|
||||
|
||||
## Available Models
|
||||
|
||||
### Anthropic
|
||||
|
||||
| Model | ID |
|
||||
|-------|-----|
|
||||
| Claude Sonnet 4.6 | `strix/claude-sonnet-4.6` |
|
||||
| Claude Opus 4.6 | `strix/claude-opus-4.6` |
|
||||
|
||||
### OpenAI
|
||||
|
||||
| Model | ID |
|
||||
|-------|-----|
|
||||
| GPT-5.2 | `strix/gpt-5.2` |
|
||||
| GPT-5.1 | `strix/gpt-5.1` |
|
||||
| GPT-5 | `strix/gpt-5` |
|
||||
| GPT-5.2 Codex | `strix/gpt-5.2-codex` |
|
||||
| GPT-5.1 Codex Max | `strix/gpt-5.1-codex-max` |
|
||||
| GPT-5.1 Codex | `strix/gpt-5.1-codex` |
|
||||
| GPT-5 Codex | `strix/gpt-5-codex` |
|
||||
|
||||
### Google
|
||||
|
||||
| Model | ID |
|
||||
|-------|-----|
|
||||
| Gemini 3 Pro | `strix/gemini-3-pro-preview` |
|
||||
| Gemini 3 Flash | `strix/gemini-3-flash-preview` |
|
||||
|
||||
### Other
|
||||
|
||||
| Model | ID |
|
||||
|-------|-----|
|
||||
| GLM-5 | `strix/glm-5` |
|
||||
| GLM-4.7 | `strix/glm-4.7` |
|
||||
|
||||
## Configuration Reference
|
||||
|
||||
<ParamField path="LLM_API_KEY" type="string" required>
|
||||
Your Strix API key from [models.strix.ai](https://models.strix.ai).
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="STRIX_LLM" type="string" required>
|
||||
Model ID from the tables above. Must be prefixed with `strix/`.
|
||||
</ParamField>
|
||||
@@ -19,7 +19,7 @@ Access any model on OpenRouter using the format `openrouter/<provider>/<model>`:
|
||||
| Model | Configuration |
|
||||
|-------|---------------|
|
||||
| GPT-5 | `openrouter/openai/gpt-5` |
|
||||
| Claude 4.5 Sonnet | `openrouter/anthropic/claude-sonnet-4.5` |
|
||||
| Claude Sonnet 4.6 | `openrouter/anthropic/claude-sonnet-4.6` |
|
||||
| Gemini 3 Pro | `openrouter/google/gemini-3-pro-preview` |
|
||||
| GLM-4.7 | `openrouter/z-ai/glm-4.7` |
|
||||
|
||||
|
||||
@@ -5,31 +5,54 @@ description: "Configure your AI model for Strix"
|
||||
|
||||
Strix uses [LiteLLM](https://docs.litellm.ai/docs/providers) for model compatibility, supporting 100+ LLM providers.
|
||||
|
||||
## Recommended Models
|
||||
## Strix Router (Recommended)
|
||||
|
||||
For best results, use one of these models:
|
||||
The fastest way to get started. [Strix Router](/llm-providers/models) gives you access to tested models with the highest rate limits and zero data retention.
|
||||
|
||||
```bash
|
||||
export STRIX_LLM="strix/claude-sonnet-4.6"
|
||||
export LLM_API_KEY="your-strix-api-key"
|
||||
```
|
||||
|
||||
Get your API key at [models.strix.ai](https://models.strix.ai).
|
||||
|
||||
## Bring Your Own Key
|
||||
|
||||
You can also use any LiteLLM-compatible provider with your own API keys:
|
||||
|
||||
| Model | Provider | Configuration |
|
||||
| ----------------- | ------------- | -------------------------------- |
|
||||
| Claude Sonnet 4.6 | Anthropic | `anthropic/claude-sonnet-4-6` |
|
||||
| GPT-5 | OpenAI | `openai/gpt-5` |
|
||||
| Claude 4.5 Sonnet | Anthropic | `anthropic/claude-sonnet-4-5` |
|
||||
| Gemini 3 Pro | Google Vertex | `vertex_ai/gemini-3-pro-preview` |
|
||||
|
||||
## Quick Setup
|
||||
|
||||
```bash
|
||||
export STRIX_LLM="openai/gpt-5"
|
||||
export STRIX_LLM="anthropic/claude-sonnet-4-6"
|
||||
export LLM_API_KEY="your-api-key"
|
||||
```
|
||||
|
||||
## Local Models
|
||||
|
||||
Run models locally with [Ollama](https://ollama.com), [LM Studio](https://lmstudio.ai), or any OpenAI-compatible server:
|
||||
|
||||
```bash
|
||||
export STRIX_LLM="ollama/llama4"
|
||||
export LLM_API_BASE="http://localhost:11434"
|
||||
```
|
||||
|
||||
See the [Local Models guide](/llm-providers/local) for setup instructions and recommended models.
|
||||
|
||||
## Provider Guides
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Strix Router" href="/llm-providers/models">
|
||||
Recommended models router with high rate limits.
|
||||
</Card>
|
||||
<Card title="OpenAI" href="/llm-providers/openai">
|
||||
GPT-5 and Codex models.
|
||||
</Card>
|
||||
<Card title="Anthropic" href="/llm-providers/anthropic">
|
||||
Claude 4.5 Sonnet, Opus, and Haiku.
|
||||
Claude Sonnet 4.6, Opus, and Haiku.
|
||||
</Card>
|
||||
<Card title="OpenRouter" href="/llm-providers/openrouter">
|
||||
Access 100+ models through a single API.
|
||||
@@ -38,7 +61,7 @@ export LLM_API_KEY="your-api-key"
|
||||
Gemini 3 models via Google Cloud.
|
||||
</Card>
|
||||
<Card title="AWS Bedrock" href="/llm-providers/bedrock">
|
||||
Claude 4.5 and Titan models via AWS.
|
||||
Claude and Titan models via AWS.
|
||||
</Card>
|
||||
<Card title="Azure OpenAI" href="/llm-providers/azure">
|
||||
GPT-5 via Azure.
|
||||
@@ -53,8 +76,8 @@ export LLM_API_KEY="your-api-key"
|
||||
Use LiteLLM's `provider/model-name` format:
|
||||
|
||||
```
|
||||
anthropic/claude-sonnet-4-6
|
||||
openai/gpt-5
|
||||
anthropic/claude-sonnet-4-5
|
||||
vertex_ai/gemini-3-pro-preview
|
||||
bedrock/anthropic.claude-4-5-sonnet-20251022-v1:0
|
||||
ollama/llama4
|
||||
|
||||
@@ -6,7 +6,7 @@ description: "Install Strix and run your first security scan"
|
||||
## Prerequisites
|
||||
|
||||
- Docker (running)
|
||||
- An LLM provider API key (OpenAI, Anthropic, or local model)
|
||||
- An LLM API key — use [Strix Router](/llm-providers/models) for the easiest setup, or bring your own key from any [supported provider](/llm-providers/overview)
|
||||
|
||||
## Installation
|
||||
|
||||
@@ -27,13 +27,23 @@ description: "Install Strix and run your first security scan"
|
||||
|
||||
Set your LLM provider:
|
||||
|
||||
```bash
|
||||
export STRIX_LLM="openai/gpt-5"
|
||||
export LLM_API_KEY="your-api-key"
|
||||
```
|
||||
<Tabs>
|
||||
<Tab title="Strix Router">
|
||||
```bash
|
||||
export STRIX_LLM="strix/claude-sonnet-4.6"
|
||||
export LLM_API_KEY="your-strix-api-key"
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="Bring Your Own Key">
|
||||
```bash
|
||||
export STRIX_LLM="anthropic/claude-sonnet-4-6"
|
||||
export LLM_API_KEY="your-api-key"
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
<Tip>
|
||||
For best results, use `openai/gpt-5`, `anthropic/claude-sonnet-4-5`, or `vertex_ai/gemini-3-pro-preview`.
|
||||
For best results, use `strix/claude-sonnet-4.6`, `strix/claude-opus-4.6`, or `strix/gpt-5.2`.
|
||||
</Tip>
|
||||
|
||||
## Run Your First Scan
|
||||
|
||||
332
poetry.lock
generated
332
poetry.lock
generated
@@ -1104,56 +1104,74 @@ pytz = ">2021.1"
|
||||
|
||||
[[package]]
|
||||
name = "cryptography"
|
||||
version = "44.0.1"
|
||||
version = "46.0.5"
|
||||
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
|
||||
optional = false
|
||||
python-versions = "!=3.9.0,!=3.9.1,>=3.7"
|
||||
python-versions = "!=3.9.0,!=3.9.1,>=3.8"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "cryptography-44.0.1-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:bf688f615c29bfe9dfc44312ca470989279f0e94bb9f631f85e3459af8efc009"},
|
||||
{file = "cryptography-44.0.1-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd7c7e2d71d908dc0f8d2027e1604102140d84b155e658c20e8ad1304317691f"},
|
||||
{file = "cryptography-44.0.1-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:887143b9ff6bad2b7570da75a7fe8bbf5f65276365ac259a5d2d5147a73775f2"},
|
||||
{file = "cryptography-44.0.1-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:322eb03ecc62784536bc173f1483e76747aafeb69c8728df48537eb431cd1911"},
|
||||
{file = "cryptography-44.0.1-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:21377472ca4ada2906bc313168c9dc7b1d7ca417b63c1c3011d0c74b7de9ae69"},
|
||||
{file = "cryptography-44.0.1-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:df978682c1504fc93b3209de21aeabf2375cb1571d4e61907b3e7a2540e83026"},
|
||||
{file = "cryptography-44.0.1-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:eb3889330f2a4a148abead555399ec9a32b13b7c8ba969b72d8e500eb7ef84cd"},
|
||||
{file = "cryptography-44.0.1-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:8e6a85a93d0642bd774460a86513c5d9d80b5c002ca9693e63f6e540f1815ed0"},
|
||||
{file = "cryptography-44.0.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:6f76fdd6fd048576a04c5210d53aa04ca34d2ed63336d4abd306d0cbe298fddf"},
|
||||
{file = "cryptography-44.0.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6c8acf6f3d1f47acb2248ec3ea261171a671f3d9428e34ad0357148d492c7864"},
|
||||
{file = "cryptography-44.0.1-cp37-abi3-win32.whl", hash = "sha256:24979e9f2040c953a94bf3c6782e67795a4c260734e5264dceea65c8f4bae64a"},
|
||||
{file = "cryptography-44.0.1-cp37-abi3-win_amd64.whl", hash = "sha256:fd0ee90072861e276b0ff08bd627abec29e32a53b2be44e41dbcdf87cbee2b00"},
|
||||
{file = "cryptography-44.0.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:a2d8a7045e1ab9b9f803f0d9531ead85f90c5f2859e653b61497228b18452008"},
|
||||
{file = "cryptography-44.0.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b8272f257cf1cbd3f2e120f14c68bff2b6bdfcc157fafdee84a1b795efd72862"},
|
||||
{file = "cryptography-44.0.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e8d181e90a777b63f3f0caa836844a1182f1f265687fac2115fcf245f5fbec3"},
|
||||
{file = "cryptography-44.0.1-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:436df4f203482f41aad60ed1813811ac4ab102765ecae7a2bbb1dbb66dcff5a7"},
|
||||
{file = "cryptography-44.0.1-cp39-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4f422e8c6a28cf8b7f883eb790695d6d45b0c385a2583073f3cec434cc705e1a"},
|
||||
{file = "cryptography-44.0.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:72198e2b5925155497a5a3e8c216c7fb3e64c16ccee11f0e7da272fa93b35c4c"},
|
||||
{file = "cryptography-44.0.1-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:2a46a89ad3e6176223b632056f321bc7de36b9f9b93b2cc1cccf935a3849dc62"},
|
||||
{file = "cryptography-44.0.1-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:53f23339864b617a3dfc2b0ac8d5c432625c80014c25caac9082314e9de56f41"},
|
||||
{file = "cryptography-44.0.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:888fcc3fce0c888785a4876ca55f9f43787f4c5c1cc1e2e0da71ad481ff82c5b"},
|
||||
{file = "cryptography-44.0.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:00918d859aa4e57db8299607086f793fa7813ae2ff5a4637e318a25ef82730f7"},
|
||||
{file = "cryptography-44.0.1-cp39-abi3-win32.whl", hash = "sha256:9b336599e2cb77b1008cb2ac264b290803ec5e8e89d618a5e978ff5eb6f715d9"},
|
||||
{file = "cryptography-44.0.1-cp39-abi3-win_amd64.whl", hash = "sha256:e403f7f766ded778ecdb790da786b418a9f2394f36e8cc8b796cc056ab05f44f"},
|
||||
{file = "cryptography-44.0.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:1f9a92144fa0c877117e9748c74501bea842f93d21ee00b0cf922846d9d0b183"},
|
||||
{file = "cryptography-44.0.1-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:610a83540765a8d8ce0f351ce42e26e53e1f774a6efb71eb1b41eb01d01c3d12"},
|
||||
{file = "cryptography-44.0.1-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:5fed5cd6102bb4eb843e3315d2bf25fede494509bddadb81e03a859c1bc17b83"},
|
||||
{file = "cryptography-44.0.1-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:f4daefc971c2d1f82f03097dc6f216744a6cd2ac0f04c68fb935ea2ba2a0d420"},
|
||||
{file = "cryptography-44.0.1-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:94f99f2b943b354a5b6307d7e8d19f5c423a794462bde2bf310c770ba052b1c4"},
|
||||
{file = "cryptography-44.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d9c5b9f698a83c8bd71e0f4d3f9f839ef244798e5ffe96febfa9714717db7af7"},
|
||||
{file = "cryptography-44.0.1.tar.gz", hash = "sha256:f51f5705ab27898afda1aaa430f34ad90dc117421057782022edf0600bec5f14"},
|
||||
{file = "cryptography-46.0.5-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:351695ada9ea9618b3500b490ad54c739860883df6c1f555e088eaf25b1bbaad"},
|
||||
{file = "cryptography-46.0.5-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c18ff11e86df2e28854939acde2d003f7984f721eba450b56a200ad90eeb0e6b"},
|
||||
{file = "cryptography-46.0.5-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d7e3d356b8cd4ea5aff04f129d5f66ebdc7b6f8eae802b93739ed520c47c79b"},
|
||||
{file = "cryptography-46.0.5-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:50bfb6925eff619c9c023b967d5b77a54e04256c4281b0e21336a130cd7fc263"},
|
||||
{file = "cryptography-46.0.5-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:803812e111e75d1aa73690d2facc295eaefd4439be1023fefc4995eaea2af90d"},
|
||||
{file = "cryptography-46.0.5-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3ee190460e2fbe447175cda91b88b84ae8322a104fc27766ad09428754a618ed"},
|
||||
{file = "cryptography-46.0.5-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:f145bba11b878005c496e93e257c1e88f154d278d2638e6450d17e0f31e558d2"},
|
||||
{file = "cryptography-46.0.5-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:e9251e3be159d1020c4030bd2e5f84d6a43fe54b6c19c12f51cde9542a2817b2"},
|
||||
{file = "cryptography-46.0.5-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:47fb8a66058b80e509c47118ef8a75d14c455e81ac369050f20ba0d23e77fee0"},
|
||||
{file = "cryptography-46.0.5-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:4c3341037c136030cb46e4b1e17b7418ea4cbd9dd207e4a6f3b2b24e0d4ac731"},
|
||||
{file = "cryptography-46.0.5-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:890bcb4abd5a2d3f852196437129eb3667d62630333aacc13dfd470fad3aaa82"},
|
||||
{file = "cryptography-46.0.5-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:80a8d7bfdf38f87ca30a5391c0c9ce4ed2926918e017c29ddf643d0ed2778ea1"},
|
||||
{file = "cryptography-46.0.5-cp311-abi3-win32.whl", hash = "sha256:60ee7e19e95104d4c03871d7d7dfb3d22ef8a9b9c6778c94e1c8fcc8365afd48"},
|
||||
{file = "cryptography-46.0.5-cp311-abi3-win_amd64.whl", hash = "sha256:38946c54b16c885c72c4f59846be9743d699eee2b69b6988e0a00a01f46a61a4"},
|
||||
{file = "cryptography-46.0.5-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:94a76daa32eb78d61339aff7952ea819b1734b46f73646a07decb40e5b3448e2"},
|
||||
{file = "cryptography-46.0.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5be7bf2fb40769e05739dd0046e7b26f9d4670badc7b032d6ce4db64dddc0678"},
|
||||
{file = "cryptography-46.0.5-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fe346b143ff9685e40192a4960938545c699054ba11d4f9029f94751e3f71d87"},
|
||||
{file = "cryptography-46.0.5-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:c69fd885df7d089548a42d5ec05be26050ebcd2283d89b3d30676eb32ff87dee"},
|
||||
{file = "cryptography-46.0.5-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:8293f3dea7fc929ef7240796ba231413afa7b68ce38fd21da2995549f5961981"},
|
||||
{file = "cryptography-46.0.5-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:1abfdb89b41c3be0365328a410baa9df3ff8a9110fb75e7b52e66803ddabc9a9"},
|
||||
{file = "cryptography-46.0.5-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:d66e421495fdb797610a08f43b05269e0a5ea7f5e652a89bfd5a7d3c1dee3648"},
|
||||
{file = "cryptography-46.0.5-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:4e817a8920bfbcff8940ecfd60f23d01836408242b30f1a708d93198393a80b4"},
|
||||
{file = "cryptography-46.0.5-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:68f68d13f2e1cb95163fa3b4db4bf9a159a418f5f6e7242564fc75fcae667fd0"},
|
||||
{file = "cryptography-46.0.5-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:a3d1fae9863299076f05cb8a778c467578262fae09f9dc0ee9b12eb4268ce663"},
|
||||
{file = "cryptography-46.0.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c4143987a42a2397f2fc3b4d7e3a7d313fbe684f67ff443999e803dd75a76826"},
|
||||
{file = "cryptography-46.0.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:7d731d4b107030987fd61a7f8ab512b25b53cef8f233a97379ede116f30eb67d"},
|
||||
{file = "cryptography-46.0.5-cp314-cp314t-win32.whl", hash = "sha256:c3bcce8521d785d510b2aad26ae2c966092b7daa8f45dd8f44734a104dc0bc1a"},
|
||||
{file = "cryptography-46.0.5-cp314-cp314t-win_amd64.whl", hash = "sha256:4d8ae8659ab18c65ced284993c2265910f6c9e650189d4e3f68445ef82a810e4"},
|
||||
{file = "cryptography-46.0.5-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:4108d4c09fbbf2789d0c926eb4152ae1760d5a2d97612b92d508d96c861e4d31"},
|
||||
{file = "cryptography-46.0.5-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7d1f30a86d2757199cb2d56e48cce14deddf1f9c95f1ef1b64ee91ea43fe2e18"},
|
||||
{file = "cryptography-46.0.5-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:039917b0dc418bb9f6edce8a906572d69e74bd330b0b3fea4f79dab7f8ddd235"},
|
||||
{file = "cryptography-46.0.5-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ba2a27ff02f48193fc4daeadf8ad2590516fa3d0adeeb34336b96f7fa64c1e3a"},
|
||||
{file = "cryptography-46.0.5-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:61aa400dce22cb001a98014f647dc21cda08f7915ceb95df0c9eaf84b4b6af76"},
|
||||
{file = "cryptography-46.0.5-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3ce58ba46e1bc2aac4f7d9290223cead56743fa6ab94a5d53292ffaac6a91614"},
|
||||
{file = "cryptography-46.0.5-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:420d0e909050490d04359e7fdb5ed7e667ca5c3c402b809ae2563d7e66a92229"},
|
||||
{file = "cryptography-46.0.5-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:582f5fcd2afa31622f317f80426a027f30dc792e9c80ffee87b993200ea115f1"},
|
||||
{file = "cryptography-46.0.5-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:bfd56bb4b37ed4f330b82402f6f435845a5f5648edf1ad497da51a8452d5d62d"},
|
||||
{file = "cryptography-46.0.5-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:a3d507bb6a513ca96ba84443226af944b0f7f47dcc9a399d110cd6146481d24c"},
|
||||
{file = "cryptography-46.0.5-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9f16fbdf4da055efb21c22d81b89f155f02ba420558db21288b3d0035bafd5f4"},
|
||||
{file = "cryptography-46.0.5-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:ced80795227d70549a411a4ab66e8ce307899fad2220ce5ab2f296e687eacde9"},
|
||||
{file = "cryptography-46.0.5-cp38-abi3-win32.whl", hash = "sha256:02f547fce831f5096c9a567fd41bc12ca8f11df260959ecc7c3202555cc47a72"},
|
||||
{file = "cryptography-46.0.5-cp38-abi3-win_amd64.whl", hash = "sha256:556e106ee01aa13484ce9b0239bca667be5004efb0aabbed28d353df86445595"},
|
||||
{file = "cryptography-46.0.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:3b4995dc971c9fb83c25aa44cf45f02ba86f71ee600d81091c2f0cbae116b06c"},
|
||||
{file = "cryptography-46.0.5-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:bc84e875994c3b445871ea7181d424588171efec3e185dced958dad9e001950a"},
|
||||
{file = "cryptography-46.0.5-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:2ae6971afd6246710480e3f15824ed3029a60fc16991db250034efd0b9fb4356"},
|
||||
{file = "cryptography-46.0.5-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:d861ee9e76ace6cf36a6a89b959ec08e7bc2493ee39d07ffe5acb23ef46d27da"},
|
||||
{file = "cryptography-46.0.5-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:2b7a67c9cd56372f3249b39699f2ad479f6991e62ea15800973b956f4b73e257"},
|
||||
{file = "cryptography-46.0.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:8456928655f856c6e1533ff59d5be76578a7157224dbd9ce6872f25055ab9ab7"},
|
||||
{file = "cryptography-46.0.5.tar.gz", hash = "sha256:abace499247268e3757271b2f1e244b36b06f8515cf27c4d49468fc9eb16e93d"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""}
|
||||
cffi = {version = ">=2.0.0", markers = "python_full_version >= \"3.9.0\" and platform_python_implementation != \"PyPy\""}
|
||||
|
||||
[package.extras]
|
||||
docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=3.0.0) ; python_version >= \"3.8\""]
|
||||
docs = ["sphinx (>=5.3.0)", "sphinx-inline-tabs", "sphinx-rtd-theme (>=3.0.0)"]
|
||||
docstest = ["pyenchant (>=3)", "readme-renderer (>=30.0)", "sphinxcontrib-spelling (>=7.3.1)"]
|
||||
nox = ["nox (>=2024.4.15)", "nox[uv] (>=2024.3.2) ; python_version >= \"3.8\""]
|
||||
pep8test = ["check-sdist ; python_version >= \"3.8\"", "click (>=8.0.1)", "mypy (>=1.4)", "ruff (>=0.3.6)"]
|
||||
nox = ["nox[uv] (>=2024.4.15)"]
|
||||
pep8test = ["check-sdist", "click (>=8.0.1)", "mypy (>=1.14)", "ruff (>=0.11.11)"]
|
||||
sdist = ["build (>=1.0.0)"]
|
||||
ssh = ["bcrypt (>=3.1.5)"]
|
||||
test = ["certifi (>=2024)", "cryptography-vectors (==44.0.1)", "pretend (>=0.7)", "pytest (>=7.4.0)", "pytest-benchmark (>=4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"]
|
||||
test = ["certifi (>=2024)", "cryptography-vectors (==46.0.5)", "pretend (>=0.7)", "pytest (>=7.4.0)", "pytest-benchmark (>=4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"]
|
||||
test-randomorder = ["pytest-randomly"]
|
||||
|
||||
[[package]]
|
||||
@@ -4353,128 +4371,112 @@ ptyprocess = ">=0.5"
|
||||
|
||||
[[package]]
|
||||
name = "pillow"
|
||||
version = "11.3.0"
|
||||
description = "Python Imaging Library (Fork)"
|
||||
version = "12.1.1"
|
||||
description = "Python Imaging Library (fork)"
|
||||
optional = true
|
||||
python-versions = ">=3.9"
|
||||
python-versions = ">=3.10"
|
||||
groups = ["main"]
|
||||
markers = "extra == \"sandbox\""
|
||||
files = [
|
||||
{file = "pillow-11.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1b9c17fd4ace828b3003dfd1e30bff24863e0eb59b535e8f80194d9cc7ecf860"},
|
||||
{file = "pillow-11.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:65dc69160114cdd0ca0f35cb434633c75e8e7fad4cf855177a05bf38678f73ad"},
|
||||
{file = "pillow-11.3.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7107195ddc914f656c7fc8e4a5e1c25f32e9236ea3ea860f257b0436011fddd0"},
|
||||
{file = "pillow-11.3.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cc3e831b563b3114baac7ec2ee86819eb03caa1a2cef0b481a5675b59c4fe23b"},
|
||||
{file = "pillow-11.3.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f1f182ebd2303acf8c380a54f615ec883322593320a9b00438eb842c1f37ae50"},
|
||||
{file = "pillow-11.3.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4445fa62e15936a028672fd48c4c11a66d641d2c05726c7ec1f8ba6a572036ae"},
|
||||
{file = "pillow-11.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:71f511f6b3b91dd543282477be45a033e4845a40278fa8dcdbfdb07109bf18f9"},
|
||||
{file = "pillow-11.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:040a5b691b0713e1f6cbe222e0f4f74cd233421e105850ae3b3c0ceda520f42e"},
|
||||
{file = "pillow-11.3.0-cp310-cp310-win32.whl", hash = "sha256:89bd777bc6624fe4115e9fac3352c79ed60f3bb18651420635f26e643e3dd1f6"},
|
||||
{file = "pillow-11.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:19d2ff547c75b8e3ff46f4d9ef969a06c30ab2d4263a9e287733aa8b2429ce8f"},
|
||||
{file = "pillow-11.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:819931d25e57b513242859ce1876c58c59dc31587847bf74cfe06b2e0cb22d2f"},
|
||||
{file = "pillow-11.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:1cd110edf822773368b396281a2293aeb91c90a2db00d78ea43e7e861631b722"},
|
||||
{file = "pillow-11.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9c412fddd1b77a75aa904615ebaa6001f169b26fd467b4be93aded278266b288"},
|
||||
{file = "pillow-11.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7d1aa4de119a0ecac0a34a9c8bde33f34022e2e8f99104e47a3ca392fd60e37d"},
|
||||
{file = "pillow-11.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:91da1d88226663594e3f6b4b8c3c8d85bd504117d043740a8e0ec449087cc494"},
|
||||
{file = "pillow-11.3.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:643f189248837533073c405ec2f0bb250ba54598cf80e8c1e043381a60632f58"},
|
||||
{file = "pillow-11.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:106064daa23a745510dabce1d84f29137a37224831d88eb4ce94bb187b1d7e5f"},
|
||||
{file = "pillow-11.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cd8ff254faf15591e724dc7c4ddb6bf4793efcbe13802a4ae3e863cd300b493e"},
|
||||
{file = "pillow-11.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:932c754c2d51ad2b2271fd01c3d121daaa35e27efae2a616f77bf164bc0b3e94"},
|
||||
{file = "pillow-11.3.0-cp311-cp311-win32.whl", hash = "sha256:b4b8f3efc8d530a1544e5962bd6b403d5f7fe8b9e08227c6b255f98ad82b4ba0"},
|
||||
{file = "pillow-11.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:1a992e86b0dd7aeb1f053cd506508c0999d710a8f07b4c791c63843fc6a807ac"},
|
||||
{file = "pillow-11.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:30807c931ff7c095620fe04448e2c2fc673fcbb1ffe2a7da3fb39613489b1ddd"},
|
||||
{file = "pillow-11.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdae223722da47b024b867c1ea0be64e0df702c5e0a60e27daad39bf960dd1e4"},
|
||||
{file = "pillow-11.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:921bd305b10e82b4d1f5e802b6850677f965d8394203d182f078873851dada69"},
|
||||
{file = "pillow-11.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:eb76541cba2f958032d79d143b98a3a6b3ea87f0959bbe256c0b5e416599fd5d"},
|
||||
{file = "pillow-11.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:67172f2944ebba3d4a7b54f2e95c786a3a50c21b88456329314caaa28cda70f6"},
|
||||
{file = "pillow-11.3.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97f07ed9f56a3b9b5f49d3661dc9607484e85c67e27f3e8be2c7d28ca032fec7"},
|
||||
{file = "pillow-11.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:676b2815362456b5b3216b4fd5bd89d362100dc6f4945154ff172e206a22c024"},
|
||||
{file = "pillow-11.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3e184b2f26ff146363dd07bde8b711833d7b0202e27d13540bfe2e35a323a809"},
|
||||
{file = "pillow-11.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6be31e3fc9a621e071bc17bb7de63b85cbe0bfae91bb0363c893cbe67247780d"},
|
||||
{file = "pillow-11.3.0-cp312-cp312-win32.whl", hash = "sha256:7b161756381f0918e05e7cb8a371fff367e807770f8fe92ecb20d905d0e1c149"},
|
||||
{file = "pillow-11.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a6444696fce635783440b7f7a9fc24b3ad10a9ea3f0ab66c5905be1c19ccf17d"},
|
||||
{file = "pillow-11.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:2aceea54f957dd4448264f9bf40875da0415c83eb85f55069d89c0ed436e3542"},
|
||||
{file = "pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:1c627742b539bba4309df89171356fcb3cc5a9178355b2727d1b74a6cf155fbd"},
|
||||
{file = "pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:30b7c02f3899d10f13d7a48163c8969e4e653f8b43416d23d13d1bbfdc93b9f8"},
|
||||
{file = "pillow-11.3.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:7859a4cc7c9295f5838015d8cc0a9c215b77e43d07a25e460f35cf516df8626f"},
|
||||
{file = "pillow-11.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ec1ee50470b0d050984394423d96325b744d55c701a439d2bd66089bff963d3c"},
|
||||
{file = "pillow-11.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7db51d222548ccfd274e4572fdbf3e810a5e66b00608862f947b163e613b67dd"},
|
||||
{file = "pillow-11.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2d6fcc902a24ac74495df63faad1884282239265c6839a0a6416d33faedfae7e"},
|
||||
{file = "pillow-11.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f0f5d8f4a08090c6d6d578351a2b91acf519a54986c055af27e7a93feae6d3f1"},
|
||||
{file = "pillow-11.3.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c37d8ba9411d6003bba9e518db0db0c58a680ab9fe5179f040b0463644bc9805"},
|
||||
{file = "pillow-11.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13f87d581e71d9189ab21fe0efb5a23e9f28552d5be6979e84001d3b8505abe8"},
|
||||
{file = "pillow-11.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:023f6d2d11784a465f09fd09a34b150ea4672e85fb3d05931d89f373ab14abb2"},
|
||||
{file = "pillow-11.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:45dfc51ac5975b938e9809451c51734124e73b04d0f0ac621649821a63852e7b"},
|
||||
{file = "pillow-11.3.0-cp313-cp313-win32.whl", hash = "sha256:a4d336baed65d50d37b88ca5b60c0fa9d81e3a87d4a7930d3880d1624d5b31f3"},
|
||||
{file = "pillow-11.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0bce5c4fd0921f99d2e858dc4d4d64193407e1b99478bc5cacecba2311abde51"},
|
||||
{file = "pillow-11.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:1904e1264881f682f02b7f8167935cce37bc97db457f8e7849dc3a6a52b99580"},
|
||||
{file = "pillow-11.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4c834a3921375c48ee6b9624061076bc0a32a60b5532b322cc0ea64e639dd50e"},
|
||||
{file = "pillow-11.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5e05688ccef30ea69b9317a9ead994b93975104a677a36a8ed8106be9260aa6d"},
|
||||
{file = "pillow-11.3.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1019b04af07fc0163e2810167918cb5add8d74674b6267616021ab558dc98ced"},
|
||||
{file = "pillow-11.3.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f944255db153ebb2b19c51fe85dd99ef0ce494123f21b9db4877ffdfc5590c7c"},
|
||||
{file = "pillow-11.3.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1f85acb69adf2aaee8b7da124efebbdb959a104db34d3a2cb0f3793dbae422a8"},
|
||||
{file = "pillow-11.3.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:05f6ecbeff5005399bb48d198f098a9b4b6bdf27b8487c7f38ca16eeb070cd59"},
|
||||
{file = "pillow-11.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a7bc6e6fd0395bc052f16b1a8670859964dbd7003bd0af2ff08342eb6e442cfe"},
|
||||
{file = "pillow-11.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:83e1b0161c9d148125083a35c1c5a89db5b7054834fd4387499e06552035236c"},
|
||||
{file = "pillow-11.3.0-cp313-cp313t-win32.whl", hash = "sha256:2a3117c06b8fb646639dce83694f2f9eac405472713fcb1ae887469c0d4f6788"},
|
||||
{file = "pillow-11.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:857844335c95bea93fb39e0fa2726b4d9d758850b34075a7e3ff4f4fa3aa3b31"},
|
||||
{file = "pillow-11.3.0-cp313-cp313t-win_arm64.whl", hash = "sha256:8797edc41f3e8536ae4b10897ee2f637235c94f27404cac7297f7b607dd0716e"},
|
||||
{file = "pillow-11.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d9da3df5f9ea2a89b81bb6087177fb1f4d1c7146d583a3fe5c672c0d94e55e12"},
|
||||
{file = "pillow-11.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0b275ff9b04df7b640c59ec5a3cb113eefd3795a8df80bac69646ef699c6981a"},
|
||||
{file = "pillow-11.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0743841cabd3dba6a83f38a92672cccbd69af56e3e91777b0ee7f4dba4385632"},
|
||||
{file = "pillow-11.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2465a69cf967b8b49ee1b96d76718cd98c4e925414ead59fdf75cf0fd07df673"},
|
||||
{file = "pillow-11.3.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41742638139424703b4d01665b807c6468e23e699e8e90cffefe291c5832b027"},
|
||||
{file = "pillow-11.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:93efb0b4de7e340d99057415c749175e24c8864302369e05914682ba642e5d77"},
|
||||
{file = "pillow-11.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7966e38dcd0fa11ca390aed7c6f20454443581d758242023cf36fcb319b1a874"},
|
||||
{file = "pillow-11.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:98a9afa7b9007c67ed84c57c9e0ad86a6000da96eaa638e4f8abe5b65ff83f0a"},
|
||||
{file = "pillow-11.3.0-cp314-cp314-win32.whl", hash = "sha256:02a723e6bf909e7cea0dac1b0e0310be9d7650cd66222a5f1c571455c0a45214"},
|
||||
{file = "pillow-11.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:a418486160228f64dd9e9efcd132679b7a02a5f22c982c78b6fc7dab3fefb635"},
|
||||
{file = "pillow-11.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:155658efb5e044669c08896c0c44231c5e9abcaadbc5cd3648df2f7c0b96b9a6"},
|
||||
{file = "pillow-11.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:59a03cdf019efbfeeed910bf79c7c93255c3d54bc45898ac2a4140071b02b4ae"},
|
||||
{file = "pillow-11.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f8a5827f84d973d8636e9dc5764af4f0cf2318d26744b3d902931701b0d46653"},
|
||||
{file = "pillow-11.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ee92f2fd10f4adc4b43d07ec5e779932b4eb3dbfbc34790ada5a6669bc095aa6"},
|
||||
{file = "pillow-11.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c96d333dcf42d01f47b37e0979b6bd73ec91eae18614864622d9b87bbd5bbf36"},
|
||||
{file = "pillow-11.3.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c96f993ab8c98460cd0c001447bff6194403e8b1d7e149ade5f00594918128b"},
|
||||
{file = "pillow-11.3.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41342b64afeba938edb034d122b2dda5db2139b9a4af999729ba8818e0056477"},
|
||||
{file = "pillow-11.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:068d9c39a2d1b358eb9f245ce7ab1b5c3246c7c8c7d9ba58cfa5b43146c06e50"},
|
||||
{file = "pillow-11.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a1bc6ba083b145187f648b667e05a2534ecc4b9f2784c2cbe3089e44868f2b9b"},
|
||||
{file = "pillow-11.3.0-cp314-cp314t-win32.whl", hash = "sha256:118ca10c0d60b06d006be10a501fd6bbdfef559251ed31b794668ed569c87e12"},
|
||||
{file = "pillow-11.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:8924748b688aa210d79883357d102cd64690e56b923a186f35a82cbc10f997db"},
|
||||
{file = "pillow-11.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:79ea0d14d3ebad43ec77ad5272e6ff9bba5b679ef73375ea760261207fa8e0aa"},
|
||||
{file = "pillow-11.3.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:48d254f8a4c776de343051023eb61ffe818299eeac478da55227d96e241de53f"},
|
||||
{file = "pillow-11.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7aee118e30a4cf54fdd873bd3a29de51e29105ab11f9aad8c32123f58c8f8081"},
|
||||
{file = "pillow-11.3.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:23cff760a9049c502721bdb743a7cb3e03365fafcdfc2ef9784610714166e5a4"},
|
||||
{file = "pillow-11.3.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6359a3bc43f57d5b375d1ad54a0074318a0844d11b76abccf478c37c986d3cfc"},
|
||||
{file = "pillow-11.3.0-cp39-cp39-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:092c80c76635f5ecb10f3f83d76716165c96f5229addbd1ec2bdbbda7d496e06"},
|
||||
{file = "pillow-11.3.0-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cadc9e0ea0a2431124cde7e1697106471fc4c1da01530e679b2391c37d3fbb3a"},
|
||||
{file = "pillow-11.3.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:6a418691000f2a418c9135a7cf0d797c1bb7d9a485e61fe8e7722845b95ef978"},
|
||||
{file = "pillow-11.3.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:97afb3a00b65cc0804d1c7abddbf090a81eaac02768af58cbdcaaa0a931e0b6d"},
|
||||
{file = "pillow-11.3.0-cp39-cp39-win32.whl", hash = "sha256:ea944117a7974ae78059fcc1800e5d3295172bb97035c0c1d9345fca1419da71"},
|
||||
{file = "pillow-11.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:e5c5858ad8ec655450a7c7df532e9842cf8df7cc349df7225c60d5d348c8aada"},
|
||||
{file = "pillow-11.3.0-cp39-cp39-win_arm64.whl", hash = "sha256:6abdbfd3aea42be05702a8dd98832329c167ee84400a1d1f61ab11437f1717eb"},
|
||||
{file = "pillow-11.3.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:3cee80663f29e3843b68199b9d6f4f54bd1d4a6b59bdd91bceefc51238bcb967"},
|
||||
{file = "pillow-11.3.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b5f56c3f344f2ccaf0dd875d3e180f631dc60a51b314295a3e681fe8cf851fbe"},
|
||||
{file = "pillow-11.3.0-pp310-pypy310_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e67d793d180c9df62f1f40aee3accca4829d3794c95098887edc18af4b8b780c"},
|
||||
{file = "pillow-11.3.0-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d000f46e2917c705e9fb93a3606ee4a819d1e3aa7a9b442f6444f07e77cf5e25"},
|
||||
{file = "pillow-11.3.0-pp310-pypy310_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:527b37216b6ac3a12d7838dc3bd75208ec57c1c6d11ef01902266a5a0c14fc27"},
|
||||
{file = "pillow-11.3.0-pp310-pypy310_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:be5463ac478b623b9dd3937afd7fb7ab3d79dd290a28e2b6df292dc75063eb8a"},
|
||||
{file = "pillow-11.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:8dc70ca24c110503e16918a658b869019126ecfe03109b754c402daff12b3d9f"},
|
||||
{file = "pillow-11.3.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7c8ec7a017ad1bd562f93dbd8505763e688d388cde6e4a010ae1486916e713e6"},
|
||||
{file = "pillow-11.3.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:9ab6ae226de48019caa8074894544af5b53a117ccb9d3b3dcb2871464c829438"},
|
||||
{file = "pillow-11.3.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fe27fb049cdcca11f11a7bfda64043c37b30e6b91f10cb5bab275806c32f6ab3"},
|
||||
{file = "pillow-11.3.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:465b9e8844e3c3519a983d58b80be3f668e2a7a5db97f2784e7079fbc9f9822c"},
|
||||
{file = "pillow-11.3.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5418b53c0d59b3824d05e029669efa023bbef0f3e92e75ec8428f3799487f361"},
|
||||
{file = "pillow-11.3.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:504b6f59505f08ae014f724b6207ff6222662aab5cc9542577fb084ed0676ac7"},
|
||||
{file = "pillow-11.3.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c84d689db21a1c397d001aa08241044aa2069e7587b398c8cc63020390b1c1b8"},
|
||||
{file = "pillow-11.3.0.tar.gz", hash = "sha256:3828ee7586cd0b2091b6209e5ad53e20d0649bbe87164a459d0676e035e8f523"},
|
||||
{file = "pillow-12.1.1-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1f1625b72740fdda5d77b4def688eb8fd6490975d06b909fd19f13f391e077e0"},
|
||||
{file = "pillow-12.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:178aa072084bd88ec759052feca8e56cbb14a60b39322b99a049e58090479713"},
|
||||
{file = "pillow-12.1.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b66e95d05ba806247aaa1561f080abc7975daf715c30780ff92a20e4ec546e1b"},
|
||||
{file = "pillow-12.1.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:89c7e895002bbe49cdc5426150377cbbc04767d7547ed145473f496dfa40408b"},
|
||||
{file = "pillow-12.1.1-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a5cbdcddad0af3da87cb16b60d23648bc3b51967eb07223e9fed77a82b457c4"},
|
||||
{file = "pillow-12.1.1-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9f51079765661884a486727f0729d29054242f74b46186026582b4e4769918e4"},
|
||||
{file = "pillow-12.1.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:99c1506ea77c11531d75e3a412832a13a71c7ebc8192ab9e4b2e355555920e3e"},
|
||||
{file = "pillow-12.1.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:36341d06738a9f66c8287cf8b876d24b18db9bd8740fa0672c74e259ad408cff"},
|
||||
{file = "pillow-12.1.1-cp310-cp310-win32.whl", hash = "sha256:6c52f062424c523d6c4db85518774cc3d50f5539dd6eed32b8f6229b26f24d40"},
|
||||
{file = "pillow-12.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:c6008de247150668a705a6338156efb92334113421ceecf7438a12c9a12dab23"},
|
||||
{file = "pillow-12.1.1-cp310-cp310-win_arm64.whl", hash = "sha256:1a9b0ee305220b392e1124a764ee4265bd063e54a751a6b62eff69992f457fa9"},
|
||||
{file = "pillow-12.1.1-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:e879bb6cd5c73848ef3b2b48b8af9ff08c5b71ecda8048b7dd22d8a33f60be32"},
|
||||
{file = "pillow-12.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:365b10bb9417dd4498c0e3b128018c4a624dc11c7b97d8cc54effe3b096f4c38"},
|
||||
{file = "pillow-12.1.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d4ce8e329c93845720cd2014659ca67eac35f6433fd3050393d85f3ecef0dad5"},
|
||||
{file = "pillow-12.1.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc354a04072b765eccf2204f588a7a532c9511e8b9c7f900e1b64e3e33487090"},
|
||||
{file = "pillow-12.1.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7e7976bf1910a8116b523b9f9f58bf410f3e8aa330cd9a2bb2953f9266ab49af"},
|
||||
{file = "pillow-12.1.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:597bd9c8419bc7c6af5604e55847789b69123bbe25d65cc6ad3012b4f3c98d8b"},
|
||||
{file = "pillow-12.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2c1fc0f2ca5f96a3c8407e41cca26a16e46b21060fe6d5b099d2cb01412222f5"},
|
||||
{file = "pillow-12.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:578510d88c6229d735855e1f278aa305270438d36a05031dfaae5067cc8eb04d"},
|
||||
{file = "pillow-12.1.1-cp311-cp311-win32.whl", hash = "sha256:7311c0a0dcadb89b36b7025dfd8326ecfa36964e29913074d47382706e516a7c"},
|
||||
{file = "pillow-12.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:fbfa2a7c10cc2623f412753cddf391c7f971c52ca40a3f65dc5039b2939e8563"},
|
||||
{file = "pillow-12.1.1-cp311-cp311-win_arm64.whl", hash = "sha256:b81b5e3511211631b3f672a595e3221252c90af017e399056d0faabb9538aa80"},
|
||||
{file = "pillow-12.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ab323b787d6e18b3d91a72fc99b1a2c28651e4358749842b8f8dfacd28ef2052"},
|
||||
{file = "pillow-12.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:adebb5bee0f0af4909c30db0d890c773d1a92ffe83da908e2e9e720f8edf3984"},
|
||||
{file = "pillow-12.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bb66b7cc26f50977108790e2456b7921e773f23db5630261102233eb355a3b79"},
|
||||
{file = "pillow-12.1.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aee2810642b2898bb187ced9b349e95d2a7272930796e022efaf12e99dccd293"},
|
||||
{file = "pillow-12.1.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a0b1cd6232e2b618adcc54d9882e4e662a089d5768cd188f7c245b4c8c44a397"},
|
||||
{file = "pillow-12.1.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7aac39bcf8d4770d089588a2e1dd111cbaa42df5a94be3114222057d68336bd0"},
|
||||
{file = "pillow-12.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ab174cd7d29a62dd139c44bf74b698039328f45cb03b4596c43473a46656b2f3"},
|
||||
{file = "pillow-12.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:339ffdcb7cbeaa08221cd401d517d4b1fe7a9ed5d400e4a8039719238620ca35"},
|
||||
{file = "pillow-12.1.1-cp312-cp312-win32.whl", hash = "sha256:5d1f9575a12bed9e9eedd9a4972834b08c97a352bd17955ccdebfeca5913fa0a"},
|
||||
{file = "pillow-12.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:21329ec8c96c6e979cd0dfd29406c40c1d52521a90544463057d2aaa937d66a6"},
|
||||
{file = "pillow-12.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:af9a332e572978f0218686636610555ae3defd1633597be015ed50289a03c523"},
|
||||
{file = "pillow-12.1.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:d242e8ac078781f1de88bf823d70c1a9b3c7950a44cdf4b7c012e22ccbcd8e4e"},
|
||||
{file = "pillow-12.1.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:02f84dfad02693676692746df05b89cf25597560db2857363a208e393429f5e9"},
|
||||
{file = "pillow-12.1.1-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:e65498daf4b583091ccbb2556c7000abf0f3349fcd57ef7adc9a84a394ed29f6"},
|
||||
{file = "pillow-12.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6c6db3b84c87d48d0088943bf33440e0c42370b99b1c2a7989216f7b42eede60"},
|
||||
{file = "pillow-12.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8b7e5304e34942bf62e15184219a7b5ad4ff7f3bb5cca4d984f37df1a0e1aee2"},
|
||||
{file = "pillow-12.1.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:18e5bddd742a44b7e6b1e773ab5db102bd7a94c32555ba656e76d319d19c3850"},
|
||||
{file = "pillow-12.1.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc44ef1f3de4f45b50ccf9136999d71abb99dca7706bc75d222ed350b9fd2289"},
|
||||
{file = "pillow-12.1.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5a8eb7ed8d4198bccbd07058416eeec51686b498e784eda166395a23eb99138e"},
|
||||
{file = "pillow-12.1.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:47b94983da0c642de92ced1702c5b6c292a84bd3a8e1d1702ff923f183594717"},
|
||||
{file = "pillow-12.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:518a48c2aab7ce596d3bf79d0e275661b846e86e4d0e7dec34712c30fe07f02a"},
|
||||
{file = "pillow-12.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a550ae29b95c6dc13cf69e2c9dc5747f814c54eeb2e32d683e5e93af56caa029"},
|
||||
{file = "pillow-12.1.1-cp313-cp313-win32.whl", hash = "sha256:a003d7422449f6d1e3a34e3dd4110c22148336918ddbfc6a32581cd54b2e0b2b"},
|
||||
{file = "pillow-12.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:344cf1e3dab3be4b1fa08e449323d98a2a3f819ad20f4b22e77a0ede31f0faa1"},
|
||||
{file = "pillow-12.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:5c0dd1636633e7e6a0afe7bf6a51a14992b7f8e60de5789018ebbdfae55b040a"},
|
||||
{file = "pillow-12.1.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0330d233c1a0ead844fc097a7d16c0abff4c12e856c0b325f231820fee1f39da"},
|
||||
{file = "pillow-12.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5dae5f21afb91322f2ff791895ddd8889e5e947ff59f71b46041c8ce6db790bc"},
|
||||
{file = "pillow-12.1.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2e0c664be47252947d870ac0d327fea7e63985a08794758aa8af5b6cb6ec0c9c"},
|
||||
{file = "pillow-12.1.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:691ab2ac363b8217f7d31b3497108fb1f50faab2f75dfb03284ec2f217e87bf8"},
|
||||
{file = "pillow-12.1.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9e8064fb1cc019296958595f6db671fba95209e3ceb0c4734c9baf97de04b20"},
|
||||
{file = "pillow-12.1.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:472a8d7ded663e6162dafdf20015c486a7009483ca671cece7a9279b512fcb13"},
|
||||
{file = "pillow-12.1.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:89b54027a766529136a06cfebeecb3a04900397a3590fd252160b888479517bf"},
|
||||
{file = "pillow-12.1.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:86172b0831b82ce4f7877f280055892b31179e1576aa00d0df3bb1bbf8c3e524"},
|
||||
{file = "pillow-12.1.1-cp313-cp313t-win32.whl", hash = "sha256:44ce27545b6efcf0fdbdceb31c9a5bdea9333e664cda58a7e674bb74608b3986"},
|
||||
{file = "pillow-12.1.1-cp313-cp313t-win_amd64.whl", hash = "sha256:a285e3eb7a5a45a2ff504e31f4a8d1b12ef62e84e5411c6804a42197c1cf586c"},
|
||||
{file = "pillow-12.1.1-cp313-cp313t-win_arm64.whl", hash = "sha256:cc7d296b5ea4d29e6570dabeaed58d31c3fea35a633a69679fb03d7664f43fb3"},
|
||||
{file = "pillow-12.1.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:417423db963cb4be8bac3fc1204fe61610f6abeed1580a7a2cbb2fbda20f12af"},
|
||||
{file = "pillow-12.1.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:b957b71c6b2387610f556a7eb0828afbe40b4a98036fc0d2acfa5a44a0c2036f"},
|
||||
{file = "pillow-12.1.1-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:097690ba1f2efdeb165a20469d59d8bb03c55fb6621eb2041a060ae8ea3e9642"},
|
||||
{file = "pillow-12.1.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2815a87ab27848db0321fb78c7f0b2c8649dee134b7f2b80c6a45c6831d75ccd"},
|
||||
{file = "pillow-12.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f7ed2c6543bad5a7d5530eb9e78c53132f93dfa44a28492db88b41cdab885202"},
|
||||
{file = "pillow-12.1.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:652a2c9ccfb556235b2b501a3a7cf3742148cd22e04b5625c5fe057ea3e3191f"},
|
||||
{file = "pillow-12.1.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d6e4571eedf43af33d0fc233a382a76e849badbccdf1ac438841308652a08e1f"},
|
||||
{file = "pillow-12.1.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b574c51cf7d5d62e9be37ba446224b59a2da26dc4c1bb2ecbe936a4fb1a7cb7f"},
|
||||
{file = "pillow-12.1.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a37691702ed687799de29a518d63d4682d9016932db66d4e90c345831b02fb4e"},
|
||||
{file = "pillow-12.1.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f95c00d5d6700b2b890479664a06e754974848afaae5e21beb4d83c106923fd0"},
|
||||
{file = "pillow-12.1.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:559b38da23606e68681337ad74622c4dbba02254fc9cb4488a305dd5975c7eeb"},
|
||||
{file = "pillow-12.1.1-cp314-cp314-win32.whl", hash = "sha256:03edcc34d688572014ff223c125a3f77fb08091e4607e7745002fc214070b35f"},
|
||||
{file = "pillow-12.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:50480dcd74fa63b8e78235957d302d98d98d82ccbfac4c7e12108ba9ecbdba15"},
|
||||
{file = "pillow-12.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:5cb1785d97b0c3d1d1a16bc1d710c4a0049daefc4935f3a8f31f827f4d3d2e7f"},
|
||||
{file = "pillow-12.1.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:1f90cff8aa76835cba5769f0b3121a22bd4eb9e6884cfe338216e557a9a548b8"},
|
||||
{file = "pillow-12.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1f1be78ce9466a7ee64bfda57bdba0f7cc499d9794d518b854816c41bf0aa4e9"},
|
||||
{file = "pillow-12.1.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:42fc1f4677106188ad9a55562bbade416f8b55456f522430fadab3cef7cd4e60"},
|
||||
{file = "pillow-12.1.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:98edb152429ab62a1818039744d8fbb3ccab98a7c29fc3d5fcef158f3f1f68b7"},
|
||||
{file = "pillow-12.1.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d470ab1178551dd17fdba0fef463359c41aaa613cdcd7ff8373f54be629f9f8f"},
|
||||
{file = "pillow-12.1.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6408a7b064595afcab0a49393a413732a35788f2a5092fdc6266952ed67de586"},
|
||||
{file = "pillow-12.1.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5d8c41325b382c07799a3682c1c258469ea2ff97103c53717b7893862d0c98ce"},
|
||||
{file = "pillow-12.1.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c7697918b5be27424e9ce568193efd13d925c4481dd364e43f5dff72d33e10f8"},
|
||||
{file = "pillow-12.1.1-cp314-cp314t-win32.whl", hash = "sha256:d2912fd8114fc5545aa3a4b5576512f64c55a03f3ebcca4c10194d593d43ea36"},
|
||||
{file = "pillow-12.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:4ceb838d4bd9dab43e06c363cab2eebf63846d6a4aeaea283bbdfd8f1a8ed58b"},
|
||||
{file = "pillow-12.1.1-cp314-cp314t-win_arm64.whl", hash = "sha256:7b03048319bfc6170e93bd60728a1af51d3dd7704935feb228c4d4faab35d334"},
|
||||
{file = "pillow-12.1.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:600fd103672b925fe62ed08e0d874ea34d692474df6f4bf7ebe148b30f89f39f"},
|
||||
{file = "pillow-12.1.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:665e1b916b043cef294bc54d47bf02d87e13f769bc4bc5fa225a24b3a6c5aca9"},
|
||||
{file = "pillow-12.1.1-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:495c302af3aad1ca67420ddd5c7bd480c8867ad173528767d906428057a11f0e"},
|
||||
{file = "pillow-12.1.1-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8fd420ef0c52c88b5a035a0886f367748c72147b2b8f384c9d12656678dfdfa9"},
|
||||
{file = "pillow-12.1.1-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f975aa7ef9684ce7e2c18a3aa8f8e2106ce1e46b94ab713d156b2898811651d3"},
|
||||
{file = "pillow-12.1.1-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8089c852a56c2966cf18835db62d9b34fef7ba74c726ad943928d494fa7f4735"},
|
||||
{file = "pillow-12.1.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:cb9bb857b2d057c6dfc72ac5f3b44836924ba15721882ef103cecb40d002d80e"},
|
||||
{file = "pillow-12.1.1.tar.gz", hash = "sha256:9ad8fa5937ab05218e2b6a4cff30295ad35afd2f83ac592e68c0d871bb0fdbc4"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
docs = ["furo", "olefile", "sphinx (>=8.2)", "sphinx-autobuild", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"]
|
||||
fpx = ["olefile"]
|
||||
mic = ["olefile"]
|
||||
test-arrow = ["pyarrow"]
|
||||
tests = ["check-manifest", "coverage (>=7.4.2)", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "trove-classifiers (>=2024.10.12)"]
|
||||
typing = ["typing-extensions ; python_version < \"3.10\""]
|
||||
test-arrow = ["arro3-compute", "arro3-core", "nanoarrow", "pyarrow"]
|
||||
tests = ["check-manifest", "coverage (>=7.4.2)", "defusedxml", "markdown2", "olefile", "packaging", "pyroma (>=5)", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "trove-classifiers (>=2024.10.12)"]
|
||||
xmp = ["defusedxml"]
|
||||
|
||||
[[package]]
|
||||
@@ -4741,23 +4743,23 @@ testing = ["google-api-core (>=1.31.5)"]
|
||||
|
||||
[[package]]
|
||||
name = "protobuf"
|
||||
version = "6.33.4"
|
||||
version = "6.33.5"
|
||||
description = ""
|
||||
optional = true
|
||||
python-versions = ">=3.9"
|
||||
groups = ["main"]
|
||||
markers = "extra == \"vertex\""
|
||||
files = [
|
||||
{file = "protobuf-6.33.4-cp310-abi3-win32.whl", hash = "sha256:918966612c8232fc6c24c78e1cd89784307f5814ad7506c308ee3cf86662850d"},
|
||||
{file = "protobuf-6.33.4-cp310-abi3-win_amd64.whl", hash = "sha256:8f11ffae31ec67fc2554c2ef891dcb561dae9a2a3ed941f9e134c2db06657dbc"},
|
||||
{file = "protobuf-6.33.4-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:2fe67f6c014c84f655ee06f6f66213f9254b3a8b6bda6cda0ccd4232c73c06f0"},
|
||||
{file = "protobuf-6.33.4-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:757c978f82e74d75cba88eddec479df9b99a42b31193313b75e492c06a51764e"},
|
||||
{file = "protobuf-6.33.4-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:c7c64f259c618f0bef7bee042075e390debbf9682334be2b67408ec7c1c09ee6"},
|
||||
{file = "protobuf-6.33.4-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:3df850c2f8db9934de4cf8f9152f8dc2558f49f298f37f90c517e8e5c84c30e9"},
|
||||
{file = "protobuf-6.33.4-cp39-cp39-win32.whl", hash = "sha256:955478a89559fa4568f5a81dce77260eabc5c686f9e8366219ebd30debf06aa6"},
|
||||
{file = "protobuf-6.33.4-cp39-cp39-win_amd64.whl", hash = "sha256:0f12ddbf96912690c3582f9dffb55530ef32015ad8e678cd494312bd78314c4f"},
|
||||
{file = "protobuf-6.33.4-py3-none-any.whl", hash = "sha256:1fe3730068fcf2e595816a6c34fe66eeedd37d51d0400b72fabc848811fdc1bc"},
|
||||
{file = "protobuf-6.33.4.tar.gz", hash = "sha256:dc2e61bca3b10470c1912d166fe0af67bfc20eb55971dcef8dfa48ce14f0ed91"},
|
||||
{file = "protobuf-6.33.5-cp310-abi3-win32.whl", hash = "sha256:d71b040839446bac0f4d162e758bea99c8251161dae9d0983a3b88dee345153b"},
|
||||
{file = "protobuf-6.33.5-cp310-abi3-win_amd64.whl", hash = "sha256:3093804752167bcab3998bec9f1048baae6e29505adaf1afd14a37bddede533c"},
|
||||
{file = "protobuf-6.33.5-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:a5cb85982d95d906df1e2210e58f8e4f1e3cdc088e52c921a041f9c9a0386de5"},
|
||||
{file = "protobuf-6.33.5-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:9b71e0281f36f179d00cbcb119cb19dec4d14a81393e5ea220f64b286173e190"},
|
||||
{file = "protobuf-6.33.5-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:8afa18e1d6d20af15b417e728e9f60f3aa108ee76f23c3b2c07a2c3b546d3afd"},
|
||||
{file = "protobuf-6.33.5-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:cbf16ba3350fb7b889fca858fb215967792dc125b35c7976ca4818bee3521cf0"},
|
||||
{file = "protobuf-6.33.5-cp39-cp39-win32.whl", hash = "sha256:a3157e62729aafb8df6da2c03aa5c0937c7266c626ce11a278b6eb7963c4e37c"},
|
||||
{file = "protobuf-6.33.5-cp39-cp39-win_amd64.whl", hash = "sha256:8f04fa32763dcdb4973d537d6b54e615cc61108c7cb38fe59310c3192d29510a"},
|
||||
{file = "protobuf-6.33.5-py3-none-any.whl", hash = "sha256:69915a973dd0f60f31a08b8318b73eab2bd6a392c79184b3612226b0a3f8ec02"},
|
||||
{file = "protobuf-6.33.5.tar.gz", hash = "sha256:6ddcac2a081f8b7b9642c09406bc6a4290128fce5f471cddd165960bb9119e5c"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5235,20 +5237,20 @@ diagrams = ["jinja2", "railroad-diagrams"]
|
||||
|
||||
[[package]]
|
||||
name = "pypdf"
|
||||
version = "6.6.2"
|
||||
version = "6.7.1"
|
||||
description = "A pure-python PDF library capable of splitting, merging, cropping, and transforming PDF files"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "pypdf-6.6.2-py3-none-any.whl", hash = "sha256:44c0c9811cfb3b83b28f1c3d054531d5b8b81abaedee0d8cb403650d023832ba"},
|
||||
{file = "pypdf-6.6.2.tar.gz", hash = "sha256:0a3ea3b3303982333404e22d8f75d7b3144f9cf4b2970b96856391a516f9f016"},
|
||||
{file = "pypdf-6.7.1-py3-none-any.whl", hash = "sha256:a02ccbb06463f7c334ce1612e91b3e68a8e827f3cee100b9941771e6066b094e"},
|
||||
{file = "pypdf-6.7.1.tar.gz", hash = "sha256:6b7a63be5563a0a35d54c6d6b550d75c00b8ccf36384be96365355e296e6b3b0"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
crypto = ["cryptography"]
|
||||
cryptodome = ["PyCryptodome"]
|
||||
dev = ["black", "flit", "pip-tools", "pre-commit", "pytest-cov", "pytest-socket", "pytest-timeout", "pytest-xdist", "wheel"]
|
||||
dev = ["flit", "pip-tools", "pre-commit", "pytest-cov", "pytest-socket", "pytest-timeout", "pytest-xdist", "wheel"]
|
||||
docs = ["myst_parser", "sphinx", "sphinx_rtd_theme"]
|
||||
full = ["Pillow (>=8.0.0)", "cryptography"]
|
||||
image = ["Pillow (>=8.0.0)"]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "strix-agent"
|
||||
version = "0.7.0"
|
||||
version = "0.8.0"
|
||||
description = "Open-source AI Hackers for your apps"
|
||||
authors = ["Strix <hi@usestrix.com>"]
|
||||
readme = "README.md"
|
||||
|
||||
@@ -335,14 +335,18 @@ echo -e "${MUTED} AI Penetration Testing Agent${NC}"
|
||||
echo ""
|
||||
echo -e "${MUTED}To get started:${NC}"
|
||||
echo ""
|
||||
echo -e " ${CYAN}1.${NC} Set your LLM provider:"
|
||||
echo -e " ${MUTED}export STRIX_LLM='openai/gpt-5'${NC}"
|
||||
echo -e " ${MUTED}export LLM_API_KEY='your-api-key'${NC}"
|
||||
echo -e " ${CYAN}1.${NC} Get your Strix API key:"
|
||||
echo -e " ${MUTED}https://models.strix.ai${NC}"
|
||||
echo ""
|
||||
echo -e " ${CYAN}2.${NC} Run a penetration test:"
|
||||
echo -e " ${CYAN}2.${NC} Set your environment:"
|
||||
echo -e " ${MUTED}export LLM_API_KEY='your-api-key'${NC}"
|
||||
echo -e " ${MUTED}export STRIX_LLM='strix/claude-sonnet-4.6'${NC}"
|
||||
echo ""
|
||||
echo -e " ${CYAN}3.${NC} Run a penetration test:"
|
||||
echo -e " ${MUTED}strix --target https://example.com${NC}"
|
||||
echo ""
|
||||
echo -e "${MUTED}For more information visit ${NC}https://strix.ai"
|
||||
echo -e "${MUTED}Supported models ${NC}https://docs.strix.ai/llm-providers/overview"
|
||||
echo -e "${MUTED}Join our community ${NC}https://discord.gg/strix-ai"
|
||||
echo ""
|
||||
|
||||
|
||||
@@ -5,6 +5,9 @@ from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
|
||||
STRIX_API_BASE = "https://models.strix.ai/api/v1"
|
||||
|
||||
|
||||
class Config:
|
||||
"""Configuration Manager for Strix."""
|
||||
|
||||
@@ -177,3 +180,31 @@ def apply_saved_config(force: bool = False) -> dict[str, str]:
|
||||
|
||||
def save_current_config() -> bool:
|
||||
return Config.save_current()
|
||||
|
||||
|
||||
def resolve_llm_config() -> tuple[str | None, str | None, str | None]:
|
||||
"""Resolve LLM model, api_key, and api_base based on STRIX_LLM prefix.
|
||||
|
||||
Returns:
|
||||
tuple: (model_name, api_key, api_base)
|
||||
- model_name: Original model name (strix/ prefix preserved for display)
|
||||
- api_key: LLM API key
|
||||
- api_base: API base URL (auto-set to STRIX_API_BASE for strix/ models)
|
||||
"""
|
||||
model = Config.get("strix_llm")
|
||||
if not model:
|
||||
return None, None, None
|
||||
|
||||
api_key = Config.get("llm_api_key")
|
||||
|
||||
if model.startswith("strix/"):
|
||||
api_base: str | None = STRIX_API_BASE
|
||||
else:
|
||||
api_base = (
|
||||
Config.get("llm_api_base")
|
||||
or Config.get("openai_api_base")
|
||||
or Config.get("litellm_base_url")
|
||||
or Config.get("ollama_api_base")
|
||||
)
|
||||
|
||||
return model, api_key, api_base
|
||||
|
||||
@@ -18,7 +18,8 @@ from rich.panel import Panel
|
||||
from rich.text import Text
|
||||
|
||||
from strix.config import Config, apply_saved_config, save_current_config
|
||||
from strix.llm.utils import get_litellm_model_name, get_strix_api_base
|
||||
from strix.config.config import resolve_llm_config
|
||||
from strix.llm.utils import get_litellm_model_name
|
||||
|
||||
|
||||
apply_saved_config()
|
||||
@@ -52,10 +53,13 @@ def validate_environment() -> None: # noqa: PLR0912, PLR0915
|
||||
missing_required_vars = []
|
||||
missing_optional_vars = []
|
||||
|
||||
if not Config.get("strix_llm"):
|
||||
strix_llm = Config.get("strix_llm")
|
||||
uses_strix_models = strix_llm and strix_llm.startswith("strix/")
|
||||
|
||||
if not strix_llm:
|
||||
missing_required_vars.append("STRIX_LLM")
|
||||
|
||||
has_base_url = any(
|
||||
has_base_url = uses_strix_models or any(
|
||||
[
|
||||
Config.get("llm_api_base"),
|
||||
Config.get("openai_api_base"),
|
||||
@@ -97,7 +101,7 @@ def validate_environment() -> None: # noqa: PLR0912, PLR0915
|
||||
error_text.append("• ", style="white")
|
||||
error_text.append("STRIX_LLM", style="bold cyan")
|
||||
error_text.append(
|
||||
" - Model name to use with litellm (e.g., 'openai/gpt-5')\n",
|
||||
" - Model name to use with litellm (e.g., 'anthropic/claude-sonnet-4-6')\n",
|
||||
style="white",
|
||||
)
|
||||
|
||||
@@ -136,7 +140,10 @@ def validate_environment() -> None: # noqa: PLR0912, PLR0915
|
||||
)
|
||||
|
||||
error_text.append("\nExample setup:\n", style="white")
|
||||
error_text.append("export STRIX_LLM='openai/gpt-5'\n", style="dim white")
|
||||
if uses_strix_models:
|
||||
error_text.append("export STRIX_LLM='strix/claude-sonnet-4.6'\n", style="dim white")
|
||||
else:
|
||||
error_text.append("export STRIX_LLM='anthropic/claude-sonnet-4-6'\n", style="dim white")
|
||||
|
||||
if missing_optional_vars:
|
||||
for var in missing_optional_vars:
|
||||
@@ -202,15 +209,7 @@ async def warm_up_llm() -> None:
|
||||
console = Console()
|
||||
|
||||
try:
|
||||
model_name = Config.get("strix_llm")
|
||||
api_key = Config.get("llm_api_key")
|
||||
api_base = (
|
||||
Config.get("llm_api_base")
|
||||
or Config.get("openai_api_base")
|
||||
or Config.get("litellm_base_url")
|
||||
or Config.get("ollama_api_base")
|
||||
or get_strix_api_base(model_name)
|
||||
)
|
||||
model_name, api_key, api_base = resolve_llm_config()
|
||||
|
||||
test_messages = [
|
||||
{"role": "system", "content": "You are a helpful assistant."},
|
||||
|
||||
@@ -6,6 +6,11 @@ from pygments.styles import get_style_by_name
|
||||
from rich.text import Text
|
||||
from textual.widgets import Static
|
||||
|
||||
from strix.tools.reporting.reporting_actions import (
|
||||
parse_code_locations_xml,
|
||||
parse_cvss_xml,
|
||||
)
|
||||
|
||||
from .base_renderer import BaseToolRenderer
|
||||
from .registry import register_tool_renderer
|
||||
|
||||
@@ -17,6 +22,13 @@ def _get_style_colors() -> dict[Any, str]:
|
||||
|
||||
|
||||
FIELD_STYLE = "bold #4ade80"
|
||||
DIM_STYLE = "dim"
|
||||
FILE_STYLE = "bold #60a5fa"
|
||||
LINE_STYLE = "#facc15"
|
||||
LABEL_STYLE = "italic #a1a1aa"
|
||||
CODE_STYLE = "#e2e8f0"
|
||||
BEFORE_STYLE = "#ef4444"
|
||||
AFTER_STYLE = "#22c55e"
|
||||
|
||||
|
||||
@register_tool_renderer
|
||||
@@ -80,18 +92,13 @@ class CreateVulnerabilityReportRenderer(BaseToolRenderer):
|
||||
poc_script_code = args.get("poc_script_code", "")
|
||||
remediation_steps = args.get("remediation_steps", "")
|
||||
|
||||
attack_vector = args.get("attack_vector", "")
|
||||
attack_complexity = args.get("attack_complexity", "")
|
||||
privileges_required = args.get("privileges_required", "")
|
||||
user_interaction = args.get("user_interaction", "")
|
||||
scope = args.get("scope", "")
|
||||
confidentiality = args.get("confidentiality", "")
|
||||
integrity = args.get("integrity", "")
|
||||
availability = args.get("availability", "")
|
||||
cvss_breakdown_xml = args.get("cvss_breakdown", "")
|
||||
code_locations_xml = args.get("code_locations", "")
|
||||
|
||||
endpoint = args.get("endpoint", "")
|
||||
method = args.get("method", "")
|
||||
cve = args.get("cve", "")
|
||||
cwe = args.get("cwe", "")
|
||||
|
||||
severity = ""
|
||||
cvss_score = None
|
||||
@@ -140,38 +147,30 @@ class CreateVulnerabilityReportRenderer(BaseToolRenderer):
|
||||
text.append("CVE: ", style=FIELD_STYLE)
|
||||
text.append(cve)
|
||||
|
||||
if any(
|
||||
[
|
||||
attack_vector,
|
||||
attack_complexity,
|
||||
privileges_required,
|
||||
user_interaction,
|
||||
scope,
|
||||
confidentiality,
|
||||
integrity,
|
||||
availability,
|
||||
]
|
||||
):
|
||||
if cwe:
|
||||
text.append("\n\n")
|
||||
text.append("CWE: ", style=FIELD_STYLE)
|
||||
text.append(cwe)
|
||||
|
||||
parsed_cvss = parse_cvss_xml(cvss_breakdown_xml) if cvss_breakdown_xml else None
|
||||
if parsed_cvss:
|
||||
text.append("\n\n")
|
||||
cvss_parts = []
|
||||
if attack_vector:
|
||||
cvss_parts.append(f"AV:{attack_vector}")
|
||||
if attack_complexity:
|
||||
cvss_parts.append(f"AC:{attack_complexity}")
|
||||
if privileges_required:
|
||||
cvss_parts.append(f"PR:{privileges_required}")
|
||||
if user_interaction:
|
||||
cvss_parts.append(f"UI:{user_interaction}")
|
||||
if scope:
|
||||
cvss_parts.append(f"S:{scope}")
|
||||
if confidentiality:
|
||||
cvss_parts.append(f"C:{confidentiality}")
|
||||
if integrity:
|
||||
cvss_parts.append(f"I:{integrity}")
|
||||
if availability:
|
||||
cvss_parts.append(f"A:{availability}")
|
||||
for key, prefix in [
|
||||
("attack_vector", "AV"),
|
||||
("attack_complexity", "AC"),
|
||||
("privileges_required", "PR"),
|
||||
("user_interaction", "UI"),
|
||||
("scope", "S"),
|
||||
("confidentiality", "C"),
|
||||
("integrity", "I"),
|
||||
("availability", "A"),
|
||||
]:
|
||||
val = parsed_cvss.get(key)
|
||||
if val:
|
||||
cvss_parts.append(f"{prefix}:{val}")
|
||||
text.append("CVSS Vector: ", style=FIELD_STYLE)
|
||||
text.append("/".join(cvss_parts), style="dim")
|
||||
text.append("/".join(cvss_parts), style=DIM_STYLE)
|
||||
|
||||
if description:
|
||||
text.append("\n\n")
|
||||
@@ -191,6 +190,40 @@ class CreateVulnerabilityReportRenderer(BaseToolRenderer):
|
||||
text.append("\n")
|
||||
text.append(technical_analysis)
|
||||
|
||||
parsed_locations = (
|
||||
parse_code_locations_xml(code_locations_xml) if code_locations_xml else None
|
||||
)
|
||||
if parsed_locations:
|
||||
text.append("\n\n")
|
||||
text.append("Code Locations", style=FIELD_STYLE)
|
||||
for i, loc in enumerate(parsed_locations):
|
||||
text.append("\n\n")
|
||||
text.append(f" Location {i + 1}: ", style=DIM_STYLE)
|
||||
text.append(loc.get("file", "unknown"), style=FILE_STYLE)
|
||||
start = loc.get("start_line")
|
||||
end = loc.get("end_line")
|
||||
if start is not None:
|
||||
if end and end != start:
|
||||
text.append(f":{start}-{end}", style=LINE_STYLE)
|
||||
else:
|
||||
text.append(f":{start}", style=LINE_STYLE)
|
||||
if loc.get("label"):
|
||||
text.append(f"\n {loc['label']}", style=LABEL_STYLE)
|
||||
if loc.get("snippet"):
|
||||
text.append("\n ")
|
||||
text.append(loc["snippet"], style=CODE_STYLE)
|
||||
if loc.get("fix_before") or loc.get("fix_after"):
|
||||
text.append("\n ")
|
||||
text.append("Fix:", style=DIM_STYLE)
|
||||
if loc.get("fix_before"):
|
||||
text.append("\n ")
|
||||
text.append("- ", style=BEFORE_STYLE)
|
||||
text.append(loc["fix_before"], style=BEFORE_STYLE)
|
||||
if loc.get("fix_after"):
|
||||
text.append("\n ")
|
||||
text.append("+ ", style=AFTER_STYLE)
|
||||
text.append(loc["fix_after"], style=AFTER_STYLE)
|
||||
|
||||
if poc_description:
|
||||
text.append("\n\n")
|
||||
text.append("PoC Description", style=FIELD_STYLE)
|
||||
|
||||
@@ -531,16 +531,30 @@ class VulnerabilityDetailScreen(ModalScreen): # type: ignore[misc]
|
||||
lines.append("```")
|
||||
|
||||
# Code Analysis
|
||||
if vuln.get("code_file") or vuln.get("code_diff"):
|
||||
if vuln.get("code_locations"):
|
||||
lines.extend(["", "## Code Analysis", ""])
|
||||
if vuln.get("code_file"):
|
||||
lines.append(f"**File:** {vuln['code_file']}")
|
||||
for i, loc in enumerate(vuln["code_locations"]):
|
||||
file_ref = loc.get("file", "unknown")
|
||||
line_ref = ""
|
||||
if loc.get("start_line") is not None:
|
||||
if loc.get("end_line") and loc["end_line"] != loc["start_line"]:
|
||||
line_ref = f" (lines {loc['start_line']}-{loc['end_line']})"
|
||||
else:
|
||||
line_ref = f" (line {loc['start_line']})"
|
||||
lines.append(f"**Location {i + 1}:** `{file_ref}`{line_ref}")
|
||||
if loc.get("label"):
|
||||
lines.append(f" {loc['label']}")
|
||||
if loc.get("snippet"):
|
||||
lines.append(f"```\n{loc['snippet']}\n```")
|
||||
if loc.get("fix_before") or loc.get("fix_after"):
|
||||
lines.append("**Suggested Fix:**")
|
||||
lines.append("```diff")
|
||||
if loc.get("fix_before"):
|
||||
lines.extend(f"- {line}" for line in loc["fix_before"].splitlines())
|
||||
if loc.get("fix_after"):
|
||||
lines.extend(f"+ {line}" for line in loc["fix_after"].splitlines())
|
||||
lines.append("```")
|
||||
lines.append("")
|
||||
if vuln.get("code_diff"):
|
||||
lines.append("**Changes:**")
|
||||
lines.append("```diff")
|
||||
lines.append(vuln["code_diff"])
|
||||
lines.append("```")
|
||||
|
||||
# Remediation
|
||||
if vuln.get("remediation_steps"):
|
||||
|
||||
@@ -163,32 +163,34 @@ def format_vulnerability_report(report: dict[str, Any]) -> Text: # noqa: PLR091
|
||||
text.append("\n")
|
||||
text.append(poc_script_code, style="dim")
|
||||
|
||||
code_file = report.get("code_file")
|
||||
if code_file:
|
||||
code_locations = report.get("code_locations")
|
||||
if code_locations:
|
||||
text.append("\n\n")
|
||||
text.append("Code File: ", style=field_style)
|
||||
text.append(code_file)
|
||||
|
||||
code_before = report.get("code_before")
|
||||
if code_before:
|
||||
text.append("\n\n")
|
||||
text.append("Code Before", style=field_style)
|
||||
text.append("\n")
|
||||
text.append(code_before, style="dim")
|
||||
|
||||
code_after = report.get("code_after")
|
||||
if code_after:
|
||||
text.append("\n\n")
|
||||
text.append("Code After", style=field_style)
|
||||
text.append("\n")
|
||||
text.append(code_after, style="dim")
|
||||
|
||||
code_diff = report.get("code_diff")
|
||||
if code_diff:
|
||||
text.append("\n\n")
|
||||
text.append("Code Diff", style=field_style)
|
||||
text.append("\n")
|
||||
text.append(code_diff, style="dim")
|
||||
text.append("Code Locations", style=field_style)
|
||||
for i, loc in enumerate(code_locations):
|
||||
text.append("\n\n")
|
||||
text.append(f" Location {i + 1}: ", style="dim")
|
||||
text.append(loc.get("file", "unknown"), style="bold")
|
||||
start = loc.get("start_line")
|
||||
end = loc.get("end_line")
|
||||
if start is not None:
|
||||
if end and end != start:
|
||||
text.append(f":{start}-{end}")
|
||||
else:
|
||||
text.append(f":{start}")
|
||||
if loc.get("label"):
|
||||
text.append(f"\n {loc['label']}", style="italic dim")
|
||||
if loc.get("snippet"):
|
||||
text.append("\n ")
|
||||
text.append(loc["snippet"], style="dim")
|
||||
if loc.get("fix_before") or loc.get("fix_after"):
|
||||
text.append("\n Fix:")
|
||||
if loc.get("fix_before"):
|
||||
text.append("\n - ", style="dim")
|
||||
text.append(loc["fix_before"], style="dim")
|
||||
if loc.get("fix_after"):
|
||||
text.append("\n + ", style="dim")
|
||||
text.append(loc["fix_after"], style="dim")
|
||||
|
||||
remediation_steps = report.get("remediation_steps")
|
||||
if remediation_steps:
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from strix.config import Config
|
||||
from strix.config.config import resolve_llm_config
|
||||
|
||||
|
||||
class LLMConfig:
|
||||
@@ -10,7 +11,8 @@ class LLMConfig:
|
||||
timeout: int | None = None,
|
||||
scan_mode: str = "deep",
|
||||
):
|
||||
self.model_name = model_name or Config.get("strix_llm")
|
||||
resolved_model, self.api_key, self.api_base = resolve_llm_config()
|
||||
self.model_name = model_name or resolved_model
|
||||
|
||||
if not self.model_name:
|
||||
raise ValueError("STRIX_LLM environment variable must be set and not empty")
|
||||
|
||||
@@ -5,8 +5,8 @@ from typing import Any
|
||||
|
||||
import litellm
|
||||
|
||||
from strix.config import Config
|
||||
from strix.llm.utils import get_litellm_model_name, get_strix_api_base
|
||||
from strix.config.config import resolve_llm_config
|
||||
from strix.llm.utils import get_litellm_model_name
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -156,15 +156,7 @@ def check_duplicate(
|
||||
|
||||
comparison_data = {"candidate": candidate_cleaned, "existing_reports": existing_cleaned}
|
||||
|
||||
model_name = Config.get("strix_llm")
|
||||
api_key = Config.get("llm_api_key")
|
||||
api_base = (
|
||||
Config.get("llm_api_base")
|
||||
or Config.get("openai_api_base")
|
||||
or Config.get("litellm_base_url")
|
||||
or Config.get("ollama_api_base")
|
||||
or get_strix_api_base(model_name)
|
||||
)
|
||||
model_name, api_key, api_base = resolve_llm_config()
|
||||
|
||||
messages = [
|
||||
{"role": "system", "content": DEDUPE_SYSTEM_PROMPT},
|
||||
|
||||
@@ -15,7 +15,6 @@ from strix.llm.utils import (
|
||||
_truncate_to_first_function,
|
||||
fix_incomplete_tool_call,
|
||||
get_litellm_model_name,
|
||||
get_strix_api_base,
|
||||
parse_tool_invocations,
|
||||
)
|
||||
from strix.skills import load_skills
|
||||
@@ -206,18 +205,10 @@ class LLM:
|
||||
"stream_options": {"include_usage": True},
|
||||
}
|
||||
|
||||
if api_key := Config.get("llm_api_key"):
|
||||
args["api_key"] = api_key
|
||||
|
||||
api_base = (
|
||||
Config.get("llm_api_base")
|
||||
or Config.get("openai_api_base")
|
||||
or Config.get("litellm_base_url")
|
||||
or Config.get("ollama_api_base")
|
||||
or get_strix_api_base(self.config.model_name)
|
||||
)
|
||||
if api_base:
|
||||
args["api_base"] = api_base
|
||||
if self.config.api_key:
|
||||
args["api_key"] = self.config.api_key
|
||||
if self.config.api_base:
|
||||
args["api_base"] = self.config.api_base
|
||||
if self._supports_reasoning():
|
||||
args["reasoning_effort"] = self._reasoning_effort
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@ from typing import Any
|
||||
|
||||
import litellm
|
||||
|
||||
from strix.config import Config
|
||||
from strix.llm.utils import get_litellm_model_name, get_strix_api_base
|
||||
from strix.config.config import Config, resolve_llm_config
|
||||
from strix.llm.utils import get_litellm_model_name
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -106,14 +106,7 @@ def _summarize_messages(
|
||||
conversation = "\n".join(formatted)
|
||||
prompt = SUMMARY_PROMPT_TEMPLATE.format(conversation=conversation)
|
||||
|
||||
api_key = Config.get("llm_api_key")
|
||||
api_base = (
|
||||
Config.get("llm_api_base")
|
||||
or Config.get("openai_api_base")
|
||||
or Config.get("litellm_base_url")
|
||||
or Config.get("ollama_api_base")
|
||||
or get_strix_api_base(model)
|
||||
)
|
||||
_, api_key, api_base = resolve_llm_config()
|
||||
|
||||
try:
|
||||
litellm_model = get_litellm_model_name(model) or model
|
||||
|
||||
@@ -3,8 +3,6 @@ import re
|
||||
from typing import Any
|
||||
|
||||
|
||||
STRIX_API_BASE = "https://models.strix.ai/api/v1"
|
||||
|
||||
STRIX_PROVIDER_PREFIXES: dict[str, str] = {
|
||||
"claude-": "anthropic",
|
||||
"gpt-": "openai",
|
||||
@@ -12,18 +10,6 @@ STRIX_PROVIDER_PREFIXES: dict[str, str] = {
|
||||
}
|
||||
|
||||
|
||||
def is_strix_model(model_name: str | None) -> bool:
|
||||
"""Check if model uses strix/ prefix."""
|
||||
return bool(model_name and model_name.startswith("strix/"))
|
||||
|
||||
|
||||
def get_strix_api_base(model_name: str | None) -> str | None:
|
||||
"""Return Strix API base URL if using strix/ model, None otherwise."""
|
||||
if is_strix_model(model_name):
|
||||
return STRIX_API_BASE
|
||||
return None
|
||||
|
||||
|
||||
def get_litellm_model_name(model_name: str | None) -> str | None:
|
||||
"""Convert strix/ prefixed model to litellm-compatible provider/model format.
|
||||
|
||||
|
||||
@@ -130,7 +130,7 @@ def load_skills(skill_names: list[str]) -> dict[str, str]:
|
||||
if skill_path and (skills_dir / skill_path).exists():
|
||||
full_path = skills_dir / skill_path
|
||||
var_name = skill_name.split("/")[-1]
|
||||
content = full_path.read_text()
|
||||
content = full_path.read_text(encoding="utf-8")
|
||||
content = _FRONTMATTER_PATTERN.sub("", content).lstrip()
|
||||
skill_content[var_name] = content
|
||||
logger.info(f"Loaded skill: {skill_name} -> {var_name}")
|
||||
|
||||
@@ -89,10 +89,8 @@ class Tracer:
|
||||
endpoint: str | None = None,
|
||||
method: str | None = None,
|
||||
cve: str | None = None,
|
||||
code_file: str | None = None,
|
||||
code_before: str | None = None,
|
||||
code_after: str | None = None,
|
||||
code_diff: str | None = None,
|
||||
cwe: str | None = None,
|
||||
code_locations: list[dict[str, Any]] | None = None,
|
||||
) -> str:
|
||||
report_id = f"vuln-{len(self.vulnerability_reports) + 1:04d}"
|
||||
|
||||
@@ -127,14 +125,10 @@ class Tracer:
|
||||
report["method"] = method.strip()
|
||||
if cve:
|
||||
report["cve"] = cve.strip()
|
||||
if code_file:
|
||||
report["code_file"] = code_file.strip()
|
||||
if code_before:
|
||||
report["code_before"] = code_before.strip()
|
||||
if code_after:
|
||||
report["code_after"] = code_after.strip()
|
||||
if code_diff:
|
||||
report["code_diff"] = code_diff.strip()
|
||||
if cwe:
|
||||
report["cwe"] = cwe.strip()
|
||||
if code_locations:
|
||||
report["code_locations"] = code_locations
|
||||
|
||||
self.vulnerability_reports.append(report)
|
||||
logger.info(f"Added vulnerability report: {report_id} - {title}")
|
||||
@@ -323,6 +317,7 @@ class Tracer:
|
||||
("Endpoint", report.get("endpoint")),
|
||||
("Method", report.get("method")),
|
||||
("CVE", report.get("cve")),
|
||||
("CWE", report.get("cwe")),
|
||||
]
|
||||
cvss_score = report.get("cvss")
|
||||
if cvss_score is not None:
|
||||
@@ -353,15 +348,33 @@ class Tracer:
|
||||
f.write(f"{report['poc_script_code']}\n")
|
||||
f.write("```\n\n")
|
||||
|
||||
if report.get("code_file") or report.get("code_diff"):
|
||||
if report.get("code_locations"):
|
||||
f.write("## Code Analysis\n\n")
|
||||
if report.get("code_file"):
|
||||
f.write(f"**File:** {report['code_file']}\n\n")
|
||||
if report.get("code_diff"):
|
||||
f.write("**Changes:**\n")
|
||||
f.write("```diff\n")
|
||||
f.write(f"{report['code_diff']}\n")
|
||||
f.write("```\n\n")
|
||||
for i, loc in enumerate(report["code_locations"]):
|
||||
prefix = f"**Location {i + 1}:**"
|
||||
file_ref = loc.get("file", "unknown")
|
||||
line_ref = ""
|
||||
if loc.get("start_line") is not None:
|
||||
if loc.get("end_line") and loc["end_line"] != loc["start_line"]:
|
||||
line_ref = f" (lines {loc['start_line']}-{loc['end_line']})"
|
||||
else:
|
||||
line_ref = f" (line {loc['start_line']})"
|
||||
f.write(f"{prefix} `{file_ref}`{line_ref}\n")
|
||||
if loc.get("label"):
|
||||
f.write(f" {loc['label']}\n")
|
||||
if loc.get("snippet"):
|
||||
f.write(f" ```\n {loc['snippet']}\n ```\n")
|
||||
if loc.get("fix_before") or loc.get("fix_after"):
|
||||
f.write("\n **Suggested Fix:**\n")
|
||||
f.write("```diff\n")
|
||||
if loc.get("fix_before"):
|
||||
for line in loc["fix_before"].splitlines():
|
||||
f.write(f"- {line}\n")
|
||||
if loc.get("fix_after"):
|
||||
for line in loc["fix_after"].splitlines():
|
||||
f.write(f"+ {line}\n")
|
||||
f.write("```\n")
|
||||
f.write("\n")
|
||||
|
||||
if report.get("remediation_steps"):
|
||||
f.write("## Remediation\n\n")
|
||||
|
||||
@@ -48,7 +48,7 @@ def _load_xml_schema(path: Path) -> Any:
|
||||
if not path.exists():
|
||||
return None
|
||||
try:
|
||||
content = path.read_text()
|
||||
content = path.read_text(encoding="utf-8")
|
||||
|
||||
content = _process_dynamic_content(content)
|
||||
|
||||
|
||||
@@ -1,8 +1,120 @@
|
||||
import contextlib
|
||||
import re
|
||||
from pathlib import PurePosixPath
|
||||
from typing import Any
|
||||
|
||||
from strix.tools.registry import register_tool
|
||||
|
||||
|
||||
_CVSS_FIELDS = (
|
||||
"attack_vector",
|
||||
"attack_complexity",
|
||||
"privileges_required",
|
||||
"user_interaction",
|
||||
"scope",
|
||||
"confidentiality",
|
||||
"integrity",
|
||||
"availability",
|
||||
)
|
||||
|
||||
|
||||
def parse_cvss_xml(xml_str: str) -> dict[str, str] | None:
|
||||
if not xml_str or not xml_str.strip():
|
||||
return None
|
||||
result = {}
|
||||
for field in _CVSS_FIELDS:
|
||||
match = re.search(rf"<{field}>(.*?)</{field}>", xml_str, re.DOTALL)
|
||||
if match:
|
||||
result[field] = match.group(1).strip()
|
||||
return result if result else None
|
||||
|
||||
|
||||
def parse_code_locations_xml(xml_str: str) -> list[dict[str, Any]] | None:
|
||||
if not xml_str or not xml_str.strip():
|
||||
return None
|
||||
locations = []
|
||||
for loc_match in re.finditer(r"<location>(.*?)</location>", xml_str, re.DOTALL):
|
||||
loc: dict[str, Any] = {}
|
||||
loc_content = loc_match.group(1)
|
||||
for field in (
|
||||
"file",
|
||||
"start_line",
|
||||
"end_line",
|
||||
"snippet",
|
||||
"label",
|
||||
"fix_before",
|
||||
"fix_after",
|
||||
):
|
||||
field_match = re.search(rf"<{field}>(.*?)</{field}>", loc_content, re.DOTALL)
|
||||
if field_match:
|
||||
raw = field_match.group(1)
|
||||
value = (
|
||||
raw.strip("\n")
|
||||
if field in ("snippet", "fix_before", "fix_after")
|
||||
else raw.strip()
|
||||
)
|
||||
if field in ("start_line", "end_line"):
|
||||
with contextlib.suppress(ValueError, TypeError):
|
||||
loc[field] = int(value)
|
||||
elif value:
|
||||
loc[field] = value
|
||||
if loc.get("file") and loc.get("start_line") is not None:
|
||||
locations.append(loc)
|
||||
return locations if locations else None
|
||||
|
||||
|
||||
def _validate_file_path(path: str) -> str | None:
|
||||
if not path or not path.strip():
|
||||
return "file path cannot be empty"
|
||||
p = PurePosixPath(path)
|
||||
if p.is_absolute():
|
||||
return f"file path must be relative, got absolute: '{path}'"
|
||||
if ".." in p.parts:
|
||||
return f"file path must not contain '..': '{path}'"
|
||||
return None
|
||||
|
||||
|
||||
def _validate_code_locations(locations: list[dict[str, Any]]) -> list[str]:
|
||||
errors = []
|
||||
for i, loc in enumerate(locations):
|
||||
path_err = _validate_file_path(loc.get("file", ""))
|
||||
if path_err:
|
||||
errors.append(f"code_locations[{i}]: {path_err}")
|
||||
start = loc.get("start_line")
|
||||
if not isinstance(start, int) or start < 1:
|
||||
errors.append(f"code_locations[{i}]: start_line must be a positive integer")
|
||||
end = loc.get("end_line")
|
||||
if end is None:
|
||||
errors.append(f"code_locations[{i}]: end_line is required")
|
||||
elif not isinstance(end, int) or end < 1:
|
||||
errors.append(f"code_locations[{i}]: end_line must be a positive integer")
|
||||
elif isinstance(start, int) and end < start:
|
||||
errors.append(f"code_locations[{i}]: end_line ({end}) must be >= start_line ({start})")
|
||||
return errors
|
||||
|
||||
|
||||
def _extract_cve(cve: str) -> str:
|
||||
match = re.search(r"CVE-\d{4}-\d{4,}", cve)
|
||||
return match.group(0) if match else cve.strip()
|
||||
|
||||
|
||||
def _validate_cve(cve: str) -> str | None:
|
||||
if not re.match(r"^CVE-\d{4}-\d{4,}$", cve):
|
||||
return f"invalid CVE format: '{cve}' (expected 'CVE-YYYY-NNNNN')"
|
||||
return None
|
||||
|
||||
|
||||
def _extract_cwe(cwe: str) -> str:
|
||||
match = re.search(r"CWE-\d+", cwe)
|
||||
return match.group(0) if match else cwe.strip()
|
||||
|
||||
|
||||
def _validate_cwe(cwe: str) -> str | None:
|
||||
if not re.match(r"^CWE-\d+$", cwe):
|
||||
return f"invalid CWE format: '{cwe}' (expected 'CWE-NNN')"
|
||||
return None
|
||||
|
||||
|
||||
def calculate_cvss_and_severity(
|
||||
attack_vector: str,
|
||||
attack_complexity: str,
|
||||
@@ -87,7 +199,7 @@ def _validate_cvss_parameters(**kwargs: str) -> list[str]:
|
||||
|
||||
|
||||
@register_tool(sandbox_execution=False)
|
||||
def create_vulnerability_report(
|
||||
def create_vulnerability_report( # noqa: PLR0912
|
||||
title: str,
|
||||
description: str,
|
||||
impact: str,
|
||||
@@ -96,23 +208,12 @@ def create_vulnerability_report(
|
||||
poc_description: str,
|
||||
poc_script_code: str,
|
||||
remediation_steps: str,
|
||||
# CVSS Breakdown Components
|
||||
attack_vector: str,
|
||||
attack_complexity: str,
|
||||
privileges_required: str,
|
||||
user_interaction: str,
|
||||
scope: str,
|
||||
confidentiality: str,
|
||||
integrity: str,
|
||||
availability: str,
|
||||
# Optional fields
|
||||
cvss_breakdown: str,
|
||||
endpoint: str | None = None,
|
||||
method: str | None = None,
|
||||
cve: str | None = None,
|
||||
code_file: str | None = None,
|
||||
code_before: str | None = None,
|
||||
code_after: str | None = None,
|
||||
code_diff: str | None = None,
|
||||
cwe: str | None = None,
|
||||
code_locations: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
validation_errors = _validate_required_fields(
|
||||
title=title,
|
||||
@@ -125,32 +226,32 @@ def create_vulnerability_report(
|
||||
remediation_steps=remediation_steps,
|
||||
)
|
||||
|
||||
validation_errors.extend(
|
||||
_validate_cvss_parameters(
|
||||
attack_vector=attack_vector,
|
||||
attack_complexity=attack_complexity,
|
||||
privileges_required=privileges_required,
|
||||
user_interaction=user_interaction,
|
||||
scope=scope,
|
||||
confidentiality=confidentiality,
|
||||
integrity=integrity,
|
||||
availability=availability,
|
||||
)
|
||||
)
|
||||
parsed_cvss = parse_cvss_xml(cvss_breakdown)
|
||||
if not parsed_cvss:
|
||||
validation_errors.append("cvss: could not parse CVSS breakdown XML")
|
||||
else:
|
||||
validation_errors.extend(_validate_cvss_parameters(**parsed_cvss))
|
||||
|
||||
parsed_locations = parse_code_locations_xml(code_locations) if code_locations else None
|
||||
|
||||
if parsed_locations:
|
||||
validation_errors.extend(_validate_code_locations(parsed_locations))
|
||||
if cve:
|
||||
cve = _extract_cve(cve)
|
||||
cve_err = _validate_cve(cve)
|
||||
if cve_err:
|
||||
validation_errors.append(cve_err)
|
||||
if cwe:
|
||||
cwe = _extract_cwe(cwe)
|
||||
cwe_err = _validate_cwe(cwe)
|
||||
if cwe_err:
|
||||
validation_errors.append(cwe_err)
|
||||
|
||||
if validation_errors:
|
||||
return {"success": False, "message": "Validation failed", "errors": validation_errors}
|
||||
|
||||
cvss_score, severity, cvss_vector = calculate_cvss_and_severity(
|
||||
attack_vector,
|
||||
attack_complexity,
|
||||
privileges_required,
|
||||
user_interaction,
|
||||
scope,
|
||||
confidentiality,
|
||||
integrity,
|
||||
availability,
|
||||
)
|
||||
assert parsed_cvss is not None
|
||||
cvss_score, severity, cvss_vector = calculate_cvss_and_severity(**parsed_cvss)
|
||||
|
||||
try:
|
||||
from strix.telemetry.tracer import get_global_tracer
|
||||
@@ -196,17 +297,6 @@ def create_vulnerability_report(
|
||||
"reason": dedupe_result.get("reason", ""),
|
||||
}
|
||||
|
||||
cvss_breakdown = {
|
||||
"attack_vector": attack_vector,
|
||||
"attack_complexity": attack_complexity,
|
||||
"privileges_required": privileges_required,
|
||||
"user_interaction": user_interaction,
|
||||
"scope": scope,
|
||||
"confidentiality": confidentiality,
|
||||
"integrity": integrity,
|
||||
"availability": availability,
|
||||
}
|
||||
|
||||
report_id = tracer.add_vulnerability_report(
|
||||
title=title,
|
||||
description=description,
|
||||
@@ -218,14 +308,12 @@ def create_vulnerability_report(
|
||||
poc_script_code=poc_script_code,
|
||||
remediation_steps=remediation_steps,
|
||||
cvss=cvss_score,
|
||||
cvss_breakdown=cvss_breakdown,
|
||||
cvss_breakdown=parsed_cvss,
|
||||
endpoint=endpoint,
|
||||
method=method,
|
||||
cve=cve,
|
||||
code_file=code_file,
|
||||
code_before=code_before,
|
||||
code_after=code_after,
|
||||
code_diff=code_diff,
|
||||
cwe=cwe,
|
||||
code_locations=parsed_locations,
|
||||
)
|
||||
|
||||
return {
|
||||
|
||||
@@ -14,7 +14,7 @@ DO NOT USE:
|
||||
- For reporting multiple vulnerabilities at once. Use a separate create_vulnerability_report for each vulnerability.
|
||||
- To re-report a vulnerability that was already reported (even with different details)
|
||||
|
||||
White-box requirement (when you have access to the code): You MUST include code_file, code_before, code_after, and code_diff. These must contain the actual code (before/after) and a complete, apply-able unified diff.
|
||||
White-box requirement (when you have access to the code): You MUST include code_locations with nested XML, including fix_before/fix_after on locations where a fix is proposed.
|
||||
|
||||
DEDUPLICATION: If this tool returns with success=false and mentions a duplicate, DO NOT attempt to re-submit. The vulnerability has already been reported. Move on to testing other areas.
|
||||
|
||||
@@ -30,7 +30,7 @@ Professional, customer-facing report rules (PDF-ready):
|
||||
6) Impact
|
||||
7) Remediation
|
||||
8) Evidence (optional request/response excerpts, etc.) in the technical analysis field.
|
||||
- Numbered steps are allowed ONLY within the proof of concept. Elsewhere, use clear, concise paragraphs suitable for customer-facing reports.
|
||||
- Numbered steps are allowed ONLY within the proof of concept and remediation sections. Elsewhere, use clear, concise paragraphs suitable for customer-facing reports.
|
||||
- Language must be precise and non-vague; avoid hedging.
|
||||
</description>
|
||||
<parameters>
|
||||
@@ -58,51 +58,28 @@ Professional, customer-facing report rules (PDF-ready):
|
||||
<parameter name="remediation_steps" type="string" required="true">
|
||||
<description>Specific, actionable steps to fix the vulnerability</description>
|
||||
</parameter>
|
||||
<parameter name="attack_vector" type="string" required="true">
|
||||
<description>CVSS Attack Vector - How the vulnerability is exploited:
|
||||
N = Network (remotely exploitable)
|
||||
A = Adjacent (same network segment)
|
||||
L = Local (local access required)
|
||||
P = Physical (physical access required)</description>
|
||||
</parameter>
|
||||
<parameter name="attack_complexity" type="string" required="true">
|
||||
<description>CVSS Attack Complexity - Conditions beyond attacker's control:
|
||||
L = Low (no special conditions)
|
||||
H = High (special conditions must exist)</description>
|
||||
</parameter>
|
||||
<parameter name="privileges_required" type="string" required="true">
|
||||
<description>CVSS Privileges Required - Level of privileges needed:
|
||||
N = None (no privileges needed)
|
||||
L = Low (basic user privileges)
|
||||
H = High (admin privileges)</description>
|
||||
</parameter>
|
||||
<parameter name="user_interaction" type="string" required="true">
|
||||
<description>CVSS User Interaction - Does exploit require user action:
|
||||
N = None (no user interaction needed)
|
||||
R = Required (user must perform some action)</description>
|
||||
</parameter>
|
||||
<parameter name="scope" type="string" required="true">
|
||||
<description>CVSS Scope - Can the vulnerability affect resources beyond its security scope:
|
||||
U = Unchanged (only affects the vulnerable component)
|
||||
C = Changed (affects resources beyond vulnerable component)</description>
|
||||
</parameter>
|
||||
<parameter name="confidentiality" type="string" required="true">
|
||||
<description>CVSS Confidentiality Impact - Impact to confidentiality:
|
||||
N = None (no impact)
|
||||
L = Low (some information disclosure)
|
||||
H = High (all information disclosed)</description>
|
||||
</parameter>
|
||||
<parameter name="integrity" type="string" required="true">
|
||||
<description>CVSS Integrity Impact - Impact to integrity:
|
||||
N = None (no impact)
|
||||
L = Low (data can be modified but scope is limited)
|
||||
H = High (total loss of integrity)</description>
|
||||
</parameter>
|
||||
<parameter name="availability" type="string" required="true">
|
||||
<description>CVSS Availability Impact - Impact to availability:
|
||||
N = None (no impact)
|
||||
L = Low (reduced performance or interruptions)
|
||||
H = High (total loss of availability)</description>
|
||||
<parameter name="cvss_breakdown" type="string" required="true">
|
||||
<description>CVSS 3.1 base score breakdown as nested XML. All 8 metrics are required.
|
||||
|
||||
Each metric element contains a single uppercase letter value:
|
||||
- attack_vector: N (Network), A (Adjacent), L (Local), P (Physical)
|
||||
- attack_complexity: L (Low), H (High)
|
||||
- privileges_required: N (None), L (Low), H (High)
|
||||
- user_interaction: N (None), R (Required)
|
||||
- scope: U (Unchanged), C (Changed)
|
||||
- confidentiality: N (None), L (Low), H (High)
|
||||
- integrity: N (None), L (Low), H (High)
|
||||
- availability: N (None), L (Low), H (High)</description>
|
||||
<format>
|
||||
<attack_vector>N</attack_vector>
|
||||
<attack_complexity>L</attack_complexity>
|
||||
<privileges_required>N</privileges_required>
|
||||
<user_interaction>N</user_interaction>
|
||||
<scope>U</scope>
|
||||
<confidentiality>H</confidentiality>
|
||||
<integrity>H</integrity>
|
||||
<availability>N</availability>
|
||||
</format>
|
||||
</parameter>
|
||||
<parameter name="endpoint" type="string" required="false">
|
||||
<description>API endpoint(s) or URL path(s) (e.g., "/api/login") - for web vulnerabilities, or Git repository path(s) - for code vulnerabilities</description>
|
||||
@@ -111,19 +88,93 @@ H = High (total loss of availability)</description>
|
||||
<description>HTTP method(s) (GET, POST, etc.) - for web vulnerabilities.</description>
|
||||
</parameter>
|
||||
<parameter name="cve" type="string" required="false">
|
||||
<description>CVE identifier (e.g., "CVE-2024-1234"). Make sure it's a valid CVE. Use web search or vulnerability databases to make sure it's a valid CVE number.</description>
|
||||
<description>CVE identifier. ONLY the ID, e.g. "CVE-2024-1234" — do NOT include the name or description.
|
||||
You must be 100% certain of the exact CVE number. Do NOT guess, approximate, or hallucinate CVE IDs.
|
||||
If web_search is available, use it to verify the CVE exists and matches this vulnerability. If you cannot verify it, omit this field entirely.</description>
|
||||
</parameter>
|
||||
<parameter name="code_file" type="string" required="false">
|
||||
<description>MANDATORY for white-box testing: exact affected source file path(s).</description>
|
||||
<parameter name="cwe" type="string" required="false">
|
||||
<description>CWE identifier. ONLY the ID, e.g. "CWE-89" — do NOT include the name or parenthetical (wrong: "CWE-89 (SQL Injection)").
|
||||
|
||||
You must be 100% certain of the exact CWE number. Do NOT guess or approximate.
|
||||
If web_search is available and you are unsure, use it to look up the correct CWE. If you cannot be certain, omit this field entirely.
|
||||
Always prefer the most specific child CWE over a broad parent.
|
||||
For example, use CWE-89 instead of CWE-74, or CWE-78 instead of CWE-77.
|
||||
|
||||
Reference (ID only — names here are just for your reference, do NOT include them in the value):
|
||||
- Injection: CWE-79 XSS, CWE-89 SQLi, CWE-78 OS Command Injection, CWE-94 Code Injection, CWE-77 Command Injection
|
||||
- Auth/Access: CWE-287 Improper Authentication, CWE-862 Missing Authorization, CWE-863 Incorrect Authorization, CWE-306 Missing Authentication for Critical Function, CWE-639 Authorization Bypass Through User-Controlled Key
|
||||
- Web: CWE-352 CSRF, CWE-918 SSRF, CWE-601 Open Redirect, CWE-434 Unrestricted Upload of File with Dangerous Type
|
||||
- Memory: CWE-787 Out-of-bounds Write, CWE-125 Out-of-bounds Read, CWE-416 Use After Free, CWE-120 Classic Buffer Overflow
|
||||
- Data: CWE-502 Deserialization of Untrusted Data, CWE-22 Path Traversal, CWE-611 XXE
|
||||
- Crypto/Config: CWE-798 Use of Hard-coded Credentials, CWE-327 Use of Broken or Risky Cryptographic Algorithm, CWE-311 Missing Encryption of Sensitive Data, CWE-916 Password Hash With Insufficient Computational Effort
|
||||
|
||||
Do NOT use broad/parent CWEs like CWE-74, CWE-20, CWE-200, CWE-284, or CWE-693.</description>
|
||||
</parameter>
|
||||
<parameter name="code_before" type="string" required="false">
|
||||
<description>MANDATORY for white-box testing: actual vulnerable code snippet(s) copied verbatim from the repository.</description>
|
||||
</parameter>
|
||||
<parameter name="code_after" type="string" required="false">
|
||||
<description>MANDATORY for white-box testing: corrected code snippet(s) exactly as they should appear after the fix.</description>
|
||||
</parameter>
|
||||
<parameter name="code_diff" type="string" required="false">
|
||||
<description>MANDATORY for white-box testing: unified diff showing the code changes. Must be a complete, apply-able unified diff (git format) covering all affected files, with proper file headers, line numbers, and sufficient context.</description>
|
||||
<parameter name="code_locations" type="string" required="false">
|
||||
<description>Nested XML list of code locations where the vulnerability exists. MANDATORY for white-box testing.
|
||||
|
||||
CRITICAL — HOW fix_before/fix_after WORK:
|
||||
fix_before and fix_after are LITERAL BLOCK-LEVEL REPLACEMENTS used directly for GitHub/GitLab PR suggestion blocks. When a reviewer clicks "Accept suggestion", the platform replaces the EXACT lines from start_line to end_line with the fix_after content. This means:
|
||||
|
||||
1. fix_before MUST be an EXACT, VERBATIM copy of the source code at lines start_line through end_line. Same whitespace, same indentation, same line breaks. If fix_before does not match the actual file content character-for-character, the suggestion will be wrong or will corrupt the code when accepted.
|
||||
|
||||
2. fix_after is the COMPLETE replacement for that entire block. It replaces ALL lines from start_line to end_line. It can be more lines, fewer lines, or the same number of lines as fix_before.
|
||||
|
||||
3. start_line and end_line define the EXACT line range being replaced. They must precisely cover the lines in fix_before — no more, no less. If the vulnerable code spans lines 45-48, then start_line=45 and end_line=48, and fix_before must contain all 4 lines exactly as they appear in the file.
|
||||
|
||||
MULTI-PART FIXES:
|
||||
Many fixes require changes in multiple non-contiguous parts of a file (e.g., adding an import at the top AND changing code lower down), or across multiple files. Since each fix_before/fix_after pair covers ONE contiguous block, you MUST create SEPARATE location entries for each part of the fix:
|
||||
|
||||
- Each location covers one contiguous block of lines to change
|
||||
- Use the label field to describe how each part relates to the overall fix (e.g., "Add import for parameterized query library", "Replace string interpolation with parameterized query")
|
||||
- Order fix locations logically: primary fix first (where the vulnerability manifests), then supporting changes (imports, config, etc.)
|
||||
|
||||
COMMON MISTAKES TO AVOID:
|
||||
- Do NOT guess line numbers. Read the file and verify the exact lines before reporting.
|
||||
- Do NOT paraphrase or reformat code in fix_before. It must be a verbatim copy.
|
||||
- Do NOT set start_line=end_line when the vulnerable code spans multiple lines. Cover the full range.
|
||||
- Do NOT put an import addition and a code change in the same fix_before/fix_after if they are not on adjacent lines. Split them into separate locations.
|
||||
- Do NOT include lines outside the vulnerable/fixed code in fix_before just to "pad" the range.
|
||||
- Do NOT duplicate changes across locations. Each location's fix_after must ONLY contain changes for its own line range. Never repeat a change that is already covered by another location.
|
||||
|
||||
Each location element fields:
|
||||
- file (REQUIRED): Path relative to repository root. No leading slash, no absolute paths, no ".." traversal.
|
||||
Correct: "src/db/queries.ts" or "app/routes/users.py"
|
||||
Wrong: "/workspace/repo/src/db/queries.ts", "./src/db/queries.ts", "../../etc/passwd"
|
||||
- start_line (REQUIRED): Exact 1-based line number where the vulnerable/affected code begins. Must be a positive integer. You must be certain of this number — go back and verify against the actual file content if needed.
|
||||
- end_line (REQUIRED): Exact 1-based line number where the vulnerable/affected code ends. Must be >= start_line. Set equal to start_line ONLY if the code is truly on a single line.
|
||||
- snippet (optional): The actual source code at this location, copied verbatim from the file.
|
||||
- label (optional): Short role description for this location. For multi-part fixes, use this to explain the purpose of each change (e.g., "Add import for escape utility", "Sanitize user input before SQL query").
|
||||
- fix_before (optional): The vulnerable code to be replaced — VERBATIM copy of lines start_line through end_line. Must match the actual source character-for-character including whitespace and indentation.
|
||||
- fix_after (optional): The corrected code that replaces the entire fix_before block. Must be syntactically valid and ready to apply as a direct replacement.
|
||||
|
||||
Locations without fix_before/fix_after are informational context (e.g. showing the source of tainted data).
|
||||
Locations with fix_before/fix_after are actionable fixes (used directly for PR suggestion blocks).</description>
|
||||
<format>
|
||||
<location>
|
||||
<file>src/db/queries.ts</file>
|
||||
<start_line>42</start_line>
|
||||
<end_line>45</end_line>
|
||||
<snippet>const query = (
|
||||
`SELECT * FROM users ` +
|
||||
`WHERE id = ${id}`
|
||||
);</snippet>
|
||||
<label>Unsanitized input used in SQL query (sink)</label>
|
||||
<fix_before>const query = (
|
||||
`SELECT * FROM users ` +
|
||||
`WHERE id = ${id}`
|
||||
);</fix_before>
|
||||
<fix_after>const query = 'SELECT * FROM users WHERE id = $1';
|
||||
const result = await db.query(query, [id]);</fix_after>
|
||||
</location>
|
||||
<location>
|
||||
<file>src/routes/users.ts</file>
|
||||
<start_line>15</start_line>
|
||||
<end_line>15</end_line>
|
||||
<snippet>const id = req.params.id</snippet>
|
||||
<label>User input from request parameter (source)</label>
|
||||
</location>
|
||||
</format>
|
||||
</parameter>
|
||||
</parameters>
|
||||
<returns type="Dict[str, Any]">
|
||||
@@ -177,7 +228,6 @@ Impact validation:
|
||||
- Use a controlled internal endpoint (or a benign endpoint that returns a distinct marker) to demonstrate that the request is performed by the server, not the client.
|
||||
- If the application follows redirects, validate whether an allowlisted URL can redirect to a disallowed destination, and whether the redirected-to destination is still fetched.</parameter>
|
||||
<parameter=poc_script_code>import json
|
||||
import sys
|
||||
import time
|
||||
from urllib.parse import urljoin
|
||||
|
||||
@@ -262,16 +312,58 @@ if __name__ == "__main__":
|
||||
|
||||
6. Monitoring and alerting
|
||||
- Log and alert on preview attempts to unusual destinations, repeated failures, high-frequency requests, or attempts to access blocked ranges.</parameter>
|
||||
<parameter=attack_vector>N</parameter>
|
||||
<parameter=attack_complexity>L</parameter>
|
||||
<parameter=privileges_required>L</parameter>
|
||||
<parameter=user_interaction>N</parameter>
|
||||
<parameter=scope>C</parameter>
|
||||
<parameter=confidentiality>H</parameter>
|
||||
<parameter=integrity>H</parameter>
|
||||
<parameter=availability>L</parameter>
|
||||
<parameter=cvss_breakdown>
|
||||
<attack_vector>N</attack_vector>
|
||||
<attack_complexity>L</attack_complexity>
|
||||
<privileges_required>L</privileges_required>
|
||||
<user_interaction>N</user_interaction>
|
||||
<scope>C</scope>
|
||||
<confidentiality>H</confidentiality>
|
||||
<integrity>H</integrity>
|
||||
<availability>L</availability>
|
||||
</parameter>
|
||||
<parameter=endpoint>/api/v1/link-preview</parameter>
|
||||
<parameter=method>POST</parameter>
|
||||
<parameter=cwe>CWE-918</parameter>
|
||||
<parameter=code_locations>
|
||||
<location>
|
||||
<file>src/services/link-preview.ts</file>
|
||||
<start_line>45</start_line>
|
||||
<end_line>48</end_line>
|
||||
<snippet> const options = { timeout: 5000 };
|
||||
const response = await fetch(userUrl, options);
|
||||
const html = await response.text();
|
||||
return extractMetadata(html);</snippet>
|
||||
<label>Unvalidated user URL passed to server-side fetch (sink)</label>
|
||||
<fix_before> const options = { timeout: 5000 };
|
||||
const response = await fetch(userUrl, options);
|
||||
const html = await response.text();
|
||||
return extractMetadata(html);</fix_before>
|
||||
<fix_after> const validated = await validateAndResolveUrl(userUrl);
|
||||
if (!validated) throw new ForbiddenError('URL not allowed');
|
||||
const options = { timeout: 5000 };
|
||||
const response = await fetch(validated, options);
|
||||
const html = await response.text();
|
||||
return extractMetadata(html);</fix_after>
|
||||
</location>
|
||||
<location>
|
||||
<file>src/services/link-preview.ts</file>
|
||||
<start_line>2</start_line>
|
||||
<end_line>2</end_line>
|
||||
<snippet>import { extractMetadata } from '../utils/html';</snippet>
|
||||
<label>Add import for URL validation utility</label>
|
||||
<fix_before>import { extractMetadata } from '../utils/html';</fix_before>
|
||||
<fix_after>import { extractMetadata } from '../utils/html';
|
||||
import { validateAndResolveUrl } from '../utils/url-validator';</fix_after>
|
||||
</location>
|
||||
<location>
|
||||
<file>src/routes/api/v1/links.ts</file>
|
||||
<start_line>12</start_line>
|
||||
<end_line>12</end_line>
|
||||
<snippet>const userUrl = req.body.url</snippet>
|
||||
<label>User-controlled URL from request body (source)</label>
|
||||
</location>
|
||||
</parameter>
|
||||
</function>
|
||||
</examples>
|
||||
</tool>
|
||||
|
||||
Reference in New Issue
Block a user