main logo icon

Published on

July 28, 2026

|

17 min read

How Big Is an Authorization Fix? Patch Size by Weakness Family

An original measurement study of 3,806 GitHub Advisory Database advisories. Access control fix commits change a median of 64 lines against injection's 39, but the gap is non code churn and repository selection, not the fix itself.

Arafat Afzalzada

Arafat Afzalzada

Founder

Web App SecurityAdvisories

Summarize with AI

ChatGPTPerplexityGeminiGrokClaude

TL;DR

We measured the size of the fix commit behind 3,806 GitHub Advisory Database advisories, split into access control (CWE-284, 285, 639, 732, 862, 863), injection (CWE-77, 78, 89, 94) and cross site scripting (CWE-79), restricted to advisories that reference exactly one resolvable fix commit. Pooled, access control fixes change a median of 64 lines against 39 for injection and 19 for XSS. That gap does not survive the controls. Inside the 90 repositories that carry advisories in both families, the paired median difference is zero: 44 repositories put access control higher, 43 put injection higher. Strip test, documentation and lockfile lines out of the count and the two families sit at 25 and 23 code lines, a difference the data cannot separate from zero. What does survive is that access control fix commits carry far more non code churn: a median 37.5 percent of their changed lines sit in tests and docs, against 3.7 percent for injection. The access control sample is also unusually concentrated: one npm package supplies 12.3 percent of it from a single four month disclosure burst, and removing that package from every family moves the pooled medians to 53 against 36 lines and the code only gap to 1.5 lines. XSS is genuinely the smallest repair in the corpus on every measure. The script, the row level data and the exclusion log are published under CC BY 4.0 so anyone can rerun it.

The fix commit behind a broken access control advisory changes a median of 64 lines across 3 files, against 39 lines for an injection advisory and 19 lines for cross site scripting. That is the pooled result from measuring every advisory in the GitHub Advisory Database that references exactly one resolvable fix commit, 3,806 of them, snapshot 28 July 2026. Read on its own, the number says authorization repairs are the biggest of the three. That reading is wrong, and the rest of this post is the reason why.

The gap does not survive its own controls. Inside the 90 repositories that carry advisories in both families, the paired median difference between an access control fix and an injection fix is zero changed lines: 44 repositories put access control higher, 43 put injection higher, and the sign test returns p = 1.00. Strip out lines that land in tests, documentation, changelogs and lockfiles and the pooled medians collapse from 64 against 39 to 25 against 23 code lines, a difference with a bootstrap 95 percent confidence interval of [-1, 7]. Access control advisories are not attached to bigger repairs. They are attached to bigger commits, and to bigger codebases.

One more thing belongs in the first screen rather than a footnote. The access control sample is unusually concentrated: a single npm package, openclaw/openclaw, supplies 135 of the 1,100 access control advisories, or 12.3 percent, and they arrived in one four month disclosure burst between February and May 2026. Removing that package from every family drops the access control median from 64 to 53 changed lines and the injection median from 39 to 36, narrowing the raw gap from 25 lines to 17. On code lines only the gap falls from 2 to 1.5, and its significance test moves from p = 0.11 to p = 0.37. The sensitivity analysis is reported in full below, and it sharpens the conclusion rather than softening it.

This study was commissioned expecting the opposite answer. The intuitive pitch was that authorization fixes are small and local, a missing if statement bolted onto a controller. A two seed preliminary run pointed the other way, at roughly 44 to 47 median changed lines, and the full census pushed further to 64. Then the repository matched control flattened it. Every number below was frozen before the title and the headline sentence were written. The full script, the row level dataset and the complete exclusion log are published under CC BY 4.0 at stingrai.io/data/authorization-patch-size, so any claim here can be rerun rather than taken on trust.

The numbers in one screen

  • Median changed lines, access control (CWE-284, 285, 639, 732, 862, 863): 64, interquartile range 19 to 173.3, n = 1,100 (results.csv, snapshot 28 July 2026).

  • Median changed lines, injection (CWE-77, 78, 89, 94): 39, interquartile range 13 to 116.3, n = 948.

  • Median changed lines, cross site scripting (CWE-79): 19, interquartile range 6 to 64.8, n = 1,758.

  • Repository matched, paired per repository: median difference 0 changed lines, 44 repositories favour access control, 43 favour injection, 3 tie, sign test p = 1.00, across 90 repositories and 791 advisories.

  • Code lines only, tests and docs and lockfiles removed: access control 25, injection 23, XSS 11. Access control against injection: median difference 2, bootstrap 95 percent CI [-1, 7], Mann Whitney p = 0.11.

  • Non code churn: a median 37.5 percent of an access control fix commit's changed lines sit in non code files, against 3.7 percent for injection and 0 percent for XSS.

  • Commits that touch at least one non code file: access control 60.8 percent (669 of 1,100), injection 51.5 percent (488 of 948), XSS 41.5 percent (729 of 1,758).

  • Median changed files: access control 3 (IQR 2 to 5), injection 2 (IQR 1 to 4), XSS 2 (IQR 1 to 4).

  • Repository concentration: one package supplies 12.3 percent of the access control sample (135 of 1,100, across 491 distinct repositories). The next largest supplies 2.6 percent.

  • Leave one package out sensitivity: removing it from every family gives access control 53 and injection 36 changed lines (raw gap 25 to 17), and 23 against 21.5 code lines (code gap 2 to 1.5, p = 0.11 to p = 0.37). The paired repository result is unchanged at 0.

  • What survives every control: XSS is the smallest repair in the corpus. Access control against XSS on code lines only, median difference 14, Mann Whitney p below 1e-15, rank biserial correlation 0.27.

  • Corpus: 10,809 reviewed advisories in the eleven listed CWEs, narrowed to 3,806 measured. Every drop is rule based and counted.

  • Validation: 50 records hand reviewed. 0 arithmetic errors, 4 family label disagreements inherited from the advisory's own CWE mapping, 4 commit scope concerns.

