Skip to content

W6/technical tails - #235

Merged
evkir merged 4 commits into
mainfrom
w6/technical-tails
Aug 26, 2026
Merged

W6/technical tails#235
evkir merged 4 commits into
mainfrom
w6/technical-tails

Conversation

@evkir

@evkir evkir commented Aug 26, 2026

Copy link
Copy Markdown
Owner

What this changes

How it was measured

Checklist

  • ruff format --check cyberai/ tests/ and ruff check cyberai/ tests/ pass
  • pytest -W ignore::DeprecationWarning -m "not slow and not smoke" passes
  • New behaviour is covered by a test that fails without the change
  • I have read CLA.md and I hereby sign the CLA

evkir added 4 commits August 26, 2026 20:10
T3 in STANDOFF-KEY asked for an empty scope to be a violation rather than a
warning when --strict-scope is set. The flag did not exist: a grep across
cyberai/ for strict_scope returned nothing, so the item as written could not
be done. It is introduced here.

The validator warned on an absent scope and let the run proceed. That is
defensible as a default -- the pipeline has always behaved that way and a
flag that changes behaviour for people who did not set it is a surprise, not
a safety feature -- and indefensible as the only option. Absence of
authorisation is not authorisation, and an engagement where that distinction
matters had no way to say so. Under strict the phase now refuses: the
orchestrator already raises on a failed verdict, so a violation is a failed
exploit phase rather than a line of yellow text nobody reads.

The switch travels as a parameter, not as an environment read inside the
validator. A function that consults os.environ cannot be driven from a test
without patching the process, and this one is otherwise pure.

All eighteen existing call sites pass no strict argument and every one still
gets a warning; the default is unchanged by construction and by test.

One gap was found by mutation rather than by review. Deleting
`strict=self.config.strict_scope` from _run_exploit left six new tests green:
they drive the validator directly and the CLI directly, and neither can see
that the phase in between stopped passing the value on. A flag that reaches
the config and dies there is a producer without a consumer wearing full test
coverage. The seventh test drives the phase itself, and both mutations --
dropping the argument, and neutering the raise -- now fail it.
T4 asked for the bare `except Exception` around a phase to be replaced with
typed handling of network, LLM and tool errors. Measured against the code
that is the wrong fix, and doing it would have made the pipeline worse. The
clause wraps _dispatch, which reaches nmap, httpx, subprocesses, three LLM
providers and several external binaries; no honest list of what they raise
can be written, and a narrower clause trades a failed phase for a failed
run. The comment calling it deliberate is correct and it stays.

The real defect is the opposite one, and it was found by running the code
rather than by reading it. Two exceptions were being caught here that are
not phase failures at all:

BudgetExceeded says a spending cap was crossed. Measured before the change:
a run whose first phase hit the cap recorded a failed phase and moved to the
next one, which was free to spend again. The cap stopped being a cap exactly
when it began to matter.

EgressViolation says the air-gapped path was asked to reach a remote
provider. The run continued after the property it exists for had already
been broken.

Both now re-raise, in the sync path and in AsyncOrchestrator's copy of the
same clause. The phase is still recorded as failed before the exception
leaves, so the session says where the run ended rather than simply stopping.
Everything else is still caught, and a test pins that: a dead target must
not become a dead run.

Recorded phase errors also carry their type now. "refused" alone reads the
same whether a target dropped the connection or a defect raised it; the
session export, the critic deciding on a retry, and whoever opens
session.json get "ConnectionError: refused" instead. The format matches what
mcp/client_probe.py already writes.

Four mutations, four predicted victims: dropping either raise, emptying the
fatal tuple, and reducing the description to str(exc). Dropping the sync
raise leaves the async test green and vice versa, which is what proves the
two copies are covered separately rather than by accident.
T2 asked for the intel agent to stop losing ports from the KB during CVE
lookup. Measured, that does not happen: after the mass-open early return the
port list is still in recon.nmap, untouched, and the skip is a deliberate
trade the docstring explains -- a proxy answering every probe makes the
service data noise, and spraying NVD with it returns garbage.

The defect next to it is real. TLSCVEMapper had no product caller until a
previous fix gave it one, and that fix wrote the enriched list to
`intel.tls_findings`. Nothing reads that key: a grep across cyberai/ returns
the write and nothing else, and the report renders session.findings, not the
KB. Recon publishes no TLS finding either. So on a real target an expired
certificate, a negotiated TLS 1.0 and RC4 in the cipher suite were probed,
classified, matched to CVEs -- and then absent from the document the client
reads. The cure reproduced the disease one level up.

Each enriched TLS problem now becomes a finding. Severity travels from the
TLS classifier rather than being decided again here: it already knows a
deprecated protocol outranks a weak cipher. An unrecognised level degrades
to INFO rather than raising, because losing a finding over a spelling is
worse than under-rating one. A condition with no matching CVE still reaches
the report and says so -- unmapped is a configuration problem, not the
absence of one.

The call keeps its position above both early returns, and a test now covers
the mass-open path as well as the no-ports one. Those are exactly the runs a
TLS-only target lands in, and a fix that only worked on the full path would
miss both.

tests/unit/test_intel_tls_context.py stays green through every mutation of
the new code, which is the point: it pins the producer, and the new file
pins the consumer. Removing the finding loop reddens five of six new tests
and none of the four old ones.
T1 asked for authorized_scope to reach the exploit agent. It already does --
_scope_ok has been reading session.authorized_scope since before this sprint.
What the item missed is that the guard reached one caller out of three.

Its own docstring promised nuclei and the out-of-band path. Measured:
_scope_ok returned False for a target the scope excluded while
_oob_confirmer built a collector for that same target and handed it to the
walk. The HTTP walk itself never asked at all -- it sent the full payload
corpus at whatever host it was given. The orchestrator gates session.target,
which is the same value on a normal run and is not the value these methods
receive, so a direct call bypassed authorisation entirely. That bypass is
what the in-agent guard was added to close.

Both now consult it, and the OOB check sits above the collector probe: an
unauthorised host must not even cost a request to phantom-grid. An empty
scope is still a no-op, which is what the bench profile relies on.

One test in this file was worthless and mutation found it. The first version
of the web-path test asserted zeros against an agent with an empty knowledge
base, so it read the "no HTTP surface" branch and called it a refusal; with
the guard deleted it stayed green. It now puts a surface in the KB and
patches exploit_surface, so a guard that fails to fire shows up as a call.

Also: Development Status classifier moves from Alpha to Beta. It said Alpha
while the package shipped a wired trust boundary, decontaminated proofs, an
architecture-tested README and 2280 tests. Beta claims the interfaces are
stable enough to build against and that the failure modes are known and
written down -- not that the work is done.
@evkir
evkir merged commit 8f40050 into main Aug 26, 2026
7 checks passed
@evkir
evkir deleted the w6/technical-tails branch August 26, 2026 18:37
@codecov-commenter

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants