dfetch Supply Chain

Risk Context

This report follows the risk-based approach of BSI TR-03183-1 Chapter 5.

Threat model for dfetch. Covers the pre-install lifecycle: code contribution, CI/CD, build (wheel / sdist), PyPI distribution, and consumer installation. The installed dfetch package is the handoff point to tm_usage.py.

Assumptions

Name

Description

Trusted workstation

Developer workstations are trusted at development and commit time. A compromised workstation is outside the scope of this model.

CI runner posture

GitHub Actions environments inherit the security posture of the GitHub-hosted runner. Ephemeral runner isolation is provided by GitHub.

Actors

Name

Description

Developer / Contributor

Anyone who writes code for dfetch: core maintainers who push directly and cut releases, and external contributors who submit pull requests. Maintainers are trusted at workstation time and are responsible for correct branch-protection and release workflow configuration. External contributors are untrusted until their PR passes code review and CI.

Consumer / End User

Installs dfetch from PyPI (pip install dfetch) or from binary installer, then invokes it on a developer workstation or in a CI pipeline. Can verify five complementary attestation types using gh attestation verify as documented in the release-integrity guide (see C-026, C-037, C-039, C-040): SBOM attestation on the PyPI wheel; SBOM, SLSA build provenance, and VSA on binary installers; SLSA build provenance, in-toto test result attestation, and SLSA Source Provenance Attestation on the source archive and main-branch commits.

Boundaries

Name

Description

Local Developer Environment

Developer workstation or local CI runner. Assumed trusted at invocation time. Hosts the manifest (dfetch.yaml), vendor directory, dependency metadata (.dfetch_data.yaml), and patch files.

Consumer Environment

End-user workstation or downstream CI pipeline where dfetch is installed and invoked. Distinct from the developer environment: no source checkout, no signing keys, no deploy access. The consumer is trusted at invocation time but has no special relationship with the dfetch release infrastructure.

GitHub Platform

GitHub-hosted infrastructure: repository, CI/CD runners, Actions workflows, build cache, and code-scanning results. Egress traffic on runners is blocked (harden-runner with egress-policy: block) with an allowlist of permitted endpoints; ci.yml forwards only explicitly named secrets to child workflows (CODACY_PROJECT_TOKEN to test.yml, GH_DFETCH_ORG_DEPLOY to docs.yml).

PyPI / TestPyPI

Python Package Index and its staging registry. dfetch publishes via OIDC trusted publishing - no long-lived API token stored.

Data Flow Diagram

../_images/0a0ce7d4e6cf9c3b3ec24b9e5c722c4d0e2ae76d121ea0334aceb4f08a66b191.png

Sequence Diagram

../_images/729ce2682119a01ea1166a4022fdcbf978e8733638f7269e2b4281a1b7e1d235.png

Asset Identification

Name

Description

Type

C / I / A

A-01: GitHub Repository (main / protected)

The protected main branch: force-push disabled, merges require passing CI and at least one approving review. Contains the authoritative workflow definitions (.github/workflows/), release tags, and published release assets. Workflow files on main are what GitHub Actions actually executes — a PR cannot override them for its own CI run. contents:write permits CI to upload release assets to GitHub Releases. SARIF code-scanning write-back (DF-19) is performed separately under security-events: write.

ExternalEntity

High / High / High

A-01b: GitHub Repository (feature branches / PRs)

Unprotected feature branches and fork PRs: no mandatory review, no CI-gate requirement to push. Any authenticated GitHub user can open a PR modifying .github/workflows/ files; those changes are reviewed before merging to main but cannot execute during CI — GitHub enforces that PR CI runs use the workflow files from the protected main branch, not from the PR branch (see DF-12). Any pre-merge CI that does execute runs with restricted permissions and no access to production secrets, further mitigated by ci.yml secret scoping (C-024) and harden-runner egress block (C-013).

ExternalEntity

— / — / —

A-02: GitHub Actions Infrastructure

Microsoft-operated ephemeral runner executing CI/CD workflows. Egress policy is block with an explicit allowlist of permitted endpoints - non-allowlisted outbound connections are blocked at the kernel level by step-security/harden-runner.

ExternalEntity

High / High / High

A-03: PyPI / TestPyPI