Patch Size Corpus Funnel Exclusions

Key takeaways

The pooled gap is real, small, and mostly an artefact of what else rides in the commit. Access control fix commits are 64 percent larger than injection fix commits on raw changed lines, and the Mann Whitney test is comfortably significant (z = 5.60, p = 2.1e-8). But the rank biserial effect size is 0.14, which is small, and once non code files come out of the count the median difference falls to 2 lines with a confidence interval that straddles zero.

The access control sample leans on one package harder than any other family leans on anything. A single npm project supplies 12.3 percent of it, in one disclosure burst, while the next largest contributor supplies 2.6 percent. Take that project out of all three families and a third of the raw family gap goes with it. That is a composition artefact, and finding it is the same finding as the repository matched null: this comparison is very sensitive to who is in the sample and barely sensitive to what the weakness is.

Authorization advisories live in bigger codebases, and that explains a lot of the pooled result. The repositories that dominate the access control sample are large, long lived platforms: openclaw, moodle, xwiki-platform, jenkins, gitea, dolibarr. Restricting the comparison to repositories that carry both families narrows the advisory level gap from 25 median lines to 18.5, and pairing one difference per repository erases it entirely.

Access control fixes ship with more tests and more documentation than injection fixes do. This is the clearest family level difference in the dataset and it is not about the security logic at all. The median access control fix commit puts 37.5 percent of its changed lines into files that are not application code, ten times the injection figure. Roughly three in five access control fix commits touch at least one test, doc or changelog file.

Cross site scripting really is the small one, and folding it into injection would have hidden that. XSS sits at 19 raw changed lines and 11 code lines, roughly half of either other family on both measures, with a moderate effect size against access control (rank biserial 0.27) that survives the code only control. Output encoding at a template boundary is a genuinely more contained repair than rewriting a query builder or an authorization check.

Patch size is not difficulty. A two line change that adds a permission check to the correct method can take a week to locate and a minute to write. Nothing in this dataset speaks to how hard a weakness class is to find, to reason about, or to get right. It measures the diff, and the diff is the last and cheapest step.

Methodology

Corpus. The GitHub Advisory Database, restricted to type=reviewed advisories. Reviewed advisories are validated by GitHub and mapped to packages in the twelve supported ecosystems; unreviewed advisories are imported from the National Vulnerability Database feed without that check, and were excluded for exactly that reason. Enumeration used the global advisories REST endpoint with the cwes filter, one CWE per query, cursor paginated at 100 per page. Snapshot date: 28 July 2026.

Weakness families. Three sets, published in full so nothing rests on a private judgement call:

Family

CWEs included

Rationale

Access control

CWE-284, CWE-285, CWE-639, CWE-732, CWE-862, CWE-863

Improper access control, improper authorization, authorization bypass through user controlled key (the IDOR mapping), incorrect permission assignment, missing authorization, incorrect authorization

Injection

CWE-77, CWE-78, CWE-89, CWE-94

Command injection, OS command injection, SQL injection, code injection

Cross site scripting

CWE-79

Split out rather than folded into injection

An advisory joins a family only if its CWE list intersects exactly one of the three sets. Advisories touching two or more were dropped as ambiguous, because a single fix commit cannot then be attributed to a single family. That removed 97 of 10,809 advisories.

Why CWE-79 is reported separately. XSS is formally an injection weakness, and most taxonomies nest it there. Its repair shape is different. An XSS fix is usually an escape call added at a template or output boundary; a SQL or command injection fix is usually a rewrite of how a query or an argument list is constructed. Pooling CWE-79 into injection would have dragged the injection median down by weight of numbers (1,758 XSS advisories against 948 injection advisories) while telling you nothing about SQL or command injection. Splitting it out is the honest choice and it turned out to matter: XSS is the one family that stays distinct under every control applied here.

One commit, one advisory. Only advisories referencing exactly one distinct GitHub commit URL were measured, so the patch size is attributable to that advisory. Pull request URLs, compare URLs and the advisory's own URL do not count as commit references. Short and full SHAs of the same commit are collapsed by prefix match. Of 10,712 single family advisories, 5,210 referenced no GitHub commit at all and 1,052 referenced more than one, leaving 4,450 candidates.

Measurement. Each commit was fetched through the GitHub commits API. Changed lines are additions + deletions from the API's stats object; changed files is the length of the files array. Both were cross checked: across all 3,806 measured records, the per file additions and deletions sum exactly to the reported stats in every case.

Exclusion ladder, applied in this order, first match wins, so the counts sum cleanly to the 644 candidates removed:

Rule

Meaning

Dropped

commit_unresolvable

Fetch did not return HTTP 200 (repository deleted, renamed or made private)

28

merge_commit

Two or more parents

391

no_line_changes

Additions plus deletions equals zero

