Skip to content

feat(verification): independent adversarial finding verification#822

Draft
kusonooyasumi wants to merge 3 commits into
usestrix:mainfrom
kusonooyasumi:pr3-finding-verification
Draft

feat(verification): independent adversarial finding verification#822
kusonooyasumi wants to merge 3 commits into
usestrix:mainfrom
kusonooyasumi:pr3-finding-verification

Conversation

@kusonooyasumi

Copy link
Copy Markdown
Contributor

Summary

Adds an opt-in independent verification pass for findings. When enabled, every candidate finding is sent to a separate model that tries to refute it before it is persisted — reducing false positives in customer-facing artifacts.

This is one of several split-out PRs for fine-grained model control. It is fully self-contained and targets main directly (no dependency on the model-routing / budget-fallback PRs).

Behavior

  • Off by default. No change unless STRIX_VERIFY_FINDINGS=true.
  • Fail-closed: malformed responses and provider errors never let an unconfirmed finding through.
  • The verifier can use its own model, reasoning effort, and API key.
  • Verification usage is recorded in the same llm_usage ledger and counts toward --max-budget-usd.

Config

Env var Meaning
STRIX_VERIFY_FINDINGS Enable verification (default false)
STRIX_VERIFICATION_MODEL Model for the verifier (required when enabled)
VERIFICATION_LLM_API_KEY Optional provider key for the verifier
STRIX_VERIFICATION_REASONING_EFFORT Reasoning effort for the verifier (default high)

Config validation raises immediately if STRIX_VERIFY_FINDINGS is on but no model is set.

Changes

  • strix/report/verification.py (new) — adversarial refutation call, fail-closed parsing.
  • strix/tools/reporting/tool.py — gate both vuln and dependency findings on verification.
  • strix/report/state.py / writer.py — persist + render verification status; carry max_budget_usd in scan config.
  • strix/config/settings.pyFindingVerificationSettings.
  • Startup warm-up now also pings the verification model when enabled.

Test plan

  • pytest tests/test_finding_verification.py — defaults-off, required-model validation, and rejected-finding-not-persisted (fail-closed).
  • ruff + mypy clean.

Note: tests/test_runner_root_prompt.py currently fails on main (its settings mock lacks runtime while the runner reads settings.runtime.max_context_images). That is a pre-existing upstream issue, unrelated to this PR, which does not touch the runner.

Add an opt-in verification pass (STRIX_VERIFY_FINDINGS + STRIX_VERIFICATION_MODEL)
that sends every candidate finding to an independent model which tries to refute
it before the finding is persisted. Fail-closed: rejected findings and verifier
errors never reach customer-facing artifacts. The verifier can use its own model,
reasoning effort, and API key, and its usage counts toward --max-budget-usd.
@kusonooyasumi
kusonooyasumi force-pushed the pr3-finding-verification branch from c7723d5 to 8211504 Compare July 21, 2026 06:35
@kusonooyasumi
kusonooyasumi marked this pull request as ready for review July 21, 2026 07:02
@greptile-apps

greptile-apps Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds optional independent model verification before findings are saved. The main changes are:

  • New verifier model, key, and reasoning settings.
  • Fail-closed gates for vulnerability and dependency findings.
  • Verification usage and budget accounting.
  • Verification metadata in persisted and rendered reports.
  • Startup warm-up and tests for the new flow.

Confidence Score: 4/5

Verifier credential handling needs to be fixed before merging.

  • Separate-provider verification can fail during startup warm-up.
  • Same-provider verification can silently use the primary account key.
  • The finding gates and report metadata paths otherwise appear consistent.

strix/interface/main.py and strix/report/verification.py

Security Review

The verifier credential boundary is not applied consistently. A same-provider verifier can use the primary model credential instead of VERIFICATION_LLM_API_KEY, affecting account isolation, model access, billing, and quotas.

Important Files Changed

Filename Overview
strix/report/verification.py Adds fail-closed verification, response parsing, usage recording, and budget checks, but does not reliably apply a separate same-provider key.
strix/interface/main.py Adds verifier warm-up without applying the verifier-specific credential first.
strix/tools/reporting/tool.py Gates both dynamic and dependency findings before persistence.
strix/report/state.py Persists verification metadata and carries the scan budget into report state.
strix/report/writer.py Renders verification status while remaining compatible with older reports.
strix/config/settings.py Adds verifier configuration and validates that enabled verification has a model.
tests/test_finding_verification.py Covers defaults, model validation, and rejected findings, but not verifier credential selection.
Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 2
strix/interface/main.py:358-371
**Verifier Key Missing During Warm-Up**

When the verification model uses a different provider and its credential exists only in `VERIFICATION_LLM_API_KEY`, this warm-up runs before that key is installed. The primary model can connect successfully while the verifier fails authentication, causing startup to exit even though the real verification path would configure the key.

### Issue 2 of 2
strix/report/verification.py:90-94
**Primary Key Overrides Verifier Key**

When the primary and verification models use the same provider with different credentials, `configure_sdk_model_defaults()` installs the primary key first and `_mirror_api_key_to_provider_env()` only fills missing environment variables. The configured verification key can therefore be ignored, so verification uses the primary account and can fail model access or charge the wrong quota.

