Skip to content

Add named class (build-stage) extraction for dockerfile - #1988

Merged
squid-protocol merged 5 commits into
mainfrom
fix/dockerfile-class-start-1974
Aug 21, 2026
Merged

Add named class (build-stage) extraction for dockerfile#1988
squid-protocol merged 5 commits into
mainfrom
fix/dockerfile-class-start-1974

Conversation

@squid-protocol

Copy link
Copy Markdown
Owner

Summary

Dockerfile had no named class (build-stage) extraction at all: it wasn't in _CLASS_START_NAMED_EXTRACTION_LANGS, so it fell back to the legacy generic regex (class|struct|interface|trait|enum), which never matches Dockerfile's FROM syntax -- correctly, since Dockerfile has none of those keywords. Result: class_data stayed completely empty for every Dockerfile scanned, even though struct_class_start (the raw signal) correctly counted every real FROM line (confirmed while manually verifying dockerfile's tri-comparison numbers for #1976: file_data.class_count reported 69 for a Dockerfile with 69 real stages, but class_data had 0 rows).

Fix

class_start's own regex previously captured only the literal keyword FROM itself, not the build stage's real name. Extended it to an alternation shape (matching the existing Fortran/Lua/ABAP convention _resolve_class_start_match's own docstring documents):

  • Group 1 captures the AS <alias> name when present.
  • Group 2 falls back to the bare base-image reference for a stage with no alias (the file's final/default stage).
  • --platform=$VAR-style flags between FROM and the image reference are skipped over.

Added dockerfile to _CLASS_START_NAMED_EXTRACTION_LANGS, and to the boundary-resolution skip condition alongside abap (same #1907 rationale -- Dockerfile stages are never nested, and the brace search would mistake ${VAR} template-substitution braces for a real body opener).

Verification

  • pytest tests/extraction/languages/test_dockerfile.py tests/extraction/languages/test_dockerfile_strict.py: 125 passed.
  • Real pipeline check on the 4 moby corpus files: class_data row counts now match struct_class_start exactly (69/6/1/1), with real stage names (base, criu, xx, binary-dummy, ...) appearing instead of the literal string FROM repeated.
  • ruff_audit.py --ci / mypy_audit.py --ci: clean against baseline.
  • ReDoS-probed the new regex directly (5000-char runs, deeply nested line-continuations, huge flag/image tokens) -- all sub-millisecond, no catastrophic backtracking.
  • crucible_check.py (full_precision + zero_dependency): drift limited to exactly 3 new Parent Entity keys (see below) -- both golden masters re-blessed; final re-run: PASS/PASS.

One known, accepted side effect (tracked separately, not blocking)

THE LINEAGE EXTRACTOR (detector.py's generic "any class_start match with 2+ capture groups treats group 2 as an inheritance parent") doesn't know groups 1/2 here are alternation-exclusive (name-OR-name), not name-then-parent -- a bare FROM <image> (no alias) sweeps the image reference into that file's parent_entity metadata. Confirmed this is a pre-existing, already-shipped pattern, not something this fix introduces: fortran's own class_start has the identical alternation shape (MODULE|INTERFACE name in group 1, bare TYPE name in group 2) and triggers the same behavior for bare TYPE declarations today, in production. Filed as its own issue (#1983) for a future, more alternation-aware fix to the shared extractor.

Implemented via a Gemini/agy dispatch from a fully diagnosed issue, independently re-verified (diff read line-by-line, tests/audits/extraction results/ReDoS probe/crucible_check.py all re-run from scratch) before this PR.

Fixes #1974.

🤖 Generated with Claude Code

Dockerfile had no named class extraction at all: it wasn't in
_CLASS_START_NAMED_EXTRACTION_LANGS, so it fell back to the legacy
generic regex (class|struct|interface|trait|enum), which never
matches Dockerfile's FROM syntax at all -- correctly, since Dockerfile
has none of those keywords. Result: class_data stayed completely
empty for every Dockerfile scanned, even though struct_class_start
(the raw signal) correctly counted every real FROM line.

class_start's own regex previously captured only the literal keyword
FROM itself, not the build stage's real name -- extended it to an
alternation shape (matching the existing Fortran/Lua/ABAP convention
_resolve_class_start_match already documents): group 1 captures the
AS <alias> name when present, group 2 falls back to the bare base-image
reference for a stage with no alias (the file's final/default stage).
--platform=$VAR-style flags between FROM and the image reference are
skipped over. Added dockerfile to _CLASS_START_NAMED_EXTRACTION_LANGS,
and to the boundary-resolution skip condition alongside abap (same
#1907 rationale -- Dockerfile stages are never nested, and the brace
search would mistake ${VAR} template-substitution braces for a real
body opener).

Verified: class_data row counts now match struct_class_start exactly
(69/6/1/1 across the moby test corpus), with real stage names (base,
criu, xx, binary-dummy, ...) instead of the literal string "FROM"
repeated. ReDoS-probed the new regex directly (5000-char runs, deeply
nested continuations, huge flag/image tokens) -- all sub-millisecond.

One known, accepted side effect: THE LINEAGE EXTRACTOR (detector.py's
generic "any class_start match with 2+ groups treats group 2 as an
inheritance parent") doesn't know groups 1/2 here are alternation-
exclusive, not name-then-parent -- a bare FROM <image> (no alias)
sweeps the image reference into that file's parent_entity metadata.
This is a pre-existing, already-shipped pattern (fortran's own
class_start has the identical alternation shape and triggers the same
behavior for bare TYPE declarations today), not something this fix
introduces -- tracked as its own issue (#1983) for a future, more
alternation-aware fix to the shared extractor, not blocking here.

Fixes #1974.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Gemini 3.1 Pro <noreply@google.com>
@squid-protocol
squid-protocol enabled auto-merge (squash) August 21, 2026 01:52
…start-1974

# Conflicts:
#	tests/golden_master_audit.json
#	tests/golden_master_zero_dep_audit.json
…start-1974

# Conflicts:
#	tests/golden_master_audit.json
#	tests/golden_master_zero_dep_audit.json
…-shift

Second conflict round: #1987 (jcl/m4) landed on main right after the
first resolution, requiring another regenerate-and-rebless pass.

Also fixes a real, separate issue caught while investigating why
PR #1985/#1987 merged despite a failing ruff-audit CI check: #1972
(record_keeper.py's class_count fix, merged earlier and unrelated to
this PR) shifted 7 pre-existing baselined findings by a couple of
lines each -- tests/ruff_audit_baseline.json was never regenerated to
absorb that shift, so every PR's ruff-audit CI run since has been
reporting those 7 as spurious 'new' findings. Confirmed via
audit_check.py that all 7 are pure line-shifts (same file/code/
message, just moved), not real regressions, and regenerated the
baseline to absorb them. Verified against the exact CI-pinned ruff
version (0.16.0, not whatever floats on PATH locally).
@squid-protocol
squid-protocol merged commit 60b98b8 into main Aug 21, 2026
26 of 27 checks passed
@squid-protocol
squid-protocol deleted the fix/dockerfile-class-start-1974 branch August 21, 2026 02:14
@github-actions

Copy link
Copy Markdown
Contributor

🐦‍⬛ Muninn Security Scan

Severity Count
🔴 Critical 0
🟠 High 0
🟡 Medium 0
🟢 Low 0
ℹ️ Info 3

ℹ️ Info Findings

[checkov] Ensure that a user for the container has been created

File: /docs/language_status/dockerfile.md:1
Rule: CKV_DOCKER_3
Ensure that a user for the container has been created

[checkov] Ensure that HEALTHCHECK instructions have been added to container images

File: /docs/language_status/dockerfile.md:1
Rule: CKV_DOCKER_2
Ensure that HEALTHCHECK instructions have been added to container images

[checkov] Ensure the base image uses a non latest version tag

File: /docs/language_status/dockerfile.md:157
Rule: CKV_DOCKER_7
Ensure the base image uses a non latest version tag

🐦‍⬛ Powered by Muninn · Skald Lab

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

dockerfile has no named class (build-stage) extraction -- class_start regex captures the FROM keyword, not the stage alias

1 participant