1

non_code_only

Every changed file matches the non code patterns

79

size_outlier

Additions plus deletions above 1,000

145

The 1,000 line outlier threshold was fixed before the medians were computed. Commits above it are overwhelmingly release squashes, vendored dependency refreshes and generated file regenerations, and a handful of them would otherwise move a mean without moving a median. Every excluded advisory, with its rule, is published in excluded.csv.

Non code file patterns cover tests (tests/, spec/, __tests__/, *_test.*, *.spec.*), documentation (docs/, *.md, *.rst, CHANGELOG, LICENSE), lockfiles (package-lock.json, yarn.lock, Cargo.lock, go.sum and their peers), vendored and generated output (vendor/, node_modules/, dist/, *.min.js, *.map) and CI configuration. The exact regular expressions are written into results.json on every run, so the definition travels with the data.

Repository concentration, and what was done about it. Advisory counts per project are extremely uneven, and the access control sample is the most uneven of the three. Its 1,100 advisories come from 491 repositories, but openclaw/openclaw alone supplies 135 of them, 12.3 percent from a single npm package, published in a four month burst: 9 in February 2026, 66 in March, 56 in April and 4 in May. The same package also supplies 38 injection advisories (4.0 percent of that family) and 2 XSS advisories, 175 rows in total. No other project comes close in any family: the second largest access control contributor is Moodle at 2.6 percent, and the largest XSS contributor is Pimcore at 3.9 percent. Rather than drop or cap the package, which would be a judgement call made after seeing the data, the study keeps every row in the headline figures and publishes a leave one package out sensitivity analysis beside them. The package is removed from all three families, not only from access control, because removing it from one side alone would manufacture a gap change that is purely an artefact of the removal.

Two analyses, both reported. The pooled analysis takes every qualifying advisory. The repository matched analysis restricts to repositories carrying advisories in both the access control and the injection family, which holds codebase size, language, review culture and commit conventions roughly constant. Both are reported below with equal weight, including where the matched result is the less interesting one.

Statistics. Medians and quartiles use linear interpolation between order statistics. Significance uses the Mann Whitney U test with tie correction and a normal approximation, reported alongside the rank biserial correlation as an effect size, because with samples in the hundreds a tiny difference reaches significance easily. Confidence intervals on median differences come from a 10,000 iteration percentile bootstrap with a seeded PRNG. The paired repository analysis uses an exact two sided sign test. No test was chosen after seeing its result.

What was not done. No figure here was estimated, interpolated or carried over from another source. Where a quantity could not be computed reliably, the gap is stated rather than filled.

Result 1: the pooled comparison

Patch Size Pooled Median Iqr By Family V2

Metric

Access control

Injection

Cross site scripting

Advisories measured

1,100

948

1,758

Distinct repositories

491

562

754

Median changed lines

64

39

19

Interquartile range

19 to 173.3

13 to 116.3

6 to 64.8

90th percentile

380.1

260.6

174.0

Mean changed lines

134.0

100.3

65.5

Median changed files

3

2

2

Access control against injection: median difference +25 changed lines, bootstrap 95 percent CI [16, 34], Mann Whitney z = 5.60, p = 2.1e-8, rank biserial correlation 0.14. Access control against XSS: median difference +45 lines, z = 15.66, p below 1e-15, rank biserial 0.35.

Two things deserve attention in that table before anyone quotes the 64.

The first is the effect size. A rank biserial correlation of 0.14 means that if you pick one access control advisory and one injection advisory at random, the access control patch is larger about 57 percent of the time. That is a real tilt, not a chasm. The p value is small because the samples are large, not because the families are far apart.

The second is the spread. The interquartile range for access control is 19 to 173 lines, a factor of nine. Fifty percent of authorization fixes fall inside a range that wide. Any sentence of the form "an authorization fix is about N lines" is describing a distribution that does not have a characteristic size.

Patch Size Percentile Distribution By Family

The percentile view shows the gap is not a tail artefact. Access control sits above injection, and injection above XSS, at the tenth, twenty fifth, fiftieth, seventy fifth and ninetieth percentiles alike. Whatever is driving the difference operates across the whole distribution, which is what makes the next two sections necessary: a whole distribution shift is exactly what a confounder produces.

Result 1b: one package supplies an eighth of the access control sample

The pooled table above treats every advisory as an independent observation. They are not. Advisories cluster by project, and the access control sample clusters harder than either other family.

Patch Size Repository Concentration Sensitivity

Repository

Access control advisories

Share of the 1,100

openclaw/openclaw

135

12.27 percent

moodle/moodle

28

2.55 percent

xwiki/xwiki-platform

27

2.45 percent

mattermost/mattermost

24

2.18 percent

usememos/memos

22

2.00 percent

wwbn/avideo

20

1.82 percent

go-gitea/gitea

16

1.45 percent

craftcms/cms

15

1.36 percent

jenkinsci/jenkins

15

1.36 percent

keycloak/keycloak

13

1.18 percent

The distribution has one very long head and a very flat tail: 491 repositories, one of which supplies more advisories than the next four combined. Those 135 advisories were published between February and May 2026 (9, 66, 56 and 4 by month), which is the shape of a single coordinated disclosure effort against one project, not a broad industry signal.

The leave one package out result, removing all 175 of that package's advisories from all three families:

Metric

Full sample

Package removed

Change

Access control, median changed lines

