Add CRA Compliance Track B: OSCAL 1.1.2 three-tier traceability#1271
Conversation
Maps all 13 CRA Annex I Part I essential requirements (ECR-a–m) through prEN 40000-1-4 Security Objectives (SO.*) to dfetch controls (C-001–C-046). Covers Part II vulnerability-handling requirements via prEN 40000-1-3. New files: - security/cra_pren_4000014_oscal_catalog.json — prEN 40000-1-4 OSCAL 1.1.2 Catalog derived from the CEN/CLC/JTC 13 WG 9 deep-dive session (March 2026, Angelo D'Amato, Vulnir B.V. / STAN4CR) - security/compliance_data.py — data classes and all compliance constants - security/compliance.py — OSCAL builder and RST renderer; run with `python -m security.compliance --component ... --rst` - security/dfetch.component-definition.json — generated OSCAL Component Definition - doc/explanation/compliance_track.rst — generated human-readable RST Introduces four Track B controls: - C-043: release-gate CVE check on runtime dependencies (ECR-a) - C-044: data minimisation policy (ECR-g) - C-045: destination-path sensitivity warning (ECR-i) - C-046: exploit mitigation inventory (ECR-k) https://claude.ai/code/session_01NriXcboJTd9MQG2Mz2kmzQ
|
Warning Review limit reached
More reviews will be available in 37 minutes and 40 seconds. Learn how PR review limits work. Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file). ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (7)
WalkthroughIntroduces CRA Compliance Track B for dfetch under OSCAL 1.1.2. A new static data module defines dataclasses and compliance mapping constants (ECR-a through ECR-m, controls C-043–C-046). A CLI generator script produces both OSCAL Component Definition JSON and a human-readable RST document from that data. Two OSCAL JSON artifacts and updated documentation are committed alongside. ChangesCRA Compliance Track B
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested labels
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@security/compliance_data.py`:
- Around line 254-298: The control IDs referenced in the controls=[...] lists
within the SOImplementation entries in the compliance_data.py file (at lines
254-298, 390-399, and 531-540) do not match the authoritative control register
semantics, breaking ECR-to-SO-to-control traceability. Audit the authoritative
control register to determine the correct control IDs that should be assigned to
each SO_IMPLEMENTATIONS entry, then update all instances of the controls=[...]
parameter across all three affected sections to use the verified IDs.
Additionally, implement a generation-time consistency check (likely in the
register generation logic) that validates all referenced control IDs exist in
the authoritative register and have matching semantics, failing fast if any
mismatches are detected.
In `@security/compliance.py`:
- Around line 426-434: The C-045 security control description in the
compliance.py file contains a reference to dfetch/manifest/project.py for the
plaintext_warning() pattern implementation, but Track B control metadata
specifies the canonical location as dfetch/project/subproject.py. Update the
file path reference in the docstring from dfetch/manifest/project.py to
dfetch/project/subproject.py to maintain consistency with the official Track B
control metadata and ensure proper traceability of the planned C-045
implementation.
- Around line 459-465: The hardcoded text "Three CRA essential requirements" in
the _render_gap_analysis function does not match the actual number of gap
entries returned by _gap_entries(), which emits four controls (C-043–C-046).
Make the text data-driven by dynamically counting the entries from
_gap_entries() and using that count in the print statement, or update the
hardcoded number to four to keep the generated documentation consistent with the
actual data.
- Around line 64-71: The _load_track_a_controls function silently returns an
empty list when importing the pytm modules fails, which can produce incomplete
compliance artifacts without alerting users. Either remove the try-except block
to fail fast when the imports are required, or add an explicit degraded mode
flag that must be set to True before allowing the ImportError to be caught and
an empty list returned. This ensures that missing Track A controls are not
silently omitted from compliance output unless the caller has explicitly opted
into degraded mode operation.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 51d86c6b-eaa6-40d9-ad71-20a484dae8d8
📒 Files selected for processing (8)
CHANGELOG.rstdoc/explanation/compliance_track.rstdoc/explanation/security.rstsecurity/README.mdsecurity/compliance.pysecurity/compliance_data.pysecurity/cra_pren_4000014_oscal_catalog.jsonsecurity/dfetch.component-definition.json
- compliance.py: C-045 gap description now references dfetch/project/subproject.py (the planned implementation location) instead of dfetch/manifest/project.py (where plaintext_warning is defined) — aligns with the Track B control metadata - compliance.py: replace hardcoded "Three CRA essential requirements" with a data-driven count derived from _gap_entries(), so the generated RST always matches the actual number of gap controls listed - compliance.py: emit a stderr note when pytm is not installed and Track A controls are omitted from the control register, preventing a silently incomplete artifact https://claude.ai/code/session_01NriXcboJTd9MQG2Mz2kmzQ
DataStoredConfidentiality: .dfetch_data.yaml plaintext storage is a conscious choice — the developer workstation is a trusted boundary (as established in the usage threat model), and C-036 ensures no credentials reach disk. The threat model explicitly marks metadata_store.storesSensitiveData = False and isDestEncryptedAtRest = False. Status: implemented. DataTransmittedConfidentiality: plaintext transport (http://, git://, svn://) is accepted for legacy source compatibility. C-009 warns the user before proceeding (threat model: "Detection only — dfetch still proceeds"). C-005 provides content integrity for archives independent of transport. Status: implemented. ComAuth: HTTPS (C-003) and SSH (C-004) provide authenticity for the common cases; plain git:// / svn:// are accepted with C-009 warning under the same legacy-compatibility design decision. Status: implemented. https://claude.ai/code/session_01NriXcboJTd9MQG2Mz2kmzQ
ECR-i (so-prevent-attack-propagation): path traversal outside the project tree is prevented by C-001 (check_no_path_traversal). The residual risk of a manifest declaring a sensitive dst: (e.g. .github/workflows/) is explicitly accepted in the usage threat model under the 'Manifest under code review' assumption. C-045 is removed — the gap does not exist. ECR-m (so-secure-data-deletion): .dfetch_data.yaml contains only non-sensitive metadata (credentials stripped by C-036); standard OS file deletion is sufficient. No secure-wipe is needed. C-045 removed from TRACK_B_CONTROLS and _gap_entries(); CHANGELOG updated. https://claude.ai/code/session_01NriXcboJTd9MQG2Mz2kmzQ
Generate dfetch.component-definition.json during the build job using the installed package version, upload as a CI artifact, and attach it to the GitHub Release alongside the SBOM. https://claude.ai/code/session_01NriXcboJTd9MQG2Mz2kmzQ
Uses actions/attest-build-provenance (same pinned SHA as build.yml) to
sign dfetch.component-definition.json via Sigstore. Verifiable with:
gh attestation verify dfetch.component-definition.json \
--repo dfetch-org/dfetch \
--predicate-type https://slsa.dev/provenance/v1
No new permissions or egress endpoints needed — the build job already
has attestations: write, id-token: write, and Sigstore in the allowlist.
https://claude.ai/code/session_01NriXcboJTd9MQG2Mz2kmzQ
|
@coderabbitai review |
✅ Action performedReview finished.
|
|
@coderabbitai review |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
doc/explanation/compliance_track.rst (2)
1-1:⚠️ Potential issue | 🟠 Major | ⚡ Quick winGenerated artifacts are not pre-commit-clean, causing CI failure.
Please commit the auto-fixes from pre-commit so the branch is mergeable.
doc/explanation/compliance_track.rst#L1-L1: regenerate and commit whitespace/EOF fixes (trailing-whitespace,end-of-file-fixer).security/dfetch.component-definition.json#L1-L1: commit EOF normalization fromend-of-file-fixer.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@doc/explanation/compliance_track.rst` at line 1, The generated artifacts have pre-commit hook violations that are blocking CI. In doc/explanation/compliance_track.rst (line 1-1), regenerate the file and commit the fixes for trailing-whitespace and end-of-file-fixer violations. In security/dfetch.component-definition.json (line 1-1), commit the EOF normalization fix from the end-of-file-fixer hook. Run pre-commit hooks locally to auto-fix these issues, then stage and commit the cleaned artifacts to make the branch mergeable.Source: Pipeline failures
62-77:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winEscape wildcard
*literals to stop docutils parsing warnings.The current text includes raw wildcard patterns (e.g.,
GEC-*,.github/workflows/*.yml) that docutils parses as malformed inline emphasis, matching the pipeline warnings. Please wrap those patterns in literals (...) or escape*so generated docs are warning-free.Also applies to: 398-414
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@doc/explanation/compliance_track.rst` around lines 62 - 77, Raw wildcard patterns containing asterisks (such as GEC-*, SUM-*, AUM-*, SSM-*, SCM-*) in the RST file are being interpreted by docutils as inline emphasis markup rather than literal text, causing parsing warnings. Wrap all such wildcard patterns in double backticks (literal markup in RST) throughout the document at lines 62-77 and lines 398-414 to ensure they are rendered as literal strings without triggering emphasis parsing.Source: Pipeline failures
♻️ Duplicate comments (2)
security/compliance.py (1)
66-74:⚠️ Potential issue | 🟠 Major | ⚡ Quick winDo not treat every
ImportErroras “optional Track A unavailable.”Line 69 currently swallows all
ImportErrors and degrades to empty Track A controls. That also hides real breakages insidesecurity.tm_supply_chain/security.tm_usageimports and can silently generate incomplete artifacts.Suggested handling split
- except ImportError: + except ModuleNotFoundError as exc: + if exc.name not in {"pytm", "security.tm_supply_chain", "security.tm_usage"}: + raise print( "Note: pytm not available — Track A controls omitted from control register.", file=sys.stderr, ) return []🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@security/compliance.py` around lines 66 - 74, The current exception handling in the try-except block around the importlib.import_module calls for security.tm_supply_chain and security.tm_usage modules is too broad. It catches all ImportError exceptions equally, which masks real import failures that occur inside those modules. Instead, distinguish between ModuleNotFoundError (which indicates pytm is not installed and should gracefully return empty Track A controls) and other ImportError exceptions (which represent genuine breakages inside the modules that should be propagated to fail fast). Modify the except clause to specifically catch ModuleNotFoundError for the optional behavior, and let other ImportError types propagate as real errors.security/compliance_data.py (1)
243-250:⚠️ Potential issue | 🟠 Major | 🏗️ Heavy liftControl IDs
C-009/C-040still conflict with the published control-register semantics.Line 245 maps
C-040to update notification and Lines 280/392/533 mapC-009to plaintext-transport detection, but the generated final register (doc/explanation/compliance_track.rst, Lines 395-398 and 471-474) defines those IDs as different controls. This breaks the core ECR → SO → control traceability contract and makes the OSCAL narrative non-auditable.Also applies to: 280-286, 392-398, 531-539
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@security/compliance_data.py` around lines 243 - 250, The control ID mappings in security/compliance_data.py conflict with the authoritative published definitions in doc/explanation/compliance_track.rst, breaking traceability. Verify the correct control IDs from the published register at lines 395-398 and 471-474 of doc/explanation/compliance_track.rst, then update all four affected mapping locations in security/compliance_data.py: the controls list at line 245 (currently showing C-040 for update notification), line 280 (C-009 context), line 392 (C-009 context), and line 533 (C-009 context) to use the control IDs that match the published definitions, ensuring consistency across all four sites so that the SO-to-control mappings align with the generated compliance register.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@CHANGELOG.rst`:
- Around line 9-10: The underline for the Release 0.14.0 section heading is too
short and does not match the width of the title. Extend the underline made of
equals signs to be the same length as the title "Release 0.14.0 (released
2026-06-14)" by adding more equals signs to Line 10 until the underline width
matches the title width exactly.
In `@security/compliance_data.py`:
- Around line 317-327: The description incorrectly claims that C-005 (an
integrity control using hashing) provides "content-level confidentiality
assurance" on Line 325, but integrity hashing does not provide confidentiality
or secrecy. Correct the description in the controls section to accurately
reflect that C-005 is an integrity control that verifies content has not been
tampered with, not a confidentiality control that prevents exposure of plaintext
data. Remove the claim about confidentiality assurance from the integrity hash,
as hashing cannot provide protection against content being readable over
plaintext transport. Either reword the justification to use only accurate
control properties, or acknowledge that the plaintext transport acceptance is a
residual gap without confidentiality protection.
---
Outside diff comments:
In `@doc/explanation/compliance_track.rst`:
- Line 1: The generated artifacts have pre-commit hook violations that are
blocking CI. In doc/explanation/compliance_track.rst (line 1-1), regenerate the
file and commit the fixes for trailing-whitespace and end-of-file-fixer
violations. In security/dfetch.component-definition.json (line 1-1), commit the
EOF normalization fix from the end-of-file-fixer hook. Run pre-commit hooks
locally to auto-fix these issues, then stage and commit the cleaned artifacts to
make the branch mergeable.
- Around line 62-77: Raw wildcard patterns containing asterisks (such as GEC-*,
SUM-*, AUM-*, SSM-*, SCM-*) in the RST file are being interpreted by docutils as
inline emphasis markup rather than literal text, causing parsing warnings. Wrap
all such wildcard patterns in double backticks (literal markup in RST)
throughout the document at lines 62-77 and lines 398-414 to ensure they are
rendered as literal strings without triggering emphasis parsing.
---
Duplicate comments:
In `@security/compliance_data.py`:
- Around line 243-250: The control ID mappings in security/compliance_data.py
conflict with the authoritative published definitions in
doc/explanation/compliance_track.rst, breaking traceability. Verify the correct
control IDs from the published register at lines 395-398 and 471-474 of
doc/explanation/compliance_track.rst, then update all four affected mapping
locations in security/compliance_data.py: the controls list at line 245
(currently showing C-040 for update notification), line 280 (C-009 context),
line 392 (C-009 context), and line 533 (C-009 context) to use the control IDs
that match the published definitions, ensuring consistency across all four sites
so that the SO-to-control mappings align with the generated compliance register.
In `@security/compliance.py`:
- Around line 66-74: The current exception handling in the try-except block
around the importlib.import_module calls for security.tm_supply_chain and
security.tm_usage modules is too broad. It catches all ImportError exceptions
equally, which masks real import failures that occur inside those modules.
Instead, distinguish between ModuleNotFoundError (which indicates pytm is not
installed and should gracefully return empty Track A controls) and other
ImportError exceptions (which represent genuine breakages inside the modules
that should be propagated to fail fast). Modify the except clause to
specifically catch ModuleNotFoundError for the optional behavior, and let other
ImportError types propagate as real errors.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 8f7b8958-7e13-4397-99ea-bb6af05f16c3
📒 Files selected for processing (6)
.github/workflows/python-publish.ymlCHANGELOG.rstdoc/explanation/compliance_track.rstsecurity/compliance.pysecurity/compliance_data.pysecurity/dfetch.component-definition.json
spoorcc
left a comment
There was a problem hiding this comment.
Security Review — CRA Compliance Track B
What this does: Adds OSCAL 1.1.2 machine-readable compliance artifacts mapping CRA Annex I requirements through prEN 40000-1-4 Security Objectives to dfetch controls, with CI integration to generate and publish the Component Definition at release time. The voluntary alignment under Art. 13(5) and the Recital 18 classification are both correctly reasoned. The three-tier traceability model (ECR → SO → control) is methodologically sound.
CI / pipeline — clean
Both new action pins (attest-build-provenance@a2bbfa25…, download-artifact@3e5f45b2…) follow the commit-SHA pinning discipline already required by C-009. Version extraction via importlib.metadata is correct. No shell injection vectors in the new workflow steps. Attestation on the OSCAL artifact is a nice touch.
Findings
1. C-045 is missing — internal inconsistency (blocking)
The PR description, security.rst, and the PR title all describe C-045 ("destination-path sensitivity warning", ECR-i → SO.PreventAttackPropagation → LIM-2) as one of four Track B controls. It appears in doc/explanation/security.rst:
- **C-045** (destination-path sensitivity warning) — ECR-i / SO.PreventAttackPropagation → LIM-2But C-045 is absent from TRACK_B_CONTROLS in compliance_data.py, absent from SO_IMPLEMENTATIONS, absent from the CHANGELOG entry, and absent from the generated compliance_track.rst. The committed OSCAL Component Definition therefore omits it too.
As-is, the artifacts produced by this PR are internally inconsistent with the PR description and the docs. Either implement C-045 or remove all references to it. If the intent is "planned, not yet defined", it needs the same treatment as C-043 and C-046 (defined in TRACK_B_CONTROLS with status="planned").
2. UUID namespace label is wrong
In security/compliance.py:
_UUID_NS = uuid.UUID("6ba7b810-9dad-11d1-80b4-00c04fd430c8") # uuid.NAMESPACE_URL6ba7b810-… is uuid.NAMESPACE_DNS. uuid.NAMESPACE_URL is 6ba7b811-… (note the trailing 1). The comment is incorrect. Changing this to _UUID_NS = uuid.NAMESPACE_DNS removes the ambiguity entirely and avoids confusion for anyone auditing the UUID derivation.
3. Hard-coded default version is immediately stale
build_oscal_component_definition(version: str = "0.14.0") and the argparse --version default both hard-code 0.14.0. This PR targets 0.15.0, so the default is wrong the moment this merges. The CI publish step correctly passes --version "$VERSION", so production artifacts are safe — but calling the function directly (e.g. in tests, from the REPL, or during local development) silently produces Component Definitions tagged with the wrong version.
Consider replacing the default with a runtime lookup:
try:
import importlib.metadata
_DFETCH_VERSION = importlib.metadata.version("dfetch")
except importlib.metadata.PackageNotFoundError:
_DFETCH_VERSION = "0.0.0"4. Committed generated artifact will drift from CI output
security/dfetch.component-definition.json is both committed to the repo and regenerated in CI at release time. _build_metadata() embeds date.today(), so every run produces a different file. There is no CI check to detect divergence between the committed version and what the tool would generate today.
Options:
- Add the file to
.gitignoreand rely solely on the release upload — removes the stale-artifact problem entirely. - Or add a
--checkflag that compares (excludinglast-modified) and fail CI if the content has drifted.
Leaving it as-is means the committed file will be perpetually out of date after the first release that regenerates it.
5. No tests for new modules
security/compliance.py and security/compliance_data.py have no test coverage. A basic round-trip test — generate the Component Definition, parse the JSON, assert known control IDs and SO IDs are present — would catch regressions and validate OSCAL schema conformance. These artifacts are what downstream integrators rely on for their own conformity assessments; silent breakage here is higher-impact than a typical internal code path.
Summary
| # | Finding | Severity |
|---|---|---|
| 1 | C-045 referenced in docs/PR but not implemented | Blocking |
| 2 | UUID namespace comment wrong (NAMESPACE_DNS ≠ NAMESPACE_URL) |
Low |
| 3 | Default version 0.14.0 is stale as of this PR |
Low |
| 4 | Committed generated artifact has non-deterministic timestamp | Low |
| 5 | No tests for compliance modules | Low |
The OSCAL mapping and compliance rationale are solid. Resolve finding 1 (and ideally 3) before merge.
Generated by Claude Code
… accuracy - Narrow `except ImportError` to `except ModuleNotFoundError` in `_load_track_a_controls` so genuine import errors inside tm modules propagate rather than being silently swallowed - Add `_rst_escape_star` helper that escapes standalone `*` characters (regex `(?<!\*)\*(?!\*)` → `\*`) without touching `**bold**` markup, eliminating docutils inline-emphasis warnings from generated RST - Fix C-005 description in `so-data-transmitted-confidentiality`: hash verification does not provide confidentiality; corrected to state it verifies tamper-absence without encrypting content - Fix CHANGELOG.rst underline length for Release 0.14.0 title - Regenerate `compliance_track.rst` and `dfetch.component-definition.json` https://claude.ai/code/session_01NriXcboJTd9MQG2Mz2kmzQ
Previously _load_track_a_controls silently returned [] on ModuleNotFoundError, producing incomplete compliance artifacts with no user-visible error. Now the function raises RuntimeError unless --track-b-only is explicitly passed, making degraded mode opt-in rather than silent. The CI publish workflow passes --track-b-only since pytm is not installed in that environment. Update security/README.md and the inline OSCAL Artifacts code block to include --track-b-only in the regeneration command. Fixes review thread PRRT_kwDOEh9FL86JrNyh. https://claude.ai/code/session_01NriXcboJTd9MQG2Mz2kmzQ
Maps all 13 CRA Annex I Part I essential requirements (ECR-a–m) through
prEN 40000-1-4 Security Objectives (SO.*) to dfetch controls (C-001–C-046).
Covers Part II vulnerability-handling requirements via prEN 40000-1-3.
New files:
Catalog derived from the CEN/CLC/JTC 13 WG 9 deep-dive session (March 2026,
Angelo D'Amato, Vulnir B.V. / STAN4CR)
python -m security.compliance --component ... --rstIntroduces four Track B controls:
https://claude.ai/code/session_01NriXcboJTd9MQG2Mz2kmzQ
Summary by CodeRabbit