Commit Graph

503 Commits

Author SHA1 Message Date
0xallam a51e7820f9 Restore sitemap tools + unify proxy I/O contract
Re-add list_sitemap and view_sitemap_entry from main, ported to the
new caido-sdk-client layout via raw GraphQL queries (the typed SDK
doesn't expose sitemap operations, but the Caido server still
supports sitemapRootEntries / sitemapDescendantEntries / sitemapEntry).
Wired through caido_api (sandbox-importable helpers), the host-side
@function_tool wrappers, factory _BASE_TOOLS, the system prompt, the
python skill doc, and the public proxy docs.

While threading these through, lock down the output contract across
every proxy tool so the model sees one consistent shape:

- All tools wrap success/failure in {"success": bool, "error"?: str}
- Canonical field names: status_code, length, roundtrip_ms (omitted
  when 0), is_tls, has_descendants. snake_case everywhere on output;
  camelCase stays only on the input side where it's the GraphQL
  schema.
- repeat_request now returns a structured response that matches
  list_requests' response_summary shape (parse_raw_response parses
  the raw bytes into status_code / length / headers / body), with
  body capped at 8KB and a body_truncated flag so the model knows
  when to fetch the full body via view_request.
- RepeatRequestRenderer was reading non-existent top-level keys
  (status_code, response_time_ms, body) and silently displaying
  nothing useful — now reads the structured response shape.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 00:23:46 -07:00
0xallam 5921fec16c Proxy tool sweep: drop send_request, fix Caido SDK gotchas
send_request was a thin wrapper over the Caido Replay API that the model
could replicate with a one-liner `curl` via exec_command. The sandbox's
HTTP_PROXY env captures all such traffic for free, so the tool was
adding bugs (duplicate dispatch, dropped responses) without adding
capability. Removed across factory, tools module, sandbox-importable
caido_api helper, TUI renderer, prompt template, skill doc, and public
docs. repeat_request stays — it operates on captured request IDs with
structured modifications, which curl can't replicate cleanly.

Three caido-sdk-client workarounds that were hitting us through both
send_request and repeat_request:

- replay_send_raw used to pass CreateReplaySessionFromRaw to
  sessions.create(), which seeds a stored entry server-side, then
  called send() — producing two history rows per call. Empty-create +
  send produces one dispatched request.
- The same helper read result.entry.response_raw, an attribute that
  doesn't exist on ReplayEntry, so response bytes were silently
  dropped. Fixed to walk result.entry.response.raw with proper None
  guards.
- get_request_with_client passed include_request_raw / include_response_raw
  based on the requested part, but the SDK's generated pydantic models
  declare raw as required even though the GraphQL fragment makes it
  conditional via @include. Passing False crashed view_request with a
  pydantic validation error. Always request both raw bodies; the caller
  picks which to surface.

Also wrapped replay.send() in asyncio.wait_for(30s) so a stalled Caido
dispatch (notably loopback targets that don't route cleanly through the
sandbox proxy) fails fast with a model-readable error instead of
hanging the agent until the function_tool 120s budget expires.