64 (n = 1,100)

53 (n = 965)

down 11

Injection, median changed lines

39 (n = 948)

36 (n = 910)

down 3

XSS, median changed lines

19 (n = 1,758)

19 (n = 1,756)

unchanged

Raw gap, access control minus injection

+25

+17

a third of the gap

Access control, median code lines

25

23

down 2

Injection, median code lines

23

21.5

down 1.5

Code only gap

+2 (p = 0.11)

+1.5 (p = 0.37)

further from significance

Paired per repository difference

0 (90 repos, p = 1.00)

0 (89 repos, p = 0.91)

unchanged

Read the middle rows carefully, because the obvious mistake here is to remove the package from access control only. That would show the gap falling from 25 to 14 and overstate the effect, because the same project also supplies 38 injection advisories whose removal pulls the injection median down too. Removed from both sides, the honest number is a fall from 25 lines to 17.

This does not weaken the study's conclusion. It sharpens it. The thesis is that the pooled gap is a composition artefact rather than a property of the weakness class, and this is a second, independent demonstration of exactly that: one package accounts for roughly a third of it, the repository matched pairing accounts for the rest, and the code only split removes what is left. The paired per repository result, which is the strictest control in the study, is completely unmoved at a median difference of 0.

One neutral observation about the corpus, stated as an observation and nothing more: the single largest contributor to the 2026 access control advisory sample is an agentic AI tool distributed on npm. That is a fact about which project attracted concentrated security research attention in one four month window. It is not evidence that AI written code is more or less secure than any other code, and this dataset cannot address that question: it contains no comparison group, no measure of code provenance, and no denominator for how much of any project was written by whom.

Result 2: the repository matched control

Advisories are not randomly assigned to repositories. If access control bugs are reported disproportionately in large, mature, heavily reviewed platforms, and those platforms have larger commits for every kind of change, then the pooled gap measures the codebase, not the weakness.

To test that, the comparison was restricted to repositories carrying advisories in both families. That leaves 90 repositories and 791 advisories, including moodle, xwiki-platform, dolibarr, pimcore, craftcms, gitea, jenkins, gogs, phpmyfaq and mantisbt.

Patch Size Repository Matched Control

Analysis

Access control

Injection

Test

Pooled, all repositories

median 64 (n = 1,100)

median 39 (n = 948)

median difference +25, CI [16, 34], p = 2.1e-8

Advisory level inside the 90 matched repositories

median 68 (n = 469, IQR 23 to 154)

median 49.5 (n = 322, IQR 13 to 131.5)

median difference +18.5, CI [6, 33], p = 0.0066, rank biserial 0.11

Paired, one difference per repository

median difference 0, IQR -65 to +40.5, 44 up, 43 down, 3 ties, sign test p = 1.00

The two matched analyses point in different directions, and both belong in the record.

At the advisory level inside matched repositories, a gap survives: 68 against 49.5 median lines, still statistically significant, with the median difference narrowing from 25 to 18.5 lines and the effect size shrinking from 0.14 to 0.11. Roughly a quarter of the pooled gap is repository selection.

At the repository level, nothing survives. Taking one number per repository, the median access control fix inside a project versus the median injection fix inside that same project, the differences scatter almost symmetrically around zero. Forty four repositories put access control higher, forty three put injection higher, and the median of those differences is exactly zero. The interquartile range of the per repository differences runs from -65 to +40.5 lines, which is to say the direction flips freely from project to project. The same test on changed files gives 35 repositories up, 39 down, 16 ties, p = 0.73.

The repository level test has far less statistical power (87 usable pairs against 791 advisories), so a null there is not proof of no effect. But the honest summary of the two together is this: whatever separates the families in the pooled numbers is largely a property of where those advisories live, not of what the fix has to do. Access control advisories concentrate in bigger codebases, and bigger codebases have bigger commits.

That is the less interesting of the two findings and it gets equal billing because it is the one that constrains what anybody may claim from the pooled figure.

Result 3: strip the non code lines and the gap disappears

The exclusion ladder throws away commits where every changed file is a test, doc or lockfile. It does not adjust commits that mix code and non code files, and most of them do. So the dataset also records code_changed_lines, counting only lines in files that do not match the non code patterns.

Metric

Access control

Injection

Cross site scripting

Median changed lines, all files

64

39

19

Median code changed lines

25

23

11

Code lines IQR

9 to 68

8 to 61.3

4 to 32.8

Median share of changed lines in non code files

37.5 percent

3.7 percent

0 percent

Commits touching at least one non code file

60.8 percent

51.5 percent

41.5 percent

Access control against injection on code lines only: median difference 2 lines, bootstrap 95 percent CI [-1, 7], Mann Whitney z = 1.59, p = 0.11, rank biserial 0.04. Inside the 90 matched repositories the code only comparison is flatter still: Mann Whitney z = 0.03, p = 0.98, rank biserial 0.00, and the paired per repository median difference is -3.5 lines (36 repositories up, 50 down, p = 0.16), which if anything leans the other way.

This is the finding that reframes the whole study. An authorization fix is not a bigger code change than an injection fix. It is a bigger commit. The family gap falls from 25 median lines to 2 once non code files come out of the count, and the confidence interval on the 2 includes zero.

