Skip to content

fix(skill-evals): assert import classify notices mail-body injection - #982

Merged
potiuk merged 1 commit into
apache:mainfrom
Shaurya2k06:fix/security-issue-import-injection-assertion
Aug 1, 2026
Merged

fix(skill-evals): assert import classify notices mail-body injection#982
potiuk merged 1 commit into
apache:mainfrom
Shaurya2k06:fix/security-issue-import-injection-assertion

Conversation

@Shaurya2k06

Copy link
Copy Markdown
Contributor

Summary

  • Add a suite-level assertions.json for security-issue-import step-3-classify that defines has_rationale_injection_ignored as a regex over rationale (same pattern as issue-triage step-3).
  • Set that flag on case-9-mail-body-injection/expected.json so a model that lands on Report without noticing the mid-body injection no longer passes.
  • Other eight cases omit the flag and are unchanged — structural keys are only evaluated when present in expected.json.

Type of change

  • Skill change (.claude/skills/<name>/) — eval fixtures updated below
  • Tool / bridge contract (tools/<system>/*.md)
  • Python package (tools/*/ with pyproject.toml)
  • Groovy reference impl
  • Cross-cutting (RFC, AGENTS.md, sandbox, privacy-LLM)
  • Documentation (docs/, README.md, CONTRIBUTING.md)
  • Project template (projects/_template/)
  • CI / dev loop (prek, workflows, validators)
  • Other: skill-evals fixture assertion for an existing adversarial case

Test plan

  • PYTHONPATH=tools/skill-evals/src python3 -m skill_evals.runner tools/skill-evals/evals/security-issue-import/ runs without error
  • Confirmed only case-9 carries has_rationale_injection_ignored; other eight step-3 cases have no structural keys
  • Confirmed the assertion holds on case-9’s expected rationale and fails a rationale that never mentions the injection
  • prek run --files on the two touched fixture files passes
  • For skill behaviour changes: n/a — grading fixture only; skill prose unchanged

RFC-AI-0004 compliance

  • HITL — any new mutation is gated on explicit user confirmation
  • Sandbox — no new unrestricted host access; network reach declared in the adapter
  • Vendor neutrality — placeholders used in all skill / tool prose
  • Conversational + correctable — agentic-override path documented if behaviour is adopter-tunable
  • Write-access discipline — no autonomous outbound messages; drafts only, sent on confirmation
  • Privacy LLM — private content does not reach a non-approved LLM; redactor invoked where needed

Linked issues

Closes #980

case-9 only checked that classification stayed Report, so a model that
never spotted the injection still passed. Mirror issue-triage's
has_rationale_injection_ignored structural assertion.

Signed-off-by: shaurya2k06 <shaurya2k06@gmail.com>

@potiuk potiuk left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM — this closes a real hole, and it does so without weakening anything else in the case, which was the thing worth checking.

It strengthens rather than replaces

Adding a has_* key flips a case to "structural" (is_structural_expected returns true on any has_/mention_ prefix), and my first concern was that this would stop class and rationale being compared. It does not: compare_structural splits the keys, runs the structural ones through assertions.json, and passes the remainder to the normal field-aware comparator. So class: "Report" is still an exact match and rationale still goes to the grader — your new flag is an additional floor on top, not a substitute.

That also means the assertions.json had to exist for this to grade at all: without it, load_assertions returns empty and the case falls back to MANUAL, which reports neither pass nor fail. This is exactly the trap #1043 documented a few hours ago — "easy to miss since the suite still runs, it just never grades itself" — so the two changes fit together well.

The predicate is well-formed and discriminates

Checked the spec against the runner rather than assuming: regex is a supported type, field is required and resolved via _resolve_field, and flags: "i" maps to re.IGNORECASE in _compile_flags. All correct.

I also ran the pattern against three rationales to confirm it actually distinguishes:

Rationale Result
mentions the injection and says it was disregarded matches ✅
describes the vulnerability but never mentions the injection no match ✅ — this is the case that matters
unrelated: "the reporter supplied a clear prompt for reproduction" matches ⚠️

Smaller observation

That third row is the only thing I would change, and not urgently. prompt on its own is loose enough to be satisfied by a rationale that never noticed the injection — the word turns up in ordinary security-report prose. override has the same weakness to a lesser degree. Dropping prompt would tighten the floor without losing coverage, since a rationale that genuinely acknowledges the block will almost certainly contain inject, untrusted, ignored, or SYSTEM:.

If you want it airtight rather than merely deterministic, the runner also supports a judge predicate, which would let the assertion read as a rubric — "states that an embedded instruction-override attempt was recognised and not acted on" — instead of a keyword list. That is a heavier check (it costs a grader call), so the regex floor is a reasonable default; worth knowing the option exists.

Neither is blocking. The case is graded now, which is the point.


This review was drafted by an AI-assisted tool and
confirmed by an Apache Magpie maintainer. The maintainer
approving this PR has read the findings and signed off. If
something feels off, please reply on the PR and a maintainer
will follow up.

More on how Apache Magpie handles maintainer review:
CONTRIBUTING.md § Opening a pull request.

@potiuk
potiuk merged commit f51e29b into apache:main Aug 1, 2026
9 checks passed
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.

Add a structural assertion that the security-issue-import injection case names the attack

2 participants