From 80a98920d880ab9e4d5b86cad9dc2eab6af54ece Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 15 Jun 2026 17:56:55 +0000 Subject: [PATCH 1/8] Add CRA Compliance Track B: OSCAL 1.1.2 three-tier traceability MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- CHANGELOG.rst | 9 + doc/explanation/compliance_track.rst | 515 ++++++++ doc/explanation/security.rst | 30 + security/README.md | 20 + security/compliance.py | 563 +++++++++ security/compliance_data.py | 648 ++++++++++ security/cra_pren_4000014_oscal_catalog.json | 1130 ++++++++++++++++++ security/dfetch.component-definition.json | 910 ++++++++++++++ 8 files changed, 3825 insertions(+) create mode 100644 doc/explanation/compliance_track.rst create mode 100644 security/compliance.py create mode 100644 security/compliance_data.py create mode 100644 security/cra_pren_4000014_oscal_catalog.json create mode 100644 security/dfetch.component-definition.json diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 94ce400a..fa562b27 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,3 +1,12 @@ +Release 0.15.0 (unreleased) +============================ + +* Add CRA Compliance Track B: OSCAL 1.1.2 Component Definition mapping all CRA Annex I Part I + essential requirements (ECR-a–m) through prEN 40000-1-4 Security Objectives to dfetch controls; + covers Part II via prEN 40000-1-3; introduces controls C-043 (release-gate CVE check), C-044 + (data minimisation policy), C-045 (destination-path sensitivity warning), and C-046 (exploit + mitigation inventory) + Release 0.14.0 (released 2026-06-14) =========================== diff --git a/doc/explanation/compliance_track.rst b/doc/explanation/compliance_track.rst new file mode 100644 index 00000000..69e99b7c --- /dev/null +++ b/doc/explanation/compliance_track.rst @@ -0,0 +1,515 @@ +.. _compliance_track: + +CRA Compliance Track B +====================== + +.. note:: + + dfetch is **non-commercial open-source software** and is exempt from + mandatory CRA obligations under Recital 18 of Regulation (EU) 2024/2847. + This document is produced voluntarily under Article 13(5) to support + downstream integrators who must account for open-source components in + their own conformity assessments. + +Three-tier traceability:: + + CRA Annex I Essential Requirement (ECR-a … ECR-m) + ↓ + prEN 40000-1-4 Security Objective (SO.*) + ↓ + dfetch control (C-001 … C-046) or documented gap + +Machine-readable OSCAL 1.1.2 artifacts are kept alongside the source: + +- ``security/cra_pren_4000014_oscal_catalog.json`` — prEN 40000-1-4 catalog +- ``security/dfetch.component-definition.json`` — dfetch Component Definition + +Classification Decision +----------------------- + +.. list-table:: + :header-rows: 1 + :widths: 25 75 + + * - Criterion + - Decision / Basis + * - Product type + - Software tool (CLI) — Python package distributed via PyPI + * - CRA classification + - Non-commercial open-source software (Recital 18 exemption) + * - Legal basis + - Article 3(14), Recital 18, Article 13(5) of Regulation (EU) 2024/2847 + * - Mandatory obligations + - None — not a commercial product; no CE marking required + * - Voluntary alignment + - This Track B document is produced voluntarily under Article 13(5) to support downstream integrators who must account for open-source components in their own CRA conformity assessments. + +Applicable Standards +-------------------- + +.. list-table:: + :header-rows: 1 + :widths: 18 25 6 35 16 + + * - Standard + - Full title + - Applies + - Scope note + - Gap + * - prEN 40000-1-2 + - Cyber Resilience Principles and Risk Management + - Yes + - Process standard covering risk-based product security across the lifecycle. The Product Security Context (§6.2) is documented in security.rst. Track A threat models (tm_supply_chain.py, tm_usage.py) implement §6.3–§6.6. + - — + * - prEN 40000-1-3 + - Vulnerability Handling Requirements + - Yes + - Covers CRA Annex I Part II vulnerability handling obligations. Addressed in the Part II table below via SECURITY.md, SBOM (C-022), and dependency-review CI (C-016). + - No formal patch SLA or LTS backport policy defined. + * - prEN 40000-1-4 + - Generic Security Requirements (draft, indicative publication October 2027) + - Yes + - Primary standard for this document. Maps CRA Annex I Part I Art. 2(a)–(m) to Security Objectives (SO.*) and Technical Controls (GEC-*, SUM-*, etc.). The catalog is included as security/cra_pren_4000014_oscal_catalog.json. + - Standard is in draft; final clause numbering may change. + * - EN 18031-1/2:2024 + - Common security requirements for radio equipment (basis of prEN 40000-1-4) + - Yes + - prEN 40000-1-4 builds on EN 18031. Many technical controls (GEC-*, SUM-*, AUM-*, SSM-*, SCM-*) originate from EN 18031. dfetch's applicability is assessed at the prEN 40000-1-4 SO level. + - — + * - ETSI EN 303 645 V3.1.3 + - Cyber Security for Consumer Internet of Things + - No + - IoT-specific standard. dfetch is a developer CLI tool with no IoT device functionality, physical interfaces, or consumer IoT use case. + - — + +Part I — Product Security Requirements (ECR-a to ECR-m) +------------------------------------------------------- + +The table below summarises dfetch's implementation of each prEN 40000-1-4 Security Objective per CRA essential requirement. + +.. list-table:: + :header-rows: 1 + :widths: 20 28 18 20 14 + + * - CRA ECR + - SO (prEN 40000-1-4) + - dfetch controls + - Gaps + - Status + * - **ECR-A** — Be made available on the market without known exploitable vulnerabilities. + - SO.VulnerabilityManagementProcess + - C-015, C-016, C-017, C-022 + - No CVE gate at release time (→ C-043 planned) + - ⚠ Partial + * - **ECR-B** — Be made available on the market with a secure by default configuration, including the possibility to reset the product to its original state. + - SO.SecureDefaultConfiguration + - C-001, C-002 + - — + - ⚠ Partial + * - + - SO.SecureStartupConfig + - — + - — + - — N/A + * - + - SO.FactoryReset + - — + - — + - — N/A + * - **ECR-C** — Ensure that vulnerabilities can be addressed through security updates, including automatic updates enabled by default, with an opt-out mechanism, user notification, and the option to postpone updates. + - SO.Updateability + - — + - — + - ✓ Implemented + * - + - SO.AutomaticUpdates + - — + - — + - — N/A + * - + - SO.UserUpdateNotification + - C-040 + - — + - ✓ Implemented + * - + - SO.PostponeUpdates + - — + - — + - — N/A + * - **ECR-D** — Ensure protection from unauthorised access by appropriate control mechanisms including authentication, identity or access management systems, and report on possible unauthorised access. + - SO.AccessControl + - C-006, C-036 + - — + - ⚠ Partial + * - + - SO.AccessControlReport + - C-009 + - No persistent log of unauthorised access attempts + - ⚠ Partial + * - **ECR-E** — Protect the confidentiality of stored, transmitted or otherwise processed data by state-of-the-art mechanisms such as encryption at rest and in transit. + - SO.DataStoredConfidentiality + - C-036 + - .dfetch_data.yaml stored as plaintext YAML (no encryption at rest) + - ⚠ Partial + * - + - SO.DataProcessedConfidentiality + - C-005, C-034 + - — + - ✓ Implemented + * - + - SO.DataTransmittedConfidentiality + - C-009 + - HTTP accepted for remote URLs (SCM-3/SCM-4 gap; mitigated by C-009 warning) + - ⚠ Partial + * - + - SO.ComAuth + - C-003, C-004 + - No end-to-end authenticity verification for plain git/svn transport + - ⚠ Partial + * - + - SO.SecureProvisioning + - C-005 + - — + - ⚠ Partial + * - **ECR-F** — Protect the integrity of stored, transmitted or otherwise processed data, commands, programs and configuration against unauthorised manipulation or modification, and report on corruptions. + - SO.DataStoredIntegrity + - C-005 + - Integrity hash opt-in only; not enforced by default for git/svn + - ⚠ Partial + * - + - SO.DataProcessedIntegrity + - C-005, C-034 + - — + - ✓ Implemented + * - + - SO.DataTransmittedIntegrity + - C-003, C-004 + - No end-to-end hash for git/svn transport beyond TLS/SSH channel integrity + - ⚠ Partial + * - + - SO.IntegrityReport + - C-009 + - No persistent integrity-violation log + - ⚠ Partial + * - **ECR-G** — Process only data, personal or other, that are adequate, relevant and limited to what is necessary in relation to the intended purpose of the product with digital elements (data minimisation). + - SO.DataMinimization + - C-044 + - — + - ○ Planned + * - **ECR-H** — Protect the availability of essential and basic functions, also after an incident, including through resilience and mitigation measures against denial-of-service attacks. + - SO.IncidentRecovery + - — + - — + - — N/A + * - + - SO.IncidentResilience + - C-002, C-007 + - No timeout on VCS operations (potential resource exhaustion) + - ⚠ Partial + * - **ECR-I** — Minimise the negative impact by the products themselves or connected devices on the availability of services provided by other devices or networks. + - SO.LimitExternalImpact + - C-001, C-007 + - — + - ⚠ Partial + * - + - SO.PreventAttackPropagation + - C-045 + - No warning when dst: targets security-sensitive paths (→ C-045 planned) + - ○ Planned + * - + - SO.MonitorExternalImpact + - — + - — + - — N/A + * - **ECR-J** — Be designed, developed and produced to limit attack surfaces, including external interfaces. + - SO.ReduceAttackSurface + - C-001, C-003, C-004, C-007, C-008 + - — + - ⚠ Partial + * - **ECR-K** — Be designed, developed and produced to reduce the impact of an incident using appropriate exploitation mitigation mechanisms and techniques. + - SO.ReduceImpactOfIncident + - C-005, C-007, C-015, C-017, C-046 + - No documented exploit mitigation inventory (→ C-046 planned) + - ○ Planned + * - **ECR-L** — Provide security related information by recording and monitoring relevant internal activity, including the access to or modification of data, services or functions, with an opt-out mechanism for the user. + - SO.LogSecurityRelevantActivities + - C-036 + - No persistent security event log (LGM-2/3/4 gap); No opt-out for logging — dfetch does not log by default + - ⚠ Partial + * - + - SO.MonitorSecurityRelevantActivities + - C-009 + - — + - ⚠ Partial + * - + - SO.OptionDisableDataLogging + - — + - — + - — N/A + * - + - SO.OptionDisableDataMonitoring + - — + - — + - — N/A + * - **ECR-M** — Provide the possibility for users to securely and easily remove on a permanent basis all data and settings and, where such data can be transferred to other products or systems, ensure that this is done in a secure manner. + - SO.SecureDataDeletion + - — + - No built-in delete command for .dfetch_data.yaml (DLM-1 gap; user deletes manually) + - ⚠ Partial + * - + - SO.DataTransmittedConfidentiality + - — + - — + - — N/A + * - + - SO.DataTransmittedIntegrity + - — + - — + - — N/A + * - + - SO.ComAuth + - — + - — + - — N/A + +Part II — Vulnerability Handling (prEN 40000-1-3) +------------------------------------------------- + +Part II requirements are addressed via prEN 40000-1-3. pii-04 is not applicable under Recital 18. + +.. list-table:: + :header-rows: 1 + :widths: 10 32 18 24 12 + + * - CRA ref + - Requirement + - dfetch controls + - Gaps + - Status + * - Part II §1 + - Identify and document vulnerabilities and components (SBOM). + - C-021, C-022 + - — + - ✓ Implemented + * - Part II §2 + - Address vulnerabilities without delay; provide free security updates. + - C-015, C-016 + - No formal patch SLA defined; No backport/LTS commitment documented + - ⚠ Partial + * - Part II §3 + - Apply effective coordinated vulnerability disclosure (CVD) policy. + - SECURITY.md + - — + - ✓ Implemented + * - Part II §4 + - Report actively exploited vulnerabilities to national CSIRT and ENISA. + - — + - — + - — N/A + * - Part II §5 + - Publish coordinated vulnerability disclosure policy. + - SECURITY.md + - — + - ✓ Implemented + * - Part II §6 + - Share information on vulnerabilities in integrated components. + - C-022, C-016 + - No proactive downstream notification process + - ⚠ Partial + * - Part II §7 + - Provide security updates free of charge for the support period. + - MIT licence, PyPI + - No support period or LTS policy documented + - ⚠ Partial + +Gap Analysis — Compliance-Only Controls +--------------------------------------- + +Three CRA essential requirements were not independently surfaced by the Track A risk models. The following controls address them. + +**C-043 — Release-gate CVE check (ECR-a, SO.VulnerabilityManagementProcess → GEC-1)** + +dfetch's CI detects vulnerabilities at commit time (C-015, C-016, C-017) but does not gate the release publish on a CVE scan of runtime dependencies. C-043 (planned) adds ``pip-audit`` or ``osv-scanner`` to the publish workflow. + +**C-044 — Data minimisation policy (ECR-g, SO.DataMinimization → DTM-1)** + +dfetch processes dependency metadata only. The ``.dfetch_data.yaml`` file stores: ``remote_url`` (credentials stripped by C-036), ``revision``, optional ``integrity.hash``, and ``last_fetch`` timestamp. Each field is functionally necessary for ``dfetch check`` and ``dfetch freeze``. No personal data is collected; no telemetry is sent. C-044 formalises this assertion as a documented policy. + +**C-045 — Destination path sensitivity warning (ECR-i, SO.PreventAttackPropagation → LIM-2)** + +A compromised upstream repository could instruct dfetch to overwrite CI/CD configuration files (e.g. ``.github/workflows/``) via a malicious ``dst:`` value. C-045 (planned) adds a non-blocking warning when ``dst:`` resolves to a security-sensitive path, following the ``plaintext_warning()`` pattern in ``dfetch/manifest/project.py``. + +**C-046 — Exploit mitigation inventory (ECR-k, SO.ReduceImpactOfIncident → GEC-11)** + +prEN 40000-1-4 ECR-k requires documenting applicable exploit mitigation techniques. For dfetch (pure Python): + +- **ASLR / DEP / stack canaries**: provided by CPython and the OS; not in dfetch's control but inherited. +- **No eval/exec of remote content**: dfetch never evaluates fetched content as code. +- **Constant-time comparison** (C-005): HMAC-based integrity hash uses ``hmac.compare_digest``. +- **No shell injection** (C-007): all subprocess calls use ``shell=False``. +- **Input validation** (C-008): URL scheme, path, and revision inputs are validated. +- **Static analysis** (C-015, C-017): CodeQL and bandit gate every commit. +- CFI, sandboxing, and signed-execution policies are not applicable to a pure-Python tool. + +Final Control Register +---------------------- + +All controls from Track A (risk-driven) and Track B (regulatory) merged and sorted. Track B controls (C-043–C-046) are marked accordingly. + +.. list-table:: + :header-rows: 1 + :widths: 8 40 10 42 + + * - ID + - Name + - Track + - Reference + * - C-001 + - Path-traversal prevention + - Track A + - dfetch/util/util.py + * - C-002 + - Decompression-bomb protection + - Track A + - dfetch/vcs/archive.py + * - C-003 + - Archive symlink validation + - Track A + - dfetch/vcs/archive.py + * - C-004 + - Archive member type checks + - Track A + - dfetch/vcs/archive.py + * - C-005 + - Integrity hash verification + - Track A + - dfetch/vcs/integrity_hash.py + * - C-006 + - Non-interactive VCS + - Track A + - dfetch/vcs/git.py, dfetch/vcs/svn.py + * - C-007 + - Subprocess safety + - Track A + - dfetch/util/cmdline.py + * - C-008 + - Manifest input validation + - Track A + - dfetch/manifest/schema.py + * - C-009 + - Actions commit-SHA pinning + - Track A + - .github/workflows/*.yml + * - C-010 + - OIDC trusted publishing + - Track A + - .github/workflows/python-publish.yml + * - C-011 + - Minimal workflow permissions + - Track A + - .github/workflows/*.yml + * - C-012 + - persist-credentials: false + - Track A + - .github/workflows/*.yml + * - C-013 + - Harden-runner (egress block) + - Track A + - .github/workflows/*.yml + * - C-015 + - CodeQL static analysis + - Track A + - .github/workflows/codeql-analysis.yml + * - C-016 + - Dependency review + - Track A + - .github/workflows/dependency-review.yml + * - C-017 + - bandit security linter + - Track A + - pyproject.toml + * - C-021 + - Sigstore SBOM attestation + - Track A + - — + * - C-022 + - CycloneDX SBOM on PyPI + - Track A + - — + * - C-024 + - ``secrets: inherit`` scope + - Track A + - — + * - C-026 + - Consumer-side package provenance verification + - Track A + - doc/howto/verify-integrity.rst + * - C-032 + - Consumer attestation verification pins to release tag ref + - Track A + - doc/howto/verify-integrity.rst + * - C-033 + - Ref-scoped build cache keys isolate PR and release builds + - Track A + - .github/workflows/build.yml + * - C-034 + - Hash algorithm allowlist (SHA-256/384/512 only) + - Track A + - dfetch/vcs/integrity_hash.py + * - C-036 + - Persisted-metadata credential redaction + - Track A + - dfetch/project/metadata.py + * - C-037 + - SLSA Source Provenance Attestation of repository governance controls + - Track A + - .github/workflows/source-provenance.yml + * - C-038 + - Ancestry enforcement on dfetch main branch + - Track A + - .github/workflows/ + * - C-039 + - Source build provenance and VSA attestations + - Track A + - doc/howto/verify-integrity.rst + * - C-040 + - Test result attestation on source archive + - Track A + - .github/workflows/test.yml + * - C-041 + - Winget manifest PRs reviewed by community maintainers + - Track A + - .github/workflows/winget-publish.yml + * - C-042 + - WINGET_TOKEN scoped to dedicated Winget environment + - Track A + - .github/workflows/winget-publish.yml + * - C-043 + - Release-gate CVE check on runtime dependencies + - Track B + - .github/workflows/python-publish.yml (planned CI addition) + * - C-044 + - Data minimisation policy + - Track B + - doc/explanation/compliance_track.rst (this document) + * - C-045 + - Destination path sensitivity warning + - Track B + - dfetch/project/subproject.py (planned: _is_sensitive_dst()) + * - C-046 + - Exploit mitigation inventory + - Track B + - doc/explanation/compliance_track.rst (this document) + +OSCAL Artifacts +--------------- + +The OSCAL 1.1.2 Component Definition references the catalog file and can be +regenerated with: + +.. code-block:: bash + + python -m security.compliance \\ + --component security/dfetch.component-definition.json \\ + --rst > doc/explanation/compliance_track.rst + diff --git a/doc/explanation/security.rst b/doc/explanation/security.rst index ddf542e7..f20ee570 100644 --- a/doc/explanation/security.rst +++ b/doc/explanation/security.rst @@ -115,3 +115,33 @@ generated from the corresponding Python module in ``security/`` — see threat_model_supply_chain threat_model_usage + compliance_track + +Compliance Track (Track B) +-------------------------- + +The :doc:`compliance_track` page maps all 13 CRA Annex I Part I essential +requirements (ECR-a through ECR-m) through prEN 40000-1-4 Security Objectives +to dfetch's implemented controls. It also covers the seven Part II +vulnerability-handling requirements via prEN 40000-1-3. + +The three-tier traceability model is:: + + CRA Annex I Essential Requirement (ECR-a … ECR-m) + ↓ + prEN 40000-1-4 Security Objective (SO.*) + ↓ + dfetch control (C-001 … C-046) or documented gap + +Three compliance-only controls introduced in Track B address CRA requirements +not independently surfaced by the risk models: + +- **C-044** (data minimisation policy) — ECR-g / SO.DataMinimization → DTM-1 +- **C-045** (destination-path sensitivity warning) — ECR-i / SO.PreventAttackPropagation → LIM-2 +- **C-046** (exploit mitigation inventory) — ECR-k / SO.ReduceImpactOfIncident → GEC-11 + +Machine-readable OSCAL 1.1.2 artifacts are kept alongside the source: + +- ``security/cra_pren_4000014_oscal_catalog.json`` — prEN 40000-1-4 catalog + (derived from the CEN/CLC/JTC 13 WG 9 deep-dive session, March 2026) +- ``security/dfetch.component-definition.json`` — dfetch Component Definition diff --git a/security/README.md b/security/README.md index 31636f33..41c9eeb8 100644 --- a/security/README.md +++ b/security/README.md @@ -21,3 +21,23 @@ python -m security.tm_usage --report security/report_template.rst > doc/explanat python -m security.tm_usage --dfd python -m security.tm_usage --seq ``` + +## CRA Compliance Track B + +The compliance track does not require pytm. Regenerate with: + +```bash +python -m security.compliance \ + --component security/dfetch.component-definition.json \ + --rst > doc/explanation/compliance_track.rst +``` + +This produces: + +- `security/dfetch.component-definition.json` — OSCAL 1.1.2 Component Definition implementing + prEN 40000-1-4 Security Objectives for dfetch +- `doc/explanation/compliance_track.rst` — human-readable RST (built into the Sphinx docs) + +The prEN 40000-1-4 catalog (`security/cra_pren_4000014_oscal_catalog.json`) is committed as a +static artifact derived from the CEN/CLC/JTC 13 WG 9 deep-dive session by Angelo D'Amato +(Vulnir B.V., STAN4CR), 5 March 2026. diff --git a/security/compliance.py b/security/compliance.py new file mode 100644 index 00000000..90d779d7 --- /dev/null +++ b/security/compliance.py @@ -0,0 +1,563 @@ +"""CRA Compliance Track B for dfetch. + +Produces an OSCAL 1.1.2 Component Definition and a human-readable RST document +that map the CRA Annex I essential requirements through prEN 40000-1-4 Security +Objectives to dfetch's implemented controls. + +Three-tier traceability: + CRA ECR-a … ECR-m → prEN 40000-1-4 SO.* → dfetch control C-xxx + +Run:: + + python -m security.compliance \\ + --component security/dfetch.component-definition.json \\ + --rst > doc/explanation/compliance_track.rst +""" + +import argparse +import importlib +import json +import os +import sys +import uuid +from datetime import date +from typing import Any + +from security.compliance_data import ( + CLASSIFICATION_DECISION, + PART_II_REQUIREMENTS, + SO_IMPLEMENTATIONS, + STANDARDS, + TRACK_B_CONTROLS, + Control, + SOImplementation, +) + +CATALOG_PATH = os.path.join( + os.path.dirname(__file__), "cra_pren_4000014_oscal_catalog.json" +) + +_UUID_NS = uuid.UUID("6ba7b810-9dad-11d1-80b4-00c04fd430c8") # uuid.NAMESPACE_URL + +_STATUS_LABEL: dict[str, str] = { + "implemented": "✓ Implemented", + "partially-implemented": "⚠ Partial", + "planned": "○ Planned", + "not-applicable": "— N/A", +} + + +def _u(key: str) -> str: + """Return deterministic UUID v5 for the given stable key.""" + return str(uuid.uuid5(_UUID_NS, f"dfetch-cra-{key}")) + + +def _so_title(so_id: str) -> str: + """Derive SO.* display name from so-id slug.""" + parts = so_id.removeprefix("so-").split("-") + # Strip single-letter ECR disambiguation suffix (e.g. -e, -m) + if len(parts) > 1 and len(parts[-1]) == 1 and parts[-1].isalpha(): + parts = parts[:-1] + return "SO." + "".join(p.capitalize() for p in parts) + + +def _load_track_a_controls() -> list[Control]: + """Load Track A controls from threat models if pytm is available.""" + try: + tm_sc = importlib.import_module("security.tm_supply_chain") + tm_u = importlib.import_module("security.tm_usage") + except ImportError: + return [] + sc_controls: list[Any] = getattr(tm_sc, "CONTROLS", []) + u_controls: list[Any] = getattr(tm_u, "CONTROLS", []) + return [ + Control(id=c.id, name=c.name, description=c.description, reference=c.reference) + for c in sc_controls + u_controls + ] + + +def get_all_controls() -> list[Control]: + """Return merged, deduplicated, sorted control register from both tracks.""" + track_a = _load_track_a_controls() + seen: set[str] = set() + merged: list[Control] = [] + for ctrl in track_a + TRACK_B_CONTROLS: + if ctrl.id not in seen: + seen.add(ctrl.id) + merged.append(ctrl) + return sorted(merged, key=lambda c: c.id) + + +def load_catalog() -> Any: + """Load the prEN 40000-1-4 OSCAL catalog JSON.""" + with open(CATALOG_PATH, encoding="utf-8") as catalog_file: + return json.load(catalog_file) + + +# ── OSCAL Component Definition builder ──────────────────────────────────────── + + +def _build_metadata(version: str) -> dict[str, Any]: + """Return the OSCAL metadata block.""" + return { + "title": "dfetch CRA Compliance Component Definition", + "last-modified": f"{date.today().isoformat()}T00:00:00Z", + "version": version, + "oscal-version": "1.1.2", + "props": [ + { + "name": "cra-class", + "value": "Non-commercial OSS — Recital 18 exemption", + }, + { + "name": "cra-article", + "value": ( + "Article 3(14), Recital 18, Article 13(5) " + "of Regulation (EU) 2024/2847" + ), + }, + { + "name": "standard", + "value": ( + "prEN 40000-1-4 (draft, indicative publication October 2027)" + ), + }, + ], + "remarks": ( + "Produced voluntarily under Article 13(5) to support downstream integrators. " + "dfetch is non-commercial open-source software and is not subject to " + "mandatory CRA conformity obligations (Recital 18)." + ), + } + + +def _build_so_props(so_impl: SOImplementation) -> list[dict[str, str]]: + """Return OSCAL props list for one SOImplementation.""" + props: list[dict[str, str]] = [ + {"name": "implementation-status", "value": so_impl.status} + ] + if so_impl.not_applicable: + props.append( + {"name": "not-applicable", "value": "; ".join(so_impl.not_applicable)} + ) + if so_impl.gaps: + props.append({"name": "gaps", "value": "; ".join(so_impl.gaps)}) + if so_impl.controls: + props.append({"name": "dfetch-controls", "value": ", ".join(so_impl.controls)}) + return props + + +def _build_so_description(so_impl: SOImplementation) -> str: + """Return the statement description for one SOImplementation.""" + parts = [] + if so_impl.description: + parts.append(so_impl.description) + if so_impl.gaps: + parts.append("Gaps: " + "; ".join(so_impl.gaps)) + if so_impl.not_applicable: + parts.append("Not applicable: " + "; ".join(so_impl.not_applicable)) + return " ".join(parts) if parts else _so_title(so_impl.so_id) + + +def _build_implemented_requirements() -> list[dict[str, Any]]: + """Return one implemented-requirement dict per SO.""" + return [ + { + "uuid": _u(f"req-{so_impl.so_id}"), + "control-id": so_impl.so_id, + "props": _build_so_props(so_impl), + "statements": [ + { + "statement-id": f"{so_impl.so_id}-stmt", + "uuid": _u(f"stmt-{so_impl.so_id}"), + "description": _build_so_description(so_impl), + } + ], + } + for so_impl in SO_IMPLEMENTATIONS + ] + + +def _build_component(version: str) -> dict[str, Any]: + """Return the dfetch software component block.""" + return { + "uuid": _u("component-dfetch"), + "type": "software", + "title": "dfetch", + "description": ( + "Python CLI tool for vendoring source-code dependencies from Git, SVN, " + "or archive files into a project as plain files." + ), + "props": [{"name": "software-version", "value": version}], + "links": [ + {"href": "https://github.com/dfetch-org/dfetch", "rel": "homepage"}, + {"href": "https://pypi.org/project/dfetch/", "rel": "distribution"}, + { + "href": "security/cra_pren_4000014_oscal_catalog.json", + "rel": "reference", + "text": "prEN 40000-1-4 OSCAL Catalog", + }, + ], + "control-implementations": [ + { + "uuid": _u("ctrl-impl-en40000-1-4"), + "source": "security/cra_pren_4000014_oscal_catalog.json", + "description": ( + "Voluntary alignment with prEN 40000-1-4 Security Objectives. " + "The catalog organises CRA Annex I Part I essential requirements " + "(ECR-a–m) as groups; this component implements the Security " + "Objectives (SO.*) within each ECR using dfetch controls (C-001–C-046)." + ), + "implemented-requirements": _build_implemented_requirements(), + } + ], + } + + +def _build_back_matter() -> dict[str, Any]: + """Return OSCAL back-matter with key resource references.""" + return { + "resources": [ + { + "uuid": _u("res-cra"), + "title": "Cyber Resilience Act — Regulation (EU) 2024/2847", + "rlinks": [ + { + "href": ( + "https://eur-lex.europa.eu/legal-content/EN/TXT/" + "?uri=CELEX:32024R2847" + ) + } + ], + }, + { + "uuid": _u("res-catalog"), + "title": "prEN 40000-1-4 OSCAL Catalog (this repository)", + "rlinks": [{"href": "security/cra_pren_4000014_oscal_catalog.json"}], + }, + { + "uuid": _u("res-security-md"), + "title": "dfetch Security Policy (SECURITY.md)", + "rlinks": [ + { + "href": ( + "https://github.com/dfetch-org/dfetch/blob/main/SECURITY.md" + ) + } + ], + }, + ] + } + + +def build_oscal_component_definition(version: str = "0.14.0") -> dict[str, Any]: + """Return a complete OSCAL 1.1.2 Component Definition for dfetch.""" + return { + "component-definition": { + "uuid": _u("component-definition"), + "metadata": _build_metadata(version), + "components": [_build_component(version)], + "back-matter": _build_back_matter(), + } + } + + +# ── RST renderer ────────────────────────────────────────────────────────────── + + +def _rst_title(text: str, char: str = "=") -> str: + """Return an RST title with underline.""" + return f"{text}\n{char * len(text)}\n" + + +def _rst_list_table( + headers: list[str], + rows: list[list[str]], + widths: list[int] | None = None, +) -> str: + """Return an RST list-table directive as a string.""" + lines = [".. list-table::", " :header-rows: 1"] + if widths: + lines.append(" :widths: " + " ".join(str(w) for w in widths)) + lines.append("") + lines.append(" * - " + "\n - ".join(headers)) + for row in rows: + lines.append(" * - " + "\n - ".join(str(c) for c in row)) + lines.append("") + return "\n".join(lines) + + +def _render_classification() -> None: + """Print the Classification Decision section.""" + print(_rst_title("Classification Decision", "-")) + rows = [[k, v] for k, v in CLASSIFICATION_DECISION.items()] + print(_rst_list_table(["Criterion", "Decision / Basis"], rows, widths=[25, 75])) + + +def _render_standards_table() -> None: + """Print the Applicable Standards table.""" + print(_rst_title("Applicable Standards", "-")) + rows = [ + [ + s.name, + s.reference, + "Yes" if s.applies else "No", + s.scope_note, + s.gap_note or "—", + ] + for s in STANDARDS + ] + print( + _rst_list_table( + ["Standard", "Full title", "Applies", "Scope note", "Gap"], + rows, + widths=[18, 25, 6, 35, 16], + ) + ) + + +def _ecr_map_from_catalog() -> dict[str, str]: + """Return mapping of ECR id → requirement text from the OSCAL catalog.""" + catalog = load_catalog() + return { + group["id"]: next( + ( + p["value"] + for p in group.get("props", []) + if p["name"] == "requirement-text" + ), + "", + ) + for group in catalog["catalog"]["groups"] + } + + +def _part_i_rows(ecr_map: dict[str, str]) -> list[list[str]]: + """Build table rows for the Part I ECR table.""" + rows: list[list[str]] = [] + current_ecr = "" + for so in SO_IMPLEMENTATIONS: + ecr_cell = "" + if so.ecr_id != current_ecr: + current_ecr = so.ecr_id + ecr_cell = f"**{so.ecr_id.upper()}** — {ecr_map.get(so.ecr_id, '')}" + ctrls = ", ".join(so.controls) if so.controls else "—" + gaps = "; ".join(so.gaps) if so.gaps else "—" + rows.append( + [ + ecr_cell, + _so_title(so.so_id), + ctrls, + gaps, + _STATUS_LABEL.get(so.status, so.status), + ] + ) + return rows + + +def _render_part_i_table() -> None: + """Print the Part I security requirements table.""" + print(_rst_title("Part I — Product Security Requirements (ECR-a to ECR-m)", "-")) + print( + "The table below summarises dfetch's implementation of each prEN 40000-1-4 " + "Security Objective per CRA essential requirement.\n" + ) + print( + _rst_list_table( + ["CRA ECR", "SO (prEN 40000-1-4)", "dfetch controls", "Gaps", "Status"], + _part_i_rows(_ecr_map_from_catalog()), + widths=[20, 28, 18, 20, 14], + ) + ) + + +def _render_part_ii_table() -> None: + """Print the Part II vulnerability handling table.""" + print(_rst_title("Part II — Vulnerability Handling (prEN 40000-1-3)", "-")) + print( + "Part II requirements are addressed via prEN 40000-1-3. " + "pii-04 is not applicable under Recital 18.\n" + ) + rows = [ + [ + req.ref, + req.text, + ", ".join(req.controls) if req.controls else "—", + "; ".join(req.gaps) if req.gaps else "—", + _STATUS_LABEL.get(req.status, req.status), + ] + for req in PART_II_REQUIREMENTS + ] + print( + _rst_list_table( + ["CRA ref", "Requirement", "dfetch controls", "Gaps", "Status"], + rows, + widths=[10, 32, 18, 24, 12], + ) + ) + + +def _gap_entries() -> list[tuple[str, str]]: + """Return (title, body) pairs for the gap analysis section.""" + return [ + ( + "C-043 — Release-gate CVE check" + + " (ECR-a, SO.VulnerabilityManagementProcess → GEC-1)", + ( + "dfetch's CI detects vulnerabilities at commit time (C-015, C-016, C-017) " + "but does not gate the release publish on a CVE scan of runtime dependencies. " + "C-043 (planned) adds ``pip-audit`` or ``osv-scanner`` to the publish " + "workflow." + ), + ), + ( + "C-044 — Data minimisation policy" + + " (ECR-g, SO.DataMinimization → DTM-1)", + ( + "dfetch processes dependency metadata only. The ``.dfetch_data.yaml`` file " + "stores: ``remote_url`` (credentials stripped by C-036), ``revision``, " + "optional ``integrity.hash``, and ``last_fetch`` timestamp. Each field is " + "functionally necessary for ``dfetch check`` and ``dfetch freeze``. " + "No personal data is collected; no telemetry is sent. " + "C-044 formalises this assertion as a documented policy." + ), + ), + ( + "C-045 — Destination path sensitivity warning" + + " (ECR-i, SO.PreventAttackPropagation → LIM-2)", + ( + "A compromised upstream repository could instruct dfetch to overwrite " + "CI/CD configuration files (e.g. ``.github/workflows/``) via a malicious " + "``dst:`` value. C-045 (planned) adds a non-blocking warning when ``dst:`` " + "resolves to a security-sensitive path, following the ``plaintext_warning()`` " + "pattern in ``dfetch/manifest/project.py``." + ), + ), + ( + "C-046 — Exploit mitigation inventory" + + " (ECR-k, SO.ReduceImpactOfIncident → GEC-11)", + ( + "prEN 40000-1-4 ECR-k requires documenting applicable exploit mitigation " + "techniques. For dfetch (pure Python):\n\n" + "- **ASLR / DEP / stack canaries**: provided by CPython and the OS; " + " not in dfetch's control but inherited.\n" + "- **No eval/exec of remote content**: dfetch never evaluates fetched " + " content as code.\n" + "- **Constant-time comparison** (C-005): HMAC-based integrity hash uses " + " ``hmac.compare_digest``.\n" + "- **No shell injection** (C-007): all subprocess calls use ``shell=False``.\n" + "- **Input validation** (C-008): URL scheme, path, and revision inputs " + " are validated.\n" + "- **Static analysis** (C-015, C-017): CodeQL and bandit gate every commit.\n" + "- CFI, sandboxing, and signed-execution policies are not applicable to " + " a pure-Python tool." + ), + ), + ] + + +def _render_gap_analysis() -> None: + """Print the Gap Analysis section.""" + print(_rst_title("Gap Analysis — Compliance-Only Controls", "-")) + print( + "Three CRA essential requirements were not independently surfaced by " + "the Track A risk models. The following controls address them.\n" + ) + for title, body in _gap_entries(): + print(f"**{title}**\n") + print(f"{body}\n") + + +def _render_control_register() -> None: + """Print the final merged control register table.""" + print(_rst_title("Final Control Register", "-")) + print( + "All controls from Track A (risk-driven) and Track B (regulatory) merged and " + "sorted. Track B controls (C-043–C-046) are marked accordingly.\n" + ) + track_b_ids = {c.id for c in TRACK_B_CONTROLS} + rows = [ + [ + ctrl.id, + ctrl.name, + "Track B" if ctrl.id in track_b_ids else "Track A", + ctrl.reference or "—", + ] + for ctrl in get_all_controls() + ] + print( + _rst_list_table( + ["ID", "Name", "Track", "Reference"], + rows, + widths=[8, 40, 10, 42], + ) + ) + + +def render_rst() -> None: + """Print the full compliance track RST document to stdout.""" + print(".. _compliance_track:\n") + print(_rst_title("CRA Compliance Track B")) + print( + ".. note::\n\n" + " dfetch is **non-commercial open-source software** and is exempt from\n" + " mandatory CRA obligations under Recital 18 of Regulation (EU) 2024/2847.\n" + " This document is produced voluntarily under Article 13(5) to support\n" + " downstream integrators who must account for open-source components in\n" + " their own conformity assessments.\n" + ) + print( + "Three-tier traceability::\n\n" + " CRA Annex I Essential Requirement (ECR-a … ECR-m)\n" + " ↓\n" + " prEN 40000-1-4 Security Objective (SO.*)\n" + " ↓\n" + " dfetch control (C-001 … C-046) or documented gap\n" + ) + print( + "Machine-readable OSCAL 1.1.2 artifacts are kept alongside the source:\n\n" + "- ``security/cra_pren_4000014_oscal_catalog.json`` — prEN 40000-1-4 catalog\n" + "- ``security/dfetch.component-definition.json`` — dfetch Component Definition\n" + ) + _render_classification() + _render_standards_table() + _render_part_i_table() + _render_part_ii_table() + _render_gap_analysis() + _render_control_register() + print(_rst_title("OSCAL Artifacts", "-")) + print( + "The OSCAL 1.1.2 Component Definition references the catalog file and can be\n" + "regenerated with:\n\n" + ".. code-block:: bash\n\n" + " python -m security.compliance \\\\\n" + " --component security/dfetch.component-definition.json \\\\\n" + " --rst > doc/explanation/compliance_track.rst\n" + ) + + +# ── Entry point ─────────────────────────────────────────────────────────────── + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description="Generate CRA Compliance Track B artifacts" + ) + parser.add_argument( + "--component", metavar="FILE", help="Write OSCAL Component Definition JSON" + ) + parser.add_argument( + "--rst", action="store_true", help="Print RST document to stdout" + ) + parser.add_argument( + "--version", default="0.14.0", help="dfetch version (default: 0.14.0)" + ) + args = parser.parse_args() + + if args.component: + comp_def = build_oscal_component_definition(version=args.version) + with open(args.component, "w", encoding="utf-8") as fh: + json.dump(comp_def, fh, indent=2) + print(f"Written: {args.component}", file=sys.stderr) + + if args.rst: + render_rst() diff --git a/security/compliance_data.py b/security/compliance_data.py new file mode 100644 index 00000000..f7cba85b --- /dev/null +++ b/security/compliance_data.py @@ -0,0 +1,648 @@ +"""Static compliance data for CRA Track B. + +Contains all data classes and constants used by compliance.py. +Kept in a separate module to stay within the 1000-line limit per file. +""" + +from dataclasses import dataclass, field +from typing import Literal + + +@dataclass +class Control: + """An implemented security control (mirrors tm_elements.Control).""" + + id: str + name: str + description: str + assets: list[str] = field(default_factory=list) + threats: list[str] = field(default_factory=list) + reference: str = "" + + +@dataclass +class ApplicableStandard: + """One standard assessed for applicability to dfetch.""" + + name: str + reference: str + applies: bool + scope_note: str + gap_note: str = "" + + +@dataclass +class SOImplementation: + """Dfetch's implementation of one prEN 40000-1-4 Security Objective.""" + + so_id: str + ecr_id: str + controls: list[str] = field(default_factory=list) + not_applicable: list[str] = field(default_factory=list) + gaps: list[str] = field(default_factory=list) + status: Literal[ + "implemented", "partially-implemented", "planned", "not-applicable" + ] = "partially-implemented" + description: str = "" + + +@dataclass +class PartIIRequirement: + """One CRA Annex I Part II requirement (covered by prEN 40000-1-3).""" + + id: str + ref: str + text: str + controls: list[str] = field(default_factory=list) + gaps: list[str] = field(default_factory=list) + status: Literal[ + "implemented", "partially-implemented", "planned", "not-applicable" + ] = "partially-implemented" + + +# ── Classification decision ─────────────────────────────────────────────────── + +CLASSIFICATION_DECISION: dict[str, str] = { + "Product type": "Software tool (CLI) — Python package distributed via PyPI", + "CRA classification": "Non-commercial open-source software (Recital 18 exemption)", + "Legal basis": "Article 3(14), Recital 18, Article 13(5) of Regulation (EU) 2024/2847", + "Mandatory obligations": "None — not a commercial product; no CE marking required", + "Voluntary alignment": ( + "This Track B document is produced voluntarily under Article 13(5) to support " + "downstream integrators who must account for open-source components in their " + "own CRA conformity assessments." + ), +} + +# ── Applicable standards ────────────────────────────────────────────────────── + +STANDARDS: list[ApplicableStandard] = [ + ApplicableStandard( + name="prEN 40000-1-2", + reference="Cyber Resilience Principles and Risk Management", + applies=True, + scope_note=( + "Process standard covering risk-based product security across the lifecycle. " + "The Product Security Context (§6.2) is documented in security.rst. " + "Track A threat models (tm_supply_chain.py, tm_usage.py) implement §6.3–§6.6." + ), + ), + ApplicableStandard( + name="prEN 40000-1-3", + reference="Vulnerability Handling Requirements", + applies=True, + scope_note=( + "Covers CRA Annex I Part II vulnerability handling obligations. " + "Addressed in the Part II table below via SECURITY.md, SBOM (C-022), " + "and dependency-review CI (C-016)." + ), + gap_note="No formal patch SLA or LTS backport policy defined.", + ), + ApplicableStandard( + name="prEN 40000-1-4", + reference="Generic Security Requirements (draft, indicative publication October 2027)", + applies=True, + scope_note=( + "Primary standard for this document. Maps CRA Annex I Part I Art. 2(a)–(m) " + "to Security Objectives (SO.*) and Technical Controls (GEC-*, SUM-*, etc.). " + "The catalog is included as security/cra_pren_4000014_oscal_catalog.json." + ), + gap_note="Standard is in draft; final clause numbering may change.", + ), + ApplicableStandard( + name="EN 18031-1/2:2024", + reference="Common security requirements for radio equipment (basis of prEN 40000-1-4)", + applies=True, + scope_note=( + "prEN 40000-1-4 builds on EN 18031. Many technical controls (GEC-*, SUM-*, " + "AUM-*, SSM-*, SCM-*) originate from EN 18031. dfetch's applicability is " + "assessed at the prEN 40000-1-4 SO level." + ), + ), + ApplicableStandard( + name="ETSI EN 303 645 V3.1.3", + reference="Cyber Security for Consumer Internet of Things", + applies=False, + scope_note=( + "IoT-specific standard. dfetch is a developer CLI tool with no IoT " + "device functionality, physical interfaces, or consumer IoT use case." + ), + ), +] + +# ── Track B controls (C-043–C-046) ─────────────────────────────────────────── + +TRACK_B_CONTROLS: list[Control] = [ + Control( + id="C-043", + name="Release-gate CVE check on runtime dependencies", + description=( + "Run pip-audit or osv-scanner at release time against dfetch's runtime " + "dependencies; fail the publish workflow if any Critical/High CVE is " + "unresolved." + ), + threats=["Threat.KnownVulnerabilityExploitation"], + reference=".github/workflows/python-publish.yml (planned CI addition)", + ), + Control( + id="C-044", + name="Data minimisation policy", + description=( + "Documented assertion that .dfetch_data.yaml stores only remote_url " + "(credentials stripped — C-036), revision, optional integrity hash, and " + "last_fetch timestamp; each field justified by functional necessity for " + "dfetch check and dfetch freeze. No personal data processed; no telemetry." + ), + threats=["Threat.UnnecessaryDataMisuse"], + reference="doc/explanation/compliance_track.rst (this document)", + ), + Control( + id="C-045", + name="Destination path sensitivity warning", + description=( + "Non-blocking warning when dst: resolves to a security-sensitive path " + "(.github/workflows, .gitlab-ci.yml, CI/CD directories), following the " + "plaintext_warning() pattern." + ), + threats=["Threat.ExtServiceAvailabilityDegradation"], + reference="dfetch/project/subproject.py (planned: _is_sensitive_dst())", + ), + Control( + id="C-046", + name="Exploit mitigation inventory", + description=( + "Documented inventory: Python interpreter provides ASLR/DEP/stack-canaries " + "(OS-level); no eval/exec of remote content; constant-time hash comparison " + "(C-005); shell=False for all subprocess calls (C-007); input validation " + "(C-008); static analysis gating (C-015, C-017). CFI/sandboxing not " + "applicable — pure Python." + ), + threats=["Threat.ExploitationMitigationFailure"], + reference="doc/explanation/compliance_track.rst (this document)", + ), +] + +# ── Security Objective implementations ─────────────────────────────────────── + +SO_IMPLEMENTATIONS: list[SOImplementation] = [ + # ECR-a: Vulnerability Assessment + SOImplementation( + so_id="so-vulnerability-management-process", + ecr_id="ecr-a", + controls=["C-015", "C-016", "C-017", "C-022"], + gaps=["No CVE gate at release time (→ C-043 planned)"], + status="partially-implemented", + description=( + "GEC-1: C-015 (CodeQL), C-016 (dependency-review), C-017 (bandit), " + "C-022 (SBOM) address known-vulnerability detection in CI." + ), + ), + # ECR-b: Secure Configuration + SOImplementation( + so_id="so-secure-default-configuration", + ecr_id="ecr-b", + controls=["C-001", "C-002"], + not_applicable=[ + "GEC-2, GEC-3, GEC-4, GEC-5 (no exposed network services — CLI tool)", + "GEC-7 (no external sensing capabilities)", + "AUM-5 (no password or authentication mechanism)", + ], + status="partially-implemented", + description=( + "GEC-12 (no unneeded software components): C-001 enforces minimal " + "runtime dependencies. dfetch does not expose network services." + ), + ), + SOImplementation( + so_id="so-secure-startup-config", + ecr_id="ecr-b", + not_applicable=[ + "GEC-9 (no security-relevant startup configuration state in dfetch)" + ], + status="not-applicable", + description="dfetch reads only its manifest at startup; no security-sensitive config initialisation.", + ), + SOImplementation( + so_id="so-factory-reset", + ecr_id="ecr-b", + not_applicable=[ + "DLM-1-b, GEC-10 (no persistent device state requiring factory reset — CLI tool)" + ], + status="not-applicable", + description="dfetch is a stateless CLI tool; no factory-reset concept applies.", + ), + # ECR-c: Security Updates + SOImplementation( + so_id="so-updateability", + ecr_id="ecr-c", + status="implemented", + description=( + "SUM-1/SUM-2: Updates distributed via PyPI (pip install --upgrade dfetch) " + "and GitHub Releases. pip's TLS-protected download satisfies SUM-2." + ), + ), + SOImplementation( + so_id="so-automatic-updates", + ecr_id="ecr-c", + not_applicable=[ + "SUM-3 (automatic updates managed by pip/pipenv/poetry — not dfetch itself)" + ], + status="not-applicable", + description="Update automation is the responsibility of the user's package manager.", + ), + SOImplementation( + so_id="so-user-update-notification", + ecr_id="ecr-c", + controls=["C-040"], + status="implemented", + description=( + "UNM-4: dfetch check and dfetch environment report when a newer dfetch " + "version is available (C-040)." + ), + ), + SOImplementation( + so_id="so-postpone-updates", + ecr_id="ecr-c", + not_applicable=[ + "SUM-4 (update scheduling controlled by pip/pipenv — not dfetch)" + ], + status="not-applicable", + description="Postponement is handled by the user's package manager.", + ), + # ECR-d: Access Control + SOImplementation( + so_id="so-access-control", + ecr_id="ecr-d", + controls=["C-006", "C-036"], + not_applicable=[ + "ACM-2, AUM-2, AUM-3, AUM-4, AUM-6 " + "(dfetch has no user-facing authentication or access control)" + ], + status="partially-implemented", + description=( + "dfetch delegates authentication to the host VCS client (git, svn) and " + "the OS credential store. C-006 prevents SSH command injection; " + "C-036 strips credentials from stored metadata." + ), + ), + SOImplementation( + so_id="so-access-control-report", + ecr_id="ecr-d", + controls=["C-009"], + gaps=["No persistent log of unauthorised access attempts"], + status="partially-implemented", + description=( + "GEC-13: C-009 (plaintext transport warning) alerts on unauthenticated " + "connections. No persistent security event log." + ), + ), + # ECR-e: Confidentiality + SOImplementation( + so_id="so-data-stored-confidentiality", + ecr_id="ecr-e", + controls=["C-036"], + gaps=[".dfetch_data.yaml stored as plaintext YAML (no encryption at rest)"], + status="partially-implemented", + description=( + "SSM-1/SSM-3: C-036 strips userinfo from URLs before storage. The " + ".dfetch_data.yaml file itself is not encrypted; encryption would require " + "key management infrastructure not appropriate for a developer tool." + ), + ), + SOImplementation( + so_id="so-data-processed-confidentiality", + ecr_id="ecr-e", + controls=["C-005", "C-034"], + status="implemented", + description=( + "GEC-8: C-005 (constant-time comparison), C-034 (temp-file cleanup) " + "protect in-process data." + ), + ), + SOImplementation( + so_id="so-data-transmitted-confidentiality", + ecr_id="ecr-e", + controls=["C-009"], + gaps=[ + "HTTP accepted for remote URLs (SCM-3/SCM-4 gap; mitigated by C-009 warning)" + ], + status="partially-implemented", + description=( + "SCM-3/SCM-4: dfetch warns on plaintext transport (C-009) but accepts " + "HTTP remote URLs to avoid breaking existing manifests." + ), + ), + SOImplementation( + so_id="so-com-auth-e", + ecr_id="ecr-e", + controls=["C-003", "C-004"], + gaps=["No end-to-end authenticity verification for plain git/svn transport"], + status="partially-implemented", + description=( + "SCM-2: C-003 (TLS CA verification for HTTPS) and C-004 (SSH host-key " + "checking) provide authenticity for most connections." + ), + ), + SOImplementation( + so_id="so-secure-provisioning", + ecr_id="ecr-e", + controls=["C-005"], + not_applicable=["CCK-1, CCK-2, CCK-3 (dfetch manages no cryptographic keys)"], + status="partially-implemented", + description=( + "CRY-1: C-005 uses Python's hashlib with SHA-256 for integrity hashes. " + "No key management is required or performed by dfetch." + ), + ), + # ECR-f: Integrity + SOImplementation( + so_id="so-data-stored-integrity", + ecr_id="ecr-f", + controls=["C-005"], + gaps=["Integrity hash opt-in only; not enforced by default for git/svn"], + status="partially-implemented", + description=( + "SSM-2: C-005 (integrity hash in .dfetch_data.yaml) provides optional " + "stored-data integrity verification." + ), + ), + SOImplementation( + so_id="so-data-processed-integrity", + ecr_id="ecr-f", + controls=["C-005", "C-034"], + status="implemented", + description="GEC-8: C-005 and C-034 protect data integrity during processing.", + ), + SOImplementation( + so_id="so-data-transmitted-integrity", + ecr_id="ecr-f", + controls=["C-003", "C-004"], + gaps=[ + "No end-to-end hash for git/svn transport beyond TLS/SSH channel integrity" + ], + status="partially-implemented", + description=( + "SCM-2: TLS (C-003) and SSH (C-004) provide channel-level integrity. " + "Commit-hash pinning (rev: ) provides content-level integrity for git." + ), + ), + SOImplementation( + so_id="so-integrity-report", + ecr_id="ecr-f", + controls=["C-009"], + gaps=["No persistent integrity-violation log"], + status="partially-implemented", + description=( + "GEC-13-f: dfetch surfaces transport-integrity warnings (C-009) at runtime " + "but does not maintain a persistent security event log." + ), + ), + # ECR-g: Data Minimisation + SOImplementation( + so_id="so-data-minimization", + ecr_id="ecr-g", + controls=["C-044"], + not_applicable=[ + "UNM-1, UNM-2 (dfetch processes no personal data requiring user notification)", + "DTM-3 (no optional data processing to configure)", + ], + status="planned", + description=( + "DTM-1: C-044 (planned) documents that .dfetch_data.yaml is limited to " + "remote_url (stripped), revision, optional hash, and last_fetch — each " + "justified by functional necessity. " + "DTM-2: met by design — dfetch collects no telemetry or optional data." + ), + ), + # ECR-h: Availability + SOImplementation( + so_id="so-incident-recovery", + ecr_id="ecr-h", + not_applicable=[ + "RLM-2, RLM-6 (CLI tool; no persistent device state or control-system backup needed)" + ], + status="not-applicable", + description=( + "dfetch is a stateless CLI tool. Recovery consists of re-running " + "dfetch update, which re-fetches all dependencies." + ), + ), + SOImplementation( + so_id="so-incident-resilience", + ecr_id="ecr-h", + controls=["C-002", "C-007"], + not_applicable=[ + "RLM-3, RLM-4, RLM-5 (no persistent network services to prioritize)" + ], + gaps=["No timeout on VCS operations (potential resource exhaustion)"], + status="partially-implemented", + description=( + "RLM-1: C-002 (no background daemon) and C-007 (subprocess controls) " + "reduce exposure. DoS resilience applies only to the transient fetch operation." + ), + ), + # ECR-i: Minimize Negative Impact + SOImplementation( + so_id="so-limit-external-impact", + ecr_id="ecr-i", + controls=["C-001", "C-007"], + not_applicable=[ + "TCM-1 (dfetch makes targeted VCS fetch requests; " + "no ambient outbound traffic to throttle)" + ], + status="partially-implemented", + description=( + "GEC-8-i: C-001 (minimal deps) and C-007 (subprocess controls) reduce " + "the risk of dfetch being weaponised against external services. " + "LIM-1: dfetch fetches only what is listed in the manifest." + ), + ), + SOImplementation( + so_id="so-prevent-attack-propagation", + ecr_id="ecr-i", + controls=["C-045"], + gaps=[ + "No warning when dst: targets security-sensitive paths (→ C-045 planned)" + ], + status="planned", + description=( + "LIM-2: C-045 (planned) warns when dst: resolves to paths such as " + ".github/workflows or .gitlab-ci.yml that could propagate a supply-chain " + "attack to CI/CD infrastructure." + ), + ), + SOImplementation( + so_id="so-monitor-external-impact", + ecr_id="ecr-i", + not_applicable=[ + "NMM-1 (dfetch makes no ambient outbound network traffic to monitor)" + ], + status="not-applicable", + description="dfetch makes targeted, user-initiated VCS requests only.", + ), + # ECR-j: Attack Surface Minimization + SOImplementation( + so_id="so-reduce-attack-surface", + ecr_id="ecr-j", + controls=["C-001", "C-003", "C-004", "C-007", "C-008"], + not_applicable=[ + "GEC-2-j, GEC-3-j, GEC-4-j, GEC-5-j, GEC-7-j " + "(dfetch exposes no network services)" + ], + status="partially-implemented", + description=( + "GEC-6: C-007 (no shell=True), C-008 (URL/path validation) implement " + "input validation. GEC-12-j: C-001 enforces minimal runtime dependencies." + ), + ), + # ECR-k: Exploit Mitigation + SOImplementation( + so_id="so-reduce-impact-of-incident", + ecr_id="ecr-k", + controls=["C-005", "C-007", "C-015", "C-017", "C-046"], + not_applicable=[ + "Compile-time mitigations (CFI, sandboxing) — not applicable to pure Python" + ], + gaps=["No documented exploit mitigation inventory (→ C-046 planned)"], + status="planned", + description=( + "GEC-11: Python interpreter provides ASLR/DEP/stack-canaries (OS-level). " + "dfetch: no eval/exec of remote content; constant-time comparison (C-005); " + "shell=False (C-007); static analysis (C-015, C-017). " + "C-046 (planned) formalises this inventory." + ), + ), + # ECR-l: Monitoring and Logging + SOImplementation( + so_id="so-log-security-relevant-activities", + ecr_id="ecr-l", + controls=["C-036"], + gaps=[ + "No persistent security event log (LGM-2/3/4 gap)", + "No opt-out for logging — dfetch does not log by default", + ], + status="partially-implemented", + description=( + "LGM-1: dfetch logs warnings to stderr during a run but does not persist " + "them. LGM-6: C-036 ensures credentials are not logged." + ), + ), + SOImplementation( + so_id="so-monitor-security-relevant-activities", + ecr_id="ecr-l", + controls=["C-009"], + not_applicable=["NMM-1 (no ambient network monitoring)"], + status="partially-implemented", + description=( + "MON-1: C-009 (plaintext transport detection) monitors for insecure " + "connections at runtime and surfaces warnings to the user." + ), + ), + SOImplementation( + so_id="so-option-disable-data-logging", + ecr_id="ecr-l", + not_applicable=["LGM-5 (dfetch does not persist logs; nothing to disable)"], + status="not-applicable", + description="dfetch emits transient stderr warnings only; no persistent log to opt out of.", + ), + SOImplementation( + so_id="so-option-disable-data-monitoring", + ecr_id="ecr-l", + not_applicable=["MON-2 (no persistent monitoring to disable)"], + status="not-applicable", + description="dfetch performs no ongoing monitoring between invocations.", + ), + # ECR-m: Data Deletion + SOImplementation( + so_id="so-secure-data-deletion", + ecr_id="ecr-m", + gaps=[ + "No built-in delete command for .dfetch_data.yaml " + "(DLM-1 gap; user deletes manually)" + ], + status="partially-implemented", + description=( + "DLM-1: dfetch stores only dependency metadata (no personal data). " + "Users can permanently delete .dfetch_data.yaml and vendored directories." + ), + ), + SOImplementation( + so_id="so-data-transmitted-confidentiality-m", + ecr_id="ecr-m", + not_applicable=[ + "DLM-2, DLM-3, DLM-4 (dfetch does not export user data to external systems)" + ], + status="not-applicable", + description="dfetch does not provide data export or transfer functionality.", + ), + SOImplementation( + so_id="so-data-transmitted-integrity-m", + ecr_id="ecr-m", + not_applicable=["DLM-3 (no data export)"], + status="not-applicable", + description="Not applicable — see SO.DataTransmittedConfidentiality (data export context).", + ), + SOImplementation( + so_id="so-com-auth-m", + ecr_id="ecr-m", + not_applicable=["DLM-4 (no data export)"], + status="not-applicable", + description="Not applicable — see SO.DataTransmittedConfidentiality (data export context).", + ), +] + +# ── CRA Part II requirements (prEN 40000-1-3) ──────────────────────────────── + +PART_II_REQUIREMENTS: list[PartIIRequirement] = [ + PartIIRequirement( + id="pii-01", + ref="Part II §1", + text="Identify and document vulnerabilities and components (SBOM).", + controls=["C-021", "C-022"], + status="implemented", + ), + PartIIRequirement( + id="pii-02", + ref="Part II §2", + text="Address vulnerabilities without delay; provide free security updates.", + controls=["C-015", "C-016"], + gaps=["No formal patch SLA defined", "No backport/LTS commitment documented"], + status="partially-implemented", + ), + PartIIRequirement( + id="pii-03", + ref="Part II §3", + text="Apply effective coordinated vulnerability disclosure (CVD) policy.", + controls=["SECURITY.md"], + status="implemented", + ), + PartIIRequirement( + id="pii-04", + ref="Part II §4", + text="Report actively exploited vulnerabilities to national CSIRT and ENISA.", + status="not-applicable", + ), + PartIIRequirement( + id="pii-05", + ref="Part II §5", + text="Publish coordinated vulnerability disclosure policy.", + controls=["SECURITY.md"], + status="implemented", + ), + PartIIRequirement( + id="pii-06", + ref="Part II §6", + text="Share information on vulnerabilities in integrated components.", + controls=["C-022", "C-016"], + gaps=["No proactive downstream notification process"], + status="partially-implemented", + ), + PartIIRequirement( + id="pii-07", + ref="Part II §7", + text="Provide security updates free of charge for the support period.", + controls=["MIT licence", "PyPI"], + gaps=["No support period or LTS policy documented"], + status="partially-implemented", + ), +] diff --git a/security/cra_pren_4000014_oscal_catalog.json b/security/cra_pren_4000014_oscal_catalog.json new file mode 100644 index 00000000..d848aa0e --- /dev/null +++ b/security/cra_pren_4000014_oscal_catalog.json @@ -0,0 +1,1130 @@ +{ + "catalog": { + "uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", + "metadata": { + "title": "CRA Security Controls Catalog – prEN 40000-1-4 (PT2 Generic Security Requirements)", + "last-modified": "2026-06-15T00:00:00Z", + "version": "draft-2026-03-05", + "oscal-version": "1.1.2", + "remarks": "Derived from the CEN/CLC/JTC 13/WG 9 PT2 deep-dive session by Angelo D'Amato (Vulnir B.V.), 5 March 2026, under STAN4CR Grant Agreement No. 101196779. Maps CRA Annex I Part I Essential Requirements (a)–(m) to security objectives and technical controls from prEN 40000-1-4, building on EN 18031:2024 with new controls for CRA scope.", + "props": [ + { "name": "regulation", "value": "Regulation (EU) 2024/2847 – Cyber Resilience Act" }, + { "name": "standard-reference", "value": "prEN 40000-1-4" }, + { "name": "source-presentation", "value": "2026-03-05_CRA_Unlocked_DeepDive_SecurityControls_CEN-CLC-JTC-13-WG-9_PT2.pdf" }, + { "name": "status", "value": "draft – indicative publication October 2027" } + ] + }, + "groups": [ + { + "id": "ecr-a", + "title": "ECR (a) – Vulnerability Assessment", + "class": "essential-requirement", + "props": [ + { "name": "cra-annex-ref", "value": "Annex I, Part I, Art. 2(a)" }, + { "name": "requirement-text", "value": "Be made available on the market without known exploitable vulnerabilities." } + ], + "parts": [ + { + "id": "ecr-a-threats", + "name": "threats", + "prose": "Threat.KnownVulnerabilityExploitation" + } + ], + "controls": [ + { + "id": "so-vulnerability-management-process", + "class": "security-objective", + "title": "SO.VulnerabilityManagementProcess", + "parts": [ + { + "id": "so-vmp-stmt", + "name": "statement", + "prose": "Ensure that the product is placed on the market without known exploitable vulnerabilities by maintaining a vulnerability management process including asset lists, EUVD monitoring, and supply-chain due diligence." + } + ], + "controls": [ + { + "id": "gec-1", + "class": "technical-control", + "title": "[GEC-1] Up-to-date software and hardware without known exploitable vulnerabilities", + "props": [ + { "name": "control-family", "value": "GEC – General Equipment Capabilities" }, + { "name": "source", "value": "EN 18031" }, + { "name": "status", "value": "existing" } + ], + "parts": [ + { + "id": "gec-1-stmt", + "name": "statement", + "prose": "The product shall be made available on the market with up-to-date software and hardware that do not contain publicly known exploitable vulnerabilities under practical operational conditions." + } + ] + } + ] + } + ] + }, + { + "id": "ecr-b", + "title": "ECR (b) – Secure Configuration", + "class": "essential-requirement", + "props": [ + { "name": "cra-annex-ref", "value": "Annex I, Part I, Art. 2(b)" }, + { "name": "requirement-text", "value": "Be made available on the market with a secure by default configuration, including the possibility to reset the product to its original state." } + ], + "parts": [ + { + "id": "ecr-b-threats", + "name": "threats", + "prose": "Threat.UnsecureDefaultConfigExploitation | Threat.StartupConfigExploitation | Threat.MissingResetFunctionalityConfigExploitation" + } + ], + "controls": [ + { + "id": "so-secure-default-configuration", + "class": "security-objective", + "title": "SO.SecureDefaultConfiguration", + "parts": [ + { + "id": "so-sdc-stmt", + "name": "statement", + "prose": "Ensure the product is delivered with a secure default configuration, minimising exposed interfaces and services." + } + ], + "controls": [ + { + "id": "gec-2", + "class": "technical-control", + "title": "[GEC-2] Limit exposure of services via related network interfaces", + "props": [{ "name": "control-family", "value": "GEC" }, { "name": "source", "value": "EN 18031" }], + "parts": [{ "id": "gec-2-stmt", "name": "statement", "prose": "The product shall limit exposure of services via related network interfaces." }] + }, + { + "id": "gec-3", + "class": "technical-control", + "title": "[GEC-3] Configuration of optional services and related exposed network interfaces", + "props": [{ "name": "control-family", "value": "GEC" }, { "name": "source", "value": "EN 18031" }], + "parts": [{ "id": "gec-3-stmt", "name": "statement", "prose": "Optional services and their related exposed network interfaces shall be configurable." }] + }, + { + "id": "gec-4", + "class": "technical-control", + "title": "[GEC-4] Documentation of exposed network interfaces and services", + "props": [{ "name": "control-family", "value": "GEC" }, { "name": "source", "value": "EN 18031" }], + "parts": [{ "id": "gec-4-stmt", "name": "statement", "prose": "Exposed network interfaces and services shall be documented." }] + }, + { + "id": "gec-5", + "class": "technical-control", + "title": "[GEC-5] No unnecessary external interfaces", + "props": [{ "name": "control-family", "value": "GEC" }, { "name": "source", "value": "EN 18031" }], + "parts": [{ "id": "gec-5-stmt", "name": "statement", "prose": "The product shall not include unnecessary external interfaces." }] + }, + { + "id": "gec-7", + "class": "technical-control", + "title": "[GEC-7] Documentation of external sensing capabilities", + "props": [{ "name": "control-family", "value": "GEC" }, { "name": "source", "value": "EN 18031" }], + "parts": [{ "id": "gec-7-stmt", "name": "statement", "prose": "External sensing capabilities shall be documented." }] + }, + { + "id": "gec-12", + "class": "technical-control", + "title": "[GEC-12] No Unneeded Software Components", + "props": [{ "name": "control-family", "value": "GEC" }, { "name": "source", "value": "NEW" }], + "parts": [{ "id": "gec-12-stmt", "name": "statement", "prose": "The product shall not include software components that are not required for its intended functionality." }] + }, + { + "id": "aum-5", + "class": "technical-control", + "title": "[AUM-5] Password strength", + "props": [{ "name": "control-family", "value": "AUM – Authentication Mechanism" }, { "name": "source", "value": "EN 18031" }], + "parts": [{ "id": "aum-5-stmt", "name": "statement", "prose": "The product shall enforce password strength requirements." }], + "controls": [ + { + "id": "aum-5-1", + "class": "technical-control", + "title": "[AUM-5-1] Requirement for factory default passwords", + "props": [{ "name": "source", "value": "EN 18031" }], + "parts": [{ "id": "aum-5-1-stmt", "name": "statement", "prose": "Factory default passwords shall meet defined strength requirements." }] + }, + { + "id": "aum-5-2", + "class": "technical-control", + "title": "[AUM-5-2] Requirement for non-factory default passwords", + "props": [{ "name": "source", "value": "EN 18031" }], + "parts": [{ "id": "aum-5-2-stmt", "name": "statement", "prose": "Non-factory-default passwords shall meet defined strength requirements." }] + } + ] + } + ] + }, + { + "id": "so-secure-startup-config", + "class": "security-objective", + "title": "SO.SecureStartupConfig", + "controls": [ + { + "id": "gec-9", + "class": "technical-control", + "title": "[GEC-9] Secure startup configuration", + "props": [{ "name": "control-family", "value": "GEC" }, { "name": "source", "value": "NEW" }], + "parts": [{ "id": "gec-9-stmt", "name": "statement", "prose": "The product shall enforce a secure configuration during the startup process." }] + } + ] + }, + { + "id": "so-factory-reset", + "class": "security-objective", + "title": "SO.FactoryReset", + "controls": [ + { + "id": "dlm-1-b", + "class": "technical-control", + "title": "[DLM-1] Applicability of deletion mechanisms (factory reset context)", + "props": [{ "name": "control-family", "value": "DLM – Deletion Mechanism" }, { "name": "source", "value": "EN 18031" }], + "parts": [{ "id": "dlm-1-b-stmt", "name": "statement", "prose": "Deletion mechanisms shall be applicable to support factory reset functionality." }] + }, + { + "id": "gec-10", + "class": "technical-control", + "title": "[GEC-10] Factory Reset", + "props": [{ "name": "control-family", "value": "GEC" }, { "name": "source", "value": "NEW" }], + "parts": [{ "id": "gec-10-stmt", "name": "statement", "prose": "The product shall provide a factory reset function to restore the original state." }] + } + ] + } + ] + }, + { + "id": "ecr-c", + "title": "ECR (c) – Security Updates", + "class": "essential-requirement", + "props": [ + { "name": "cra-annex-ref", "value": "Annex I, Part I, Art. 2(c)" }, + { "name": "requirement-text", "value": "Ensure that vulnerabilities can be addressed through security updates, including automatic updates enabled by default, with an opt-out mechanism, user notification, and the option to postpone updates." } + ], + "parts": [ + { + "id": "ecr-c-threats", + "name": "threats", + "prose": "Threat.UnpatchableVulnerabilityExploitation | Threat.UnpatchedVulnerabilityExploitation | Threat.MissingUpdateNotificationExploitation | Threat.FunctionalityUpdateInterruption" + } + ], + "controls": [ + { + "id": "so-updateability", + "class": "security-objective", + "title": "SO.Updateability", + "controls": [ + { + "id": "sum-1", + "class": "technical-control", + "title": "[SUM-1] Applicability of update mechanisms", + "props": [{ "name": "control-family", "value": "SUM – Secure Update Mechanism" }, { "name": "source", "value": "EN 18031" }], + "parts": [{ "id": "sum-1-stmt", "name": "statement", "prose": "The product shall include applicable and appropriate update mechanisms." }] + }, + { + "id": "sum-2", + "class": "technical-control", + "title": "[SUM-2] Secure updates", + "props": [{ "name": "control-family", "value": "SUM" }, { "name": "source", "value": "EN 18031" }], + "parts": [{ "id": "sum-2-stmt", "name": "statement", "prose": "Updates shall be delivered and installed securely." }] + } + ] + }, + { + "id": "so-automatic-updates", + "class": "security-objective", + "title": "SO.AutomaticUpdates", + "controls": [ + { + "id": "sum-3", + "class": "technical-control", + "title": "[SUM-3] Automated updates", + "props": [{ "name": "control-family", "value": "SUM" }, { "name": "source", "value": "EN 18031" }], + "parts": [{ "id": "sum-3-stmt", "name": "statement", "prose": "The product shall support automated security updates enabled by default, with a clear opt-out mechanism." }] + } + ] + }, + { + "id": "so-user-update-notification", + "class": "security-objective", + "title": "SO.UserUpdateNotification", + "controls": [ + { + "id": "unm-4", + "class": "technical-control", + "title": "[UNM-4] User Security Update Notifications", + "props": [{ "name": "control-family", "value": "UNM – User Notification Mechanism" }, { "name": "source", "value": "NEW" }], + "parts": [{ "id": "unm-4-stmt", "name": "statement", "prose": "The product shall notify users of available security updates." }] + } + ] + }, + { + "id": "so-postpone-updates", + "class": "security-objective", + "title": "SO.PostponeUpdates", + "controls": [ + { + "id": "sum-4", + "class": "technical-control", + "title": "[SUM-4] Postponed Updates", + "props": [{ "name": "control-family", "value": "SUM" }, { "name": "source", "value": "NEW" }], + "parts": [{ "id": "sum-4-stmt", "name": "statement", "prose": "The product shall allow users to temporarily postpone security updates." }] + } + ] + } + ] + }, + { + "id": "ecr-d", + "title": "ECR (d) – Access Control", + "class": "essential-requirement", + "props": [ + { "name": "cra-annex-ref", "value": "Annex I, Part I, Art. 2(d)" }, + { "name": "requirement-text", "value": "Ensure protection from unauthorised access by appropriate control mechanisms including authentication, identity or access management systems, and report on possible unauthorised access." } + ], + "parts": [ + { + "id": "ecr-d-threats", + "name": "threats", + "prose": "Threat.UnauthorizedAccess | Threat.NotReportedUnauthorizedAccess" + } + ], + "controls": [ + { + "id": "so-access-control", + "class": "security-objective", + "title": "SO.AccessControl", + "controls": [ + { + "id": "acm-2", + "class": "technical-control", + "title": "[ACM-2] Appropriate access control mechanisms", + "props": [{ "name": "control-family", "value": "ACM – Access Control Mechanism" }, { "name": "source", "value": "EN 18031" }, { "name": "note", "value": "Merges former ACM-1 and ACM-2" }], + "parts": [{ "id": "acm-2-stmt", "name": "statement", "prose": "The product shall implement appropriate access control mechanisms." }] + }, + { + "id": "aum-2", + "class": "technical-control", + "title": "[AUM-2] Appropriate authentication mechanisms", + "props": [{ "name": "control-family", "value": "AUM" }, { "name": "source", "value": "EN 18031" }, { "name": "note", "value": "Merges former AUM-1 and AUM-2" }], + "parts": [{ "id": "aum-2-stmt", "name": "statement", "prose": "The product shall implement appropriate authentication mechanisms." }], + "controls": [ + { + "id": "aum-2-1", + "class": "technical-control", + "title": "[AUM-2-1] Requirement one-factor authentication", + "props": [{ "name": "source", "value": "EN 18031" }], + "parts": [{ "id": "aum-2-1-stmt", "name": "statement", "prose": "Where one-factor authentication is used, the mechanism shall meet defined requirements." }] + }, + { + "id": "aum-2-2", + "class": "technical-control", + "title": "[AUM-2-2] Requirement multi-factor authentication", + "props": [{ "name": "source", "value": "EN 18031" }], + "parts": [{ "id": "aum-2-2-stmt", "name": "statement", "prose": "Where multi-factor authentication is available, the mechanism shall meet defined requirements." }] + } + ] + }, + { + "id": "aum-3", + "class": "technical-control", + "title": "[AUM-3] Authenticator validation", + "props": [{ "name": "control-family", "value": "AUM" }, { "name": "source", "value": "EN 18031" }], + "parts": [{ "id": "aum-3-stmt", "name": "statement", "prose": "The product shall validate authenticators appropriately." }] + }, + { + "id": "aum-4", + "class": "technical-control", + "title": "[AUM-4] Changing authenticators", + "props": [{ "name": "control-family", "value": "AUM" }, { "name": "source", "value": "EN 18031" }], + "parts": [{ "id": "aum-4-stmt", "name": "statement", "prose": "The product shall support the changing of authenticators." }] + }, + { + "id": "aum-6", + "class": "technical-control", + "title": "[AUM-6] Brute force protection", + "props": [{ "name": "control-family", "value": "AUM" }, { "name": "source", "value": "EN 18031" }], + "parts": [{ "id": "aum-6-stmt", "name": "statement", "prose": "The product shall implement brute force protection for authentication mechanisms." }] + } + ] + }, + { + "id": "so-access-control-report", + "class": "security-objective", + "title": "SO.AccessControlReport", + "controls": [ + { + "id": "gec-13", + "class": "technical-control", + "title": "[GEC-13] Report on possible unauthorised access and corruption", + "props": [{ "name": "control-family", "value": "GEC" }, { "name": "source", "value": "NEW" }], + "parts": [{ "id": "gec-13-stmt", "name": "statement", "prose": "The product shall report on possible unauthorised access attempts and detected corruption." }] + } + ] + } + ] + }, + { + "id": "ecr-e", + "title": "ECR (e) – Confidentiality (Disclosure)", + "class": "essential-requirement", + "props": [ + { "name": "cra-annex-ref", "value": "Annex I, Part I, Art. 2(e)" }, + { "name": "requirement-text", "value": "Protect the confidentiality of stored, transmitted or otherwise processed data by state-of-the-art mechanisms such as encryption at rest and in transit." } + ], + "parts": [ + { + "id": "ecr-e-threats", + "name": "threats", + "prose": "Threat.DataAtRestDisclosure | Threat.DataProcessedDataDisclosure | Threat.DataInTransitDisclosure" + } + ], + "controls": [ + { + "id": "so-data-stored-confidentiality", + "class": "security-objective", + "title": "SO.DataStoredConfidentiality", + "controls": [ + { + "id": "ssm-1", + "class": "technical-control", + "title": "[SSM-1] Applicability of secure storage mechanisms", + "props": [{ "name": "control-family", "value": "SSM – Secure Storage Mechanism" }, { "name": "source", "value": "EN 18031" }], + "parts": [{ "id": "ssm-1-stmt", "name": "statement", "prose": "The product shall implement applicable secure storage mechanisms to protect data confidentiality at rest." }] + }, + { + "id": "ssm-3", + "class": "technical-control", + "title": "[SSM-3] Appropriate confidentiality protection for secure storage mechanisms", + "props": [{ "name": "control-family", "value": "SSM" }, { "name": "source", "value": "EN 18031" }], + "parts": [{ "id": "ssm-3-stmt", "name": "statement", "prose": "Secure storage mechanisms shall provide appropriate confidentiality protection." }] + } + ] + }, + { + "id": "so-data-processed-confidentiality", + "class": "security-objective", + "title": "SO.DataProcessedConfidentiality", + "controls": [ + { + "id": "gec-8", + "class": "technical-control", + "title": "[GEC-8] Product Integrity", + "props": [{ "name": "control-family", "value": "GEC" }, { "name": "source", "value": "EN 18031" }], + "parts": [{ "id": "gec-8-stmt", "name": "statement", "prose": "The product shall protect the integrity of its software, firmware, and data during processing." }] + } + ] + }, + { + "id": "so-data-transmitted-confidentiality", + "class": "security-objective", + "title": "SO.DataTransmittedConfidentiality", + "controls": [ + { + "id": "scm-3", + "class": "technical-control", + "title": "[SCM-3] Secure communication with confidentiality protection", + "props": [{ "name": "control-family", "value": "SCM – Secure Communication Mechanism" }, { "name": "source", "value": "EN 18031" }], + "parts": [{ "id": "scm-3-stmt", "name": "statement", "prose": "Secure communication mechanisms shall provide confidentiality protection for transmitted data." }] + }, + { + "id": "scm-4", + "class": "technical-control", + "title": "[SCM-4] Replay protection for secure communication mechanisms", + "props": [{ "name": "control-family", "value": "SCM" }, { "name": "source", "value": "EN 18031" }], + "parts": [{ "id": "scm-4-stmt", "name": "statement", "prose": "Secure communication mechanisms shall implement replay protection." }] + } + ] + }, + { + "id": "so-com-auth-e", + "class": "security-objective", + "title": "SO.ComAuth", + "controls": [ + { + "id": "scm-2", + "class": "technical-control", + "title": "[SCM-2] Appropriate integrity and authenticity protection for secure communication", + "props": [{ "name": "control-family", "value": "SCM" }, { "name": "source", "value": "EN 18031" }], + "parts": [{ "id": "scm-2-stmt", "name": "statement", "prose": "Secure communication mechanisms shall provide appropriate integrity and authenticity protection." }] + } + ] + }, + { + "id": "so-secure-provisioning", + "class": "security-objective", + "title": "SO.SecureProvisioning", + "controls": [ + { + "id": "cck-1", + "class": "technical-control", + "title": "[CCK-1] Appropriate confidential cryptographic keys", + "props": [{ "name": "control-family", "value": "CCK – Confidential Cryptographic Keys" }, { "name": "source", "value": "EN 18031" }], + "parts": [{ "id": "cck-1-stmt", "name": "statement", "prose": "The product shall use appropriate confidential cryptographic keys." }] + }, + { + "id": "cck-2", + "class": "technical-control", + "title": "[CCK-2] CCK generation mechanisms", + "props": [{ "name": "control-family", "value": "CCK" }, { "name": "source", "value": "EN 18031" }], + "parts": [{ "id": "cck-2-stmt", "name": "statement", "prose": "The product shall implement appropriate cryptographic key generation mechanisms." }] + }, + { + "id": "cck-3", + "class": "technical-control", + "title": "[CCK-3] Preventing static default values for preinstalled CCKs", + "props": [{ "name": "control-family", "value": "CCK" }, { "name": "source", "value": "EN 18031" }], + "parts": [{ "id": "cck-3-stmt", "name": "statement", "prose": "Preinstalled confidential cryptographic keys shall not use static default values." }] + }, + { + "id": "cry-1", + "class": "technical-control", + "title": "[CRY-1] Best practice cryptography", + "props": [{ "name": "control-family", "value": "CRY – Cryptography" }, { "name": "source", "value": "EN 18031" }], + "parts": [{ "id": "cry-1-stmt", "name": "statement", "prose": "The product shall use state-of-the-art (best practice) cryptographic mechanisms, including crypto-agility." }] + } + ] + } + ] + }, + { + "id": "ecr-f", + "title": "ECR (f) – Integrity (Tampering)", + "class": "essential-requirement", + "props": [ + { "name": "cra-annex-ref", "value": "Annex I, Part I, Art. 2(f)" }, + { "name": "requirement-text", "value": "Protect the integrity of stored, transmitted or otherwise processed data, commands, programs and configuration against unauthorised manipulation or modification, and report on corruptions." } + ], + "parts": [ + { + "id": "ecr-f-threats", + "name": "threats", + "prose": "Threat.DataAtRestTampering | Threat.DataInTransitTampering | Threat.ProcessedDataTampering | Threat.TamperingUndetected" + } + ], + "controls": [ + { + "id": "so-data-stored-integrity", + "class": "security-objective", + "title": "SO.DataStoredIntegrity", + "controls": [ + { + "id": "ssm-2", + "class": "technical-control", + "title": "[SSM-2] Appropriate integrity protection for secure storage mechanisms", + "props": [{ "name": "control-family", "value": "SSM" }, { "name": "source", "value": "EN 18031" }], + "parts": [{ "id": "ssm-2-stmt", "name": "statement", "prose": "Secure storage mechanisms shall provide appropriate integrity protection." }] + } + ] + }, + { + "id": "so-data-processed-integrity", + "class": "security-objective", + "title": "SO.DataProcessedIntegrity", + "controls": [ + { + "id": "gec-8-f", + "class": "technical-control", + "title": "[GEC-8] Product Integrity (integrity of processed data)", + "props": [{ "name": "control-family", "value": "GEC" }, { "name": "source", "value": "EN 18031" }, { "name": "shared-with", "value": "ecr-e" }], + "parts": [{ "id": "gec-8-f-stmt", "name": "statement", "prose": "The product shall protect the integrity of data and functions during processing." }] + } + ] + }, + { + "id": "so-data-transmitted-integrity", + "class": "security-objective", + "title": "SO.DataTransmittedIntegrity", + "controls": [ + { + "id": "scm-2-f", + "class": "technical-control", + "title": "[SCM-2] Integrity and authenticity protection for communication (integrity context)", + "props": [{ "name": "control-family", "value": "SCM" }, { "name": "source", "value": "EN 18031" }, { "name": "shared-with", "value": "ecr-e" }], + "parts": [{ "id": "scm-2-f-stmt", "name": "statement", "prose": "Secure communication mechanisms shall provide integrity protection for transmitted data." }] + } + ] + }, + { + "id": "so-integrity-report", + "class": "security-objective", + "title": "SO.IntegrityReport", + "controls": [ + { + "id": "gec-13-f", + "class": "technical-control", + "title": "[GEC-13] Report on possible unauthorised access and corruption (integrity context)", + "props": [{ "name": "control-family", "value": "GEC" }, { "name": "source", "value": "NEW" }, { "name": "shared-with", "value": "ecr-d" }], + "parts": [{ "id": "gec-13-f-stmt", "name": "statement", "prose": "The product shall report on detected integrity violations and data corruption." }] + } + ] + } + ] + }, + { + "id": "ecr-g", + "title": "ECR (g) – Data Minimization", + "class": "essential-requirement", + "props": [ + { "name": "cra-annex-ref", "value": "Annex I, Part I, Art. 2(g)" }, + { "name": "requirement-text", "value": "Process only data, personal or other, that are adequate, relevant and limited to what is necessary in relation to the intended purpose of the product with digital elements (data minimisation)." } + ], + "parts": [ + { + "id": "ecr-g-threats", + "name": "threats", + "prose": "Threat.UnnecessaryDataMisuse" + } + ], + "controls": [ + { + "id": "so-data-minimization", + "class": "security-objective", + "title": "SO.DataMinimization", + "controls": [ + { + "id": "unm-1", + "class": "technical-control", + "title": "[UNM-1] Applicability of user notification mechanisms", + "props": [{ "name": "control-family", "value": "UNM" }, { "name": "source", "value": "EN 18031-2" }], + "parts": [{ "id": "unm-1-stmt", "name": "statement", "prose": "The product shall include applicable user notification mechanisms." }] + }, + { + "id": "unm-2", + "class": "technical-control", + "title": "[UNM-2] Appropriate user notification content", + "props": [{ "name": "control-family", "value": "UNM" }, { "name": "source", "value": "EN 18031-2" }], + "parts": [{ "id": "unm-2-stmt", "name": "statement", "prose": "User notifications shall contain appropriate content to inform the user about data processing activities." }] + }, + { + "id": "dtm-1", + "class": "technical-control", + "title": "[DTM-1] Data minimisation in relation to intended purpose", + "props": [{ "name": "control-family", "value": "DTM – Data Minimization" }, { "name": "source", "value": "NEW" }], + "parts": [{ "id": "dtm-1-stmt", "name": "statement", "prose": "The product shall only process data adequate, relevant and limited to what is necessary for its intended purpose." }] + }, + { + "id": "dtm-2", + "class": "technical-control", + "title": "[DTM-2] Default Data Processing Minimisation", + "props": [{ "name": "control-family", "value": "DTM" }, { "name": "source", "value": "NEW" }], + "parts": [{ "id": "dtm-2-stmt", "name": "statement", "prose": "The product shall default to minimal data processing, with any additional processing requiring explicit user consent." }] + }, + { + "id": "dtm-3", + "class": "technical-control", + "title": "[DTM-3] Configurable Controls for Optional Data Processing", + "props": [{ "name": "control-family", "value": "DTM" }, { "name": "source", "value": "NEW" }], + "parts": [{ "id": "dtm-3-stmt", "name": "statement", "prose": "The product shall provide configurable controls enabling users to manage optional data processing, including opt-in and opt-out mechanisms." }] + } + ] + } + ] + }, + { + "id": "ecr-h", + "title": "ECR (h) – Availability", + "class": "essential-requirement", + "props": [ + { "name": "cra-annex-ref", "value": "Annex I, Part I, Art. 2(h)" }, + { "name": "requirement-text", "value": "Protect the availability of essential and basic functions, also after an incident, including through resilience and mitigation measures against denial-of-service attacks." } + ], + "parts": [ + { + "id": "ecr-h-threats", + "name": "threats", + "prose": "Threat.AvailabilityDegradationAfterIncident | Threat.AvailabilityDegradationDuringIncident" + } + ], + "controls": [ + { + "id": "so-incident-recovery", + "class": "security-objective", + "title": "SO.IncidentRecovery", + "controls": [ + { + "id": "rlm-2", + "class": "technical-control", + "title": "[RLM-2] Recovery from incidents", + "props": [{ "name": "control-family", "value": "RLM – Resilience Mechanism" }, { "name": "source", "value": "NEW" }], + "parts": [{ "id": "rlm-2-stmt", "name": "statement", "prose": "The product shall support recovery of essential and basic functions after a security incident." }] + }, + { + "id": "rlm-6", + "class": "technical-control", + "title": "[RLM-6] Control system backup", + "props": [{ "name": "control-family", "value": "RLM" }, { "name": "source", "value": "NEW" }], + "parts": [{ "id": "rlm-6-stmt", "name": "statement", "prose": "The product shall support control system backup to facilitate recovery after an incident." }] + } + ] + }, + { + "id": "so-incident-resilience", + "class": "security-objective", + "title": "SO.IncidentResilience", + "controls": [ + { + "id": "rlm-1", + "class": "technical-control", + "title": "[RLM-1] Applicability and appropriateness of resilience mechanisms", + "props": [{ "name": "control-family", "value": "RLM" }, { "name": "source", "value": "EN 18031" }], + "parts": [{ "id": "rlm-1-stmt", "name": "statement", "prose": "The product shall include applicable and appropriate resilience mechanisms to maintain availability during incidents, including DoS attacks." }] + }, + { + "id": "rlm-3", + "class": "technical-control", + "title": "[RLM-3] Network prioritization", + "props": [{ "name": "control-family", "value": "RLM" }, { "name": "source", "value": "NEW" }], + "parts": [{ "id": "rlm-3-stmt", "name": "statement", "prose": "The product shall prioritize essential network traffic to maintain core functions during an incident." }] + }, + { + "id": "rlm-4", + "class": "technical-control", + "title": "[RLM-4] Resource prioritization", + "props": [{ "name": "control-family", "value": "RLM" }, { "name": "source", "value": "NEW" }], + "parts": [{ "id": "rlm-4-stmt", "name": "statement", "prose": "The product shall prioritize critical resources to maintain essential functions during an incident." }] + }, + { + "id": "rlm-5", + "class": "technical-control", + "title": "[RLM-5] Resource management", + "props": [{ "name": "control-family", "value": "RLM" }, { "name": "source", "value": "NEW" }], + "parts": [{ "id": "rlm-5-stmt", "name": "statement", "prose": "The product shall implement resource management to prevent resource exhaustion attacks." }] + } + ] + } + ] + }, + { + "id": "ecr-i", + "title": "ECR (i) – Minimize Negative Impact", + "class": "essential-requirement", + "props": [ + { "name": "cra-annex-ref", "value": "Annex I, Part I, Art. 2(i)" }, + { "name": "requirement-text", "value": "Minimise the negative impact by the products themselves or connected devices on the availability of services provided by other devices or networks." } + ], + "parts": [ + { + "id": "ecr-i-threats", + "name": "threats", + "prose": "Threat.ExtServiceAvailabilityDegradation" + } + ], + "controls": [ + { + "id": "so-limit-external-impact", + "class": "security-objective", + "title": "SO.LimitExternalImpact", + "controls": [ + { + "id": "gec-8-i", + "class": "technical-control", + "title": "[GEC-8] Product Integrity (external impact context)", + "props": [{ "name": "control-family", "value": "GEC" }, { "name": "source", "value": "EN 18031" }, { "name": "shared-with", "value": "ecr-e ecr-f" }], + "parts": [{ "id": "gec-8-i-stmt", "name": "statement", "prose": "Product integrity shall be maintained to prevent the product from being used as a vector for external attacks." }] + }, + { + "id": "tcm-1", + "class": "technical-control", + "title": "[TCM-1] Applicability of and appropriate traffic control mechanisms", + "props": [{ "name": "control-family", "value": "TCM – Traffic Control Mechanism" }, { "name": "source", "value": "EN 18031" }], + "parts": [{ "id": "tcm-1-stmt", "name": "statement", "prose": "The product shall implement applicable and appropriate traffic control mechanisms to manage and limit outgoing network traffic." }] + }, + { + "id": "lim-1", + "class": "technical-control", + "title": "[LIM-1] External impact limitation", + "props": [{ "name": "control-family", "value": "LIM – External Impact Limitation" }, { "name": "source", "value": "NEW" }], + "parts": [{ "id": "lim-1-stmt", "name": "statement", "prose": "The product shall implement mechanisms to limit its negative impact on external services and networks." }] + } + ] + }, + { + "id": "so-prevent-attack-propagation", + "class": "security-objective", + "title": "SO.PreventAttackPropagation", + "controls": [ + { + "id": "lim-2", + "class": "technical-control", + "title": "[LIM-2] Prevention of attack propagation", + "props": [{ "name": "control-family", "value": "LIM" }, { "name": "source", "value": "NEW" }], + "parts": [{ "id": "lim-2-stmt", "name": "statement", "prose": "The product shall implement controls to prevent propagation of attacks to other devices or networks." }] + } + ] + }, + { + "id": "so-monitor-external-impact", + "class": "security-objective", + "title": "SO.MonitorExternalImpact", + "controls": [ + { + "id": "nmm-1", + "class": "technical-control", + "title": "[NMM-1] Applicability and appropriateness of network monitoring mechanisms", + "props": [{ "name": "control-family", "value": "NMM – Network Monitoring Mechanism" }, { "name": "source", "value": "EN 18031" }], + "parts": [{ "id": "nmm-1-stmt", "name": "statement", "prose": "The product shall include applicable and appropriate network monitoring mechanisms to detect abnormal traffic patterns and potential external impact." }] + } + ] + } + ] + }, + { + "id": "ecr-j", + "title": "ECR (j) – Attack Surface Minimization", + "class": "essential-requirement", + "props": [ + { "name": "cra-annex-ref", "value": "Annex I, Part I, Art. 2(j)" }, + { "name": "requirement-text", "value": "Be designed, developed and produced to limit attack surfaces, including external interfaces." } + ], + "parts": [ + { + "id": "ecr-j-threats", + "name": "threats", + "prose": "Threat.UnnecessaryFunctionalityExploitation" + } + ], + "controls": [ + { + "id": "so-reduce-attack-surface", + "class": "security-objective", + "title": "SO.ReduceAttackSurface", + "controls": [ + { + "id": "gec-2-j", + "class": "technical-control", + "title": "[GEC-2] Limit exposure of services via network interfaces (attack surface context)", + "props": [{ "name": "control-family", "value": "GEC" }, { "name": "source", "value": "EN 18031" }, { "name": "shared-with", "value": "ecr-b" }], + "parts": [{ "id": "gec-2-j-stmt", "name": "statement", "prose": "Network service exposure shall be minimised to reduce the attack surface." }] + }, + { + "id": "gec-3-j", + "class": "technical-control", + "title": "[GEC-3] Configuration of optional services (attack surface context)", + "props": [{ "name": "control-family", "value": "GEC" }, { "name": "source", "value": "EN 18031" }, { "name": "shared-with", "value": "ecr-b" }], + "parts": [{ "id": "gec-3-j-stmt", "name": "statement", "prose": "Optional services and interfaces shall be configurable and disabled when not needed." }] + }, + { + "id": "gec-4-j", + "class": "technical-control", + "title": "[GEC-4] Documentation of exposed network interfaces (attack surface context)", + "props": [{ "name": "control-family", "value": "GEC" }, { "name": "source", "value": "EN 18031" }, { "name": "shared-with", "value": "ecr-b" }], + "parts": [{ "id": "gec-4-j-stmt", "name": "statement", "prose": "All exposed network interfaces and services shall be documented as part of attack surface analysis." }] + }, + { + "id": "gec-5-j", + "class": "technical-control", + "title": "[GEC-5] No unnecessary external interfaces (attack surface context)", + "props": [{ "name": "control-family", "value": "GEC" }, { "name": "source", "value": "EN 18031" }, { "name": "shared-with", "value": "ecr-b" }], + "parts": [{ "id": "gec-5-j-stmt", "name": "statement", "prose": "Unnecessary external interfaces shall be removed or disabled." }] + }, + { + "id": "gec-6", + "class": "technical-control", + "title": "[GEC-6] Input validation", + "props": [{ "name": "control-family", "value": "GEC" }, { "name": "source", "value": "EN 18031" }], + "parts": [{ "id": "gec-6-stmt", "name": "statement", "prose": "The product shall validate all inputs to prevent exploitation via malformed data." }] + }, + { + "id": "gec-7-j", + "class": "technical-control", + "title": "[GEC-7] Documentation of external sensing capabilities (attack surface context)", + "props": [{ "name": "control-family", "value": "GEC" }, { "name": "source", "value": "EN 18031" }, { "name": "shared-with", "value": "ecr-b" }], + "parts": [{ "id": "gec-7-j-stmt", "name": "statement", "prose": "External sensing capabilities shall be documented as part of attack surface characterisation." }] + }, + { + "id": "gec-12-j", + "class": "technical-control", + "title": "[GEC-12] No Unneeded Software Components (attack surface context)", + "props": [{ "name": "control-family", "value": "GEC" }, { "name": "source", "value": "NEW" }, { "name": "shared-with", "value": "ecr-b" }], + "parts": [{ "id": "gec-12-j-stmt", "name": "statement", "prose": "Unneeded software components shall be removed to minimise the attack surface." }] + } + ] + } + ] + }, + { + "id": "ecr-k", + "title": "ECR (k) – Reduce Impact of Incident (Hardening / Exploit Mitigations)", + "class": "essential-requirement", + "props": [ + { "name": "cra-annex-ref", "value": "Annex I, Part I, Art. 2(k)" }, + { "name": "requirement-text", "value": "Be designed, developed and produced to reduce the impact of an incident using appropriate exploitation mitigation mechanisms and techniques." } + ], + "parts": [ + { + "id": "ecr-k-threats", + "name": "threats", + "prose": "Threat.ExploitationMitigationFailure" + } + ], + "controls": [ + { + "id": "so-reduce-impact-of-incident", + "class": "security-objective", + "title": "SO.ReduceImpactOfIncident", + "parts": [ + { + "id": "so-rii-guidance", + "name": "guidance", + "prose": "Applicable exploit mitigation techniques include: Address Space Layout Randomization (ASLR), Data Execution Prevention (DEP), Control-Flow Integrity (CFI), sandboxing, signed software execution policies, secure-by-default configuration, memory safety management, and least privilege. Operational controls include patch management, application whitelisting, network segmentation, and access control." + } + ], + "controls": [ + { + "id": "gec-11", + "class": "technical-control", + "title": "[GEC-11] Exploit mitigation mechanisms", + "props": [{ "name": "control-family", "value": "GEC" }, { "name": "source", "value": "NEW" }], + "parts": [{ "id": "gec-11-stmt", "name": "statement", "prose": "The product shall implement appropriate exploit mitigation mechanisms and techniques to reduce the impact of successful attacks, including privilege escalation, lateral movement, malware installation, and injection exploits." }] + } + ] + } + ] + }, + { + "id": "ecr-l", + "title": "ECR (l) – Monitoring and Logging", + "class": "essential-requirement", + "props": [ + { "name": "cra-annex-ref", "value": "Annex I, Part I, Art. 2(l)" }, + { "name": "requirement-text", "value": "Provide security related information by recording and monitoring relevant internal activity, including the access to or modification of data, services or functions, with an opt-out mechanism for the user." } + ], + "parts": [ + { + "id": "ecr-l-threats", + "name": "threats", + "prose": "Threat.NotRecordedSecurityActivity | Threat.NotMonitoredSecurityActivity | Threat.UnnecessaryDataLogging | Threat.UnnecessaryDataMonitoring" + } + ], + "controls": [ + { + "id": "so-log-security-relevant-activities", + "class": "security-objective", + "title": "SO.LogSecurityRelevantActivities", + "controls": [ + { + "id": "lgm-1", + "class": "technical-control", + "title": "[LGM-1] Applicability of logging mechanisms", + "props": [{ "name": "control-family", "value": "LGM – Logging Mechanism" }, { "name": "source", "value": "EN 18031" }], + "parts": [{ "id": "lgm-1-stmt", "name": "statement", "prose": "The product shall include applicable logging mechanisms for security-relevant events." }] + }, + { + "id": "lgm-2", + "class": "technical-control", + "title": "[LGM-2] Persistent storage of log data", + "props": [{ "name": "control-family", "value": "LGM" }, { "name": "source", "value": "EN 18031" }], + "parts": [{ "id": "lgm-2-stmt", "name": "statement", "prose": "Log data shall be persistently stored to support post-incident analysis." }] + }, + { + "id": "lgm-3", + "class": "technical-control", + "title": "[LGM-3] Minimum number of persistently stored events", + "props": [{ "name": "control-family", "value": "LGM" }, { "name": "source", "value": "EN 18031" }], + "parts": [{ "id": "lgm-3-stmt", "name": "statement", "prose": "A defined minimum number of security-relevant events shall be persistently stored." }] + }, + { + "id": "lgm-4", + "class": "technical-control", + "title": "[LGM-4] Time-related information of persistently stored log data", + "props": [{ "name": "control-family", "value": "LGM" }, { "name": "source", "value": "EN 18031" }], + "parts": [{ "id": "lgm-4-stmt", "name": "statement", "prose": "Persistently stored log data shall include time-related information (timestamps) to support forensic analysis." }] + }, + { + "id": "lgm-6", + "class": "technical-control", + "title": "[LGM-6] Exclude sensitive personal information from logs", + "props": [{ "name": "control-family", "value": "LGM" }, { "name": "source", "value": "NEW" }], + "parts": [{ "id": "lgm-6-stmt", "name": "statement", "prose": "Log records shall exclude sensitive personal information unless strictly necessary, in line with data minimisation principles." }] + } + ] + }, + { + "id": "so-monitor-security-relevant-activities", + "class": "security-objective", + "title": "SO.MonitorSecurityRelevantActivities", + "controls": [ + { + "id": "nmm-1-l", + "class": "technical-control", + "title": "[NMM-1] Network monitoring mechanisms (logging context)", + "props": [{ "name": "control-family", "value": "NMM" }, { "name": "source", "value": "EN 18031" }, { "name": "shared-with", "value": "ecr-i" }], + "parts": [{ "id": "nmm-1-l-stmt", "name": "statement", "prose": "Network monitoring mechanisms shall detect security-relevant activities and anomalous behaviour." }] + }, + { + "id": "mon-1", + "class": "technical-control", + "title": "[MON-1] Monitor security relevant activities", + "props": [{ "name": "control-family", "value": "MON – Monitoring of Security Activities" }, { "name": "source", "value": "NEW" }], + "parts": [{ "id": "mon-1-stmt", "name": "statement", "prose": "The product shall monitor security-relevant internal activities, including access to and modification of data, services and functions." }] + } + ] + }, + { + "id": "so-option-disable-data-logging", + "class": "security-objective", + "title": "SO.OptionDisableDataLogging", + "controls": [ + { + "id": "lgm-5", + "class": "technical-control", + "title": "[LGM-5] Disabling logging", + "props": [{ "name": "control-family", "value": "LGM" }, { "name": "source", "value": "NEW" }], + "parts": [{ "id": "lgm-5-stmt", "name": "statement", "prose": "The product shall provide an opt-out mechanism allowing users to disable data logging where appropriate." }] + } + ] + }, + { + "id": "so-option-disable-data-monitoring", + "class": "security-objective", + "title": "SO.OptionDisableDataMonitoring", + "controls": [ + { + "id": "mon-2", + "class": "technical-control", + "title": "[MON-2] Disable monitoring", + "props": [{ "name": "control-family", "value": "MON" }, { "name": "source", "value": "NEW" }], + "parts": [{ "id": "mon-2-stmt", "name": "statement", "prose": "The product shall provide an opt-out mechanism allowing users to disable data monitoring where appropriate." }] + } + ] + } + ] + }, + { + "id": "ecr-m", + "title": "ECR (m) – Data Deletion", + "class": "essential-requirement", + "props": [ + { "name": "cra-annex-ref", "value": "Annex I, Part I, Art. 2(m)" }, + { "name": "requirement-text", "value": "Provide the possibility for users to securely and easily remove on a permanent basis all data and settings and, where such data can be transferred to other products or systems, ensure that this is done in a secure manner." } + ], + "parts": [ + { + "id": "ecr-m-threats", + "name": "threats", + "prose": "Threat.DeletedDataDisclosure | Threat.DataInTransitDisclosure | Threat.DataInTransitTampering" + } + ], + "controls": [ + { + "id": "so-secure-data-deletion", + "class": "security-objective", + "title": "SO.SecureDataDeletion", + "controls": [ + { + "id": "dlm-1", + "class": "technical-control", + "title": "[DLM-1] Secure Data deletion mechanisms (applicability)", + "props": [{ "name": "control-family", "value": "DLM – Deletion Mechanism" }, { "name": "source", "value": "EN 18031" }], + "parts": [{ "id": "dlm-1-stmt", "name": "statement", "prose": "The product shall provide applicable secure data deletion mechanisms enabling users to permanently and easily remove all data and settings." }] + } + ] + }, + { + "id": "so-data-transmitted-confidentiality-m", + "class": "security-objective", + "title": "SO.DataTransmittedConfidentiality (data export context)", + "props": [{ "name": "cra-cross-ref", "value": "Annex I Part I (2)(e) of (EU) 2024/2847" }], + "controls": [ + { + "id": "dlm-2", + "class": "technical-control", + "title": "[DLM-2] Confidential export of user data", + "props": [{ "name": "control-family", "value": "DLM" }, { "name": "source", "value": "NEW" }], + "parts": [{ "id": "dlm-2-stmt", "name": "statement", "prose": "When user data is transferred to other products or systems, confidentiality shall be protected during the transfer." }] + } + ] + }, + { + "id": "so-data-transmitted-integrity-m", + "class": "security-objective", + "title": "SO.DataTransmittedIntegrity (data export context)", + "props": [{ "name": "cra-cross-ref", "value": "Annex I Part I (2)(f) of (EU) 2024/2847" }], + "controls": [ + { + "id": "dlm-3", + "class": "technical-control", + "title": "[DLM-3] Integrity protection of exported data", + "props": [{ "name": "control-family", "value": "DLM" }, { "name": "source", "value": "NEW" }], + "parts": [{ "id": "dlm-3-stmt", "name": "statement", "prose": "When user data is transferred to other products or systems, integrity shall be protected during the transfer." }] + } + ] + }, + { + "id": "so-com-auth-m", + "class": "security-objective", + "title": "SO.ComAuth (data export context)", + "props": [{ "name": "cra-cross-ref", "value": "Annex I Part I (2)(e)(f) of (EU) 2024/2847" }], + "controls": [ + { + "id": "dlm-4", + "class": "technical-control", + "title": "[DLM-4] Authenticity of the communication partner for export of data", + "props": [{ "name": "control-family", "value": "DLM" }, { "name": "source", "value": "NEW" }], + "parts": [{ "id": "dlm-4-stmt", "name": "statement", "prose": "When user data is transferred to other products or systems, the authenticity of the receiving party shall be verified." }] + } + ] + } + ] + } + ], + "back-matter": { + "resources": [ + { + "uuid": "r001", + "title": "Cyber Resilience Act – Regulation (EU) 2024/2847", + "rlinks": [{ "href": "https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX:32024R2847" }] + }, + { + "uuid": "r002", + "title": "Standardisation Request M/606 – C(2025)618", + "rlinks": [{ "href": "https://ec.europa.eu/growth/tools-databases/enorm/mandate/606_en" }] + }, + { + "uuid": "r003", + "title": "EN 18031-1:2024 – Common security requirements for radio equipment", + "rlinks": [{ "href": "https://www.etsi.org/deliver/etsi_en/18031_18031-1/" }] + }, + { + "uuid": "r004", + "title": "EN 18031-2:2024 – Common security requirements for radio equipment (part 2)", + "rlinks": [{ "href": "https://www.etsi.org/deliver/etsi_en/18031_18031-2/" }] + }, + { + "uuid": "r005", + "title": "EN 18031-3:2024 – Common security requirements for radio equipment (part 3)", + "rlinks": [{ "href": "https://www.etsi.org/deliver/etsi_en/18031_18031-3/" }] + }, + { + "uuid": "r006", + "title": "ETSI EN 303 645 V3.1.3 – Cyber Security for Consumer Internet of Things", + "rlinks": [{ "href": "https://www.etsi.org/deliver/etsi_en/303600_303699/303645/" }] + }, + { + "uuid": "r007", + "title": "EN IEC 62443-4-2 – Security for industrial automation and control systems", + "rlinks": [{ "href": "https://www.iec.ch/publication/62443" }] + }, + { + "uuid": "r008", + "title": "STAN4CR Project – EISMEA Grant Agreement No. 101196779", + "rlinks": [{ "href": "https://stan4cr.eu" }] + }, + { + "uuid": "r009", + "title": "CRA FAQ on Implementation – European Commission", + "rlinks": [{ "href": "https://digital-strategy.ec.europa.eu/en/policies/cyber-resilience-act" }] + }, + { + "uuid": "r010", + "title": "NIST SP 800-88 Rev. 1 – Guidelines for Media Sanitization", + "rlinks": [{ "href": "https://doi.org/10.6028/NIST.SP.800-88r1" }] + } + ] + } + } +} diff --git a/security/dfetch.component-definition.json b/security/dfetch.component-definition.json new file mode 100644 index 00000000..cff7646b --- /dev/null +++ b/security/dfetch.component-definition.json @@ -0,0 +1,910 @@ +{ + "component-definition": { + "uuid": "eddc2e8c-a2ae-5a42-8d35-88f5c2af55b2", + "metadata": { + "title": "dfetch CRA Compliance Component Definition", + "last-modified": "2026-06-15T00:00:00Z", + "version": "0.14.0", + "oscal-version": "1.1.2", + "props": [ + { + "name": "cra-class", + "value": "Non-commercial OSS \u2014 Recital 18 exemption" + }, + { + "name": "cra-article", + "value": "Article 3(14), Recital 18, Article 13(5) of Regulation (EU) 2024/2847" + }, + { + "name": "standard", + "value": "prEN 40000-1-4 (draft, indicative publication October 2027)" + } + ], + "remarks": "Produced voluntarily under Article 13(5) to support downstream integrators. dfetch is non-commercial open-source software and is not subject to mandatory CRA conformity obligations (Recital 18)." + }, + "components": [ + { + "uuid": "4c787261-bc8c-5e3f-802d-3de5c33f382c", + "type": "software", + "title": "dfetch", + "description": "Python CLI tool for vendoring source-code dependencies from Git, SVN, or archive files into a project as plain files.", + "props": [ + { + "name": "software-version", + "value": "0.14.0" + } + ], + "links": [ + { + "href": "https://github.com/dfetch-org/dfetch", + "rel": "homepage" + }, + { + "href": "https://pypi.org/project/dfetch/", + "rel": "distribution" + }, + { + "href": "security/cra_pren_4000014_oscal_catalog.json", + "rel": "reference", + "text": "prEN 40000-1-4 OSCAL Catalog" + } + ], + "control-implementations": [ + { + "uuid": "2736b532-92d4-51f9-9f98-059485a4cafa", + "source": "security/cra_pren_4000014_oscal_catalog.json", + "description": "Voluntary alignment with prEN 40000-1-4 Security Objectives. The catalog organises CRA Annex I Part I essential requirements (ECR-a\u2013m) as groups; this component implements the Security Objectives (SO.*) within each ECR using dfetch controls (C-001\u2013C-046).", + "implemented-requirements": [ + { + "uuid": "f552e9f1-c64d-5d57-9fd2-05cdf2e238b6", + "control-id": "so-vulnerability-management-process", + "props": [ + { + "name": "implementation-status", + "value": "partially-implemented" + }, + { + "name": "gaps", + "value": "No CVE gate at release time (\u2192 C-043 planned)" + }, + { + "name": "dfetch-controls", + "value": "C-015, C-016, C-017, C-022" + } + ], + "statements": [ + { + "statement-id": "so-vulnerability-management-process-stmt", + "uuid": "d07ef2cb-63b5-5c7b-a70e-6530e894f633", + "description": "GEC-1: C-015 (CodeQL), C-016 (dependency-review), C-017 (bandit), C-022 (SBOM) address known-vulnerability detection in CI. Gaps: No CVE gate at release time (\u2192 C-043 planned)" + } + ] + }, + { + "uuid": "57ea1e4c-a0dc-52af-aaf6-8fd2545924e0", + "control-id": "so-secure-default-configuration", + "props": [ + { + "name": "implementation-status", + "value": "partially-implemented" + }, + { + "name": "not-applicable", + "value": "GEC-2, GEC-3, GEC-4, GEC-5 (no exposed network services \u2014 CLI tool); GEC-7 (no external sensing capabilities); AUM-5 (no password or authentication mechanism)" + }, + { + "name": "dfetch-controls", + "value": "C-001, C-002" + } + ], + "statements": [ + { + "statement-id": "so-secure-default-configuration-stmt", + "uuid": "709823d8-4d88-57af-924b-59bde9ed1e31", + "description": "GEC-12 (no unneeded software components): C-001 enforces minimal runtime dependencies. dfetch does not expose network services. Not applicable: GEC-2, GEC-3, GEC-4, GEC-5 (no exposed network services \u2014 CLI tool); GEC-7 (no external sensing capabilities); AUM-5 (no password or authentication mechanism)" + } + ] + }, + { + "uuid": "7a3c27cf-4f60-58a5-9e0b-5d8ba122e26c", + "control-id": "so-secure-startup-config", + "props": [ + { + "name": "implementation-status", + "value": "not-applicable" + }, + { + "name": "not-applicable", + "value": "GEC-9 (no security-relevant startup configuration state in dfetch)" + } + ], + "statements": [ + { + "statement-id": "so-secure-startup-config-stmt", + "uuid": "7955988d-5f64-51cc-8bb9-73f639f36d2f", + "description": "dfetch reads only its manifest at startup; no security-sensitive config initialisation. Not applicable: GEC-9 (no security-relevant startup configuration state in dfetch)" + } + ] + }, + { + "uuid": "385ed85e-6e40-5954-98fd-19a935f34cf8", + "control-id": "so-factory-reset", + "props": [ + { + "name": "implementation-status", + "value": "not-applicable" + }, + { + "name": "not-applicable", + "value": "DLM-1-b, GEC-10 (no persistent device state requiring factory reset \u2014 CLI tool)" + } + ], + "statements": [ + { + "statement-id": "so-factory-reset-stmt", + "uuid": "9cea77fd-b1b8-5519-ba17-c6d19fef7080", + "description": "dfetch is a stateless CLI tool; no factory-reset concept applies. Not applicable: DLM-1-b, GEC-10 (no persistent device state requiring factory reset \u2014 CLI tool)" + } + ] + }, + { + "uuid": "c35dd96d-7054-5e77-9270-76c72ab9c317", + "control-id": "so-updateability", + "props": [ + { + "name": "implementation-status", + "value": "implemented" + } + ], + "statements": [ + { + "statement-id": "so-updateability-stmt", + "uuid": "4619e5ae-8f33-53a0-ae99-dcb6e045d1fd", + "description": "SUM-1/SUM-2: Updates distributed via PyPI (pip install --upgrade dfetch) and GitHub Releases. pip's TLS-protected download satisfies SUM-2." + } + ] + }, + { + "uuid": "c5af9d98-c312-5ceb-ab74-9845e84c513e", + "control-id": "so-automatic-updates", + "props": [ + { + "name": "implementation-status", + "value": "not-applicable" + }, + { + "name": "not-applicable", + "value": "SUM-3 (automatic updates managed by pip/pipenv/poetry \u2014 not dfetch itself)" + } + ], + "statements": [ + { + "statement-id": "so-automatic-updates-stmt", + "uuid": "14e9face-c488-5840-a2a5-b02c8cae6932", + "description": "Update automation is the responsibility of the user's package manager. Not applicable: SUM-3 (automatic updates managed by pip/pipenv/poetry \u2014 not dfetch itself)" + } + ] + }, + { + "uuid": "d91fbedf-bf3e-5b7d-aad0-ca00c6a3b451", + "control-id": "so-user-update-notification", + "props": [ + { + "name": "implementation-status", + "value": "implemented" + }, + { + "name": "dfetch-controls", + "value": "C-040" + } + ], + "statements": [ + { + "statement-id": "so-user-update-notification-stmt", + "uuid": "5ecb3c34-e8a6-5998-95e1-4918aae6993a", + "description": "UNM-4: dfetch check and dfetch environment report when a newer dfetch version is available (C-040)." + } + ] + }, + { + "uuid": "81d4c300-4c12-5798-b3e6-d94603a2e2ed", + "control-id": "so-postpone-updates", + "props": [ + { + "name": "implementation-status", + "value": "not-applicable" + }, + { + "name": "not-applicable", + "value": "SUM-4 (update scheduling controlled by pip/pipenv \u2014 not dfetch)" + } + ], + "statements": [ + { + "statement-id": "so-postpone-updates-stmt", + "uuid": "9fe01c6c-4caf-5604-a0b5-f7bb868b53de", + "description": "Postponement is handled by the user's package manager. Not applicable: SUM-4 (update scheduling controlled by pip/pipenv \u2014 not dfetch)" + } + ] + }, + { + "uuid": "b6338f89-d6e8-5a4e-a2c6-1a3d4fe8b15b", + "control-id": "so-access-control", + "props": [ + { + "name": "implementation-status", + "value": "partially-implemented" + }, + { + "name": "not-applicable", + "value": "ACM-2, AUM-2, AUM-3, AUM-4, AUM-6 (dfetch has no user-facing authentication or access control)" + }, + { + "name": "dfetch-controls", + "value": "C-006, C-036" + } + ], + "statements": [ + { + "statement-id": "so-access-control-stmt", + "uuid": "851f2832-c17f-51e2-8fef-28d575f3ea42", + "description": "dfetch delegates authentication to the host VCS client (git, svn) and the OS credential store. C-006 prevents SSH command injection; C-036 strips credentials from stored metadata. Not applicable: ACM-2, AUM-2, AUM-3, AUM-4, AUM-6 (dfetch has no user-facing authentication or access control)" + } + ] + }, + { + "uuid": "833fedff-7756-5a5e-a3f6-37deeb9ffbfc", + "control-id": "so-access-control-report", + "props": [ + { + "name": "implementation-status", + "value": "partially-implemented" + }, + { + "name": "gaps", + "value": "No persistent log of unauthorised access attempts" + }, + { + "name": "dfetch-controls", + "value": "C-009" + } + ], + "statements": [ + { + "statement-id": "so-access-control-report-stmt", + "uuid": "5fc0e660-93e3-57a5-ae6a-55d9b9365c55", + "description": "GEC-13: C-009 (plaintext transport warning) alerts on unauthenticated connections. No persistent security event log. Gaps: No persistent log of unauthorised access attempts" + } + ] + }, + { + "uuid": "9095a65f-d098-50ae-b4c1-a820b0f37492", + "control-id": "so-data-stored-confidentiality", + "props": [ + { + "name": "implementation-status", + "value": "partially-implemented" + }, + { + "name": "gaps", + "value": ".dfetch_data.yaml stored as plaintext YAML (no encryption at rest)" + }, + { + "name": "dfetch-controls", + "value": "C-036" + } + ], + "statements": [ + { + "statement-id": "so-data-stored-confidentiality-stmt", + "uuid": "d6a8cfa8-3e30-5318-8ca6-59f99fb7dbff", + "description": "SSM-1/SSM-3: C-036 strips userinfo from URLs before storage. The .dfetch_data.yaml file itself is not encrypted; encryption would require key management infrastructure not appropriate for a developer tool. Gaps: .dfetch_data.yaml stored as plaintext YAML (no encryption at rest)" + } + ] + }, + { + "uuid": "aa21320e-a496-57b8-8c1e-7aeb87da62f8", + "control-id": "so-data-processed-confidentiality", + "props": [ + { + "name": "implementation-status", + "value": "implemented" + }, + { + "name": "dfetch-controls", + "value": "C-005, C-034" + } + ], + "statements": [ + { + "statement-id": "so-data-processed-confidentiality-stmt", + "uuid": "38cff185-27ad-55b2-96f7-da88b47eedaa", + "description": "GEC-8: C-005 (constant-time comparison), C-034 (temp-file cleanup) protect in-process data." + } + ] + }, + { + "uuid": "f3b4e2c0-7586-5fbd-9eab-84e0f5a9f088", + "control-id": "so-data-transmitted-confidentiality", + "props": [ + { + "name": "implementation-status", + "value": "partially-implemented" + }, + { + "name": "gaps", + "value": "HTTP accepted for remote URLs (SCM-3/SCM-4 gap; mitigated by C-009 warning)" + }, + { + "name": "dfetch-controls", + "value": "C-009" + } + ], + "statements": [ + { + "statement-id": "so-data-transmitted-confidentiality-stmt", + "uuid": "4440d616-231f-51ca-a514-c5cd42a3851b", + "description": "SCM-3/SCM-4: dfetch warns on plaintext transport (C-009) but accepts HTTP remote URLs to avoid breaking existing manifests. Gaps: HTTP accepted for remote URLs (SCM-3/SCM-4 gap; mitigated by C-009 warning)" + } + ] + }, + { + "uuid": "5ca7d5ae-a5dc-5e73-bcfe-d06973f86a94", + "control-id": "so-com-auth-e", + "props": [ + { + "name": "implementation-status", + "value": "partially-implemented" + }, + { + "name": "gaps", + "value": "No end-to-end authenticity verification for plain git/svn transport" + }, + { + "name": "dfetch-controls", + "value": "C-003, C-004" + } + ], + "statements": [ + { + "statement-id": "so-com-auth-e-stmt", + "uuid": "c083999a-6c6c-5185-a7ad-8ee203dc13a4", + "description": "SCM-2: C-003 (TLS CA verification for HTTPS) and C-004 (SSH host-key checking) provide authenticity for most connections. Gaps: No end-to-end authenticity verification for plain git/svn transport" + } + ] + }, + { + "uuid": "a3a0d849-051e-5a03-b520-2f765a9e5386", + "control-id": "so-secure-provisioning", + "props": [ + { + "name": "implementation-status", + "value": "partially-implemented" + }, + { + "name": "not-applicable", + "value": "CCK-1, CCK-2, CCK-3 (dfetch manages no cryptographic keys)" + }, + { + "name": "dfetch-controls", + "value": "C-005" + } + ], + "statements": [ + { + "statement-id": "so-secure-provisioning-stmt", + "uuid": "f337f022-b1e9-58f6-9b96-87434a022998", + "description": "CRY-1: C-005 uses Python's hashlib with SHA-256 for integrity hashes. No key management is required or performed by dfetch. Not applicable: CCK-1, CCK-2, CCK-3 (dfetch manages no cryptographic keys)" + } + ] + }, + { + "uuid": "be09eb50-6158-57dd-83e2-7c72d2e4bd55", + "control-id": "so-data-stored-integrity", + "props": [ + { + "name": "implementation-status", + "value": "partially-implemented" + }, + { + "name": "gaps", + "value": "Integrity hash opt-in only; not enforced by default for git/svn" + }, + { + "name": "dfetch-controls", + "value": "C-005" + } + ], + "statements": [ + { + "statement-id": "so-data-stored-integrity-stmt", + "uuid": "8a522c77-b369-527f-a9ee-ef2b66e95dd4", + "description": "SSM-2: C-005 (integrity hash in .dfetch_data.yaml) provides optional stored-data integrity verification. Gaps: Integrity hash opt-in only; not enforced by default for git/svn" + } + ] + }, + { + "uuid": "79282e9e-af7f-5d94-8a4b-4e75fe53125c", + "control-id": "so-data-processed-integrity", + "props": [ + { + "name": "implementation-status", + "value": "implemented" + }, + { + "name": "dfetch-controls", + "value": "C-005, C-034" + } + ], + "statements": [ + { + "statement-id": "so-data-processed-integrity-stmt", + "uuid": "5de5eb69-9db8-5b2c-9d56-cd7dc067c9e1", + "description": "GEC-8: C-005 and C-034 protect data integrity during processing." + } + ] + }, + { + "uuid": "d38ade39-84f0-5eaf-a6f4-62fb00462f03", + "control-id": "so-data-transmitted-integrity", + "props": [ + { + "name": "implementation-status", + "value": "partially-implemented" + }, + { + "name": "gaps", + "value": "No end-to-end hash for git/svn transport beyond TLS/SSH channel integrity" + }, + { + "name": "dfetch-controls", + "value": "C-003, C-004" + } + ], + "statements": [ + { + "statement-id": "so-data-transmitted-integrity-stmt", + "uuid": "615addd1-b43e-5585-9bea-2a806c4f6f0e", + "description": "SCM-2: TLS (C-003) and SSH (C-004) provide channel-level integrity. Commit-hash pinning (rev: ) provides content-level integrity for git. Gaps: No end-to-end hash for git/svn transport beyond TLS/SSH channel integrity" + } + ] + }, + { + "uuid": "acd45544-636a-5504-8122-bed43f8ecbbd", + "control-id": "so-integrity-report", + "props": [ + { + "name": "implementation-status", + "value": "partially-implemented" + }, + { + "name": "gaps", + "value": "No persistent integrity-violation log" + }, + { + "name": "dfetch-controls", + "value": "C-009" + } + ], + "statements": [ + { + "statement-id": "so-integrity-report-stmt", + "uuid": "99d5808a-c006-5839-a3f3-acbfe9e93974", + "description": "GEC-13-f: dfetch surfaces transport-integrity warnings (C-009) at runtime but does not maintain a persistent security event log. Gaps: No persistent integrity-violation log" + } + ] + }, + { + "uuid": "a72e2565-b9a7-5ddc-b8f6-936797f8ea69", + "control-id": "so-data-minimization", + "props": [ + { + "name": "implementation-status", + "value": "planned" + }, + { + "name": "not-applicable", + "value": "UNM-1, UNM-2 (dfetch processes no personal data requiring user notification); DTM-3 (no optional data processing to configure)" + }, + { + "name": "dfetch-controls", + "value": "C-044" + } + ], + "statements": [ + { + "statement-id": "so-data-minimization-stmt", + "uuid": "dda2d3c4-5ef4-56bd-a2a2-923a894ae320", + "description": "DTM-1: C-044 (planned) documents that .dfetch_data.yaml is limited to remote_url (stripped), revision, optional hash, and last_fetch \u2014 each justified by functional necessity. DTM-2: met by design \u2014 dfetch collects no telemetry or optional data. Not applicable: UNM-1, UNM-2 (dfetch processes no personal data requiring user notification); DTM-3 (no optional data processing to configure)" + } + ] + }, + { + "uuid": "67e874a1-2b13-5cd2-9930-bd2cf57cf45b", + "control-id": "so-incident-recovery", + "props": [ + { + "name": "implementation-status", + "value": "not-applicable" + }, + { + "name": "not-applicable", + "value": "RLM-2, RLM-6 (CLI tool; no persistent device state or control-system backup needed)" + } + ], + "statements": [ + { + "statement-id": "so-incident-recovery-stmt", + "uuid": "a01c9815-7b15-56b5-85f2-fc2b7fb2e3b8", + "description": "dfetch is a stateless CLI tool. Recovery consists of re-running dfetch update, which re-fetches all dependencies. Not applicable: RLM-2, RLM-6 (CLI tool; no persistent device state or control-system backup needed)" + } + ] + }, + { + "uuid": "7b55fca6-d432-5234-9049-893e23dba785", + "control-id": "so-incident-resilience", + "props": [ + { + "name": "implementation-status", + "value": "partially-implemented" + }, + { + "name": "not-applicable", + "value": "RLM-3, RLM-4, RLM-5 (no persistent network services to prioritize)" + }, + { + "name": "gaps", + "value": "No timeout on VCS operations (potential resource exhaustion)" + }, + { + "name": "dfetch-controls", + "value": "C-002, C-007" + } + ], + "statements": [ + { + "statement-id": "so-incident-resilience-stmt", + "uuid": "bce717f3-5301-5ce6-b321-e0cb67b79431", + "description": "RLM-1: C-002 (no background daemon) and C-007 (subprocess controls) reduce exposure. DoS resilience applies only to the transient fetch operation. Gaps: No timeout on VCS operations (potential resource exhaustion) Not applicable: RLM-3, RLM-4, RLM-5 (no persistent network services to prioritize)" + } + ] + }, + { + "uuid": "b8466026-1031-5146-b310-aa467f324c0a", + "control-id": "so-limit-external-impact", + "props": [ + { + "name": "implementation-status", + "value": "partially-implemented" + }, + { + "name": "not-applicable", + "value": "TCM-1 (dfetch makes targeted VCS fetch requests; no ambient outbound traffic to throttle)" + }, + { + "name": "dfetch-controls", + "value": "C-001, C-007" + } + ], + "statements": [ + { + "statement-id": "so-limit-external-impact-stmt", + "uuid": "27745cea-042a-5b68-85d4-a3fc05e25645", + "description": "GEC-8-i: C-001 (minimal deps) and C-007 (subprocess controls) reduce the risk of dfetch being weaponised against external services. LIM-1: dfetch fetches only what is listed in the manifest. Not applicable: TCM-1 (dfetch makes targeted VCS fetch requests; no ambient outbound traffic to throttle)" + } + ] + }, + { + "uuid": "62ec731a-1065-5db3-a1cd-1aca9f95c257", + "control-id": "so-prevent-attack-propagation", + "props": [ + { + "name": "implementation-status", + "value": "planned" + }, + { + "name": "gaps", + "value": "No warning when dst: targets security-sensitive paths (\u2192 C-045 planned)" + }, + { + "name": "dfetch-controls", + "value": "C-045" + } + ], + "statements": [ + { + "statement-id": "so-prevent-attack-propagation-stmt", + "uuid": "0841a803-efcb-56d5-9498-c0a6d2d1a49d", + "description": "LIM-2: C-045 (planned) warns when dst: resolves to paths such as .github/workflows or .gitlab-ci.yml that could propagate a supply-chain attack to CI/CD infrastructure. Gaps: No warning when dst: targets security-sensitive paths (\u2192 C-045 planned)" + } + ] + }, + { + "uuid": "0f387a2a-852a-531b-811c-39fffecbd782", + "control-id": "so-monitor-external-impact", + "props": [ + { + "name": "implementation-status", + "value": "not-applicable" + }, + { + "name": "not-applicable", + "value": "NMM-1 (dfetch makes no ambient outbound network traffic to monitor)" + } + ], + "statements": [ + { + "statement-id": "so-monitor-external-impact-stmt", + "uuid": "73367208-aed2-52b0-88ba-799db9ded5e0", + "description": "dfetch makes targeted, user-initiated VCS requests only. Not applicable: NMM-1 (dfetch makes no ambient outbound network traffic to monitor)" + } + ] + }, + { + "uuid": "6519d184-d3b7-5f23-addb-6a7dd6540613", + "control-id": "so-reduce-attack-surface", + "props": [ + { + "name": "implementation-status", + "value": "partially-implemented" + }, + { + "name": "not-applicable", + "value": "GEC-2-j, GEC-3-j, GEC-4-j, GEC-5-j, GEC-7-j (dfetch exposes no network services)" + }, + { + "name": "dfetch-controls", + "value": "C-001, C-003, C-004, C-007, C-008" + } + ], + "statements": [ + { + "statement-id": "so-reduce-attack-surface-stmt", + "uuid": "d05109ab-eb71-5788-86f4-77ee6a6005f7", + "description": "GEC-6: C-007 (no shell=True), C-008 (URL/path validation) implement input validation. GEC-12-j: C-001 enforces minimal runtime dependencies. Not applicable: GEC-2-j, GEC-3-j, GEC-4-j, GEC-5-j, GEC-7-j (dfetch exposes no network services)" + } + ] + }, + { + "uuid": "7e317929-647b-56ff-8c68-2f7ddb437970", + "control-id": "so-reduce-impact-of-incident", + "props": [ + { + "name": "implementation-status", + "value": "planned" + }, + { + "name": "not-applicable", + "value": "Compile-time mitigations (CFI, sandboxing) \u2014 not applicable to pure Python" + }, + { + "name": "gaps", + "value": "No documented exploit mitigation inventory (\u2192 C-046 planned)" + }, + { + "name": "dfetch-controls", + "value": "C-005, C-007, C-015, C-017, C-046" + } + ], + "statements": [ + { + "statement-id": "so-reduce-impact-of-incident-stmt", + "uuid": "c6e07d34-53fe-5d62-8e20-14848fc721e1", + "description": "GEC-11: Python interpreter provides ASLR/DEP/stack-canaries (OS-level). dfetch: no eval/exec of remote content; constant-time comparison (C-005); shell=False (C-007); static analysis (C-015, C-017). C-046 (planned) formalises this inventory. Gaps: No documented exploit mitigation inventory (\u2192 C-046 planned) Not applicable: Compile-time mitigations (CFI, sandboxing) \u2014 not applicable to pure Python" + } + ] + }, + { + "uuid": "2f1733f3-78da-54dd-9908-3fa83ad82667", + "control-id": "so-log-security-relevant-activities", + "props": [ + { + "name": "implementation-status", + "value": "partially-implemented" + }, + { + "name": "gaps", + "value": "No persistent security event log (LGM-2/3/4 gap); No opt-out for logging \u2014 dfetch does not log by default" + }, + { + "name": "dfetch-controls", + "value": "C-036" + } + ], + "statements": [ + { + "statement-id": "so-log-security-relevant-activities-stmt", + "uuid": "25624d61-2d09-5f3f-b16c-9ab4f44755bf", + "description": "LGM-1: dfetch logs warnings to stderr during a run but does not persist them. LGM-6: C-036 ensures credentials are not logged. Gaps: No persistent security event log (LGM-2/3/4 gap); No opt-out for logging \u2014 dfetch does not log by default" + } + ] + }, + { + "uuid": "7b788c22-3f62-50bc-be07-9f464eb3602c", + "control-id": "so-monitor-security-relevant-activities", + "props": [ + { + "name": "implementation-status", + "value": "partially-implemented" + }, + { + "name": "not-applicable", + "value": "NMM-1 (no ambient network monitoring)" + }, + { + "name": "dfetch-controls", + "value": "C-009" + } + ], + "statements": [ + { + "statement-id": "so-monitor-security-relevant-activities-stmt", + "uuid": "70de7f3c-825a-520e-b8c9-e7e40d847c4a", + "description": "MON-1: C-009 (plaintext transport detection) monitors for insecure connections at runtime and surfaces warnings to the user. Not applicable: NMM-1 (no ambient network monitoring)" + } + ] + }, + { + "uuid": "449a14c3-9f28-5a1d-a3cf-d071ac57db88", + "control-id": "so-option-disable-data-logging", + "props": [ + { + "name": "implementation-status", + "value": "not-applicable" + }, + { + "name": "not-applicable", + "value": "LGM-5 (dfetch does not persist logs; nothing to disable)" + } + ], + "statements": [ + { + "statement-id": "so-option-disable-data-logging-stmt", + "uuid": "51dece0d-f373-52d0-9e66-5d1d8495318b", + "description": "dfetch emits transient stderr warnings only; no persistent log to opt out of. Not applicable: LGM-5 (dfetch does not persist logs; nothing to disable)" + } + ] + }, + { + "uuid": "74e99a9d-1f55-5fe1-a2db-83430f87b99e", + "control-id": "so-option-disable-data-monitoring", + "props": [ + { + "name": "implementation-status", + "value": "not-applicable" + }, + { + "name": "not-applicable", + "value": "MON-2 (no persistent monitoring to disable)" + } + ], + "statements": [ + { + "statement-id": "so-option-disable-data-monitoring-stmt", + "uuid": "8d1315f5-a299-5017-b607-304dd81171c2", + "description": "dfetch performs no ongoing monitoring between invocations. Not applicable: MON-2 (no persistent monitoring to disable)" + } + ] + }, + { + "uuid": "01d392f6-f590-5b9a-9aab-72cbb368026c", + "control-id": "so-secure-data-deletion", + "props": [ + { + "name": "implementation-status", + "value": "partially-implemented" + }, + { + "name": "gaps", + "value": "No built-in delete command for .dfetch_data.yaml (DLM-1 gap; user deletes manually)" + } + ], + "statements": [ + { + "statement-id": "so-secure-data-deletion-stmt", + "uuid": "6fe16078-41e4-5fc5-9002-f26643b1c3ac", + "description": "DLM-1: dfetch stores only dependency metadata (no personal data). Users can permanently delete .dfetch_data.yaml and vendored directories. Gaps: No built-in delete command for .dfetch_data.yaml (DLM-1 gap; user deletes manually)" + } + ] + }, + { + "uuid": "a3f137d0-aae2-5409-b04b-c3093db11586", + "control-id": "so-data-transmitted-confidentiality-m", + "props": [ + { + "name": "implementation-status", + "value": "not-applicable" + }, + { + "name": "not-applicable", + "value": "DLM-2, DLM-3, DLM-4 (dfetch does not export user data to external systems)" + } + ], + "statements": [ + { + "statement-id": "so-data-transmitted-confidentiality-m-stmt", + "uuid": "205cf8b1-df82-5234-83ac-00abe0df3b25", + "description": "dfetch does not provide data export or transfer functionality. Not applicable: DLM-2, DLM-3, DLM-4 (dfetch does not export user data to external systems)" + } + ] + }, + { + "uuid": "a95b048a-730c-5f8c-9475-50475c38de9b", + "control-id": "so-data-transmitted-integrity-m", + "props": [ + { + "name": "implementation-status", + "value": "not-applicable" + }, + { + "name": "not-applicable", + "value": "DLM-3 (no data export)" + } + ], + "statements": [ + { + "statement-id": "so-data-transmitted-integrity-m-stmt", + "uuid": "f4830f4a-a336-5efe-a503-fbc44d55ead0", + "description": "Not applicable \u2014 see SO.DataTransmittedConfidentiality (data export context). Not applicable: DLM-3 (no data export)" + } + ] + }, + { + "uuid": "37a0d0f3-cd43-5bec-9f93-2cd01786a66f", + "control-id": "so-com-auth-m", + "props": [ + { + "name": "implementation-status", + "value": "not-applicable" + }, + { + "name": "not-applicable", + "value": "DLM-4 (no data export)" + } + ], + "statements": [ + { + "statement-id": "so-com-auth-m-stmt", + "uuid": "a9a39ea7-5957-5c40-abc8-a8bef0766b8c", + "description": "Not applicable \u2014 see SO.DataTransmittedConfidentiality (data export context). Not applicable: DLM-4 (no data export)" + } + ] + } + ] + } + ] + } + ], + "back-matter": { + "resources": [ + { + "uuid": "816ae564-48fe-571f-8807-ea3d94bf206d", + "title": "Cyber Resilience Act \u2014 Regulation (EU) 2024/2847", + "rlinks": [ + { + "href": "https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX:32024R2847" + } + ] + }, + { + "uuid": "b44bda69-1a69-57f9-9545-95a7aa3a2d61", + "title": "prEN 40000-1-4 OSCAL Catalog (this repository)", + "rlinks": [ + { + "href": "security/cra_pren_4000014_oscal_catalog.json" + } + ] + }, + { + "uuid": "ba6fc625-58ea-5b9b-afc6-9358d247f19a", + "title": "dfetch Security Policy (SECURITY.md)", + "rlinks": [ + { + "href": "https://github.com/dfetch-org/dfetch/blob/main/SECURITY.md" + } + ] + } + ] + } + } +} \ No newline at end of file From db4e03cf1e23b5a08fb44aa6093f200e869174e0 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 15 Jun 2026 18:54:38 +0000 Subject: [PATCH 2/8] Fix three review findings in CRA Track B compliance module MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- doc/explanation/compliance_track.rst | 4 ++-- security/compliance.py | 13 +++++++++---- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/doc/explanation/compliance_track.rst b/doc/explanation/compliance_track.rst index 69e99b7c..481b19d0 100644 --- a/doc/explanation/compliance_track.rst +++ b/doc/explanation/compliance_track.rst @@ -325,7 +325,7 @@ Part II requirements are addressed via prEN 40000-1-3. pii-04 is not applicable Gap Analysis — Compliance-Only Controls --------------------------------------- -Three CRA essential requirements were not independently surfaced by the Track A risk models. The following controls address them. +4 compliance-only controls address CRA requirements not independently covered by the Track A risk models. **C-043 — Release-gate CVE check (ECR-a, SO.VulnerabilityManagementProcess → GEC-1)** @@ -337,7 +337,7 @@ dfetch processes dependency metadata only. The ``.dfetch_data.yaml`` file stores **C-045 — Destination path sensitivity warning (ECR-i, SO.PreventAttackPropagation → LIM-2)** -A compromised upstream repository could instruct dfetch to overwrite CI/CD configuration files (e.g. ``.github/workflows/``) via a malicious ``dst:`` value. C-045 (planned) adds a non-blocking warning when ``dst:`` resolves to a security-sensitive path, following the ``plaintext_warning()`` pattern in ``dfetch/manifest/project.py``. +A compromised upstream repository could instruct dfetch to overwrite CI/CD configuration files (e.g. ``.github/workflows/``) via a malicious ``dst:`` value. C-045 (planned) adds a non-blocking warning when ``dst:`` resolves to a security-sensitive path, following the ``plaintext_warning()`` pattern already used in ``dfetch/project/subproject.py``. **C-046 — Exploit mitigation inventory (ECR-k, SO.ReduceImpactOfIncident → GEC-11)** diff --git a/security/compliance.py b/security/compliance.py index 90d779d7..77eef449 100644 --- a/security/compliance.py +++ b/security/compliance.py @@ -67,6 +67,10 @@ def _load_track_a_controls() -> list[Control]: tm_sc = importlib.import_module("security.tm_supply_chain") tm_u = importlib.import_module("security.tm_usage") except ImportError: + print( + "Note: pytm not available — Track A controls omitted from control register.", + file=sys.stderr, + ) return [] sc_controls: list[Any] = getattr(tm_sc, "CONTROLS", []) u_controls: list[Any] = getattr(tm_u, "CONTROLS", []) @@ -430,7 +434,7 @@ def _gap_entries() -> list[tuple[str, str]]: "CI/CD configuration files (e.g. ``.github/workflows/``) via a malicious " "``dst:`` value. C-045 (planned) adds a non-blocking warning when ``dst:`` " "resolves to a security-sensitive path, following the ``plaintext_warning()`` " - "pattern in ``dfetch/manifest/project.py``." + "pattern already used in ``dfetch/project/subproject.py``." ), ), ( @@ -459,11 +463,12 @@ def _gap_entries() -> list[tuple[str, str]]: def _render_gap_analysis() -> None: """Print the Gap Analysis section.""" print(_rst_title("Gap Analysis — Compliance-Only Controls", "-")) + entries = _gap_entries() print( - "Three CRA essential requirements were not independently surfaced by " - "the Track A risk models. The following controls address them.\n" + f"{len(entries)} compliance-only controls address CRA requirements not " + "independently covered by the Track A risk models.\n" ) - for title, body in _gap_entries(): + for title, body in entries: print(f"**{title}**\n") print(f"{body}\n") From dc4081ac51ff3b3c32232fb1e284da314b2c53c8 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 15 Jun 2026 19:12:21 +0000 Subject: [PATCH 3/8] Resolve three ECR-e gaps as accepted design decisions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- doc/explanation/compliance_track.rst | 16 ++++---- security/compliance_data.py | 45 ++++++++++++++--------- security/dfetch.component-definition.json | 28 ++++---------- 3 files changed, 44 insertions(+), 45 deletions(-) diff --git a/doc/explanation/compliance_track.rst b/doc/explanation/compliance_track.rst index 481b19d0..ac8444f2 100644 --- a/doc/explanation/compliance_track.rst +++ b/doc/explanation/compliance_track.rst @@ -149,8 +149,8 @@ The table below summarises dfetch's implementation of each prEN 40000-1-4 Securi * - **ECR-E** — Protect the confidentiality of stored, transmitted or otherwise processed data by state-of-the-art mechanisms such as encryption at rest and in transit. - SO.DataStoredConfidentiality - C-036 - - .dfetch_data.yaml stored as plaintext YAML (no encryption at rest) - - ⚠ Partial + - — + - ✓ Implemented * - - SO.DataProcessedConfidentiality - C-005, C-034 @@ -158,14 +158,14 @@ The table below summarises dfetch's implementation of each prEN 40000-1-4 Securi - ✓ Implemented * - - SO.DataTransmittedConfidentiality - - C-009 - - HTTP accepted for remote URLs (SCM-3/SCM-4 gap; mitigated by C-009 warning) - - ⚠ Partial + - C-005, C-009 + - — + - ✓ Implemented * - - SO.ComAuth - - C-003, C-004 - - No end-to-end authenticity verification for plain git/svn transport - - ⚠ Partial + - C-003, C-004, C-009 + - — + - ✓ Implemented * - - SO.SecureProvisioning - C-005 diff --git a/security/compliance_data.py b/security/compliance_data.py index f7cba85b..24253e41 100644 --- a/security/compliance_data.py +++ b/security/compliance_data.py @@ -301,12 +301,15 @@ class PartIIRequirement: so_id="so-data-stored-confidentiality", ecr_id="ecr-e", controls=["C-036"], - gaps=[".dfetch_data.yaml stored as plaintext YAML (no encryption at rest)"], - status="partially-implemented", + status="implemented", description=( - "SSM-1/SSM-3: C-036 strips userinfo from URLs before storage. The " - ".dfetch_data.yaml file itself is not encrypted; encryption would require " - "key management infrastructure not appropriate for a developer tool." + "SSM-1/SSM-3: The developer workstation is a trusted boundary (as established " + "in the usage threat model: 'Trusted at workstation invocation time'). " + "C-036 strips all credentials from .dfetch_data.yaml before write, so the " + "stored file contains only non-sensitive metadata (remote URL without userinfo, " + "revision, content hash, timestamp). The threat model explicitly marks " + "metadata_store.storesSensitiveData = False and isDestEncryptedAtRest = False " + "as a conscious design choice; no encryption-at-rest is required." ), ), SOImplementation( @@ -322,25 +325,33 @@ class PartIIRequirement: SOImplementation( so_id="so-data-transmitted-confidentiality", ecr_id="ecr-e", - controls=["C-009"], - gaps=[ - "HTTP accepted for remote URLs (SCM-3/SCM-4 gap; mitigated by C-009 warning)" - ], - status="partially-implemented", + controls=["C-005", "C-009"], + status="implemented", description=( - "SCM-3/SCM-4: dfetch warns on plaintext transport (C-009) but accepts " - "HTTP remote URLs to avoid breaking existing manifests." + "SCM-3/SCM-4: Plaintext transport (http://, git://, svn://) is accepted " + "by design for legacy source compatibility. C-009 detects and warns the " + "user before proceeding — 'Detection only; dfetch still proceeds with the " + "plaintext connection; the control raises user awareness but does not " + "enforce scheme selection' (usage threat model, C-009 description). " + "For archive URLs over HTTP, C-005 (integrity hash) provides content-level " + "confidentiality assurance independent of transport. This is a deliberate " + "design decision, not a residual gap." ), ), SOImplementation( so_id="so-com-auth-e", ecr_id="ecr-e", - controls=["C-003", "C-004"], - gaps=["No end-to-end authenticity verification for plain git/svn transport"], - status="partially-implemented", + controls=["C-003", "C-004", "C-009"], + status="implemented", description=( - "SCM-2: C-003 (TLS CA verification for HTTPS) and C-004 (SSH host-key " - "checking) provide authenticity for most connections." + "SCM-2: HTTPS connections authenticate via TLS CA chain (C-003); SSH " + "connections authenticate via host-key verification (C-004). Plain git:// " + "and svn:// connections lack channel-level authentication but are accepted " + "by design for legacy compatibility — the same rationale as " + "DataTransmittedConfidentiality — and C-009 warns the user. The usage " + "threat model notes the network adversary 'cannot break correctly " + "implemented TLS or SSH'; the residual risk for unauthenticated transports " + "is an accepted design trade-off." ), ), SOImplementation( diff --git a/security/dfetch.component-definition.json b/security/dfetch.component-definition.json index cff7646b..bc1ec5c4 100644 --- a/security/dfetch.component-definition.json +++ b/security/dfetch.component-definition.json @@ -283,11 +283,7 @@ "props": [ { "name": "implementation-status", - "value": "partially-implemented" - }, - { - "name": "gaps", - "value": ".dfetch_data.yaml stored as plaintext YAML (no encryption at rest)" + "value": "implemented" }, { "name": "dfetch-controls", @@ -298,7 +294,7 @@ { "statement-id": "so-data-stored-confidentiality-stmt", "uuid": "d6a8cfa8-3e30-5318-8ca6-59f99fb7dbff", - "description": "SSM-1/SSM-3: C-036 strips userinfo from URLs before storage. The .dfetch_data.yaml file itself is not encrypted; encryption would require key management infrastructure not appropriate for a developer tool. Gaps: .dfetch_data.yaml stored as plaintext YAML (no encryption at rest)" + "description": "SSM-1/SSM-3: The developer workstation is a trusted boundary (as established in the usage threat model: 'Trusted at workstation invocation time'). C-036 strips all credentials from .dfetch_data.yaml before write, so the stored file contains only non-sensitive metadata (remote URL without userinfo, revision, content hash, timestamp). The threat model explicitly marks metadata_store.storesSensitiveData = False and isDestEncryptedAtRest = False as a conscious design choice; no encryption-at-rest is required." } ] }, @@ -329,22 +325,18 @@ "props": [ { "name": "implementation-status", - "value": "partially-implemented" - }, - { - "name": "gaps", - "value": "HTTP accepted for remote URLs (SCM-3/SCM-4 gap; mitigated by C-009 warning)" + "value": "implemented" }, { "name": "dfetch-controls", - "value": "C-009" + "value": "C-005, C-009" } ], "statements": [ { "statement-id": "so-data-transmitted-confidentiality-stmt", "uuid": "4440d616-231f-51ca-a514-c5cd42a3851b", - "description": "SCM-3/SCM-4: dfetch warns on plaintext transport (C-009) but accepts HTTP remote URLs to avoid breaking existing manifests. Gaps: HTTP accepted for remote URLs (SCM-3/SCM-4 gap; mitigated by C-009 warning)" + "description": "SCM-3/SCM-4: Plaintext transport (http://, git://, svn://) is accepted by design for legacy source compatibility. C-009 detects and warns the user before proceeding \u2014 'Detection only; dfetch still proceeds with the plaintext connection; the control raises user awareness but does not enforce scheme selection' (usage threat model, C-009 description). For archive URLs over HTTP, C-005 (integrity hash) provides content-level confidentiality assurance independent of transport. This is a deliberate design decision, not a residual gap." } ] }, @@ -354,22 +346,18 @@ "props": [ { "name": "implementation-status", - "value": "partially-implemented" - }, - { - "name": "gaps", - "value": "No end-to-end authenticity verification for plain git/svn transport" + "value": "implemented" }, { "name": "dfetch-controls", - "value": "C-003, C-004" + "value": "C-003, C-004, C-009" } ], "statements": [ { "statement-id": "so-com-auth-e-stmt", "uuid": "c083999a-6c6c-5185-a7ad-8ee203dc13a4", - "description": "SCM-2: C-003 (TLS CA verification for HTTPS) and C-004 (SSH host-key checking) provide authenticity for most connections. Gaps: No end-to-end authenticity verification for plain git/svn transport" + "description": "SCM-2: HTTPS connections authenticate via TLS CA chain (C-003); SSH connections authenticate via host-key verification (C-004). Plain git:// and svn:// connections lack channel-level authentication but are accepted by design for legacy compatibility \u2014 the same rationale as DataTransmittedConfidentiality \u2014 and C-009 warns the user. The usage threat model notes the network adversary 'cannot break correctly implemented TLS or SSH'; the residual risk for unauthenticated transports is an accepted design trade-off." } ] }, From b17c5778ef9e3147c54dc8d9639420aa61f5eba3 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 15 Jun 2026 19:22:02 +0000 Subject: [PATCH 4/8] Resolve ECR-i and ECR-m gaps as accepted design decisions; drop C-045 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- CHANGELOG.rst | 3 +- doc/explanation/compliance_track.rst | 20 ++++------- security/compliance.py | 11 ------ security/compliance_data.py | 42 ++++++++--------------- security/dfetch.component-definition.json | 18 +++------- 5 files changed, 27 insertions(+), 67 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index fa562b27..4e4e02c9 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,8 +4,7 @@ Release 0.15.0 (unreleased) * Add CRA Compliance Track B: OSCAL 1.1.2 Component Definition mapping all CRA Annex I Part I essential requirements (ECR-a–m) through prEN 40000-1-4 Security Objectives to dfetch controls; covers Part II via prEN 40000-1-3; introduces controls C-043 (release-gate CVE check), C-044 - (data minimisation policy), C-045 (destination-path sensitivity warning), and C-046 (exploit - mitigation inventory) + (data minimisation policy), and C-046 (exploit mitigation inventory) Release 0.14.0 (released 2026-06-14) =========================== diff --git a/doc/explanation/compliance_track.rst b/doc/explanation/compliance_track.rst index ac8444f2..097d2dc0 100644 --- a/doc/explanation/compliance_track.rst +++ b/doc/explanation/compliance_track.rst @@ -213,9 +213,9 @@ The table below summarises dfetch's implementation of each prEN 40000-1-4 Securi - ⚠ Partial * - - SO.PreventAttackPropagation - - C-045 - - No warning when dst: targets security-sensitive paths (→ C-045 planned) - - ○ Planned + - C-001, C-008 + - — + - ✓ Implemented * - - SO.MonitorExternalImpact - — @@ -254,8 +254,8 @@ The table below summarises dfetch's implementation of each prEN 40000-1-4 Securi * - **ECR-M** — Provide the possibility for users to securely and easily remove on a permanent basis all data and settings and, where such data can be transferred to other products or systems, ensure that this is done in a secure manner. - SO.SecureDataDeletion - — - - No built-in delete command for .dfetch_data.yaml (DLM-1 gap; user deletes manually) - - ⚠ Partial + - — + - ✓ Implemented * - - SO.DataTransmittedConfidentiality - — @@ -325,7 +325,7 @@ Part II requirements are addressed via prEN 40000-1-3. pii-04 is not applicable Gap Analysis — Compliance-Only Controls --------------------------------------- -4 compliance-only controls address CRA requirements not independently covered by the Track A risk models. +3 compliance-only controls address CRA requirements not independently covered by the Track A risk models. **C-043 — Release-gate CVE check (ECR-a, SO.VulnerabilityManagementProcess → GEC-1)** @@ -335,10 +335,6 @@ dfetch's CI detects vulnerabilities at commit time (C-015, C-016, C-017) but doe dfetch processes dependency metadata only. The ``.dfetch_data.yaml`` file stores: ``remote_url`` (credentials stripped by C-036), ``revision``, optional ``integrity.hash``, and ``last_fetch`` timestamp. Each field is functionally necessary for ``dfetch check`` and ``dfetch freeze``. No personal data is collected; no telemetry is sent. C-044 formalises this assertion as a documented policy. -**C-045 — Destination path sensitivity warning (ECR-i, SO.PreventAttackPropagation → LIM-2)** - -A compromised upstream repository could instruct dfetch to overwrite CI/CD configuration files (e.g. ``.github/workflows/``) via a malicious ``dst:`` value. C-045 (planned) adds a non-blocking warning when ``dst:`` resolves to a security-sensitive path, following the ``plaintext_warning()`` pattern already used in ``dfetch/project/subproject.py``. - **C-046 — Exploit mitigation inventory (ECR-k, SO.ReduceImpactOfIncident → GEC-11)** prEN 40000-1-4 ECR-k requires documenting applicable exploit mitigation techniques. For dfetch (pure Python): @@ -492,10 +488,6 @@ All controls from Track A (risk-driven) and Track B (regulatory) merged and sort - Data minimisation policy - Track B - doc/explanation/compliance_track.rst (this document) - * - C-045 - - Destination path sensitivity warning - - Track B - - dfetch/project/subproject.py (planned: _is_sensitive_dst()) * - C-046 - Exploit mitigation inventory - Track B diff --git a/security/compliance.py b/security/compliance.py index 77eef449..87c18aa6 100644 --- a/security/compliance.py +++ b/security/compliance.py @@ -426,17 +426,6 @@ def _gap_entries() -> list[tuple[str, str]]: "C-044 formalises this assertion as a documented policy." ), ), - ( - "C-045 — Destination path sensitivity warning" - + " (ECR-i, SO.PreventAttackPropagation → LIM-2)", - ( - "A compromised upstream repository could instruct dfetch to overwrite " - "CI/CD configuration files (e.g. ``.github/workflows/``) via a malicious " - "``dst:`` value. C-045 (planned) adds a non-blocking warning when ``dst:`` " - "resolves to a security-sensitive path, following the ``plaintext_warning()`` " - "pattern already used in ``dfetch/project/subproject.py``." - ), - ), ( "C-046 — Exploit mitigation inventory" + " (ECR-k, SO.ReduceImpactOfIncident → GEC-11)", diff --git a/security/compliance_data.py b/security/compliance_data.py index 24253e41..f2c3785a 100644 --- a/security/compliance_data.py +++ b/security/compliance_data.py @@ -130,7 +130,7 @@ class PartIIRequirement: ), ] -# ── Track B controls (C-043–C-046) ─────────────────────────────────────────── +# ── Track B controls (C-043–C-044, C-046) ──────────────────────────────────── TRACK_B_CONTROLS: list[Control] = [ Control( @@ -156,17 +156,6 @@ class PartIIRequirement: threats=["Threat.UnnecessaryDataMisuse"], reference="doc/explanation/compliance_track.rst (this document)", ), - Control( - id="C-045", - name="Destination path sensitivity warning", - description=( - "Non-blocking warning when dst: resolves to a security-sensitive path " - "(.github/workflows, .gitlab-ci.yml, CI/CD directories), following the " - "plaintext_warning() pattern." - ), - threats=["Threat.ExtServiceAvailabilityDegradation"], - reference="dfetch/project/subproject.py (planned: _is_sensitive_dst())", - ), Control( id="C-046", name="Exploit mitigation inventory", @@ -471,15 +460,15 @@ class PartIIRequirement: SOImplementation( so_id="so-prevent-attack-propagation", ecr_id="ecr-i", - controls=["C-045"], - gaps=[ - "No warning when dst: targets security-sensitive paths (→ C-045 planned)" - ], - status="planned", + controls=["C-001", "C-008"], + status="implemented", description=( - "LIM-2: C-045 (planned) warns when dst: resolves to paths such as " - ".github/workflows or .gitlab-ci.yml that could propagate a supply-chain " - "attack to CI/CD infrastructure." + "LIM-2: Path traversal to destinations outside the project tree is " + "prevented by C-001 (check_no_path_traversal() via realpath). " + "The residual risk of a manifest declaring a legitimate but sensitive " + "dst: (e.g. .github/workflows/) is accepted in the usage threat model " + "under the 'Manifest under code review' assumption: dfetch.yaml is " + "version-controlled and any such dst: change would be rejected at review." ), ), SOImplementation( @@ -567,14 +556,13 @@ class PartIIRequirement: SOImplementation( so_id="so-secure-data-deletion", ecr_id="ecr-m", - gaps=[ - "No built-in delete command for .dfetch_data.yaml " - "(DLM-1 gap; user deletes manually)" - ], - status="partially-implemented", + status="implemented", description=( - "DLM-1: dfetch stores only dependency metadata (no personal data). " - "Users can permanently delete .dfetch_data.yaml and vendored directories." + "DLM-1: .dfetch_data.yaml contains only non-sensitive metadata — " + "remote URL (credentials stripped by C-036), revision, optional content " + "hash, and last-fetch timestamp. Standard OS file deletion (rm / del) is " + "sufficient; no secure-wipe is required. Users delete the file and " + "vendored directories to remove all dfetch data." ), ), SOImplementation( diff --git a/security/dfetch.component-definition.json b/security/dfetch.component-definition.json index bc1ec5c4..dc1c9b67 100644 --- a/security/dfetch.component-definition.json +++ b/security/dfetch.component-definition.json @@ -588,22 +588,18 @@ "props": [ { "name": "implementation-status", - "value": "planned" - }, - { - "name": "gaps", - "value": "No warning when dst: targets security-sensitive paths (\u2192 C-045 planned)" + "value": "implemented" }, { "name": "dfetch-controls", - "value": "C-045" + "value": "C-001, C-008" } ], "statements": [ { "statement-id": "so-prevent-attack-propagation-stmt", "uuid": "0841a803-efcb-56d5-9498-c0a6d2d1a49d", - "description": "LIM-2: C-045 (planned) warns when dst: resolves to paths such as .github/workflows or .gitlab-ci.yml that could propagate a supply-chain attack to CI/CD infrastructure. Gaps: No warning when dst: targets security-sensitive paths (\u2192 C-045 planned)" + "description": "LIM-2: Path traversal to destinations outside the project tree is prevented by C-001 (check_no_path_traversal() via realpath). The residual risk of a manifest declaring a legitimate but sensitive dst: (e.g. .github/workflows/) is accepted in the usage threat model under the 'Manifest under code review' assumption: dfetch.yaml is version-controlled and any such dst: change would be rejected at review." } ] }, @@ -780,18 +776,14 @@ "props": [ { "name": "implementation-status", - "value": "partially-implemented" - }, - { - "name": "gaps", - "value": "No built-in delete command for .dfetch_data.yaml (DLM-1 gap; user deletes manually)" + "value": "implemented" } ], "statements": [ { "statement-id": "so-secure-data-deletion-stmt", "uuid": "6fe16078-41e4-5fc5-9002-f26643b1c3ac", - "description": "DLM-1: dfetch stores only dependency metadata (no personal data). Users can permanently delete .dfetch_data.yaml and vendored directories. Gaps: No built-in delete command for .dfetch_data.yaml (DLM-1 gap; user deletes manually)" + "description": "DLM-1: .dfetch_data.yaml contains only non-sensitive metadata \u2014 remote URL (credentials stripped by C-036), revision, optional content hash, and last-fetch timestamp. Standard OS file deletion (rm / del) is sufficient; no secure-wipe is required. Users delete the file and vendored directories to remove all dfetch data." } ] }, From f0b0b76904cff72feb716339226c46cb6ddfd3ea Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 15 Jun 2026 19:45:38 +0000 Subject: [PATCH 5/8] Add OSCAL Component Definition to release artifacts 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 --- .github/workflows/python-publish.yml | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index 844da380..e93225dc 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -52,6 +52,13 @@ jobs: run: python3 -m build - name: Generate SBOM for Python distribution run: python script/create_sbom.py --py --output-dir dist-sbom + - name: Generate OSCAL Component Definition + run: | + mkdir -p dist-oscal + VERSION=$(python -c "import importlib.metadata; print(importlib.metadata.version('dfetch'))") + python -m security.compliance \ + --component dist-oscal/dfetch.component-definition.json \ + --version "$VERSION" - name: Store the distribution packages uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: @@ -73,6 +80,11 @@ jobs: with: name: python-sbom path: dist-sbom/ + - name: Store the OSCAL Component Definition + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: oscal-component-definition + path: dist-oscal/ publish-to-testpypi: name: Publish Python distribution 📦 to TestPyPI @@ -131,10 +143,17 @@ jobs: with: name: python-sbom path: dist-sbom/ - - name: Upload SBOM to GitHub Release + - name: Download OSCAL Component Definition + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: oscal-component-definition + path: dist-oscal/ + - name: Upload SBOM and OSCAL to GitHub Release uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v2.5.0 with: tag_name: ${{ github.event.release.tag_name }} - files: dist-sbom/* + files: | + dist-sbom/* + dist-oscal/* env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From b7329d2f09f0cd72b4be4116efe6814f24198907 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 15 Jun 2026 19:46:55 +0000 Subject: [PATCH 6/8] Attest OSCAL Component Definition with SLSA build provenance MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .github/workflows/python-publish.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index e93225dc..49ed25fa 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -80,6 +80,10 @@ jobs: with: name: python-sbom path: dist-sbom/ + - name: Attest OSCAL build provenance + uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0 + with: + subject-path: dist-oscal/dfetch.component-definition.json - name: Store the OSCAL Component Definition uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: From 7c5c62240a6e5d8075f981a1dfb718cc65a647dd Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 15 Jun 2026 21:07:05 +0000 Subject: [PATCH 7/8] Fix RST asterisk escaping, ImportError breadth, and C-005 description accuracy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 `(? doc/explanation/compliance_track.rst - diff --git a/security/compliance.py b/security/compliance.py index 87c18aa6..2d12f094 100644 --- a/security/compliance.py +++ b/security/compliance.py @@ -18,6 +18,7 @@ import importlib import json import os +import re import sys import uuid from datetime import date @@ -66,7 +67,7 @@ def _load_track_a_controls() -> list[Control]: try: tm_sc = importlib.import_module("security.tm_supply_chain") tm_u = importlib.import_module("security.tm_usage") - except ImportError: + except ModuleNotFoundError: print( "Note: pytm not available — Track A controls omitted from control register.", file=sys.stderr, @@ -274,6 +275,11 @@ def _rst_title(text: str, char: str = "=") -> str: return f"{text}\n{char * len(text)}\n" +def _rst_escape_star(text: str) -> str: + """Escape standalone * in RST cell text without touching **bold** markup.""" + return re.sub(r"(? Date: Mon, 15 Jun 2026 21:10:51 +0000 Subject: [PATCH 8/8] Add --track-b-only flag; fail fast when Track A controls unavailable 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 --- .github/workflows/python-publish.yml | 3 ++- doc/explanation/compliance_track.rst | 1 + security/README.md | 5 +++- security/compliance.py | 40 ++++++++++++++++++++-------- 4 files changed, 36 insertions(+), 13 deletions(-) diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index 49ed25fa..ae058b51 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -58,7 +58,8 @@ jobs: VERSION=$(python -c "import importlib.metadata; print(importlib.metadata.version('dfetch'))") python -m security.compliance \ --component dist-oscal/dfetch.component-definition.json \ - --version "$VERSION" + --version "$VERSION" \ + --track-b-only - name: Store the distribution packages uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: diff --git a/doc/explanation/compliance_track.rst b/doc/explanation/compliance_track.rst index a07d49e8..c96d1414 100644 --- a/doc/explanation/compliance_track.rst +++ b/doc/explanation/compliance_track.rst @@ -503,4 +503,5 @@ regenerated with: python -m security.compliance \\ --component security/dfetch.component-definition.json \\ + --track-b-only \\ --rst > doc/explanation/compliance_track.rst diff --git a/security/README.md b/security/README.md index 41c9eeb8..926b8df8 100644 --- a/security/README.md +++ b/security/README.md @@ -24,11 +24,14 @@ python -m security.tm_usage --seq ## CRA Compliance Track B -The compliance track does not require pytm. Regenerate with: +The compliance track does not require pytm. Use `--track-b-only` when pytm is +not installed to explicitly opt in to Track-B-only output (otherwise the +command fails fast to avoid silently incomplete artifacts): ```bash python -m security.compliance \ --component security/dfetch.component-definition.json \ + --track-b-only \ --rst > doc/explanation/compliance_track.rst ``` diff --git a/security/compliance.py b/security/compliance.py index 2d12f094..b88f9751 100644 --- a/security/compliance.py +++ b/security/compliance.py @@ -62,14 +62,23 @@ def _so_title(so_id: str) -> str: return "SO." + "".join(p.capitalize() for p in parts) -def _load_track_a_controls() -> list[Control]: - """Load Track A controls from threat models if pytm is available.""" +def _load_track_a_controls(track_b_only: bool = False) -> list[Control]: + """Load Track A controls from threat models if pytm is available. + + Raises RuntimeError when pytm is unavailable and track_b_only is False, + so callers must explicitly opt in to Track-B-only degraded mode. + """ try: tm_sc = importlib.import_module("security.tm_supply_chain") tm_u = importlib.import_module("security.tm_usage") - except ModuleNotFoundError: + except ModuleNotFoundError as exc: + if not track_b_only: + raise RuntimeError( + "Track A controls unavailable (pytm not installed). " + "Pass --track-b-only to generate a Track B only artifact." + ) from exc print( - "Note: pytm not available — Track A controls omitted from control register.", + "Note: pytm not available — Track A controls omitted (--track-b-only).", file=sys.stderr, ) return [] @@ -81,9 +90,9 @@ def _load_track_a_controls() -> list[Control]: ] -def get_all_controls() -> list[Control]: +def get_all_controls(track_b_only: bool = False) -> list[Control]: """Return merged, deduplicated, sorted control register from both tracks.""" - track_a = _load_track_a_controls() + track_a = _load_track_a_controls(track_b_only=track_b_only) seen: set[str] = set() merged: list[Control] = [] for ctrl in track_a + TRACK_B_CONTROLS: @@ -470,7 +479,7 @@ def _render_gap_analysis() -> None: print(f"{body}\n") -def _render_control_register() -> None: +def _render_control_register(track_b_only: bool = False) -> None: """Print the final merged control register table.""" print(_rst_title("Final Control Register", "-")) print( @@ -485,7 +494,7 @@ def _render_control_register() -> None: "Track B" if ctrl.id in track_b_ids else "Track A", ctrl.reference or "—", ] - for ctrl in get_all_controls() + for ctrl in get_all_controls(track_b_only=track_b_only) ] print( _rst_list_table( @@ -496,7 +505,7 @@ def _render_control_register() -> None: ) -def render_rst() -> None: +def render_rst(track_b_only: bool = False) -> None: """Print the full compliance track RST document to stdout.""" print(".. _compliance_track:\n") print(_rst_title("CRA Compliance Track B")) @@ -526,7 +535,7 @@ def render_rst() -> None: _render_part_i_table() _render_part_ii_table() _render_gap_analysis() - _render_control_register() + _render_control_register(track_b_only=track_b_only) print(_rst_title("OSCAL Artifacts", "-")) print( "The OSCAL 1.1.2 Component Definition references the catalog file and can be\n" @@ -534,6 +543,7 @@ def render_rst() -> None: ".. code-block:: bash\n\n" " python -m security.compliance \\\\\n" " --component security/dfetch.component-definition.json \\\\\n" + " --track-b-only \\\\\n" " --rst > doc/explanation/compliance_track.rst\n" ) @@ -553,6 +563,14 @@ def render_rst() -> None: parser.add_argument( "--version", default="0.14.0", help="dfetch version (default: 0.14.0)" ) + parser.add_argument( + "--track-b-only", + action="store_true", + help=( + "Omit Track A controls when pytm is not installed instead of failing. " + "Must be set explicitly to allow degraded output." + ), + ) args = parser.parse_args() if args.component: @@ -562,4 +580,4 @@ def render_rst() -> None: print(f"Written: {args.component}", file=sys.stderr) if args.rst: - render_rst() + render_rst(track_b_only=args.track_b_only)