The obvious follow up question is what that churn actually is, and the commits answer it plainly: regression tests. Access control fixes disproportionately arrive with a test that asserts the forbidden request now returns 403, plus a changelog entry describing the behaviour change. Spring Security's broken access control fix for direct use of isFullyAuthenticated changed 149 lines across 16 files, of which only 47 lines in 9 files were application code. Deno's sandbox bypass fix is the extreme case: 257 changed lines across 34 files, and exactly one of those files was code. Traefik's cross namespace middleware fix shipped a fixture, a test and a migration doc alongside 138 lines of change.

There is a benign reading and a less benign one, and this dataset cannot choose between them. The benign reading is that authorization changes are behavioural, so they are testable, so maintainers test them. The less benign reading is that authorization changes are risky enough that maintainers feel obliged to prove them, which is a statement about confidence rather than about complexity. Either way, the extra lines are evidence of process, not of a harder repair.

Changed files tell a quieter version of the same story

Patch Size Changed Files By Family

Access control fixes touch a median of 3 files (IQR 2 to 5). Injection and XSS both sit at 2 files (IQR 1 to 4). Paired inside matched repositories, the median difference in file counts is zero, with 35 repositories up and 39 down (p = 0.73).

File counts separate the families less than line counts do, and they should be read with care in both directions. A one file commit can still rewrite an entire authorization layer inside that file, and a five file commit can be four test files and a one line guard. The file count is a coarser instrument than the line count, and the line count is already a proxy.

One measured record hits the GitHub API's 300 file listing cap, so its file count is a floor rather than a value. It is a single row in 3,806 and it does not move any median or quartile reported here.

What the corpus actually contains

Composition matters for how far these numbers travel.

Family

Top ecosystems by advisory count

Severity mix

Access control

Go 266, Maven 239, npm 223, Composer 221, pip 103

medium 597, high 323, critical 95, low 85

Injection

npm 264, Composer 250, pip 167, Maven 112, Go 88

high 452, critical 343, medium 133, low 20

Cross site scripting

Composer 747, Maven 335, npm 284, pip 174, Go 122

medium 1,291, high 280, low 111, critical 76

Two observations worth keeping in view. Access control advisories skew towards medium severity while injection advisories skew towards high and critical, which is consistent with the OWASP Top 10 2025 weighting where injection carries a higher average exploit score even after falling from third to fifth place. And the ecosystem mixes differ enough that any language level comparison would need its own controls, which is why none is offered here.

For context on why this comparison is worth making at all: Broken Access Control holds first place in the OWASP Top 10 2025 with 40 mapped CWEs, an average incidence rate of 3.74 percent and 1,839,701 recorded occurrences, while Injection sits at fifth with 37 mapped CWEs and 1,404,249 occurrences. Access control is the larger problem by volume. This study says it is not, on average, the larger diff.

Validation

Fifty measured records were drawn with a seeded shuffle (--seed 20260728) and reviewed by hand against the advisory text, the commit message and the changed file list. The full sample and the verdict on each record are published in validation-review.csv.

Check

Result

Arithmetic: per file additions and deletions sum to the API stats

0 errors in 50, and 0 errors across all 3,806 measured records

Merge commits leaking past the filter

0 in 50 (all sampled records had exactly one parent)

Family assignment consistent with the advisory's own CWE list

50 of 50

Family assignment consistent with the weakness the advisory describes

46 of 50, 4 flagged (8 percent)

Referenced commit is a clean fix for that advisory

46 of 50, 4 flagged (8 percent)

Overall, 8 of 50 records (16 percent) carried at least one flag. No record carried both.

The four family label flags are all inherited from the advisory's own CWE mapping and all fall in the injection family: a log injection issue mapped to CWE-94, a PHP object deserialization issue mapped to CWE-94, an arbitrary file write mapped to CWE-78, and a DNS rebinding issue mapped to CWE-94 alongside CWE-352. The pipeline applied the published rule faithfully in every case. The sample contained 13 access control records, 11 injection records and 26 XSS records, so those four flags are four of eleven sampled injection records. Eleven records is far too few to put a confident rate on, and the point estimate is uncomfortable enough to state plainly: the injection family here is the one carrying upstream label noise, and its figures should be read as an upper bound on purity.

The four commit scope flags are cases where the single referenced commit does more than fix the advisory: a template loading refactor that carried a Synapse fix, a follow up commit to an earlier Langroid mitigation, a bundled Grav security release fixing several issues at once, and a Microweber commit whose only message is "update". These inflate measured patch size. They fall across all three families (one access control, one injection, two XSS), so the bias is not obviously directional, but 8 percent of measured patches being larger than the security change they represent is a real ceiling on precision.

Neither flag class is fixable by a stricter automated rule without hand curating thousands of advisories, which is precisely the tradeoff that separates this kind of census from a manually curated dataset. The rate is reported rather than engineered away.

Prior art, honestly positioned

Nothing here is a new measurement technique. Linking CVEs to fix commits and measuring the diff is a well established line of work, and several groups have built better curated corpora than this one.

CVEfixes (Bhandari, Naseer and Moonen, PROMISE 2021) is the closest relative. It automatically links NVD CVE records to fix commits and ships a relational database with code and metrics at repository, CVE, commit, file and method level. Release v1.0.8 covers 12,107 vulnerability fixing commits across 4,249 projects, 11,873 CVEs and 272 CWE types, to a cutoff of 23 July 2024. It is larger than this dataset and richer per record. What it does not publish is a weakness family comparison of patch size with a repository matched control.