Reviews (1): Last reviewed commit: "feat(verification): independent adversar..." | Re-trigger Greptile

Comment thread strix/interface/main.py
Comment on lines +358 to +371
verifier = StrixProvider().get_model(verification_model)
await asyncio.wait_for(
verifier.get_response(
system_instructions="You are a helpful assistant.",
input="Reply with just 'OK'.",
model_settings=ModelSettings(),
tools=[],
output_schema=None,
handoffs=[],
tracing=ModelTracing.DISABLED,
previous_response_id=None,
conversation_id=None,
prompt=None,
),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Verifier Key Missing During Warm-Up

When the verification model uses a different provider and its credential exists only in VERIFICATION_LLM_API_KEY, this warm-up runs before that key is installed. The primary model can connect successfully while the verifier fails authentication, causing startup to exit even though the real verification path would configure the key.

Prompt To Fix With AI
This is a comment left during a code review.
Path: strix/interface/main.py
Line: 358-371

Comment:
**Verifier Key Missing During Warm-Up**

When the verification model uses a different provider and its credential exists only in `VERIFICATION_LLM_API_KEY`, this warm-up runs before that key is installed. The primary model can connect successfully while the verifier fails authentication, causing startup to exit even though the real verification path would configure the key.

How can I resolve this? If you propose a fix, please make it concise.

Comment thread strix/report/verification.py Outdated
Comment on lines +90 to +94
configure_sdk_model_defaults(settings)
if verification.api_key:
from strix.config.models import _mirror_api_key_to_provider_env

_mirror_api_key_to_provider_env(model_name, verification.api_key)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 security Primary Key Overrides Verifier Key

When the primary and verification models use the same provider with different credentials, configure_sdk_model_defaults() installs the primary key first and _mirror_api_key_to_provider_env() only fills missing environment variables. The configured verification key can therefore be ignored, so verification uses the primary account and can fail model access or charge the wrong quota.

Prompt To Fix With AI
This is a comment left during a code review.
Path: strix/report/verification.py
Line: 90-94

Comment:
**Primary Key Overrides Verifier Key**

When the primary and verification models use the same provider with different credentials, `configure_sdk_model_defaults()` installs the primary key first and `_mirror_api_key_to_provider_env()` only fills missing environment variables. The configured verification key can therefore be ignored, so verification uses the primary account and can fail model access or charge the wrong quota.

How can I resolve this? If you propose a fix, please make it concise.

Provider env vars are global, so installing the verification key via the
environment either clobbered or was clobbered by the primary key when both
used the same provider (setdefault only fills missing vars). Pass the
verification key as a per-call api_key in model_settings.extra_args instead,
in both the runtime path and startup warm-up, so a separate-provider verifier
authenticates correctly and warm-up no longer fails before the key exists.
@kusonooyasumi

Copy link
Copy Markdown
Contributor Author

Addressed review feedback — verifier credentials

Two issues flagged:

  1. Primary key overrides verifier key (same provider). configure_sdk_model_defaults() installs the primary key first and _mirror_api_key_to_provider_env() only fills missing env vars, so a same-provider VERIFICATION_LLM_API_KEY was ignored — verification ran on the primary account.
  2. Verifier key missing during warm-up. Startup pinged the verification model before its key was installed, so a separate-provider verifier could fail warm-up and abort startup even though the runtime path would have configured the key.

Fix (fix(verification): send verifier credential per call): the verifier now sends its key as a per-call api_key in model_settings.extra_args (via the new _verifier_model_settings helper) instead of mutating global env. The same key is now also passed in the warm-up ping, so warm-up matches the runtime auth path.

New tests assert the key rides on the request and is omitted when unset.

Add VERIFICATION_LLM_API_BASE and send the verifier's api_base per call
alongside its api_key. The process-wide OPENAI_BASE_URL / litellm api_base is
set from the main model, so a verifier on a different endpoint (self-hosted
gateway, separate provider) could not be reached. Passing api_base in
model_settings.extra_args keeps the verifier endpoint independent of the main
model, in both the runtime path and startup warm-up.
@kusonooyasumi

Copy link
Copy Markdown
Contributor Author

Addressed review feedback

1. Missing API base URL. FindingVerificationSettings had no endpoint field, and the process-wide OPENAI_BASE_URL/litellm base is set from the main model — so a verifier on a different endpoint couldn't be reached.
→ Added VERIFICATION_LLM_API_BASE; the verifier now sends api_base (alongside api_key) as a per-call value in model_settings.extra_args, in both the runtime path and startup warm-up. (commit fix(verification): support a distinct verifier endpoint)

2. Verification should be a real agent that independently tests, not a one-shot. Agreed — the current verify_finding is a stateless get_response with tools=[], so it can only reason over the writeup.
→ Implemented as a stacked follow-up: kusonooyasumi#2 adds a dedicated verifier agent that reproduces the finding in the sandbox (shell / HTTP proxy / browser) and submits a structured verdict, falling back to the one-shot path when no sandbox is available.

That follow-up is stacked on the routing PR (#824) because agentic verification needs per-agent models — otherwise the verifier would run on the main scan model instead of STRIX_VERIFICATION_MODEL. It'll be retargeted here/upstream once #824 and this PR land. Flagging the new dependency: agentic verification → #824.

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.

1 participant