mirror of
https://github.com/usestrix/strix.git
synced 2026-11-09 05:19:39 -10:00
Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ba5cab24c2 | ||
|
|
0b78002686 | ||
|
|
7c490203ab |
No files matched your search
@@ -32,7 +32,6 @@ jobs:
|
||||
wheel-platform: win_amd64
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 45
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||
@@ -111,7 +110,6 @@ jobs:
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
|
||||
@@ -1,39 +1,48 @@
|
||||
name: Binary
|
||||
name: Package
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- pyproject.toml
|
||||
- uv.lock
|
||||
- strix.spec
|
||||
- scripts/**
|
||||
- strix/interface/tui/**
|
||||
- .github/actions/**
|
||||
- .github/workflows/binary.yml
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- pyproject.toml
|
||||
- uv.lock
|
||||
- strix.spec
|
||||
- scripts/**
|
||||
- strix/interface/tui/**
|
||||
- .github/actions/**
|
||||
- .github/workflows/binary.yml
|
||||
workflow_dispatch:
|
||||
workflow_call:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
wheel:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
|
||||
with:
|
||||
go-version-file: strix/interface/tui/go.mod
|
||||
cache-dependency-path: strix/interface/tui/go.sum
|
||||
- uses: ./.github/actions/setup-python-env
|
||||
- run: uv build --wheel
|
||||
- name: Wheel ships the TUI sidecar and the viewer bundle
|
||||
run: |
|
||||
uv run --frozen python - <<'PY'
|
||||
import glob, zipfile
|
||||
(wheel,) = glob.glob("dist/*.whl")
|
||||
names = zipfile.ZipFile(wheel).namelist()
|
||||
assert "strix/bin/strix-tui" in names, names
|
||||
assert "strix/interface/viewer/static/index.html" in names, names
|
||||
assert not any(name.startswith("strix/interface/viewer/frontend/") for name in names)
|
||||
assert not any(name.startswith("strix/interface/tui/cmd/") for name in names)
|
||||
PY
|
||||
- name: Install into a clean environment and run the CLI
|
||||
env:
|
||||
UV_EXCLUDE_NEWER: 5 days
|
||||
run: |
|
||||
uv venv --python 3.12 /tmp/strix-smoke
|
||||
uv pip install --python /tmp/strix-smoke/bin/python dist/*.whl
|
||||
/tmp/strix-smoke/bin/strix --version
|
||||
/tmp/strix-smoke/bin/strix --help >/dev/null
|
||||
- name: Vertex AI and Bedrock dependencies install with the wheel
|
||||
run: /tmp/strix-smoke/bin/python -c "import google.auth.transport.requests, boto3"
|
||||
|
||||
binary:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||
with:
|
||||
@@ -0,0 +1,61 @@
|
||||
name: Python
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
lock:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2
|
||||
- run: uv lock --check
|
||||
|
||||
ruff:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: ./.github/actions/setup-python-env
|
||||
- run: uv run --frozen ruff check .
|
||||
- run: uv run --frozen ruff format --check .
|
||||
|
||||
mypy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: ./.github/actions/setup-python-env
|
||||
- run: uv run --frozen mypy strix/
|
||||
|
||||
bandit:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: ./.github/actions/setup-python-env
|
||||
- run: uv run --frozen bandit -r strix/ -c pyproject.toml
|
||||
|
||||
pytest:
|
||||
name: pytest (${{ matrix.python-version }})
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python-version: ['3.12', '3.13', '3.14']
|
||||
steps:
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: ./.github/actions/setup-python-env
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- run: uv run --frozen pytest -q
|
||||
@@ -0,0 +1,26 @@
|
||||
name: TUI
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
go:
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: strix/interface/tui
|
||||
steps:
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
|
||||
with:
|
||||
go-version-file: strix/interface/tui/go.mod
|
||||
cache-dependency-path: strix/interface/tui/go.sum
|
||||
- run: test -z "$(gofmt -l .)"
|
||||
- run: go vet ./...
|
||||
- run: CGO_ENABLED=0 go build -trimpath -o /dev/null ./cmd/strix-tui
|
||||
- run: go test -race ./...
|
||||
@@ -0,0 +1,27 @@
|
||||
name: Viewer
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: strix/interface/viewer/frontend
|
||||
steps:
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
node-version: '24'
|
||||
cache: npm
|
||||
cache-dependency-path: strix/interface/viewer/frontend/package-lock.json
|
||||
- run: npm ci --no-audit --no-fund
|
||||
- run: npm run build
|
||||
- name: Committed viewer bundle matches the build
|
||||
run: git -C "$GITHUB_WORKSPACE" diff --exit-code --stat -- strix/interface/viewer/static && test -z "$(git -C "$GITHUB_WORKSPACE" status --porcelain -- strix/interface/viewer/static)"
|
||||
@@ -1,27 +1,14 @@
|
||||
name: Workflows
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- .github/**
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- .github/**
|
||||
workflow_dispatch:
|
||||
workflow_call:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
actionlint:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
env:
|
||||
ACTIONLINT_VERSION: 1.7.12
|
||||
ACTIONLINT_SCRIPT_REF: 914e7df21a07ef503a81201c76d2b11c789d3fca # v1.7.12
|
||||
@@ -40,7 +27,6 @@ jobs:
|
||||
|
||||
zizmor:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||
with:
|
||||
+26
-95
@@ -15,107 +15,38 @@ concurrency:
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: ./.github/actions/setup-python-env
|
||||
- run: uv lock --check
|
||||
- run: uv run --frozen ruff check .
|
||||
- run: uv run --frozen ruff format --check .
|
||||
- run: uv run --frozen mypy strix/
|
||||
- run: uv run --frozen bandit -r strix/ -c pyproject.toml
|
||||
|
||||
pytest:
|
||||
name: pytest (${{ matrix.python-version }})
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python-version: ['3.12', '3.14']
|
||||
steps:
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: ./.github/actions/setup-python-env
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- run: uv run --frozen pytest -q
|
||||
python:
|
||||
uses: ./.github/workflows/ci-python.yml
|
||||
|
||||
tui:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
defaults:
|
||||
run:
|
||||
working-directory: strix/interface/tui
|
||||
steps:
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
|
||||
with:
|
||||
go-version-file: strix/interface/tui/go.mod
|
||||
cache-dependency-path: strix/interface/tui/go.sum
|
||||
- run: test -z "$(gofmt -l .)"
|
||||
- run: go vet ./...
|
||||
- run: CGO_ENABLED=0 go build -trimpath -o /dev/null ./cmd/strix-tui
|
||||
- run: go test -race ./...
|
||||
uses: ./.github/workflows/ci-tui.yml
|
||||
|
||||
viewer:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
defaults:
|
||||
run:
|
||||
working-directory: strix/interface/viewer/frontend
|
||||
steps:
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
node-version: '24'
|
||||
cache: npm
|
||||
cache-dependency-path: strix/interface/viewer/frontend/package-lock.json
|
||||
- run: npm ci --no-audit --no-fund
|
||||
- run: npm run build
|
||||
- name: Committed viewer bundle matches the build
|
||||
run: git -C "$GITHUB_WORKSPACE" diff --exit-code --stat -- strix/interface/viewer/static && test -z "$(git -C "$GITHUB_WORKSPACE" status --porcelain -- strix/interface/viewer/static)"
|
||||
uses: ./.github/workflows/ci-viewer.yml
|
||||
|
||||
wheel:
|
||||
package:
|
||||
uses: ./.github/workflows/ci-package.yml
|
||||
|
||||
workflows:
|
||||
uses: ./.github/workflows/ci-workflows.yml
|
||||
|
||||
ci-passed:
|
||||
name: ci-passed
|
||||
if: always()
|
||||
needs:
|
||||
- python
|
||||
- tui
|
||||
- viewer
|
||||
- package
|
||||
- workflows
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
|
||||
with:
|
||||
go-version-file: strix/interface/tui/go.mod
|
||||
cache-dependency-path: strix/interface/tui/go.sum
|
||||
- uses: ./.github/actions/setup-python-env
|
||||
- run: uv build --wheel
|
||||
- name: Wheel ships the TUI sidecar and the viewer bundle
|
||||
run: |
|
||||
uv run --frozen python - <<'PY'
|
||||
import glob, zipfile
|
||||
(wheel,) = glob.glob("dist/*.whl")
|
||||
names = zipfile.ZipFile(wheel).namelist()
|
||||
assert "strix/bin/strix-tui" in names, names
|
||||
assert "strix/interface/viewer/static/index.html" in names, names
|
||||
assert not any(name.startswith("strix/interface/viewer/frontend/") for name in names)
|
||||
assert not any(name.startswith("strix/interface/tui/cmd/") for name in names)
|
||||
PY
|
||||
- name: Install into a clean environment and run the CLI
|
||||
- name: Every job succeeded
|
||||
env:
|
||||
UV_EXCLUDE_NEWER: 5 days
|
||||
NEEDS: ${{ toJSON(needs) }}
|
||||
run: |
|
||||
uv venv --python 3.12 /tmp/strix-smoke
|
||||
uv pip install --python /tmp/strix-smoke/bin/python dist/*.whl
|
||||
/tmp/strix-smoke/bin/strix --version
|
||||
/tmp/strix-smoke/bin/strix --help >/dev/null
|
||||
- name: Vertex AI and Bedrock dependencies install with the wheel
|
||||
run: /tmp/strix-smoke/bin/python -c "import google.auth.transport.requests, boto3"
|
||||
failing=$(jq -r 'to_entries[] | select(.value.result != "success") | "\(.key): \(.value.result)"' <<< "$NEEDS")
|
||||
if [ -n "$failing" ]; then
|
||||
echo "$failing"
|
||||
exit 1
|
||||
fi
|
||||
@@ -45,3 +45,23 @@ repos:
|
||||
hooks:
|
||||
- id: bandit
|
||||
args: [-c, pyproject.toml]
|
||||
|
||||
# Additional Python code quality checks
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v3.21.2
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
args: [--py312-plus]
|
||||
|
||||
ci:
|
||||
autofix_commit_msg: |
|
||||
[pre-commit.ci] auto fixes from pre-commit.com hooks
|
||||
|
||||
for more information, see https://pre-commit.ci
|
||||
autofix_prs: true
|
||||
autoupdate_branch: ""
|
||||
autoupdate_commit_msg: "[pre-commit.ci] pre-commit autoupdate"
|
||||
autoupdate_schedule: weekly
|
||||
# pre-commit.ci cannot run `language: system` hooks; mypy runs via `make check-all`.
|
||||
skip: [mypy]
|
||||
submodules: false
|
||||
@@ -136,8 +136,12 @@ fi
|
||||
- A finding with a call stack ⇒ `reachability=reachable_call_path`, put the
|
||||
call-path excerpt (entrypoint → vulnerable function) in
|
||||
`reachability_evidence`.
|
||||
- Listed as affecting a required module but with no reachable symbol ⇒ fall
|
||||
back to the import/symbol checks below (`imported` / `not_imported`).
|
||||
- Listed as affecting a required module but with no reachable symbol ⇒ the
|
||||
call graph found no path to the vulnerable function: `not_reached` when
|
||||
application code imports the module (cite the govulncheck result and the
|
||||
import), `not_imported` when nothing imports it. When govulncheck could not
|
||||
analyze the module (build errors, missing deps), fall back to the
|
||||
import/symbol checks below instead of claiming a level.
|
||||
|
||||
**All other ecosystems — import check, then symbol match:**
|
||||
|
||||
@@ -148,15 +152,25 @@ fi
|
||||
`PyYAML` → `yaml`). No hits ⇒ `not_imported`, with the search scope stated
|
||||
in `reachability_evidence`. For a **transitive** dependency, the check is
|
||||
whether application code imports it directly; if not, it is reachable only
|
||||
through the direct dependency — check whether the direct dep's usage can
|
||||
hit it (if unclear, use `imported` when the direct dep is used at all).
|
||||
through the packages that depend on it — walk every introducing chain
|
||||
from the lockfile (e.g. `express > body-parser > qs`) and inspect the code
|
||||
of the package one hop above the vulnerable one on each chain
|
||||
(`body-parser`, not just `express`; its installed sources, or the class
|
||||
references of its jar) for the affected API. No chain references it ⇒
|
||||
`not_reached` (below), citing every artifact you inspected; referenced ⇒
|
||||
that package's call is the usage site, continue the trace upward from
|
||||
there. If you could not inspect every chain, or one of them is unclear,
|
||||
the package is still loaded, so the level is `imported`, never
|
||||
`not_reached`.
|
||||
2. **Symbol match — per CVE, not per package.** Read each CVE's own advisory
|
||||
(GHSA/NVD/OSV `affected[].ecosystem_specific.imports` or the advisory
|
||||
text) for the affected functions/classes/APIs. Search application code for
|
||||
those symbols (`ast-grep` pattern or `rg -n`). Hits ⇒
|
||||
`vulnerable_symbol_used`, with repo-relative `file:line` of each hit (up
|
||||
to a handful) in `reachability_evidence`. Imported but no affected-symbol
|
||||
usage found (or the advisory names no symbols) ⇒ `imported`.
|
||||
to a handful) in `reachability_evidence`. Imported, search completed, and
|
||||
you can say why the vulnerable code does not run here ⇒ `not_reached`
|
||||
(below). Imported but the search was partial, the result is unclear, or
|
||||
the advisory names no symbols ⇒ `imported`.
|
||||
Different CVEs on the same package usually affect **different** symbols
|
||||
(one hits a parser, another a header check) — never copy one CVE's
|
||||
verdict/evidence onto its siblings; run the symbol search against each
|
||||
@@ -182,6 +196,23 @@ fi
|
||||
dynamic loading, unparsable sources) ⇒ `unknown` and say why in
|
||||
`assumptions`.
|
||||
|
||||
**`not_reached` — loaded, but this CVE's vulnerable code does not run here.**
|
||||
Use it when the package is imported (so `not_imported` is wrong) and you
|
||||
showed that the code path the advisory describes cannot execute in this
|
||||
codebase, and `reachability_evidence` says what you checked. The judgement is
|
||||
per CVE: it rests on what triggers *this* flaw (an affected function, a mode,
|
||||
an option, a kind of input) against what this code does. Situations that
|
||||
commonly support it, as inspiration rather than a checklist: the affected API
|
||||
has no caller in application code and the search was complete; the only call
|
||||
site is behind a condition that is false here (a polyfill for an older
|
||||
runtime, a flag that is off, a path only a dev tool runs); a transitive
|
||||
package's parent never references the affected API; the package is build or
|
||||
test tooling that the shipped artifact never executes; a library compiled into
|
||||
the runtime whose flaw sits in a mode the application never uses (a TLS server
|
||||
path in a client-only service). An inconclusive or skipped check stays
|
||||
`imported`. `not_reached` is still an evidence level, not proof of safety:
|
||||
dynamic dispatch, reflection and framework wiring can evade static search.
|
||||
|
||||
Cheap-first budgeting: the import check is one search per package — always do
|
||||
it. Do the per-CVE symbol match for every CVE whose advisory names affected
|
||||
symbols (they can be batched into one multi-pattern search per package);
|
||||
@@ -189,13 +220,18 @@ prioritize `critical`/`high`/KEV when the budget is tight; a CVE whose symbol
|
||||
search was skipped may still be reported as `imported` (the import check is
|
||||
real evidence), but its `reachability_evidence` must state that the
|
||||
affected-symbol check was not performed, so a skipped search is never
|
||||
mistaken for a completed one with no hits. Never let this analysis stall
|
||||
mistaken for a completed one with no hits, and is never filed as
|
||||
`not_reached`. Never let this analysis stall
|
||||
reporting — `unknown` with a reason beats an unverified claim.
|
||||
|
||||
Anti-overclaim rules:
|
||||
|
||||
- `not_imported` still does NOT mean safe (dynamic `import()`/reflection/
|
||||
framework wiring evade static search) — never phrase it as "not exploitable".
|
||||
- `not_reached` needs the check that shows the vulnerable code does not run,
|
||||
named in `reachability_evidence`; "no call found" after a partial search is
|
||||
`imported`, and one CVE's `not_reached` never carries over to a sibling CVE
|
||||
on the same package.
|
||||
- `reachable_call_path` is reserved for call-graph tools (govulncheck); a
|
||||
symbol grep hit is `vulnerable_symbol_used`, no matter how convinced you are.
|
||||
- The tool rejects any level other than `unknown` without
|
||||
|
||||
@@ -207,7 +207,7 @@ agent-browser snapshot -i
|
||||
```
|
||||
|
||||
Credentials in shell history are a leak. For anything sensitive, use the
|
||||
auth vault (see `references/authentication.md`):
|
||||
auth vault (see [references/authentication.md](references/authentication.md)):
|
||||
|
||||
```bash
|
||||
agent-browser auth save my-app --url https://app.example.com/login \
|
||||
@@ -367,7 +367,7 @@ agent-browser click @e3
|
||||
agent-browser record stop
|
||||
```
|
||||
|
||||
See `references/video-recording.md` for
|
||||
See [references/video-recording.md](references/video-recording.md) for
|
||||
codec options, GIF export, and more.
|
||||
|
||||
### Iframes
|
||||
@@ -491,8 +491,8 @@ snapshot — fall back to `eval` in the iframe's origin or use the
|
||||
|
||||
**Authentication expires mid-workflow**
|
||||
Use `--session-name <name>` or `state save`/`state load` so your session
|
||||
survives browser restarts. See `references/session-management.md`
|
||||
and `references/authentication.md`.
|
||||
survives browser restarts. See [references/session-management.md](references/session-management.md)
|
||||
and [references/authentication.md](references/authentication.md).
|
||||
|
||||
## Global flags worth knowing
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: semantic-confusion
|
||||
description: Cross-component semantic confusion testing for parser differentials, normalization mismatches, email and IDNA identity drift, overloaded fields, lifecycle state drift, internal redirects, protocol translation, and validator-to-sink inconsistencies
|
||||
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
|
||||
@@ -59,27 +59,6 @@ The highest-signal condition is `security_check(value_A)` followed by `sink(tran
|
||||
- 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.
|
||||
|
||||
### Email and Account Identity Drift
|
||||
|
||||
Treat an email address as both a principal selector and a delivery destination. Trace the complete boundary before testing:
|
||||
|
||||
```text
|
||||
submitted or identity-provider address
|
||||
-> application normalization / IDNA
|
||||
-> database comparison and collation
|
||||
-> selected local principal
|
||||
-> token, session, invitation, or link binding
|
||||
-> recipient or linked identity consumed by the final service
|
||||
```
|
||||
|
||||
- Cover recovery, magic links, invitations, registration uniqueness, email changes, and OAuth/OIDC account linking. Test case/accent-insensitive collations, Unicode normalization, IDNA U-label/A-label conversion, trailing dots, and provider canonicalization only when the observed stack supports them.
|
||||
- In black-box testing, report the observed equivalence and routing behavior. Do not name a specific collation, normalizer, or provider algorithm without source, configuration, or version evidence.
|
||||
- Determine whether two non-identical addresses can select the same protected principal while SMTP or an identity provider routes them to different owners. The address that receives authority must identify the same principal selected by the security check.
|
||||
- In recovery flows, compare the submitted address with the selected account's stored canonical address. A fuzzy lookup followed by delivery to the submitted value is high risk; an exact lookup plus delivery to the stored value is the primary negative control.
|
||||
- Use only an explicitly authorized attacker-controlled mailbox or test domain. Derive a bounded variant set from the deployed collation and canonicalizers rather than spraying external domains or the Unicode range.
|
||||
|
||||
For a recovery, magic-link, or invitation finding, a generic success response is not proof. Demonstrate that the variant selects the protected principal, the issued authority reaches the controlled mailbox or identity, and redemption yields the protected principal's access. If delivery or redemption cannot be observed, keep the path `needs_follow_up`.
|
||||
|
||||
### 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.
|
||||
@@ -191,8 +170,6 @@ A valid confusion finding should include:
|
||||
- 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
|
||||
- A Unicode or Punycode lookalike that neither selects another principal nor receives authority intended for that principal
|
||||
- A permissive database comparison where recovery delivery still uses the selected account's stored canonical address
|
||||
- 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
|
||||
|
||||
@@ -1693,6 +1693,7 @@ _VALID_REACHABILITY = frozenset(
|
||||
{
|
||||
"not_imported",
|
||||
"imported",
|
||||
"not_reached",
|
||||
"vulnerable_symbol_used",
|
||||
"reachable_call_path",
|
||||
"unknown",
|
||||
@@ -1840,6 +1841,9 @@ def _build_dependency_metadata(
|
||||
_REACHABILITY_EVIDENCE_LABELS = {
|
||||
"not_imported": "not imported by application code",
|
||||
"imported": "imported by application code; affected API usage unconfirmed",
|
||||
"not_reached": (
|
||||
"imported by application code; the vulnerable code of this advisory is not reached"
|
||||
),
|
||||
"vulnerable_symbol_used": "the advisory's affected API is used in application code",
|
||||
"reachable_call_path": (
|
||||
"a call path from application code to the vulnerable function was proven"
|
||||
@@ -2157,6 +2161,11 @@ async def create_dependency_report(
|
||||
can evade static search).
|
||||
- ``imported`` — application code imports the package, but usage of
|
||||
the advisory's affected API was not confirmed.
|
||||
- ``not_reached`` — application code imports the package, and the
|
||||
analysis showed that the vulnerable code of this advisory does not
|
||||
run here (the affected API has no caller, the only call site is
|
||||
behind a condition that is false in this codebase, the parent
|
||||
package never reaches it, ...). Per advisory, never per package.
|
||||
- ``vulnerable_symbol_used`` — the advisory's affected
|
||||
function/class/API appears in application code.
|
||||
- ``reachable_call_path`` — a call-graph tool (e.g. ``govulncheck``)
|
||||
@@ -2210,8 +2219,9 @@ async def create_dependency_report(
|
||||
repository root. This binds the finding to its exact file so
|
||||
remediation can target the right repository.
|
||||
reachability: Usage-evidence level from static analysis — one of
|
||||
``not_imported`` / ``imported`` / ``vulnerable_symbol_used`` /
|
||||
``reachable_call_path`` / ``unknown``. Claim only what the
|
||||
``not_imported`` / ``imported`` / ``not_reached`` /
|
||||
``vulnerable_symbol_used`` / ``reachable_call_path`` /
|
||||
``unknown``. Claim only what the
|
||||
evidence proves; when in doubt use ``unknown``.
|
||||
reachability_evidence: **Required.** The concrete proof for the
|
||||
claimed level, or, for ``unknown``, what you searched and why
|
||||
|
||||
@@ -668,6 +668,39 @@ async def test_dependency_report_records_reachability(report_state: ReportState)
|
||||
assert report["severity"] == "high"
|
||||
|
||||
|
||||
async def test_dependency_report_records_not_reached(report_state: ReportState) -> None:
|
||||
result = await _do_create_dependency(
|
||||
title="CVE-2021-23337 in lodash 4.17.20",
|
||||
description="Command injection via template.",
|
||||
target="repo/package.json",
|
||||
cve="CVE-2021-23337",
|
||||
package_name="lodash",
|
||||
installed_version="4.17.20",
|
||||
impact="Command injection where template is used.",
|
||||
remediation_steps="Upgrade to 4.17.21.",
|
||||
assumptions="Static search only; no dynamic dispatch into lodash was found.",
|
||||
package_ecosystem="npm",
|
||||
manifest_path="package-lock.json",
|
||||
fixed_version="4.17.21",
|
||||
cwe=None,
|
||||
advisory_cvss=7.2,
|
||||
technical_analysis=None,
|
||||
fix_effort="low",
|
||||
reachability="not_reached",
|
||||
reachability_evidence=(
|
||||
"src/util/strings.ts:3 imports lodash; a complete search of src/ finds no "
|
||||
"call to _.template, the only API the advisory affects."
|
||||
),
|
||||
contextual_cvss_breakdown=_DEP_CONTEXT,
|
||||
contextual_cvss_reasoning=_DEP_REASONING,
|
||||
)
|
||||
|
||||
assert result["success"] is True
|
||||
report = report_state.vulnerability_reports[0]
|
||||
assert report["dependency_metadata"]["reachability"] == "not_reached"
|
||||
assert "the vulnerable code of this advisory is not reached" in report["evidence"]
|
||||
|
||||
|
||||
async def test_dependency_report_rejects_reachability_without_evidence(
|
||||
report_state: ReportState,
|
||||
) -> None:
|
||||
|
||||
Reference in new issue
Block a user