Patchworking (Canfora, Di Sorbo, Forootani, Martinez and Visaggio, Information and Software Technology 142, 2021) asks a closely related question: do vulnerabilities of the same type get resolved by similar code transformations? It examines 524 patches across ten weakness categories in 98 Java open source projects and finds that same type vulnerabilities produce similar impact on the codebase. It is the most direct precedent for this study, and it is much smaller, single language, and does not control for repository.

Open or Sneaky? Fast or Slow? Light or Heavy? (Imtiaz, Khanom and Williams, 2021) studies 4,377 security advisories across seven package ecosystems and reports that the median security release contains 134 lines of code change. That is a release level figure rather than a per advisory fix commit figure, and it is not split by weakness family, but it is the best available sanity anchor: a per commit median of 19 to 64 lines sitting well below a per release median of 134 is the right shape.

SAP project-kb contributes a manually curated set of 1,297 vulnerability statements mapping CVEs to fix commits, and Big-Vul covers 3,754 C and C++ vulnerabilities across 348 projects. Both are inputs to the field rather than answers to this question.

What this study adds: a family level median and interquartile range for access control against injection against XSS, computed on the GitHub Advisory Database rather than NVD, with a strict one commit attribution rule, a repository matched control, a code only robustness split, and the complete row level data plus the script published so the numbers can be checked rather than cited. Anyone with CVEfixes could compute a version of the pooled medians. As far as we can find, the matched control and the code only split have not been published for this comparison.

Limitations

Patch size is a proxy, not a measure of difficulty. A fix's diff is the last and cheapest step in a repair. Locating a broken authorization check across a large permission model can take days and produce four lines of code. Nothing here supports a claim about how hard any weakness class is to find, to reason about, or to get right, and no such claim is made.

The corpus is open source packages. The GitHub Advisory Database covers twelve package ecosystems. Bespoke applications, internal services and closed source products are absent, and their authorization models tend to be less library shaped and more bespoke than a published package's.

Requiring exactly one fix commit is a real selection filter. It removed 1,052 advisories fixed across multiple commits, which are plausibly the more involved repairs, in every family. The measured population is therefore skewed towards simpler fixes across the board. The direction of that bias across families is unknown.

The access control sample is concentrated in one package. 12.3 percent of it comes from a single npm project in a single four month disclosure burst, and removing that project moves the raw family gap from 25 lines to 17. Advisories are not independent observations, so every pooled significance test here treats clustered data as unclustered and its p values are optimistic. The repository matched paired analysis is the part of the study that does not have this problem, which is another reason to weight it heavily.

Family membership is inherited. Advisory CWE mappings are assigned upstream and the validation pass found them disagreeing with the advisory's own description in 4 of 50 records, all in the injection family.

A fix commit can contain more than the fix. Eight percent of sampled records showed exactly that, and there is no automated way to subtract the unrelated part.

Repository matching is not causal identification. It controls for the repository, which absorbs language, size, age and review culture together. It does not isolate any one of them, and it cannot rule out that access control advisories are systematically reported in the more complex modules of the same codebase.

These figures describe published advisories, not any tester's findings. They are not a benchmark for what a penetration test surfaces, and they should not be used as one.

What this means for defenders

Do not let "small fix" become a proxy for "small risk" in triage. The median authorization fix is 25 lines of code, and the median injection fix is 23. If your remediation queue implicitly deprioritises broken access control because those tickets look cheap, the data offers no support for the assumption that they are cheaper, and OWASP still ranks Broken Access Control first by volume. Sort by blast radius, not by expected diff size.

Budget test effort, not just fix effort, for authorization work. Three in five authorization fix commits in this corpus touch a test, doc or changelog file, and the median commit puts 37.5 percent of its lines outside application code. When you scope a remediation sprint after an assessment, the authorization findings are the ones most likely to need a regression test written before anyone will merge the change. That is a good habit; plan for it rather than discovering it at review time.

Regression coverage is the durable artefact. An authorization fix that lands without a test asserting the denied case is one refactor away from reappearing. Wiring findings into the development workflow so the check ships with the fix is what stops a repeat, and it is worth doing deliberately: see our note on integrating pentest findings into the developer workflow.

Interquartile ranges this wide mean per finding estimates, not class level estimates. Half of all authorization fixes in this corpus land between 19 and 173 changed lines. Any remediation plan that prices "the IDOR bucket" as a single line item will be wrong in both directions. Price each finding against the code path it touches.

Finding the bug is still the expensive part, and scanners are the wrong tool for it. Every number here starts after somebody discovered the vulnerability. Object level and function level authorization flaws are precisely the class an automated scanner walks past, because ownership and tenancy are facts about your data model rather than facts visible in an HTTP response. That gap is worth understanding in detail, and we have written it up in why automated API scanners miss BOLA and IDOR and in how a hidden JavaScript action header can bypass your access controls. Authorization boundaries in GraphQL and in mobile deep links fail in their own specific ways, covered in GraphQL API vulnerabilities and common attacks and full account takeover via deeplinks.

Reproduce it

The full pipeline, the row level dataset and the exclusion log are published under CC BY 4.0:

Artefact

URL

Analysis script

analyze.mjs

Row level results, one row per measured advisory

results.csv

Full summary, every statistic and every rule

results.json

Every dropped advisory with its exclusion rule

excluded.csv

