Findings from the first collection

What the collected data shows, and what it means for using it. These are observations from the first full collection of 14 repositories, run through every query in this directory. They are a starting point for analysis, not results: the paragraph below says how firm they are.

Collection.

  • Database aveloxis_large, collected with aveloxis-cve 0.4.32 to 0.4.35 on 2026-11-10.

  • Every query in this directory was run on it, as written in aveloxis-cve 0.4.38, on 2026-11-10.

  • 14 repositories: 13 Apache projects (camel, activemq, cassandra, hive, maven, tomcat, logging-log4j2, spark, hbase, flink, nifi, groovy, superset) and Jenkins.

  • Labor windows of 60 days, advanced monthly, measured with scc 4.1.0.

How firm these are. The numbers below come from the queries as they now stand. The first run used earlier path rules for CVE-16/17 and LAB-13/14. Running them again changed the CVE-16/17 classes of four repositories:

  • 37 of Cassandra's CVEs, all touching its scanner-suppression lists, moved from docs or config to dependency or build;

  • 5 of HBase's CVEs, and 2 of Hive's, moved from code to dependency or build;

  • Spark's code class grew from 23 to 26, its dependency class fell from 153 to 151, and its docs class from 11 to 10.

The other repositories' classes did not change. The generated-code figures from LAB-13/14 below are from the rerun. The data-quality tables, the delay table (CVE-06/07), the trend table and the Aveloxis comparison below use no path rule, and the rerun did not change them.

The rerun's output (from the repository's run.py, into analysis_results.txt, which is not committed) keeps only the first 80 lines of each query's result: 78 rows plus the two header lines. The limit is run.py's MAX_LINES, not the queries'. Where a section depends on a cut-off result, it says so.

The data is complete and consistent

All 14 repositories synced and measured with no recorded error (DQ-01):

  • Every repository has exactly one default-branch row, and complete, current membership (DQ-02).

  • Every non-merge commit has a computed patch-id (DQ-05).

Each labor series is complete: 135 to 277 windows per repository, none stale, none missing (DQ-06). The series reach back to 2003 for Maven and Groovy, and start no later than 2015 (Superset).

Branch counts range from 7 live branches (Tomcat) to 670 (Superset). Only Camel (5 deleted, 2 force-pushed) and ActiveMQ (4 deleted) had branches archived during collection.

Author identity

