Repository navigation
Finding identity: 35-repository corpus, batch runner and statistics - #55
Merged
Merged
Conversation
Each replay now also scores the findings of every rule category (security, performance, quality) on their own and writes the results to summary.json under by_category, with the same totals and collisions blocks as the summary itself. Scheme keys include the rule ID and the ground truth only pairs findings of the same rule, so the categories add up exactly to the overall totals; a test checks this. The change is additive: existing keys and outputs are unchanged. Signed-off-by: Vichitra Attri <mevichitra@gmail.com>
…dentity corpus.csv lists 36 open-source web applications (26 Python, 10 JavaScript/TypeScript), each pinned to a commit, with its licence, framework and why it qualifies. corpus.py pins rows to their branch head and re-checks the measurable selection criteria at the pinned commit from git alone: at least 200 first-parent commits in the two years before it, a recent pinned commit, a single OSI-approved root licence, and the declared back-end language as the majority of analysable files. The README records the criteria and the 24 rejected candidates with their reasons. `batch` replays every corpus repository at its pinned SHA in parallel, one replay process per repository. It resumes by skipping repositories whose summary already covers the pinned SHA and commit count, and logs failures to batch.jsonl and per-repository logs without stopping. `stats` reports, for all findings and for security findings, pooled rates with Wilson intervals, cluster-bootstrap intervals over repositories, macro-averaged rates, and the median and interquartile range of per-repository rates, as CSV and Markdown tables, with an optional box plot (matplotlib, listed in requirements.txt). Signed-off-by: Vichitra Attri <mevichitra@gmail.com>
The single wide Markdown table dropped the separator after the median column. Each scope now gets a false-new table with all its intervals and a second table for the other metrics. The box plot uses a symmetric log axis so rates of a few percent stay visible next to the line scheme. Signed-off-by: Vichitra Attri <mevichitra@gmail.com>
…rpus Adds paper/ with the aggregated outputs of the 35-repository run: pooled rates with Wilson and cluster-bootstrap intervals, macro rates, per-repository rates and spread, a per-repository Markdown table (new per_repo.md output of stats) and the box plot of per-repository false-new rates. CTFd commits Vite build output under CTFd/themes/*/static/assets/ that the harness does not skip, so its replay was dominated by generated bundles (12,944 findings compared in its first 75 commit pairs). It is excluded under a new, documented "no committed build output" criterion. The README records the run's runtime and notes that renamed files make every scheme lose its findings, the main source of the residual false-new rate. Signed-off-by: Vichitra Attri <mevichitra@gmail.com>
…d rates Signed-off-by: Vichitra Attri <mevichitra@gmail.com>
Brings in the long-span drift mode (PR #53). The conflicts were in the command line and the README, where both sides only added: cli.py registers the drift, drift-summary, batch, corpus and stats commands, and the README keeps both the corpus sections and the drift-mode limitations. Signed-off-by: Vichitra Attri <mevichitra@gmail.com>
5 of 11 tasks
mevichitra
added a commit
that referenced
this pull request
Oct 17, 2026
PRs #53 (long-span drift mode) and #55 (corpus, batch runner, statistics and the paper/ tables) were merged into the branch of #51 after #51 itself had been merged, so they never reached main. Conflicts, all resolved by keeping both sides: - cli.py: register the transform subcommand (main) next to drift, drift-summary and the corpus commands; replay keeps main's --analyzer and per-analyzer schemes. - scan.py: the Scanner keeps main's pluggable analyzer and gains the per-category totals. - requirements.txt: main's pinned research dependencies; matplotlib now also serves `stats --plot`. - .gitignore: ignore tables/ as well as fidelity/.build/ and .cache/. - README.md: keep both the Fidelity/RQ2 sections and the Corpus/Batch/Statistics sections; note that the committed paper/ tables predate the sonar and bandit schemes. Signed-off-by: Vichitra Attri <mevichitra@gmail.com>
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
Scales the finding-identity study (stacked on #51) from a two-repository pilot to a 35-repository, security-focused, pinned corpus with proper statistics:
research/finding_identity/corpus.csv): 35 open-source web applications (25 Python: Django, Flask, FastAPI, Pyramid, web.py, Frappe; 10 JavaScript/TypeScript: Express, Next.js, NestJS), each with pinned SHA, branch, SPDX licence, framework and a rationale naming its security-relevant code. Chosen from 60 candidates by written criteria; the 25 rejections and their reasons are in the README.corpus pin/corpus check: pins rows to their branch head and re-measures the objective criteria at the pinned commit from git alone: ≥200 first-parent commits in the 730 days before it, pinned commit ≤90 days old, one OSI licence for the whole repository (noee/carve-outs, no BSL/SUL/Commons Clause), and the back-end language with ≥100 analysable files and more files than the other language.batch: replays every repository at its pinned SHA, in parallel (--jobs), onereplayprocess per repository. Resumable: it skips a repository whosesummary.jsonmatches the pinned SHA and commit count. Failures go tobatch.jsonland per-repository logs without stopping the run.stats(stats.py): pooled (micro) rates with Wilson 95% CIs; a cluster bootstrap over repositories (10,000 resamples, fixed seed, paired across tables); macro rates with bootstrap CIs; per-repository median and IQR. Every table also comes restricted to security findings. Outputs are CSV, Markdown and an optional box plot (matplotlib inrequirements.txt).summary.jsongainsby_category. Keys include the rule ID and the ground truth pairs only same-rule findings, so the categories sum exactly to the totals (tested).schemes.pyis untouched.scan.py,replay.pyandcli.pyget only additive hooks (new attributes and functions, an optional parameter, and a three-line subcommand registration) to keep conflicts with parallel work small.Type of change
research/, not part of the released package)Checklist
git commit -s— DCO sign-off on all commitspytestpasses locally (629 passed;research/finding_identity/tests: 40 passed)ruff check research/andruff format --check research/cleanMYPYPATH=src mypy --strict research/finding_identitycleanresearch/Results: full corpus, 200 commit pairs per repository
Runtime: 14 min 44 s wall clock on 4 cores with
--jobs 4(2,781 s summed over repositories, fetching included). That covers 7,000 commit pairs and 34,275 file versions scanned, with 36,232 findings (8,703 security). There were no failures.All findings
35 repositories, 35 of them with persisting findings: 23237 persisting findings, 1974 introduced. Rates in %.
lineline_textgithubcodeguardcg_no_scopecg_no_maskcg_rawlineline_textgithubcodeguardcg_no_scopecg_no_maskcg_rawSecurity findings
35 repositories, 35 of them with persisting findings: 5794 persisting findings, 648 introduced. Rates in %.
lineline_textgithubcodeguardcg_no_scopecg_no_maskcg_rawlineline_textgithubcodeguardcg_no_scopecg_no_maskcg_rawPooled = total numerator over total denominator. Brackets after a pooled rate are the cluster bootstrap 95% CI over repositories; macro = mean of per-repository rates; IQR over per-repository rates.
Per-repository spread
Box plot of per-repository false-new rates (
paper/false_new_by_repo.png, symlog axis):False-new rate per repository (%), all and security findings (all schemes in
paper/per_repo.md)linegithubcodeguardlinegithubcodeguardObservations
lineloses about half of all persisting findings (pooled 52.6%, bootstrap CI 48.5–57.6), and as many security findings (50.7%, CI 43.1–62.9).codeguardon security findings. The pooled rate is pulled up by a few JS/TS repositories (Ghost about 20%, cal.com, Hoppscotch and hedgedoc about 6%). The cause is renamed files: every key includes the path, so a moved or.js→.tsfile loses all its findings. For example, 130 of Ghost's 137codeguardfalse-new findings are on unchanged lines in 54 renamed file changes. This is now listed in the README's limitations.codeguardcollides on 18.6% of findings (29.6% of security findings),githubon 2.1%, andcg_no_scopeon 23.6%. Its hidden rate is 1.9% (CI 0.9–3.5) against 0.8% forgithub.line_text1.73–2.08 against 0.80–3.51). Findings within a repository are strongly correlated, so the bootstrap intervals are the ones to quote.Test evidence
New tests:
test_stats.pychecks against hand-computed values: Wilson intervals (k=n/2, 0, n; n=0), type-7 quantiles, pooled vs macro (1/4 and 3/6 give 0.4 vs 0.375), the cluster bootstrap with fixed draws (percentile interpolation worked by hand), collapse on identical repositories, undefined resamples dropped, security-scope extraction, a missing scope skipped, and CSV/Markdown outputs.test_batch.pycovers resume (is_doneneeds a matching SHA and enough pairs and survives corrupt JSON), skipped/stale/new/failed handling, the journal and logs, retrying only failures,--force, and a real end-to-end fetch and replay of a local repository pinned behind its branch head. It also covers the licence classifier (GPLv3 mentioning the AGPL,v. 3.0, carve-outs, BSL) and the corpus round trip.test_categories.pychecks that per-category totals add up exactly to the overall totals.Notes for reviewer
CTFd was excluded during the run. It commits Vite build output (
CTFd/themes/*/static/assets/*-<hash>.js) that the harness's vendored-file filter does not skip, so its replay compared 12,944 findings in its first 75 pairs. No other repository exceeded about 4,000 over 200 pairs. This adds a hand-judged "no committed build output" criterion, disclosed in the README. A content-based minified-file filter inscan.pycould bring it back; I left that alone becausescan.pyis being edited in parallel. Its replay finished afterwards (200 pairs, 28 min; the full 36-repository batch took 31 min 14 s wall time, with 0 failures). As a sensitivity check, here are the results with CTFd included:linegithubcodeguardcodeguardcodeguardCTFd alone has 22,122 persisting findings, as many as the other 35 repositories together (23,237). It swamps the pooled rates, while the per-repository medians hardly move. That is the case for excluding it, and for reporting medians and cluster-bootstrap intervals next to pooled rates.
DefectDojo and IntelOwl are pinned to their development branches (
dev,develop): their default branches only get releases (184 and 14 first-parent commits in two years).Committed:
corpus.csvandpaper/(pooled.csv12 KB,per_repo.csv146 KB,repos.csv,tables.md,per_repo.md, the plot). Clones,results/andtables/are git-ignored.The PR base is
claude/nice-allen-ptg63r(Add finding-identity research harness for studying scheme effectiveness #51), so the diff shows only this work.