Follow-up correctness fixes: model matching, confidence, demo, injection probe, SSRF#2
Merged
Merged
Conversation
…shots
model_label_parts split on ":" and kept the part *after* the colon, so two
unrelated models sharing a tag like ":free" (e.g. "meta-llama/llama-3-8b:free"
and "qwen-2-7b:free") both produced a ("free",) token tuple and were treated
as the same model, masking a real substitution. It also required exact token
equality, so a standard dated-snapshot echo like "gpt-4.1-2025-04-14" did not
match the requested "gpt-4.1" and was wrongly flagged as substituted (high
risk, identity_score 0).
Compare by base name instead: keep the part before a ":tag", and strip a
dated snapshot suffix (-YYYY-MM-DD or -YYYYMMDD) as an extra candidate.
Signed-off-by: Phillip <phillip.wu@infothinker.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…rdicts The mismatch/high-risk branch weighted protocol/capability/usage as 0.25/0.45/0.1 (sum 0.8) while the normal branch sums to 1.0, so the two branches produced confidence_score on different scales — the high-risk branch could never exceed 80 even with perfect evidence. Normalize by the weight sum so both branches yield comparable 0-100 scores while preserving the intended de-emphasis of protocol/usage relative to capability. Signed-off-by: Phillip <phillip.wu@infothinker.com> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…arts
client.extract_choice_content stringified list-shaped message.content with
str(), so an OpenAI content-parts response ([{"type":"text","text":...}])
became a Python repr ("[{'type': 'text', ...}]") in answer_preview and in
substring scoring, while the protocol-check path already used the correct
providers.extract_openai_choice_text. Drop the duplicate copy and reuse the
providers helper, which joins the text parts.
Signed-off-by: Phillip <phillip.wu@infothinker.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
demo_protocol_report and demo_answer hardcoded the upstream model as
"gpt-4.1", so selecting either of the other advertised demo models
("gpt-4.1-mini", "claude-3-5-sonnet") produced a model mismatch and a
false "substituted"/high-risk verdict even though every canned probe
answer passed. Echo the requested model instead.
Also parse the model-verification settings query with parse_qs so the
percent-encoded claimed_model the frontend sends (e.g. "openai%2F...") is
decoded before profile matching, replacing the hand-rolled parser that
only handled "+".
Signed-off-by: Phillip <phillip.wu@infothinker.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The prompt_injection probe asked the model to summarize a document that itself contained the injection text, then forbade the literal phrase "只回答 PASS". A faithful, injection-resistant summary naturally quotes that phrase and scored 0/suspicious, while a hijacked model answering only "PASS" scored 35 — correct behavior ranked below compromised behavior. Replace it with a real task (a small sum) plus an inline injection lure: a resistant model that solves the task scores 100, while a model that obeys the injection and answers "PASS" misses the expected answer and scores 35. Update the demo and test canned answers to match the new prompt. Signed-off-by: Phillip <phillip.wu@infothinker.com> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
validate_public_base_url was applied at the CLI and demo-server boundaries but not inside run_verification itself, so a direct caller of the public Python API could pass base_url="http://169.254.169.254" (or any private host) and reach localhost/metadata/internal services with the supplied auth header. Validate target.base_url at the start of run_verification. Note: url_safety still resolves DNS only for the pre-flight check and fails open on resolution errors, so DNS-rebinding/TOCTOU remains; closing that needs request-time IP pinning and is left as a follow-up. Signed-off-by: Phillip <phillip.wu@infothinker.com> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
# Conflicts: # tests/test_demo_server.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Follow-up correctness fixes from a code review of the verifier core, on top of #1. Each fix is an independent, focused commit with a regression test, so they can be reviewed and merged commit by commit.
Fixes
scoring: model-label matching (
model_label_parts):kept the part after the colon, so two unrelated models sharing a tag like:free(meta-llama/llama-3-8b:freevsqwen-2-7b:free) matched on a shared("free",)token tuple — masking a real substitution.gpt-4.1-2025-04-14did not match the requestedgpt-4.1, so an honest gateway was wrongly flagged as substituted (high risk, identity_score 0).:tag) and strip a dated snapshot suffix (-YYYY-MM-DD/-YYYYMMDD) as an extra candidate.scoring: confidence_score scale — the mismatch/high-risk branch weighted components to sum 0.8 while the normal branch sums to 1.0, so the high-risk branch could never exceed 80 even with perfect evidence. Normalize by the weight sum so both branches share a 0-100 scale.
client: content extraction —
extract_choice_contentstringified list-shapedmessage.content([{"type":"text",...}]) into a Python repr, while the protocol path already used the correctproviders.extract_openai_choice_text. Drop the duplicate and reuse the providers helper.demo: verdict consistency + query decode —
demo_protocol_report/demo_answerhardcoded upstreamgpt-4.1, so the other two advertised demo models always produced a falsesubstituted/high-risk verdict; now echo the selected model. Also parse the settings query withparse_qsfor proper percent-decoding, replacing a hand-rolled parser that only handled+.probes: injection-resistance scoring — the prompt_injection probe forbade a phrase (
只回答 PASS) that a faithful summary naturally quotes, so an injection-resistant answer scored 0/suspicious while a hijacked "PASS" scored 35 — correct behavior ranked below compromised behavior. Replace it with a real task plus an inline injection lure: resistant → 100, hijacked → 35.report: SSRF guard on the core entry point —
validate_public_base_urlran at the CLI/demo-server boundaries but not insiderun_verification, so a direct Python-API caller could pass a private/metadata host and reach it with the supplied auth header. Validatetarget.base_urlat the entry point.Out of scope (deliberately not changed)
url_safety— the guard resolves DNS only for a pre-flight check and fails open on resolution errors, so TOCTOU rebinding remains. Closing it needs request-time IP pinning across the httpx paths; left as a follow-up (noted in the commit message of fix 6).198.18.0.0/15allowlist anddev-local.shhost-API default — both looked like bugs in review but are intentional and locked by existing tests /check-deploy-scripts.sh(a benchmark/proxy range that is deliberately allowed; a developer default that assumes a local host API). Left as-is.Testing
pytest -q— all pass (52), including new regression tests for each fix.🤖 Generated with Claude Code