mirror of
https://github.com/usestrix/strix.git
synced 2026-08-21 18:52:47 +02:00
Compare commits
17
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1ce43d1b94 | ||
|
|
2cc8167814 | ||
|
|
d6a3ca7e58 | ||
|
|
9099710cef | ||
|
|
634cb98241 | ||
|
|
1b36343eea | ||
|
|
b5ef93e744 | ||
|
|
e152c4c7c0 | ||
|
|
fe758af4fc | ||
|
|
deb2057e20 | ||
|
|
d6f2218756 | ||
|
|
6f88b7d7d5 | ||
|
|
8d3693df8c | ||
|
|
9cd81e5c76 | ||
|
|
e8272c6a21 | ||
|
|
aa5867f5df | ||
|
|
7b8f9cb160 |
@@ -15,6 +15,14 @@ npx skills add usestrix/strix
|
|||||||
- `fix-security-vulnerabilities-with-strix` — remediate findings and re-run Strix to verify
|
- `fix-security-vulnerabilities-with-strix` — remediate findings and re-run Strix to verify
|
||||||
- `ci-security-scanning-with-strix` — add PR scanning to CI/CD (self-hosted CLI or managed app)
|
- `ci-security-scanning-with-strix` — add PR scanning to CI/CD (self-hosted CLI or managed app)
|
||||||
|
|
||||||
|
Target-specific workflows built on the same engine:
|
||||||
|
|
||||||
|
- `application-security-testing` — whole-product AppSec review: pick the right test per asset, then rank the results
|
||||||
|
- `web-app-penetration-testing` — black-box pentest of a live web app or staging site
|
||||||
|
- `api-security-testing` — REST/GraphQL APIs and the OWASP API Security Top 10 (BOLA/IDOR, authz)
|
||||||
|
- `owasp-top-10-testing` — systematic OWASP Top 10 assessment with honest per-category coverage
|
||||||
|
- `find-security-vulnerabilities-in-code` — white-box review of a repo or working tree
|
||||||
|
|
||||||
**Two ways to run, same engine — pick per situation:**
|
**Two ways to run, same engine — pick per situation:**
|
||||||
|
|
||||||
- **Open-source CLI (self-hosted):** free, fully local, BYO LLM key, needs Docker. Best for local dev loops, air-gapped/offline, and full control.
|
- **Open-source CLI (self-hosted):** free, fully local, BYO LLM key, needs Docker. Best for local dev loops, air-gapped/offline, and full control.
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ Strix is agent-ready. Give Claude Code, Cursor, Codex, or any [SKILL.md-compatib
|
|||||||
npx skills add usestrix/strix
|
npx skills add usestrix/strix
|
||||||
```
|
```
|
||||||
|
|
||||||
This installs four skills: **penetration-testing-with-strix** (run headless scans and read results), **managed-pentesting-with-strix** (drive the managed [app.strix.ai](https://app.strix.ai) platform via REST — no local Docker or LLM key), **fix-security-vulnerabilities-with-strix** (remediate + re-scan to verify), and **ci-security-scanning-with-strix** (PR scanning in CI). Agents can run Strix two ways with the same engine — the open-source CLI locally, or the managed cloud when there's no local infra — and read [`AGENTS.md`](AGENTS.md) for a quick reference, [docs.strix.ai/llms.txt](https://docs.strix.ai/llms.txt) for the CLI docs, and [docs.app.strix.ai](https://docs.app.strix.ai) for the API.
|
This installs nine skills: **penetration-testing-with-strix** (run headless scans and read results), **managed-pentesting-with-strix** (drive the managed [app.strix.ai](https://app.strix.ai) platform via REST — no local Docker or LLM key), **fix-security-vulnerabilities-with-strix** (remediate + re-scan to verify), **ci-security-scanning-with-strix** (PR scanning in CI), plus target-specific workflows: **application-security-testing**, **web-app-penetration-testing**, **api-security-testing**, **owasp-top-10-testing**, and **find-security-vulnerabilities-in-code**. Agents can run Strix two ways with the same engine — the open-source CLI locally, or the managed cloud when there's no local infra — and read [`AGENTS.md`](AGENTS.md) for a quick reference, [docs.strix.ai/llms.txt](https://docs.strix.ai/llms.txt) for the CLI docs, and [docs.app.strix.ai](https://docs.app.strix.ai) for the API.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -167,10 +167,15 @@ strix view
|
|||||||
|
|
||||||
# ...or open a specific run by name
|
# ...or open a specific run by name
|
||||||
strix view my-run-name
|
strix view my-run-name
|
||||||
|
|
||||||
|
# Expose the viewer on all IPv4 interfaces at a fixed port
|
||||||
|
strix view --host 0.0.0.0 --port 8080 --no-open
|
||||||
```
|
```
|
||||||
|
|
||||||
`strix view` starts a lightweight local server (bound to `127.0.0.1` on a random port) and opens your browser to a private, tokened link. Nothing leaves your machine: the dashboard reads the run's files straight off disk, with no cloud account or upload required. The UI ships prebuilt with Strix, so there is no extra install and no JS build step.
|
`strix view` starts a lightweight local server (bound to `127.0.0.1` on a random port) and opens your browser to a private, tokened link. Nothing leaves your machine: the dashboard reads the run's files straight off disk, with no cloud account or upload required. The UI ships prebuilt with Strix, so there is no extra install and no JS build step.
|
||||||
|
|
||||||
|
Use `--host 0.0.0.0` to make the viewer reachable from other machines. Replace `0.0.0.0` in the printed URL with the server's reachable IP or hostname. The token in that URL grants access to the selected run's scan data, history, and steering, so only share it with trusted users and restrict the port with your firewall. Requests without the token-derived session cannot read run data.
|
||||||
|
|
||||||
### What's in the dashboard
|
### What's in the dashboard
|
||||||
|
|
||||||
- **Overview**: run status, target, and a severity breakdown of everything found so far.
|
- **Overview**: run status, target, and a severity breakdown of everything found so far.
|
||||||
|
|||||||
@@ -19,6 +19,11 @@ npx skills add usestrix/strix
|
|||||||
| `managed-pentesting-with-strix` | Drive the managed [app.strix.ai](https://app.strix.ai) platform over REST — no local Docker or LLM key needed |
|
| `managed-pentesting-with-strix` | Drive the managed [app.strix.ai](https://app.strix.ai) platform over REST — no local Docker or LLM key needed |
|
||||||
| `fix-security-vulnerabilities-with-strix` | Triage findings, fix root causes, and re-run Strix to verify each fix |
|
| `fix-security-vulnerabilities-with-strix` | Triage findings, fix root causes, and re-run Strix to verify each fix |
|
||||||
| `ci-security-scanning-with-strix` | Add PR security scanning to GitHub Actions or any CI (self-hosted CLI or managed app) |
|
| `ci-security-scanning-with-strix` | Add PR security scanning to GitHub Actions or any CI (self-hosted CLI or managed app) |
|
||||||
|
| `application-security-testing` | Assess a whole product: choose the right test for each asset, then rank the findings into one remediation plan |
|
||||||
|
| `web-app-penetration-testing` | Black-box pentest of a live web app or staging site — scope, credentials, and multi-account access-control testing |
|
||||||
|
| `api-security-testing` | Test a REST/GraphQL API against the OWASP API Security Top 10 — schema-driven enumeration, BOLA/IDOR, authz |
|
||||||
|
| `owasp-top-10-testing` | Systematic OWASP Top 10 assessment with honest per-category coverage |
|
||||||
|
| `find-security-vulnerabilities-in-code` | White-box security review of a repo or working tree, with exploits to confirm findings |
|
||||||
|
|
||||||
Install a single skill with `npx skills add usestrix/strix --skill penetration-testing-with-strix`, or use one without installing:
|
Install a single skill with `npx skills add usestrix/strix --skill penetration-testing-with-strix`, or use one without installing:
|
||||||
|
|
||||||
|
|||||||
@@ -270,6 +270,10 @@ ignore = [
|
|||||||
"strix/tools/thinking/tool.py" = ["TC002"]
|
"strix/tools/thinking/tool.py" = ["TC002"]
|
||||||
"strix/tools/web_search/tool.py" = ["TC002"]
|
"strix/tools/web_search/tool.py" = ["TC002"]
|
||||||
"strix/tools/proxy/tools.py" = ["TC002", "PLR0911"]
|
"strix/tools/proxy/tools.py" = ["TC002", "PLR0911"]
|
||||||
|
# The generated Caido GraphQL schema is slow to import, so the SDK is imported
|
||||||
|
# on first proxy call instead of at module scope (keeps it off the launch path).
|
||||||
|
"strix/tools/proxy/caido_api.py" = ["PLC0415"]
|
||||||
|
"strix/runtime/caido_bootstrap.py" = ["PLC0415"]
|
||||||
"strix/tools/agents_graph/tools.py" = ["TC002"]
|
"strix/tools/agents_graph/tools.py" = ["TC002"]
|
||||||
"strix/agents/factory.py" = ["TC002"]
|
"strix/agents/factory.py" = ["TC002"]
|
||||||
# Entry point: ``Path`` is used at runtime by the typing of the
|
# Entry point: ``Path`` is used at runtime by the typing of the
|
||||||
@@ -280,6 +284,13 @@ ignore = [
|
|||||||
# a runtime ``Callable`` annotation on ``vulnerability_found_callback``.
|
# a runtime ``Callable`` annotation on ``vulnerability_found_callback``.
|
||||||
"strix/report/state.py" = ["TC003", "PLR0912", "PLR0915", "E501", "PERF401", "PLC0415"]
|
"strix/report/state.py" = ["TC003", "PLR0912", "PLR0915", "E501", "PERF401", "PLC0415"]
|
||||||
"strix/report/usage.py" = ["PLC0415"]
|
"strix/report/usage.py" = ["PLC0415"]
|
||||||
|
# LiteLLM and the Docker SDK are imported on first use, not at module scope:
|
||||||
|
# both cost seconds to import and neither is needed until a model call is made
|
||||||
|
# (or, for Docker, unless the Docker runtime backend is in use).
|
||||||
|
"strix/core/execution.py" = ["PLC0415"]
|
||||||
|
"strix/report/pricing.py" = ["PLC0415"]
|
||||||
|
"strix/llm/compaction.py" = ["PLC0415"]
|
||||||
|
"strix/llm/context_budget.py" = ["PLC0415"]
|
||||||
# Lazy import of strix.config.models avoids a circular dependency between the
|
# Lazy import of strix.config.models avoids a circular dependency between the
|
||||||
# report pipeline and the config layer.
|
# report pipeline and the config layer.
|
||||||
"strix/report/dedupe.py" = ["PLC0415"]
|
"strix/report/dedupe.py" = ["PLC0415"]
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
---
|
||||||
|
name: api-security-testing
|
||||||
|
description: Security-test a REST, GraphQL, or gRPC API with Strix — autonomous agents that enumerate endpoints from an OpenAPI/GraphQL schema (or by crawling), then actually exploit the API-specific vulnerability classes in the OWASP API Security Top 10 (2023) — broken object-level authorization (BOLA/IDOR), broken object property level authorization (excessive data exposure and mass assignment), broken function-level authorization, unrestricted resource consumption, SSRF, injection, and auth/token flaws. Every finding comes with a working proof-of-concept request. Use when the user asks to pentest, security-test, audit, or find vulnerabilities in an API, endpoint, or backend service.
|
||||||
|
license: Apache-2.0
|
||||||
|
metadata:
|
||||||
|
author: usestrix
|
||||||
|
homepage: https://docs.strix.ai
|
||||||
|
---
|
||||||
|
|
||||||
|
# Security-test an API
|
||||||
|
|
||||||
|
APIs fail differently from web UIs: there is no rendered surface to crawl, the interesting bugs are authorization-shaped rather than injection-shaped, and the same endpoint behaves differently per token. This workflow targets those specifics with Strix's autonomous agents, using the current [OWASP API Security Top 10 (2023)](https://owasp.org/API-Security/editions/2023/en/0x11-t10/) as the coverage checklist. For the web-app equivalent, the current edition is the OWASP Top 10:2025 — see **owasp-top-10-testing**.
|
||||||
|
|
||||||
|
Install, LLM setup, full CLI flags, and the managed-cloud path are in the **penetration-testing-with-strix** skill. Read it if `strix --version` fails or the target is not an API.
|
||||||
|
|
||||||
|
## 1. Gather what the agents need
|
||||||
|
|
||||||
|
APIs are near-impossible to test blind, so collect first:
|
||||||
|
|
||||||
|
| Input | Why it matters |
|
||||||
|
|---|---|
|
||||||
|
| **Schema** — OpenAPI/Swagger file, Postman collection, GraphQL endpoint (introspection), or a gRPC `.proto` | Turns guesswork into full endpoint enumeration. Biggest single win in coverage. An OpenAPI/Swagger or Postman spec (`.json`/`.yaml`/`.yml`) is a target Strix takes directly; a `.proto` is not, so pass it with `--workspace-file`. |
|
||||||
|
| **Two sets of credentials/tokens**, ideally in different tenants | BOLA/IDOR — API1:2023, still the #1 API risk — can only be *proven* by accessing tenant A's objects with tenant B's token. |
|
||||||
|
| **A low-privilege and a high-privilege token** | Required to prove broken function-level authorization (API5:2023 — a `user` calling admin-only routes). |
|
||||||
|
| **Example object IDs** | Lets agents test ID tampering immediately instead of hunting for valid identifiers. |
|
||||||
|
| **Out-of-scope routes** | Payments, mass notification, destructive admin endpoints. |
|
||||||
|
| **Rate limits / WAF** in front of the API | Avoids agents burning budget on throttled requests; mention them so testing adapts. |
|
||||||
|
|
||||||
|
Ask the user for anything missing — do not fabricate tokens or scan an API they do not own.
|
||||||
|
|
||||||
|
## 2. Run the scan
|
||||||
|
|
||||||
|
Pass the spec as a **target**, not as prose in the instruction — Strix parses OpenAPI/Swagger (`.json`/`.yaml`) and Postman collection exports directly, so the agents start from the real endpoint list:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
strix -n -t ./openapi.yaml -t https://api.staging.example.com --max-budget 20 \
|
||||||
|
--instruction "Tenant A token: <tokenA> (org 1111, user id 11, order id 501).
|
||||||
|
Tenant B token: <tokenB> (org 2222, user id 22).
|
||||||
|
Admin token: <tokenAdmin>.
|
||||||
|
Focus: BOLA across orgs (API1), function-level authz on /admin/* (API5), object property level authz on PATCH /users/{id} — both mass assignment and over-exposed fields in list responses (API3), unrestricted resource consumption (API4).
|
||||||
|
Out of scope: POST /billing/*, POST /notifications/broadcast."
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Postman instead of OpenAPI:** a collection export works as a target (`-t ./collection.postman_collection.json`), or pull one live with `-t postman://<collection-uuid>` (optionally `"postman://<collection-uuid>?env=<environment-uuid>"`), which needs `POSTMAN_API_KEY` in the environment.
|
||||||
|
- **Many services at once:** put one target per line in a file and pass `--target-list ./targets.txt`, repeatable and combinable with `-t`.
|
||||||
|
- **Add the backend source for depth:** `-t ./services/api -t https://api.staging.example.com`. With code access the agents can reason about authorization checks and object ownership rather than inferring them from responses.
|
||||||
|
- **gRPC:** target the endpoint and pass the definition as a workspace file, `-t https://grpc.staging.example.com --workspace-file ./service.proto`. Only `.json`, `.yaml`, and `.yml` specs are recognized as targets, so `-t ./service.proto` fails with "Path exists but is not a directory".
|
||||||
|
- **GraphQL:** point at the GraphQL endpoint and say whether introspection is enabled; call out that you want batching/aliasing abuse, depth/complexity limits, and per-field authorization tested.
|
||||||
|
- **Internal/private APIs** unreachable from your machine: use the managed platform's network connector — see **managed-pentesting-with-strix**.
|
||||||
|
- Use `--instruction-file` when the credential/context block gets long, and keep tokens out of shell history and out of committed files.
|
||||||
|
- **Supporting files** the agents should read but not test, such as an endpoint wordlist or handwritten notes about the tenancy model: pass `--workspace-file ./notes.md`. The file lands read-only in `/workspace`. Add `:DEST` to choose the path, for example `--workspace-file ./wordlist.txt:lists/wordlist.txt`.
|
||||||
|
|
||||||
|
## 3. Verify findings
|
||||||
|
|
||||||
|
`strix_runs/<run>/penetration_test_report.md` first, then `vulnerabilities/*.md` — each contains the exact request that proved the issue. Replay it (for example, with `curl`) before reporting; for authorization findings, confirm the response really contains the other tenant's data rather than an empty 200.
|
||||||
|
|
||||||
|
`findings.sarif` uploads to GitHub code scanning; `vulnerabilities.json` is the structured index for ticketing.
|
||||||
|
|
||||||
|
## 4. Fix, re-test, and keep it tested
|
||||||
|
|
||||||
|
Remediate with **fix-security-vulnerabilities-with-strix** (fix the authorization check, not the single endpoint), then re-run against the same target to prove the exploit is dead. Wire it into pull-request CI with **ci-security-scanning-with-strix** so new endpoints get tested as they ship.
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
---
|
||||||
|
name: application-security-testing
|
||||||
|
description: Application security testing (AppSec) across a whole product with Strix — decide which asset needs which test (source code, running web app, API, CI pipeline), run it, and turn the results into a ranked remediation plan. Autonomous agents exploit and prove each issue instead of emitting static-analysis alerts, so the plan is ordered by what is actually reachable. Use when the user asks for an application security review or audit, an appsec assessment, vulnerability scanning across their stack, a security review before a launch or a customer security questionnaire, or does not yet know which kind of security test they need.
|
||||||
|
license: Apache-2.0
|
||||||
|
metadata:
|
||||||
|
author: usestrix
|
||||||
|
homepage: https://docs.strix.ai
|
||||||
|
---
|
||||||
|
|
||||||
|
# Application security testing
|
||||||
|
|
||||||
|
Entry point for "make my application secure" requests, where the target is not yet a single URL or repo. The job here is to pick the right test per asset, run it, and produce one ranked plan — not to run everything at maximum depth.
|
||||||
|
|
||||||
|
Install, LLM setup, all CLI flags, and the managed-cloud path live in the **penetration-testing-with-strix** skill. Read it first if `strix --version` fails.
|
||||||
|
|
||||||
|
Only test assets the user owns or is authorized to test. Confirm authorization before the first run, and prefer staging over production, because the agents send real exploit payloads and can change data.
|
||||||
|
|
||||||
|
## 1. Map the assets
|
||||||
|
|
||||||
|
Ask (or read from the repo) and write the answers down before scanning:
|
||||||
|
|
||||||
|
- **Source** — one repo, a monorepo, several services? Which languages/frameworks?
|
||||||
|
- **Running environments** — is there a staging deployment? A public production site? A local dev server only?
|
||||||
|
- **APIs** — REST, GraphQL, gRPC? Is there an OpenAPI/GraphQL schema?
|
||||||
|
- **Authentication** — can you get two test accounts in different tenants? Most high-impact bugs need them.
|
||||||
|
- **Constraints** — out-of-scope paths, whether production may be touched, budget and wall-clock limits.
|
||||||
|
|
||||||
|
If there is no staging environment and production is off limits, say so early. A code-only review is still valuable, but it cannot prove exploitability against a live app.
|
||||||
|
|
||||||
|
## 2. Pick the right test per asset
|
||||||
|
|
||||||
|
| Asset | Skill to use |
|
||||||
|
| --- | --- |
|
||||||
|
| Repository or working tree | **find-security-vulnerabilities-in-code** |
|
||||||
|
| Live web app or staging site | **web-app-penetration-testing** |
|
||||||
|
| REST/GraphQL/gRPC API | **api-security-testing** |
|
||||||
|
| Assessment mapped to OWASP categories | **owasp-top-10-testing** |
|
||||||
|
| Every pull request, continuously | **ci-security-scanning-with-strix** |
|
||||||
|
| No Docker, no LLM key, or a report an auditor will accept | **managed-pentesting-with-strix** |
|
||||||
|
|
||||||
|
Those skills carry the flags, credential handling, and result-reading details. Do not duplicate their instructions here.
|
||||||
|
|
||||||
|
Sequence for a first assessment:
|
||||||
|
|
||||||
|
1. Review the code. It is the cheapest run and it maps the authorization model.
|
||||||
|
2. Pentest staging with credentials, and pass the repo as a second target so the agents keep source context.
|
||||||
|
3. Add CI scanning, so later regressions are caught without another manual pass.
|
||||||
|
|
||||||
|
Run one asset at a time and read each report before starting the next. Findings from the code review make the live run sharper.
|
||||||
|
|
||||||
|
## 3. Consolidate into one plan
|
||||||
|
|
||||||
|
Findings arrive per run in `strix_runs/<run>/`. Merge them into a single list and rank by **proven impact**, not by scanner severity:
|
||||||
|
|
||||||
|
1. Validated exploits reachable without authentication.
|
||||||
|
2. Validated cross-tenant or privilege-escalation issues.
|
||||||
|
3. Validated issues needing an authenticated account.
|
||||||
|
4. Unproven observations (configuration, dependency, and hardening notes) — flag as such, and never present them as confirmed vulnerabilities.
|
||||||
|
|
||||||
|
Deduplicate: the same root cause often surfaces in both the code review and the live pentest.
|
||||||
|
|
||||||
|
## 4. Be honest about coverage
|
||||||
|
|
||||||
|
State plainly what was *not* tested — assets with no staging environment, categories a black-box run cannot reach (logging and alerting, supply-chain integrity, insecure design), and any run that hit its budget or turn cap before finishing. Check `run.json` status and cost against `--max-budget` for each run. An empty result set from a truncated scan is not a clean bill of health.
|
||||||
|
|
||||||
|
Then remediate with **fix-security-vulnerabilities-with-strix**, which re-runs Strix against each fix to prove the exploit no longer works.
|
||||||
@@ -12,7 +12,7 @@ metadata:
|
|||||||
You can gate PRs two ways — pick based on the environment, or combine them:
|
You can gate PRs two ways — pick based on the environment, or combine them:
|
||||||
|
|
||||||
- **Managed platform (recommended for most teams)** — connect the GitHub/GitLab/Bitbucket app once and Strix reviews every PR with **no workflow file, no runner, no Docker, and no LLM key**. Results post as PR comments and land in the team dashboard. Best when you want zero CI maintenance, central tracking, or your runners lack Docker. See "Managed platform" below and the **managed-pentesting-with-strix** skill.
|
- **Managed platform (recommended for most teams)** — connect the GitHub/GitLab/Bitbucket app once and Strix reviews every PR with **no workflow file, no runner, no Docker, and no LLM key**. Results post as PR comments and land in the team dashboard. Best when you want zero CI maintenance, central tracking, or your runners lack Docker. See "Managed platform" below and the **managed-pentesting-with-strix** skill.
|
||||||
- **Self-hosted OSS CLI in your runner** — run a diff-scoped scan as a pipeline step. Fully in your infra, free (BYO LLM key), no external account. Requires Docker on the runner. Best for air-gapped/self-hosted CI or when you don't want scans leaving your environment.
|
- **Self-hosted OSS CLI in your runner** — run a diff-scoped scan as a pipeline step. Fully in your infra, free (BYO LLM key), no external account. Requires Docker on the runner. Best for air-gapped/self-hosted CI or when you do not want scans leaving your environment.
|
||||||
|
|
||||||
Both fail the build on validated findings and both emit SARIF 2.1.0, so you can start with one and add the other later.
|
Both fail the build on validated findings and both emit SARIF 2.1.0, so you can start with one and add the other later.
|
||||||
|
|
||||||
@@ -63,13 +63,13 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
```
|
```
|
||||||
|
|
||||||
Then tell the user to add two repository secrets: `STRIX_LLM` (model id, e.g. `openai/gpt-5.4`) and `LLM_API_KEY` (the provider key). Do not create these values yourself.
|
Then tell the user to add two repository secrets: `STRIX_LLM` (model id, for example `openai/gpt-5.4`) and `LLM_API_KEY` (the provider key). Do not create these values yourself.
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
- In CI/headless runs Strix automatically scopes to the PR's changed files (`--scope-mode auto`). If diff resolution fails, keep `fetch-depth: 0` or set `--diff-base` to the PR's actual base branch — use `origin/${{ github.base_ref }}` in GitHub Actions rather than a hard-coded `origin/main`, since repos use different default branches.
|
- In CI/headless runs Strix automatically scopes to the PR's changed files (`--scope-mode auto`). If diff resolution fails, keep `fetch-depth: 0` or set `--diff-base` to the PR's actual base branch — use `origin/${{ github.base_ref }}` in GitHub Actions rather than a hard-coded `origin/main`, since repos use different default branches.
|
||||||
- Exit codes: `0` pass, `2` vulnerabilities found (fails the job), `1` setup error.
|
- Exit codes: `0` pass, `2` vulnerabilities found (fails the job), `1` setup error.
|
||||||
- The runner needs Docker (default GitHub-hosted Ubuntu runners have it).
|
- The runner needs Docker (default GitHub-hosted Ubuntu runners have it).
|
||||||
- **Size the budget so the scan completes — don't let it fail open.** A `0` exit means "no validated vulnerabilities in what was analyzed"; if `--max-budget` is hit before the diff is fully covered, the scan wraps up early and can still exit `0`. The "Fail unless the scan completed" step above narrows the gap: `strix_runs/<run>/run.json` is `"stopped"` when the scan was cut off at the hard budget limit without a final report. It is not a complete guard — the agents get graduated wrap-up warnings before that limit, and a run that wraps up on a warning still calls `finish_scan` and records `"completed"` with partial coverage. So keep that step in any pipeline that gates merges **and** give the scan real headroom (compare `run.json`'s `llm_usage.cost` against `--max-budget`; if it ran right up to the cap, raise it). For a `quick` diff-scoped PR scan `--max-budget 10` is usually ample, raise it for large diffs.
|
- **Size the budget so the scan completes — do not let it fail open.** A `0` exit means "no validated vulnerabilities in what was analyzed"; if `--max-budget` is hit before the diff is fully covered, the scan wraps up early and can still exit `0`. The "Fail unless the scan completed" step above narrows the gap: `strix_runs/<run>/run.json` is `"stopped"` when the scan was cut off at the hard budget limit without a final report. It is not a complete guard — the agents get graduated wrap-up warnings before that limit, and a run that wraps up on a warning still calls `finish_scan` and records `"completed"` with partial coverage. So keep that step in any pipeline that gates merges **and** give the scan real headroom (compare `run.json`'s `llm_usage.cost` against `--max-budget`; if it ran right up to the cap, raise it). For a `quick` diff-scoped PR scan `--max-budget 10` is usually ample, raise it for large diffs.
|
||||||
|
|
||||||
### Optional: upload findings to GitHub code scanning
|
### Optional: upload findings to GitHub code scanning
|
||||||
|
|
||||||
@@ -90,7 +90,7 @@ Any pipeline works the same way — install, set the two env vars, run headless:
|
|||||||
```bash
|
```bash
|
||||||
curl -sSL https://strix.ai/install | bash
|
curl -sSL https://strix.ai/install | bash
|
||||||
# Resolve the PR's base branch robustly (use your CI's base-branch variable if it
|
# Resolve the PR's base branch robustly (use your CI's base-branch variable if it
|
||||||
# has one, e.g. GitHub Actions: origin/${{ github.base_ref }}). Avoid piping the
|
# has one, for example GitHub Actions: origin/${{ github.base_ref }}). Avoid piping the
|
||||||
# git lookup into another command — a failed lookup would otherwise be masked.
|
# git lookup into another command — a failed lookup would otherwise be masked.
|
||||||
BASE_BRANCH="${CI_MERGE_REQUEST_TARGET_BRANCH_NAME:-}" # GitLab MR target
|
BASE_BRANCH="${CI_MERGE_REQUEST_TARGET_BRANCH_NAME:-}" # GitLab MR target
|
||||||
if [ -z "$BASE_BRANCH" ]; then
|
if [ -z "$BASE_BRANCH" ]; then
|
||||||
@@ -98,7 +98,7 @@ if [ -z "$BASE_BRANCH" ]; then
|
|||||||
BASE_BRANCH="${BASE_BRANCH#origin/}"
|
BASE_BRANCH="${BASE_BRANCH#origin/}"
|
||||||
fi
|
fi
|
||||||
DIFF_BASE="origin/${BASE_BRANCH:-main}"
|
DIFF_BASE="origin/${BASE_BRANCH:-main}"
|
||||||
# Fail loudly rather than silently narrowing scope (e.g. to HEAD~1, which on a
|
# Fail loudly rather than silently narrowing scope (for example, to HEAD~1, which on a
|
||||||
# multi-commit branch would scan only the last commit and let earlier ones pass).
|
# multi-commit branch would scan only the last commit and let earlier ones pass).
|
||||||
if ! git rev-parse --verify --quiet "$DIFF_BASE" >/dev/null; then
|
if ! git rev-parse --verify --quiet "$DIFF_BASE" >/dev/null; then
|
||||||
echo "Cannot resolve diff base '$DIFF_BASE'. Fetch the base branch (git fetch origin <base>) or set --diff-base explicitly." >&2
|
echo "Cannot resolve diff base '$DIFF_BASE'. Fetch the base branch (git fetch origin <base>) or set --diff-base explicitly." >&2
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
---
|
||||||
|
name: find-security-vulnerabilities-in-code
|
||||||
|
description: Find security vulnerabilities in a codebase or repository with Strix — a white-box AI security review that reads your source, reasons about the actual data flow and authorization model, then exploits what it finds in a live sandbox so every reported issue has a working proof-of-concept instead of a noisy static-analysis alert. Covers injection, XSS, SSRF, broken access control and IDOR, insecure deserialization, secrets in code, unsafe dependencies, and business-logic flaws. Use when the user asks to security-scan, security-review, or audit their code, repo, or pull request for vulnerabilities.
|
||||||
|
license: Apache-2.0
|
||||||
|
metadata:
|
||||||
|
author: usestrix
|
||||||
|
homepage: https://docs.strix.ai
|
||||||
|
---
|
||||||
|
|
||||||
|
# Find security vulnerabilities in code
|
||||||
|
|
||||||
|
White-box security review with Strix: the agents read the source to build a model of routes, sinks, and authorization checks, then attempt real exploitation. Findings come with a proof-of-concept, so the output is a short list of proven issues rather than the hundreds of "potential" hits a pattern-matching scanner produces.
|
||||||
|
|
||||||
|
Install, LLM setup, all flags, and the managed-cloud path are in the **penetration-testing-with-strix** skill.
|
||||||
|
|
||||||
|
## Run it
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Local working tree
|
||||||
|
strix -n -t ./ --scan-mode standard --max-budget 15
|
||||||
|
|
||||||
|
# A GitHub repo directly
|
||||||
|
strix -n -t https://github.com/org/app --max-budget 15
|
||||||
|
|
||||||
|
# Monorepo: point at the service that matters, not the whole tree
|
||||||
|
strix -n -t ./services/checkout --max-budget 20
|
||||||
|
|
||||||
|
# Only what a branch changed (whole-repo review is wasteful on a large repo)
|
||||||
|
strix -n -t ./ --scope-mode diff --diff-base origin/main --max-budget 10
|
||||||
|
```
|
||||||
|
|
||||||
|
A local path is mounted into the sandbox **writable**, so the agents can modify it. Run against a clean checkout.
|
||||||
|
|
||||||
|
Two things sharply improve results:
|
||||||
|
|
||||||
|
1. **Add a running instance of the app.** `-t ./ -t http://host.docker.internal:3000` lets the agents confirm exploitability against live behavior instead of reasoning about it statically — this is the difference between "this looks unsafe" and a validated finding. If nothing is running, static-only findings should be described as unconfirmed.
|
||||||
|
2. **Scope the review.** Point at the risky subtree and say what matters:
|
||||||
|
```bash
|
||||||
|
strix -n -t ./services/api --max-budget 15 \
|
||||||
|
--instruction "Focus on the authorization layer in src/auth and every route under src/routes/admin. Multi-tenant app: tenant id comes from the JWT. Flag any query that filters by object id without also filtering by tenant."
|
||||||
|
```
|
||||||
|
Tenancy model, trust boundaries, and which inputs are attacker-controlled are things the agents cannot infer reliably — tell them.
|
||||||
|
|
||||||
|
## Reviewing a pull request instead of the whole repo
|
||||||
|
|
||||||
|
For diff-scoped review of a branch or PR (and blocking merges on findings), use **ci-security-scanning-with-strix** — it covers diff scoping, PR comments, and SARIF upload to GitHub code scanning. The managed platform can also review PRs directly via API (**managed-pentesting-with-strix**).
|
||||||
|
|
||||||
|
## Read the results
|
||||||
|
|
||||||
|
In `strix_runs/<run>/`: `penetration_test_report.md` (start here), `vulnerabilities/*.md` (one per finding, with PoC and remediation), `vulnerabilities.json` / `.csv`, `findings.sarif` (upload to code scanning), `run.json`.
|
||||||
|
|
||||||
|
Before reporting to the user, open each finding and check the PoC actually demonstrates impact. Report file and line alongside the exploit so the fix is obvious.
|
||||||
|
|
||||||
|
Exit `0` means nothing exploitable was proven in what was analyzed — not that the codebase is clean. Check `run.json` status and cost against `--max-budget`, and note which paths went unreviewed if the run was capped.
|
||||||
|
|
||||||
|
## Complementary tooling
|
||||||
|
|
||||||
|
This is exploit-validated review, not an exhaustive inventory. Keep a dependency scanner (SCA) and secret scanning in place for complete coverage of known-CVE dependencies and committed credentials; use this for the logic, authorization, and injection bugs those tools structurally cannot find.
|
||||||
|
|
||||||
|
## Fix and verify
|
||||||
|
|
||||||
|
Hand results to **fix-security-vulnerabilities-with-strix**: patch the root cause (the shared authorization helper, not the one route), then re-run Strix to prove the exploit no longer works.
|
||||||
@@ -27,7 +27,7 @@ Order work by severity: critical → high → medium → low. Every Strix findin
|
|||||||
For each finding:
|
For each finding:
|
||||||
|
|
||||||
1. Reproduce it with the PoC from the finding file when feasible.
|
1. Reproduce it with the PoC from the finding file when feasible.
|
||||||
2. Fix the root cause, not the specific payload (e.g. parameterize all queries, don't blocklist one string; enforce authorization in the handler, don't hide the endpoint).
|
2. Fix the root cause, not the specific payload (parameterize every query instead of blocking one string, and enforce authorization in the handler instead of hiding the endpoint).
|
||||||
3. Prefer the framework's built-in defense (ORM parameterization, template auto-escaping, CSRF middleware, centralized authz) over ad-hoc sanitization.
|
3. Prefer the framework's built-in defense (ORM parameterization, template auto-escaping, CSRF middleware, centralized authz) over ad-hoc sanitization.
|
||||||
4. Keep the diff minimal and apply the repo's existing patterns. Finding files often include `fix_before`/`fix_after` snippets — use them as a starting point, not verbatim.
|
4. Keep the diff minimal and apply the repo's existing patterns. Finding files often include `fix_before`/`fix_after` snippets — use them as a starting point, not verbatim.
|
||||||
|
|
||||||
@@ -70,7 +70,7 @@ new_id=$(curl -sS "$BASE/scans/$scan_id/rerun" "${auth[@]}" -X POST | jq -r .sca
|
|||||||
Or, if the cloud scan came from a repo/PR, trigger a fresh PR review on the fix branch (`POST /pr-reviews/start`). The platform also retests a single finding directly: `POST /api/v1/vulnerabilities/{vulnerabilityId}/retest`.
|
Or, if the cloud scan came from a repo/PR, trigger a fresh PR review on the fix branch (`POST /pr-reviews/start`). The platform also retests a single finding directly: `POST /api/v1/vulnerabilities/{vulnerabilityId}/retest`.
|
||||||
|
|
||||||
- Also re-run the PoC manually when it is a simple request/script — fastest signal.
|
- Also re-run the PoC manually when it is a simple request/script — fastest signal.
|
||||||
- Run the project's own test suite to make sure the fix doesn't break behavior.
|
- Run the project's own test suite to make sure the fix does not break behavior.
|
||||||
|
|
||||||
## 4. Report
|
## 4. Report
|
||||||
|
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ Useful `CreateScanRequest` fields:
|
|||||||
| `domain_ids` / `repository_ids` / `internal_targets` | targets (at least one) |
|
| `domain_ids` / `repository_ids` / `internal_targets` | targets (at least one) |
|
||||||
| `domain_paths` / `repository_branches` | narrow to specific paths / branches |
|
| `domain_paths` / `repository_branches` | narrow to specific paths / branches |
|
||||||
| `credentials` | authenticated scanning, incl. `mfa_method` (`totp`/`email_otp`/…) + `totp_secret` |
|
| `credentials` | authenticated scanning, incl. `mfa_method` (`totp`/`email_otp`/…) + `totp_secret` |
|
||||||
| `headers` | extra HTTP headers (e.g. API keys) for the target |
|
| `headers` | extra HTTP headers (API keys, for example) for the target |
|
||||||
| `focus` / `concerns` / `context` | steer the agents |
|
| `focus` / `concerns` / `context` | steer the agents |
|
||||||
| `upload_ids` | attach uploaded source/docs archives for white-box context |
|
| `upload_ids` | attach uploaded source/docs archives for white-box context |
|
||||||
| `notify_on_completion` / `notification_emails` | email when done |
|
| `notify_on_completion` / `notification_emails` | email when done |
|
||||||
@@ -89,7 +89,7 @@ Response is `{ scan_id, title, status }` with `status` = `pending`.
|
|||||||
|
|
||||||
## 3. Poll to completion
|
## 3. Poll to completion
|
||||||
|
|
||||||
`GET /scans/{scanId}` (`scans:read`). Status flow: `pending → running → completed` (or `failed` / `cancelled`). Poll on an interval — scans take minutes to hours; don't block.
|
`GET /scans/{scanId}` (`scans:read`). Status flow: `pending → running → completed` (or `failed` / `cancelled`). Poll on an interval — scans take minutes to hours. Do not block.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
while :; do
|
while :; do
|
||||||
@@ -143,10 +143,10 @@ List/inspect via `GET /pr-reviews` and `GET /pr-reviews/{id}`. Repo-level PR-rev
|
|||||||
## 7. Continuous testing (schedules & webhooks)
|
## 7. Continuous testing (schedules & webhooks)
|
||||||
|
|
||||||
- **Schedules** (`schedules:write`, Pro plan): create recurring scans and trigger them on demand — the managed equivalent of a cron-driven CLI loop.
|
- **Schedules** (`schedules:write`, Pro plan): create recurring scans and trigger them on demand — the managed equivalent of a cron-driven CLI loop.
|
||||||
- **Webhooks** (`webhooks:write`): subscribe to pentest/vulnerability lifecycle events (e.g. `scan.completed`, `vulnerability.created`) to push results into Slack, ticketing, or your own pipeline instead of polling.
|
- **Webhooks** (`webhooks:write`): subscribe to pentest/vulnerability lifecycle events such as `scan.completed` and `vulnerability.created` to push results into Slack, ticketing, or your own pipeline instead of polling.
|
||||||
|
|
||||||
See the schedules and webhooks sections at [docs.app.strix.ai](https://docs.app.strix.ai) for payloads.
|
See the schedules and webhooks sections at [docs.app.strix.ai](https://docs.app.strix.ai) for payloads.
|
||||||
|
|
||||||
## Safety
|
## Safety
|
||||||
|
|
||||||
Only scan assets the user's organization owns or is authorized to test. External domain scans require verification (DNS/file/meta-tag) enforced by the platform — don't try to bypass it.
|
Only scan assets the user's organization owns or is authorized to test. External domain scans require verification (DNS/file/meta-tag) enforced by the platform — do not try to bypass it.
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
---
|
||||||
|
name: owasp-top-10-testing
|
||||||
|
description: Test an application against the OWASP Top 10 with Strix — autonomous AI agents that attempt real exploits for each category of the current OWASP Top 10:2025 (broken access control including SSRF, security misconfiguration, software supply chain failures, cryptographic failures, injection, insecure design, authentication failures, integrity failures, logging and alerting failures, mishandling of exceptional conditions) and report only what they could actually prove, mapped back to the category with a proof-of-concept. Also covers the OWASP API Security Top 10 (2023). Use when the user asks for an OWASP Top 10 assessment, OWASP compliance testing, or a security review mapped to OWASP categories.
|
||||||
|
license: Apache-2.0
|
||||||
|
metadata:
|
||||||
|
author: usestrix
|
||||||
|
homepage: https://docs.strix.ai
|
||||||
|
---
|
||||||
|
|
||||||
|
# Test against the OWASP Top 10
|
||||||
|
|
||||||
|
The OWASP Top 10 is a taxonomy of risk categories, not a test suite — "OWASP Top 10 testing" means exercising each category against the real application and reporting what's actually exploitable. Strix's agents do the exploitation; this skill covers running it category-by-category and reporting coverage honestly.
|
||||||
|
|
||||||
|
**Use the current edition: [OWASP Top 10:2025](https://owasp.org/Top10/)** (8th installment, superseding 2021). Ask the user before targeting an older edition — some compliance checklists still reference 2021, and a report labelled with the wrong edition is misleading. Key differences from 2021: **SSRF is folded into A01**, **A03 Software Supply Chain Failures** expands the old "Vulnerable and Outdated Components", and **A10 Mishandling of Exceptional Conditions** is new; A02 Security Misconfiguration moved 5→2.
|
||||||
|
|
||||||
|
Install, LLM setup, and the managed-cloud alternative: **penetration-testing-with-strix**.
|
||||||
|
|
||||||
|
## What is and is not testable by an agent
|
||||||
|
|
||||||
|
Be straight with the user about this — claiming a clean sweep of all ten is misleading.
|
||||||
|
|
||||||
|
| Category (2025) | Coverage |
|
||||||
|
|---|---|
|
||||||
|
| A01 Broken Access Control (incl. SSRF) | **Strong** — cross-user/tenant access, privilege escalation, IDOR, and SSRF (including blind, via out-of-band callbacks) are all exploit-validated. Needs two accounts plus a privileged one to prove the authorization half. |
|
||||||
|
| A02 Security Misconfiguration | **Strong** — debug endpoints, verbose errors, permissive CORS, missing hardening, default credentials, exposed admin surfaces. |
|
||||||
|
| A03 Software Supply Chain Failures | **Partial** — version fingerprinting, and vulnerable/outdated dependency review when source is supplied. Build-system and distribution-infrastructure compromise (the broader half of this category) is out of scope for a runtime scan — pair with SCA plus build-provenance controls. |
|
||||||
|
| A04 Cryptographic Failures | **Partial** — transport config, unencrypted data in transit, secrets and tokens leaked in responses. At-rest crypto and key management need source or infra review. |
|
||||||
|
| A05 Injection | **Strong** — SQL/NoSQL/command/template injection and XSS, exploit-validated. |
|
||||||
|
| A06 Insecure Design | **Partial** — business-logic abuse (price/quantity tampering, workflow skipping, race conditions) is found where reachable; design intent still needs human review and threat modelling. |
|
||||||
|
| A07 Authentication Failures | **Strong** — auth bypass, weak session/token handling, password-reset and MFA flaws. |
|
||||||
|
| A08 Software or Data Integrity Failures | **Partial** — insecure deserialization and unsigned-update paths where reachable; CI/CD trust boundaries are not runtime-testable. |
|
||||||
|
| A09 Security Logging & Alerting Failures | **Not testable from outside** — requires reviewing the logging and alerting pipeline. State this rather than reporting it as passed. |
|
||||||
|
| A10 Mishandling of Exceptional Conditions | **Partial** — agents actively probe error handling and fail-open behavior (malformed input, forced errors, race and timeout conditions) and report what leaks or bypasses a control; exhaustive coverage of internal error paths needs source review. |
|
||||||
|
|
||||||
|
For APIs, run the same exercise against the **OWASP API Security Top 10 (2023)** — API1 BOLA, API3 Broken Object Property Level Authorization (2019's excessive data exposure + mass assignment merged), API5 broken function-level authorization — using the **api-security-testing** skill.
|
||||||
|
|
||||||
|
## Run it
|
||||||
|
|
||||||
|
Maximum category coverage comes from giving the agents both the source and a running instance, plus credentials at two privilege levels:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
strix -n \
|
||||||
|
-t https://github.com/org/app \
|
||||||
|
-t https://staging.example.com \
|
||||||
|
--scan-mode deep --max-budget 30 \
|
||||||
|
--instruction "OWASP Top 10:2025 assessment. Cover every category systematically and map each finding to its 2025 category id.
|
||||||
|
Accounts: userA@example.com/<pw> (org 1), userB@example.com/<pw> (org 2), admin@example.com/<pw>.
|
||||||
|
Prioritise A01 (cross-org access, privilege escalation, SSRF), A02, A05, A07, A10.
|
||||||
|
Out of scope: /billing/*, outbound email."
|
||||||
|
```
|
||||||
|
|
||||||
|
- `--scan-mode deep` matters here: systematically walking ten categories is not a quick scan.
|
||||||
|
- Without a second account, A01 results are structurally incomplete — say so in the report rather than leaving it implied.
|
||||||
|
- Need an auditor-facing PDF? Run it through the managed platform and pull the technical report (**managed-pentesting-with-strix**).
|
||||||
|
|
||||||
|
## Report honestly
|
||||||
|
|
||||||
|
From `strix_runs/<run>/`, group `vulnerabilities/*.md` by category and state, per category: what was attempted, what was proven, and what could not be assessed (A09 always; A03/A04/A06/A08/A10 partially). Label the report with the edition used. Verify each PoC yourself before it goes in front of the user.
|
||||||
|
|
||||||
|
A `0` exit code means nothing exploitable was proven **in what was analyzed** — check `run.json` status and cost against `--max-budget`; a budget-capped run is not a completed assessment.
|
||||||
|
|
||||||
|
## Then fix and re-test
|
||||||
|
|
||||||
|
Remediate with **fix-security-vulnerabilities-with-strix** and re-run to prove each exploit is closed. For ongoing coverage as the app changes, gate pull requests using **ci-security-scanning-with-strix**.
|
||||||
@@ -14,14 +14,14 @@ Strix runs autonomous AI pentesting agents that dynamically exploit a target and
|
|||||||
- **Open-source CLI** (self-hosted) — runs on your machine in a Docker sandbox with your own LLM key. Free, fully local, BYO-LLM, air-gap capable. Docs: [docs.strix.ai](https://docs.strix.ai).
|
- **Open-source CLI** (self-hosted) — runs on your machine in a Docker sandbox with your own LLM key. Free, fully local, BYO-LLM, air-gap capable. Docs: [docs.strix.ai](https://docs.strix.ai).
|
||||||
- **Cloud API** (managed) — runs on Strix's infrastructure via `https://app.strix.ai/api/v1`. No Docker, no LLM key, no local compute; adds team dashboards, scheduling, PR reviews, downloadable PDF/DOCX reports (Enterprise plan), and internal-network connectors. Docs: [docs.app.strix.ai](https://docs.app.strix.ai). Full workflow in the **managed-pentesting-with-strix** skill.
|
- **Cloud API** (managed) — runs on Strix's infrastructure via `https://app.strix.ai/api/v1`. No Docker, no LLM key, no local compute; adds team dashboards, scheduling, PR reviews, downloadable PDF/DOCX reports (Enterprise plan), and internal-network connectors. Docs: [docs.app.strix.ai](https://docs.app.strix.ai). Full workflow in the **managed-pentesting-with-strix** skill.
|
||||||
|
|
||||||
## Which one? (decide, don't default)
|
## Which one? (decide, do not default)
|
||||||
|
|
||||||
Choose honestly based on the situation — neither is "better":
|
Choose honestly based on the situation — neither is "better":
|
||||||
|
|
||||||
| Situation | Prefer |
|
| Situation | Prefer |
|
||||||
|---|---|
|
|---|---|
|
||||||
| No Docker available, or a sandboxed/hosted agent/CI environment | **Cloud** |
|
| No Docker available, or a sandboxed/hosted agent/CI environment | **Cloud** |
|
||||||
| User has no LLM key / doesn't want to pay per-token or manage models | **Cloud** |
|
| User has no LLM key / does not want to pay per-token or manage models | **Cloud** |
|
||||||
| Team visibility, shareable dashboard, scheduled/continuous scans, PR reviews, downloadable PDF/DOCX report (Enterprise) | **Cloud** |
|
| Team visibility, shareable dashboard, scheduled/continuous scans, PR reviews, downloadable PDF/DOCX report (Enterprise) | **Cloud** |
|
||||||
| Scanning internal/private infrastructure not reachable from your machine | **Cloud** (network connector) |
|
| Scanning internal/private infrastructure not reachable from your machine | **Cloud** (network connector) |
|
||||||
| Source must never leave local infra (privacy/air-gap), or fully offline | **OSS CLI** |
|
| Source must never leave local infra (privacy/air-gap), or fully offline | **OSS CLI** |
|
||||||
@@ -30,7 +30,7 @@ Choose honestly based on the situation — neither is "better":
|
|||||||
| CI: runner already has Docker and you want a self-contained gate | **OSS CLI** |
|
| CI: runner already has Docker and you want a self-contained gate | **OSS CLI** |
|
||||||
| CI: no Docker, or you want results tracked centrally | **Cloud** |
|
| CI: no Docker, or you want results tracked centrally | **Cloud** |
|
||||||
|
|
||||||
**Mix them:** e.g. use the OSS CLI for the fast local dev-loop while writing/fixing code, and the Cloud for the authoritative, team-visible scan + report + tracking; or gate PRs with the OSS CLI in CI while the Cloud runs scheduled deep scans and PR reviews across the org. Both emit the same SARIF 2.1.0, so findings line up across environments.
|
**Mix them:** use the OSS CLI for the fast local dev-loop while writing/fixing code, and the Cloud for the authoritative, team-visible scan + report + tracking; or gate PRs with the OSS CLI in CI while the Cloud runs scheduled deep scans and PR reviews across the org. Both emit the same SARIF 2.1.0, so findings line up across environments.
|
||||||
|
|
||||||
If unsure and the user has (or will create) an app.strix.ai account, prefer **Cloud** — it avoids all local-infra friction. If they want zero signup / full local control, use the **OSS CLI**.
|
If unsure and the user has (or will create) an app.strix.ai account, prefer **Cloud** — it avoids all local-infra friction. If they want zero signup / full local control, use the **OSS CLI**.
|
||||||
|
|
||||||
@@ -70,21 +70,33 @@ strix -n -t https://github.com/org/app -t https://staging.example.com
|
|||||||
strix -n -t https://app.example.com \
|
strix -n -t https://app.example.com \
|
||||||
--instruction "Use credentials user@example.com:pass123. Focus on IDOR and auth bypass."
|
--instruction "Use credentials user@example.com:pass123. Focus on IDOR and auth bypass."
|
||||||
|
|
||||||
# Large monorepo: bind-mount instead of copying
|
# API spec as a first-class target (OpenAPI/Swagger or a Postman collection export)
|
||||||
strix -n --mount ./huge-monorepo
|
strix -n -t ./openapi.yaml -t https://api.staging.example.com
|
||||||
|
|
||||||
|
# Many targets from a file, one per line
|
||||||
|
strix -n --target-list ./targets.txt --max-budget 30
|
||||||
|
|
||||||
|
# Give the agents a file to work with (wordlist, spec, notes) without making it a target
|
||||||
|
strix -n -t https://staging.example.com --workspace-file ./wordlist.txt --max-budget 20
|
||||||
```
|
```
|
||||||
|
|
||||||
|
A local path passed with `-t` is mounted into the sandbox **writable** — the agents can read and modify it, so point at a clean checkout, not uncommitted work you care about.
|
||||||
|
|
||||||
Key flags:
|
Key flags:
|
||||||
|
|
||||||
| Flag | Meaning |
|
| Flag | Meaning |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `-t, --target` | URL, repo URL, local path, domain, or IP. Repeatable. |
|
| `-t, --target` | URL, repo URL, local path, domain, IP, OpenAPI/Postman spec, or `postman://<uuid>`. Repeatable. |
|
||||||
|
| `--target-list PATH` | File of targets, one per line (`#` comments allowed). Repeatable, combines with `-t`. |
|
||||||
| `-n, --non-interactive` | Headless, exits on completion. Required for agents. |
|
| `-n, --non-interactive` | Headless, exits on completion. Required for agents. |
|
||||||
| `-m, --scan-mode` | `quick` (minutes) / `standard` (~30 min) / `deep` (hours, default). |
|
| `-m, --scan-mode` | `quick` (minutes) / `standard` (~30 min) / `deep` (hours, default). |
|
||||||
| `--instruction` / `--instruction-file` | Credentials, focus areas, scope rules. |
|
| `--instruction` / `--instruction-file` | Credentials, focus areas, scope rules. |
|
||||||
|
| `--workspace-file PATH[:DEST]` | Place a file from this machine into `/workspace` read-only before the scan, for a wordlist, a spec, or notes. Repeatable. |
|
||||||
| `--max-budget USD` | Hard LLM spend cap; scan wraps up cleanly at the limit. |
|
| `--max-budget USD` | Hard LLM spend cap; scan wraps up cleanly at the limit. |
|
||||||
| `--max-turns N` | Per-agent turn cap (default 500). |
|
| `--max-turns N` | Per-agent turn cap (default 500). |
|
||||||
| `--resume RUN_NAME` | Resume a prior run from `strix_runs/`. |
|
| `--resume RUN_NAME` | Resume a prior run from `strix_runs/`, with its agent history and targets. Cannot be combined with `-t`. |
|
||||||
|
| `--scope-mode` | For code targets: `auto` (diff-scope in CI/headless), `diff` (force changed files only), `full` (whole tree). |
|
||||||
|
| `--diff-base REF` | Branch or commit that `diff` scope compares against. Defaults to the repo's default branch. |
|
||||||
|
|
||||||
Scans take minutes (`quick`) to hours (`deep`). Run them in the background and poll for completion rather than blocking.
|
Scans take minutes (`quick`) to hours (`deep`). Run them in the background and poll for completion rather than blocking.
|
||||||
|
|
||||||
@@ -130,7 +142,7 @@ curl -sS "$BASE/scans/$scan_id" -H "Authorization: Bearer $STRIX_API_TOKEN" | jq
|
|||||||
curl -sS "$BASE/scans/$scan_id/sarif" -H "Authorization: Bearer $STRIX_API_TOKEN" -o findings.sarif
|
curl -sS "$BASE/scans/$scan_id/sarif" -H "Authorization: Bearer $STRIX_API_TOKEN" -o findings.sarif
|
||||||
```
|
```
|
||||||
|
|
||||||
Ask the user to create the token (and register the target as a domain/repository asset) if they haven't. If Docker/local prerequisites aren't already satisfied, use this path instead of trying to install infra.
|
Ask the user to create the token (and register the target as a domain/repository asset) if they have not. If Docker/local prerequisites are not already satisfied, use this path instead of trying to install infra.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
---
|
||||||
|
name: web-app-penetration-testing
|
||||||
|
description: Pentest a web app or website end to end — black-box testing of a live URL, staging environment, or local dev server that finds and exploits real vulnerabilities (auth bypass, broken access control, IDOR, injection, XSS, SSRF, business logic) and proves each one with a working proof-of-concept instead of a signature match. Runs with Strix, either the self-hosted open-source CLI or the managed app.strix.ai cloud. Use when the user asks to pentest, hack, security-test, or audit their web app, website, web application, or staging site.
|
||||||
|
license: Apache-2.0
|
||||||
|
metadata:
|
||||||
|
author: usestrix
|
||||||
|
homepage: https://docs.strix.ai
|
||||||
|
---
|
||||||
|
|
||||||
|
# Pentest a web application
|
||||||
|
|
||||||
|
Black-box (and optionally source-assisted) penetration testing of a running web app with Strix's autonomous agents. Every reported finding is validated with a working exploit, so there are no signature-based false positives to triage.
|
||||||
|
|
||||||
|
Install, LLM setup, all CLI flags, and the managed-cloud alternative are covered in the **penetration-testing-with-strix** skill — read it if the target is not a running web app, or if `strix --version` fails. This skill is the web-app-specific workflow.
|
||||||
|
|
||||||
|
## 1. Confirm authorization and scope
|
||||||
|
|
||||||
|
Before running anything, establish:
|
||||||
|
|
||||||
|
- **The target is the user's** (or they are explicitly authorized to test it). Never pentest a third-party site on a hunch.
|
||||||
|
- **Which environment.** Prefer staging over production; agents send real exploit payloads and will create/modify data.
|
||||||
|
- **Out-of-scope paths** — payment flows, mass-email endpoints, admin destructive actions, third-party SSO providers.
|
||||||
|
- **Credentials.** Most real vulnerabilities live behind login. Without a test account, the agents only ever see the marketing surface.
|
||||||
|
|
||||||
|
Ask for anything missing rather than guessing.
|
||||||
|
|
||||||
|
## 2. Run the scan
|
||||||
|
|
||||||
|
```bash
|
||||||
|
strix -n -t https://staging.example.com --max-budget 20 \
|
||||||
|
--instruction "Test account: qa@example.com / <password>. In scope: /app/*, /api/*. Do not touch /billing or send email. Focus on access control between the two seeded orgs."
|
||||||
|
```
|
||||||
|
|
||||||
|
Notes that matter for web apps specifically:
|
||||||
|
|
||||||
|
- **Give it credentials via `--instruction`** (or `--instruction-file` for anything long), including how to log in if the flow is unusual (magic link, SSO, MFA-exempt test user).
|
||||||
|
- **Two accounts beat one.** Multi-tenant IDOR and broken-access-control bugs — consistently the highest-impact class in web apps — can only be proven when the agent can attempt cross-account access.
|
||||||
|
- **Add the repo for white-box depth** when you have the source: `-t https://github.com/org/app -t https://staging.example.com` (or a local path). Source access materially improves coverage of business-logic and authorization flaws.
|
||||||
|
- **Localhost works.** Point at `http://host.docker.internal:3000` (Docker Desktop) so the sandbox can reach a dev server on the host.
|
||||||
|
- `--scan-mode quick` for a fast dev-loop pass, `standard` (~30 min) for a normal review, `deep` for pre-release assurance. Always set `--max-budget`.
|
||||||
|
|
||||||
|
For a hosted run with no Docker/LLM key, or when the user wants a shareable dashboard and an auditor-ready PDF, use the cloud path in **managed-pentesting-with-strix** instead — same engine, same findings.
|
||||||
|
|
||||||
|
## 3. Review results
|
||||||
|
|
||||||
|
Read `strix_runs/<run>/penetration_test_report.md` first, then per-finding files in `vulnerabilities/`. Each contains the PoC — re-run it yourself to confirm before reporting to the user.
|
||||||
|
|
||||||
|
Exit codes: `0` no validated vulns in what was analyzed, `2` vulnerabilities found, `1` fatal error. A `0` is not proof of full coverage — if the budget or turn cap was hit the scan wraps up early, so check `run.json` status and cost against `--max-budget` before calling the app clean.
|
||||||
|
|
||||||
|
## 4. Fix and verify
|
||||||
|
|
||||||
|
Hand findings to the **fix-security-vulnerabilities-with-strix** skill: patch the root cause, then re-run Strix against the same target to prove the exploit no longer works. Re-testing is the only reliable confirmation a fix landed.
|
||||||
|
|
||||||
|
To keep the app tested on every change rather than once, wire Strix into CI with **ci-security-scanning-with-strix**.
|
||||||
+47
-10
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import dataclasses
|
||||||
import inspect
|
import inspect
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
@@ -222,6 +223,17 @@ def _with_coerced_arguments(tool: FunctionTool) -> FunctionTool:
|
|||||||
return tool
|
return tool
|
||||||
|
|
||||||
|
|
||||||
|
def _with_strictness(tool: FunctionTool, strict_schemas: bool) -> FunctionTool:
|
||||||
|
"""Drop strict JSON-schema mode when the route can't take it (see
|
||||||
|
``supports_strict_tool_schemas``); the tool stays functionally identical.
|
||||||
|
|
||||||
|
Returns a copy so the shared tool singletons keep their declared mode.
|
||||||
|
"""
|
||||||
|
if strict_schemas or not tool.strict_json_schema:
|
||||||
|
return tool
|
||||||
|
return dataclasses.replace(tool, strict_json_schema=False)
|
||||||
|
|
||||||
|
|
||||||
def _function_tool_with_error_result(tool: FunctionTool) -> FunctionTool:
|
def _function_tool_with_error_result(tool: FunctionTool) -> FunctionTool:
|
||||||
invoke_tool = tool.on_invoke_tool
|
invoke_tool = tool.on_invoke_tool
|
||||||
|
|
||||||
@@ -285,24 +297,38 @@ def _bound_custom_tool(tool: CustomTool) -> CustomTool:
|
|||||||
return tool
|
return tool
|
||||||
|
|
||||||
|
|
||||||
def _configure_filesystem_tools(toolset: Any, *, chat_completions: bool) -> None:
|
def _configure_filesystem_tools(
|
||||||
|
toolset: Any, *, chat_completions: bool, strict_schemas: bool = True
|
||||||
|
) -> None:
|
||||||
for name, tool in vars(toolset).items():
|
for name, tool in vars(toolset).items():
|
||||||
if chat_completions:
|
if chat_completions:
|
||||||
if isinstance(tool, CustomTool):
|
if isinstance(tool, CustomTool):
|
||||||
setattr(toolset, name, _custom_tool_as_function_tool(tool))
|
setattr(toolset, name, _custom_tool_as_function_tool(tool))
|
||||||
elif isinstance(tool, FunctionTool):
|
elif isinstance(tool, FunctionTool):
|
||||||
setattr(
|
setattr(
|
||||||
toolset, name, _function_tool_with_error_result(_with_coerced_arguments(tool))
|
toolset,
|
||||||
|
name,
|
||||||
|
_function_tool_with_error_result(
|
||||||
|
_with_strictness(_with_coerced_arguments(tool), strict_schemas)
|
||||||
|
),
|
||||||
)
|
)
|
||||||
elif isinstance(tool, CustomTool):
|
elif isinstance(tool, CustomTool):
|
||||||
setattr(toolset, name, _bound_custom_tool(tool))
|
setattr(toolset, name, _bound_custom_tool(tool))
|
||||||
elif isinstance(tool, FunctionTool):
|
elif isinstance(tool, FunctionTool):
|
||||||
setattr(toolset, name, _with_bounded_result(_with_coerced_arguments(tool)))
|
setattr(
|
||||||
|
toolset,
|
||||||
|
name,
|
||||||
|
_with_bounded_result(
|
||||||
|
_with_strictness(_with_coerced_arguments(tool), strict_schemas)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _make_filesystem_configurator(*, chat_completions: bool) -> Any:
|
def _make_filesystem_configurator(*, chat_completions: bool, strict_schemas: bool) -> Any:
|
||||||
def configure(toolset: Any) -> None:
|
def configure(toolset: Any) -> None:
|
||||||
_configure_filesystem_tools(toolset, chat_completions=chat_completions)
|
_configure_filesystem_tools(
|
||||||
|
toolset, chat_completions=chat_completions, strict_schemas=strict_schemas
|
||||||
|
)
|
||||||
|
|
||||||
return configure
|
return configure
|
||||||
|
|
||||||
@@ -406,11 +432,13 @@ def _wrap_write_stdin(tool: FunctionTool) -> FunctionTool:
|
|||||||
return tool
|
return tool
|
||||||
|
|
||||||
|
|
||||||
def _configure_shell_tools(toolset: Any, *, chat_completions: bool) -> None:
|
def _configure_shell_tools(
|
||||||
|
toolset: Any, *, chat_completions: bool, strict_schemas: bool = True
|
||||||
|
) -> None:
|
||||||
for name, tool in vars(toolset).items():
|
for name, tool in vars(toolset).items():
|
||||||
if not isinstance(tool, FunctionTool):
|
if not isinstance(tool, FunctionTool):
|
||||||
continue
|
continue
|
||||||
wrapped = _with_coerced_arguments(tool)
|
wrapped = _with_strictness(_with_coerced_arguments(tool), strict_schemas)
|
||||||
if tool.name == "exec_command":
|
if tool.name == "exec_command":
|
||||||
wrapped = _wrap_exec_command(wrapped)
|
wrapped = _wrap_exec_command(wrapped)
|
||||||
elif tool.name == "write_stdin":
|
elif tool.name == "write_stdin":
|
||||||
@@ -420,9 +448,11 @@ def _configure_shell_tools(toolset: Any, *, chat_completions: bool) -> None:
|
|||||||
setattr(toolset, name, wrapped)
|
setattr(toolset, name, wrapped)
|
||||||
|
|
||||||
|
|
||||||
def _make_shell_configurator(*, chat_completions: bool) -> Any:
|
def _make_shell_configurator(*, chat_completions: bool, strict_schemas: bool) -> Any:
|
||||||
def configure(toolset: Any) -> None:
|
def configure(toolset: Any) -> None:
|
||||||
_configure_shell_tools(toolset, chat_completions=chat_completions)
|
_configure_shell_tools(
|
||||||
|
toolset, chat_completions=chat_completions, strict_schemas=strict_schemas
|
||||||
|
)
|
||||||
|
|
||||||
return configure
|
return configure
|
||||||
|
|
||||||
@@ -568,6 +598,7 @@ def build_strix_agent(
|
|||||||
is_whitebox: bool = False,
|
is_whitebox: bool = False,
|
||||||
interactive: bool = False,
|
interactive: bool = False,
|
||||||
chat_completions_tools: bool = False,
|
chat_completions_tools: bool = False,
|
||||||
|
strict_tool_schemas: bool = True,
|
||||||
system_prompt_context: dict[str, Any] | None = None,
|
system_prompt_context: dict[str, Any] | None = None,
|
||||||
extra_tools: Sequence[Tool] | None = None,
|
extra_tools: Sequence[Tool] | None = None,
|
||||||
instructions_override: str | None = None,
|
instructions_override: str | None = None,
|
||||||
@@ -577,6 +608,8 @@ def build_strix_agent(
|
|||||||
Args:
|
Args:
|
||||||
chat_completions_tools: Wrap SDK custom tools as function tools
|
chat_completions_tools: Wrap SDK custom tools as function tools
|
||||||
when the selected backend cannot accept Responses custom tools.
|
when the selected backend cannot accept Responses custom tools.
|
||||||
|
strict_tool_schemas: Send function tools as strict-schema tools. Off
|
||||||
|
for routes that reject a toolset this size as strict.
|
||||||
extra_tools: Additional tools for this scan agent only, on top of any
|
extra_tools: Additional tools for this scan agent only, on top of any
|
||||||
registered via ``register_agent_tools``.
|
registered via ``register_agent_tools``.
|
||||||
instructions_override: Use this verbatim as the system prompt instead
|
instructions_override: Use this verbatim as the system prompt instead
|
||||||
@@ -604,7 +637,7 @@ def build_strix_agent(
|
|||||||
tools = [*_BASE_TOOLS, *agent_tools, agent_finish]
|
tools = [*_BASE_TOOLS, *agent_tools, agent_finish]
|
||||||
_ensure_unique_tool_names(tools)
|
_ensure_unique_tool_names(tools)
|
||||||
tools = [
|
tools = [
|
||||||
_with_bounded_result(_with_coerced_arguments(tool))
|
_with_bounded_result(_with_strictness(_with_coerced_arguments(tool), strict_tool_schemas))
|
||||||
if isinstance(tool, FunctionTool)
|
if isinstance(tool, FunctionTool)
|
||||||
else tool
|
else tool
|
||||||
for tool in tools
|
for tool in tools
|
||||||
@@ -630,11 +663,13 @@ def build_strix_agent(
|
|||||||
Filesystem(
|
Filesystem(
|
||||||
configure_tools=_make_filesystem_configurator(
|
configure_tools=_make_filesystem_configurator(
|
||||||
chat_completions=chat_completions_tools,
|
chat_completions=chat_completions_tools,
|
||||||
|
strict_schemas=strict_tool_schemas,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Shell(
|
Shell(
|
||||||
configure_tools=_make_shell_configurator(
|
configure_tools=_make_shell_configurator(
|
||||||
chat_completions=chat_completions_tools,
|
chat_completions=chat_completions_tools,
|
||||||
|
strict_schemas=strict_tool_schemas,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -647,6 +682,7 @@ def make_child_factory(
|
|||||||
is_whitebox: bool = False,
|
is_whitebox: bool = False,
|
||||||
interactive: bool = False,
|
interactive: bool = False,
|
||||||
chat_completions_tools: bool = False,
|
chat_completions_tools: bool = False,
|
||||||
|
strict_tool_schemas: bool = True,
|
||||||
system_prompt_context: dict[str, Any] | None = None,
|
system_prompt_context: dict[str, Any] | None = None,
|
||||||
) -> Any:
|
) -> Any:
|
||||||
"""Return the runner-owned builder used by ``spawn_child_agent``.
|
"""Return the runner-owned builder used by ``spawn_child_agent``.
|
||||||
@@ -665,6 +701,7 @@ def make_child_factory(
|
|||||||
is_whitebox=is_whitebox,
|
is_whitebox=is_whitebox,
|
||||||
interactive=interactive,
|
interactive=interactive,
|
||||||
chat_completions_tools=chat_completions_tools,
|
chat_completions_tools=chat_completions_tools,
|
||||||
|
strict_tool_schemas=strict_tool_schemas,
|
||||||
system_prompt_context=system_prompt_context,
|
system_prompt_context=system_prompt_context,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -749,6 +749,18 @@ def uses_chat_completions_tool_schema(model_name: str, settings: Settings) -> bo
|
|||||||
return not model_supports_reasoning(model_name)
|
return not model_supports_reasoning(model_name)
|
||||||
|
|
||||||
|
|
||||||
|
def supports_strict_tool_schemas(model_name: str) -> bool:
|
||||||
|
"""Return whether the route accepts strict tool schemas for Strix's toolset.
|
||||||
|
|
||||||
|
Claude caps a request at 20 strict tools and 16 union-typed parameters
|
||||||
|
across all strict schemas. Strix ships ~30 tools and the strict dialect
|
||||||
|
turns every optional parameter into a nullable union, so both caps are
|
||||||
|
exceeded and the request is rejected outright.
|
||||||
|
"""
|
||||||
|
name = model_name.strip().lower()
|
||||||
|
return not any(marker in name for marker in _ANTHROPIC_MODEL_MARKERS)
|
||||||
|
|
||||||
|
|
||||||
def model_supports_reasoning(model_name: str) -> bool:
|
def model_supports_reasoning(model_name: str) -> bool:
|
||||||
import litellm
|
import litellm
|
||||||
|
|
||||||
@@ -845,6 +857,9 @@ def is_known_openai_bare_model(model_name: str) -> bool:
|
|||||||
return bool(entry and entry.get("litellm_provider") == "openai")
|
return bool(entry and entry.get("litellm_provider") == "openai")
|
||||||
|
|
||||||
|
|
||||||
|
_ANTHROPIC_MODEL_MARKERS = ("anthropic", "claude", "sonnet", "opus", "haiku")
|
||||||
|
|
||||||
|
|
||||||
def is_claude_model(model_name: str) -> bool:
|
def is_claude_model(model_name: str) -> bool:
|
||||||
return "claude" in (model_name or "").strip().lower()
|
return "claude" in (model_name or "").strip().lower()
|
||||||
|
|
||||||
|
|||||||
+17
-3
@@ -7,13 +7,12 @@ import contextlib
|
|||||||
import logging
|
import logging
|
||||||
import uuid
|
import uuid
|
||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
|
from functools import cache
|
||||||
from typing import TYPE_CHECKING, Any, cast
|
from typing import TYPE_CHECKING, Any, cast
|
||||||
|
|
||||||
import litellm
|
|
||||||
from agents import RunConfig, Runner
|
from agents import RunConfig, Runner
|
||||||
from agents.exceptions import AgentsException, MaxTurnsExceeded, UserError
|
from agents.exceptions import AgentsException, MaxTurnsExceeded, UserError
|
||||||
from agents.sandbox.errors import ExecTransportError
|
from agents.sandbox.errors import ExecTransportError
|
||||||
from docker import errors as docker_errors # type: ignore[import-untyped, unused-ignore]
|
|
||||||
from openai import (
|
from openai import (
|
||||||
APIConnectionError,
|
APIConnectionError,
|
||||||
APIError,
|
APIError,
|
||||||
@@ -56,6 +55,19 @@ _INPUT_REJECTION_CODES = frozenset({400, 404, 422})
|
|||||||
_MAX_COMPACTIONS_PER_CYCLE = 2
|
_MAX_COMPACTIONS_PER_CYCLE = 2
|
||||||
|
|
||||||
|
|
||||||
|
@cache
|
||||||
|
def _teardown_sandbox_errors() -> tuple[type[BaseException], ...]:
|
||||||
|
"""Sandbox-gone errors, tolerated during shutdown.
|
||||||
|
|
||||||
|
The Docker SDK is imported here rather than at module scope: it is only
|
||||||
|
reachable with the Docker runtime backend, and importing it eagerly puts it
|
||||||
|
on every launch's critical path.
|
||||||
|
"""
|
||||||
|
from docker import errors as docker_errors # type: ignore[import-untyped, unused-ignore]
|
||||||
|
|
||||||
|
return (ExecTransportError, docker_errors.NotFound)
|
||||||
|
|
||||||
|
|
||||||
class ProviderRefusalError(AgentsException):
|
class ProviderRefusalError(AgentsException):
|
||||||
"""Raised when a provider returns a structured refusal instead of an exception."""
|
"""Raised when a provider returns a structured refusal instead of an exception."""
|
||||||
|
|
||||||
@@ -126,6 +138,8 @@ def _is_transient_model_error(exc: BaseException) -> bool:
|
|||||||
return True
|
return True
|
||||||
code = _model_error_status_code(exc)
|
code = _model_error_status_code(exc)
|
||||||
if code is not None:
|
if code is not None:
|
||||||
|
import litellm
|
||||||
|
|
||||||
return bool(litellm._should_retry(code))
|
return bool(litellm._should_retry(code))
|
||||||
return isinstance(exc, APIError)
|
return isinstance(exc, APIError)
|
||||||
|
|
||||||
@@ -692,7 +706,7 @@ async def _run_cycle( # noqa: PLR0912, PLR0915
|
|||||||
"Ignoring LiteLLM end-of-stream shutdown race for %s",
|
"Ignoring LiteLLM end-of-stream shutdown race for %s",
|
||||||
agent_id,
|
agent_id,
|
||||||
)
|
)
|
||||||
except (ExecTransportError, docker_errors.NotFound):
|
except _teardown_sandbox_errors():
|
||||||
if not coordinator.is_shutting_down:
|
if not coordinator.is_shutting_down:
|
||||||
raise
|
raise
|
||||||
logger.warning(
|
logger.warning(
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ from strix.config import load_settings
|
|||||||
from strix.config.models import (
|
from strix.config.models import (
|
||||||
StrixProvider,
|
StrixProvider,
|
||||||
configure_sdk_model_defaults,
|
configure_sdk_model_defaults,
|
||||||
|
supports_strict_tool_schemas,
|
||||||
uses_chat_completions_tool_schema,
|
uses_chat_completions_tool_schema,
|
||||||
)
|
)
|
||||||
from strix.config.settings import DEFAULT_MAX_TURNS
|
from strix.config.settings import DEFAULT_MAX_TURNS
|
||||||
@@ -175,6 +176,9 @@ async def run_strix_scan(
|
|||||||
)
|
)
|
||||||
logger.info("LLM model resolved: %s", resolved_model)
|
logger.info("LLM model resolved: %s", resolved_model)
|
||||||
chat_completions_tools = uses_chat_completions_tool_schema(resolved_model, settings)
|
chat_completions_tools = uses_chat_completions_tool_schema(resolved_model, settings)
|
||||||
|
strict_tool_schemas = supports_strict_tool_schemas(resolved_model)
|
||||||
|
if not strict_tool_schemas:
|
||||||
|
logger.info("Sending non-strict tool schemas: %s caps strict tools", resolved_model)
|
||||||
|
|
||||||
if coordinator is None:
|
if coordinator is None:
|
||||||
coordinator = AgentCoordinator()
|
coordinator = AgentCoordinator()
|
||||||
@@ -306,6 +310,7 @@ async def run_strix_scan(
|
|||||||
is_whitebox=is_whitebox,
|
is_whitebox=is_whitebox,
|
||||||
interactive=interactive,
|
interactive=interactive,
|
||||||
chat_completions_tools=chat_completions_tools,
|
chat_completions_tools=chat_completions_tools,
|
||||||
|
strict_tool_schemas=strict_tool_schemas,
|
||||||
system_prompt_context=root_context,
|
system_prompt_context=root_context,
|
||||||
instructions_override=root_instructions,
|
instructions_override=root_instructions,
|
||||||
)
|
)
|
||||||
@@ -324,6 +329,7 @@ async def run_strix_scan(
|
|||||||
is_whitebox=is_whitebox,
|
is_whitebox=is_whitebox,
|
||||||
interactive=interactive,
|
interactive=interactive,
|
||||||
chat_completions_tools=chat_completions_tools,
|
chat_completions_tools=chat_completions_tools,
|
||||||
|
strict_tool_schemas=strict_tool_schemas,
|
||||||
system_prompt_context=scope_context,
|
system_prompt_context=scope_context,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -346,7 +346,7 @@ def _load_resume_state(args: argparse.Namespace, parser: argparse.ArgumentParser
|
|||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
state = read_run_record(run_dir)
|
state = read_run_record(run_dir)
|
||||||
except RuntimeError as exc:
|
except (RuntimeError, TypeError) as exc:
|
||||||
parser.error(f"--resume {args.resume}: run.json unreadable: {exc}")
|
parser.error(f"--resume {args.resume}: run.json unreadable: {exc}")
|
||||||
|
|
||||||
args.targets_info = state.get("targets_info") or []
|
args.targets_info = state.get("targets_info") or []
|
||||||
|
|||||||
@@ -431,6 +431,10 @@ def main() -> None:
|
|||||||
|
|
||||||
sys.exit(run_auth(sys.argv[2:]))
|
sys.exit(run_auth(sys.argv[2:]))
|
||||||
|
|
||||||
|
from strix.llm.warmup import start_import_warmup
|
||||||
|
|
||||||
|
start_import_warmup()
|
||||||
|
|
||||||
args = parse_arguments()
|
args = parse_arguments()
|
||||||
|
|
||||||
start_background_check()
|
start_background_check()
|
||||||
|
|||||||
@@ -146,7 +146,9 @@ def bounded_state_projection(state: dict[str, Any]) -> dict[str, Any]:
|
|||||||
}
|
}
|
||||||
for message in state["messages"][-5:]
|
for message in state["messages"][-5:]
|
||||||
]
|
]
|
||||||
state["usage"] = {}
|
state["usage"] = {
|
||||||
|
key: state["usage"][key] for key in ("total_tokens", "cost") if key in state["usage"]
|
||||||
|
}
|
||||||
state["error"] = terminal_projection(state["error"], max_string=512)
|
state["error"] = terminal_projection(state["error"], max_string=512)
|
||||||
state["model_warning"] = terminal_projection(state["model_warning"], max_string=256)
|
state["model_warning"] = terminal_projection(state["model_warning"], max_string=256)
|
||||||
state["caido_url"] = terminal_projection(state["caido_url"], max_string=256)
|
state["caido_url"] = terminal_projection(state["caido_url"], max_string=256)
|
||||||
@@ -173,7 +175,7 @@ def bounded_state_projection(state: dict[str, Any]) -> dict[str, Any]:
|
|||||||
"model_warning": "",
|
"model_warning": "",
|
||||||
"caido_url": None,
|
"caido_url": None,
|
||||||
"messages": [],
|
"messages": [],
|
||||||
"usage": {},
|
"usage": state["usage"],
|
||||||
"subscription": state["subscription"],
|
"subscription": state["subscription"],
|
||||||
"viewer_status": state["viewer_status"],
|
"viewer_status": state["viewer_status"],
|
||||||
"viewer_url": None,
|
"viewer_url": None,
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ func applyMarkdownStyles(text string) string {
|
|||||||
case strings.HasPrefix(line, "- "), strings.HasPrefix(line, "* "):
|
case strings.HasPrefix(line, "- "), strings.HasPrefix(line, "* "):
|
||||||
out.WriteString(Col(Green).Render("• ") + inlineFormat(line[2:]))
|
out.WriteString(Col(Green).Render("• ") + inlineFormat(line[2:]))
|
||||||
case len(line) > 2 && line[0] >= '0' && line[0] <= '9' && (line[1:3] == ". " || line[1:3] == ") "):
|
case len(line) > 2 && line[0] >= '0' && line[0] <= '9' && (line[1:3] == ". " || line[1:3] == ") "):
|
||||||
out.WriteString(Col(Green).Render(string(line[0])+". ") + inlineFormat(line[2:]))
|
out.WriteString(Col(Green).Render(line[:2]+" ") + inlineFormat(line[3:]))
|
||||||
case line == "---" || line == "***" || line == "___":
|
case line == "---" || line == "***" || line == "___":
|
||||||
out.WriteString(Col(Green).Render(strings.Repeat("─", 40)))
|
out.WriteString(Col(Green).Render(strings.Repeat("─", 40)))
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -72,6 +72,19 @@ func TestNonTablePipeLinesAreLeftAlone(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMarkdownOrderedListsUseSingleSpaceAfterMarker(t *testing.T) {
|
||||||
|
out := renderAssistantMarkdown("1. hello\n2) world")
|
||||||
|
plain := ansi.Strip(out)
|
||||||
|
for _, want := range []string{"1. hello", "2) world"} {
|
||||||
|
if !strings.Contains(plain, want) {
|
||||||
|
t.Fatalf("ordered list item %q missing: %q", want, plain)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if strings.Contains(plain, "1. hello") || strings.Contains(plain, "2) world") {
|
||||||
|
t.Fatalf("double space after the list marker: %q", plain)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestInlineFormatKeepsNonEmphasisMarkers(t *testing.T) {
|
func TestInlineFormatKeepsNonEmphasisMarkers(t *testing.T) {
|
||||||
literal := []string{
|
literal := []string{
|
||||||
"ls *.py *.go",
|
"ls *.py *.go",
|
||||||
|
|||||||
@@ -13,9 +13,7 @@ from pathlib import Path
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
from urllib.parse import parse_qs, urlparse
|
from urllib.parse import parse_qs, urlparse
|
||||||
|
|
||||||
import docker
|
|
||||||
import requests
|
import requests
|
||||||
from docker.errors import DockerException, ImageNotFound
|
|
||||||
from rich.console import Console
|
from rich.console import Console
|
||||||
from rich.panel import Panel
|
from rich.panel import Panel
|
||||||
from rich.text import Text
|
from rich.text import Text
|
||||||
@@ -1599,6 +1597,9 @@ def clone_repository(repo_url: str, run_name: str, dest_name: str | None = None)
|
|||||||
|
|
||||||
|
|
||||||
def check_docker_connection() -> Any:
|
def check_docker_connection() -> Any:
|
||||||
|
import docker
|
||||||
|
from docker.errors import DockerException
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return docker.from_env()
|
return docker.from_env()
|
||||||
except DockerException:
|
except DockerException:
|
||||||
@@ -1624,6 +1625,8 @@ def check_docker_connection() -> Any:
|
|||||||
|
|
||||||
|
|
||||||
def image_exists(client: Any, image_name: str) -> bool:
|
def image_exists(client: Any, image_name: str) -> bool:
|
||||||
|
from docker.errors import ImageNotFound
|
||||||
|
|
||||||
try:
|
try:
|
||||||
client.images.get(image_name)
|
client.images.get(image_name)
|
||||||
except ImageNotFound:
|
except ImageNotFound:
|
||||||
|
|||||||
@@ -45,7 +45,11 @@ def run_view(argv: list[str]) -> None:
|
|||||||
default=0,
|
default=0,
|
||||||
help="Port to serve on (default: an available ephemeral port).",
|
help="Port to serve on (default: an available ephemeral port).",
|
||||||
)
|
)
|
||||||
parser.add_argument("--host", default="127.0.0.1", help=argparse.SUPPRESS)
|
parser.add_argument(
|
||||||
|
"--host",
|
||||||
|
default="127.0.0.1",
|
||||||
|
help="Host to bind to (default: 127.0.0.1; use 0.0.0.0 for all IPv4 interfaces).",
|
||||||
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--no-open",
|
"--no-open",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
|
|||||||
@@ -135,8 +135,9 @@ class _ViewerState:
|
|||||||
# exchanged for a session cookie only when presented on the initial page
|
# exchanged for a session cookie only when presented on the initial page
|
||||||
# load. It is the request-level authorization the review asked for:
|
# load. It is the request-level authorization the review asked for:
|
||||||
# reachability of the port (e.g. when bound with ``--host``) is not
|
# reachability of the port (e.g. when bound with ``--host``) is not
|
||||||
# enough to steer a live scan, trigger a report, or browse history --
|
# enough to read run data, steer a live scan, trigger a report, or
|
||||||
# the token is never handed to a caller who merely reaches ``/``.
|
# browse history -- the token is never handed to a caller who merely
|
||||||
|
# reaches ``/``.
|
||||||
self.session_token = secrets.token_urlsafe(32)
|
self.session_token = secrets.token_urlsafe(32)
|
||||||
# Finalized in ``serve()`` once the port is known (the server binds
|
# Finalized in ``serve()`` once the port is known (the server binds
|
||||||
# after this state is constructed); see SESSION_COOKIE_PREFIX.
|
# after this state is constructed); see SESSION_COOKIE_PREFIX.
|
||||||
@@ -234,11 +235,11 @@ def _make_handler(state: _ViewerState) -> type[BaseHTTPRequestHandler]:
|
|||||||
self.end_headers()
|
self.end_headers()
|
||||||
|
|
||||||
def _handle_api(self, path: str, query: dict[str, list[str]]) -> None:
|
def _handle_api(self, path: str, query: dict[str, list[str]]) -> None:
|
||||||
# The launched run is always viewable with no verification. The
|
# The cross-run history list (/api/runs) unlocks its entries only for
|
||||||
# cross-run history list (/api/runs) unlocks its entries only for a
|
# a caller that holds this process's session capability *and* is
|
||||||
# caller that holds this process's session capability *and* is email
|
# email verified, so merely reaching an exposed --host port never
|
||||||
# verified, so merely reaching an exposed --host port never leaks the
|
# leaks the run list (the payload still advertises the count as a
|
||||||
# run list (the payload still advertises the count as a teaser).
|
# teaser).
|
||||||
if path == "/api/runs":
|
if path == "/api/runs":
|
||||||
unlocked = self._has_session() and auth.is_verified()
|
unlocked = self._has_session() and auth.is_verified()
|
||||||
payload = build_runs_payload(state.base_dir, verified=unlocked)
|
payload = build_runs_payload(state.base_dir, verified=unlocked)
|
||||||
@@ -253,6 +254,13 @@ def _make_handler(state: _ViewerState) -> type[BaseHTTPRequestHandler]:
|
|||||||
self._handle_auth_status()
|
self._handle_auth_status()
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# All remaining GET endpoints expose run metadata or scan output.
|
||||||
|
# Require the capability even for the run used to launch the viewer;
|
||||||
|
# reachability of an exposed --host port must not grant data access.
|
||||||
|
if not self._has_session():
|
||||||
|
self._send_json(HTTPStatus.FORBIDDEN, {"error": "forbidden"})
|
||||||
|
return
|
||||||
|
|
||||||
run_values = query.get("run")
|
run_values = query.get("run")
|
||||||
run_param = run_values[0] if run_values else None
|
run_param = run_values[0] if run_values else None
|
||||||
run_dir = resolve_run_dir(state.base_dir, run_param, state.run_dir)
|
run_dir = resolve_run_dir(state.base_dir, run_param, state.run_dir)
|
||||||
@@ -260,16 +268,10 @@ def _make_handler(state: _ViewerState) -> type[BaseHTTPRequestHandler]:
|
|||||||
self._send_json(HTTPStatus.NOT_FOUND, {"error": "unknown run"})
|
self._send_json(HTTPStatus.NOT_FOUND, {"error": "unknown run"})
|
||||||
return
|
return
|
||||||
|
|
||||||
# The launched run is always viewable. Any *other* run's data is part
|
# Any run other than the one used to launch the viewer is part of the
|
||||||
# of the gated history: it needs this process's session capability
|
# email-gated history. The session check above applies to both paths;
|
||||||
# (so merely reaching an exposed --host port is not enough) *and*
|
# verification adds a second gate for historical run data.
|
||||||
# email verification -- otherwise knowing a run name would leak its
|
if run_dir.resolve() != state.run_dir.resolve() and not auth.is_verified():
|
||||||
# metadata, vulnerabilities, report, and transcript.
|
|
||||||
if run_dir.resolve() != state.run_dir.resolve():
|
|
||||||
if not self._has_session():
|
|
||||||
self._send_json(HTTPStatus.FORBIDDEN, {"error": "forbidden"})
|
|
||||||
return
|
|
||||||
if not auth.is_verified():
|
|
||||||
self._send_json(HTTPStatus.UNAUTHORIZED, {"error": "unverified"})
|
self._send_json(HTTPStatus.UNAUTHORIZED, {"error": "unverified"})
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -385,7 +387,7 @@ def _make_handler(state: _ViewerState) -> type[BaseHTTPRequestHandler]:
|
|||||||
except auth.RelayError as exc:
|
except auth.RelayError as exc:
|
||||||
self._send_relay_error(exc)
|
self._send_relay_error(exc)
|
||||||
return
|
return
|
||||||
# The password is returned only to the local (127.0.0.1) browser.
|
# The password is returned only to a session-authorized browser.
|
||||||
self._send_json(
|
self._send_json(
|
||||||
HTTPStatus.OK,
|
HTTPStatus.OK,
|
||||||
{"ok": True, "password": password, "filename": filename},
|
{"ok": True, "password": password, "filename": filename},
|
||||||
|
|||||||
+16
-3
@@ -10,11 +10,11 @@ pairing so the trimmed history is still valid provider input.
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
from functools import cache
|
||||||
from typing import TYPE_CHECKING, Any
|
from typing import TYPE_CHECKING, Any
|
||||||
|
|
||||||
from agents.model_settings import ModelSettings
|
from agents.model_settings import ModelSettings
|
||||||
from agents.models.interface import ModelTracing
|
from agents.models.interface import ModelTracing
|
||||||
from litellm.exceptions import BadRequestError, ContextWindowExceededError
|
|
||||||
from openai.types.responses import ResponseOutputMessage, ResponseOutputText
|
from openai.types.responses import ResponseOutputMessage, ResponseOutputText
|
||||||
|
|
||||||
from strix.config import load_settings
|
from strix.config import load_settings
|
||||||
@@ -63,6 +63,18 @@ _OVERFLOW_MARKERS = (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@cache
|
||||||
|
def _overflow_error_types() -> tuple[type[BaseException], type[BaseException]]:
|
||||||
|
"""``(ContextWindowExceededError, BadRequestError)``, imported on first use.
|
||||||
|
|
||||||
|
LiteLLM costs seconds to import, and nothing needs it until a model call is
|
||||||
|
actually made, so it stays off the launch path.
|
||||||
|
"""
|
||||||
|
from litellm.exceptions import BadRequestError, ContextWindowExceededError
|
||||||
|
|
||||||
|
return ContextWindowExceededError, BadRequestError
|
||||||
|
|
||||||
|
|
||||||
def is_context_overflow(exc: BaseException) -> bool:
|
def is_context_overflow(exc: BaseException) -> bool:
|
||||||
"""Whether ``exc`` is a model context-window-overflow error.
|
"""Whether ``exc`` is a model context-window-overflow error.
|
||||||
|
|
||||||
@@ -70,9 +82,10 @@ def is_context_overflow(exc: BaseException) -> bool:
|
|||||||
OpenRouter branch raises a plain BadRequestError, so for that we fall back to
|
OpenRouter branch raises a plain BadRequestError, so for that we fall back to
|
||||||
matching the provider message.
|
matching the provider message.
|
||||||
"""
|
"""
|
||||||
if isinstance(exc, ContextWindowExceededError):
|
context_window_exceeded, bad_request = _overflow_error_types()
|
||||||
|
if isinstance(exc, context_window_exceeded):
|
||||||
return True
|
return True
|
||||||
if isinstance(exc, BadRequestError):
|
if isinstance(exc, bad_request):
|
||||||
msg = str(exc).lower()
|
msg = str(exc).lower()
|
||||||
if any(x in msg for x in _OVERFLOW_EXCLUSIONS):
|
if any(x in msg for x in _OVERFLOW_EXCLUSIONS):
|
||||||
return False
|
return False
|
||||||
|
|||||||
@@ -8,8 +8,6 @@ import logging
|
|||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import litellm
|
|
||||||
|
|
||||||
from strix.config import load_settings
|
from strix.config import load_settings
|
||||||
|
|
||||||
|
|
||||||
@@ -38,6 +36,8 @@ def _lookup_key(model: str) -> str:
|
|||||||
|
|
||||||
def _safe_get_model_info(model: str) -> dict[str, Any] | None:
|
def _safe_get_model_info(model: str) -> dict[str, Any] | None:
|
||||||
try:
|
try:
|
||||||
|
import litellm
|
||||||
|
|
||||||
return dict(litellm.get_model_info(model))
|
return dict(litellm.get_model_info(model))
|
||||||
except Exception: # noqa: BLE001 - unmapped models raise; caller falls back.
|
except Exception: # noqa: BLE001 - unmapped models raise; caller falls back.
|
||||||
return None
|
return None
|
||||||
@@ -82,6 +82,8 @@ def count_tokens(model: str, text: str) -> int:
|
|||||||
if not text:
|
if not text:
|
||||||
return 0
|
return 0
|
||||||
try:
|
try:
|
||||||
|
import litellm
|
||||||
|
|
||||||
return int(litellm.token_counter(model=_lookup_key(model), text=text))
|
return int(litellm.token_counter(model=_lookup_key(model), text=text))
|
||||||
except Exception: # noqa: BLE001 - tokenizer may be unavailable for some models.
|
except Exception: # noqa: BLE001 - tokenizer may be unavailable for some models.
|
||||||
return len(text.encode("utf-8"))
|
return len(text.encode("utf-8"))
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
"""Background pre-import of the heavy scan dependencies.
|
||||||
|
|
||||||
|
The scan engine's import graph (the agents SDK, OpenAI client, LiteLLM, the
|
||||||
|
Caido SDK, the Docker SDK) costs seconds to import cold, but none of it is
|
||||||
|
needed until a scan actually starts. Importing it on a daemon thread at CLI
|
||||||
|
entry overlaps that cost with the I/O-bound startup work that always precedes
|
||||||
|
a scan (argument parsing, Docker checks, image pull, TUI setup), so by the
|
||||||
|
time the scan begins the modules are already in ``sys.modules``. Any thread
|
||||||
|
that needs one of them before the warm-up finishes just blocks on the normal
|
||||||
|
import lock, so behaviour is unchanged either way.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import importlib
|
||||||
|
import logging
|
||||||
|
import threading
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
WARMUP_MODULES = (
|
||||||
|
"strix.core.runner",
|
||||||
|
"litellm",
|
||||||
|
"caido_sdk_client",
|
||||||
|
"docker",
|
||||||
|
)
|
||||||
|
|
||||||
|
_lock = threading.Lock()
|
||||||
|
_thread: threading.Thread | None = None
|
||||||
|
|
||||||
|
|
||||||
|
def _warm(modules: tuple[str, ...]) -> None:
|
||||||
|
for name in modules:
|
||||||
|
try:
|
||||||
|
importlib.import_module(name)
|
||||||
|
except Exception: # noqa: BLE001 - a failed warm-up must never fail the run.
|
||||||
|
logger.debug("Import warm-up for %r failed", name, exc_info=True)
|
||||||
|
|
||||||
|
|
||||||
|
def start_import_warmup(modules: tuple[str, ...] = WARMUP_MODULES) -> threading.Thread:
|
||||||
|
"""Start importing the heavy scan dependencies in the background, once.
|
||||||
|
|
||||||
|
``modules`` lets embedders that never touch some backends (e.g. a cloud
|
||||||
|
runtime that has no local Docker) warm a narrower set.
|
||||||
|
"""
|
||||||
|
global _thread # noqa: PLW0603
|
||||||
|
with _lock:
|
||||||
|
if _thread is not None:
|
||||||
|
return _thread
|
||||||
|
_thread = threading.Thread(
|
||||||
|
target=_warm, args=(modules,), name="strix-import-warmup", daemon=True
|
||||||
|
)
|
||||||
|
_thread.start()
|
||||||
|
return _thread
|
||||||
@@ -15,12 +15,10 @@ import json
|
|||||||
import logging
|
import logging
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from caido_sdk_client import Client, TokenAuthOptions
|
|
||||||
from caido_sdk_client.types import CreateProjectOptions
|
|
||||||
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from agents.sandbox.session import BaseSandboxSession
|
from agents.sandbox.session import BaseSandboxSession
|
||||||
|
from caido_sdk_client import Client
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -87,6 +85,12 @@ async def bootstrap_caido(
|
|||||||
container_url: str,
|
container_url: str,
|
||||||
) -> Client:
|
) -> Client:
|
||||||
"""Connect to the in-container Caido sidecar and select a fresh project."""
|
"""Connect to the in-container Caido sidecar and select a fresh project."""
|
||||||
|
# The Caido SDK (and its generated GraphQL schema) is slow to import and is
|
||||||
|
# only needed once a sandbox is actually being bootstrapped, so it is
|
||||||
|
# imported here rather than at module scope.
|
||||||
|
from caido_sdk_client import Client, TokenAuthOptions
|
||||||
|
from caido_sdk_client.types import CreateProjectOptions
|
||||||
|
|
||||||
logger.info("Bootstrapping Caido client (host=%s, container=%s)", host_url, container_url)
|
logger.info("Bootstrapping Caido client (host=%s, container=%s)", host_url, container_url)
|
||||||
|
|
||||||
access_token = await _login_as_guest(session, container_url=container_url)
|
access_token = await _login_as_guest(session, container_url=container_url)
|
||||||
|
|||||||
@@ -42,8 +42,14 @@ Notable source-aware skills:
|
|||||||
- `source_aware_whitebox` (coordination): white-box orchestration playbook
|
- `source_aware_whitebox` (coordination): white-box orchestration playbook
|
||||||
- `source_aware_sast` (custom): semgrep/AST/secrets/supply-chain static triage workflow
|
- `source_aware_sast` (custom): semgrep/AST/secrets/supply-chain static triage workflow
|
||||||
- `dependency_cve_scanning` (custom): trivy-based SCA workflow for reporting known dependency CVEs via `create_dependency_report`
|
- `dependency_cve_scanning` (custom): trivy-based SCA workflow for reporting known dependency CVEs via `create_dependency_report`
|
||||||
|
- `npx_confusion` (custom): npx/npm exec/bunx fallback and adjacent package-runner identity confusion, with runner-specific registry and reporting gates
|
||||||
|
- `semantic_confusion` (vulnerabilities): cross-boundary parser, normalization, and representation mismatch analysis
|
||||||
|
- `agentic_system_security` (vulnerabilities): effective-authority and MCP/tool ecosystem security testing
|
||||||
|
- `browser_security` (vulnerabilities): browsing-context, postMessage, XS-Leaks, service-worker, and cross-origin state-machine testing
|
||||||
- `azure` (cloud): Azure and Microsoft Entra privilege, PIM, workload identity, and cross-plane escalation analysis
|
- `azure` (cloud): Azure and Microsoft Entra privilege, PIM, workload identity, and cross-plane escalation analysis
|
||||||
|
- `infrastructure_lifecycle` (reconnaissance): abandoned or mutable external dependencies such as update endpoints, MX, storage, and control domains
|
||||||
- `argument_injection` (vulnerabilities): shell-free CLI option smuggling, secondary argument-file parsing, and platform-specific argv transformation boundaries
|
- `argument_injection` (vulnerabilities): shell-free CLI option smuggling, secondary argument-file parsing, and platform-specific argv transformation boundaries
|
||||||
|
- `electron_desktop_apps` (technologies): Electron renderer-to-native trust boundaries, preload/IPC exposure, and navigation analysis
|
||||||
|
|
||||||
Notable LLM security skills:
|
Notable LLM security skills:
|
||||||
- `llm_applications` (technologies): end-to-end OWASP 2026 LLM01-LLM10 coverage across models, RAG, vectors, agents, tools, outputs, supply chain, and resource controls
|
- `llm_applications` (technologies): end-to-end OWASP 2026 LLM01-LLM10 coverage across models, RAG, vectors, agents, tools, outputs, supply chain, and resource controls
|
||||||
|
|||||||
@@ -0,0 +1,233 @@
|
|||||||
|
---
|
||||||
|
name: npx-confusion
|
||||||
|
description: Test package and executable identity confusion in npx, npm exec, and bunx fallback, plus explicit auto-fetch runners such as pnpm/yarn dlx and deno run npm:, with runner-specific resolution analysis, registry-state controls, reporting gates, and false-positive elimination
|
||||||
|
---
|
||||||
|
|
||||||
|
# npx Confusion
|
||||||
|
|
||||||
|
Use this skill when a package runner may execute code from a package other than the publisher or package the workflow intended. For `npx`, `npm exec`, and `bunx`, the recurring case is a missing local executable being reinterpreted as a remotely fetched package spec. Explicit auto-fetch runners such as `pnpm dlx`, `yarn dlx`, and `deno run npm:` have different semantics; analyze them as an adjacent package-identity problem rather than pretending they share npm's fallback order.
|
||||||
|
|
||||||
|
Load `dependency_cve_scanning` for known vulnerable versions, `infrastructure_lifecycle` for abandoned domains or registry resources, `agentic_system_security` for the authority of an MCP/agent process, and `semantic_confusion` for the general lookup-order model.
|
||||||
|
|
||||||
|
## Core Condition
|
||||||
|
|
||||||
|
Choose the branch that matches the runner.
|
||||||
|
|
||||||
|
For local-first fallback (`npx`, `npm exec`, or `bunx`), require all of the following:
|
||||||
|
|
||||||
|
1. A target-controlled workflow invokes a bare executable or ambiguous package token.
|
||||||
|
2. The intended package and its executable name differ, or other evidence establishes the expected publisher/package.
|
||||||
|
3. The executable is not resolved in the workflow's real local, workspace, global, or cache context as applicable to that runner.
|
||||||
|
4. The runner consequently selects an unintended remote package spec from its configured registry.
|
||||||
|
5. The affected workflow reaches that package's executable with security-relevant authority.
|
||||||
|
|
||||||
|
For explicit auto-fetch runners (`pnpm dlx`/`pnx`/`pnpx`, `yarn dlx`, or `deno run npm:`), do not require or claim a missing-local-binary fallback. Require evidence that the command names or infers a package different from the one the workflow intended, such as a scoped-package/bin mismatch, typo, generated configuration error, or wrong publisher. Then prove the exact fetched package, chosen binary/module, execution path, and inherited authority.
|
||||||
|
|
||||||
|
A public package merely being outside the target's ownership is not a vulnerability. Third-party packages are normal; the mismatch between intended executable provenance and actual registry resolution is the finding.
|
||||||
|
|
||||||
|
## Resolution Model
|
||||||
|
|
||||||
|
Record the npm version because `npx` has used `npm exec` since npm 7 and resolver behavior changes between releases. For npm, model these decisions:
|
||||||
|
|
||||||
|
```text
|
||||||
|
bare command
|
||||||
|
-> executable in ancestor node_modules/.bin?
|
||||||
|
-> executable in global bin?
|
||||||
|
-> matching local/global package and usable bin?
|
||||||
|
-> matching environment in the npx cache?
|
||||||
|
-> treat the command token as a package spec
|
||||||
|
-> fetch its manifest from the configured registry
|
||||||
|
-> infer one executable from package.json#bin
|
||||||
|
-> install into the npx cache and execute
|
||||||
|
```
|
||||||
|
|
||||||
|
Also record:
|
||||||
|
|
||||||
|
- working directory and workspace root
|
||||||
|
- local dependency tree and generated `node_modules/.bin` links
|
||||||
|
- global prefix/bin directory and npx cache
|
||||||
|
- `registry`, scope-specific registry rules, proxy and authentication configuration
|
||||||
|
- command form, flags, package spec/version, TTY/CI state, and `yes` policy
|
||||||
|
- npm's executable-inference result when the package exposes zero, one, or several `bin` entries
|
||||||
|
|
||||||
|
Do not collapse package-name lookup and bin selection into one step. npm can fetch a manifest yet fail because it cannot infer exactly one executable.
|
||||||
|
|
||||||
|
### Runner distinctions
|
||||||
|
|
||||||
|
Record the exact runner and version. Do not reuse npm's local/global/cache ordering for another implementation.
|
||||||
|
|
||||||
|
| Runner | Resolution behavior to model | Package binding / fetch control |
|
||||||
|
|---|---|---|
|
||||||
|
| `npx` / `npm exec` | Local/workspace/global/cache resolution followed by package-spec fallback; executable inference depends on `package.json#bin` | `--package <pkg>` binds the provider; `--no` rejects an install prompt |
|
||||||
|
| `bunx` | Checks a locally installed package, then can install from npm into Bun's cache | `--package <pkg>` binds the provider; `--no-install` forbids installation |
|
||||||
|
| `yarn dlx` | Downloads the command-named package into a temporary environment by default; this is not a local-bin fallback | `--package <pkg>` selects a different provider package |
|
||||||
|
| `pnpm dlx` / `pnx` / `pnpx` | Fetches and hotloads a registry package, then runs its default binary; project trust policies are version-dependent | `--package=<pkg>` selects the provider; prefer declared dependencies plus `pnpm exec` when remote fetch is unintended |
|
||||||
|
| `deno run npm:<pkg>` | Uses an explicit npm package spec and cache; a subpath can select a binary | Pin the package/subpath and model lock, cache, lifecycle-script, and Deno permission settings |
|
||||||
|
|
||||||
|
Treat mutable tags and ranges such as `latest`, `next`, `@2`, caret, and tilde ranges as selectors, not pins. A privileged repeatable workflow needs an exact reviewed version plus lockfile/integrity enforcement where the runner supports it.
|
||||||
|
|
||||||
|
## High-Signal Patterns
|
||||||
|
|
||||||
|
### Bare executable fallback
|
||||||
|
|
||||||
|
```text
|
||||||
|
npx internal-tool
|
||||||
|
npx -y internal-tool
|
||||||
|
npm exec -- internal-tool
|
||||||
|
```
|
||||||
|
|
||||||
|
The signal is strongest in CI, release scripts, bootstrap commands, developer setup, and tool/agent configuration where the same command is run repeatedly.
|
||||||
|
|
||||||
|
### Scoped package versus unscoped bin
|
||||||
|
|
||||||
|
A scoped package can expose an unscoped executable:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "@org/tooling",
|
||||||
|
"bin": { "org-tool": "./bin/run.js" }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Inside a correctly installed workspace, `npx org-tool` may resolve `node_modules/.bin/org-tool`. Outside that tree, the same command can fall back to the public package named `org-tool`. Treat documentation, MCP configuration, and bootstrap scripts as separate execution contexts rather than assuming the repository-local result applies everywhere.
|
||||||
|
|
||||||
|
### Agent and MCP launchers
|
||||||
|
|
||||||
|
Inspect `.mcp.json`, editor/desktop agent configuration, devcontainers, and generated tool launchers for `command: npx` plus `-y` and a bare package or binary name. Combine this resolver analysis with `agentic_system_security` to determine the credentials, tools, files, and network access inherited by that process.
|
||||||
|
|
||||||
|
## Candidate Collection
|
||||||
|
|
||||||
|
Search executable surfaces and retain file, line, command, and execution context:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rg -n --no-heading -g '!node_modules' -g '!**/dist/**' \
|
||||||
|
-e '\b(npx|npm\s+exec|bunx|pnx|pnpx|pnpm\s+dlx|yarn\s+dlx)\s+[^[:space:]]+' \
|
||||||
|
-e '\bdeno\s+run\b[^\n]*\bnpm:' \
|
||||||
|
-e '"command"\s*:\s*"(npx|bunx|pnx|pnpx|pnpm|yarn|deno)"' \
|
||||||
|
-e '"args"\s*:\s*\[[^]]*"(dlx|npm:[^"]+|-y)"' \
|
||||||
|
.
|
||||||
|
```
|
||||||
|
|
||||||
|
Search the source/configuration tree rather than a fixed file list: these commands also live in
|
||||||
|
`scripts/`, husky/lint-staged hooks, `turbo.json`/`nx.json` task definitions,
|
||||||
|
`.circleci/`, composite-action `action.yml`, devcontainer `postCreateCommand`,
|
||||||
|
nested workspace `package.json` files, and editor/agent config under
|
||||||
|
`.cursor/`, `.vscode/`, and `.mcp.json`. If generated output is itself shipped or executed, search its specific directory separately instead of globally including every `dist/` artifact.
|
||||||
|
|
||||||
|
Also inspect:
|
||||||
|
|
||||||
|
- package scripts and lifecycle hooks
|
||||||
|
- workspace package `name` and `bin` maps
|
||||||
|
- READMEs and generated setup instructions
|
||||||
|
- CI composite actions and reusable workflows
|
||||||
|
- source maps or bundled package metadata that reveal internal commands
|
||||||
|
|
||||||
|
Discard paths, shell variables, flags, Node built-ins, and text that is not executed or presented as an executable command.
|
||||||
|
|
||||||
|
## Establish the Actual Resolution
|
||||||
|
|
||||||
|
Prefer inspecting the existing dependency tree, lockfile, workspace packages, and `.bin` links. Do not run `npm ci` merely to decide whether a command is local: it changes the tree and can execute lifecycle scripts.
|
||||||
|
|
||||||
|
For a version-controlled reproduction environment, record npm's registry lookup without allowing a missing package to be installed:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx --no --loglevel=http <candidate>
|
||||||
|
```
|
||||||
|
|
||||||
|
Interpret this carefully:
|
||||||
|
|
||||||
|
- a local executable may run immediately; `--no` only refuses missing-package installation
|
||||||
|
- an HTTP registry request shows fallback, not ownership or successful execution
|
||||||
|
- a cancellation naming the missing package shows npm's chosen package spec
|
||||||
|
- cache, global installs, parent directories, workspaces, and registry configuration can change the result
|
||||||
|
|
||||||
|
Repeat the resolution analysis in every context that matters: repository root, documented launch directory, CI checkout, generated agent configuration, and bootstrap-before-install flow. Do not substitute a clean empty directory for the target context except to understand npm's generic name mapping.
|
||||||
|
|
||||||
|
Do not apply `npx --no` as a generic dry-run flag. Use `bunx --no-install` only for Bun's local-resolution question. `dlx` and `deno run npm:` already name a remotely resolvable package, so validate their package spec, registry, cache/lock, selected binary or subpath, and permissions using that runner's own behavior.
|
||||||
|
|
||||||
|
## Ownership and Registry State
|
||||||
|
|
||||||
|
Query the exact registry selected by the target configuration, then distinguish:
|
||||||
|
|
||||||
|
- intended package owned by the expected publisher
|
||||||
|
- unrelated public package with the same name
|
||||||
|
- unregistered name (`404` from a functioning registry)
|
||||||
|
- private or access-controlled name (`401`/`403`)
|
||||||
|
- transient/rate-limited/blocked lookup (`429`, `5xx`, timeout)
|
||||||
|
- placeholder, reserved, disputed, or previously unpublished name
|
||||||
|
|
||||||
|
Before trusting any of those states, check whether the target's lookup path can distinguish a known existing package from a newly generated negative control. Resolve the registry from the same working directory and configuration used by the target:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Public npm example; use a known package from the actual registry when different.
|
||||||
|
task_registry="$(npm config get registry)"
|
||||||
|
npm view --registry="$task_registry" lodash name --json
|
||||||
|
npm view --registry="$task_registry" "$(openssl rand -hex 12)" name --json
|
||||||
|
```
|
||||||
|
|
||||||
|
Run the pair through the same `.npmrc`, scope routing, authentication, proxy, and egress path as the candidate. Direct `curl` requests to the public registry are a separate observation unless the target runner uses that exact route. A successful pair establishes coarse positive/negative discrimination, not authenticity of every candidate response; verify that returned documents name the requested package and contain plausible registry metadata.
|
||||||
|
|
||||||
|
If the pair fails or returns indistinguishable responses, mark the target-path registry state `UNKNOWN`. An independently verified public-registry response may characterize public state, but it does not prove what the target runner resolves. Re-confirm candidate absence before relying on it.
|
||||||
|
|
||||||
|
A `404` proves absence from that registry at that time; it does not by itself prove that registration would be accepted. Registry similarity, trademark, reservation, security-hold, and unpublish rules remain separate facts. Two concrete cases to check rather than infer:
|
||||||
|
|
||||||
|
- A registry-owned security placeholder occupies the name even when its only version is `0.0.1-security`. Do not identify one from the version alone: inspect the packument, description, dist-tags, top-level and version-level maintainers, and version publisher such as `_npmUser`.
|
||||||
|
- npm rejects new unscoped names that collide with an existing package after `.`, `-`, and `_` are removed. Normalize both the candidate and existing names: looking up only the candidate's stripped form catches `some-tool` versus `sometool`, but misses the reverse direction when the existing package contains punctuation. Treat this as registry-policy eligibility evidence, not a guarantee that registration would otherwise succeed.
|
||||||
|
|
||||||
|
When a candidate name is already registered, distinguish the target's own
|
||||||
|
organization from an unrelated party before calling it a clash. Correlate `npm owner ls <name>`, version-level publisher metadata, known target-controlled npm organizations, and independently verified repository provenance. Repository/homepage fields are self-asserted supporting evidence and do not settle ownership alone. If publisher identity remains ambiguous, mark it `UNKNOWN`.
|
||||||
|
|
||||||
|
## Validation and Impact
|
||||||
|
|
||||||
|
Demonstrate the complete resolver statement:
|
||||||
|
|
||||||
|
```text
|
||||||
|
target-controlled invocation and context
|
||||||
|
-> intended executable absent
|
||||||
|
-> exact public package spec selected
|
||||||
|
-> package ownership/availability state
|
||||||
|
-> execution trigger and inherited authority
|
||||||
|
```
|
||||||
|
|
||||||
|
Do not report an unregistered name without an execution path, or an execution path whose command is satisfied locally in every relevant context. Derive impact from the environment that executes the package: developer workstation, CI job, release pipeline, agent runtime, container build, or documentation-only workflow.
|
||||||
|
|
||||||
|
## Reporting
|
||||||
|
|
||||||
|
There is no CVE and no vulnerable installed version here, so this does not go through `create_dependency_report`; that tool requires an advisory-matched CVE. Use `create_vulnerability_report` only after the applicable core condition is fully verified.
|
||||||
|
|
||||||
|
A registry lookup or `404` alone is candidate evidence, not a working PoC. The report must preserve the target invocation and execution context, show the exact selected package and binary/module, demonstrate the runner's execution transition in a representative controlled setup without publishing the contested name, and establish the authority inherited by that process. When source is available, include the responsible invocation/configuration and concrete fix in `code_locations`.
|
||||||
|
|
||||||
|
Do not file documentation/comment-only references, locally satisfied commands, unregisterable names, ambiguous ownership, or chains that stop before package execution. Retain them as investigation notes only when useful.
|
||||||
|
|
||||||
|
Derive CVSS from the demonstrated path rather than a fixed severity label. Account for required developer/user action, registry and configuration prerequisites, runner permissions, credential availability, and the confidentiality, integrity, and availability actually exposed. A CI, release, container-build, or agent context can be severe, but the context name alone does not establish High or Critical impact.
|
||||||
|
|
||||||
|
Deduplicate by root cause, affected asset/workflow, and remediation. Combine call sites when the same configuration mistake and fix apply; keep separate findings when the same candidate name affects different products, tenants, runner semantics, authority, or fixes.
|
||||||
|
|
||||||
|
## False Positives
|
||||||
|
|
||||||
|
- The executable is provided by a declared dependency in every real execution context.
|
||||||
|
- `npx --package @scope/pkg <bin>` explicitly binds the executable to the intended package.
|
||||||
|
- A versioned package spec or scope-specific registry points to the intended publisher.
|
||||||
|
- The public package is the deliberately selected third-party tool.
|
||||||
|
- npm fetches the manifest but cannot infer or execute a bin.
|
||||||
|
- The reference appears only in generated/minified text with no executable call site.
|
||||||
|
- A registry/proxy error is misread as an unregistered name, or the target-path control pair is inconclusive.
|
||||||
|
- A package is absent but registry policy prevents the contested registration.
|
||||||
|
- The command resolves to the deliberately selected ecosystem tool and expected publisher.
|
||||||
|
- The already-registered name belongs to the target's own organization.
|
||||||
|
- An explicit `dlx` or `npm:` package spec is treated as missing-local fallback without evidence of a package/publisher mismatch.
|
||||||
|
|
||||||
|
## Remediation
|
||||||
|
|
||||||
|
- Install the intended package and invoke its local executable through an npm script.
|
||||||
|
- For npm, bind and pin the provider: `npx --package @org/tool@<version> org-tool`; use `--no` when a missing dependency must fail.
|
||||||
|
- For Bun, use `bunx --package @org/tool@<version> org-tool` and `--no-install` when remote installation is not intended.
|
||||||
|
- Replace `yarn dlx`/`pnpm dlx` in repeatable or privileged workflows with a declared, locked dependency plus the runner's local `exec` command. When ephemeral execution is required, bind and pin the provider package explicitly.
|
||||||
|
- For Deno, pin the `npm:` package and binary subpath, retain a reviewed lockfile, use cache-only operation where appropriate, and grant only the permissions the command requires.
|
||||||
|
- Route private scopes to the intended registry and prevent public fallback.
|
||||||
|
- Pin package versions and lockfiles in privileged workflows.
|
||||||
|
- Replace bare `npx -y <name>` agent launchers with reviewed, publisher-qualified, version-pinned package specs.
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
Treat package-runner confusion as an identity and execution-context bug. Prove the runner-specific transition, distinguish binary names from package names, verify registry and publisher state without equating absence with eligibility, and report only a complete execution path under the affected workflow's actual authority.
|
||||||
@@ -105,6 +105,39 @@ tree-sitter parse -q <file>
|
|||||||
|
|
||||||
Use outputs to improve route/symbol/sink maps for subsequent targeted scans.
|
Use outputs to improve route/symbol/sink maps for subsequent targeted scans.
|
||||||
|
|
||||||
|
## Cross-Component Semantic Mapping
|
||||||
|
|
||||||
|
Pattern scanners find local sinks but often miss a security decision in one component followed by a different interpretation in another. For complex middleware, proxies, frameworks, and plugin systems:
|
||||||
|
|
||||||
|
1. Identify shared request/context fields and every writer/reader.
|
||||||
|
2. Order the readers and writers by lifecycle phase: parse, route, authenticate, rewrite, authorize, dispatch, render.
|
||||||
|
3. Mark fields whose semantic type changes (URL/path, MIME/handler, alias/package, external/internal route).
|
||||||
|
4. Trace normal, error, retry, subrequest, and internal-redirect paths separately.
|
||||||
|
5. Compare the representation checked by security code with the representation consumed by the final sink.
|
||||||
|
|
||||||
|
Load `semantic_confusion` when this graph reveals overloaded fields, multiple parsers, normalization steps, or protocol translation.
|
||||||
|
|
||||||
|
## Resolution and Namespace Risks
|
||||||
|
|
||||||
|
In repositories with developer tooling, plugins, templates, or package runners, inspect lookup order rather than only dependency versions:
|
||||||
|
|
||||||
|
- command runners that fall back from local binaries or `PATH` to a public registry
|
||||||
|
- scoped/private package names exposing unscoped binary or alias names
|
||||||
|
- plugin, template, module, and autoload search paths writable by a lower-privileged actor
|
||||||
|
- CI/composite actions and devcontainer/bootstrap scripts that transitively execute package commands
|
||||||
|
- missing local artifacts that silently activate a remote or broader fallback
|
||||||
|
|
||||||
|
Record candidate names and verify ownership/existence without claiming or publishing them. A namespace gap is reportable only when the target actually resolves or executes the attacker-contestable name under realistic conditions.
|
||||||
|
|
||||||
|
For npm/JavaScript, distinguish the package name from the executable name and
|
||||||
|
model the actual working directory, dependency tree, global bin directory,
|
||||||
|
cache, and registry configuration. `load_skill(["npx_confusion"])` when a bare
|
||||||
|
`npx`/`npm exec` command may fall back from a missing executable to a public
|
||||||
|
package. Trivy cannot detect this class because no installed package version
|
||||||
|
needs to be vulnerable.
|
||||||
|
|
||||||
|
Load `infrastructure_lifecycle` when source, images, firmware, or history contain abandoned domains, provider resources, package namespaces, update URLs, mail identities, telemetry, or control endpoints. Use targeted string/dataflow analysis when this is the research question; the full baseline scanner bundle is not required merely to trace one endpoint consumer.
|
||||||
|
|
||||||
## Secret and Supply Chain Coverage
|
## Secret and Supply Chain Coverage
|
||||||
|
|
||||||
Detect hardcoded credentials:
|
Detect hardcoded credentials:
|
||||||
|
|||||||
@@ -0,0 +1,226 @@
|
|||||||
|
---
|
||||||
|
name: infrastructure-lifecycle
|
||||||
|
description: Discovery and security analysis of abandoned or ownership-drifted infrastructure trusted by software, firmware, DNS, mail, update systems, packages, scripts, telemetry, and deployed agents
|
||||||
|
---
|
||||||
|
|
||||||
|
# Infrastructure Lifecycle Trust
|
||||||
|
|
||||||
|
Use this skill when a product, application, device, image, or organization continues to trust an external name or provider resource whose ownership can expire, be deleted, be reassigned, or move outside the intended organization.
|
||||||
|
|
||||||
|
This is broader than subdomain takeover. The vulnerable asset may make outbound requests to a retired update bucket, load JavaScript from an abandoned domain, send mail to an expired MX domain, query a reassigned WHOIS/RDAP server, install from a missing package namespace, or beacon to an embedded telemetry/control endpoint. The security property is continuity of ownership across the full lifetime of every trust consumer.
|
||||||
|
|
||||||
|
## Trust-Consumer Graph
|
||||||
|
|
||||||
|
Model each dependency:
|
||||||
|
|
||||||
|
```text
|
||||||
|
consumer/version/deployment
|
||||||
|
-> embedded logical name or URL
|
||||||
|
-> DNS/provider/package resolution chain
|
||||||
|
-> current owner/controller
|
||||||
|
-> content/protocol accepted
|
||||||
|
-> privilege and trigger in the consumer
|
||||||
|
```
|
||||||
|
|
||||||
|
Record separately:
|
||||||
|
|
||||||
|
- where the reference is stored: source, binary, firmware, image layer, config, database, IaC, documentation, update metadata
|
||||||
|
- deployed versions and whether the consumer still runs
|
||||||
|
- endpoint type, resolution chain, TLS/signature/authentication requirements, and fallback order
|
||||||
|
- current registration/provider ownership and historical ownership
|
||||||
|
- request trigger, frequency, payload/data sent, and response/content interpretation
|
||||||
|
- consumer privilege: browser origin, installer/root, CI runner, mail receiver, parser, agent, or telemetry process
|
||||||
|
- decommission owner, renewal/update process, and monitoring coverage
|
||||||
|
|
||||||
|
A domain or bucket being available is only half the finding. Show that a live in-scope consumer still trusts it and what that consumer would accept.
|
||||||
|
|
||||||
|
## Control and Claimability Levels
|
||||||
|
|
||||||
|
Do not collapse these into one claim:
|
||||||
|
|
||||||
|
| Level | Evidence |
|
||||||
|
|---|---|
|
||||||
|
| Indicator | NXDOMAIN, expired registration, provider tombstone, missing package/resource |
|
||||||
|
| Authoritative availability | Registrar/provider/package authority confirms the exact name/resource can be acquired or bound |
|
||||||
|
| Acquisition/control | Authorized tester controls the registrable domain, resource, namespace, or provider binding |
|
||||||
|
| Protocol identity | Required DNS, custom-host binding, TLS certificate, authentication, or protocol handshake succeeds |
|
||||||
|
| Consumer acceptance | A live in-scope consumer contacts the controlled endpoint and accepts the relevant response semantics |
|
||||||
|
|
||||||
|
Record the highest proven level for every consumer. Before acquisition or provider binding, determine whether control can immediately receive existing third-party traffic and apply the Passive Sensor and Sinkhole plan below.
|
||||||
|
|
||||||
|
## High-Value Dependency Classes
|
||||||
|
|
||||||
|
### Update and Code Distribution
|
||||||
|
|
||||||
|
- firmware/software update URLs, manifests, package indexes, installers, drivers, VM/container images
|
||||||
|
- CDN/object-storage buckets serving binaries, scripts, templates, rules, signatures, or configuration
|
||||||
|
- browser JavaScript/CSS imports and desktop/mobile auto-update channels
|
||||||
|
- bootstrap, CI, devcontainer, build, and installation scripts
|
||||||
|
- model/agent skill, plugin, prompt, MCP server, and tool-definition update channels
|
||||||
|
|
||||||
|
Record signature, hash, certificate, pinning, version/rollback, and content-type enforcement. TLS alone authenticates the current domain controller, not continuity with the original publisher.
|
||||||
|
|
||||||
|
### Naming and Package Resolution
|
||||||
|
|
||||||
|
- missing public/private package names, scoped package versus executable alias, plugin/module/template namespaces
|
||||||
|
- `PATH`, autoload, search path, registry, cache, mirror, and remote fallback order
|
||||||
|
- provider-generated hostnames or globally unique resource names released on deletion
|
||||||
|
- legacy aliases retained in manifests, lockfiles, scripts, or installed products
|
||||||
|
|
||||||
|
Do not register or publish candidate names merely to test them without explicit authorization and a containment plan. Prove the consumer's resolution behavior first.
|
||||||
|
|
||||||
|
Registry "missing" responses are not interchangeable with "claimable".
|
||||||
|
Similarity, reservation, security-hold, dispute, and unpublish rules can block
|
||||||
|
a name that returns `404`; verify ownership and registry policy separately.
|
||||||
|
Load `npx_confusion` when the consumer first treats a missing executable as an
|
||||||
|
npm package spec. Model other ecosystems independently rather than assuming
|
||||||
|
npm's resolution order applies to them.
|
||||||
|
|
||||||
|
### Mail and Identity
|
||||||
|
|
||||||
|
- expired organizational, supplier, recovery, notification, or former employee domains
|
||||||
|
- MX targets and catch-all aliases that remain in applications, address books, SSO, password recovery, certificates, or vendor accounts
|
||||||
|
- OAuth redirect/logout URIs, SAML endpoints, webhook callbacks, CORS/CSP allowlists, and trusted-origin lists tied to retired hosts
|
||||||
|
- domain-based tenant verification and support/administrative identity flows
|
||||||
|
|
||||||
|
Differentiate ability to receive a tester-created message from interception of real correspondence. Do not access unrelated mail or use received secrets/credentials.
|
||||||
|
|
||||||
|
### Telemetry, Control, and Protocol Infrastructure
|
||||||
|
|
||||||
|
- crash reporting, analytics, licensing, activation, NTP/DNS, support, and health-check endpoints
|
||||||
|
- hardcoded agent/controller, webshell/C2, webhook, exfiltration, or callback domains embedded in deployed systems
|
||||||
|
- hardcoded retired WHOIS/RDAP endpoints, certificate validation services, keyservers, mirrors, proxies, and service-discovery dependencies
|
||||||
|
- local/remote management domains in appliances, mobile apps, extensions, and container images
|
||||||
|
|
||||||
|
Treat unexpected inbound traffic as potentially sensitive. Passive receipt does not authorize interaction, command issuance, credential use, or expansion beyond the approved sensor purpose.
|
||||||
|
|
||||||
|
## Discovery
|
||||||
|
|
||||||
|
### Source, Image, and Firmware Corpus
|
||||||
|
|
||||||
|
Extract hostnames, URLs, email domains, bucket names, package names, registry endpoints, and certificate subjects from:
|
||||||
|
|
||||||
|
- source and history, lockfiles, CI/IaC, release assets, SBOMs
|
||||||
|
- container/VM layers including deleted-file history
|
||||||
|
- firmware rootfs, strings/resources, scripts, configs, examples, and updater logic
|
||||||
|
- JavaScript/mobile/desktop bundles, extensions, templates, and documentation
|
||||||
|
- logs and network captures from controlled normal operation
|
||||||
|
|
||||||
|
Use staged extraction rather than relying on one broad regex:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# URLs and email addresses
|
||||||
|
rg -n -i 'https?://|wss?://|s3[.-]|blob\.core\.|[A-Z0-9._%+-]+@[A-Z0-9.-]+' extracted/
|
||||||
|
|
||||||
|
# Then query format-aware config keys, DNS/MX data, certificate metadata,
|
||||||
|
# package manifests, and binary strings for bare hostnames/namespaces.
|
||||||
|
```
|
||||||
|
|
||||||
|
Review bare-hostname candidates for prose, source-map, test, and generated-data false positives. Deduplicate content-addressed layers and repeated vendor boilerplate so prevalence is not inflated. Preserve the source file, artifact hash, version, and surrounding semantic context for every candidate.
|
||||||
|
|
||||||
|
### Ownership and Resolution History
|
||||||
|
|
||||||
|
- Resolve A/AAAA/CNAME/NS/MX/TXT/CAA and retain complete chains.
|
||||||
|
- Check current registrar/provider resource state through authoritative sources, including custom-domain binding and reservation rules.
|
||||||
|
- Use historical DNS, CT, WHOIS/RDAP, package metadata, source history, and release timelines to establish ownership drift.
|
||||||
|
- Identify wildcard/catch-all responses, parked domains, provider tombstones, and reused cloud IPs that mimic availability.
|
||||||
|
- Compare vulnerable/current builds to learn whether the reference was removed, replaced, or cryptographically hardened. Record CAA, DNSSEC/DANE where relevant, certificate issuance/custom-host requirements, pinning, embedded trust stores, and independent content signatures.
|
||||||
|
|
||||||
|
Do not rely on an HTTP `404`, NXDOMAIN, or “NoSuchBucket” alone. Providers reserve names, enforce ownership verification, or return identical errors for owned/private resources.
|
||||||
|
|
||||||
|
### Live Consumer Confirmation
|
||||||
|
|
||||||
|
Within scope, observe a controlled consumer through:
|
||||||
|
|
||||||
|
- offline code/dataflow from trigger to request and response consumer
|
||||||
|
- DNS/HTTP proxy logs in a lab
|
||||||
|
- packet capture or process/network tracing during a normal test operation
|
||||||
|
- a tester-owned canary endpoint configured through a supported setting
|
||||||
|
- already-authorized sensor/sinkhole telemetry
|
||||||
|
|
||||||
|
Record request method/protocol, SNI/Host, headers, authentication, body data classification, retry cadence, TLS verification, and how the response is parsed or executed.
|
||||||
|
|
||||||
|
## Security Analysis
|
||||||
|
|
||||||
|
Ask in order:
|
||||||
|
|
||||||
|
1. Can ownership/control actually transfer to an unrelated party?
|
||||||
|
2. Does an in-scope deployed consumer still resolve or contact it?
|
||||||
|
3. What authenticity/integrity checks survive endpoint takeover?
|
||||||
|
4. What response fields/content/protocol messages can the controller influence?
|
||||||
|
5. Under what identity and privilege does the consumer process them?
|
||||||
|
6. Is the trigger automatic, scheduled, administrative, user-driven, or update-only?
|
||||||
|
7. What population and versions remain affected?
|
||||||
|
8. What claimability level is proven, and is acquisition necessary for the remaining questions?
|
||||||
|
9. Could acquisition receive out-of-scope traffic or data?
|
||||||
|
10. Does this name serve several distinct consumers that require separate semantics and impact analysis?
|
||||||
|
|
||||||
|
High-impact patterns include:
|
||||||
|
|
||||||
|
- unsigned or weakly verified update/package content processed with system/administrator privilege
|
||||||
|
- JavaScript loaded under a trusted web origin or CSP allowlist
|
||||||
|
- mail/recovery/identity messages delivered to a re-registered domain
|
||||||
|
- secrets or device metadata automatically sent to a reassigned endpoint
|
||||||
|
- trusted control/telemetry responses parsed as commands, config, templates, or executable content
|
||||||
|
- CA/domain verification, service discovery, or protocol logic depending on mutable external ownership
|
||||||
|
|
||||||
|
## Passive Sensor and Sinkhole Handling
|
||||||
|
|
||||||
|
Operating a domain or provider resource that receives real third-party traffic is a separate data-handling activity, not ordinary proof-of-concept hosting. Before enabling it, define:
|
||||||
|
|
||||||
|
- written authorization and legal/privacy owner
|
||||||
|
- accepted protocols and non-interaction policy
|
||||||
|
- collection minimization, encryption, access control, retention, deletion, and redaction
|
||||||
|
- handling for credentials, personal data, malware, or out-of-scope victims
|
||||||
|
- notification/escalation and provider/registrar coordination
|
||||||
|
- prohibition on commands, authentication attempts, payload delivery, or use of received secrets
|
||||||
|
|
||||||
|
Prefer aggregate metadata or a unique tester-controlled canary. Do not deliberately expose a genuinely vulnerable product to collect wild exploitation without separate deployment authorization and containment review.
|
||||||
|
|
||||||
|
## Relationship to Other Skills
|
||||||
|
|
||||||
|
- Load `subdomain_takeover` for dangling DNS records or custom-domain provider bindings. Ordinary expiration/re-registration of a registrable domain, MX identity, or embedded software endpoint remains in this skill.
|
||||||
|
- Load `source_aware_sast` for targeted source/dataflow confirmation; string presence does not prove current ownership or live consumption.
|
||||||
|
- Load `agentic_system_security` only when the endpoint supplies or controls AI skills, plugins, MCP/model adapters, tool definitions, or effective agent authority.
|
||||||
|
- Load `semantic_confusion` only when a security decision and privileged consumer use different endpoint/package/alias representations or resolution results. Pure temporal ownership drift does not require it.
|
||||||
|
|
||||||
|
## Validation Deliverable
|
||||||
|
|
||||||
|
Include:
|
||||||
|
|
||||||
|
1. exact consumer artifact/version/deployment and reference location
|
||||||
|
2. full DNS/provider/package resolution and current ownership evidence
|
||||||
|
3. historical ownership/decommission timeline
|
||||||
|
4. live or source-confirmed request trigger and accepted response semantics
|
||||||
|
5. TLS/signature/hash/authentication behavior
|
||||||
|
6. consumer privilege, affected population, and configuration prerequisites
|
||||||
|
7. controlled ownership/canary evidence where authorized
|
||||||
|
8. highest claimability level and confidence in live-consumer/prevalence evidence
|
||||||
|
9. sensor/data-handling authorization when acquisition could receive existing traffic
|
||||||
|
10. separate impact analysis for each mail, identity, update, telemetry, code, or control consumer
|
||||||
|
11. remediation across both the endpoint and every retained consumer
|
||||||
|
|
||||||
|
## Common False Positives
|
||||||
|
|
||||||
|
- NXDOMAIN/provider tombstone with a name that cannot be registered or bound.
|
||||||
|
- A hardcoded URL present only in dead code, examples, tests, or an undeployed version.
|
||||||
|
- Live requests go to a vendor-controlled wildcard/catch-all despite an apparently missing specific resource.
|
||||||
|
- Update content is independently signed and the reassigned endpoint cannot produce an accepted artifact; this usually blocks forged-code impact, but metadata exposure, update suppression, unsigned manifest fields, and rollback/version behavior still require analysis.
|
||||||
|
- Expired domain appears in documentation but is absent from authentication, mail, software, and deployed configuration.
|
||||||
|
- A package name is unregistered but the consumer is pinned to a private registry with no public fallback, the scope is routed by `.npmrc`, or the command is already satisfied by a locally installed binary.
|
||||||
|
- The name is unregistered but registry policy, reservation, dispute, or unpublish state prevents the contested registration.
|
||||||
|
- Inbound sensor traffic cannot be attributed to an in-scope consumer/version.
|
||||||
|
|
||||||
|
## Remediation
|
||||||
|
|
||||||
|
- Remove or replace references in every supported and still-deployed version.
|
||||||
|
- Retain defensive ownership of externally embedded domains/resource names for the consumer's realistic lifetime.
|
||||||
|
- Sign update/config/package content with independently managed, rotatable keys and enforce rollback/version policy.
|
||||||
|
- Eliminate implicit public fallback; pin registries, publishers, hashes, and plugin identities.
|
||||||
|
- Inventory domain/MX/provider/package dependencies in decommission workflows and continuous monitoring.
|
||||||
|
- Revoke old credentials/tokens, rotate trust, and provide a migration/kill-switch path for stranded clients.
|
||||||
|
- Monitor DNS, CT, registrar, provider binding, package namespace, and live outbound traffic for ownership drift.
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
External names are long-lived security dependencies. Track every consumer to its current controller, prove that deployed software still trusts the endpoint, analyze the authenticity checks and processing privilege, and manage ownership for as long as any supported or abandoned client can call home.
|
||||||
@@ -0,0 +1,181 @@
|
|||||||
|
---
|
||||||
|
name: electron-desktop-apps
|
||||||
|
description: Test Electron desktop applications across renderer, preload, IPC, main-process, navigation, custom-protocol, storage, permission, and update trust boundaries; use for packaged Electron apps, ASAR review, web-to-native capability analysis, and Electron-specific exploit chains
|
||||||
|
---
|
||||||
|
|
||||||
|
# Electron Desktop Applications
|
||||||
|
|
||||||
|
Use this skill for Electron applications. Other webview desktop frameworks may share the high-level web-to-native trust question, but their bridge, sandbox, update, and process APIs differ; do not apply Electron-specific conclusions to NW.js, CEF, Tauri, or Wails without mapping that framework separately.
|
||||||
|
|
||||||
|
Pair this skill with `browser_security` for browser state and navigation, `xss` for renderer injection, `argument_injection` for native subprocess launches, and `insecure_deserialization` or `rce` for a main-process sink.
|
||||||
|
|
||||||
|
## Architecture and Authority Map
|
||||||
|
|
||||||
|
Inventory each security principal and the capabilities crossing between them:
|
||||||
|
|
||||||
|
```text
|
||||||
|
origin + document + frame
|
||||||
|
-> renderer JavaScript
|
||||||
|
-> preload isolated world
|
||||||
|
-> contextBridge API
|
||||||
|
-> IPC channel
|
||||||
|
-> sender/argument/identity checks
|
||||||
|
-> main process or utility process
|
||||||
|
-> filesystem, process, credential, media, network, update, or OS action
|
||||||
|
```
|
||||||
|
|
||||||
|
Record:
|
||||||
|
|
||||||
|
- Electron, Chromium, Node, and application versions
|
||||||
|
- packaging form, `app.asar`, unpacked resources, entry point, and fuses
|
||||||
|
- every `BrowserWindow`, `WebContentsView`, `<webview>`, session/partition, and child window
|
||||||
|
- `webPreferences`: `preload`, `nodeIntegration`, `contextIsolation`, `sandbox`, `webSecurity`, `allowRunningInsecureContent`, experimental features, and subframe/worker integration
|
||||||
|
- every preload export and every `ipcMain.handle`/`ipcMain.on` consumer
|
||||||
|
- origins/documents/frames that can reach each exported API
|
||||||
|
- custom protocols, deep links, navigation helpers, permissions, downloads, storage, and update channels
|
||||||
|
|
||||||
|
Do not infer authority from a setting or channel name alone. Follow one request from renderer input to the main-process side effect and record each authorization decision.
|
||||||
|
|
||||||
|
## Package and Source Reconnaissance
|
||||||
|
|
||||||
|
Extract the application bundle with a reviewed, version-pinned ASAR implementation or inspect an already unpacked `resources/app` tree. Locate `package.json#main`, preload paths, build metadata, Electron version, native modules, and update configuration.
|
||||||
|
|
||||||
|
Search for:
|
||||||
|
|
||||||
|
```text
|
||||||
|
BrowserWindow WebContentsView webviewTag webPreferences
|
||||||
|
preload contextBridge.exposeInMainWorld ipcRenderer
|
||||||
|
ipcMain.handle ipcMain.on webContents.ipc
|
||||||
|
will-navigate will-frame-navigate will-redirect
|
||||||
|
setWindowOpenHandler loadURL loadFile openExternal
|
||||||
|
setPermissionRequestHandler registerSchemesAsPrivileged
|
||||||
|
setAsDefaultProtocolClient open-url second-instance
|
||||||
|
autoUpdater electron-updater
|
||||||
|
```
|
||||||
|
|
||||||
|
Treat decompiled or bundled JavaScript as a hypothesis when source maps, minification, generated IPC bindings, or runtime feature flags can change the installed behavior.
|
||||||
|
|
||||||
|
## Preload and Context-Bridge Analysis
|
||||||
|
|
||||||
|
A preload script has privileged Electron/Node access even when `nodeIntegration` is disabled. With context isolation, it can still expose selected functions and values into the page's main world.
|
||||||
|
|
||||||
|
Classify every export:
|
||||||
|
|
||||||
|
- narrow operation with fixed channel and validated arguments
|
||||||
|
- caller-selected channel or event name
|
||||||
|
- direct exposure of `ipcRenderer`, Node/Electron modules, filesystem/process objects, or mutable privileged objects
|
||||||
|
- callback/event registration that leaks the raw IPC event or privileged objects
|
||||||
|
- secret/session/storage access
|
||||||
|
- operation whose authorization exists only in renderer JavaScript
|
||||||
|
|
||||||
|
A generic `send(channel, ...)` or `invoke(channel, ...)` bridge expands the renderer's candidate capability set, but the registered handler list is not the ACL. For each handler, inspect:
|
||||||
|
|
||||||
|
- `event.senderFrame` URL/origin and frame identity validation
|
||||||
|
- expected `webContents`, window, session/partition, and application state
|
||||||
|
- user/tenant authorization and request provenance
|
||||||
|
- argument schema, paths, URLs, command options, and object deserialization
|
||||||
|
- result exposure and event subscriptions
|
||||||
|
|
||||||
|
An IPC handler's existence does not prove an untrusted frame can invoke it successfully.
|
||||||
|
|
||||||
|
## Navigation and Window Boundaries
|
||||||
|
|
||||||
|
Web preferences belong to a `webContents`; navigation does not automatically turn a privileged window into an ordinary browser tab. A configured preload can run for newly loaded documents and expose its bridge to content that was never intended to receive it.
|
||||||
|
|
||||||
|
Map all navigation causes:
|
||||||
|
|
||||||
|
- user- or page-initiated main-frame navigation (`will-navigate`)
|
||||||
|
- subframe navigation (`will-frame-navigate`)
|
||||||
|
- server redirects (`will-redirect`)
|
||||||
|
- new windows and popups (`setWindowOpenHandler`)
|
||||||
|
- application calls to `loadURL`, `loadFile`, history APIs, or routing helpers
|
||||||
|
- custom-protocol redirects and external-link handlers
|
||||||
|
|
||||||
|
`will-navigate` does not cover every programmatic navigation, so the event's presence is not complete enforcement.
|
||||||
|
|
||||||
|
Parse candidate URLs with `URL` and compare explicit protocol, origin/host, port, and path rules. Do not use string-prefix checks such as `startsWith("https://trusted.example")`. Apply the same canonical policy to initial loads, redirects, frames, popups, programmatic loads, and externally opened URLs.
|
||||||
|
|
||||||
|
Before calling `shell.openExternal`, validate the scheme and complete destination expected by the feature. Treat `file:`, custom schemes, handler-specific arguments, credentials in URLs, and ambiguous encodings as separate cases.
|
||||||
|
|
||||||
|
## Node, Isolation, and Sandbox Settings
|
||||||
|
|
||||||
|
- `nodeIntegration: true` in a renderer that can execute untrusted script directly exposes Node capability and commonly turns renderer injection into native code execution.
|
||||||
|
- `contextIsolation: false` weakens the boundary between page and preload worlds but is not, by itself, proof of native code execution.
|
||||||
|
- `sandbox: false` removes Chromium process isolation; determine which preload or renderer capabilities become reachable rather than reporting the flag alone.
|
||||||
|
- `webSecurity: false`, `allowRunningInsecureContent`, permissive experimental features, and unsafe `<webview>` preferences change separate browser boundaries and must be traced to an exploit path.
|
||||||
|
- `nodeIntegrationInSubFrames` and preload injection into frames require frame-by-frame sender and origin analysis.
|
||||||
|
|
||||||
|
Record Electron-version defaults. A missing explicit setting can mean different behavior on different major releases.
|
||||||
|
|
||||||
|
## Custom Protocols and Deep Links
|
||||||
|
|
||||||
|
Treat OS-delivered URLs and second-instance command lines as attacker-controlled inputs:
|
||||||
|
|
||||||
|
```text
|
||||||
|
OS handler / browser / document
|
||||||
|
-> custom scheme or argv
|
||||||
|
-> URL/argument parsing
|
||||||
|
-> application router
|
||||||
|
-> renderer navigation or native operation
|
||||||
|
```
|
||||||
|
|
||||||
|
Test authority and parser boundaries for host/path normalization, duplicate parameters, encoding depth, file paths, option injection, and cross-profile/account routing. Confirm which application instance and user session receives the event.
|
||||||
|
|
||||||
|
For custom application protocols, record whether the scheme is registered as secure, standard, CORS-enabled, stream-capable, or privileged, and how that affects origin and storage behavior.
|
||||||
|
|
||||||
|
## Permissions, Storage, and Secrets
|
||||||
|
|
||||||
|
Map session permission handlers for media, notifications, geolocation, clipboard, display capture, USB/HID/serial, filesystem access, and external protocols. Verify decisions use the requesting frame/origin and cannot be inherited from a more trusted window.
|
||||||
|
|
||||||
|
Inventory secrets and capability-bearing state reachable from renderer or preload code:
|
||||||
|
|
||||||
|
- tokens, cookies, session identifiers, recovery material, and encryption keys
|
||||||
|
- IndexedDB, local/session storage, cookies, cache, filesystem databases, and keychain wrappers
|
||||||
|
- local service ports, named pipes, Unix sockets, and authentication material
|
||||||
|
|
||||||
|
At-rest encryption does not protect data when the renderer can retrieve the key or ask a privileged bridge to decrypt it.
|
||||||
|
|
||||||
|
## Updates and Native Extensions
|
||||||
|
|
||||||
|
Trace the update pipeline as an executable supply chain:
|
||||||
|
|
||||||
|
- feed URL and channel selection
|
||||||
|
- TLS identity, redirects, proxy behavior, and metadata parsing
|
||||||
|
- artifact signature and publisher verification
|
||||||
|
- version/rollback policy and staged update state
|
||||||
|
- native modules, helper binaries, installers, and post-update hooks
|
||||||
|
|
||||||
|
An attacker-controlled feed is not automatically native code execution if independent artifact signatures are mandatory. Conversely, HTTPS does not compensate for missing artifact authenticity or unsafe rollback behavior.
|
||||||
|
|
||||||
|
## Validation
|
||||||
|
|
||||||
|
- Record the exact installed build, Electron version, preferences, preload, handler, and current document/frame origin.
|
||||||
|
- Demonstrate the complete path from attacker-controlled input or renderer state to the main-process operation.
|
||||||
|
- Capture sender-validation and argument-validation outcomes, not only successful IPC transport.
|
||||||
|
- Re-test after cross-origin navigation, redirect, frame creation, window creation, and session/profile changes.
|
||||||
|
- Separate renderer script execution, bridge access, accepted IPC, privileged data access, filesystem/process control, and native code execution.
|
||||||
|
|
||||||
|
## False Positives
|
||||||
|
|
||||||
|
- A preload or handler exists but the tested document/frame cannot reach it.
|
||||||
|
- A channel is registered but rejects the sender, identity, state, or arguments.
|
||||||
|
- `contextIsolation` or sandboxing is disabled without a reachable privileged API.
|
||||||
|
- Navigation is blocked on user links but still possible through application code, or vice versa.
|
||||||
|
- A remote page has no preload export, Node integration, IPC route, or privileged permission.
|
||||||
|
- An update feed is mutable but every artifact and version transition is independently authenticated.
|
||||||
|
- A secret-looking value is scoped to synthetic/test data or cannot authorize any downstream action.
|
||||||
|
|
||||||
|
## Remediation
|
||||||
|
|
||||||
|
- Load local application UI and isolate remote content in an unprivileged `WebContentsView` or external browser.
|
||||||
|
- Keep Node integration disabled, context isolation enabled, and renderer sandboxing enabled.
|
||||||
|
- Expose narrow preload APIs with fixed operations and strict schemas.
|
||||||
|
- Validate every IPC sender frame, application identity, authorization context, and argument in the main process.
|
||||||
|
- Parse and allowlist navigation destinations consistently across every navigation path.
|
||||||
|
- Restrict permissions per session and requesting origin.
|
||||||
|
- Keep credentials and encryption keys outside renderer reach.
|
||||||
|
- Authenticate update metadata and artifacts, enforce rollback policy, and pin publishers.
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
Electron security depends on which document and frame can reach which native capability. Map navigation, preload exports, IPC sender checks, permissions, storage, protocols, and updates as one authority graph, then validate the entire path to the privileged operation.
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
---
|
||||||
|
name: hurl
|
||||||
|
description: Reproducible, reviewable HTTP request chains and response assertions with Hurl for authorized multi-step security validation, vulnerable-versus-fixed regression cases, captured values, and low-rate semantic oracles
|
||||||
|
---
|
||||||
|
|
||||||
|
# Hurl Security Regression Playbook
|
||||||
|
|
||||||
|
Use [Hurl](https://hurl.dev/) when a security proof requires an ordered HTTP session whose requests, captured values, and assertions should be code-reviewed and replayed. It is well suited to authentication flows, redirects, cookies, CSRF tokens, upload lifecycles, patch regression, and paired semantic-differential cases.
|
||||||
|
|
||||||
|
Hurl sends exactly what the file describes. It does not make state-changing requests safe. Review scope, methods, targets, and captured secrets before every run.
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
Prefer an official release binary or package. On macOS:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
brew install hurl
|
||||||
|
hurl --version
|
||||||
|
```
|
||||||
|
|
||||||
|
Official alternatives include release packages and `cargo install --locked hurl`; see [installation](https://hurl.dev/docs/installation.html). Record the tool version with results.
|
||||||
|
|
||||||
|
## Minimal Chain
|
||||||
|
|
||||||
|
```hurl
|
||||||
|
# lab-regression.hurl
|
||||||
|
GET {{base_url}}/session
|
||||||
|
HTTP 200
|
||||||
|
[Captures]
|
||||||
|
csrf: xpath "string(//input[@name='csrf']/@value)"
|
||||||
|
[Asserts]
|
||||||
|
header "Content-Type" startsWith "text/html"
|
||||||
|
|
||||||
|
POST {{base_url}}/action
|
||||||
|
Content-Type: application/x-www-form-urlencoded
|
||||||
|
[FormParams]
|
||||||
|
csrf: {{csrf}}
|
||||||
|
operation: noop
|
||||||
|
HTTP 204
|
||||||
|
```
|
||||||
|
|
||||||
|
Hurl keeps cookies across requests in the same file, so an explicit `Cookie` header is unnecessary here.
|
||||||
|
|
||||||
|
Run one reviewed case against one authorized target first:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
hurl --test --jobs 1 --connect-timeout 5s --max-time 15s \
|
||||||
|
--variable base_url=https://lab.example lab-regression.hurl
|
||||||
|
```
|
||||||
|
|
||||||
|
When credentials are required, pass them with `--secrets-file local-secrets.env`, keep that file outside version control, and avoid verbose/debug output that could expose headers or bodies. Use `--variables-file` only for non-secret environment values.
|
||||||
|
|
||||||
|
## Designing a Security Regression
|
||||||
|
|
||||||
|
- Assert the security invariant, not only a status code: denied identity, final normalized location, absence/presence of a structural field, unchanged object state, or exact benign result.
|
||||||
|
- Capture only values needed by later requests. Do not write tokens, personal data, or response bodies into committed reports.
|
||||||
|
- Encode a malformed but non-triggering control alongside the suspected case.
|
||||||
|
- Run the same file against vulnerable and fixed builds through `base_url` or other explicit variables.
|
||||||
|
- Keep state-changing methods in a clearly labeled lab/staging file; prefer no-op actions, inert markers, and cleanup requests.
|
||||||
|
- Check every redirect step when the vulnerability crosses routing, origin, or authentication boundaries. Blindly following redirects can hide the relevant transition.
|
||||||
|
- Use unique canaries so cached or pre-existing state cannot create a false positive.
|
||||||
|
|
||||||
|
## Chain Structure
|
||||||
|
|
||||||
|
Organize longer files around capability transitions:
|
||||||
|
|
||||||
|
```text
|
||||||
|
fingerprint -> establish session -> reach boundary -> prove primitive -> verify state -> cleanup
|
||||||
|
```
|
||||||
|
|
||||||
|
At each response, assert the condition required by the next request. A final success assertion cannot explain which earlier assumption failed.
|
||||||
|
|
||||||
|
Useful Hurl features include:
|
||||||
|
|
||||||
|
- captures from headers, cookies, JSONPath, XPath, and regex queries
|
||||||
|
- assertions over status, headers, body, JSON/XML, redirects, and timing
|
||||||
|
- request-local options and variables
|
||||||
|
- `--test` plus JSON, JUnit, TAP, or HTML reports
|
||||||
|
|
||||||
|
Consult the [Hurl manual](https://hurl.dev/docs/manual.html) for version-specific syntax instead of guessing an option.
|
||||||
|
|
||||||
|
## Safety Rules
|
||||||
|
|
||||||
|
- Use an explicit `base_url`; never derive the destination from untrusted response data without validating scheme, host, and port.
|
||||||
|
- Review POST/PUT/PATCH/DELETE requests and server-side side effects before replay.
|
||||||
|
- Set bounded timeouts and retries for the target; do not use polling as an unbounded brute-force loop.
|
||||||
|
- Do not use Hurl for raw HTTP parser/smuggling cases when its HTTP stack normalizes the bytes being tested; use an appropriate raw harness in an isolated lab.
|
||||||
|
- Use `--path-as-is` when literal `/../` or `/./` path segments are the behavior under test; otherwise Hurl's underlying URL handling can normalize them.
|
||||||
|
- Redact reports. HTML/JSON/JUnit artifacts may contain request URLs, headers, captured variables, and response snippets.
|
||||||
|
- Keep authentication material in local secret storage and use dedicated test accounts with minimum privilege.
|
||||||
|
|
||||||
|
## Validation Deliverable
|
||||||
|
|
||||||
|
1. reviewed `.hurl` file with variableized target and no embedded secrets
|
||||||
|
2. vulnerable, fixed, and negative-control environment descriptions
|
||||||
|
3. assertion at every capability transition
|
||||||
|
4. deterministic results with tool version and timestamps
|
||||||
|
5. side effects, cleanup, and residual-state check
|
||||||
|
6. redacted report appropriate for sharing
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
---
|
||||||
|
name: hypothesis
|
||||||
|
description: Property-based local differential testing with Hypothesis for parsers, canonicalizers, serializers, validators, routers, and other pure functions, emphasizing explicit invariants, shrinking, reproducibility, and bounded resource use
|
||||||
|
---
|
||||||
|
|
||||||
|
# Hypothesis Differential Testing
|
||||||
|
|
||||||
|
Use [Hypothesis](https://hypothesis.readthedocs.io/) when a security property can be expressed over local code and failures are likely to hide in combinations of encoding, normalization, structure, or parser recovery. It is especially useful for comparing two implementations or checking that validation and consumption preserve the same meaning.
|
||||||
|
|
||||||
|
Do not point unrestricted generators at a live service. Hypothesis is safest and most useful against pure local adapters with no network, subprocess, filesystem, or persistent-state side effects.
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
Use an isolated virtual environment and install a reviewed pinned version:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python -m pip install 'hypothesis==<reviewed-version>'
|
||||||
|
```
|
||||||
|
|
||||||
|
Official project: [Hypothesis](https://github.com/HypothesisWorks/hypothesis)
|
||||||
|
|
||||||
|
## Start From an Invariant
|
||||||
|
|
||||||
|
Write the security relationship before writing strategies. Examples:
|
||||||
|
|
||||||
|
```text
|
||||||
|
allowlist(raw) implies sink(canonicalize(raw)) remains inside the allowed origin/path
|
||||||
|
validator(raw) accepts implies consumer(raw) assigns the same media type/structure
|
||||||
|
parse_A(raw) and parse_B(raw) agree on message boundaries and authoritative fields
|
||||||
|
serialize(parse(raw)) cannot introduce a delimiter, wildcard, traversal, or new field
|
||||||
|
```
|
||||||
|
|
||||||
|
A test that only checks “does not crash” can find robustness bugs but does not establish a security differential.
|
||||||
|
|
||||||
|
## Minimal Differential Harness
|
||||||
|
|
||||||
|
```python
|
||||||
|
from hypothesis import given, settings, strategies as st
|
||||||
|
|
||||||
|
|
||||||
|
def outcome(parser, raw):
|
||||||
|
try:
|
||||||
|
return ("accept", parser(raw))
|
||||||
|
except ExpectedParseError as exc:
|
||||||
|
return ("reject", type(exc).__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@settings(max_examples=250, deadline=500)
|
||||||
|
@given(st.text(max_size=128))
|
||||||
|
def test_security_boundary(raw: str) -> None:
|
||||||
|
checked = outcome(security_parser, raw)
|
||||||
|
consumed = outcome(sink_parser, raw)
|
||||||
|
assert equivalent_security_meaning(checked, consumed)
|
||||||
|
```
|
||||||
|
|
||||||
|
- Bound string/list/binary sizes, recursion, examples, and deadline.
|
||||||
|
- Build structured inputs from relevant tokens rather than generating unrestricted noise.
|
||||||
|
- Normalize expected accept/reject/error outcomes explicitly so ordinary parser rejection is not mistaken for a property-test failure.
|
||||||
|
- Use `st.one_of`, `st.sampled_from`, `st.lists`, `st.binary`, `st.text`, and composite strategies to represent the actual grammar.
|
||||||
|
- Add explicit edge seeds with `@example` for known delimiters and regressions.
|
||||||
|
- Let Hypothesis shrink failures; the minimal counterexample is often the clearest explanation of the parser disagreement.
|
||||||
|
|
||||||
|
## High-Value Strategy Axes
|
||||||
|
|
||||||
|
- percent and double encoding, malformed escapes, mixed separators
|
||||||
|
- Unicode normalization, replacement characters, surrogates, case folding, IDNA
|
||||||
|
- dot segments, slash/backslash, absolute/relative paths, sibling-prefix collisions
|
||||||
|
- duplicate, empty, first/last, comma-joined, or differently cased fields
|
||||||
|
- declared length versus actual bytes, truncation, padding, and terminators
|
||||||
|
- nested objects, parser depth, ordering, unknown keys, and error recovery
|
||||||
|
- serialize/deserialize round trips and version-to-version behavior
|
||||||
|
|
||||||
|
Generate only axes supported by the target's transformation graph. Cartesian payload spraying obscures causality.
|
||||||
|
|
||||||
|
## Reproducibility
|
||||||
|
|
||||||
|
- Keep the minimized failing example as a normal regression test.
|
||||||
|
- Preserve code revision, dependency lock, locale, platform, and parser/library versions.
|
||||||
|
- Keep Hypothesis's example database in a task-specific artifact directory when replay across runs matters.
|
||||||
|
- For CI, rely on stored explicit regressions for critical cases; randomized discovery supplements them.
|
||||||
|
- Classify nondeterminism before suppressing health checks. Timing, global state, environment, and shared caches can create flaky false differentials.
|
||||||
|
|
||||||
|
## Safety and Resource Controls
|
||||||
|
|
||||||
|
- Adapt target functions so tests cannot reach the network or execute commands.
|
||||||
|
- Use temporary directories and non-secret corpora for parsers that require files.
|
||||||
|
- Put native parsers in a disposable, networkless process/container with CPU, memory, file-size, and process ceilings.
|
||||||
|
- Do not disable deadlines globally to hide hangs; isolate and bound intentionally slow examples.
|
||||||
|
- A crash, timeout, or excessive allocation is a robustness result. Prove a security boundary or exploitability separately.
|
||||||
|
- Never reuse captured credentials, customer content, or production requests as generative corpora without sanitization.
|
||||||
|
|
||||||
|
## Validation Deliverable
|
||||||
|
|
||||||
|
1. stated invariant and why it protects a security boundary
|
||||||
|
2. adapters and exact component/version pair compared
|
||||||
|
3. bounded strategies and resource settings
|
||||||
|
4. minimized counterexample and both interpretations
|
||||||
|
5. stable explicit regression test
|
||||||
|
6. impact trace from disagreement to privileged consumer
|
||||||
|
7. fixed-version or corrected-invariant result
|
||||||
@@ -0,0 +1,207 @@
|
|||||||
|
---
|
||||||
|
name: agentic-system-security
|
||||||
|
description: Security testing for authorized AI agents and MCP-style tool ecosystems, covering effective authority, tool/resource/prompt inventory, confused-deputy behavior, side-effect authorization, cross-tenant isolation, executable component supply chain, shadow integrations, and repeatable safety regression
|
||||||
|
---
|
||||||
|
|
||||||
|
# Agentic System Security
|
||||||
|
|
||||||
|
Use this skill when an AI system can select tools, retrieve resources, invoke remote/local services, maintain memory, delegate to other agents, or install skills/plugins. Pair it with `llm_prompt_injection` for instruction attacks and classic vulnerability skills for the downstream HTTP, cloud, filesystem, identity, or code-execution sink.
|
||||||
|
|
||||||
|
Prompt text is not an authorization boundary. Treat the agent runtime as a confused deputy whose effective authority is bounded by the union of its credentials, tools, resources, network reach, filesystem access, delegated agents, and approval policy, then reduce that upper bound to the actually reachable subset by tracing token audience, scopes, routing, target authorization, environment, and approval flow.
|
||||||
|
|
||||||
|
## Effective-Authority Map
|
||||||
|
|
||||||
|
Draw the complete path:
|
||||||
|
|
||||||
|
```text
|
||||||
|
user / external content
|
||||||
|
-> model context and memory
|
||||||
|
-> planner / router / policy
|
||||||
|
-> tool or delegated agent
|
||||||
|
-> credential and target system
|
||||||
|
-> side effect / returned data
|
||||||
|
```
|
||||||
|
|
||||||
|
Inventory, for each node:
|
||||||
|
|
||||||
|
- trust source and tenant/user ownership
|
||||||
|
- immutable component identity, package/server name, version, and transport
|
||||||
|
- tools, resources, prompts, model endpoints, plugins, skills, and MCP servers
|
||||||
|
- credential identity, issuer, audience/resource, subject, tenant, scopes/roles, expiry, downstream token exchange, environment, and where it is injected
|
||||||
|
- readable data and write/execute capabilities
|
||||||
|
- network/listener exposure and test-versus-production target
|
||||||
|
- argument validation, authorization point, approval point, schema/argument digest, delegated principal propagation, and audit log
|
||||||
|
- data returned to the model and whether it can contain new instructions
|
||||||
|
|
||||||
|
Test from the lowest-privileged realistic user and device. The key comparison is the user's authority versus the agent/tool credential's authority.
|
||||||
|
|
||||||
|
## Core Test Areas
|
||||||
|
|
||||||
|
### Shadow Agent and AI Discovery
|
||||||
|
|
||||||
|
Do not assume the approved application inventory contains every agent, model endpoint, browser extension, local MCP server, or AI API integration. Correlate multiple independent signals:
|
||||||
|
|
||||||
|
- DNS/proxy/egress logs for first-seen model, agent, vector database, plugin, and AI SaaS domains
|
||||||
|
- OAuth/SSO grants, enterprise-app consent, service principals, API tokens, and unusual delegated scopes
|
||||||
|
- endpoint processes, browser extensions/native messaging, listening loopback ports, and MCP client/server configuration
|
||||||
|
- repository, CI/CD, secrets-manager, and container/image references to model providers, tool servers, and AI credentials
|
||||||
|
- cloud-hosted model endpoints, notebooks, functions, gateways, and procurement/expense/SaaS inventory
|
||||||
|
|
||||||
|
Baseline local discovery from the host before interpreting network or SSO signals:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# macOS
|
||||||
|
lsof -nP -iTCP -sTCP:LISTEN
|
||||||
|
ps -axo pid,ppid,user,command
|
||||||
|
|
||||||
|
# Linux
|
||||||
|
ss -lntp
|
||||||
|
ps -eo pid,ppid,user,args
|
||||||
|
|
||||||
|
# Windows PowerShell
|
||||||
|
Get-NetTCPConnection -State Listen | Select-Object LocalAddress,LocalPort,OwningProcess
|
||||||
|
Get-Process | Select-Object Id,ProcessName,Path
|
||||||
|
|
||||||
|
# Cross-platform config and credential leads
|
||||||
|
rg -l 'mcpServers|modelContextProtocol|OPENAI_API_KEY|ANTHROPIC_API_KEY|AZURE_OPENAI_ENDPOINT' <reviewed-roots>
|
||||||
|
```
|
||||||
|
|
||||||
|
Correlate each listener or config hit to PID/container, parent process, binary hash/version, launch command, config file, destination, and credential reference before calling it an active agent component. A loopback listener is a lead, not proof of reachable authority.
|
||||||
|
|
||||||
|
Classify each discovered integration by data read, data write, external communication, execution, identity/admin, and production reach. Human-validate attribution before treating a domain or key name as active AI use. Inspect unauthenticated local MCP/agent listeners separately; network inventory tools often miss loopback-only services.
|
||||||
|
|
||||||
|
### Tool Discovery and Argument Boundaries
|
||||||
|
|
||||||
|
- Enumerate advertised and conditionally available tools, resources, prompts, schemas, annotations, and delegated agents.
|
||||||
|
- Compare what the UI exposes with what the protocol/runtime accepts directly.
|
||||||
|
- Test missing, extra, duplicate, nested, oversized, alternate-type, and cross-tenant identifiers in tool arguments.
|
||||||
|
- Validate scheme/host/path, filesystem paths, cloud resource IDs, recipient identities, SQL/query fields, and command arguments at the tool boundary.
|
||||||
|
- Treat tool descriptions, names, examples, resource metadata, and returned content as attacker-influenceable unless provenance is enforced.
|
||||||
|
- Canonicalize tool identity as `server identity/version + endpoint/transport + tool name + schema digest`; do not collapse two identically named tools from different servers into one trust decision.
|
||||||
|
- Treat protocol hints such as `readOnlyHint`, `destructiveHint`, `idempotentHint`, and `openWorldHint` as untrusted metadata, not authorization.
|
||||||
|
- Verify that unknown tools or schema-invalid calls fail closed without falling back to a broader handler.
|
||||||
|
|
||||||
|
### Confused Deputy and Consequential Actions
|
||||||
|
|
||||||
|
- Ask whether untrusted user/document/tool text can choose the tool, target, identity, or action.
|
||||||
|
- Test read-to-write escalation: a summarizer should not send, publish, delete, purchase, deploy, or modify because retrieved text requests it.
|
||||||
|
- Test whether approval binds the exact server identity/version, tool name, schema digest, normalized arguments, credential, target, side effect, and expiry. Revalidate those fields immediately before execution; a generic “continue?” is weak if arguments can change after approval.
|
||||||
|
- Exercise replay, retry, parallel calls, partial failure, cancellation, and delegated execution for duplicate or bypassed actions.
|
||||||
|
- Prove impact at the actual target and audit log. Model narration or a fabricated tool result is not evidence.
|
||||||
|
- Use dry-run/no-op/read-only operations first; require explicit human approval for consequential operations.
|
||||||
|
|
||||||
|
### Identity, Tenant, and Environment Isolation
|
||||||
|
|
||||||
|
- Vary user, workspace, tenant, session, conversation, and delegated-agent identity independently.
|
||||||
|
- Test whether one tenant can reference another tenant's resources, tool sessions, caches, vector entries, files, or credentials.
|
||||||
|
- Check whether development/test tools or credentials can reach production, and whether local tools inherit broad workstation authority.
|
||||||
|
- Verify credential scoping at the target service, not only in the agent's application logic.
|
||||||
|
- Confirm memory and cached tool results are partitioned and revoked when identity or role changes.
|
||||||
|
|
||||||
|
### MCP and Local Tool Servers
|
||||||
|
|
||||||
|
- Inventory stdio, streamable HTTP, SSE/legacy, and custom transports; record bind address, origin/auth controls, process command, environment, and lifecycle.
|
||||||
|
- Look for unauthenticated loopback services reachable from browsers, containers, local users, SSRF, port forwarding, or shared hosts.
|
||||||
|
- Compare `tools/list`, `resources/list`, and `prompts/list` results across identities, but do not assume listing means calling is authorized.
|
||||||
|
- For each tool, validate the same authorization and argument checks through every supported transport.
|
||||||
|
- Treat server-launched subprocess configuration, environment variables, and working directories as sensitive executable configuration.
|
||||||
|
- For HTTP/SSE transports, validate OAuth issuer, signature, expiry, audience/resource, tenant, and scope claims at the server boundary. Reject tokens minted for the wrong audience, and do not treat a session ID as identity.
|
||||||
|
- For downstream APIs, do not pass through the same bearer token unless the target explicitly authorizes that audience and principal. Separate upstream MCP authentication from downstream target authorization.
|
||||||
|
- For browser or loopback OAuth, review redirect URI, state/PKCE handling, localhost binding, and consent proxying. Treat metadata fetches and tool discovery on remote servers as SSRF-relevant surfaces.
|
||||||
|
- For stdio servers, the launch command and environment are already code execution. Discovery must not execute an unreviewed server binary or mutable package tag.
|
||||||
|
|
||||||
|
### Executable Component Supply Chain
|
||||||
|
|
||||||
|
Every skill, plugin, MCP server, model adapter, package, and update channel is an executable or behavior-shaping dependency. Record:
|
||||||
|
|
||||||
|
- canonical source, publisher, package namespace, pinned version and integrity/provenance
|
||||||
|
- install/update mechanism, manifest/lockfile/config source, mutable tags, automatic updates, and rollback path
|
||||||
|
- declared and effective permissions, credentials, filesystem/network access
|
||||||
|
- transitive dependencies and lifecycle scripts
|
||||||
|
- review/approval ownership and last verification date
|
||||||
|
|
||||||
|
In agent and MCP configs, inspect `command: npx` with `-y` and a bare package or
|
||||||
|
binary name. The process can fetch code without an interactive prompt and then
|
||||||
|
run it with the agent's authority. Load `npx_confusion` to determine whether the
|
||||||
|
name resolves locally, becomes a public package spec, and belongs to the
|
||||||
|
intended publisher.
|
||||||
|
|
||||||
|
Test missing/private-name fallback, typosquatting exposure, mutable remote instructions, compromised-update blast radius, and whether an “instruction-only” component can invoke tools or modify executable files. Resolve `latest`, floating git refs, and mutable image tags to immutable versions or digests before launch. Do not claim or publish contestable package names as proof, and do not execute unknown packages just to discover what they are.
|
||||||
|
|
||||||
|
Load `infrastructure_lifecycle` when a skill, plugin, MCP server, model adapter, tool-schema origin, package namespace, or update endpoint is retired, mutable, or externally reassignable. Passive receipt of an agent heartbeat or catalog request does not authorize returning tool definitions, prompts, commands, or executable content.
|
||||||
|
|
||||||
|
### Output, Telemetry, and Failure Modes
|
||||||
|
|
||||||
|
- Validate model/tool output before it reaches HTML, shell, SQL, URLs, file paths, templates, or a second agent.
|
||||||
|
- Ensure logs record initiating user, tool/server identity, sanitized arguments, approval, target, result, and correlation ID without storing secrets.
|
||||||
|
- Test timeout, tool error, truncated output, malformed result, model retry, and policy-service failure. Failures should not silently switch to a more privileged tool or credential.
|
||||||
|
- Verify kill switches, credential revocation, and disabling a component actually terminate active sessions and queued work.
|
||||||
|
|
||||||
|
## Safe Testing Workflow
|
||||||
|
|
||||||
|
1. **Map** every capability and trust boundary before injecting prompts.
|
||||||
|
2. **Classify** tools as read, write, execute, communicate, identity/admin, or external-cost.
|
||||||
|
3. **Establish controls** with dedicated test tenants, synthetic data, read-only credentials, budgets, and target allowlists.
|
||||||
|
4. **Probe one boundary** at a time: selection, arguments, authorization, approval, execution, result handling.
|
||||||
|
5. **Validate the side effect** in the target system and audit trail; compare denied and allowed identities.
|
||||||
|
6. **Chain confirmed primitives** using the effective-authority and capability map from this skill.
|
||||||
|
7. **Clean up and revoke** created data, sessions, tokens, and local servers.
|
||||||
|
8. **Turn each confirmed case into a regression** across relevant models, prompts, tools, roles, and environments.
|
||||||
|
|
||||||
|
## MCP Inspector (Conditional)
|
||||||
|
|
||||||
|
Use the official [MCP Inspector](https://github.com/modelcontextprotocol/inspector) only against a reviewed local/test server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx @modelcontextprotocol/inspector@<reviewed-version> --cli \
|
||||||
|
--config reviewed-mcp.json --server test-server \
|
||||||
|
--method tools/list --format json
|
||||||
|
```
|
||||||
|
|
||||||
|
- Current upstream requirements should be checked before pinning; as of August 12, 2026, MCP Inspector 2.1.0 requires Node.js `>=22.19.0`.
|
||||||
|
- Prefer CLI/TUI and loopback binding over exposing the web UI.
|
||||||
|
- Preserve the generated API token; never disable authentication or bind the process-spawning backend to an external interface.
|
||||||
|
- Do not publish ports 6274/6277 or pass through the Docker socket/host devices.
|
||||||
|
- `tools/list` is protocol-read-only, but launching/initializing an arbitrary stdio server executes it and list handlers can still have process-side effects. Review the server command/config first. Calling a tool can perform real external actions.
|
||||||
|
- Treat the inspected server command/config as executable; `npx` also downloads code, so pin a reviewed package version for repeatable or sensitive work.
|
||||||
|
|
||||||
|
## Regression With Promptfoo (Conditional)
|
||||||
|
|
||||||
|
[Promptfoo](https://github.com/promptfoo/promptfoo) can encode a bounded model/tool safety matrix after manual validation:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx promptfoo@<reviewed-version> eval
|
||||||
|
```
|
||||||
|
|
||||||
|
- Current upstream engine constraints should be checked before pinning; as of August 12, 2026, Promptfoo documents Node.js `^20.20.0` or `>=22.22.0`.
|
||||||
|
- Use synthetic prompts/data and a dedicated test provider/project.
|
||||||
|
- Provider calls transmit data externally and can incur cost even when evaluation orchestration is local. Set request/concurrency and spending ceilings.
|
||||||
|
- Pin model, provider, prompt, tool schema, retrieval corpus revision, and evaluator versions.
|
||||||
|
- Include allowed and denied controls across roles/tenants; use multiple runs for nondeterministic outcomes.
|
||||||
|
- Automated red-team labels are leads, not findings. Confirm the real tool call, data access, or side effect manually.
|
||||||
|
- Store redacted results; evaluation logs can contain system prompts, secrets, retrieved data, and tool arguments.
|
||||||
|
|
||||||
|
## Validation
|
||||||
|
|
||||||
|
A report must include:
|
||||||
|
|
||||||
|
1. initiating identity, tenant, model/runtime, and exact component versions
|
||||||
|
2. effective-authority map and relevant tool/resource schema
|
||||||
|
3. untrusted input source and decision boundary crossed
|
||||||
|
4. exact target-side operation or data access, with redacted audit evidence
|
||||||
|
5. denied identity/input and allowed control results across repeat runs
|
||||||
|
6. credential, feature, approval, environment, and user-interaction prerequisites
|
||||||
|
7. cleanup/revocation and a bounded regression case
|
||||||
|
|
||||||
|
## False Positives
|
||||||
|
|
||||||
|
- The model claims a tool ran but the target and audit log show no action.
|
||||||
|
- A listed tool cannot be invoked by the tested identity or validates arguments safely.
|
||||||
|
- A safety refusal changes wording but effective capability remains denied.
|
||||||
|
- Cross-session output is synthetic, cached public data, or hallucinated rather than another user's data.
|
||||||
|
- A scanner flags an instruction string without showing that it reaches a privileged decision or sink.
|
||||||
|
- A component has broad declared permissions but the runtime credential/network policy prevents the claimed access.
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
Agent security is capability security. Map the real authority carried through models, tools, credentials, plugins, and delegated agents; validate authorization and approval at the target-side effect; treat every installed component as executable supply chain; and preserve each confirmed boundary failure as a bounded regression.
|
||||||
@@ -0,0 +1,192 @@
|
|||||||
|
---
|
||||||
|
name: browser-security
|
||||||
|
description: Browser-internals security testing for browsing-context relationships, postMessage, client-side path traversal, XS-Leaks, service workers, Web Workers, navigation behavior, CSP interactions, caches, and cross-origin state machines
|
||||||
|
---
|
||||||
|
|
||||||
|
# Browser Security
|
||||||
|
|
||||||
|
Use this skill when exploitability depends on browser behavior beyond a basic HTML injection. Model origins, browsing contexts, navigation history, workers, caches, router decoding, request metadata, and user activation as explicit state.
|
||||||
|
|
||||||
|
Pair this skill with `xss`, `oauth`, `open_redirect`, `csrf`, or `semantic_confusion` when one of those is the primary vulnerability class. For an Electron renderer with a preload or IPC bridge, load `electron_desktop_apps` to analyze whether navigation and origin transitions reach native capability.
|
||||||
|
|
||||||
|
## Safety Boundary
|
||||||
|
|
||||||
|
- Use a controlled browser profile, synthetic account/data, explicit target allowlist, and a fresh assessment-specific proxy/CA when interception is required.
|
||||||
|
- Redact tokens, cookies, message contents, storage values, and personal data from console logs, captures, recordings, and reports.
|
||||||
|
- Treat oversized URLs/headers, cookie inflation, redirect loops, cache exhaustion, and high-rate timing trials as resource/denial-of-service tests; run them only with strict ceilings in a restartable lab.
|
||||||
|
- Do not attempt to set or spoof browser-generated `event.origin`. Vary the sender URL and record the serialized origin supplied by the browser.
|
||||||
|
- Restore monkey-patched browser APIs and unregister test workers/caches after validation.
|
||||||
|
|
||||||
|
## Browser State Model
|
||||||
|
|
||||||
|
For each relevant page or worker, record:
|
||||||
|
|
||||||
|
- origin and site, including transitions after navigation
|
||||||
|
- top-level window, opener, parent, child frames, named contexts, and retained references
|
||||||
|
- sandbox flags, CSP `frame-ancestors`, COOP, COEP, CORP, and X-Frame-Options
|
||||||
|
- service-worker controller and scope
|
||||||
|
- storage access: cookies, local/session storage, IndexedDB, Cache API
|
||||||
|
- navigation/history entries and redirect type: HTTP, JavaScript, form, meta refresh
|
||||||
|
- user-activation and interaction requirements
|
||||||
|
- browser family/version and enabled experimental features
|
||||||
|
|
||||||
|
Draw the context graph. Security checks on `event.origin`, `event.source`, or a popup reference are meaningful only when the lifetime and ownership of that context are understood.
|
||||||
|
|
||||||
|
## High-Value Surfaces
|
||||||
|
|
||||||
|
### postMessage and Window Relationships
|
||||||
|
|
||||||
|
- Enumerate listeners and senders; record message schema, origin check, source check, and reachable sinks/actions.
|
||||||
|
- Validate origins after URL parsing and canonicalization, not with raw-string regexes.
|
||||||
|
- Test numeric/alternate IP forms, userinfo, path masquerading as a host suffix, and redirects.
|
||||||
|
- Treat predictable `window.open()` target names and iframe names as potentially shared namespace entries. Confirm reuse within the same browsing-context group, opener chain, COOP state, and relevant navigation/message timing.
|
||||||
|
- Check whether a blocked intermediate frame leaves a useful browsing-context relationship intact.
|
||||||
|
- Use random per-flow names or `_blank` with `noopener` where an opener relationship is unnecessary.
|
||||||
|
|
||||||
|
### Client-Side Path Traversal
|
||||||
|
|
||||||
|
Trace the complete source-to-request pipeline:
|
||||||
|
|
||||||
|
```text
|
||||||
|
browser URL -> router parser -> route/query/hash accessor -> app interpolation -> fetch/XHR -> final normalized URL
|
||||||
|
```
|
||||||
|
|
||||||
|
- Test path parameters, query parameters, and hashes independently.
|
||||||
|
- Determine exactly where `%2F`, `%5C`, `%2E`, and double-encoded forms decode or re-encode.
|
||||||
|
- Instrument `fetch`, XHR, Axios, router navigation, and server-side fetch wrappers to capture the final URL.
|
||||||
|
- Escalate only after identifying the sink: state-changing API for CSRF-like impact, HTML/attachment response rendered in an unsafe sink for XSS, or server-side fetch for SSRF.
|
||||||
|
- Do not assume the same framework API behaves identically in client components, server components, and route handlers.
|
||||||
|
|
||||||
|
### XS-Leaks and Cross-Origin Oracles
|
||||||
|
|
||||||
|
Inventory observable signals that do not require reading the cross-origin response:
|
||||||
|
|
||||||
|
- load/error events for script, image, stylesheet, frame, media, and module elements
|
||||||
|
- timing, connection reuse, cache state, redirect count, and navigation success
|
||||||
|
- window/frame count, focus, history length, and resource dimensions
|
||||||
|
- browser-generated error pages and status-dependent behavior
|
||||||
|
- request headers such as `Sec-Fetch-Dest`, `Sec-Fetch-Mode`, and `Origin`
|
||||||
|
|
||||||
|
Test controls such as ORB, CORP, COEP, and MIME enforcement. A service worker or alternate fetch path can change request destination metadata and therefore change whether a blocked response becomes a network error or an empty response. Validate the oracle across authenticated and unauthenticated control cases.
|
||||||
|
|
||||||
|
### Service Workers and Caches
|
||||||
|
|
||||||
|
- Map service-worker registration scope, update lifecycle, controller acquisition, and fetch handlers.
|
||||||
|
- Inspect Cache API keys and responses; determine whether HTML or JavaScript is served directly from a writable cache.
|
||||||
|
- Test whether a constrained script context can poison app-managed cache entries later consumed by a normal page or service worker.
|
||||||
|
- Treat service-worker persistence as high impact, but prove registration/control scope and update survivability.
|
||||||
|
- Compare a direct subresource request with the same request proxied through `fetch(event.request)`; request destination and mode can differ.
|
||||||
|
|
||||||
|
### Web Workers and Constrained Script Execution
|
||||||
|
|
||||||
|
When script runs inside a worker, inventory capabilities instead of dismissing it as low impact:
|
||||||
|
|
||||||
|
- credentialed same-origin `fetch` for data access and state changes
|
||||||
|
- `postMessage` gadgets into the main page
|
||||||
|
- IndexedDB and Cache API shared with other same-origin contexts
|
||||||
|
- Blob construction and object URLs
|
||||||
|
- import mechanisms, WebSocket, and available browser-specific APIs
|
||||||
|
|
||||||
|
Prove the strongest reliable capability first. If escalation requires a user gesture, document the exact gesture, timing, browser, and visibility rather than calling it zero-click XSS.
|
||||||
|
|
||||||
|
### Navigation and Redirect Control
|
||||||
|
|
||||||
|
- Distinguish HTTP 30x, script navigation, form submission, meta refresh, and popup navigation.
|
||||||
|
- Test invalid or blocked URL schemes and WAF-generated error pages only when they support a real flow. Oversized URLs/headers, cookie-path-specific header inflation, redirect limits, and navigation throttling are restartable-lab-only tests with strict size/iteration limits and health checks.
|
||||||
|
- A sandbox inherited by a new top-level context can selectively block forms, scripts, popups, or navigation; enumerate the exact flag set.
|
||||||
|
- Preserve and inspect history when a built-in error page replaces the active document; do not assume the errored URL is lost.
|
||||||
|
|
||||||
|
### CSP and Browser Parsing
|
||||||
|
|
||||||
|
- Evaluate the delivered policy on the exact response, including redirects and error/API/static paths.
|
||||||
|
- Map nonces, hashes, `strict-dynamic`, allowed schemes, trusted script gadgets, `base-uri`, `frame-ancestors`, and Trusted Types.
|
||||||
|
- Test parser namespaces and repairs in HTML, SVG, and MathML. A protected attribute or sanitizer rule in the HTML namespace may behave differently after namespace transitions.
|
||||||
|
- Treat scriptless disclosure of a nonce or trusted URL as a primitive; prove a second controllable sink before claiming bypass.
|
||||||
|
- For response splitting, consider whether a same-origin endpoint can be turned into a script resource with a controlled body length or framing.
|
||||||
|
|
||||||
|
### JavaScript Gadget Discovery
|
||||||
|
|
||||||
|
- When direct calls are blocked, inspect implicit coercions (`toString`, `valueOf`, iterators, getters, proxies) and callbacks invoked by accessible library functions.
|
||||||
|
- Search for functions whose `this` object and arguments can be attacker-shaped.
|
||||||
|
- Build a bounded harness to enumerate reachable globals and observe property reads/calls; avoid assuming one library gadget is universal.
|
||||||
|
- Validate the complete call chain to a dangerous sink such as navigation, HTML insertion, `eval`, `Function`, or a privileged API.
|
||||||
|
|
||||||
|
## Reconnaissance
|
||||||
|
|
||||||
|
### Runtime Instrumentation
|
||||||
|
|
||||||
|
Instrument in a controlled browser session:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const realFetch = window.fetch;
|
||||||
|
window.fetch = (...args) => {
|
||||||
|
const input = args[0];
|
||||||
|
const rawUrl = typeof input === 'string' ? input : input.url;
|
||||||
|
const url = new URL(rawUrl, location.href);
|
||||||
|
const method = args[1]?.method || input?.method || 'GET';
|
||||||
|
console.log('fetch', {method, origin: url.origin, path: url.pathname});
|
||||||
|
return realFetch(...args);
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('message', e => {
|
||||||
|
const keys = e.data && typeof e.data === 'object' ? Object.keys(e.data) : [];
|
||||||
|
console.log('message', {origin: e.origin, sourceMatches: e.source === window.opener, keys});
|
||||||
|
}, true);
|
||||||
|
```
|
||||||
|
|
||||||
|
Use the wrapper only in the controlled profile and restore `window.fetch = realFetch` afterward. Do not log bodies, message values, credentials, or query strings.
|
||||||
|
|
||||||
|
Also inspect DevTools network initiators, service workers, storage, CSP violations, frame tree, and navigation history. Use raw browser behavior for validation; command-line HTTP clients cannot reproduce origin/window/worker semantics.
|
||||||
|
|
||||||
|
### Source Review
|
||||||
|
|
||||||
|
- Search for `postMessage`, message listeners, `window.open`, named targets, opener/parent access, frame creation, and sandbox attributes.
|
||||||
|
- Search for router parameter APIs flowing into `fetch`, Axios, navigation, or HTML rendering.
|
||||||
|
- Search for service-worker registration, Cache API writes, worker constructors, Blob URLs, and dynamic imports.
|
||||||
|
- Search for raw HTML sinks and trust escape hatches in every supported frontend framework.
|
||||||
|
- Compare CSP and framing headers across document, API, static, callback, redirect, and error routes.
|
||||||
|
|
||||||
|
## Testing Methodology
|
||||||
|
|
||||||
|
1. **Define the browser state** - Origin/site, context graph, policies, workers, storage, and activation.
|
||||||
|
2. **Identify a source and observable sink** - Message, URL component, cache entry, navigation, load/error event, or implicit call.
|
||||||
|
3. **Trace transformations** - URL parsing, framework decode, browser normalization, request destination, and document replacement.
|
||||||
|
4. **Build paired controls** - Same-origin/cross-origin, status success/error, worker/direct, unique/predictable window name, encoded/raw path.
|
||||||
|
5. **Prove the primitive** - Data transfer, path change, state oracle, cache modification, or context capture.
|
||||||
|
6. **Escalate deliberately** - Chain to a privileged action, sensitive disclosure, SSRF, or executable DOM sink.
|
||||||
|
7. **Cross-browser check** - At minimum record Chromium/Firefox/Safari applicability when the primitive is browser-specific.
|
||||||
|
8. **State interaction requirements** - Click, drag, popup permission, timing window, login state, and visual deception.
|
||||||
|
|
||||||
|
## Validation
|
||||||
|
|
||||||
|
1. Capture the context graph and relevant policies at exploit time.
|
||||||
|
2. Show the exact browser-parsed origin or final request URL, not just the attacker-supplied string.
|
||||||
|
3. For postMessage, prove both message origin and source/context ownership.
|
||||||
|
4. For XS-Leaks, repeat randomized success/failure trials and quantify separation and noise.
|
||||||
|
5. For workers/caches, show which later context consumes the modified data.
|
||||||
|
6. For client-side traversal, capture the final network request and the security-relevant response/action.
|
||||||
|
7. For interaction-dependent chains, provide a screen recording or deterministic event trace.
|
||||||
|
|
||||||
|
## False Positives
|
||||||
|
|
||||||
|
- A message reaches a listener but fails schema, origin, source, or state validation before any action
|
||||||
|
- A router decodes traversal characters but the value never reaches a URL/path sink
|
||||||
|
- Different load/error behavior caused by unstable network rather than protected state
|
||||||
|
- Worker script execution with no sensitive API, shared state, main-thread gadget, or meaningful action
|
||||||
|
- CSP nonce disclosure without a controllable way to reuse it in an executable sink
|
||||||
|
- Named-window collision blocked by origin scoping, randomized names, COOP, or `noopener`
|
||||||
|
- Browser-specific behavior reported without the required version, flag, or user interaction
|
||||||
|
|
||||||
|
## Pro Tips
|
||||||
|
|
||||||
|
1. Treat browsing-context names as attacker-contestable identifiers unless randomized.
|
||||||
|
2. Query parameters are usually decoded automatically; path parameters vary by router and execution context.
|
||||||
|
3. Compare request metadata, not just URLs. Service workers can alter destination/mode semantics.
|
||||||
|
4. A strict origin check does not compensate for attacker control of the supposedly trusted window reference.
|
||||||
|
5. Error pages, redirects, and blocked frames still mutate history and context relationships.
|
||||||
|
6. Keep browser-version claims narrow and retest; these behaviors change faster than server-side primitives.
|
||||||
|
7. Prefer a small state-machine explanation over a large payload catalog.
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
Browser exploitation is state-machine exploitation. Map origins, context references, policies, workers, storage, navigation, and decoding as one system. Prove each state transition with browser evidence, then chain only the primitives that survive the target's browser and interaction constraints.
|
||||||
@@ -5,7 +5,7 @@ description: HTTP header injection testing covering CRLF / response splitting, c
|
|||||||
|
|
||||||
# HTTP Header Injection
|
# HTTP Header Injection
|
||||||
|
|
||||||
Header injection turns user input into protocol-level control: response splitting, cache poisoning, session fixation, authentication bypass, and request smuggling all trace back to a server-controlled header value that wasn't normalized. The bug usually lives in middle layers — frameworks that copy a request value into a response header, proxies that trust forwarded headers, caches keyed on something the attacker influences. Treat any user-controlled value that reaches a header as code-execution-equivalent until proven otherwise.
|
Header injection turns user input into protocol-level control: response splitting, cache poisoning, session fixation, authentication bypass, and downstream parser confusion can trace back to a server-controlled header value that was not normalized. The bug usually lives in middle layers — frameworks that copy a request value into a response header, proxies that trust forwarded headers, caches keyed on something the attacker influences. Impact depends on which downstream component consumes the injected field and how.
|
||||||
|
|
||||||
## Attack Surface
|
## Attack Surface
|
||||||
|
|
||||||
@@ -62,7 +62,7 @@ Header injection turns user input into protocol-level control: response splittin
|
|||||||
|
|
||||||
## Key Vulnerabilities
|
## Key Vulnerabilities
|
||||||
|
|
||||||
### CRLF Response Splitting and Smuggling
|
### CRLF Response Splitting
|
||||||
|
|
||||||
Inject `\r\n\r\n` to terminate the current response and prepend a second attacker-controlled response. Cache or downstream proxy may key on the first response and serve the second to other users.
|
Inject `\r\n\r\n` to terminate the current response and prepend a second attacker-controlled response. Cache or downstream proxy may key on the first response and serve the second to other users.
|
||||||
|
|
||||||
@@ -70,7 +70,7 @@ Inject `\r\n\r\n` to terminate the current response and prepend a second attacke
|
|||||||
GET /redirect?to=foo%0d%0aSet-Cookie:%20admin=1%0d%0a%0d%0a<html>poisoned</html> HTTP/1.1
|
GET /redirect?to=foo%0d%0aSet-Cookie:%20admin=1%0d%0a%0d%0a<html>poisoned</html> HTTP/1.1
|
||||||
```
|
```
|
||||||
|
|
||||||
Request smuggling is the same primitive at the request layer: inject a header that causes the proxy and backend to disagree on message framing — most commonly conflicting `Content-Length` and `Transfer-Encoding`, or two `Content-Length` headers with different values. Backend reads one request, frontend reads a different one; the leftover bytes become a smuggled request prepended to the next victim's connection.
|
Request smuggling is a separate request-boundary vulnerability involving disagreement between two HTTP parsers, not simply response header injection at the request layer. Load `http_request_smuggling` when conflicting lengths, transfer coding, HTTP/2 downgrades, or connection desynchronization are in scope.
|
||||||
|
|
||||||
### Cache Poisoning
|
### Cache Poisoning
|
||||||
|
|
||||||
@@ -106,16 +106,23 @@ The `X-Forwarded-*` family is informational — there is no protocol guarantee a
|
|||||||
- `X-Forwarded-For: 127.0.0.1` to bypass IP allowlists or rate limits keyed on client IP
|
- `X-Forwarded-For: 127.0.0.1` to bypass IP allowlists or rate limits keyed on client IP
|
||||||
- `X-Forwarded-Proto: https` to satisfy "HTTPS-only" checks while still using HTTP
|
- `X-Forwarded-Proto: https` to satisfy "HTTPS-only" checks while still using HTTP
|
||||||
- `X-Forwarded-Host: attacker.tld` for the Host-confusion variants above
|
- `X-Forwarded-Host: attacker.tld` for the Host-confusion variants above
|
||||||
- `X-Real-IP`, `Client-IP`, `True-Client-IP`, `CF-Connecting-IP`, `Forwarded` (RFC 7239) — same primitive, different header names; spray all of them
|
- `X-Real-IP`, `Client-IP`, `True-Client-IP`, `CF-Connecting-IP`, `Forwarded` (RFC 7239) — same trust class under different conventions; select evidence-supported variants for the observed proxy/CDN stack
|
||||||
- `X-Original-URL` / `X-Rewrite-URL` (IIS, ASP.NET) — server-side URL rewriting after auth check, classic admin-panel auth bypass
|
- `X-Original-URL` / `X-Rewrite-URL` (IIS, ASP.NET) — server-side URL rewriting after auth check, classic admin-panel auth bypass
|
||||||
|
|
||||||
### Content-Type / Encoding Confusion
|
### Content-Type / Encoding Confusion
|
||||||
|
|
||||||
- Inject `Content-Type: text/html` into an endpoint that returned JSON; browsers may sniff and render → XSS
|
- Inject `Content-Type: text/html` into an endpoint that returned JSON; browsers may sniff and render → XSS
|
||||||
- Inject `charset=utf-7` in `Content-Type` for legacy XSS via UTF-7-encoded payloads
|
|
||||||
- Inject `Content-Disposition: inline` to switch a download into in-page rendering
|
- Inject `Content-Disposition: inline` to switch a download into in-page rendering
|
||||||
- Inject `Content-Encoding: gzip` without actually compressing — clients decode-fail and may reveal raw response bytes in error paths
|
|
||||||
- *Absence* of `X-Content-Type-Options: nosniff` is what enables the sniffing attacks above; the header is a hardening control, not an attack surface — but if a server sets it inconsistently across endpoints, target the ones that don't
|
- *Absence* of `X-Content-Type-Options: nosniff` is what enables the sniffing attacks above; the header is a hardening control, not an attack surface — but if a server sets it inconsistently across endpoints, target the ones that don't
|
||||||
|
- Compare MIME validators with browser parsing of duplicate or comma-joined `Content-Type` values. Record first/last valid member behavior and invalid-parameter recovery for each consumer.
|
||||||
|
|
||||||
|
### Internal Redirect and Handler Confusion
|
||||||
|
|
||||||
|
- Determine whether CGI/FastCGI/WSGI-style response headers can trigger an internal redirect instead of an external response.
|
||||||
|
- Trace which request fields survive the redirect: content type, handler, method, authorization result, path, and environment.
|
||||||
|
- Test whether response metadata is reused as an internal handler, proxy target, template type, or interpreter selection.
|
||||||
|
- Compare direct access controls with the internally dispatched resource. A protected URL may be unreachable directly while the same handler is invokable through a clean internal redirect.
|
||||||
|
- Treat CRLF injection and response-controlling SSRF as possible inputs to this chain, then validate handler selection before using a privileged handler.
|
||||||
|
|
||||||
### XSS via Response Headers
|
### XSS via Response Headers
|
||||||
|
|
||||||
@@ -165,8 +172,9 @@ The `X-Forwarded-*` family is informational — there is no protocol guarantee a
|
|||||||
4. **Probe forwarding headers** — spoof `X-Forwarded-For`, `X-Real-IP`, `True-Client-IP`, `CF-Connecting-IP` against IP-restricted endpoints (admin, rate-limited)
|
4. **Probe forwarding headers** — spoof `X-Forwarded-For`, `X-Real-IP`, `True-Client-IP`, `CF-Connecting-IP` against IP-restricted endpoints (admin, rate-limited)
|
||||||
5. **Test cache key / response content split** — find inputs that change the body but not the cache key; confirm a second request from a different session sees the poisoned response
|
5. **Test cache key / response content split** — find inputs that change the body but not the cache key; confirm a second request from a different session sees the poisoned response
|
||||||
6. **Test method override** — `X-HTTP-Method-Override` paired with state-changing endpoints reachable via POST or GET
|
6. **Test method override** — `X-HTTP-Method-Override` paired with state-changing endpoints reachable via POST or GET
|
||||||
7. **Test request smuggling pairs** — conflicting `Content-Length` and `Transfer-Encoding`, two `Content-Length` headers, malformed chunked encoding, against any frontend → backend pair
|
7. **Route framing discrepancies** — if evidence indicates request-boundary disagreement, switch to `http_request_smuggling`
|
||||||
8. **Cross-protocol** — replay payloads over HTTP/1.1 and HTTP/2; diff behavior
|
8. **Cross-protocol** — replay payloads over HTTP/1.1 and HTTP/2; diff behavior
|
||||||
|
9. **Trace internal reprocessing** — where response headers can cause subrequests/internal redirects, diff retained fields and final handler selection
|
||||||
|
|
||||||
## Validation
|
## Validation
|
||||||
|
|
||||||
@@ -174,8 +182,8 @@ The `X-Forwarded-*` family is informational — there is no protocol guarantee a
|
|||||||
2. Capture a password-reset / OAuth link pointing at attacker-controlled host — proves Host injection
|
2. Capture a password-reset / OAuth link pointing at attacker-controlled host — proves Host injection
|
||||||
3. Demonstrate the same endpoint returning different auth decisions with and without a forged forwarding header
|
3. Demonstrate the same endpoint returning different auth decisions with and without a forged forwarding header
|
||||||
4. For response splitting: show a downstream cache or proxy serving the injected second response to an unrelated request
|
4. For response splitting: show a downstream cache or proxy serving the injected second response to an unrelated request
|
||||||
5. For request smuggling: show one victim request seeing data from a different request appended (not just timing or single-shot anomaly)
|
5. All findings should produce a durable artifact (cached response, sent email, log entry, session change) — transient anomalies are not validation
|
||||||
6. All findings should produce a durable artifact (cached response, sent email, log entry, session change) — transient anomalies are not validation
|
6. For internal redirects, capture both the injected response metadata and the final internally selected route/handler
|
||||||
|
|
||||||
## False Positives
|
## False Positives
|
||||||
|
|
||||||
@@ -183,7 +191,6 @@ The `X-Forwarded-*` family is informational — there is no protocol guarantee a
|
|||||||
- `X-Forwarded-*` reflected back but only used for logging — not a security boundary, may not be exploitable
|
- `X-Forwarded-*` reflected back but only used for logging — not a security boundary, may not be exploitable
|
||||||
- Browsers blocking `Location: javascript:` or `Location: data:` — capability exists in the protocol but most modern browsers refuse to navigate
|
- Browsers blocking `Location: javascript:` or `Location: data:` — capability exists in the protocol but most modern browsers refuse to navigate
|
||||||
- CRLF appearing in response headers but stripped by an outer proxy before reaching any client or cache
|
- CRLF appearing in response headers but stripped by an outer proxy before reaching any client or cache
|
||||||
- Request smuggling indicators that turn out to be normal pipelining or keep-alive behavior
|
|
||||||
|
|
||||||
## Impact
|
## Impact
|
||||||
|
|
||||||
@@ -192,7 +199,6 @@ The `X-Forwarded-*` family is informational — there is no protocol guarantee a
|
|||||||
- Auth bypass on endpoints trusting forwarding headers
|
- Auth bypass on endpoints trusting forwarding headers
|
||||||
- Session fixation and cookie tossing leading to account hijack
|
- Session fixation and cookie tossing leading to account hijack
|
||||||
- Open redirect for phishing / OAuth `redirect_uri` abuse
|
- Open redirect for phishing / OAuth `redirect_uri` abuse
|
||||||
- Request smuggling — one victim's request reads another victim's response, including auth headers and cookies
|
|
||||||
- WAF / detection bypass via header-name and encoding tricks
|
- WAF / detection bypass via header-name and encoding tricks
|
||||||
|
|
||||||
## Pro Tips
|
## Pro Tips
|
||||||
@@ -200,7 +206,7 @@ The `X-Forwarded-*` family is informational — there is no protocol guarantee a
|
|||||||
1. The fastest win is usually Host / `X-Forwarded-Host` in a password-reset or OAuth flow — try first, costs one request
|
1. The fastest win is usually Host / `X-Forwarded-Host` in a password-reset or OAuth flow — try first, costs one request
|
||||||
2. For cache poisoning, find the *unkeyed* input first (header that influences body but not cache key); the rest follows
|
2. For cache poisoning, find the *unkeyed* input first (header that influences body but not cache key); the rest follows
|
||||||
3. `X-HTTP-Method-Override` is high-yield against backends that route on it before checking method-based auth — most useful from server-side / non-browser callers (it triggers CORS preflight in a browser, so not a CSRF primitive)
|
3. `X-HTTP-Method-Override` is high-yield against backends that route on it before checking method-based auth — most useful from server-side / non-browser callers (it triggers CORS preflight in a browser, so not a CSRF primitive)
|
||||||
4. Smuggling lives at the boundary — identify the proxy → backend pair (CDN → origin, ingress → service) and target the framing disagreement
|
4. If a header test exposes message-boundary disagreement, switch to the dedicated request-smuggling workflow and identify the proxy → backend pair
|
||||||
5. `X-Original-URL` / `X-Rewrite-URL` against IIS / ASP.NET admin endpoints is still a high-yield bypass
|
5. `X-Original-URL` / `X-Rewrite-URL` against IIS / ASP.NET admin endpoints is still a high-yield bypass
|
||||||
6. Before claiming a CRLF win, verify the second line landed as a real header in the cache or downstream consumer — many servers strip CRLF silently
|
6. Before claiming a CRLF win, verify the second line landed as a real header in the cache or downstream consumer — many servers strip CRLF silently
|
||||||
7. Outbound email flows are a separate but related surface — user input flowing into SMTP headers (To, Cc, Subject, Reply-To) is its own injection class with the same root cause
|
7. Outbound email flows are a separate but related surface — user input flowing into SMTP headers (To, Cc, Subject, Reply-To) is its own injection class with the same root cause
|
||||||
|
|||||||
@@ -10,13 +10,16 @@ Insecure deserialization passes attacker-controlled byte streams or structured b
|
|||||||
## Attack Surface
|
## Attack Surface
|
||||||
|
|
||||||
**Formats**
|
**Formats**
|
||||||
- Java: Java native serialization, XStream, JSON → object mappers (Jackson, Fastjson), YAML (SnakeYAML)
|
- Java: Java native serialization, XStream, JSON → object mappers (Jackson, Fastjson), YAML (SnakeYAML), Hessian/Burlap, Kryo
|
||||||
- Python: `pickle`, `yaml.load` (unsafe), `marshal`, shelve
|
- Python: `pickle`, `yaml.load` (unsafe), `marshal`, shelve
|
||||||
- PHP: `unserialize()`, Phar deserialization
|
- PHP: `unserialize()`, Phar deserialization
|
||||||
- .NET: `BinaryFormatter`, `Json.NET TypeNameHandling`, ViewState
|
- .NET: `BinaryFormatter`, `Json.NET TypeNameHandling`, ViewState
|
||||||
- Ruby: `Marshal.load`, YAML.load
|
- Ruby: `Marshal.load`, YAML.load
|
||||||
- Node.js: `node-serialize`, `unserialize.js` (less common; see prototype_pollution for merge bugs)
|
- Node.js: `node-serialize`, `unserialize.js` (less common; see prototype_pollution for merge bugs)
|
||||||
|
|
||||||
|
**Transports and Containers**
|
||||||
|
- Java RMI/JMX, HTTP/RPC endpoints, messaging protocols, queues, signed wrappers, and product-specific binary envelopes can carry one or more formats above
|
||||||
|
|
||||||
**Input Locations**
|
**Input Locations**
|
||||||
- Cookies, session tokens, hidden form fields
|
- Cookies, session tokens, hidden form fields
|
||||||
- API parameters (`data`, `state`, `object`, base64 blobs)
|
- API parameters (`data`, `state`, `object`, base64 blobs)
|
||||||
@@ -58,6 +61,22 @@ yaml.load readObject( TypeNameHandling Marshal.load
|
|||||||
```
|
```
|
||||||
When `enableDefaultTyping` or `@JsonTypeInfo` allows attacker-chosen types.
|
When `enableDefaultTyping` or `@JsonTypeInfo` allows attacker-chosen types.
|
||||||
|
|
||||||
|
**JNDI Pivots from Object Construction**
|
||||||
|
|
||||||
|
JNDI injection is not itself a serialization format. It becomes part of this workflow when an attacker-selected type, setter, or gadget performs `Context.lookup()` during object construction or property population. `JdbcRowSetImpl` and some historical polymorphic JSON chains are examples; Log4j lookups reach JNDI through a different input path and should not be classified as deserialization.
|
||||||
|
|
||||||
|
- Trace fields such as `dataSourceName`, `jndiName`, and `namingURL` into the exact lookup API and provider.
|
||||||
|
- Record the accepted schemes/provider factories (`ldap`, `ldaps`, `rmi`, DNS URL context, or application-specific naming providers). A `dns://` value is not a universal oracle; it works only when the relevant DNS provider and lookup path are present.
|
||||||
|
- Separate network lookup, remote object/reference processing, serialized LDAP attributes, remote codebase loading, and local object-factory invocation. Each is a different capability with different runtime controls.
|
||||||
|
- JEP 290 filters incoming Java serialization graphs; it does not disable JNDI remote codebase loading. JNDI providers gained separate remote-class-loading and serialized-data controls across JDK updates, and current JDKs disable remote code downloading by default. Record the exact JDK build and relevant provider properties instead of using a single “modern Java” rule.
|
||||||
|
- When remote class loading is unavailable, test whether the returned reference can reach a compatible **local** `ObjectFactory`, bean-property path, expression engine, script engine, or other class already present. Confirm exact class names, versions, module access, and trigger methods from the deployed classpath.
|
||||||
|
|
||||||
|
**Hessian / Burlap**
|
||||||
|
- Binary RPC formats deserialized by `HessianInput`/`Hessian2Input`. Attacker object graphs reach gadgets even though it is not native Java serialization.
|
||||||
|
- Treat serializer version, allowed type metadata, constructors/setters invoked, collection/comparator behavior, and classpath as independent prerequisites.
|
||||||
|
- Pair `semantic_confusion` when a proxy or route policy is expected to make the RPC endpoint unreachable.
|
||||||
|
- Inspect the exact deployed libraries rather than relying on generic gadget labels; similar-looking Spring, Resin, Tomcat, XBean, EL, or Groovy classes are not interchangeable.
|
||||||
|
|
||||||
### Python Pickle
|
### Python Pickle
|
||||||
|
|
||||||
Pickle executes arbitrary code during unpickling by design:
|
Pickle executes arbitrary code during unpickling by design:
|
||||||
@@ -162,6 +181,8 @@ When `TypeNameHandling` != `None`.
|
|||||||
3. Check cookies named `JSESSIONID` alternatives, `.ASPXAUTH`, `laravel_session`, custom tokens
|
3. Check cookies named `JSESSIONID` alternatives, `.ASPXAUTH`, `laravel_session`, custom tokens
|
||||||
4. In white-box, trace from `readObject`/`unserialize`/`pickle.loads` backward to source
|
4. In white-box, trace from `readObject`/`unserialize`/`pickle.loads` backward to source
|
||||||
5. ViewState MAC off is still common on legacy ASP.NET — test early on `.aspx` apps
|
5. ViewState MAC off is still common on legacy ASP.NET — test early on `.aspx` apps
|
||||||
|
6. Model JNDI lookup, reference/object processing, remote codebase loading, and local factory invocation as separate stages
|
||||||
|
7. A "blocked" enterprise deserialization endpoint may still be reachable through a proxy/path-normalization mismatch — pair `semantic_confusion`
|
||||||
|
|
||||||
## Tooling
|
## Tooling
|
||||||
|
|
||||||
@@ -172,6 +193,7 @@ Payload generation is the practitioner's core tool here. The sandbox has `git`/`
|
|||||||
| **ysoserial** (frohoff) | Java native | Gadget-chain payloads: `CommonsCollections1-7`, `Groovy1`, `Spring1/2`, and `URLDNS` for a safe no-exec DNS oracle. Needs a JRE. |
|
| **ysoserial** (frohoff) | Java native | Gadget-chain payloads: `CommonsCollections1-7`, `Groovy1`, `Spring1/2`, and `URLDNS` for a safe no-exec DNS oracle. Needs a JRE. |
|
||||||
| **phpggc** (ambionics) | PHP `unserialize` / Phar | Framework POP chains (Laravel, Symfony, WordPress, Drupal, Monolog). Needs `php-cli`. |
|
| **phpggc** (ambionics) | PHP `unserialize` / Phar | Framework POP chains (Laravel, Symfony, WordPress, Drupal, Monolog). Needs `php-cli`. |
|
||||||
| **ysoserial.net** | .NET `BinaryFormatter` / Json.NET | Windows/.NET gadget payloads. Needs .NET/mono — usually out of scope in a Linux sandbox. |
|
| **ysoserial.net** | .NET `BinaryFormatter` / Json.NET | Windows/.NET gadget payloads. Needs .NET/mono — usually out of scope in a Linux sandbox. |
|
||||||
|
| **marshalsec** | Java Hessian/Burlap, Kryo, JSON, and JNDI reference tooling | Use only from a reviewed, pinned upstream commit when a non-native Java marshaller requires it. It has no stable release and intentionally bundles historical gadget dependencies; do not treat it as a globally installed default tool. |
|
||||||
|
|
||||||
```
|
```
|
||||||
# Java: prove the sink with a no-exec DNS oracle BEFORE any RCE chain
|
# Java: prove the sink with a no-exec DNS oracle BEFORE any RCE chain
|
||||||
|
|||||||
@@ -67,6 +67,8 @@ Upload surfaces are high risk: server-side execution (RCE), stored XSS, malware
|
|||||||
|
|
||||||
- Double extensions: avatar.jpg.php, report.pdf.html; mixed casing: .pHp, .PhAr
|
- Double extensions: avatar.jpg.php, report.pdf.html; mixed casing: .pHp, .PhAr
|
||||||
- Magic-byte spoofing: valid JPEG header then embedded script; verify server uses content inspection, not extensions alone
|
- Magic-byte spoofing: valid JPEG header then embedded script; verify server uses content inspection, not extensions alone
|
||||||
|
- Detector/consumer differential: make the upload validator and the later parser disagree about type, structure, or validity
|
||||||
|
- Probe detector scan windows, recursion/nesting limits, maximum bytes inspected, invalid-syntax recovery, and version-specific magic databases
|
||||||
|
|
||||||
### Archive Attacks
|
### Archive Attacks
|
||||||
|
|
||||||
@@ -120,6 +122,8 @@ Upload surfaces are high risk: server-side execution (RCE), stored XSS, malware
|
|||||||
- Client-side only checks; relying on JS/MIME provided by browser
|
- Client-side only checks; relying on JS/MIME provided by browser
|
||||||
- Trusting multipart boundary part headers blindly
|
- Trusting multipart boundary part headers blindly
|
||||||
- Extension allowlists without server-side content inspection
|
- Extension allowlists without server-side content inspection
|
||||||
|
- One parser validates metadata or leading bytes while another parser processes the full file
|
||||||
|
- Type-detection wrappers assumed identical even when they bundle different library/database versions
|
||||||
|
|
||||||
### Evasion Tricks
|
### Evasion Tricks
|
||||||
|
|
||||||
@@ -146,8 +150,9 @@ Upload surfaces are high risk: server-side execution (RCE), stored XSS, malware
|
|||||||
1. **Map the pipeline** - Client → ingress → storage → processors → serving. Note where validation and auth occur
|
1. **Map the pipeline** - Client → ingress → storage → processors → serving. Note where validation and auth occur
|
||||||
2. **Identify allowed types** - Size limits, filename rules, storage keys, and who serves the content
|
2. **Identify allowed types** - Size limits, filename rules, storage keys, and who serves the content
|
||||||
3. **Collect baselines** - Capture resulting URLs and headers for legitimate uploads
|
3. **Collect baselines** - Capture resulting URLs and headers for legitimate uploads
|
||||||
4. **Exercise bypass families** - Extension games, MIME/content-type, magic bytes, polyglots, metadata payloads, archive structure
|
4. **Map validators and consumers** - Identify the detector/library/version when possible and every later parser, converter, renderer, or browser context
|
||||||
5. **Validate execution** - Can uploaded content execute on server or client?
|
5. **Exercise bypass families** - Extension games, MIME/content-type, magic bytes, parser limits, polyglots, metadata payloads, archive structure
|
||||||
|
6. **Validate execution** - Prove the accepted object reaches a more privileged consumer and can execute or render active content
|
||||||
|
|
||||||
## Validation
|
## Validation
|
||||||
|
|
||||||
@@ -182,6 +187,7 @@ Upload surfaces are high risk: server-side execution (RCE), stored XSS, malware
|
|||||||
8. When you cannot get execution, aim for stored XSS or header-driven script execution
|
8. When you cannot get execution, aim for stored XSS or header-driven script execution
|
||||||
9. Validate that CDNs honor attachment/nosniff
|
9. Validate that CDNs honor attachment/nosniff
|
||||||
10. Document full pipeline behavior per asset type
|
10. Document full pipeline behavior per asset type
|
||||||
|
11. Reproduce detector/consumer mismatches on the deployed library versions; OS packages and language bindings may ship different limits
|
||||||
|
|
||||||
## Summary
|
## Summary
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ Prompt injection occurs when attacker-influenced content changes model behavior
|
|||||||
|
|
||||||
Load `llm_applications` for the full OWASP 2026 LLM01-LLM10 architecture and coverage workflow. Treat every LLM feature as a potential confused deputy: models cannot reliably distinguish instructions from data, but impact depends on the application's data, tools, decisions, and output sinks.
|
Load `llm_applications` for the full OWASP 2026 LLM01-LLM10 architecture and coverage workflow. Treat every LLM feature as a potential confused deputy: models cannot reliably distinguish instructions from data, but impact depends on the application's data, tools, decisions, and output sinks.
|
||||||
|
|
||||||
|
When the system can invoke MCP servers, plugins, skills, delegated agents, or consequential tools, also load `agentic_system_security` to model effective authority, target-side authorization, executable component supply chain, and repeatable safety regression. This skill remains focused on instruction/data confusion and unsafe model output.
|
||||||
|
|
||||||
## Attack Surface
|
## Attack Surface
|
||||||
|
|
||||||
**Direct Injection**
|
**Direct Injection**
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ Improper file path handling and dynamic inclusion enable sensitive file disclosu
|
|||||||
|
|
||||||
**Path Traversal**
|
**Path Traversal**
|
||||||
- Read files outside intended roots via `../`, encoding, normalization gaps
|
- Read files outside intended roots via `../`, encoding, normalization gaps
|
||||||
|
- Write or create files outside intended roots, then evaluate framework-controlled resolution paths separately from direct web access
|
||||||
|
|
||||||
**Local File Inclusion (LFI)**
|
**Local File Inclusion (LFI)**
|
||||||
- Include server-side files into interpreters/templates
|
- Include server-side files into interpreters/templates
|
||||||
@@ -51,7 +52,7 @@ Improper file path handling and dynamic inclusion enable sensitive file disclosu
|
|||||||
### Capability Probes
|
### Capability Probes
|
||||||
|
|
||||||
- Path traversal baseline: `../../etc/hosts` and `C:\Windows\win.ini`
|
- Path traversal baseline: `../../etc/hosts` and `C:\Windows\win.ini`
|
||||||
- Encodings: `%2e%2e%2f`, `%252e%252e%252f`, `..%2f`, `..%5c`, mixed UTF-8 (`%c0%2e`), Unicode dots and slashes
|
- Encodings: `%2e%2e%2f`, `%252e%252e%252f`, `..%2f`, `..%5c`, and Unicode lookalikes only where a documented conversion layer maps them to path syntax
|
||||||
- Normalization tests: `..../`, `..\\`, `././`, trailing dot/double dot segments; repeated decoding
|
- Normalization tests: `..../`, `..\\`, `././`, trailing dot/double dot segments; repeated decoding
|
||||||
- Absolute path acceptance: `/etc/passwd`, `C:\Windows\System32\drivers\etc\hosts`
|
- Absolute path acceptance: `/etc/passwd`, `C:\Windows\System32\drivers\etc\hosts`
|
||||||
- Server mismatch: `/static/..;/../etc/passwd` ("..;"), encoded slashes (`%2F`), double-decoding via upstream
|
- Server mismatch: `/static/..;/../etc/passwd` ("..;"), encoded slashes (`%2F`), double-decoding via upstream
|
||||||
@@ -69,7 +70,7 @@ Improper file path handling and dynamic inclusion enable sensitive file disclosu
|
|||||||
|
|
||||||
### OAST
|
### OAST
|
||||||
|
|
||||||
- RFI/LFI with wrappers that trigger outbound fetches (HTTP/DNS) to confirm inclusion/execution
|
- For RFI or URL-capable resource loaders, a correlated callback confirms server-side resolution/fetch. It does not by itself prove inclusion or execution; use a separate response or side-effect oracle for that claim.
|
||||||
|
|
||||||
### Side Effects
|
### Side Effects
|
||||||
|
|
||||||
@@ -81,7 +82,7 @@ Improper file path handling and dynamic inclusion enable sensitive file disclosu
|
|||||||
### Path Traversal Bypasses
|
### Path Traversal Bypasses
|
||||||
|
|
||||||
**Encodings**
|
**Encodings**
|
||||||
- Single/double URL-encoding, mixed case, overlong UTF-8, UTF-16, path normalization oddities
|
- Single/double URL-encoding, mixed case, UTF-16 or Unicode conversion only when present in the stack, and path normalization oddities
|
||||||
|
|
||||||
**Mixed Separators**
|
**Mixed Separators**
|
||||||
- `/` and `\\` on Windows; `//` and `\\\\` collapse differences across frameworks
|
- `/` and `\\` on Windows; `//` and `\\\\` collapse differences across frameworks
|
||||||
@@ -147,13 +148,38 @@ Improper file path handling and dynamic inclusion enable sensitive file disclosu
|
|||||||
- Verify symlink handling and path canonicalization prior to write
|
- Verify symlink handling and path canonicalization prior to write
|
||||||
- Impact: overwrite config/templates or drop webshells into served directories
|
- Impact: overwrite config/templates or drop webshells into served directories
|
||||||
|
|
||||||
|
### File Write to Execution
|
||||||
|
|
||||||
|
Characterize the write primitive before choosing a payload:
|
||||||
|
|
||||||
|
- create vs overwrite vs append; atomic replace vs streamed write
|
||||||
|
- absolute vs relative path; controllable directory, filename, extension, and bytes
|
||||||
|
- text encoding, newline conversion, templating, compression, or report generation applied before write
|
||||||
|
- target process permissions and whether symlinks are followed
|
||||||
|
- immediate load, hot reload, cache invalidation, restart, scheduled task, or user action required
|
||||||
|
|
||||||
|
Then inventory generic execution and influence surfaces:
|
||||||
|
|
||||||
|
- view/template search paths and implicit rendering
|
||||||
|
- module, controller, plugin, package, or class autoload directories
|
||||||
|
- application bootstrap files and language package initializers
|
||||||
|
- server/user configuration that changes handler or interpreter behavior
|
||||||
|
- job definitions, hooks, startup scripts, cron/task inputs, and CI workspace files
|
||||||
|
- logs, sessions, caches, generated sources, and compiled-template directories later included or evaluated
|
||||||
|
|
||||||
|
Do not require the malicious file to be directly web-accessible. An HTTP extension allowlist can block `/path/payload.ext` while an internal view engine, autoloader, or interpreter still opens and executes that file through a clean route. Trace public request filtering and internal file resolution as separate security boundaries.
|
||||||
|
|
||||||
|
Test search order with candidate marker files or filesystem traces. Trigger the normal route/action that causes internal resolution. Record whether the framework creates, compiles, caches, or executes the artifact and what reload condition is required.
|
||||||
|
|
||||||
## Testing Methodology
|
## Testing Methodology
|
||||||
|
|
||||||
1. **Inventory file operations** - Downloads, previews, templates, logs, exports/imports, report engines, uploads, archive extractors
|
1. **Inventory file operations** - Downloads, previews, templates, logs, exports/imports, report engines, uploads, archive extractors
|
||||||
2. **Identify input joins** - Path joins (base + user), include/require/template loads, resource fetchers, archive extract destinations
|
2. **Identify input joins** - Path joins (base + user), include/require/template loads, resource fetchers, archive extract destinations
|
||||||
3. **Probe normalization** - Separators, encodings, double-decodes, case, trailing dots/slashes
|
3. **Probe normalization** - Separators, encodings, double-decodes, case, trailing dots/slashes
|
||||||
4. **Compare behaviors** - Web server vs application behavior
|
4. **Compare behaviors** - Web server vs application behavior
|
||||||
5. **Escalate** - From disclosure (read) to influence (write/extract/include), then to execution (wrapper/engine chains)
|
5. **Characterize writes** - Determine create/overwrite/append, path and byte control, permissions, and reload/trigger conditions
|
||||||
|
6. **Map resolvers** - Test template/view search paths, autoloaders, plugins, configs, jobs, and other internal consumers separately from direct file serving
|
||||||
|
7. **Escalate** - From disclosure (read) to influence (write/extract/include), then to execution through a proven resolver or interpreter
|
||||||
|
|
||||||
## Validation
|
## Validation
|
||||||
|
|
||||||
@@ -161,7 +187,8 @@ Improper file path handling and dynamic inclusion enable sensitive file disclosu
|
|||||||
2. For LFI, demonstrate inclusion of a benign local file or harmless wrapper output (`php://filter` base64 of index.php)
|
2. For LFI, demonstrate inclusion of a benign local file or harmless wrapper output (`php://filter` base64 of index.php)
|
||||||
3. For RFI, prove remote fetch by OAST or controlled output; avoid destructive payloads
|
3. For RFI, prove remote fetch by OAST or controlled output; avoid destructive payloads
|
||||||
4. For Zip Slip, create an archive with `../` entries and show write outside target (e.g., marker file read back)
|
4. For Zip Slip, create an archive with `../` entries and show write outside target (e.g., marker file read back)
|
||||||
5. Provide before/after file paths, exact requests, and content hashes/lengths for reproducibility
|
5. For file-write chains, first prove a canary is created at the intended path, then prove the normal resolver loads it; document cache/reload requirements
|
||||||
|
6. Provide before/after file paths, exact requests, and content hashes/lengths for reproducibility
|
||||||
|
|
||||||
## False Positives
|
## False Positives
|
||||||
|
|
||||||
@@ -184,6 +211,7 @@ Improper file path handling and dynamic inclusion enable sensitive file disclosu
|
|||||||
3. For LFI, prefer `php://filter` base64 probes over destructive payloads; enumerate readable logs and sessions
|
3. For LFI, prefer `php://filter` base64 probes over destructive payloads; enumerate readable logs and sessions
|
||||||
4. Validate extraction code with synthetic archives; include symlinks and deep `../` chains
|
4. Validate extraction code with synthetic archives; include symlinks and deep `../` chains
|
||||||
5. Use minimal PoCs and hard evidence (hashes, paths). Avoid noisy DoS against filesystems
|
5. Use minimal PoCs and hard evidence (hashes, paths). Avoid noisy DoS against filesystems
|
||||||
|
6. When direct execution is blocked, enumerate internal search paths before assuming the write is low impact
|
||||||
|
|
||||||
## Summary
|
## Summary
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,189 @@
|
|||||||
|
---
|
||||||
|
name: semantic-confusion
|
||||||
|
description: Cross-component semantic confusion testing for parser differentials, normalization mismatches, overloaded fields, lifecycle state drift, internal redirects, protocol translation, and validator-to-sink inconsistencies
|
||||||
|
---
|
||||||
|
|
||||||
|
# Semantic Confusion
|
||||||
|
|
||||||
|
Use this skill when two or more components consume the same attacker-influenced value. The central question is not merely whether input is validated, but whether every consumer assigns the same meaning to the value at the moment it makes a security decision.
|
||||||
|
|
||||||
|
Typical chains cross a validator, router, proxy, framework, parser, filesystem, interpreter, cache, or browser. A value can be safe in one representation and dangerous after a later decode, normalization, fallback, or field mutation.
|
||||||
|
|
||||||
|
## Authorization and Safety Boundary
|
||||||
|
|
||||||
|
- Run active differentials only against explicit authorized targets. Preserve destination allowlists and set request, rate, body, response, timeout, and retry ceilings.
|
||||||
|
- Perform malformed framing, delayed-body, oversized-input, crash, or resource-exhaustion cases only in a restartable isolated lab with health monitoring.
|
||||||
|
- Use synthetic canaries, reversible actions, non-secret protected resources, or a constant per-test callback identifier. Never place target-derived secrets in an OAST label/body.
|
||||||
|
- Change one representation axis at a time so the security-relevant disagreement remains attributable to a specific boundary.
|
||||||
|
- Pair `browser_security` when the final consumer is a browser context, worker, cache, or navigation state machine.
|
||||||
|
- Do not load this skill for pure ownership drift where every component resolves and interprets the name consistently; use `infrastructure_lifecycle` unless a representation, alias, identity, or resolution-result mismatch is present.
|
||||||
|
|
||||||
|
## Core Model
|
||||||
|
|
||||||
|
Build a transformation graph before spraying payloads:
|
||||||
|
|
||||||
|
```text
|
||||||
|
raw bytes
|
||||||
|
-> transport parser
|
||||||
|
-> proxy / middleware representation
|
||||||
|
-> authorization or validation decision
|
||||||
|
-> rewrite / decode / normalization
|
||||||
|
-> internal redirect or dispatch
|
||||||
|
-> final sink interpretation
|
||||||
|
```
|
||||||
|
|
||||||
|
For every edge, record:
|
||||||
|
|
||||||
|
- exact input representation: bytes, string, URL, path, header list, object, or structured field
|
||||||
|
- owning component and implementation/version
|
||||||
|
- transformation performed, including error and fallback behavior
|
||||||
|
- security decision made before or after the transformation
|
||||||
|
- whether the original and transformed values remain available simultaneously
|
||||||
|
- whether a field changes semantic type, such as filename to URL or MIME type to handler
|
||||||
|
|
||||||
|
The highest-signal condition is `security_check(value_A)` followed by `sink(transform(value_A))` where the checked and consumed representations are not equivalent.
|
||||||
|
|
||||||
|
## High-Value Confusion Classes
|
||||||
|
|
||||||
|
### Parser Differentials
|
||||||
|
|
||||||
|
- Compare browser, framework, proxy, library, and backend parsing of the exact same bytes.
|
||||||
|
- Test duplicate and comma-joined fields, first-match vs last-match behavior, invalid-token recovery, comments, quoting, and empty members.
|
||||||
|
- Include structured formats and metadata: URL, MIME, JSON, multipart, XML, cookies, forwarded headers, and serialized objects.
|
||||||
|
- Treat leniency as a security feature only when every downstream consumer is equally lenient in the same way.
|
||||||
|
|
||||||
|
### Normalization and Canonicalization Drift
|
||||||
|
|
||||||
|
- Map percent-decoding count, Unicode conversion, slash/backslash handling, dot-segment removal, case folding, IDNA, numeric IP conversion, and filesystem cleanup.
|
||||||
|
- Compare string-prefix checks with segment-aware or origin-aware comparisons.
|
||||||
|
- Test malformed Unicode and replacement behavior; a rejected code point may become an allowed delimiter or wildcard later.
|
||||||
|
- Test path, query, and fragment separately. Browsers and routers commonly transform each source differently.
|
||||||
|
|
||||||
|
### Field and Type Overloading
|
||||||
|
|
||||||
|
- Identify shared fields reused for different concepts: path vs URL, content type vs handler, display name vs executable name, route vs filesystem location.
|
||||||
|
- Trace every writer and reader of the field across the complete lifecycle.
|
||||||
|
- Look for implicit fallback: when the intended field is empty, another field becomes authoritative.
|
||||||
|
- Exercise fields after errors, rewrites, subrequests, retries, internal redirects, and protocol upgrades/downgrades.
|
||||||
|
|
||||||
|
### Lifecycle and State Drift
|
||||||
|
|
||||||
|
- Trigger error paths that should terminate processing and verify that later phases actually stop.
|
||||||
|
- Look for stale metadata copied into a new request, subrequest, background job, cache entry, or retry.
|
||||||
|
- Compare direct external access with internal dispatch. Edge controls may inspect the public URL while an internal resolver opens a different path or invokes a different handler.
|
||||||
|
- Test order-dependent behavior: validation before rewrite, auth before route normalization, or content classification before processing.
|
||||||
|
|
||||||
|
### Boundary Translation
|
||||||
|
|
||||||
|
- Map HTTP/2 to HTTP/1 translation, proxy to application rewriting, URL to filesystem resolution, upload detector to content consumer, and client router to API request construction.
|
||||||
|
- In a restartable lab and only when supported by evidence, vary framing, bounded delays/body sizes, content type, pseudo-headers, and method conversion. Check target health after resource-sensitive cases.
|
||||||
|
- Do not assume a WAF or authorization sidecar sees the full body or final normalized request.
|
||||||
|
|
||||||
|
### Namespace and Resolution Fallback
|
||||||
|
|
||||||
|
- Identify names resolved across multiple scopes: local path, environment `PATH`, cache, private registry, public registry, plugin directory, template search path, or autoloader.
|
||||||
|
- Record lookup order and what happens when the intended entry is missing.
|
||||||
|
- Compare protected package/module names with exposed command, binary, handler, or alias names. For npm, a scoped package can expose an unscoped `bin` name, so the protected package name and invoked executable may differ.
|
||||||
|
- Treat automatic remote fallback or search-path fallback as an execution boundary.
|
||||||
|
- Load `npx_confusion` when `npx` or `npm exec` may reinterpret a missing executable as a public package spec.
|
||||||
|
|
||||||
|
## Reconnaissance
|
||||||
|
|
||||||
|
### Black-Box Mapping
|
||||||
|
|
||||||
|
1. Capture a clean baseline with raw request and response bytes.
|
||||||
|
2. Change one representation axis at a time: encoding depth, delimiter, duplicate, separator, method, protocol, body framing, or Unicode form.
|
||||||
|
3. Diff status, headers, body digest/length, timing, redirects, cache state, and out-of-band callbacks.
|
||||||
|
4. Replay through different paths: direct origin vs CDN, HTTP/1.1 vs HTTP/2, public route vs alternate host, synchronous vs background processing.
|
||||||
|
5. Cluster responses by behavior before escalating. Small differentials reveal component boundaries.
|
||||||
|
|
||||||
|
### Source-Aware Mapping
|
||||||
|
|
||||||
|
- Find every read and write of shared request/context fields, not just the obvious sink.
|
||||||
|
- Trace route matching, auth middleware, rewrites, internal redirects, handler selection, and response generation in execution order.
|
||||||
|
- Inventory decode/parse/normalize calls and note whether return values or errors are ignored.
|
||||||
|
- Search for compatibility fallbacks, legacy aliases, permissive recovery, default handlers, and search-path iteration.
|
||||||
|
- Inspect packaging and deployment defaults; distro configuration, enabled modules, plugins, and symlinks often determine reachability.
|
||||||
|
|
||||||
|
## Differential Test Matrix
|
||||||
|
|
||||||
|
Build a bounded matrix from relevant axes instead of blindly combining everything:
|
||||||
|
|
||||||
|
| Axis | Representative variants |
|
||||||
|
|---|---|
|
||||||
|
| Encoding | raw, once encoded, twice encoded, mixed case, malformed Unicode |
|
||||||
|
| Structure | duplicate, comma-joined, empty member, quoted, comment-like suffix |
|
||||||
|
| Path | `/`, `\\`, `//`, dot segments, absolute, sibling-prefix collision |
|
||||||
|
| URL | userinfo, numeric IP, alternate IP radix, trailing dot, fragment/query split |
|
||||||
|
| Transport | HTTP/1.1, HTTP/2, chunked/fixed body, delayed DATA, oversized body |
|
||||||
|
| Lifecycle | normal, error, retry, internal redirect, cache hit, background worker |
|
||||||
|
| Consumer | edge, application, library, filesystem, interpreter, browser |
|
||||||
|
|
||||||
|
Select axes supported by evidence from the target. Record which component saw which representation.
|
||||||
|
|
||||||
|
### Repeatable Harnesses
|
||||||
|
|
||||||
|
- For two local parsers, canonicalizers, or validator/consumer functions, load `hypothesis` and express the expected relationship as a property. Bound sizes/examples and keep the minimized disagreement as a regression test.
|
||||||
|
- For an ordered HTTP flow with cookies, redirects, captured values, and assertions, load `hurl` and encode vulnerable, fixed, and negative-control environments using the same request chain.
|
||||||
|
- Use raw-byte or protocol-specific harnesses when a high-level HTTP client would normalize the ambiguity away.
|
||||||
|
- Separate input generation from transport. Generators that are safe against pure local functions become active fuzzers when connected to a live target.
|
||||||
|
|
||||||
|
## Chaining Strategy
|
||||||
|
|
||||||
|
Treat the first differential as a primitive, then ask what authority the later consumer has:
|
||||||
|
|
||||||
|
- auth or ACL bypass -> protected route or file
|
||||||
|
- path/URL confusion -> source disclosure, SSRF, local socket, or unintended handler
|
||||||
|
- detector/consumer mismatch -> active upload processing or inline browser execution
|
||||||
|
- internal redirect state carryover -> handler selection or policy bypass
|
||||||
|
- search-path or namespace fallback -> attacker-controlled code resolution
|
||||||
|
- browser/router decode -> client-side path traversal, CSRF-like action, SSRF, or XSS sink
|
||||||
|
|
||||||
|
Enumerate existing local gadgets only after the primitive is proven. Prefer generic classes such as interpreters, template engines, debug tools, package scripts, local sockets, and autoload paths over a vendor-specific file list.
|
||||||
|
|
||||||
|
## Testing Methodology
|
||||||
|
|
||||||
|
1. **Define the invariant** - State what all components are expected to agree on: origin, path, type, handler, identity, length, or package name.
|
||||||
|
2. **Draw the graph** - List consumers and transformations in real execution order.
|
||||||
|
3. **Locate early decisions** - Mark validation, auth, WAF, cache, and routing checks.
|
||||||
|
4. **Locate late meaning changes** - Mark decodes, rewrites, fallback, internal dispatch, and sink parsing.
|
||||||
|
5. **Build a focused matrix** - Exercise only transformations supported by the stack.
|
||||||
|
6. **Isolate the disagreement** - Produce paired inputs that differ at one boundary and explain both interpretations.
|
||||||
|
7. **Prove the primitive safely** - Use a synthetic protected canary, reversible marker, constant callback identifier, or no-op handler whose behavior and side effects are understood.
|
||||||
|
8. **Escalate by capability** - Track Read -> influence -> write -> dispatch -> execute transitions with evidence and prerequisites for every edge.
|
||||||
|
9. **Cross-check versions/configurations** - Reproduce on a fixed version or hardened configuration when possible.
|
||||||
|
|
||||||
|
## Validation
|
||||||
|
|
||||||
|
A valid confusion finding should include:
|
||||||
|
|
||||||
|
1. the exact bytes or structured input supplied
|
||||||
|
2. the representation observed by the security control
|
||||||
|
3. the different representation observed by the final consumer
|
||||||
|
4. the transformation or lifecycle event that created the difference
|
||||||
|
5. paired control and exploit results across repeat runs
|
||||||
|
6. version, protocol, configuration, and interaction prerequisites
|
||||||
|
7. a minimal impact proof that does not depend on unrelated undefined behavior
|
||||||
|
|
||||||
|
## False Positives
|
||||||
|
|
||||||
|
- Different error messages with identical final authorization and sink behavior
|
||||||
|
- A parser accepts odd syntax but downstream consumers preserve the same safe meaning
|
||||||
|
- A normalization difference visible only in logs, with no security decision between representations
|
||||||
|
- WAF bypass where the application itself rejects the request identically
|
||||||
|
- Version-specific behavior claimed as universal without testing the relevant deployment
|
||||||
|
- A search-path candidate that is attacker-named but cannot be created, claimed, loaded, or executed
|
||||||
|
|
||||||
|
## Pro Tips
|
||||||
|
|
||||||
|
1. Begin with relationships and shared state, not endpoint payload lists.
|
||||||
|
2. Preserve raw traffic; high-level clients often normalize away the exploit before sending it.
|
||||||
|
3. Error paths are alternate lifecycles. Verify which fields survive and which phases still execute.
|
||||||
|
4. Compare direct and internal access separately; ingress policy rarely governs framework file IO or handler dispatch.
|
||||||
|
5. When a prefix allowlist is used, test a sibling sharing the prefix and verify with a segment-aware comparison.
|
||||||
|
6. Distinguish presence, reachability, and impact. Each needs separate evidence.
|
||||||
|
7. Generalize a finding by naming the disagreement class, not by copying its final payload.
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
Semantic confusion exists when a security decision and a privileged consumer disagree about the meaning of the same attacker-influenced data. Model the entire transformation lifecycle, isolate one disagreement at a time, and prove both interpretations. The reusable unit is the boundary and its invariant—not a CVE-specific string.
|
||||||
@@ -7,6 +7,8 @@ description: Subdomain takeover testing for dangling DNS records and unclaimed c
|
|||||||
|
|
||||||
Subdomain takeover lets an attacker serve content from a trusted subdomain by claiming resources referenced by dangling DNS (CNAME/A/ALIAS/NS) or mis-bound provider configurations. Consequences include phishing on a trusted origin, cookie and CORS pivot, OAuth redirect abuse, and CDN cache poisoning.
|
Subdomain takeover lets an attacker serve content from a trusted subdomain by claiming resources referenced by dangling DNS (CNAME/A/ALIAS/NS) or mis-bound provider configurations. Consequences include phishing on a trusted origin, cookie and CORS pivot, OAuth redirect abuse, and CDN cache poisoning.
|
||||||
|
|
||||||
|
Use `infrastructure_lifecycle` instead for expired registrable domains, MX/recovery identity, update/control endpoints, or long-lived software consumers. Provider error fingerprints are leads; confirm current claimability and custom-domain ownership requirements from authoritative provider behavior/documentation.
|
||||||
|
|
||||||
## Attack Surface
|
## Attack Surface
|
||||||
|
|
||||||
- Dangling CNAME/A/ALIAS to third-party services (hosting, storage, serverless, CDN)
|
- Dangling CNAME/A/ALIAS to third-party services (hosting, storage, serverless, CDN)
|
||||||
|
|||||||
@@ -10,20 +10,16 @@ import urllib.request
|
|||||||
from typing import TYPE_CHECKING, Any, Literal
|
from typing import TYPE_CHECKING, Any, Literal
|
||||||
from urllib.parse import parse_qs, urlencode, urlparse, urlunparse
|
from urllib.parse import parse_qs, urlencode, urlparse, urlunparse
|
||||||
|
|
||||||
from caido_sdk_client import Client, TokenAuthOptions
|
|
||||||
from caido_sdk_client.types import (
|
|
||||||
ConnectionInfoInput,
|
|
||||||
CreateScopeOptions,
|
|
||||||
ReplaySendOptions,
|
|
||||||
RequestGetOptions,
|
|
||||||
UpdateScopeOptions,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
|
# The generated Caido GraphQL schema module is slow to import and is only needed
|
||||||
|
# once a proxy tool actually runs, so the SDK is imported on first use rather
|
||||||
|
# than at module scope, which would put it on every launch's critical path.
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from collections.abc import Awaitable, Callable
|
from collections.abc import Awaitable, Callable
|
||||||
|
|
||||||
|
from caido_sdk_client import Client
|
||||||
from caido_sdk_client import Client as CaidoClient
|
from caido_sdk_client import Client as CaidoClient
|
||||||
|
from caido_sdk_client.types import ConnectionInfoInput
|
||||||
|
|
||||||
|
|
||||||
RequestPart = Literal["request", "response"]
|
RequestPart = Literal["request", "response"]
|
||||||
@@ -85,6 +81,8 @@ def _login_as_guest() -> str:
|
|||||||
|
|
||||||
|
|
||||||
async def _new_client() -> Client:
|
async def _new_client() -> Client:
|
||||||
|
from caido_sdk_client import Client, TokenAuthOptions
|
||||||
|
|
||||||
token = await asyncio.to_thread(_login_as_guest)
|
token = await asyncio.to_thread(_login_as_guest)
|
||||||
client = Client(caido_url(), auth=TokenAuthOptions(token=token))
|
client = Client(caido_url(), auth=TokenAuthOptions(token=token))
|
||||||
await client.connect()
|
await client.connect()
|
||||||
@@ -163,6 +161,8 @@ async def get_request_with_client(
|
|||||||
# Passing False for either causes pydantic validation to fail with
|
# Passing False for either causes pydantic validation to fail with
|
||||||
# "Field required" on the missing raw field. Always request both —
|
# "Field required" on the missing raw field. Always request both —
|
||||||
# the caller picks which one to surface via ``part``.
|
# the caller picks which one to surface via ``part``.
|
||||||
|
from caido_sdk_client.types import RequestGetOptions
|
||||||
|
|
||||||
opts = RequestGetOptions(request_raw=True, response_raw=True)
|
opts = RequestGetOptions(request_raw=True, response_raw=True)
|
||||||
return await client.request.get(request_id, opts)
|
return await client.request.get(request_id, opts)
|
||||||
|
|
||||||
@@ -206,6 +206,8 @@ def build_raw_request(
|
|||||||
if body:
|
if body:
|
||||||
final_headers["Content-Length"] = str(len(body.encode("utf-8")))
|
final_headers["Content-Length"] = str(len(body.encode("utf-8")))
|
||||||
|
|
||||||
|
from caido_sdk_client.types import ConnectionInfoInput
|
||||||
|
|
||||||
lines = [f"{method.upper()} {path} HTTP/1.1"]
|
lines = [f"{method.upper()} {path} HTTP/1.1"]
|
||||||
lines.extend(f"{k}: {v}" for k, v in final_headers.items())
|
lines.extend(f"{k}: {v}" for k, v in final_headers.items())
|
||||||
raw = ("\r\n".join(lines) + "\r\n\r\n" + body).encode("utf-8")
|
raw = ("\r\n".join(lines) + "\r\n\r\n" + body).encode("utf-8")
|
||||||
@@ -334,6 +336,8 @@ async def replay_send_raw(
|
|||||||
raw: bytes,
|
raw: bytes,
|
||||||
connection: ConnectionInfoInput,
|
connection: ConnectionInfoInput,
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
|
from caido_sdk_client.types import ReplaySendOptions
|
||||||
|
|
||||||
started = time.time()
|
started = time.time()
|
||||||
# Create an empty replay session, then dispatch via ``send()``.
|
# Create an empty replay session, then dispatch via ``send()``.
|
||||||
# Passing ``CreateReplaySessionFromRaw`` here would also seed a stored
|
# Passing ``CreateReplaySessionFromRaw`` here would also seed a stored
|
||||||
@@ -391,6 +395,8 @@ async def scope_create(
|
|||||||
allowlist: list[str] | None = None,
|
allowlist: list[str] | None = None,
|
||||||
denylist: list[str] | None = None,
|
denylist: list[str] | None = None,
|
||||||
) -> Any:
|
) -> Any:
|
||||||
|
from caido_sdk_client.types import CreateScopeOptions
|
||||||
|
|
||||||
return await client.scope.create(
|
return await client.scope.create(
|
||||||
CreateScopeOptions(
|
CreateScopeOptions(
|
||||||
name=name,
|
name=name,
|
||||||
@@ -408,6 +414,8 @@ async def scope_update(
|
|||||||
allowlist: list[str] | None = None,
|
allowlist: list[str] | None = None,
|
||||||
denylist: list[str] | None = None,
|
denylist: list[str] | None = None,
|
||||||
) -> Any:
|
) -> Any:
|
||||||
|
from caido_sdk_client.types import UpdateScopeOptions
|
||||||
|
|
||||||
return await client.scope.update(
|
return await client.scope.update(
|
||||||
scope_id,
|
scope_id,
|
||||||
UpdateScopeOptions(
|
UpdateScopeOptions(
|
||||||
|
|||||||
@@ -112,3 +112,19 @@ def test_wait_for_agents_is_available_in_both_modes() -> None:
|
|||||||
for interactive in (True, False):
|
for interactive in (True, False):
|
||||||
agent = factory.build_strix_agent(is_root=True, interactive=interactive)
|
agent = factory.build_strix_agent(is_root=True, interactive=interactive)
|
||||||
assert "wait_for_agents" in [t.name for t in agent.tools]
|
assert "wait_for_agents" in [t.name for t in agent.tools]
|
||||||
|
|
||||||
|
|
||||||
|
def test_strict_tool_schemas_can_be_disabled_per_route() -> None:
|
||||||
|
"""Claude routes cap strict tools; the toolset must be sendable without strict."""
|
||||||
|
agent = factory.build_strix_agent(is_root=True, strict_tool_schemas=False)
|
||||||
|
|
||||||
|
function_tools = [t for t in agent.tools if isinstance(t, FunctionTool)]
|
||||||
|
assert function_tools
|
||||||
|
assert not any(t.strict_json_schema for t in function_tools)
|
||||||
|
|
||||||
|
|
||||||
|
def test_disabling_strict_leaves_shared_tools_untouched() -> None:
|
||||||
|
factory.build_strix_agent(is_root=True, strict_tool_schemas=False)
|
||||||
|
agent = factory.build_strix_agent(is_root=True)
|
||||||
|
|
||||||
|
assert any(t.strict_json_schema for t in agent.tools if isinstance(t, FunctionTool))
|
||||||
|
|||||||
@@ -227,3 +227,18 @@ def test_resume_still_requires_targets_or_a_workspace(
|
|||||||
cli_main.parse_arguments()
|
cli_main.parse_arguments()
|
||||||
|
|
||||||
assert "has no targets_info" in capsys.readouterr().err
|
assert "has no targets_info" in capsys.readouterr().err
|
||||||
|
|
||||||
|
def test_resume_non_object_run_json_exits(tmp_path: Path, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]) -> None:
|
||||||
|
monkeypatch.chdir(tmp_path)
|
||||||
|
run_dir = tmp_path / "strix_runs" / "pentest_abcd"
|
||||||
|
run_dir.mkdir(parents=True)
|
||||||
|
(run_dir / "run.json").write_text("[]", encoding="utf-8")
|
||||||
|
|
||||||
|
monkeypatch.setattr(sys, "argv", ["strix", "--resume", "pentest_abcd"])
|
||||||
|
with pytest.raises(SystemExit) as exc_info:
|
||||||
|
cli_main.parse_arguments()
|
||||||
|
|
||||||
|
assert exc_info.value.code == 2
|
||||||
|
captured = capsys.readouterr()
|
||||||
|
assert "run.json unreadable" in captured.err
|
||||||
|
assert "not an object" in captured.err
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ def test_context_window_chatgpt_prefix_skips_provider_auth(
|
|||||||
calls.append(model)
|
calls.append(model)
|
||||||
return {"max_input_tokens": 1_050_000, "max_output_tokens": 128_000}
|
return {"max_input_tokens": 1_050_000, "max_output_tokens": 128_000}
|
||||||
|
|
||||||
monkeypatch.setattr("strix.llm.context_budget.litellm.get_model_info", _model_info)
|
monkeypatch.setattr("litellm.get_model_info", _model_info)
|
||||||
try:
|
try:
|
||||||
assert context_budget.context_window("chatgpt/gpt-5.6-luna") == 1_050_000
|
assert context_budget.context_window("chatgpt/gpt-5.6-luna") == 1_050_000
|
||||||
assert calls == ["gpt-5.6-luna"]
|
assert calls == ["gpt-5.6-luna"]
|
||||||
@@ -45,7 +45,7 @@ def test_context_window_unmapped_uses_fallback(monkeypatch: pytest.MonkeyPatch)
|
|||||||
def _raise(_model: str) -> dict[str, int]:
|
def _raise(_model: str) -> dict[str, int]:
|
||||||
raise ValueError("This model isn't mapped yet.")
|
raise ValueError("This model isn't mapped yet.")
|
||||||
|
|
||||||
monkeypatch.setattr("strix.llm.context_budget.litellm.get_model_info", _raise)
|
monkeypatch.setattr("litellm.get_model_info", _raise)
|
||||||
expected = load_settings().context.fallback_context_tokens
|
expected = load_settings().context.fallback_context_tokens
|
||||||
assert context_budget.context_window("totally-made-up-model") == expected
|
assert context_budget.context_window("totally-made-up-model") == expected
|
||||||
context_budget._model_info.cache_clear()
|
context_budget._model_info.cache_clear()
|
||||||
@@ -55,7 +55,7 @@ def test_count_tokens_fallback_on_error(monkeypatch: pytest.MonkeyPatch) -> None
|
|||||||
def _raise(**_kwargs: object) -> int:
|
def _raise(**_kwargs: object) -> int:
|
||||||
raise RuntimeError("no tokenizer")
|
raise RuntimeError("no tokenizer")
|
||||||
|
|
||||||
monkeypatch.setattr("strix.llm.context_budget.litellm.token_counter", _raise)
|
monkeypatch.setattr("litellm.token_counter", _raise)
|
||||||
# Falls back to UTF-8 byte length (upper bound on tokens).
|
# Falls back to UTF-8 byte length (upper bound on tokens).
|
||||||
assert context_budget.count_tokens("weird-model", "x" * 400) == 400
|
assert context_budget.count_tokens("weird-model", "x" * 400) == 400
|
||||||
assert context_budget.count_tokens("weird-model", "😀" * 10) == 40
|
assert context_budget.count_tokens("weird-model", "😀" * 10) == 40
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ from strix.config.models import (
|
|||||||
RECOMMENDED_MODEL_NAMES,
|
RECOMMENDED_MODEL_NAMES,
|
||||||
is_recommended_or_frontier_model,
|
is_recommended_or_frontier_model,
|
||||||
request_timeout_extra_args,
|
request_timeout_extra_args,
|
||||||
|
supports_strict_tool_schemas,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -90,3 +91,24 @@ def test_frontier_model_families_are_accepted(model_name: str) -> None:
|
|||||||
)
|
)
|
||||||
def test_non_frontier_models_are_rejected(model_name: str) -> None:
|
def test_non_frontier_models_are_rejected(model_name: str) -> None:
|
||||||
assert not is_recommended_or_frontier_model(model_name)
|
assert not is_recommended_or_frontier_model(model_name)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"model_name",
|
||||||
|
[
|
||||||
|
"anthropic/claude-sonnet-4-6",
|
||||||
|
"bedrock/anthropic.claude-opus-4-8-v1:0",
|
||||||
|
"vertex_ai/claude-sonnet-5",
|
||||||
|
"Sonnet-5",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_claude_routes_reject_strict_tool_schemas(model_name: str) -> None:
|
||||||
|
assert not supports_strict_tool_schemas(model_name)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"model_name",
|
||||||
|
["openai/gpt-5.4", "gpt-5.4", "gemini/gemini-3.1-pro-preview", "deepseek/deepseek-v4"],
|
||||||
|
)
|
||||||
|
def test_other_routes_keep_strict_tool_schemas(model_name: str) -> None:
|
||||||
|
assert supports_strict_tool_schemas(model_name)
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ from agents.tool import ToolOutputImage
|
|||||||
|
|
||||||
from strix.config.settings import DEFAULT_MAX_TURNS
|
from strix.config.settings import DEFAULT_MAX_TURNS
|
||||||
from strix.interface.tui.backend.controller import TuiController
|
from strix.interface.tui.backend.controller import TuiController
|
||||||
from strix.interface.tui.backend.projection import terminal_projection
|
from strix.interface.tui.backend.projection import bounded_state_projection, terminal_projection
|
||||||
from strix.interface.tui.backend.protocol import (
|
from strix.interface.tui.backend.protocol import (
|
||||||
MAX_COMMAND_BYTES,
|
MAX_COMMAND_BYTES,
|
||||||
PROTOCOL_CAPABILITIES,
|
PROTOCOL_CAPABILITIES,
|
||||||
@@ -215,7 +215,11 @@ def test_unicode_heavy_setup_state_stays_within_control_frame_limit() -> None:
|
|||||||
"Any",
|
"Any",
|
||||||
SimpleNamespace(
|
SimpleNamespace(
|
||||||
caido_url="https://例え.example/" + "道" * 10_000,
|
caido_url="https://例え.example/" + "道" * 10_000,
|
||||||
get_total_llm_usage=lambda: {f"model-{index}": "費" * 10_000 for index in range(20)},
|
get_total_llm_usage=lambda: {
|
||||||
|
"total_tokens": 720_400,
|
||||||
|
"cost": 20.0,
|
||||||
|
**{f"model-{index}": "🔒" * 10_000 for index in range(20)},
|
||||||
|
},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
server = TuiBackendServer(controller)
|
server = TuiBackendServer(controller)
|
||||||
@@ -226,6 +230,26 @@ def test_unicode_heavy_setup_state_stays_within_control_frame_limit() -> None:
|
|||||||
assert len(encoded) <= MAX_COMMAND_BYTES
|
assert len(encoded) <= MAX_COMMAND_BYTES
|
||||||
assert "🔒".encode() in encoded
|
assert "🔒".encode() in encoded
|
||||||
assert snapshot["projection_truncated"] is True
|
assert snapshot["projection_truncated"] is True
|
||||||
|
assert snapshot["usage"] == {"total_tokens": 720_400, "cost": 20.0}
|
||||||
|
|
||||||
|
|
||||||
|
def test_defensive_state_projection_preserves_usage_summary() -> None:
|
||||||
|
controller = TuiController(args())
|
||||||
|
controller.report_state = cast(
|
||||||
|
"Any",
|
||||||
|
SimpleNamespace(
|
||||||
|
caido_url=None,
|
||||||
|
get_total_llm_usage=lambda: {"total_tokens": 720_400, "cost": 20.0},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
state = controller.snapshot()
|
||||||
|
state["provider"] = None
|
||||||
|
state["future_oversized_field"] = "x" * 100_000
|
||||||
|
|
||||||
|
snapshot = bounded_state_projection(state)
|
||||||
|
|
||||||
|
assert snapshot["projection_truncated"] is True
|
||||||
|
assert snapshot["usage"] == {"total_tokens": 720_400, "cost": 20.0}
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
|
|||||||
+49
-7
@@ -11,6 +11,7 @@ from typing import TYPE_CHECKING
|
|||||||
from urllib.parse import urlsplit
|
from urllib.parse import urlsplit
|
||||||
|
|
||||||
from strix.core.paths import latest_run_dir, runs_base_dir
|
from strix.core.paths import latest_run_dir, runs_base_dir
|
||||||
|
from strix.interface.viewer.cli import run_view
|
||||||
from strix.interface.viewer.server import serve
|
from strix.interface.viewer.server import serve
|
||||||
from strix.interface.viewer.transcript import (
|
from strix.interface.viewer.transcript import (
|
||||||
build_run_state,
|
build_run_state,
|
||||||
@@ -48,6 +49,31 @@ def test_latest_run_dir_none_when_no_runs(tmp_path: Path, monkeypatch: pytest.Mo
|
|||||||
assert runs_base_dir() == tmp_path / "strix_runs"
|
assert runs_base_dir() == tmp_path / "strix_runs"
|
||||||
|
|
||||||
|
|
||||||
|
def test_view_cli_help_includes_host(capsys: pytest.CaptureFixture[str]) -> None:
|
||||||
|
try:
|
||||||
|
run_view(["--help"])
|
||||||
|
except SystemExit as exc:
|
||||||
|
assert exc.code == 0
|
||||||
|
else:
|
||||||
|
raise AssertionError("--help should exit")
|
||||||
|
|
||||||
|
help_text = capsys.readouterr().out
|
||||||
|
assert "--host HOST" in help_text
|
||||||
|
assert "0.0.0.0" in help_text
|
||||||
|
|
||||||
|
|
||||||
|
def test_server_can_bind_all_ipv4_interfaces(tmp_path: Path) -> None:
|
||||||
|
run_dir = _make_run(tmp_path, "remote", status="running", end_time=None)
|
||||||
|
|
||||||
|
httpd, url, _ = serve(run_dir, host="0.0.0.0", open_browser=False)
|
||||||
|
try:
|
||||||
|
assert httpd.server_address[0] == "0.0.0.0"
|
||||||
|
assert url == f"http://0.0.0.0:{httpd.server_address[1]}"
|
||||||
|
finally:
|
||||||
|
httpd.shutdown()
|
||||||
|
httpd.server_close()
|
||||||
|
|
||||||
|
|
||||||
def test_latest_run_dir_picks_newest_by_record_mtime(
|
def test_latest_run_dir_picks_newest_by_record_mtime(
|
||||||
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
||||||
) -> None:
|
) -> None:
|
||||||
@@ -173,14 +199,15 @@ def test_server_serves_api_and_static(tmp_path: Path, monkeypatch: pytest.Monkey
|
|||||||
(assets / "assets" / "app.js").write_text("console.log(1)", encoding="utf-8")
|
(assets / "assets" / "app.js").write_text("console.log(1)", encoding="utf-8")
|
||||||
monkeypatch.setattr("strix.interface.viewer.server.bundle_dir", lambda: assets)
|
monkeypatch.setattr("strix.interface.viewer.server.bundle_dir", lambda: assets)
|
||||||
|
|
||||||
httpd, url, _ = serve(run_dir, open_browser=False)
|
httpd, url, token = serve(run_dir, open_browser=False)
|
||||||
try:
|
try:
|
||||||
status, ctype, body = _get(f"{url}/api/run")
|
cookie = _session_cookie(url, token)
|
||||||
|
status, ctype, body = _get(f"{url}/api/run", cookie=cookie)
|
||||||
assert status == 200
|
assert status == 200
|
||||||
assert "application/json" in ctype
|
assert "application/json" in ctype
|
||||||
assert json.loads(body)["finished"] is True
|
assert json.loads(body)["finished"] is True
|
||||||
|
|
||||||
status, _, body = _get(f"{url}/api/transcript")
|
status, _, body = _get(f"{url}/api/transcript", cookie=cookie)
|
||||||
assert {a["id"] for a in json.loads(body)["agents"]} == {"root", "child"}
|
assert {a["id"] for a in json.loads(body)["agents"]} == {"root", "child"}
|
||||||
|
|
||||||
# Real asset is served.
|
# Real asset is served.
|
||||||
@@ -429,6 +456,22 @@ def test_unauthorized_client_cannot_acquire_capability(
|
|||||||
httpd.server_close()
|
httpd.server_close()
|
||||||
|
|
||||||
|
|
||||||
|
def test_run_data_requires_session(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||||
|
run_dir = _make_run(tmp_path, "private", status="completed", end_time="2026-01-01T00:00:00Z")
|
||||||
|
_bundle(tmp_path, monkeypatch)
|
||||||
|
|
||||||
|
httpd, url, token = serve(run_dir, open_browser=False)
|
||||||
|
try:
|
||||||
|
cookie = _session_cookie(url, token)
|
||||||
|
for path in ("/api/run", "/api/vulnerabilities", "/api/report", "/api/transcript"):
|
||||||
|
assert _get_status(url + path) == 403, path
|
||||||
|
assert _get_status(url + path, cookie=f"{_cookie_name(url)}=wrong") == 403, path
|
||||||
|
assert _get_status(url + path, cookie=cookie) == 200, path
|
||||||
|
finally:
|
||||||
|
httpd.shutdown()
|
||||||
|
httpd.server_close()
|
||||||
|
|
||||||
|
|
||||||
def test_auth_status_reflects_expiry(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
def test_auth_status_reflects_expiry(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||||
run_dir = _make_run(tmp_path, "status", status="running", end_time=None)
|
run_dir = _make_run(tmp_path, "status", status="running", end_time=None)
|
||||||
_bundle(tmp_path, monkeypatch)
|
_bundle(tmp_path, monkeypatch)
|
||||||
@@ -561,11 +604,10 @@ def test_historical_run_data_requires_verification(
|
|||||||
|
|
||||||
httpd, url, token = serve(launched, open_browser=False)
|
httpd, url, token = serve(launched, open_browser=False)
|
||||||
try:
|
try:
|
||||||
# The launched run is always viewable, no verification and no cookie.
|
# The launched run needs the session capability, but not email verification.
|
||||||
status, _, _ = _get(f"{url}/api/run")
|
assert _get_status(f"{url}/api/run") == 403
|
||||||
assert status == 200
|
|
||||||
|
|
||||||
cookie = _session_cookie(url, token)
|
cookie = _session_cookie(url, token)
|
||||||
|
assert _get_status(f"{url}/api/run", cookie=cookie) == 200
|
||||||
|
|
||||||
# A different run needs the session capability first: a cookie-less
|
# A different run needs the session capability first: a cookie-less
|
||||||
# caller is forbidden even once the machine is verified.
|
# caller is forbidden even once the machine is verified.
|
||||||
|
|||||||
Reference in New Issue
Block a user