The 50 record validation sample and verdicts

validation-review.csv

Codejavascript
1# Node 18 or newer, plus an authenticated gh CLI or a GITHUB_TOKEN.
2node analyze.mjs --out ./out --cache ./.cache

The full run is roughly 4,600 authenticated REST calls and checkpoints after every census page and every commit, so a rate limit pause never loses completed work. Attribute reuse to "Stingrai, Authorization Fix Patch Size dataset, 2026". The upstream GitHub Advisory Database is itself CC BY 4.0.

Reruns after 28 July 2026 will produce a larger corpus and slightly different medians, because the advisory database grows daily and existing advisories can gain CWE mappings and references after publication. The snapshot date in results.json is what pins any given run.

Frequently asked questions

Are broken access control fixes bigger than injection fixes?

Pooled across the GitHub Advisory Database, yes: an access control fix commit changes a median of 64 lines against 39 for injection, a difference of 25 lines with a bootstrap 95 percent confidence interval of [16, 34]. That gap does not survive the controls. Inside the 90 repositories carrying advisories in both families, the paired median difference is zero (44 repositories up, 43 down, p = 1.00), and counting only lines in application code the medians are 25 against 23, a difference of 2 lines with a confidence interval of [-1, 7].

How big is a typical authorization vulnerability patch?

The median is 64 changed lines across 3 files, with an interquartile range of 19 to 173 lines, measured across 1,100 access control advisories in the GitHub Advisory Database as of 28 July 2026. Counting only application code and excluding tests, docs and lockfiles, the median falls to 25 lines. The spread is wide enough that "typical" is doing a lot of work in that question: half of all such fixes fall inside a nine fold range.

Why did the gap between families disappear when you controlled for repository?

Because access control advisories concentrate in large, long lived codebases where every commit is bigger. The repositories dominating the access control sample include Moodle, XWiki, Jenkins, Gitea and Dolibarr. Restricting the comparison to repositories with advisories in both families narrows the advisory level gap from 25 median lines to 18.5, and pairing one difference per repository flattens it to zero. The pooled figure was measuring the codebase as much as the weakness.

What is the difference between the pooled and repository matched analyses?

The pooled analysis compares every access control advisory against every injection advisory, regardless of which project they came from. The repository matched analysis only compares advisories within projects that have both, which holds language, codebase size, age and review culture constant. Pooled comparisons are more powerful and more confounded; matched comparisons are cleaner and lower powered. Both are reported here because they disagree.

Why is cross site scripting reported separately from injection?

XSS is formally an injection weakness, but its fixes are dominated by output encoding at a template boundary, which is a different repair shape from rewriting query or command construction. There are also more XSS advisories (1,758) than injection advisories (948) in this corpus, so folding them together would have moved the injection median by weight of numbers without saying anything about SQL or command injection. Keeping CWE-79 separate turned out to matter: it is the one family that stays distinct under every control applied.

Does a bigger patch mean a harder vulnerability to fix?

No, and this dataset cannot support that inference. A fix's diff is the last and cheapest step in a repair. Locating a missing permission check across a large authorization model can take days and produce four lines of code. Patch size measures the change that shipped, not the effort behind it, and no claim about difficulty is made here.

Why do authorization fixes carry so much non code churn?

Because they arrive with tests. A median 37.5 percent of an access control fix commit's changed lines land in files that are not application code, against 3.7 percent for injection, and 60.8 percent of access control fix commits touch at least one test, doc or changelog file. Authorization changes are behavioural and therefore easy to assert on, so maintainers assert on them. That accounts for most of the raw line count difference between the families.

Which advisories were excluded and why?

Of 4,450 candidates, 644 were excluded by a fixed ladder: 391 merge commits, 145 patches over 1,000 changed lines, 79 commits touching only tests, docs or lockfiles, 28 commits that could not be fetched, and 1 with no line changes. Before that, 97 advisories were dropped for spanning two weakness families and 6,262 for not referencing exactly one GitHub fix commit. Every dropped advisory and its rule is published in excluded.csv.

Does one package skew the access control results?

Yes, and the sensitivity analysis quantifies it. A single npm package, openclaw/openclaw, supplies 135 of the 1,100 access control advisories, 12.3 percent, published in one four month burst between February and May 2026. Removing all 175 of its advisories from every family drops the access control median from 64 to 53 changed lines and the injection median from 39 to 36, narrowing the raw gap from 25 lines to 17 and the code only gap from 2 lines to 1.5 with p moving from 0.11 to 0.37. The paired per repository result is unchanged at a median difference of 0. The concentration reinforces the study's conclusion that the pooled gap is a composition artefact rather than a property of the weakness class.

How accurate is the dataset?

Fifty randomly sampled records were reviewed by hand. Arithmetic was correct in all 50, and a full census check confirmed the per file additions and deletions sum exactly to the reported totals in all 3,806 measured records. Four records (8 percent) carried a family label inherited from the advisory's own CWE mapping that disagreed with the weakness the advisory described, all of them in the injection family. Four records (8 percent) referenced a commit that did more than fix the advisory. Sixteen percent of records carried at least one flag.

Where can I get the underlying data?

Everything is published under CC BY 4.0 at stingrai.io/data/authorization-patch-size: the row level results.csv, the full results.json summary, the excluded.csv log, the validation-review.csv verdicts and the runnable analyze.mjs script. Attribute reuse to "Stingrai, Authorization Fix Patch Size dataset, 2026".