Finally, list_requests now omits the roundtrip_ms field when Caido
reports 0 — proxy-captured unscoped traffic consistently reports 0
while scoped/replay traffic carries real measurements, so the absence
of the field is now informative ("Caido didn't measure this") rather
than misleading ("this request took 0ms").

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 19:16:06 -07:00
0xallam 940319f28a Align note IDs with todo IDs (6-char hex)
Notes generated 5-char IDs via a 20-try collision loop while todos
generated 6-char IDs in one shot. Mixed widths across the agent's
view made the two tools look unrelated. Match todo's shape — same
length, same one-shot generation. Collision retry is unnecessary at
scan-scale (a few hundred items vs 16^6 keys).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 18:10:57 -07:00
0xallam bdc3c5470e Tighten todo + think tool contracts
Reject ambiguous calls in todo tools that previously combined the
single-target params and the bulk-array param (e.g. create_todo with
both `title` and `todos` would silently create N+1 items). Each tool
now errors with a mode-specific hint pointing the model at the
appropriate form. Also drop the meaningless char-count from `think`'s
success message — the model already knows what it wrote.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 16:58:09 -07:00
Sandiyo Christan 2380cf55cb feat: add HTTP request smuggling skill (#405)
* feat: add HTTP request smuggling skill

Add a new vulnerability skill covering HTTP request smuggling (HRS)
across CL.TE, TE.CL, H2.CL, and H2.TE desync variants. HRS is absent
from the existing skill set despite being a distinct, high-impact
vulnerability class frequently present in any architecture using a
reverse proxy or CDN in front of an application server.

Coverage:
- CL.TE: front-end uses Content-Length, back-end uses Transfer-Encoding
- TE.CL: front-end uses Transfer-Encoding, back-end uses Content-Length
- H2.CL: HTTP/2 front-end downgrades to HTTP/1.1 with injected Content-Length
- H2.TE: Transfer-Encoding header injection through HTTP/2 desync
- Transfer-Encoding obfuscation techniques (tab, space, duplicate, xchunked)
- Front-end security control bypass via smuggled prefix
- Cross-user request capture for session token theft
- Response queue poisoning and WebSocket handshake hijacking
- Timing-based and differential response detection methodology
- HTTP/2 specific probing techniques

Includes raw HTTP examples for each variant, step-by-step testing
methodology, exploitation PoCs, false-positive conditions, and
infrastructure topology guidance.

* fix: correct TE.CL probe, pseudo-header terminology, PoC Content-Length values, \x20 representation

Four reviewer findings addressed:

P1 — TE.CL timing-probe description inverted: previous text said
'Content-Length set to fewer bytes than the chunk content' which
describes socket-poisoning behavior (differential response), not a
timeout. Corrected to: send a complete chunked body with CL set to MORE
bytes than provided so the back-end waits for data that never arrives.
Also corrected Testing Methodology step 3 to match.

P2 — pseudo-header terminology: 'content-length' is a regular HTTP/2
header, not a pseudo-header (pseudo-headers are exclusively :method,
:path, :authority, :scheme). Fixed the H2.CL explanation (line 75),
HTTP/2-specific detection bullet, and Pro Tip #4 which referred to
':content-length pseudo-header'.

P2 — PoC Content-Length values: outer Content-Length in the bypass PoC
corrected from 116 to 100 (actual byte count of the body shown); capture
PoC corrected from 129 to 120.

P2 — \x20 representation: replaced the \x20 escape sequence in the code
block (which renders as a literal four-character string, not a space byte)
with an explanatory comment and actual whitespace characters so the intent
is unambiguous.

* Update strix/skills/vulnerabilities/http_request_smuggling.md
2026-05-20 21:45:16 -04:00
Chethas Dileep dc395316ae Add Docker sandbox host mappings (#488)
* Add Docker sandbox host mappings

* Address docker extra hosts review feedback

* Revert README change for STRIX_SANDBOX_EXTRA_HOSTS

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: 0xallam <ahmed39652003@gmail.com>
2026-05-19 01:49:22 -07:00
n1majne3 6b9bd4d5f2 Fix MiniMax tool calling (#456)
Co-authored-by: n1majne3 <24203125+n1majne3@users.noreply.github.com>
2026-05-03 19:49:18 -07:00
Bala e1f38f8339 perf(agent): wake on state change instead of 500ms polling (#305)
Co-authored-by: 0xallam <ahmed39652003@gmail.com>
2026-05-03 19:30:41 -07:00
Jorge Moya 6942ecb33e add empty-array IDOR FP and OAST source-IP SSRF FP signals (#183) 2026-05-03 18:19:57 -07:00
Modark 8574119f4d Add SSTI and Header Injection vulnerability skills (#191) 2026-05-03 17:54:04 -07:00
0xhis 67050d9133 fix(llm): include system prompt tokens in memory compressor budget (#381)
Co-authored-by: 0xhis <0xhis@users.noreply.github.com>
2026-05-03 16:26:34 -07:00
Alex 6f17c7de17 feat: add Novita AI as LLM provider (#385)
Co-authored-by: 0xallam <ahmed39652003@gmail.com>
2026-05-03 16:23:35 -07:00
Dr Alex Mitre a75ad2960e fix: MiniMax tool call normalization and thinking block handling (#458)
Co-authored-by: 0xallam <ahmed39652003@gmail.com>
2026-05-03 16:12:37 -07:00
Sandiyo Christan 6da7315aa3 feat: add NoSQL injection skill (#404)
Co-authored-by: 0xallam <ahmed39652003@gmail.com>
2026-05-03 15:49:43 -07:00
0xallam c4d76d72bc Simplify Python proxy automation 2026-04-27 00:21:54 -07:00
0xallam a61b5a02c5 Fix sandbox tool error wrapper 2026-04-26 17:00:02 -07:00
0xallam 756457f108 Support chat-compatible sandbox patch tool 2026-04-26 16:54:34 -07:00
0xallam 88fc7be8c1 Support xhigh reasoning effort 2026-04-26 16:03:07 -07:00
0xallam ef50c2dfa6 Record usage per SDK LLM response 2026-04-26 15:54:45 -07:00
0xallam 4791feb08e Track SDK LLM usage 2026-04-26 15:38:41 -07:00
0xallam af826e1281 refactor: consolidate run state layout 2026-04-26 15:01:35 -07:00
0xallam 0a5be6be3f chore: remove generated migration docs 2026-04-26 14:36:58 -07:00
0xallam 629ea60b02 refactor: reorganize core report and tui modules 2026-04-26 14:28:50 -07:00
0xallam c163ef882b refactor: remove custom llm provider layer 2026-04-26 14:04:32 -07:00
0xallam 9f45121dce Fix interactive lifecycle and resume history 2026-04-26 12:26:48 -07:00
0xallam e8b172bd2a Enforce lifecycle completion in non-interactive runs 2026-04-26 12:06:06 -07:00
0xallam 1d0da89090 Simplify TUI SDK event rendering 2026-04-26 11:53:20 -07:00
0xallam bd40884fcf Simplify SDK-native orchestration 2026-04-26 11:30:00 -07:00
0xallam dc03f1f4ed Use shared agent persistence files 2026-04-26 09:30:13 -07:00
0xallam 5ec1e0786f Simplify SDK agent orchestration 2026-04-26 09:25:47 -07:00
0xallam 53188a7583 fix(runtime,interface): mount sources at advertised paths + surface scan failures in TUI
Two fixes that surfaced from a single broken run.

(1) Source mounting was double-broken:

- ``session_manager.create_or_reuse`` mounted the *parent* of the first
  local source under a hardcoded ``"sources"`` key, so the host's
  unrelated content leaked in at ``/workspace/sources/...`` while the
  agent's task prompt advertised ``/workspace/<workspace_subdir>``
  (from ``_build_root_task``). Result: the agent looked at
  ``/workspace/empty/`` (per the prompt), found nothing, and bailed.
- ``backends._docker_backend`` never called ``await session.start()``
  after ``client.create()`` — the SDK's manifest application
  (``LocalDir`` materialization, mount setup) only runs inside
  ``start()`` (or ``async with session:``). So even with the right
  ``entries`` the workspace would have been empty anyway.

Fix: thread ``args.local_sources`` (already populated by
``collect_local_sources``) all the way through to the session manager,
build ``Manifest.entries`` keyed by each source's ``workspace_subdir``,
and call ``session.start()`` in the docker backend so the SDK actually
materializes the entries. Drop the now-unused ``_resolve_sources_path``
helpers from ``cli.py`` and ``tui.py``.

(2) Scan-failure visibility was nonexistent in TUI mode:

- The SDK's ``on_agent_end`` hook only fires after the agent reaches its
  first turn. A failure earlier (model routing, sandbox bring-up, …)
  left the root agent stuck at ``status=running`` in the bus and
  tracer, so the TUI animated "Initializing" forever.
- ``scan_target`` in ``tui.py`` caught the exception and called
  ``logging.exception`` but never propagated it. ``run_tui`` returned
  cleanly when the user finally ctrl-q'd, so ``main.py`` happily
  printed the success-completion banner over a dead scan.

Fix: in ``run_strix_scan``'s ``except BaseException`` block, finalize
the root agent as ``"failed"`` in both the bus and the tracer (with the
error message attached). Capture the exception on
``StrixTUIApp._scan_error`` from the scan thread; ``run_tui`` re-raises
it after ``app.run_async()`` returns so ``main.py``'s existing handler
prints the traceback. Add a ``"failed"`` branch to
``_get_status_display_content`` that shows the error message in red,
mirroring the existing ``llm_failed`` branch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 07:27:36 -07:00
0xallam 0518599f29 fix(llm): thread LLM_API_KEY into the SDK's native OpenAIProvider
``MultiProvider`` was constructed with no openai kwargs, so the inner
``OpenAIProvider`` defaulted to reading ``OPENAI_API_KEY`` from the
environment. Strix's contract is that ``LLM_API_KEY`` works for every
provider, so users with ``STRIX_LLM=openai/<model>`` + ``LLM_API_KEY``
hit ``openai.OpenAIError`` at the first turn — the warm-up call worked
because that path goes through ``litellm.completion`` directly with
explicit creds, but the actual scan went through the SDK's MultiProvider
where the key was never plumbed.

Pass ``Settings.llm.api_key`` and ``Settings.llm.api_base`` through to
the underlying ``OpenAIProvider`` via the ``openai_api_key`` /
``openai_base_url`` ctor kwargs. ``openai_use_responses`` flips to
``False`` when ``LLM_API_BASE`` is set — non-default base URLs are the
reliable signal that the user is on an OpenAI-compatible endpoint
that doesn't speak the Responses API. Genuine OpenAI usage keeps the
Responses API as the default transport.

The ``anthropic/`` prefix continues to route through
``AnthropicCachingLitellmModel`` for prompt caching; ``litellm/`` and
other prefixes still fall through to the SDK's stock routing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 07:26:48 -07:00
0xallam ead54ba82c fix(runtime): preserve image ENTRYPOINT so caido-cli actually starts
The SDK's ``DockerSandboxClient._create_container`` overrode both
``entrypoint`` and ``command`` (``tail`` + ``-f /dev/null``), which kept
the container alive but bypassed the image's ``docker-entrypoint.sh``.
That script is what launches ``caido-cli`` and sets up the browser CA
trust. With it skipped, every scan since the harness migration sat in
``bootstrap_caido`` retrying ``loginAsGuest`` for 30 s against a dead
port and then aborted before any agent work happened.

Drop the ``entrypoint`` override and pass ``[tail, -f, /dev/null]`` as
``command``. The image's ENTRYPOINT runs setup, then ``exec \"\$@\"``
swaps PID 1 to ``tail`` for the keep-alive — same long-running
no-op the SDK was after, but with the manifest/init work done first.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 07:26:13 -07:00
0xallam 8bbb31e075 chore(image): chromium-from-apt + anti-detection flags via agent-browser env
Drops the ``agent-browser install --with-deps`` step (Chrome for
Testing has no ARM64 build and ships several automation tells)
and uses the apt-installed Chromium across both arches.

``agent-browser`` is wired via three env vars baked into the image:

  * ``AGENT_BROWSER_EXECUTABLE_PATH=/usr/bin/chromium`` — every
    browser launch picks up the apt binary; no per-call flag needed.
  * ``AGENT_BROWSER_USER_AGENT`` — recent stable Chrome 131 Linux UA.
  * ``AGENT_BROWSER_ARGS`` — minimal stealth flag set:
    ``--disable-blink-features=AutomationControlled`` (the most-
    checked tell), ``--exclude-switches=enable-automation``,
    ``--disable-features=IsolateOrigins,site-per-process,Translate,
    BlinkGenPropertyTrees``, sane window-size + lang, infobars +
    save-password + session-crashed bubbles off.

The ``agent-browser doctor --offline --quick`` step at build time
verifies the binary launches; subsequent runtime calls inherit
the env automatically.

Net: smaller image (no ~150 MB Chrome-for-Testing download),
ARM64-clean, env-driven config so future flag tweaks land without
touching the agent-browser install.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 01:42:20 -07:00
0xallam c011c66889 chore(image): bump sandbox tag 0.1.13 → 0.2.0
Picks up the recent in-image deps (``pip install caido-sdk-client``
for ``python_action`` + Caido CLI bumped to v0.56.0). 0.2.0 is the
new minor since this is the first SDK-migration-era image; users
pulling the new strix should pull the matching new image.

Updated:
- ``strix/config/settings.py:64`` — ``RuntimeSettings.image`` default
- ``strix/runtime/session_manager.py`` + ``strix/orchestration/scan.py`` — docstring example
- ``HARNESS_WIKI.md`` — three references in the runtime + config docs
- ``MIGRATION_EVALUATION.md`` — the SDK-bridging note

The historical changelog row (``HARNESS_WIKI.md:744`` — "bump to
0.1.13") stays untouched on purpose; it records what commit
``640bd67`` did, not the current pin.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 01:19:56 -07:00
0xallam e83522cec5 fix(scan): respawn-skip finalizes cancelled agents as `stopped`
When ``_respawn_subagents`` skipped an agent because it was in
``bus.stopping`` (the user clicked stop before the crash), the bus
state was left untouched — status stayed ``running`` forever, so
``view_agent_graph`` and the TUI tree showed phantom agents that
would never make progress.

Now the skip path collects those agent ids and finalizes each as
``stopped`` outside the lock, which transitions status correctly,
clears the ``stopping`` entry (``finalize`` already discards it),
moves the live stats to ``stats_completed``, and triggers the
post-finalize snapshot. A subsequent ``view_agent_graph`` shows the
truth: the agent is stopped.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 01:16:26 -07:00
0xallam 671c69327b fix(persistence): snapshot resume-instruction + persist notes to disk
Two follow-ups from the post-fix audit:

**#1 critical**: ``orchestration/scan.py`` injects the user's new
``--instruction`` into the root's bus inbox via ``bus.send`` on resume,
but ``send`` is one of the deliberately-not-snapshotted high-frequency
mutations. A SIGKILL between that send and the model's first turn
would silently drop the user's new directive. Force a snapshot
immediately after the inject — that's the one specific message we
can't afford to lose, while leaving general ``send`` traffic
unsnapshotted as designed.

**Notes persistence**: ``strix/tools/notes/tools.py`` now mirrors the
todo pattern. ``_notes_storage`` writes through to
``{run_dir}/notes.json`` after every create/update/delete via the
same atomic-tempfile + ``Path.replace`` flow. New
``hydrate_notes_from_disk(run_dir)`` is wired in ``run_strix_scan``
alongside ``hydrate_todos_from_disk`` so a resumed scan recovers the
exact note set the prior process saw, including ``wiki``-category
notes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 01:09:56 -07:00
0xallam 5fd2a64562 fix(persistence): close all 9 gaps from the resume audit
Three critical correctness fixes + six TUI/audit/UX fixes from the
parallel-agent audit. All changes verified by an end-to-end smoke
that builds, persists, and re-hydrates state across two simulated
process boundaries.

Critical (resume integrity):

1. ``bus.cancel_descendants_graceful`` now calls ``_maybe_snapshot``
   after mutating the ``stopping`` set. Previously, a process crash
   between user-initiated graceful-stop and the next finalize lost
   the stop signal — respawned agents would run forever instead of
   exiting. ``_respawn_subagents`` also gains a guard that skips
   agents in ``stopping`` so a previously-cancelled agent is not
   resurrected on resume.

2. ``Tracer.hydrate_from_run_dir`` now **raises** on corrupt
   ``vulnerabilities.json`` instead of swallowing the exception. The
   prior behaviour silently reset ``vulnerability_reports`` to empty,
   so the next ``add_vulnerability_report`` would allocate ``vuln-0001``
   and overwrite the prior MD on disk — silent data loss.

3. ``--instruction`` passed on resume now reaches the model. The CLI
   captures whether the user explicitly passed an instruction
   (``args.user_explicit_instruction``) before ``_load_resume_state``
   loads the persisted one. ``run_strix_scan`` reads
   ``scan_config["resume_instruction"]`` and, on resume, sends the
   new instruction to root's bus inbox before calling
   ``run_with_continuation`` (which uses ``initial_input=[]`` for SDK
   replay). The inject filter surfaces it on the next turn.

4. ``--resume X`` errors loudly when ``scan_state.json`` exists but
   ``bus.json`` doesn't. Previously this silently fresh-started in
   the same dir, confusing the user who explicitly asked to resume.

TUI / audit / UX:

5. ``Tracer.hydrate_from_run_dir`` now reads ``bus.json`` too and
   pre-populates ``tracer.agents`` from the snapshot's ``statuses`` /
   ``names`` / ``parent_of``. Before this, the TUI tree on resume
   showed only currently-running agents; completed/crashed children
   from the prior run were invisible.

6. ``Tracer.hydrate_from_run_dir`` also seeds ``self._llm_stats`` from
   ``bus.stats_live + bus.stats_completed`` so the resume's footer
   shows cumulative tokens / requests across the prior run plus the
   resume segment, instead of resetting to zero.

7. ``Tracer.save_run_data`` now also writes ``run_metadata.json``
   (start_time, run_id, run_name, targets, status), and
   ``hydrate_from_run_dir`` restores ``start_time`` from it. Prior
   behaviour reset start_time to ``now()`` on every Tracer init,
   breaking the final report's duration calc on resumed scans.

8. Per-agent todos persist to ``{run_dir}/todos.json`` (atomic write
   on every CRUD). ``hydrate_todos_from_disk`` (called from
   ``run_strix_scan``) reloads them so respawned subagents find
   their lists intact. Previously, the module-level
   ``_todos_storage`` was lost on every process restart.

9. ``_load_resume_state`` validates each ``cloned_repo_path`` from
   the persisted ``scan_state.json`` still exists on disk. Previously
   a deleted clone dir would let the resume proceed with an empty
   source tree, with agents silently scanning nothing.

Bonus: ``bus.finalize`` no longer pops ``parent_of`` and ``names``
for finalized agents. Routing protection (don't accept ``send`` to
finalized agents) comes from the ``statuses[id]`` terminal-state
check in ``send`` itself, so dropping those keys was overzealous and
made completed children invisible in ``view_agent_graph`` and the
TUI tree.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 00:57:52 -07:00
0xallam fb6fdffb40 feat(cli): --resume <run_name> as the canonical resume command
Adds an explicit ``--resume RUN_NAME`` flag that loads the prior
run's persisted scan state from ``strix_runs/<run_name>/scan_state.json``
and replays it (targets, scan_mode, instruction, local_sources,
diff_scope, scope_mode, diff_base) so the user never has to retype
their original args.

The exit panel now suggests ``strix --resume <run_name>`` instead of
``--run-name``. Same single-line, same dim-label / coloured-value
styling as ``Target`` / ``Output`` rows, gated on
``not scan_completed``.

CLI contract:
  * ``--resume X`` cannot be combined with ``--target`` (parser error).
  * ``--resume X`` errors with a clear message if
    ``strix_runs/X/scan_state.json`` is missing.
  * Fresh runs persist scan_state.json once at the end of setup —
    after target normalization, repo cloning, local-source
    collection, diff-scope resolution, and final instruction
    composition. So whatever the agent saw on first run is exactly
    what the resumed run sees.

Internally the resume path stays implicit (presence of bus.json
triggers it inside ``run_strix_scan``); ``--resume`` is a UX layer
that:
  1. Sets ``args.run_name = args.resume``.
  2. Pre-populates ``args.targets_info`` and friends from disk.
  3. Skips the fresh-only steps (target re-parse, repo clone,
     diff-scope re-resolution) — the persisted values were already
     finalized on the first run.

HARNESS_WIKI.md: drop the "delete the run dir to force fresh"
instruction.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 00:43:22 -07:00
0xallam b5ee0c283c feat(interface): show resume hint on the existing exit panel
When a scan ends without calling ``finish_scan`` (Ctrl+C, TUI quit,
crash), ``display_completion_message`` now appends one extra line
inside the existing completion panel:

    Resume  strix --run-name <run_name>

Same ``dim``-label / coloured-value styling as the panel's ``Target``
and ``Output`` rows. Only rendered when ``scan_completed`` is False —
a finished scan doesn't need a resume nudge.

Triggers ``orchestration/scan.py``'s implicit-resume path on the next
invocation (presence of ``{run_dir}/bus.json`` is the trigger), so
the user gets back exactly where they left off — root + every
non-terminal subagent's full LLM history, bus topology, prior
findings.

Covers both ``run_cli`` and ``run_tui`` paths since
``display_completion_message`` is called from ``main()`` regardless
of which front-end ran.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 00:38:17 -07:00
0xallam 1c4cb4dc8a feat(interface): show resume hint on user-initiated exit
When the user shuts down a run (Ctrl+C in CLI, Ctrl+Q / quit dialog
in TUI, or an uncaught exception during the scan), print a Rich
panel telling them the exact command to pick up where they left off:

    strix --run-name <run_name>

The panel only appears when ``strix_runs/<run_name>/bus.json``
exists — i.e. the scan registered at least the root agent and has
snapshot state worth resuming from. Suppressed when:

  * No run-name was assigned (Ctrl+C before sandbox bring-up).
  * The run dir doesn't exist or has no bus.json yet.

Implementation:

  * ``strix/interface/utils.py`` gains ``format_resume_hint(run_name)
    -> Panel | None``.
  * ``cli.py`` calls it in the SIGINT/SIGTERM/SIGHUP handler before
    ``sys.exit(1)``, and in the ``except Exception`` arm before the
    re-raise.
  * ``tui.py:run_tui`` calls it in a ``finally`` after
    ``app.run_async()`` so the hint lands on the real terminal once
    Textual has restored it (whether the user pressed Ctrl+Q,
    confirmed the quit dialog, or the run completed naturally).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 00:34:44 -07:00
0xallam d538acf66b feat(orchestration): always-on resume across the agent graph
A scan that crashes or is stopped can now be resumed by re-invoking
``strix`` with the same ``--run-name``. Resume is implicit — presence
of ``{run_dir}/bus.json`` triggers it. To force a fresh start, delete
the run dir.

What survives a process restart with the same scan_id:

  * Root agent's LLM history — already worked (root SDK SQLiteSession).
  * Every non-terminal subagent's LLM history — new. ``create_agent``
    now opens SQLiteSession(session_id=child_id,
    db_path={run_dir}/sessions/{child_id}.db) per child and passes it
    to ``run_with_continuation``.
  * Bus topology — new. ``AgentMessageBus`` gains snapshot/restore/
    _maybe_snapshot async methods plus a ``metadata`` field that holds
    per-agent {task, skills, is_whitebox, scan_mode, diff_scope}.
    ``register``, ``finalize``, ``park``, and ``mark_llm_failed`` each
    call ``_maybe_snapshot`` to atomically persist the bus to
    {run_dir}/bus.json (tempfile + Path.replace).
  * Vulnerability reports — new. ``ScanArtifactWriter._write_
    vulnerabilities`` now also writes ``vulnerabilities.json``
    (atomic). ``Tracer.hydrate_from_run_dir`` reads it on resume so
    new vuln-NNNN ids don't collide with prior on-disk files.

What does not survive: the sandbox container itself (fresh per
process), so ``/workspace/scratch`` and Caido state are lost.
``/workspace/sources`` re-mounts from the host so source code is
unchanged.

``orchestration/scan.py:run_strix_scan`` does the actual resume:
  1. Resolve run_dir up front; if bus.json exists it's a resume.
  2. Acquire {run_dir}/.lock (fcntl.flock) so a second strix process
     can't run concurrently on the same scan_id.
  3. ``bus.set_snapshot_path(...)``, ``tracer.hydrate_from_run_dir()``.
  4. On resume: load + bus.restore, find root_id from snapshot (the
     agent with parent_of[id] is None), spawn the sandbox, skip the
     root's bus.register (already in snapshot).
  5. ``_respawn_subagents`` walks every agent with status in
     running/waiting/llm_failed: reopens its SQLiteSession, rebuilds
     the child agent via the captured factory, builds run config /
     context, asyncio.create_task the run with initial_input=[] so
     the SDK replays from session. Per-child failure (missing/corrupt
     DB, factory raises) finalizes that child as crashed and continues.
  6. Open root SQLiteSession at the same path, run the root with
     initial_input=[] on resume (or the formatted root task on a
     fresh run), and let SDK replay drive the next turn.
  7. ``finally``: close every per-agent session, take a final
     snapshot, tear down sandbox, release the lock.

HARNESS_WIKI.md updated with the new run-dir layout (sessions/,
bus.json, vulnerabilities.json, .lock) and the resume contract.

Net: +500 LoC across 7 files. No new deps.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 00:29:37 -07:00
0xallam 81703e286f refactor(notes): drop disk persistence + shared-wiki prose
The notes tool no longer touches disk. ``_notes_storage`` lives in
memory for the lifetime of one scan process, shared across every
agent in that process via the existing RLock. Process exit clears
the lot — no notes.jsonl event log, no wiki/<slug>.md Markdown
rendering, no replay-on-startup hydration.

Removed ~10 internal helpers (``_get_run_dir``,
``_get_notes_jsonl_path``, ``_append_note_event``,
``_load_notes_from_jsonl``, ``_ensure_notes_loaded``,
``_persist_wiki_note``, ``_remove_wiki_note``,
``_get_wiki_directory``, ``_get_wiki_note_path``,
``_sanitize_wiki_title``) plus the ``_loaded_notes_run_dir`` module
state, ``wiki_filename`` per-note field, and the ``OSError`` branches
that only existed for the wiki write path.

The ``wiki`` category is preserved as a free-form long-form bucket;
it just no longer has any special persistence behaviour.

Skill prompts scrubbed of every "shared wiki memory" / "repo wiki" /
"append a delta before agent_finish" instruction:
``coordination/source_aware_whitebox.md``,
``custom/source_aware_sast.md``,
``scan_modes/{quick,standard,deep}.md``, plus the WHITE-BOX TESTING
block in ``agents/prompts/system_prompt.jinja``.

HARNESS_WIKI.md updated to drop the wiki-as-shared-knowledge-base
description, the per-run output-tree references to ``notes/notes.jsonl``
and ``wiki/{note_id}-{slug}.md``, and the ``is_whitebox`` toggle prose.

Net: -178 LoC in notes/tools.py, -45 LoC across skills/system_prompt
and the wiki doc. The notes tool surface (5 ``@function_tool``s) is
unchanged for the agent.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 23:56:22 -07:00
0xallam f8213452ea feat(logging): close audit gaps — SDK records, proxy tracebacks, CLI/docker/posthog
Five gaps from the post-implementation audit, closed:

1. **SDK logger captured.** The openai-agents SDK uses
   ``logging.getLogger("openai.agents")`` for its own lifecycle events
   (Runner.run starts, tool dispatch, model retries, exceptions).
   Previous setup only attached handlers to the ``strix`` root, so
   SDK-internal events were dropped. Tracked-roots tuple now covers
   both, with the same FileHandler/StreamHandler/Filter chain.

2. **Proxy tool exception tracebacks.** Every ``@function_tool`` in
   ``strix/tools/proxy/tools.py`` returns a JSON error to the LLM via
   the ``_err(name, exc)`` helper. The tracebacks were silently
   formatted away — the LLM saw the message, the human reading the
   log saw nothing. ``_err`` now emits ``logger.exception(...)``
   covering all five tools at once.

3. **CLI bootstrap.** ``strix/interface/main.py`` had its module
   ``logger`` removed by the previous commit and was emitting nothing.
   Restored, plus log lines for env validation, docker check, LLM
   warm-up, and image pull (debug for already-present, info for
   pull, exception for failures).

4. **Docker client.** ``strix/runtime/docker_client.py`` had no
   logger. Container creation now logs caps + exposed ports at DEBUG
   and the resulting container id at INFO.

5. **PostHog telemetry.** ``strix/telemetry/posthog.py`` had no
   logger. Now logs send success/failure at DEBUG, version-detection
   failures at DEBUG, and disabled-skip at DEBUG (so the log shows
   when telemetry is off, instead of being silent about it).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 23:43:19 -07:00
0xallam 46ff025209 feat(logging): per-scan `{run_dir}/strix.log` with scan/agent context tagging
Every scan now writes a complete log file at ``{run_dir}/strix.log``
captured from the moment ``run_dir`` is resolved through teardown.
Stdlib ``logging`` only — no parallel framework.

New ``strix/telemetry/logging.py``:
  * ``setup_scan_logging(run_dir, debug=)`` attaches a ``FileHandler``
    (DEBUG, all ``strix.*``) plus a ``StreamHandler`` (ERROR by
    default; DEBUG via ``STRIX_DEBUG=1``).
  * ``ContextVar``-backed ``scan_id`` and ``agent_id`` injected by a
    ``Filter`` so every line is auto-tagged across asyncio tasks
    without callers passing them explicitly.
  * Third-party noise (``httpx``, ``litellm``, ``openai``,
    ``anthropic``, ``urllib3``, ``httpcore``) capped at WARNING.
  * Returns a teardown handle for ``finally`` cleanup.

Wiring:
  * ``orchestration/scan.py`` calls ``setup_scan_logging`` once per
    scan after ``run_dir`` resolves; sets scan_id; tears down in
    ``finally``. Adds INFO logs for sandbox bring-up + scan
    start/end.
  * ``orchestration/hooks.py`` sets/clears ``agent_id`` ContextVar in
    ``on_agent_start`` / ``on_agent_end`` and emits INFO for agent
    lifecycle, DEBUG for every tool start/end and LLM call.
  * ``interface/main.py`` drops the ``setLevel(ERROR)`` silencer.

Coverage expanded across ~20 files (orchestration, agents, runtime,
llm, tools, interface, config, skills) with INFO for lifecycle and
DEBUG for verbose detail. Per the system instructions in
``logger.warning(f"…{e}")`` were converted to module logger calls.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 23:35:01 -07:00
0xallam 9d7f754b59 feat(tools): python_action — stateless Python execution with proxy helpers
Restores the legacy persistent-IPython tool's *ergonomics* (proxy
helpers pre-bound, structured stdout/stderr/error returns) without the
in-container daemon: each call ships ``strix.tools.proxy._calls`` source
into ``/tmp`` alongside a per-call driver, runs ``python3 -u`` against
it, and parses a sentinel-delimited JSON payload back from stdout. The
driver fetches its own guest token from Caido at ``localhost:48080``
and binds ``list_requests`` / ``view_request`` / ``send_request`` /
``repeat_request`` / ``scope_rules`` to that client; user code runs
inside an ``async def`` wrapper so top-level ``await`` works.

The proxy SDK call sequences live in one file —
``strix/tools/proxy/_calls.py`` — and are reused by both the host-side
``@function_tool`` wrappers (which add JSON serialization for the LLM)
and the in-container kernel (which exposes the bare async functions).
No code duplication; the helper logic itself is host-shipped, so
tweaking the proxy helpers does not require an image rebuild.

Image: a single ``pip install caido-sdk-client`` line so the driver's
``import caido_sdk_client`` resolves. Skill ``tooling/python`` is
always-loaded alongside ``tooling/agent_browser``.

Trade-off accepted: state does not persist across calls (no kernel).
For multi-step workflows the agent combines into one ``code`` block or
writes a script to ``/workspace/scratch/`` and runs via
``exec_command``. If a workflow surfaces that genuinely needs
persistence, the same tool surface migrates to a kernel-backed
executor without changing the LLM contract.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 22:58:53 -07:00
0xallam 767dc83581 chore(image): bump caido-cli v0.48.0 → v0.56.0; parametrize via CAIDO_VERSION
The pinned URL pattern (https://caido.download/releases/v<X>/caido-cli-v<X>-linux-<arch>.tar.gz)
is canonical — it's published by api.caido.io/releases/latest. HEAD requests
return 404 because the upstream R2 bucket only honors GET-with-redirect, but
the wget call in the Dockerfile uses GET so the original URL was never
actually broken — it was just stale.

Switch to an ARG so future bumps are a single --build-arg override.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 19:03:58 -07:00
0xallam 72d932f6c4 refactor: collapse strix/io/, strix/run_config_factory.py, strix/entry.py
Three top-level files that didn't earn their place:

- ``strix/io/scan_artifacts.py`` had a single consumer (the Tracer);
  collapsing it into ``strix/telemetry/`` puts it next to that consumer.
  ``strix/io/`` is gone.

- ``strix/run_config_factory.py`` held two helpers that didn't earn the
  factoring. ``make_agent_context`` was a 17-line dict-spelling function
  whose argument names were identical to its dict keys — replaced with
  inline dict literals at the two call sites. ``make_run_config`` had
  enough RunConfig assembly logic to justify a helper, but with only
  two callers (root scan + ``create_agent``) inlining is cleaner than
  keeping a top-level file. ``DEFAULT_RETRY`` moves to
  ``strix/llm/retry.py`` next to its other LLM-policy peers; the dead
  ``STRIX_DEFAULT_MAX_TURNS`` constant is dropped.

- ``strix/entry.py`` is a misnomer — it isn't *the* entry point (that's
  ``strix/interface/main.py`` for the CLI), it's the per-scan bring-up
  driver: build the bus, bring up the sandbox, build the root agent +
  child factory, format the scope-context block, register root in bus,
  open SQLiteSession, hand off to ``run_with_continuation``. That all
  lives next to its peers in ``strix/orchestration/`` now, renamed to
  ``scan.py`` so the role is obvious.

No behavior change. Net -125 LoC.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 18:54:46 -07:00
0xallam 5253332906 fix(telemetry): capture tool args in tool_executions for TUI renderers
The 19 tool renderers under strix/interface/tool_components/ all read
tool_data.get("args", {}) to render meaningful previews (URLs, methods,
note titles, vuln severities, etc.). After the SDK migration,
tracer.log_tool_start was only recording tool_name — every renderer
silently fell back to its empty-args path and the TUI lost its
per-call context.

Pull args from the SDK-native ToolContext (tool_input when parsed,
otherwise json-decode tool_arguments) and stash them on the
tool_executions entry. log_tool_start now takes an optional args dict;
existing callers pass nothing and get the empty-dict default.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 18:08:36 -07:00
0xallam 6bdaa843d9 docs(finish_scan): elevate the active-agent check to a mandatory pre-flight
Audit flagged that legacy ``finish_scan`` had a code-level guard
(``_check_active_agents``) that refused completion if any subagent was
still running or stopping. Restoring it as code would be defensive
mid-stream cancellation we don't actually want — the agent should
choose whether to wait, message, or stop each child.

Lift the responsibility to the prompt instead: docstring now opens
with a numbered pre-flight checklist that requires the agent to
``view_agent_graph`` first and refuses self-permission to call
``finish_scan`` while any peer is in ``running`` / ``waiting`` /
``llm_failed``. The model sees this as part of the tool's schema and
treats it as a hard rule (matches our pattern for similar
constraints).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 17:56:20 -07:00