This report accompanies the ChimangoScan artifact for the paper
"Vulnerabilities, Secrets and Misconfiguration in the Highest-Exposure Docker
Hub Images". The committed reference analysis/expected/paper_values.json
lists every reproducible number the camera-ready paper asserts (240 checks:
every cell of the pull-distribution, dataset, severity, per-scanner,
misconfiguration, reach, propagation, top-package and reproduction tables, plus
the headline numbers in the abstract, introduction and results text), each with
its source locator in the paper.
To verify a recomputation, point the verifier at the directory holding the
recomputed artifacts (table_values.json, crawl_stats.json,
graph_stats.json, propagation_v3.json, secret_validation_report.json;
each is optional, so a single stage can be verified in isolation):
python3 analysis/scripts/verify_values.py --results artifacts/analysis \
[--timestamp "$(date -u +%FT%TZ)"]
Comparison is exact after normalization (thousands separators and LaTeX markup stripped; recomputed values formatted at the paper's printed precision). The verifier prints one PASS/FAIL/SKIP line per check, rewrites the "Verification results" section below in place, and exits non-zero iff any check FAILs.
-
Severity table, Low "% of findings" (paper typo, fixed). Earlier camera-ready drafts printed 13.8% for the Low row, but the table's own cells give 19,418,002 / 141,683,960 = 13.7%. The typo was found by this verification and corrected in the camera-ready;
paper_values.jsoncarries the corrected 13.7. Every other percentage in the paper is consistent with its underlying counts. -
Pull-distribution table had no committed script. The camera-ready's Table 2 (pull-count buckets) was originally computed with ad-hoc queries; the MongoDB analysis stage now ships
analysis/scripts/crawl_stats.py, which recomputes every bucket, the crawl totals and the pull-count median/p99/max server-side from the Stage I database.Snapshot verification. Running that script against the frozen Stage I MongoDB confirms the two anchor counts of the paper exactly: 12,716,568 repositories and 2,051,801 prefix queries (
crawler_keywords). The pull-count buckets of Table 2 and the Section 3.1 pull statistics are all over the full crawl:crawl_stats.pyrecomputes the buckets, and the median (62), 99th percentile (20,781) and maximum (2.4e10) directly from the raw MongoDB, with no sampling. (An earlier draft reported median 198 / p99 160,061 here; those were percentiles of the exposure-ranked resolved head, a different and smaller population, mistakenly presented as full-crawl figures. The camera-ready now reports the full-crawl values, consistent with Table 2.) The Stage I crawl is a LIVE crawl that kept running after the paper's numbers were frozen, and the MongoDB was not snapshotted at that exact moment, so every available copy is marginally later: the released dump holds 12,788,247 repositories and 2,053,459 prefix queries against the paper's 12,716,568 and 2,051,801 (a ~0.6% drift), and the pull-count field likewise kept updating (buckets within ~0.5%, e.g.=1B: 114 vs 113; median 61 vs 62). All crawl-derived checks therefore verify within a 2% tolerance (reported DRIFT, not FAIL); the qualitative distribution is unchanged. By contrast the scan-reports SQLite database WAS frozen, so every scan/results number (52,895 images and all of Section 4) reproduces exactly. (An earlier note here claimed the frozen dir matched the paper anchors exactly; that was
estimatedDocumentCountreading stale metadata after an unclean shutdown, which coincidentally matched. The actualcount_documentsis 12,788,247.) The Neo4j layer graph is the same kind of live-pipeline snapshot: reproducinggraph_stats.jsonon the released dump gives 56,160,876IS_BASE_OFedges and 87,485,665 nodes against the paper's 54,382,383 and 84.7 M (+3.3%), sograph.*checks also verify within the 5% tolerance (DRIFT). Only the scan-reports SQLite database was frozen; it reproduces exactly.MongoDB version. The released Stage I dump was written by MongoDB 8.x;
mongo:7refuses to open it (exitCode 62, invalid featureCompatibilityVersion 8.2). The stage driver now defaultsMONGO_IMAGEtomongo:8.last_updated coverage. The paper's Table 3 "last_updated coverage 95.7%" is not a property of
repositories_data(whoselast_updatedfield is an empty string for every document) nor oftags_data(100% populated); its exact population is undetermined, socrawl_stats.pydoes not currently reproduce that single cell. -
Uncommitted input generators, now added. The generators for
plan_crawl.json(crawl-wide pull CDF),tags_full.jsonland the top-60k corpus filter were never committed with the original analysis; the artifact now includesexport_plan_crawl.py,export_tags.pyandmake_corpus_filter.pyso these seed inputs are reproducible from the raw databases instead of being opaque files. -
fig_repro_panel.pdfwas not wired into the figure orchestrator. The figure existed in the paper repository butregenerate_all.pydid not rebuild it; it is now wired into the figures stage, so a full regeneration produces all figures the paper embeds. -
Shipped extracts predate the Dockle-misconfig keying fix. Commit
a5655eefixed how misconfiguration findings are keyed by Dockle check id. Analysis extracts produced before that fix (including the staletable_values.jsonkept in the private paper repository) carryMC*= 0 and lack theMCEMPTYPW/MCSUDOtokens; the paper's Table 8 numbers come from the fixed recomputation. Verifying a pre-fixtable_values.jsontherefore FAILs exactly the misconfiguration checks — by design. -
Propagation method: union, not sum. Table 7 uses the union method (
propagation_compute.py→propagation_v3.json): the distinct downstream set of all images carrying a CVE, so shared descendants are not double-counted. OlderPROP*tokens inapply_repo_numbers.pyencode a superseded per-image-sum variant; the verifier checks Table 7 against the union numbers inpropagation_v3.jsonand ignores thePROP*tokens. -
Secret ground-truth labels are committed, not re-elicited. The manual verdicts of the 1,100-detection sample live as
MANUAL_TPinvalidate_secrets.py(the review worksheet's MANUAL column was left empty), so a reproduction reuses the committed labels rather than re-labeling by hand. Re-labeling would require a human pass over the sample. -
The committed secret sample is authoritative.
secret_sample.pyis seeded (random.seed(42)) but its reservoir sampling depends on database row order, which is not guaranteed to be stable across dumps/restores. The committedsecret_sample.jsonis therefore the authoritative sample; the validation report is deterministic given that file. -
Dataset-size statements disagree. The README has referred to the dataset as 146 GB, ~179 GB and 283 GB in different places; the paper's Table 3 reports 283 GB total (192 GB reports + 24.9 GB MongoDB + 65.9 GB Neo4j). The discrepancy is a documentation artifact (compressed vs. uncompressed, and whether the layer graph is included) and does not affect any measurement. Storage sizes are environment-dependent and are not machine-verified.
-
Zenodo DOI not yet minted. The dataset release references a Zenodo deposit whose DOI has not been minted yet; until then the download locations in
DATASET.mdare provisional.
Not machine-checkable. Fourteen paper numbers have no recomputable artifact in this release and are reported SKIP by the verifier: the Stage II resolution progress (5,601,045 repositories; 44.05%) and scan-queue size (6.7 M), which describe the dataset freeze rather than a recomputation; the scanned-corpus pull-coverage share (84.7%), which needs the exposure ranking join; the scan wall-times (median 117 s, mean 197 s, max 9,433 s; Grype 35.1 s/image), which are recomputable only from the raw timing lists in the scan database; the campaign constants (6 scanners, 13 workers); and the storage sizes (192 / 24.9 / 65.9 / 283 GB).
No verification run recorded yet. Run
python3 analysis/scripts/verify_values.py --results <DIR> to populate this
section.