A configured tool_output_max_bytes smaller than the truncation notice
itself can't fit a bounded preview, so a persisted result could exceed the
ceiling. Enforce a config floor (ge=1024) so nonsensical values are
rejected at load time instead of being worked around at runtime.
The head+tail slices could each take half of max_bytes, then the
truncation notice and its separators were appended on top, so the value
persisted to history could exceed the configured maximum. Reserve an
upper bound for the notice (and separators) out of the byte budget before
slicing so the whole joined result stays within max_bytes.
Chat-completions mode converts filesystem CustomTools to FunctionTools
(which bounds their result), but the Responses-API path kept them native
and unbounded, so a large read_file could still exhaust the context
window. Always configure the Filesystem capability to head+tail bound
tool output in both modes.
Treat tool_output_max_tokens as a ceiling so an explicit model-supplied
cap can't exceed it, and derive the truncation notice's dropped-line
count from the lines actually kept after the byte-trim pass. Also cast
the pygments fallback lexer so it satisfies the resolve_lexer return
type under the pre-commit mypy hook.
Cap the size of every tool result so a single verbose command (recursive
find, noisy scanner, full page dump) can't pin the conversation near the
model's context window for the rest of a scan.
- New ContextSettings config group with env-tunable caps.
- Default the SDK shell tools' max_output_tokens so exec_command /
write_stdin truncate head+tail instead of returning unbounded output.
- Bound Strix's own FunctionTool/CustomTool results (line + UTF-8 byte
head+tail preview with a truncation notice) and cap error strings.
The Exit Codes table in the CLI reference only listed 0 and 2, but the
CLI also exits with 1 on fatal errors (missing environment variables,
Docker unavailable, invalid config file, diff-scope resolution failure,
or an unhandled exception). It also implied exit 0 means no
vulnerabilities were found, which is only true in headless mode -
interactive runs always exit 0 regardless of findings.
Document exit code 1 and clarify the two cases for exit 0.
Every `uses:` in build-release.yml was a mutable tag; the `release` job has
`contents: write` and publishes the binaries users install, so a compromised
action could tamper the release. Action tag-hijacking keeps recurring
(aquasecurity/trivy-action, 75 tags, Mar 2026 TeamPCP; tj-actions, 2025;
codfish/semantic-release-action, Jun 2026) and SHA-pinned workflows were immune
each time. Pin all six actions to the commit each @major resolves to today
(concrete version in a trailing comment; setup-uv's annotated tag dereferenced
to its commit, not the tag object, so Dependabot tracks it). Also add a
top-level `permissions: contents: read` (the release job keeps its explicit
write) and `persist-credentials: false` on the build checkout.
actionlint passes. Pairs with #860 (Dependabot github-actions keeps the pins
current).
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Remove the post-scan local viewer hosting step so the run exits directly
instead of blocking with 'Hosting the local viewer. Press Ctrl-C to stop.'
Drop the 'View in web' link (and 'Reopen' variant) from the completion
panel, which now always shows 'View strix view <run_name>'.
cryptography 49.x ships arm64-only macOS wheels (no universal2), forcing the
Intel macOS (macos-x86_64) release runner to build from sdist under
`uv sync --frozen`. Pin to 48.0.1, which still clears GHSA-537c-gmf6-5ccf
(fixed in 48.0.1) and provides a macosx_10_9_universal2 wheel.
Resolves GHSA-537c-gmf6-5ccf (vulnerable OpenSSL in cryptography wheels,
fixed in 48.0.1) and CVE-2026-59885 / CVE-2026-59886 (pyasn1 DoS via
OBJECT IDENTIFIER / REAL decoding, fixed in 0.6.4).
* feat(cli): update notifications + self-update (strix --update)
* fix(update): verify release checksum, clean up staged binary, roll back Windows rename on failure
* feat(update): 3-way pre-scan prompt (update now / not now / skip this version) + package-manager upgrade
* fix(update): never show update prompt/notice in non-interactive runs
* docs(reporting): add CVSS calibration guidance to reduce severity inflation
The create_vulnerability_report tool documents the cvss_breakdown format but
gives no guidance on choosing metric values, so findings are frequently
over-rated. Add a concise calibration block covering the most common
inflation mistakes: scoring scenarios that presuppose the attacker already
holds a stolen secret as unauthenticated (PR:N) criticals, using C:H/I:H for
single-user or read-only/enumeration impact, folding a chained worst case
into one vector, and ignoring adversary-in-the-middle or user-interaction
prerequisites.
* docs(reporting): drop 'one weakness per report' calibration bullet
* fix(report): prevent code-fence breakout in vulnerability markdown
render_vulnerability_md wrapped LLM-authored poc_script_code and code
snippet values in a fixed three-backtick fence, so a triple-backtick inside
the value closed the fence early and the rest rendered as live markdown
(headings, tracking-beacon images) in the shareable report deliverable.
Open each such block with a fence one backtick longer than the longest
backtick run in the payload (CommonMark: a block closes only on a fence at
least as long as the opener), so the content always renders verbatim. The
adjacent ```diff block is already safe (its lines are '- '/'+ ' prefixed and
so can never be a bare-backtick closing fence) and is left unchanged.
Fixes#815
* fix(report): indent multiline snippets
---------
Co-authored-by: thejesh23 <thejesh23@users.noreply.github.com>
Co-authored-by: Alex Schapiro <bearsyankees@gmail.com>
build_raw_request kept the Content-Length inherited from the captured
request, so replaying a modified body (repeat_request) emitted a request
whose declared length did not match the body — truncating the payload or
stalling the target. Drop any inherited Content-Length (case-insensitively)
and recompute it from the body actually being sent.
Adds tests covering a lengthened body, an emptied body, and the
no-inherited-header path.
Fixes#814
Co-authored-by: thejesh23 <thejesh23@users.noreply.github.com>
An httpx.Timeout in ModelSettings.extra_args crashes
ModelSettings.to_json_dict() (PydanticSerializationError) on the Chat
Completions and LiteLLM model paths, which serialize settings for their
tracing generation span — failing every model turn on those paths. Pass
the timeout as a plain float, which httpx-based clients apply as the
read (inactivity) timeout.
The SDK's http_status retry policy only retries errors carrying a known
HTTP status code, but quota/billing (and other provider-side) failures
often surface inside a streamed response as a bare error with no status
code, so they were failing on the first attempt. Add a statusless retry
policy to DEFAULT_MODEL_RETRY (retry count and backoff unchanged) so they
are retried before a genuine exhaustion fails the run; user aborts are
never retried.
The SDK's http_status retry policy only retries errors carrying a known
HTTP status code, but quota/billing (and other provider-side) failures
often surface inside a streamed response as a bare error with no status
code, so they were failing on the first attempt. Add a statusless retry
policy to DEFAULT_MODEL_RETRY so they are retried (before any content is
streamed; user aborts are never retried), restoring the pre-SDK engine's
resilience. If the provider is genuinely exhausted, the error still
propagates and fails the scan after retries.
* fix(proxy,tooling): serialize+reconnect Caido client, actionable HTTPQL errors, sandbox tool guidance
Addresses the top recurring agent tool-call failures observed in telemetry:
- proxy: the shared Caido client had no locking or reconnect, so concurrent
agent calls raced ("Transport is already connected") and a dead transport
poisoned the rest of the run ("Connector is closed"/"Server disconnected").
Add an asyncio lock + bounded reconnect in caido_api.call_with_client (sandbox
path) and a scan-wide caido_lock in the run context that host-side proxy tools
hold around every call. Deterministic errors are not retried.
- proxy: list_requests now returns Caido's exact parser message, echoes the
offending query, and includes a corrected-syntax hint so agents self-correct
instead of retrying a broken HTTPQL filter.
- shell/prompt: document that write_stdin requires a process started with
tty=true; nudge toward writing Python to a file over deeply-nested one-liners;
note the venv pre-installs common libs.
- agent-browser: distinguish daemon/connection failures (run doctor, don't loop)
from malformed commands; invoke directly (no sh -c wrapper).
- containers: use POSIX '.' instead of the bashism 'source' in generated rc
files (fixes 'sh: source: not found'); add file + xxd and pre-install
requests/httpx/beautifulsoup4/lxml/pyjwt/cryptography in the sandbox venv.
- tests: cover proxy serialization/reconnect/no-retry and HTTPQL errors.
* fix(proxy): host-side reconnect, close stale clients, don't retry mutations
Addresses Greptile review on the reconnect logic:
- Host path had no reconnect: a dead shared context client (Caido restart /
network blip) previously disabled proxy tools for the rest of the scan. Add
SharedCaidoClient, a serialized reconnect-safe holder stored once per scan in
the run context and shared across agents. On a dead transport it rebuilds via
reconnect_caido, which re-selects the SAME Caido project (preserving captured
traffic) instead of creating a new empty one.
- Don't repeat completed mutations: call_with_client / SharedCaidoClient.call
take idempotent=. Reads retry once on reconnect; replay + scope
create/update/delete heal the client but re-raise instead of risking a
double-apply.
- Don't leak replaced clients: the stale client is aclose()d (best-effort) on
every reconnect.
- Extend tests to cover close-on-reconnect, non-idempotent re-raise, and the
SharedCaidoClient holder.
* fix(proxy): close replacement Caido client when project.select fails
Addresses Greptile P1: in reconnect_caido (and bootstrap_caido) a successful
connect() followed by a failing project.select()/create() discarded the
connected client without closing it, so a missing/unavailable project could
leak a transport on every retry. Close the client before re-raising.
---------
Co-authored-by: Alex Schapiro <bearsyankees@gmail.com>
* fix(prompt): treat demo/sample data and demo environments as low severity or skip
* Update system_prompt.jinja
* fix(prompt): use demo context as a skip signal, not a CVSS override
* fix(prompt): let demo context honestly inform CVSS impact metrics
* fix(prompt): focus on detecting demo environments to inform CVSS impact
* fix(prompt): keep demo-environment check concise
* fix(prompt): trim demo-environment check to a short addendum
---------
Co-authored-by: Alex Schapiro <bearsyankees@gmail.com>
Co-authored-by: alex s <46074070+bearsyankees@users.noreply.github.com>