feat(corpus): XXE, path-traversal and Go SSRF cases + pinned real repos - #31
Merged
Conversation
Closes #11, #12, #29, #13. Corpus goes 52 -> 60 cases: * LANG-53/54 XXE (CWE-611) in Java and PHP (#11) * LANG-56/57 path traversal (CWE-22) in Go and Java (#12) * LANG-59 SSRF (CWE-918) in Go (#29) * LANG-55/58/60 three SAFE decoys The decoys are the point as much as the positives: default-safe PHP XML parsing (entities are off by default on PHP 8+, so "parses XML" is not the bug), a constant filesystem path, and a constant host with a user-supplied query string. LANG-60 paid for itself immediately — it caught a false positive in signetry-core's brand-new go.taint.ssrf rule (constant host + tainted query string reported as SSRF), fixed in Signetry/core#97 before this landed. That is exactly the false-positive class the Python SSRF rule is held to, so the Go rule had to meet it too. Real repos (#13): OWASP WebGoat (Java) and OWASP RailsGoat (Ruby), both pinned to a full SHA. They are the first JVM/Ruby targets here — every existing case is Python or JavaScript, which under-exercises the multi-language tier. Fixes the pin mechanism while adding them. scan_real_repo cloned with --depth 1 then ran `git checkout <sha>` with check=False. On a shallow clone the object is absent, so the checkout failed with fatal: unable to read tree <sha> the failure was swallowed, and the scan silently ran against the default-branch tip — so a case documented as "pinned for reproducibility" was not pinned. Verified both behaviours against a real shallow clone. Now fetches the object first and, if pinning truly cannot be honoured, records that in the note instead of passing an unpinned scan off as pinned. Requires signetry-core v0.7.0 (pin bumped): the new cases depend on its Go SSRF / Go+Java path-traversal / PHP XXE rules. Verified against the published tag in a clean environment — 60 cases, recall 1.0, 0 false positives; 50 tests and ruff green.
Signetry Reviewer — 🟡 Needs human reviewA human should decide — the required check is unknown; 1 advisory finding(s) to weigh. Deterministic gates (the authority)
Findings (1, 0 blocking)
MergeA human should review and merge.
|
This was referenced Aug 18, 2026
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.
Closes #11, #12, #29 and #13.
Corpus goes 52 → 60 cases.
LANG-53-java-xxeLANG-54-php-xxeLANG-56-go-path-traversalLANG-57-java-path-traversalLANG-59-go-ssrfLANG-55-SAFE-php-xml-defaultLANG-58-SAFE-go-constant-pathLANG-60-SAFE-go-constant-urlThe decoys are the point as much as the positives
Each probes a precision distinction the corresponding rule has to make:
LANG-55—$doc->loadXML($_POST['xml'])with no entity flags. On PHP 8 / libxml 2.9 external entities are off by default, so parsing untrusted XML is not itself XXE. A rule that flags "parses XML" fails here.LANG-58—os.ReadFile("/etc/app/config.yaml"). A constant path is not traversal.LANG-60— a constant host with a user-supplied query string.LANG-60paid for itself immediately. It caught a false positive in signetry-core's brand-newgo.taint.ssrfrule: recall was 1.0 but the decoy scored 1 FP, because the rule fired on any tainted identifier on the request line —— where the destination is compiled in. That's the same FP class the Python SSRF rule is held to (#89), so the Go rule had to meet it too. Fixed in Signetry/core#97 before this PR landed.
Real repos (#13)
Two cases, both pinned to a full SHA:
7517acca…0222f7da…First JVM/Ruby targets here — every existing real-repo case is Python or JavaScript, which under-exercises the multi-language tier.
…and the pin mechanism was broken
Adding them surfaced that
commit=did nothing.scan_real_repoclones with--depth 1, then rangit checkout <sha>withcheck=False. On a shallow clone the object isn't present, so:…the failure was swallowed and the scan silently ran against the default-branch tip. A case documented as "pinned for reproducibility" was not pinned — which matters for a benchmark whose selling point is auditability.
Verified both behaviours against a real shallow clone of railsgoat:
If pinning genuinely can't be honoured, the note now says
NOT pinned (… scanned default branch)rather than passing an unpinned scan off as pinned.Dependency
Pins
signetry-coreat v0.7.0 (released today) — the new cases depend on its Go SSRF, Go/Java path-traversal and PHP XXE rules. Against the oldv0.6.0pin, 2 tests fail; I confirmed that before cutting the release rather than after.Verification
Against the published v0.7.0 tag in a clean environment:
5 new tests: case presence, multi-language coverage per class, detection of all five new positives, zero FP on all three new decoys, and that the real-repo pins are full 40-char SHAs with provenance.