Guide for AI agents working in this fork of Strix.
Strix (strix-agent, upstream v1.2.0 base) is an open-source AI-powered penetration testing
agent. It uses LLMs to autonomously conduct security assessments by executing tools inside a
Docker sandbox (Kali Linux container with preinstalled security tools). Licensed Apache-2.0.
Python 3.12+.
This fork adds exactly one thing on top of upstream: GitHub Copilot as an LLM provider
(device-code OAuth, GitHub Enterprise Cloud support). There is no Podman/FreeBSD support in this
fork — main is based directly on upstream's main branch tree, plus the Copilot delta described
in MERGE.md. If you're looking for the previous (much larger) fork with Podman/FreeBSD
support and the old XML-tool-call architecture, see the pre-merge-backup branch — but note it
predates upstream's SDK migration described below and is not maintained.
Upstream migrated from a custom XML-tool-call harness + FastAPI sidecar to
openai-agents (the OpenAI Agents SDK), with
LiteLLM as the model backend (openai-agents[litellm]). This is a different architecture than
older Strix releases (pre-v1.0): there is no strix/tools/registry.py, no XML schema files, no
strix/llm/llm.py, and no BaseAgent/AgentMeta metaclass loop anymore. If you've worked on an
older Strix fork, expect most tool/agent-loop internals to be unfamiliar.
strix/core/runner.py— top-level scan runner (run_strix_scan): builds the root agent, resolves scope/instructions, wires up hooks, and drives the SDK'sRunner.strix/core/execution.py— the actual execution loop (run_agent_loop,spawn_child_agent,respawn_subagents) built onagents.Runner+agents.RunConfig.strix/core/agents.py—AgentCoordinator: SDK-native state for Strix's addressable multi-agent graph (root + spawned sub-agents), persisted so scans can resume.strix/core/hooks.py—agents.lifecycle.RunHookssubclasses (budget enforcement, usage reporting) that hook into the SDK's run lifecycle.strix/core/sessions.py— wraps the SDK'sSQLiteSessionfor native conversation-history persistence/resume (--resume <run_name>).strix/core/inputs.py— pure builders forModelSettings, the root task text, and scope context — no side effects, easy to unit test.strix/agents/factory.py—build_strix_agent()constructs anagents.sandbox.SandboxAgentwith the base toolset (_BASE_TOOLS), the sandboxShell/Filesystemcapabilities, and atool_use_behaviorcallback that only stops the loop when a lifecycle tool (finish_scan/agent_finish) reports success.strix/agents/prompt.py— Jinja2 system-prompt renderer; templates live instrix/agents/prompts/system_prompt.jinja.register_agent_tools(*tools)(strix/agents/factory.py) is the extension point for adding tools to every scan agent (root + children) without editing the base toolset.
- Tools are plain async functions decorated with
agents.function_tool(from the SDK) — no more XML schema files, no registry, no executor. The function's docstring becomes the tool description sent to the model; type hints become the JSON schema. - Each tool module lives at
strix/tools/<name>/tools.py(ortool.pyfor a single-tool module), re-exported throughstrix/tools/<name>/__init__.py. - Shell (
exec_command,write_stdin, ...) and filesystem tools come from the SDK's ownagents.sandbox.capabilities.Shell/Filesystem— Strix only wraps theiron_invoke_toolto add error-as-result behavior and a couple of input-massaging fixes (see_wrap_exec_command/_wrap_write_stdininstrix/agents/factory.py). - Tools that must run against the sandbox container talk to it through the SDK's sandbox session
primitives (
strix/runtime/session_manager.py), not a custom HTTP tool server.
strix/config/models.py—StrixProvider(MultiProvider)routes any non-OpenAI model prefix through LiteLLM, preserving the prefix (so users typedeepseek/deepseek-chat, notlitellm/deepseek/deepseek-chat).configure_sdk_model_defaults()is the single hook that wires StrixSettingsinto SDK-native defaults (default API key, LiteLLM compatibility flags, cost callback, OpenRouter attribution headers) at startup.- Actual model calls go through
agents.extensions.models.litellm_model.LitellmModel(openai-agents[litellm]), which wrapslitellm.acompletion(). Strix does not call litellm directly for standard providers. - GitHub Copilot (fork-specific):
strix/llm/copilot.py+ the--auth-github-copilotCLI command. See "Fork-specific: GitHub Copilot support" below — this is the only fork addition to the LLM layer.
strix/runtime/backends.py—SandboxBackendregistry (STRIX_RUNTIME_BACKEND, default"docker").register_backend(name, backend)is the extension point for adding a backend; there is currently only"docker"registered (no Podman backend in this fork).strix/runtime/docker_client.py—StrixDockerSandboxClient, a thin subclass of the SDK's Docker sandbox client adding NET_ADMIN/NET_RAW capabilities andhost.docker.internalmapping.strix/runtime/session_manager.py— creates/reuses sandbox sessions per scan.strix/runtime/caido_bootstrap.py— starts the bundled Caido proxy inside the sandbox.strix/runtime/local_dir_staging.py— streams local target directories into the sandbox (file-by-file copy, or bind-mount via--mountfor large repos).- Sandbox image:
ghcr.io/usestrix/strix-sandbox(Kali Linux), version pinned instrix/config/settings.py(RuntimeSettings.image,STRIX_IMAGEenv var override).
strix/config/settings.py—pydantic-settings-basedSettings(env-var driven, nested:llm,runtime,telemetry,integrations,viewer).strix/config/loader.py—load_settings()(env + optional JSON override file via--config),persist_current()/apply_config_override().strix/config/models.py— see LLM Integration above.
- Unchanged in spirit from earlier Strix versions: Markdown files under
strix/skills/<category>/, YAML frontmatter stripped, injected into the system prompt via Jinja2. Categories:vulnerabilities/,frameworks/,technologies/,protocols/,cloud/,reconnaissance/,coordination/,scan_modes/,tooling/,custom/. register_skill_dir(path)(strix/skills/__init__.py) lets downstream users add/override skills from an external directory without editing the package; last-registered directory wins.
strix/report/—state.py(global report state + litellm cost callback),dedupe.py(finding dedup),sarif.py(SARIF export),writer.py(Markdown/CSV/JSON artifact writers),usage.py(token/cost usage tracking).
strix/viewer/— a local web app (strix view [<run>]) that serves a React SPA (strix/viewer/frontend/, prebuilt intostrix/viewer/static/viamake viewer) for browsing past runs, live agent graphs, and vulnerability reports.strix/viewer/server.pyis a stdlibhttp.server-based server, not FastAPI.
strix/telemetry/posthog.py/scarf.py— anonymous usage telemetry (STRIX_TELEMETRY=falseto disable). No more OpenTelemetry/Traceloop — upstream removed that in the SDK migration.
strix/interface/main.py— CLI entry point (strix = "strix.interface.main:main"), argument parsing, environment validation, LLM warm-up.strix/interface/cli.py— non-interactive mode (Rich live display).strix/interface/tui/— interactive TUI (Textual), withrenderers/per tool type (strix/interface/tui/renderers/registry.pymaps tool name → renderer).strix/interface/utils.py— Docker connection checks, image pulling, target inference, diff scope resolution, etc.
This is the entire fork delta on top of upstream. See MERGE.md for exactly which files/lines this touches and how to re-apply it after a future upstream merge.
- litellm (already a dependency) ships a first-class
github_copilotprovider that handles the OAuth device-code flow, token caching/refresh, and every Copilot-specific request header entirely on its own.StrixProvider._resolve_prefixed_model()already routesgithub_copilot/<model>straight to litellm untouched — no core routing changes were needed. strix/llm/copilot.pyonly adds what litellm doesn't provide:GITHUB_COPILOT_HOSTNAMEenv var → derives litellm's four separate GHE Cloud (*.ghe.com) URL overrides from one hostname.- A patched
Authenticator._poll_for_access_tokenthat honors the device-code response's owninterval/expires_ininstead of litellm's hardcoded 60-second window. has_cached_github_copilot_token()/validate_github_copilot_access_token()/clear_github_copilot_tokens()for clear CLI messaging.
strix --auth-github-copilot [--github-copilot-host <host>](wired instrix/interface/main.py) runs the device-code login once, up front, and exits — this bypasses the normal--targetrequirement the same way thestrix viewsubcommand does.warm_up_llm()also auto-triggers this on first use if no token is cached yet, before the TUI starts (so the device-code prompt is never obscured by the interface).- Important litellm bug workaround (
strix/config/models.py::_configure_github_copilot_headers(), called fromconfigure_sdk_model_defaults()): litellm'sgithub_copilotprovider only adds its own required request headers (editor-version, etc.) when the caller passes noextra_headers— but theopenai-agentsSDK always does, so every real Copilot call fails without this fix. Sets the SDK'sHEADERS_OVERRIDEcontext var so the headers are present regardless. See MERGE.md's "litellm header-injection bug" section for the full writeup — this is not obvious and easy to accidentally regress ifstrix/llm/copilot.pyor this hook is refactored. - Tests:
tests/test_copilot.py(thestrix/llm/copilot.pyunit tests),tests/test_copilot_cli.py(CLI wiring), andtests/test_models.py::TestConfigureGithubCopilotHeaders(the header-bug fix). - Docs:
docs/llm-providers/github-copilot.mdx.
# Setup
make setup-dev # uv sync (dev deps) + pre-commit hooks
uv sync --dev # manual equivalent
# Development cycle (upstream Makefile has no `test` target — run pytest directly)
make dev # format + lint + type-check
uv run pytest -v # run the test suite
make check-all # format + lint + type-check + security
# Individual checks
make format # ruff format .
make lint # ruff check --fix . (no pylint anymore)
make type-check # mypy strix/ + pyright strix/
make security # bandit -r strix/ -c pyproject.toml
make viewer # rebuild strix/viewer/frontend -> strix/viewer/static/ (commit output)
# Run the application
uv run strix --target https://example.com
uv run strix --target ./my-project --non-interactive
uv run strix --target example.com --instruction "Focus on auth vulns"
uv run strix --auth-github-copilot # fork-specific
uv run strix --auth-github-copilot --github-copilot-host octodemo.ghe.com
uv run strix view # local run viewer
# Build binary
bash scripts/build.sh # PyInstaller buildNote: there is no CI test job (.github/workflows/build-release.yml only builds/releases on tag
push) and no test/test-cov Makefile targets upstream. Run uv run pytest -v directly.
strix/
├── agents/
│ ├── factory.py # build_strix_agent(), register_agent_tools()
│ ├── prompt.py # Jinja2 system-prompt renderer
│ └── prompts/system_prompt.jinja
├── config/
│ ├── settings.py # pydantic-settings Settings (env-var driven)
│ ├── loader.py # load_settings(), persist_current(), config overrides
│ └── models.py # StrixProvider, configure_sdk_model_defaults()
├── core/
│ ├── runner.py # run_strix_scan() — top-level entry point
│ ├── execution.py # run_agent_loop(), spawn_child_agent()
│ ├── agents.py # AgentCoordinator — addressable multi-agent graph state
│ ├── hooks.py # RunHooks subclasses (budget, usage reporting)
│ ├── inputs.py # pure ModelSettings/task/scope builders
│ ├── sessions.py # SQLiteSession wrapper (native session resume)
│ └── paths.py # strix_runs/<run>/ path helpers
├── interface/
│ ├── main.py # CLI entry point, arg parsing, env validation, warm-up
│ ├── cli.py # non-interactive mode
│ ├── tui/ # interactive TUI (Textual) + renderers/
│ └── utils.py
├── llm/
│ └── copilot.py # FORK-SPECIFIC: GitHub Copilot device-flow + GHE Cloud
├── report/ # dedupe, SARIF export, Markdown/CSV/JSON writers, cost usage
├── runtime/
│ ├── backends.py # SandboxBackend registry (docker only)
│ ├── docker_client.py # StrixDockerSandboxClient
│ ├── session_manager.py # create/reuse sandbox sessions
│ ├── caido_bootstrap.py # starts Caido proxy in-sandbox
│ └── local_dir_staging.py # local target -> sandbox staging
├── skills/ # Markdown skill files by category (unchanged in spirit)
├── telemetry/ # posthog.py, scarf.py (no more OTEL/Traceloop)
├── tools/ # @function_tool modules, one dir per tool group
├── utils/ # resource_paths.py, etc.
└── viewer/ # local run viewer web app (server.py + React frontend)
tests/ # flat layout (no more tests/<module>/ subpackages)
├── conftest.py (if present)
└── test_*.py # one file per concern, class-based within
containers/Dockerfile # Kali Linux sandbox with nmap, sqlmap, nuclei, ffuf, ZAP, etc.
docs/ # Mintlify docs site (docs.json + *.mdx)
scripts/{build.sh,install.sh,docker.sh}
-
Create
strix/tools/<name>/tools.py(ortool.py):from agents import function_tool @function_tool async def my_tool(param1: str) -> str: """One-line summary shown to the model. Longer description / usage guidance goes here — this whole docstring is sent to the LLM as the tool description. Args: param1: Description of param1. """ return "result"
-
Re-export it from
strix/tools/<name>/__init__.py. -
Register it for every scan agent via
register_agent_tools(my_tool)(strix/agents/factory.py) — call this once, before the firstbuild_strix_agent()call (mirrorsstrix.runtime.backends.register_backend's registration pattern). -
Add a TUI renderer in
strix/interface/tui/renderers/and register it instrix/interface/tui/renderers/registry.pyif the tool needs custom TUI output. -
Write tests in
tests/test_<name>.py(flat layout, no subpackages).
There is no XML schema file to write and no @register_tool(sandbox_execution=...) decorator
anymore — the SDK derives everything from the function signature and docstring.
Same as always: add a .md file under strix/skills/<category>/ (optional YAML frontmatter is
stripped automatically); it's injected into the system prompt via Jinja2. Categories:
vulnerabilities/, frameworks/, technologies/, protocols/, cloud/, reconnaissance/,
coordination/, scan_modes/, tooling/, custom/.
| Setting | Value |
|---|---|
| Line length | 100 characters |
| Quote style | Double quotes |
| Indentation | 4 spaces |
| Formatter/linter | ruff (ruff format + ruff check --fix; no pylint anymore) |
| Import sorting | isort via ruff, strix as known first party |
| Type checking | mypy strict + pyright strict, both must pass |
from __future__ import annotations |
used throughout (unlike some older Strix forks) |
| Union syntax | modern `X |
| Naming | snake_case functions/modules, PascalCase classes |
| Logging | logging.getLogger(__name__) per module |
| Async | agent loop, tool execution, LLM calls are all async def/await |
| Lazy imports | heavy/optional deps (telemetry, litellm internals, provider SDKs) imported inside functions — see pyproject.toml's per-file-ignores for PLC0415 exceptions |
- pytest with
pytest-asyncio(asyncio_mode = "auto"). - Flat layout:
tests/test_<concern>.py, notests/<module>/subpackages (upstream removed the old nested layout entirely in the SDK migration). - Class-based grouping (
class TestXxx:) with@pytest.mark.parametrizeis still the preferred style within a file — seetests/test_copilot.pyfor a fork-added example. - No CI test job — run
uv run pytest -vlocally before pushing.
| Variable | Purpose | Default |
|---|---|---|
STRIX_LLM |
LLM model name (litellm format, e.g. openai/gpt-5.4, github_copilot/gpt-4o) |
— |
LLM_API_KEY / OPENAI_API_KEY |
API key for the LLM provider | — (optional; not needed for Copilot, local models, Vertex, Bedrock) |
LLM_API_BASE / OPENAI_API_BASE / OPENAI_BASE_URL / LITELLM_BASE_URL / OLLAMA_API_BASE |
Custom API base URL | — |
STRIX_REASONING_EFFORT |
Reasoning effort level | high |
STRIX_FORCE_REQUIRED_TOOL_CHOICE |
Force tool_choice=required |
false |
LLM_TIMEOUT |
Per-request LLM timeout (seconds) | 300 |
STRIX_IMAGE |
Docker sandbox image override | ghcr.io/usestrix/strix-sandbox:1.0.0 |
STRIX_RUNTIME_BACKEND |
Sandbox backend name | docker |
STRIX_MAX_LOCAL_COPY_MB |
Local target size cap before requiring --mount |
1024 |
STRIX_MAX_CONTEXT_IMAGES |
Max screenshot/image tool outputs kept live per agent | 3 |
STRIX_TELEMETRY |
Anonymous usage telemetry | true |
PERPLEXITY_API_KEY |
Enables web_search real-time research |
— |
STRIX_APP_URL |
Base URL the local viewer proxies to for email/report delivery | https://app.strix.ai |
GITHUB_COPILOT_HOSTNAME |
Fork-specific. GHE Cloud hostname (e.g. octodemo.ghe.com); derives litellm's Copilot URL overrides |
— (github.com) |
Settings are read by strix/config/settings.py (pydantic-settings) with JSON override support
via --config <path> / ~/.strix/cli-config.json.
- No XML anymore. Tool calls go through the SDK's native function/custom-tool calling, not
hand-rolled XML parsing. If you're used to older Strix forks, don't look for
strix/tools/registry.pyor*_actions_schema.xml— they don't exist. - No FastAPI sidecar / tool server. Sandbox tool execution goes through the SDK's own sandbox
session/manifest machinery (
agents.sandbox), not a custom HTTP server inside the container. - Extension points, not invasive patches. Prefer
register_backend()(strix/runtime/backends.py),register_agent_tools()(strix/agents/factory.py), andregister_skill_dir()(strix/skills/__init__.py) over editing upstream internals directly — this is exactly how the Copilot delta stays small across upstream merges. - Lazy imports still matter. Heavy/optional deps (litellm internals, provider SDKs, viewer
report generation) are imported inside functions. Ruff's
PLC0415has per-file ignores for this inpyproject.toml— add a new entry there rather than restructuring the whole module. - Two type checkers. Both mypy (strict) and pyright (strict) must pass; they sometimes
disagree — check both (
make type-check). - Entry points:
strix = "strix.interface.main:main"(CLI) andstrix view(dispatched before argument parsing inmain(), since it doesn't need a--target). - No Podman/FreeBSD in this fork. If you're porting something from the old fork
(
pre-merge-backupbranch), check whether it's actually Podman/FreeBSD-specific before re-adding it — most of that support doesn't apply to this fork's supported platforms. strix_runs/is the gitignored runtime output directory (was previously alsoagent_runs/in older forks — that directory name is gone).- Python 3.12+ required (3.12–3.14 supported).