Python Package Index - both the registry service and the published dfetch wheel/sdist (https://pypi.org/project/dfetch/). Published via OIDC trusted publishing; no long-lived API token stored. A machine-readable CycloneDX SBOM is generated during the build and published alongside the release. Account takeover, registry compromise, or namespace-squatting would affect every consumer installing dfetch.

ExternalEntity

High / High / High

A-04: Release Gate / Code Review

Branch-protection rules and mandatory peer code review enforced before merging to the default branch. Controls privileged operations: PR merge, direct push to main, and release-workflow trigger. A compromised maintainer account with merge rights bypasses peer review and can trigger a malicious release without any automated block. No hardware-token MFA or mandatory second-maintainer approval for release operations is currently enforced.

Process

High / High / High

A-05: PyPI OIDC Identity

GitHub OIDC token exchanged for a short-lived PyPI publish credential. No long-lived API token stored. The token is scoped to the GitHub Actions environment named pypi. Risk: if the OIDC issuer or the PyPI trusted-publisher mapping is misconfigured, an attacker could mint a valid publish token.

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. 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.

Process

Medium / Medium / Medium

A-07: dfetch Build / Dev Dependencies

Python packages installed during CI: setuptools, build, pylint, bandit, mypy, pytest, etc. Ruby gem fpm for platform builds. Installed via pip install . and pip install --upgrade pip build without --require-hashes - a compromised PyPI mirror or BGP hijack can substitute malicious build tools. gem install fpm and choco install svn/zig are also not hash-verified.

Datastore

High / High / —

A-08: Python Build (wheel / sdist)

Runs python -m build to produce wheel and sdist. Build deps (setuptools, build, fpm, gem) fetched from PyPI/RubyGems without hash pinning. SLSA provenance attestations are generated by the release workflow.

Process

High / High / High

A-08b: GitHub Actions Build Cache

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 / —

Dataflows

Name

From

To

Protocol

DF-11: Push commits / open PR

Developer / Contributor

A-01b: GitHub Repository (feature branches / PRs)

HTTPS

DF-22: PR enters code review

A-01b: GitHub Repository (feature branches / PRs)

A-04: Release Gate / Code Review

DF-12: Main branch workflows drive CI execution

A-01: GitHub Repository (main / protected)

A-06: GitHub Actions Workflow

DF-13a: PR CI checkout

A-01b: GitHub Repository (feature branches / PRs)

A-02: GitHub Actions Infrastructure

DF-13b: Release CI checkout

A-01: GitHub Repository (main / protected)

A-02: GitHub Actions Infrastructure

DF-14: CI cache restore

A-08b: GitHub Actions Build Cache

A-02: GitHub Actions Infrastructure

HTTPS

DF-15: Workflow triggers build step

A-06: GitHub Actions Workflow

A-08: Python Build (wheel / sdist)

DF-15b: Built wheel/sdist artifacts

A-08: Python Build (wheel / sdist)

A-02: GitHub Actions Infrastructure

DF-16: CI fetches build/dev deps from PyPI

A-03: PyPI / TestPyPI

A-07: dfetch Build / Dev Dependencies

HTTPS

DF-17: Build tools consumed by build step

A-07: dfetch Build / Dev Dependencies

A-08: Python Build (wheel / sdist)

DF-18: CI cache write

A-02: GitHub Actions Infrastructure

A-08b: GitHub Actions Build Cache

HTTPS

DF-19: CI write-back (SARIF / artifacts)

A-02: GitHub Actions Infrastructure

A-01: GitHub Repository (main / protected)

HTTPS

DF-23: Approved merge to main

A-04: Release Gate / Code Review

A-01: GitHub Repository (main / protected)

DF-24: Publish wheel to PyPI (OIDC)

A-02: GitHub Actions Infrastructure

A-03: PyPI / TestPyPI

HTTPS

DF-25: pip install dfetch

Consumer / End User

A-03: PyPI / TestPyPI

HTTPS

DF-26: Consumer downloads dfetch from PyPI

A-03: PyPI / TestPyPI

Consumer / End User

HTTPS

Threats

ID

Description

Target

Analysis

Controls / Notes

DFT-02

Supply-chain content substitution via server-side compromise

A-03: PyPI / TestPyPI

Sev: 🟠H
Risk: 🟠H
STRIDE: T S
Status: Mitigate

C-022

DFT-03

Path traversal in archive or patch extraction

A-08: Python Build (wheel / sdist)

Sev: 🔴VH
Risk: 🟡M
STRIDE: T E
Status: Mitigate

C-015, C-017

DFT-05

Mutable VCS reference enables silent content substitution

DF-16: CI fetches build/dev deps from PyPI

Sev: 🟡M
Risk: 🟡M
STRIDE: T S
Status: Mitigate

C-021

DFT-07

CI/CD secret exfiltration via supply-chain attack on build environment

A-08: Python Build (wheel / sdist)

Sev: 🟠H
Risk: 🟠H
STRIDE: S T I E
Status: Mitigate

C-009, C-010, C-011, C-012, C-013, C-024

DFT-08

Tampered secondary artifact suppresses or bypasses security checks

A-08b: GitHub Actions Build Cache

Sev: 🟡M
Risk: 🟠H
STRIDE: T
Status: Accept

Secondary artifacts are not independently verified in the supply-chain pipeline. Accepted based on the CI runner posture assumption: GitHub Actions environments run on ephemeral, GitHub-hosted runners whose isolation is provided by GitHub, so secondary artifacts written and consumed within a single job are considered adequately protected by that isolation.

DFT-09

Archive decompression bomb causes resource exhaustion

A-08: Python Build (wheel / sdist)

Sev: 🟡M
Risk: 🟡M
STRIDE: D
Status: Accept

Decompression-bomb protection is a runtime concern (C-002 in usage model), not a supply-chain pipeline control. Accepted based on the CI runner posture assumption: GitHub Actions environments run on ephemeral, isolated runners; any denial-of-service impact from a decompression bomb is bounded to the affected job and does not persist beyond it.

DFT-10

Build or development dependency substitution via compromised registry

A-07: dfetch Build / Dev Dependencies

Sev: 🟠H
Risk: 🟠H
STRIDE: T
Status: Mitigate

C-016 checks known-vulnerable deps on PRs; build deps lack --require-hashes pinning.

DFT-11

Privileged account compromise enables unauthorised merge or release

A-04: Release Gate / Code Review

Sev: 🟠H
Risk: 🟠H
STRIDE: S E
Status: Accept

No hardware-token MFA or mandatory second-approver enforced for accounts with merge or release-trigger rights. Accepted based on the Trusted workstation assumption: developer workstations and accounts are trusted at development and commit time; a compromised maintainer account is outside the scope of this model.

DFT-17

Typosquatting or unverified source identity on an unauthenticated channel

A-03: PyPI / TestPyPI

Sev: 🟠H
Risk: 🟡M
STRIDE: S
Status: Mitigate

C-026

DFT-18

Dependency confusion - public registry package shadows private internal package

A-07: dfetch Build / Dev Dependencies

Sev: 🟠H
Risk: 🟠H
STRIDE: T S
Status: Accept

Namespace-squatting on PyPI is a registry-level concern outside dfetch’s control. Accepted based on the CI runner posture assumption: GitHub Actions environments inherit the security posture of the GitHub-hosted runner, including its access to the public PyPI registry; registry-level namespace integrity is outside the scope of this model.

DFT-20

Abandoned package namespace reclaimed by malicious actor

A-07: dfetch Build / Dev Dependencies

Sev: 🟠H
Risk: 🟡M
STRIDE: S T
Status: Accept

Abandoned namespace reclaim is a registry-level concern outside dfetch’s control. Accepted based on the CI runner posture assumption: GitHub Actions environments inherit the security posture of the GitHub-hosted runner, including its access to the public PyPI registry; registry-level namespace integrity is outside the scope of this model.

DFT-23

Replay or freeze attack delivers stale content to suppress security updates

DF-16: CI fetches build/dev deps from PyPI

Sev: 🟡M
Risk: 🟡M
STRIDE: T
Status: Accept

No version-pinning or update-freshness check in the supply-chain pipeline. Accepted based on the CI runner posture assumption: GitHub Actions environments inherit the security posture of the GitHub-hosted runner; freshness enforcement for registry dependencies is a registry-level concern outside the scope of this model.

DFT-24

Local dependency cache or metadata store poisoned to suppress integrity alerts

A-08b: GitHub Actions Build Cache

Sev: 🟠H
Risk: 🟠H
STRIDE: T
Status: Accept

Build-artifact cache is ref-scoped (C-033); pipeline metadata store is not independently integrity-protected. Accepted based on the CI runner posture assumption: GitHub Actions environments run on ephemeral, GitHub-hosted runners; the ref-scoped cache key isolation provided by that environment is considered sufficient for the pipeline metadata.

DFT-25

Forged or unverifiable provenance attestation conceals malicious build output

A-08b: GitHub Actions Build Cache

Sev: 🟠H
Risk: 🟠H
STRIDE: S T R
Status: Mitigate

C-026, C-039

DFT-27

Build or release triggered from unofficial source fork or unprotected branch

A-04: Release Gate / Code Review

Sev: 🟠H
Risk: 🟠H
STRIDE: T S
Status: Mitigate

C-032

DFT-28

CI/CD build cache poisoned to silently substitute a malicious compiled artifact

A-08b: GitHub Actions Build Cache

Sev: 🟠H
Risk: 🟠H
STRIDE: T
Status: Mitigate

C-033

DFT-29

Signing key or short-lived publish credential exfiltrated from build environment

A-08: Python Build (wheel / sdist)

Sev: 🔴VH
Risk: 🟠H
STRIDE: I T
Status: Mitigate

C-013

DFT-31

Upstream source publishes no SLSA Source provenance attestation — consumer cannot verify upstream security controls

DF-26: Consumer downloads dfetch from PyPI

Sev: 🟡M
Risk: 🟢L
STRIDE: R S
Status: Mitigate

C-037, C-039, C-040

DFT-33

Upstream default-branch history rewritten — ancestry broken, pinned SHA orphaned or made unreachable

DF-26: Consumer downloads dfetch from PyPI

Sev: 🟡M
Risk: 🟢L
STRIDE: T
Status: Mitigate

C-038

Controls

ID

Name

Threats

Description

C-009

Actions commit-SHA pinning

DFT-07

Every third-party GitHub Action is pinned to a full commit SHA, preventing tag-mutable supply-chain substitution. .github/workflows/*.yml

C-010

OIDC trusted publishing

DFT-07

PyPI publishes via pypa/gh-action-pypi-publish with id-token: write and no stored long-lived API token. .github/workflows/python-publish.yml

C-011

Minimal workflow permissions

DFT-07

Each workflow declares only the permissions it requires (default contents: read). .github/workflows/*.yml

C-012

persist-credentials: false

DFT-07

persist-credentials: false is set on almost all checkout steps across all workflows that run on a runner. Two exceptions are intentional: release.yml (needs write access to push release assets) and the attest-source-governance job in source-provenance.yml (the slsa_with_provenance action requires repository write access). All other checkout steps suppress credential persistence. .github/workflows/*.yml

C-013

Harden-runner (egress block)

DFT-07, DFT-29

step-security/harden-runner is used in every workflow with egress-policy: block and an allowlist of permitted endpoints. All non-allowlisted outbound connections are blocked. .github/workflows/*.yml

C-015

CodeQL static analysis

DFT-03, DFT-06

CodeQL scans the Python codebase for security vulnerabilities on pushes and pull requests targeting main, and on a weekly cron schedule. .github/workflows/codeql-analysis.yml

C-016

Dependency review

DFT-10

actions/dependency-review-action checks for known vulnerabilities in newly added dependencies on every pull request. .github/workflows/dependency-review.yml

C-017

bandit security linter

DFT-03, DFT-06

bandit -r dfetch runs in CI to detect common Python security issues. pyproject.toml

C-021

Sigstore SBOM attestation

DFT-05

The release pipeline generates CycloneDX SBOM attestations via actions/attest with Sigstore signatures. These attest the software composition (SBOM) of the published packages using predicate type https://cyclonedx.org/bom.

C-022

CycloneDX SBOM on PyPI

DFT-02

A CycloneDX SBOM is generated during the build and published alongside the PyPI release, satisfying CRA Article 13 requirements.

C-024

secrets: inherit scope

DFT-07

ci.yml only passes required repository secrets to the test and docs workflows, preventing malicious PR steps from exfiltrating unrelated secrets.

C-026

Consumer-side package provenance verification

DFT-17, DFT-25

Consumers installing dfetch via pip install dfetch have access to documented procedures to verify the SBOM (CycloneDX) attestation of the PyPI wheel using the GitHub CLI (gh attestation verify). Consumers installing binary packages (deb, rpm, pkg, msi) can verify SLSA build provenance, SBOM, and VSA attestations. Consumers working from source can verify SLSA build provenance and in-toto test result attestations on the source archive. Platform-specific instructions for Linux, macOS, and Windows are provided in doc/howto/verify-integrity.rst. This is an interim mitigation pending PEP 740 built-in support in pip. Without this documentation, a compromised PyPI account, namespace-squatting, or dependency-confusion could serve malicious code undetected (DFT-17). An attacker controlling the release pipeline could publish a plausible attestation alongside a backdoored wheel (DFT-25). By providing clear, copy-paste instructions, we enable security-conscious consumers to verify provenance before installation. doc/howto/verify-integrity.rst

C-032

Consumer attestation verification pins to release tag ref

DFT-27

All gh attestation verify commands in the installation guide use --cert-identity pinned to the release workflow at a specific version tag (e.g. ...python-publish.yml@refs/tags/v<version> for pip packages, ...build.yml@refs/tags/v<version> for binary installers) combined with --cert-oidc-issuer https://token.actions.githubusercontent.com. This rejects attestations produced by any workflow or branch other than the expected release workflow on an official version tag. A build from an unofficial fork or unprotected branch would produce an attestation with a different cert-identity and fail verification. doc/howto/verify-integrity.rst

C-033

Ref-scoped build cache keys isolate PR and release builds

DFT-28

ccache and clcache keys in build.yml include ${{ github.ref_name }} so cache entries written by a pull-request build are scoped to the PR’s branch name and cannot be restored by a release-tag build. A malicious fork PR step cannot pre-populate a cache slot that the release workflow will restore, because the release tag name is not reachable from the PR’s branch ref. .github/workflows/build.yml

C-037

SLSA Source Provenance Attestation of repository governance controls

DFT-31

Source Provenance Attestations are published via slsa-framework/source-actions/slsa_with_provenance on every push to main. These attestations prove the specific source-level governance controls applied on each commit: branch protection, mandatory code review, and ancestry enforcement (C-038). Predicate type https://slsa.dev/source_provenance/v1 is signed by GitHub Actions via Sigstore and stored in the GitHub Attestation registry. Consumers can verify using gh attestation verify with --predicate-type https://slsa.dev/source_provenance/v1 and --cert-identity pinned to source-provenance.yml@refs/heads/main. .github/workflows/source-provenance.yml

C-038

Ancestry enforcement on dfetch main branch

DFT-33

GitHub branch-protection rules on the dfetch main branch prohibit force-pushes, satisfying the SLSA Source Level 2 ancestry-enforcement requirement. The immutable revision lineage of the main branch is preserved: no contributor can rewrite history and orphan a previously-audited commit SHA. Consumers who pin to a dfetch commit SHA can rely on that SHA remaining reachable indefinitely. .github/workflows/

C-039

Source build provenance and VSA attestations

DFT-31, DFT-25

Every dfetch release ships two complementary Sigstore-signed attestations that together let consumers trace the full source → binary chain. SLSA build provenance (source-provenance.yml) on the source archive proves the archive was produced from the official tagged commit by the official CI workflow, recording the exact inputs used at build time. A Verification Summary Attestation (VSA, build.yml) on binary installers records that the source archive was itself attested and verified before the binary was produced, linking source-level trust to the installed package. Both are signed by GitHub Actions via Sigstore and can be verified using gh attestation verify with --predicate-type https://slsa.dev/provenance/v1 or --predicate-type https://slsa.dev/verification_summary/v1 respectively. This substantially mitigates DFT-31 (consumers now have attestations to verify against) and DFT-25 (forged provenance would fail Sigstore verification). The formal SLSA Source Level attestation of governance controls is addressed by C-037. doc/howto/verify-integrity.rst

C-040

Test result attestation on source archive

DFT-31

The CI test workflow (test.yml) generates an in-toto test result attestation (predicate type https://in-toto.io/attestation/test-result/v0.1) for every release and main-branch commit. The attestation proves the full CI test suite ran against the exact source archive and every check passed, before any binary was produced from that source. Consumers can verify it using gh attestation verify dfetch-source.tar.gz with --predicate-type https://in-toto.io/attestation/test-result/v0.1 and --cert-identity pinned to test.yml at the release tag ref. This provides an additional layer of assurance beyond build provenance: not only was the artifact produced from the official commit, but the test suite demonstrably passed on that exact source before any binary was built. .github/workflows/test.yml