diff --git a/doc/explanation/security.rst b/doc/explanation/security.rst index 503c29b3..462d64d2 100644 --- a/doc/explanation/security.rst +++ b/doc/explanation/security.rst @@ -173,6 +173,46 @@ for regeneration instructions. threat_model_supply_chain threat_model_usage +.. _identified-gaps: + +Identified Gaps (Reserved Control IDs) +-------------------------------------- + +The runtime-usage threat model references four control IDs that are reserved +for improvements not yet implemented. Reserving the IDs keeps the control +numbering stable and makes each gap a first-class part of the risk analysis — +you can trace every "gap C-0xx" mention in the threat models back to this +table: + +.. list-table:: + :header-rows: 1 + :widths: 10 90 + + * - ID + - Planned control + * - C-018 + - Integrity-hash enforcement for archive dependencies. The + ``integrity.hash`` manifest field is optional today; when it is absent no + content verification occurs. You can bootstrap the hash for an existing + manifest with ``dfetch freeze``. + * - C-019 + - End-to-end content integrity for Git and SVN dependencies. Authenticity + currently relies on transport security (TLS / SSH) and the integrity of + the upstream repository; pinning to an immutable commit SHA is the + strongest mitigation available today. + * - C-020 + - Patch-file integrity verification. ``patch:`` files referenced by the + manifest carry no hash, and the destination paths inside a patch are + validated by ``patch-ng`` rather than independently by dfetch. + * - C-035 + - Upstream source-attestation verification. dfetch has no mechanism yet to + request or verify SLSA Source Provenance Attestations (:term:`VSA`) for + fetched repositories, and the manifest schema has no field to declare an + expected SLSA source level. + +When one of these controls is implemented, it moves to the +:doc:`control_register` under its reserved ID. + CRA Compliance -------------- @@ -235,7 +275,7 @@ OSCAL :term:`OSCAL` (Open Security Controls Assessment Language) is a set of NIST-published JSON/XML schemas for machine-readable security documentation. -dfetch uses OSCAL 1.1.2 for two artifacts: +dfetch uses OSCAL 1.2.2 for two artifacts: - `OSCAL Catalog model `_ — used by ``cra_pren_4000014_oscal_catalog.json`` to represent the prEN 40000-1-4 diff --git a/doc/explanation/threat_model_supply_chain.rst b/doc/explanation/threat_model_supply_chain.rst index 7d88c7cc..b15e9f11 100644 --- a/doc/explanation/threat_model_supply_chain.rst +++ b/doc/explanation/threat_model_supply_chain.rst @@ -82,7 +82,7 @@ Boundaries - Python Package Index and its staging registry. dfetch publishes via OIDC trusted publishing - no long-lived API token stored. * - Winget Community Repository - - The Windows Package Manager Community Repository (https://github.com/microsoft/winget-pkgs) where dfetch's Winget manifest is hosted. Manifest PRs are submitted automatically by the CI release pipeline (winget-publish.yml) using the stored WINGET_TOKEN PAT (A-10). Consumer installations resolve manifests from this repository; winget downloads the MSI installer from the URL declared in the manifest (pointing to GitHub Releases, A-01) and verifies its SHA256 hash. + - The Windows Package Manager Community Repository (https://github.com/microsoft/winget-pkgs) where dfetch's Winget manifest is hosted. Manifest PRs are submitted automatically by the CI release pipeline (winget-publish.yml) using the stored WINGET_TOKEN PAT (A-29). Consumer installations resolve manifests from this repository; winget downloads the MSI installer from the URL declared in the manifest (pointing to GitHub Releases, A-01) and verifies its SHA256 hash. Data Flow Diagram @@ -258,7 +258,7 @@ Data Flow Diagram shape = square; color = black; fontcolor = black; - label = "A-09: Winget\nCommunity\nRepository\n(microsoft/winget-\npkgs)"; + label = "A-28: Winget\nCommunity\nRepository\n(microsoft/winget-\npkgs)"; margin = 0.02; ] @@ -458,7 +458,7 @@ Sequence Diagram entity process_APythonBuildwheelsdist_b2e5892d06 as "A-08: Python\nBuild (wheel\n/ sdist)" database datastore_AdfetchBuildDevDependencies_990b886585 as "A-07: dfetch\nBuild / Dev\nDependencies" database datastore_AbGitHubActionsBuildCache_9df04f8dae as "A-08b:\nGitHub\nActions\nBuild Cache" - entity externalentity_AWingetCommunityRepositorymicrosoftwingetpkgs_7113ed0f48 as "A-09: Winget\nCommunity\nRepository\n(microsoft/winget-pkgs)" + entity externalentity_AWingetCommunityRepositorymicrosoftwingetpkgs_7113ed0f48 as "A-28: Winget\nCommunity\nRepository\n(microsoft/winget-pkgs)" actor_DeveloperContributor_d2006ce1bb -> externalentity_AbGitHubRepositoryfeaturebranchesPRs_0291419f72: DF-11: Push commits / open PR externalentity_AbGitHubRepositoryfeaturebranchesPRs_0291419f72 -> process_AReleaseGateCodeReview_9345ab4c19: DF-22: PR enters code review @@ -554,7 +554,7 @@ Asset Identification - Data - High / High / High * - A-06: GitHub Actions Workflow - - CI/CD pipelines: test, build (wheel/msi/deb/rpm), lint, CodeQL, Scorecard, dependency-review, docs, release, winget-publish. All actions pinned by commit SHA. harden-runner used in every workflow that executes steps on a runner (egress: block with endpoint allowlist); ci.yml is a dispatcher-only workflow with no runner steps and does not include harden-runner. winget-publish.yml uses a stored PAT (WINGET_TOKEN, A-10) to submit manifest PRs to the Winget Community Repository (A-09). + - CI/CD pipelines: test, build (wheel/msi/deb/rpm), lint, CodeQL, Scorecard, dependency-review, docs, release, winget-publish. All actions pinned by commit SHA. harden-runner used in every workflow that executes steps on a runner (egress: block with endpoint allowlist); ci.yml is a dispatcher-only workflow with no runner steps and does not include harden-runner. winget-publish.yml uses a stored PAT (WINGET_TOKEN, A-29) to submit manifest PRs to the Winget Community Repository (A-28). - Process - Medium / Medium / Medium * - A-07: dfetch Build / Dev Dependencies @@ -569,11 +569,11 @@ Asset Identification - GitHub Actions cache entries written and restored across pipeline runs. Used to speed up dependency installation (pip, gem) and incremental builds. Cache-poisoning from forked PRs (DFT-28, SLSA E6: poison the build cache) is mitigated by ref-scoped cache keys: build.yml includes ``${{ github.ref_name }}`` in both ``key`` and ``restore-keys`` (C-033), which isolates PR and release caches per branch so a fork cannot write into the release cache namespace. - Datastore - High / High / — - * - A-09: Winget Community Repository (microsoft/winget-pkgs) - - The Windows Package Manager Community Repository where the dfetch ``DFetch-org.DFetch`` manifest is hosted (https://github.com/microsoft/winget-pkgs). CI submits manifest update PRs via ``vedantmgoyal9/winget-releaser`` using a stored PAT (A-10); PRs are reviewed by ``microsoft/winget-pkgs`` maintainers before merging (C-041). Manifests contain SHA256 hashes of the installer binary; winget verifies the hash before installation. A compromised PAT or a fraudulent PR that passes review could redirect consumers to a malicious installer (DFT-35). + * - A-28: Winget Community Repository (microsoft/winget-pkgs) + - The Windows Package Manager Community Repository where the dfetch ``DFetch-org.DFetch`` manifest is hosted (https://github.com/microsoft/winget-pkgs). CI submits manifest update PRs via ``vedantmgoyal9/winget-releaser`` using a stored PAT (A-29); PRs are reviewed by ``microsoft/winget-pkgs`` maintainers before merging (C-041). Manifests contain SHA256 hashes of the installer binary; winget verifies the hash before installation. A compromised PAT or a fraudulent PR that passes review could redirect consumers to a malicious installer (DFT-35). - ExternalEntity - High / High / — - * - A-10: WINGET_TOKEN PAT + * - A-29: WINGET_TOKEN PAT - Long-lived classic GitHub Personal Access Token (fine-grained PATs are not supported by the ``vedantmgoyal9/winget-releaser`` action) with ``public_repo`` scope, stored as a GitHub Actions environment secret in the ``winget`` environment. Used by ``winget-publish.yml`` to push a manifest branch to the pre-existing fork ``dfetch-org/winget-pkgs`` and open a PR against ``microsoft/winget-pkgs``. Unlike the PyPI OIDC token (A-05) which is short-lived and not stored, this PAT persists indefinitely until rotated. If exfiltrated from the CI environment, an attacker could submit fraudulent manifest PRs from outside the project's pipeline. - Data - High / High / — @@ -677,16 +677,16 @@ Dataflows * - DF-27: Winget manifest PR submission - A-02: GitHub Actions Infrastructure - - A-09: Winget Community Repository (microsoft/winget-pkgs) + - A-28: Winget Community Repository (microsoft/winget-pkgs) - HTTPS * - DF-28: winget install dfetch - Consumer / End User - - A-09: Winget Community Repository (microsoft/winget-pkgs) + - A-28: Winget Community Repository (microsoft/winget-pkgs) - HTTPS * - DF-29: Consumer downloads MSI via winget - - A-09: Winget Community Repository (microsoft/winget-pkgs) + - A-28: Winget Community Repository (microsoft/winget-pkgs) - Consumer / End User - HTTPS @@ -728,6 +728,14 @@ Threats | **STRIDE:** T S | **Status:** Mitigate - C-021 + * - DFT-06 + - Command injection via unsanitised subprocess input + - A-01: GitHub Repository (main / protected) + - | **Sev:** 🟠H + | **Risk:** 🟠H + | **STRIDE:** T E + | **Status:** Mitigate + - C-015 (CodeQL) and C-017 (bandit) perform static analysis that detects command injection patterns before code reaches production. * - DFT-07 - CI/CD secret exfiltration via supply-chain attack on build environment - A-08: Python Build (wheel / sdist) @@ -856,9 +864,17 @@ Threats | **STRIDE:** T | **Status:** Mitigate - C-038 + * - DFT-34 + - Long-lived stored credential enables persistent unauthorised publication after exfiltration + - A-29: WINGET_TOKEN PAT + - | **Sev:** 🟠H + | **Risk:** 🟠H + | **STRIDE:** I T + | **Status:** Mitigate + - C-042 * - DFT-35 - Compromised publish credential enables malicious installer URL injection via package manifest submission - - A-09: Winget Community Repository (microsoft/winget-pkgs) + - A-28: Winget Community Repository (microsoft/winget-pkgs) - | **Sev:** 🟠H | **Risk:** 🟠H | **STRIDE:** T S diff --git a/doc/explanation/threat_model_usage.rst b/doc/explanation/threat_model_usage.rst index 5163cf32..70801094 100644 --- a/doc/explanation/threat_model_usage.rst +++ b/doc/explanation/threat_model_usage.rst @@ -679,7 +679,7 @@ Asset Identification - Datastore - Critical / Critical / High * - A-15: SBOM Output (CycloneDX) - - CycloneDX JSON/XML produced by ``dfetch report -t sbom``. Enumerates vendored components with PURL, license, and hash. Falsification hides actual dependencies from downstream CVE scanners. NOTE: this SBOM covers vendored deps only - dfetch itself has a separate machine-readable SBOM published on PyPI (see A-04 in tm_supply_chain.py). + - CycloneDX JSON/XML produced by ``dfetch report -t sbom``. Enumerates vendored components with PURL, license, and hash. Falsification hides actual dependencies from downstream CVE scanners. NOTE: this SBOM covers vendored deps only - dfetch itself has a separate machine-readable SBOM published on PyPI (see A-03 in tm_supply_chain.py). - Datastore - High / High / — * - A-16: VCS Credentials @@ -952,6 +952,14 @@ Threats | **STRIDE:** T S | **Status:** Mitigate - C-005 mitigates archive deps when hash present. Git/SVN: no integrity mechanism; pinning to an immutable commit SHA is recommended but not enforced by dfetch. + * - DFT-06 + - Command injection via unsanitised subprocess input + - A-22: dfetch Process + - | **Sev:** 🟠H + | **Risk:** 🟠H + | **STRIDE:** T E + | **Status:** Mitigate + - C-006 suppresses interactive credential prompts for Git and SVN; C-007 invokes all external commands with ``shell=False`` and list-form arguments, eliminating the shell-injection vector for non-interactive VCS operations. * - DFT-07 - CI/CD secret exfiltration via supply-chain attack on build environment - A-25: Patch Application (patch-ng) @@ -1190,10 +1198,6 @@ Controls - Manifest input validation - DFT-04, DFT-08 - StrictYAML schema with ``SAFE_STR = Regex(r"^[^\x00-\x1F\x7F-\x9F]*$")`` rejects control characters in all string fields. ``dfetch/manifest/schema.py`` - * - C-045 - - Plaintext transport detection - - DFT-26 - - ``plaintext_warning()`` (``dfetch/manifest/project.py``) inspects the resolved remote URL immediately before each VCS command is issued (inside the ``check_for_update`` and ``update`` spinners in ``subproject.py``). If the scheme is ``http://``, ``git://``, or ``svn://``, a visible warning is emitted naming the redacted URL (credentials stripped from the userinfo component) and recommending ``https://`` or ``svn+ssh://``. Detection only — dfetch still proceeds with the plaintext connection; the control raises user awareness but does not enforce scheme selection. ``dfetch/manifest/project.py, dfetch/project/subproject.py`` * - C-034 - Hash algorithm allowlist (SHA-256/384/512 only) - DFT-30 @@ -1202,3 +1206,7 @@ Controls - Persisted-metadata credential redaction - DFT-13 - ``Metadata.dump()`` rebuilds the netloc of every persisted URL from ``parsed.hostname`` and ``parsed.port`` via ``urllib.parse.urlsplit`` / ``urlunsplit``, dropping any ``user:password@`` userinfo before writing ``.dfetch_data.yaml``. The same stripper is applied to each ``dependencies[].remote_url`` entry (git submodule, svn:external) so a credential in a nested upstream URL also never reaches disk. The in-memory ``Metadata`` object held by the running command keeps the original URL — only the on-disk representation is redacted, so an in-flight authenticated fetch is unaffected. ``dfetch/project/metadata.py`` + * - C-045 + - Plaintext transport detection + - DFT-26 + - ``plaintext_warning()`` (``dfetch/manifest/project.py``) inspects the resolved remote URL immediately before each VCS command is issued (inside the ``check_for_update`` and ``update`` spinners in ``subproject.py``). If the scheme is ``http://``, ``git://``, or ``svn://``, a visible warning is emitted naming the redacted URL (credentials stripped from the userinfo component) and recommending ``https://`` or ``svn+ssh://``. Detection only — dfetch still proceeds with the plaintext connection; the control raises user awareness but does not enforce scheme selection. ``dfetch/manifest/project.py, dfetch/project/subproject.py`` diff --git a/security/README.md b/security/README.md index 926b8df8..1f934e1e 100644 --- a/security/README.md +++ b/security/README.md @@ -37,7 +37,7 @@ python -m security.compliance \ This produces: -- `security/dfetch.component-definition.json` — OSCAL 1.1.2 Component Definition implementing +- `security/dfetch.component-definition.json` — OSCAL 1.2.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) diff --git a/security/dfetch.component-definition.json b/security/dfetch.component-definition.json index 9dd467a6..d3a7ac58 100644 --- a/security/dfetch.component-definition.json +++ b/security/dfetch.component-definition.json @@ -3,8 +3,8 @@ "uuid": "eddc2e8c-a2ae-5a42-8d35-88f5c2af55b2", "metadata": { "title": "dfetch CRA Compliance Component Definition", - "last-modified": "2026-06-17T00:00:00Z", - "version": "0.15.0", + "last-modified": "2026-07-02T00:00:00Z", + "version": "0.14.0", "oscal-version": "1.2.2", "document-ids": [ { @@ -82,7 +82,7 @@ "props": [ { "name": "software-version", - "value": "0.15.0" + "value": "0.14.0" }, { "name": "asset-type", @@ -1367,4 +1367,4 @@ ] } } -} +} \ No newline at end of file diff --git a/security/tm_controls_data.py b/security/tm_controls_data.py index 737ee413..8015987e 100644 --- a/security/tm_controls_data.py +++ b/security/tm_controls_data.py @@ -301,7 +301,7 @@ def to_pytm(self) -> str: Control( id="C-041", name="Winget manifest PRs reviewed by community maintainers", - assets=["A-09"], + assets=["A-28"], threats=["DFT-35"], reference=".github/workflows/winget-publish.yml", description=( @@ -319,7 +319,7 @@ def to_pytm(self) -> str: Control( id="C-042", name="WINGET_TOKEN scoped to dedicated Winget environment", - assets=["A-10"], + assets=["A-29"], threats=["DFT-34"], reference=".github/workflows/winget-publish.yml", description=( diff --git a/security/tm_render.py b/security/tm_render.py index f7bf689b..d98a8a9b 100644 --- a/security/tm_render.py +++ b/security/tm_render.py @@ -8,6 +8,7 @@ import re import sys from collections.abc import Callable +from dataclasses import dataclass from typing import Any from pytm import TM, Data, Element @@ -120,6 +121,46 @@ def _render_asset_rows( return "".join(_asset_row(e, cia) for e in assets) +@dataclass +class _CatalogRow: + """Stand-in finding for a threat documented via ``ThreatResponse`` only. + + Two situations leave a responded-to threat without any pytm finding: + the mitigation is already modelled in the element properties, so the + threat's condition no longer matches (e.g. DFT-06 with parameterized + subprocess input), or the threat targets ``Data`` assets, which pytm's + ``resolve()`` never evaluates (it iterates ``TM._elements`` only, while + ``Data`` lives in ``TM._data`` — e.g. DFT-34). Rendering these rows + from the threat catalog keeps the decision visible in the report instead + of silently dropping it. + """ + + threat_id: str + description: str + severity: str + target: str = "—" + + +def _validate_threat_coverage( + findings: Any, + responses: list[ThreatResponse], + catalog: dict[str, Any], +) -> None: + """Fail generation when findings and responses have drifted apart. + + Every pytm finding must have a documented ``ThreatResponse``, and every + response must reference a threat that exists in the catalog. Raising here + means an undocumented threat can never silently render as an empty row. + """ + resp_ids = {r.threat_id for r in responses} + unknown = sorted(resp_ids - set(catalog)) + if unknown: + raise ValueError(f"Responses reference unknown threat IDs: {unknown}") + unhandled = sorted({f.threat_id for f in findings} - resp_ids) + if unhandled: + raise ValueError(f"Findings without a ThreatResponse: {unhandled}") + + def _render_threat_rows( tm: Any, controls: list[Control], responses: list[ThreatResponse] ) -> str: @@ -129,16 +170,31 @@ def _render_threat_rows( threat that applies to many elements produces many byte-identical rows. Collapse them to a single row documented against the threat's canonical target (``ThreatResponse.target`` when set, otherwise the matched element). + + Responses whose threat produced no pytm finding are rendered from the + threat catalog instead (see ``_CatalogRow``), so every documented risk + decision appears in the table. """ findings = tm.findings - if not findings: + if not findings and not responses: return "" + catalog: dict[str, Any] = {t.id: t for t in getattr(TM, "_threats", [])} + _validate_threat_coverage(findings, responses, catalog) + resp_map: dict[str, ThreatResponse] = {r.threat_id: r for r in responses} representative: dict[str, Any] = {} for f in findings: representative.setdefault(f.threat_id, f) + for r in responses: + if r.threat_id not in representative: + threat = catalog[r.threat_id] + representative[r.threat_id] = _CatalogRow( + threat_id=r.threat_id, + description=str(threat.description), + severity=str(threat.severity), + ) def _row(f: Any) -> str: resp = resp_map.get(f.threat_id) diff --git a/security/tm_supply_chain.py b/security/tm_supply_chain.py index 4c6afa5b..7be32c44 100644 --- a/security/tm_supply_chain.py +++ b/security/tm_supply_chain.py @@ -201,8 +201,8 @@ def _make_sc_processes(b_github: Boundary) -> tuple[Process, Process, Process]: "harden-runner used in every workflow that executes steps on a runner " "(egress: block with endpoint allowlist); ci.yml is a dispatcher-only workflow " "with no runner steps and does not include harden-runner. " - "winget-publish.yml uses a stored PAT (WINGET_TOKEN, A-10) to submit manifest " - "PRs to the Winget Community Repository (A-09)." + "winget-publish.yml uses a stored PAT (WINGET_TOKEN, A-29) to submit manifest " + "PRs to the Winget Community Repository (A-28)." ) gh_actions_workflow.controls.isHardened = ( True # SHA-pinned actions, harden-runner egress:block on all runner workflows @@ -232,7 +232,7 @@ def _make_sc_processes(b_github: Boundary) -> tuple[Process, Process, Process]: def _make_sc_datastores(b_github: Boundary) -> tuple[Datastore, Datastore]: """Create data assets; return all for use in dataflows.""" Data( - "A-10: WINGET_TOKEN PAT", + "A-29: WINGET_TOKEN PAT", description=( "Long-lived classic GitHub Personal Access Token (fine-grained PATs are not " "supported by the ``vedantmgoyal9/winget-releaser`` action) with ``public_repo`` " @@ -568,14 +568,14 @@ def _make_sc_winget_elements_and_flows( "(https://github.com/microsoft/winget-pkgs) where dfetch's Winget manifest " "is hosted. " "Manifest PRs are submitted automatically by the CI release pipeline " - "(winget-publish.yml) using the stored WINGET_TOKEN PAT (A-10). " + "(winget-publish.yml) using the stored WINGET_TOKEN PAT (A-29). " "Consumer installations resolve manifests from this repository; winget " "downloads the MSI installer from the URL declared in the manifest " "(pointing to GitHub Releases, A-01) and verifies its SHA256 hash." ) winget_repo = ExternalEntity( - "A-09: Winget Community Repository (microsoft/winget-pkgs)" + "A-28: Winget Community Repository (microsoft/winget-pkgs)" ) winget_repo.inBoundary = b_winget winget_repo.classification = Classification.SENSITIVE @@ -584,7 +584,7 @@ def _make_sc_winget_elements_and_flows( "``DFetch-org.DFetch`` manifest is hosted " "(https://github.com/microsoft/winget-pkgs). " "CI submits manifest update PRs via ``vedantmgoyal9/winget-releaser`` using " - "a stored PAT (A-10); PRs are reviewed by ``microsoft/winget-pkgs`` " + "a stored PAT (A-29); PRs are reviewed by ``microsoft/winget-pkgs`` " "maintainers before merging (C-041). " "Manifests contain SHA256 hashes of the installer binary; winget verifies " "the hash before installation. " @@ -610,8 +610,8 @@ def _make_sc_winget_elements_and_flows( df27.description = ( "On release event, ``winget-publish.yml`` uses ``vedantmgoyal9/winget-releaser`` " "to discover the MSI asset in the GitHub release, compute its SHA256 hash, " - "generate updated Winget manifests, and submit a PR to A-09 " - "(``microsoft/winget-pkgs``) authenticated via the WINGET_TOKEN PAT (A-10). " + "generate updated Winget manifests, and submit a PR to A-28 " + "(``microsoft/winget-pkgs``) authenticated via the WINGET_TOKEN PAT (A-29). " "The PR is reviewed by ``microsoft/winget-pkgs`` maintainers before merging " "(C-041). " "Residual risk: a compromised PAT (DFT-34) or a fraudulent submission that " @@ -629,7 +629,7 @@ def _make_sc_winget_elements_and_flows( df28 = Dataflow(consumer, winget_repo, "DF-28: winget install dfetch") df28.description = ( "Consumer runs ``winget install -e --id DFetch-org.DFetch``. " - "The winget client resolves the manifest from A-09 (``microsoft/winget-pkgs``). " + "The winget client resolves the manifest from A-28 (``microsoft/winget-pkgs``). " "The manifest contains the installer URL (pointing to GitHub Releases, A-01) " "and the SHA256 hash of the MSI. " "The consumer can verify the MSI attestations using ``gh attestation verify`` " @@ -642,7 +642,7 @@ def _make_sc_winget_elements_and_flows( df29 = Dataflow(winget_repo, consumer, "DF-29: Consumer downloads MSI via winget") df29.description = ( - "winget resolves the installer URL from the manifest in A-09 and downloads " + "winget resolves the installer URL from the manifest in A-28 and downloads " "the MSI directly from GitHub Releases (A-01). " "The SHA256 hash declared in the manifest is verified against the downloaded " "binary before installation begins. " @@ -723,8 +723,8 @@ def build_model() -> TM: "A-07", "A-08", "A-08b", - "A-09", - "A-10", + "A-28", + "A-29", } ASSET_CONTROLS: dict[str, list[Control]] = build_asset_controls_index( CONTROLS, _SUPPLY_CHAIN_ASSET_IDS @@ -761,6 +761,7 @@ def build_model() -> TM: "C-015 (CodeQL) and C-017 (bandit) perform static analysis that detects " "command injection patterns before code reaches production." ), + target="A-01: GitHub Repository (main / protected)", ), ThreatResponse( "DFT-07", @@ -921,14 +922,14 @@ def build_model() -> TM: "mitigate", risk="High", stride=["Information Disclosure", "Tampering"], - target="A-10: WINGET_TOKEN PAT", + target="A-29: WINGET_TOKEN PAT", ), ThreatResponse( "DFT-35", "mitigate", risk="High", stride=["Tampering", "Spoofing"], - target="A-09: Winget Community Repository (microsoft/winget-pkgs)", + target="A-28: Winget Community Repository (microsoft/winget-pkgs)", ), ] diff --git a/security/tm_usage.py b/security/tm_usage.py index 4bf0acc6..20e94564 100644 --- a/security/tm_usage.py +++ b/security/tm_usage.py @@ -257,7 +257,7 @@ def _make_usage_datastores_a( "Enumerates vendored components with PURL, license, and hash. " "Falsification hides actual dependencies from downstream CVE scanners. " "NOTE: this SBOM covers vendored deps only - dfetch itself has a separate " - "machine-readable SBOM published on PyPI (see A-04 in tm_supply_chain.py)." + "machine-readable SBOM published on PyPI (see A-03 in tm_supply_chain.py)." ) sbom_output.storesSensitiveData = False sbom_output.hasWriteAccess = True @@ -930,6 +930,7 @@ def build_model() -> TM: "C-007 invokes all external commands with ``shell=False`` and list-form arguments, " "eliminating the shell-injection vector for non-interactive VCS operations." ), + target="A-22: dfetch Process", ), ThreatResponse( "DFT-07",