References

  1. GitHub. GitHub Advisory Database. Snapshot 28 July 2026. https://github.com/advisories. Reviewed advisories across twelve package ecosystems, validated and mapped to affected packages. Licensed CC BY 4.0.

  2. GitHub. REST API: global security advisories. https://docs.github.com/en/rest/security-advisories/global-advisories. The GET /advisories endpoint used for the census, including the type and cwes filters and cursor pagination.

  3. GitHub. About the GitHub Advisory Database. https://docs.github.com/en/code-security/security-advisories/working-with-global-security-advisories-from-the-github-advisory-database/about-the-github-advisory-database. Definition of reviewed versus unreviewed advisories and the supported ecosystems.

  4. Bhandari, G. P., Naseer, A. and Moonen, L. CVEfixes: Automated Collection of Vulnerabilities and Their Fixes from Open-Source Software. PROMISE 2021. https://arxiv.org/abs/2107.08760. NVD anchored dataset linking CVEs to fix commits with code and metrics at five levels of abstraction; release v1.0.8 covers 12,107 fixing commits to 23 July 2024.

  5. Canfora, G., Di Sorbo, A., Forootani, S., Martinez, M. and Visaggio, C. A. Patchworking: Exploring the code changes induced by vulnerability fixing activities. Information and Software Technology, volume 142, 2021. https://doi.org/10.1016/j.infsof.2021.106745. Analyses 524 patches across ten weakness categories in 98 Java open source projects.

  6. Imtiaz, N., Khanom, A. and Williams, L. Open or Sneaky? Fast or Slow? Light or Heavy?: Investigating Security Releases of Open Source Packages. 2021. https://arxiv.org/abs/2112.06804. Studies 4,377 advisories across seven ecosystems; reports a median security release of 134 lines of code change.

  7. SAP. project-kb. https://github.com/SAP/project-kb. Manually curated vulnerability statements mapping CVEs to fix commits in industry relevant open source projects.

  8. OWASP. A01:2025 Broken Access Control. https://owasp.org/Top10/2025/A01_2025-Broken_Access_Control/. First place in the OWASP Top 10 2025, 40 mapped CWEs, average incidence rate 3.74 percent, 1,839,701 recorded occurrences.

  9. OWASP. A05:2025 Injection. https://owasp.org/Top10/2025/A05_2025-Injection/. Fifth place in the OWASP Top 10 2025, down from third, 37 mapped CWEs and 1,404,249 recorded occurrences.

  10. MITRE. CWE-862: Missing Authorization. https://cwe.mitre.org/data/definitions/862.html. One of the six weakness identifiers in the access control family used here.

  11. MITRE. CWE-639: Authorization Bypass Through User-Controlled Key. https://cwe.mitre.org/data/definitions/639.html. The CWE that carries most IDOR advisories.

  12. MITRE. CWE-89: Improper Neutralization of Special Elements used in an SQL Command. https://cwe.mitre.org/data/definitions/89.html. One of the four weakness identifiers in the injection family used here.

  13. MITRE. CWE-79: Improper Neutralization of Input During Web Page Generation. https://cwe.mitre.org/data/definitions/79.html. Reported as its own family rather than folded into injection.

  14. Stingrai. Authorization Fix Patch Size dataset. 28 July 2026. https://www.stingrai.io/data/authorization-patch-size/results.csv. Row level results, full summary, exclusion log, validation verdicts and the runnable analysis script, published CC BY 4.0.

Testing what the numbers cannot

This study measured the diff. The expensive part happens before the diff exists: proving that account B can read account A's object, that a lower privilege role reaches a privileged function, that a tenant boundary leaks in one direction and not the other. Those are facts about your data model, and no scanner holds them.

Stingrai is a CREST accredited penetration testing service provider, and authorization is where our web application penetration testing work concentrates. Snipe, our autonomous web application pentest agent, is built specifically to hunt the classes this post is about: IDOR, business logic flaws and broken authorization. It runs black box and white box, opens AutoFix pull requests for what it finds, and can gate pull requests so a broken check does not merge in the first place. For continuous coverage across releases see our PTaaS platform, for full scope adversary emulation see red teaming, and for the current engagement packages see pricing or the full services list.

0 views

0

X

Related reading

Who Actually Bans AI-Written Bug Reports: A Census of Disclosure Program Policies
AdvisoriesWeb App Security

Who Actually Bans AI-Written Bug Reports: A Census of Disclosure Program Policies

We coded 53 published bug bounty and disclosure policies on AI written reports. Zero ban them outright, 36 of 53 say nothing, 13 attach a condition.

16 min read

Curl Killed Its Bug Bounty Over AI Slop: How to Triage Real AI Findings From Noise
AdvisoriesWeb App Security

Curl Killed Its Bug Bounty Over AI Slop: How to Triage Real AI Findings From Noise

Curl ended its bug bounty after AI slop pushed confirmed vulnerabilities below 5 percent of reports. Here is how to triage real AI findings from noise.

11 min read

Autonomous or Human Pentesting: How to Split Your Attack Surface by Performer
AdvisoriesWeb App Security

Autonomous or Human Pentesting: How to Split Your Attack Surface by Performer

Route each asset to an autonomous AI pentest agent or a human pentester. A hybrid scoping playbook with a routing table and four decision criteria.

9 min read

Contents

X