Between 76.5% (Superset) and 99.6% (Cassandra) of commits resolve to an Aveloxis contributor (DQ-03). 13 of the 14 are above 89%.

  • One resolution step never matches. The identity step (Aveloxis's contributor_identities table) matched no commit in any repository. Possible causes: every commit its emails match is already resolved, or found ambiguous, by an earlier step (the steps run in order and the first match wins; Jenkins resolves 35,012 commits through the first), the table is empty, its emails add nothing beyond contributors' primary and alias emails, its matches all point to merged contributors or are ambiguous, or its emails are stored in a form the lookup does not match. Worth checking in Aveloxis.

  • Repositories differ in which step matches. Jenkins resolves almost entirely through the author Aveloxis attached to the same commit (35,012 commits), and hardly ever by email (230). Most Apache projects split between that step, email and alias.

  • A suspect mapping. In Camel, one author's commits resolve to an Aveloxis contributor whose login belongs to someone else, judging by the name. That looks like a wrong contributor link on the Aveloxis side. CVE-08 on Camel shows it. This document names no individuals.

  • People counted twice. A person with both resolved and unresolved commits appears as two rows (for example a resolved account and a users.noreply.github.com address). Assuming each resolved link is right (see the suspect mapping above, which can merge two people into one row), the number of people is an upper bound, and each row's counts are lower bounds on that person's totals.

Which commits name CVEs

A commit naming a CVE is rarely a fix of the project's own code. In most projects, CVE ids in commit messages come from dependency upgrades: "bump X to fix CVE-...". Fixes of a project's own vulnerabilities are usually committed without the id, often before the CVE is public.

CVE-16 and CVE-17 classify each CVE by the paths its fix touched:

Repository

CVEs named

Code

Dependency or build

Docs or config

Test

spark

187

26

151

10

0

tomcat

75

55

1

6

13

camel

54

3

21

29

1

cassandra

40

1

38

1

0

hive

31

4

27

0

0

hbase

20

2

18

0

0

superset

18

0

17

1

0

flink

16

0

16

0

0

activemq

12

0

12

0

0

log4j2

8

5

0

3

0

nifi

6

0

6

0

0

groovy

4

1

3

0

0

jenkins

4

0

4

0

0

maven

1

0

1

0

0

  • Tomcat is the exception (with log4j2, 5 of 8). 55 of Tomcat's 75 named CVEs were fixed in code, and its commits name the CVE. These may be Tomcat's own CVEs (Tomcat publishes them in its changelog), but the class only says the fix touched code. Checking the ids against Tomcat's security pages would confirm it.

  • 27 of Camel's 29 "docs or config" CVEs are entries in Camel's security model page, which catalogues CVEs inside the repository (docs/user-manual/modules/ROOT/pages/security-model.adoc, touched for 28 CVEs including one code CVE, and its attachments/security-model.yaml, 26). The other 2 are signed advisory files under docs/user-manual/en/security-advisories/. Its dependency changes mostly touch parent/pom.xml (22 CVEs: 20 of its 21 dependency CVEs, and 2 classed code because they also touched code) and camel-dependencies/pom.xml (7). Cassandra's 38 "dependency or build" CVEs are mostly entries in its dependency-scanner suppression lists. 37 of them touch at least one of its two lists under .build/ (35 the main dependency-check-suppressions.xml, 4 the one under owasp/, 2 of them both). Only the 38th, and one code CVE, touch build.xml. They record known CVEs as not affecting Cassandra; they are not fixes or upgrades. CVE-2021-44521 is Cassandra's own CVE, but its named commits touch only CHANGES.txt, NEWS.txt and the suppression list, with no code. At most they record a fix made in other commits; they do not make it. The rules class .build/ as dependency or build, so read Cassandra's dependency class as suppressions.

  • Dependency or build changes dominate Spark (151 of 187), Superset (17 of 18), HBase (18 of 20), Hive (27 of 31) and Groovy (3 of 4). They account for all of Flink's, ActiveMQ's, NiFi's, Jenkins' and Maven's named CVEs. The class says which files a fix touched, not that it was an upgrade: Cassandra's are mostly suppression entries, and the others have not been checked one by one.

  • HBase's four Bootstrap CVEs (CVE-2018-14040, -14041 and -14042, and CVE-2019-8331) changed only dependency or build files, 6 each. Between them they have 120 of HBase's 304 backports, but they are probably one change: each has the same single change of 6 files and 18/20 lines (CVE-09), backported to 30 branches and counted once per CVE.

  • GHSA-only commits: 31 commits in Spark name a GitHub advisory without a CVE id, plus 5 in Groovy, 3 each in Camel and HBase, 2 in Superset, and 1 each in Hive and NiFi (CVE-14). The views ignore them.

What it means for research. The question "how do projects fix their own vulnerabilities" cannot be answered from commit messages alone. Options:

  • Restrict to CVE-16's code class. This removes most dependency noise, but a dependency upgrade that also changes code still counts as code: HBase's CVE-2020-13949 is a Thrift upgrade, most of whose files are regenerated sources. It also keeps a project's fix of a dependency CVE in its own code, and misses unnamed fixes.

  • Bring in an advisory source that lists, for each CVE, the affected product and its fix commits (NVD references, OSV, GitHub advisories). Matched by commit hash against commits, it would identify the project's own fixes, named or not. aveloxis-cve has no such source yet. On the dependency side, Aveloxis records its dependency vulnerability findings in aveloxis_data.repo_deps_vulnerabilities (with CVE ids, aliases and references). Its aveloxis_scan schema holds ScanCode license and file scans, not vulnerabilities.

How fixes reach branches

From CVE-06 and CVE-07, every repository:

Repository

CVEs on default

CVEs backported

Backports

Median delay (days)

90th percentile (days)

Longest (days)

tomcat

74

74

151

0.0

0.0

2,560.1

cassandra

40

39

133

0.0

0.0

0.0

hbase

20

18

304

0.2

9.7

11.7

spark

176

50

98

1.1

48.3

296.4

camel

44

11

32

0.1

109.6

113.1

superset

16

9

23

23.0

23.0

23.0

flink

16

4

9

4.7

47.0

72.1

hive

31

3

11

-3.9

497.8

1,054.6

groovy

4

3

5

0.6

118.0

196.2

activemq

3

3

6

0.0

0.0

0.0

log4j2

7

2

11

-0.1

4.8

4.8

nifi

5

2

28

0.0

0.0

0.0

jenkins

3

1

1

8.5

8.5

8.5

maven

1

0

0

  • Backports are mostly same-day. Tomcat commits a fix to every maintained branch at once, so its median delay is 0. Cassandra's 0-day "backports" are mostly its scanner suppressions merged forward through its release branches, not fixes. HBase has the most backports (304 for 18 CVEs), across many release lines. 268 of them are dependency or build changes.

  • Negative delays (Hive's median of -3.9 days) mean a maintenance branch got the fix before the default branch. This is consistent with fixes prepared for a release and merged forward later.

  • Long tails exist. Hive's 90th percentile is about 16 months, and Camel's and Groovy's top delays are over 100 days. Tomcat's median is 0 but its longest backport is 2,560 days (7 years): a fix that reached a very old branch much later.

  • Reverted fixes are rare. Only one CVE fix was later reverted: Spark's for CVE-2022-24765, on 8 branches, not fixed again (CVE-12). CVE-12 changed in 0.4.41 only for a fix and an undo that landed at the same time; Spark's landed at 11:44:59 and 22:45:58 on every branch, so its result stands.

  • Few backports are fixes of code. This comes from joining CVE-06 with CVE-16's classes. Spark's CVE-16 result is cut off at 78 rows, so 24 of its backported CVEs are not classified in the output. Its CVE-06 result is cut off too, but it sorts by backports, so all 50 of Spark's backported CVEs appear in it (98 backports, as in CVE-07). All of Spark's code CVEs are listed before the cut, so those 24 are dependency or docs changes:

    Class

    Backported CVEs

    Backports

    code

    70 (Tomcat 55)

    176 (Tomcat 99, HBase 36)

    dependency or build

    105 (37 of them Cassandra's suppression-list CVEs)

    527 (HBase 268, Cassandra 126)

    docs or config

    7

    14

    test only

    13 (Tomcat)

    41

    Spark, not classified in the output

    24

    54

    A backport here is a (CVE, branch name) pair, as CVE-06 and CVE-07 count them, so a change naming several CVEs counts once per CVE. Of the 812 backports, only 176 (22%) are in the code class, and that is an upper bound on fixes of code. 27 of HBase's 36 code backports are CVE-2020-13949, a Thrift upgrade. 55 of its 63 files are Thrift-generated sources under .../generated/, which CVE-16 counts as code. It also changes hand-written code (ThriftServer.java, ThriftConnection.java, the example DemoClient.java), so how much of it is a fix of code is unclear. At least 527 (65%) are in the dependency or build class. 126 of those are Cassandra's suppression-list changes, none of which change code (CVE-2021-44521's 3 at most record Cassandra's own fix, see above). None of the 126 is a fix or an upgrade. Without them, at least 401 are dependency or build changes: 49% of all 812, and 58% of the 686 that are not suppressions. Filter by class before reading a delay as a backport of a fix.

Complexity over time

Complexity grows in every repository, mostly at a near-constant rate: r-squared is 0.52 to 0.97 (LAB-03), and 0.86 or more in 8 of 14. The two lowest fits have large single-step falls: HBase lost 95,491 in one month (70% of its current total) and Jenkins 13,320 (56%). Those look like modules or generated code removed or moved out, and break a linear trend; read their slopes with that in mind.

Repository

First window

Complexity then

Now

Per year

r-squared

hive

2008-11

17,143

389,849

24,656

0.96

spark

2010-05

3,403

267,883

14,864

0.97

flink

2013-06

19,065

149,662

9,797

0.91

camel

2007-05

2,299

240,823

7,404

0.93

hbase

2007-06

1,965

135,945

7,120

0.63

nifi

2015-02

27,071

102,596

5,588

0.74

superset

2015-08

2,696

108,163

4,773

0.75

cassandra

2009-05

6,876

100,141

3,877

0.86

groovy

2003-10

2,199

81,244

2,551

0.94

activemq

2006-02

13,820

59,413

2,010

0.86

tomcat

2006-05

29,175

73,255

1,587

0.93

jenkins

2007-01

4,499

23,909

964

0.52

log4j2

2012-04

3,768

19,954

862

0.78

maven

2003-10

0

26,214

598

0.66

Camel's complexity rose in 212 of its 233 monthly steps and fell in 17. LAB-12 shows which files each step's change came from.

Generated and vendored code inflates the totals

The most complex files are often not hand-written (LAB-14):

  • Hive: its five most complex files are Thrift output under standalone-metastore/.../src/gen/thrift/. ThriftHiveMetastore.java alone carries 56,303 of Hive's 389,849 complexity (14%), and the Thrift C++ and Python copies add more.

  • HBase: at least 16.9% of its whole complexity (16.98%, from the 50 most complex files only) is in Thrift-generated files under hbase-thrift/.../generated/. At least another 10.6% is in the Thrift example output under hbase-examples/ (gen-cpp/, gen-php/, gen-perl/, gen_py/). Its top 50 files together hold 37.6%.

  • Camel: its single most complex file is docs/.pnp.cjs, a generated Yarn loader, followed by generated HL7 converter loaders.

  • Spark: its most complex file is a minified JavaScript library, dagre-d3.min.js.

LAB-13 now catches these by path, including HBase's gen_py/ (underscore). Path patterns cannot see vendored or generated code that lives in ordinary source directories:

  • ActiveMQ's most complex files are two copies of the Prototype JavaScript library, under webapp/js/ and src/main/resources/.

  • log4j2's is a vendored copy of picocli (core/tools/picocli/CommandLine.java).

  • Flink's include copies of Apache Calcite (src/main/java/org/apache/calcite/...) and the AWS SDK (com/amazonaws/...) in its own modules, patched versions of upstream classes.

  • Tomcat's include its JavaCC-generated EL parser (java/org/apache/el/parser/ELParser.java).

Two ways to catch these:

  • a check that a Java or Scala file's package belongs to the project, which would flag org/apache/calcite inside Flink;

  • a scan of file headers for generator markers ("Generated By:JavaCC", "DO NOT EDIT").

Both need data aveloxis-cve does not store yet (packages or file headers). Until then, add project-specific paths to LAB-13's generated list.

Agreement with Aveloxis

DQ-09 compares each repository's latest window with Aveloxis's own scc snapshot (repo_labor). Two things differ between them:

  • The commit. The window's end predates Aveloxis's analysis by up to 29 days (ActiveMQ), and its end commit can be older still: it is the newest commit at or before the window's end. Jenkins' window ends after Aveloxis's analysis, on the same day.

  • The scc version. According to the operator, Aveloxis on the server ran scc 3.7.0, while aveloxis-cve measured with 4.1.0. Between versions, scc may score a language's complexity differently, and may recognise different files as code (scc counts only files in languages it knows). aveloxis-cve's emulated walk (which files reach scc) matches both versions on its test cases, which cover the walk's ignore rules, not language detection. repo_labor records no scc version.

Repository

aveloxis-cve

Aveloxis

Gap

Identical files

Days apart

spark

267,883

160,813

+66.6%

62%

5

groovy

81,244

97,375

-16.6%

76%

12

superset

108,163

99,441

+8.8%

87%

2

camel

240,823

261,547

-7.9%

80%

22

tomcat

73,255

76,874

-4.7%

86%

14

jenkins

23,909

23,202

+3.0%

99%

0

nifi

102,596

100,006

+2.6%

98%

3

activemq

59,413

58,256

+2.0%

97%

29

flink

149,662

147,021

+1.8%

94%

24

hive

389,849

396,308

-1.6%

83%

8

log4j2

19,954

19,631

+1.6%

99%

2

maven

26,214

25,879

+1.3%

99.6%

2

hbase

135,945

135,054

+0.7%

98%

7

cassandra

100,141

99,946

+0.2%

99%

1

  • 9 of 14 agree within about 3%.

  • The other five differ by more than their commit gaps explain. Spark is two-thirds higher with only 62% of files identical, 5 days apart. Groovy is 17% lower (12 days). Superset is 9% higher (2 days). Camel and Tomcat are 5-8% lower (22 and 14 days). Where aveloxis-cve counts markedly fewer files and less complexity (Camel 38,449 against 40,198, Tomcat 4,607 against 4,841, Groovy 5,647 against 5,781), part of the gap may be files the two scc versions recognise differently. The version difference is the likely cause overall, in line with the operator's report. Which languages' scoring or detection changed between scc 3.7 and 4.1 has not been checked.

  • Jenkins has equal file counts (11,204) and 99% identical files, but its window ends at 2026-11-04 21:16, after Aveloxis's analysis at 05:49 that day, so the two may have measured different commits. Its 3% gap may be that day's commits, the scc version, or both.

  • Within aveloxis-cve the measurement is consistent. Every window of every series was measured with scc 4.1.0 (DQ-06), so trends are not affected. Comparisons with Aveloxis's numbers are, until both use one scc version. Running labor --recompute after installing scc 3.7.0, or Aveloxis with 4.1.0, would make them comparable; then compare a window whose end commit matches Aveloxis's.

Open questions

  • Why does the identity resolution step never match? (Aveloxis data.)

  • Which of Tomcat's 55 code-class CVEs are Tomcat's own? (Needs an advisory source.)

  • Does one scc version close the gaps with Aveloxis's repo_labor for Spark, Groovy, Superset, Camel and Tomcat? (Measure both with the same version, at one commit.)

  • How much complexity sits in vendored and generated code in ordinary source directories? (Needs package or header data.)

  • Why do 1,808 commits stay pending (DQ-04)? The saved output keeps only the first 78 rows, all Camel's:

    • 43 have pending cherry-pick references. 11 of them name one abbreviated SHA that is already linked, so they stay pending only because the SHA is abbreviated. 8 more have some cherry-pick links but still have unlinked references.

    • 35 have pending revert references: 13 are Revert commits, 2 are SVN-era "Blocked revisions" lists, and 20 are other commits.

    The full set has not been examined. Do any of them matter for CVE propagation?

  • What made HBase's 95,491 and Jenkins' 13,320 single-month complexity drops? (LAB-12 for those steps.)