Skip to content

fix(ci): the release-bump guard was missing the system deps its check needs - #1259

Merged
stormer78 merged 1 commit into
mainfrom
fix/release-guard-system-deps
Sep 6, 2026
Merged

fix(ci): the release-bump guard was missing the system deps its check needs#1259
stormer78 merged 1 commit into
mainfrom
fix/release-guard-system-deps

Conversation

@stormer78

Copy link
Copy Markdown
Contributor

The guard added in #1256 shipped without the Install system dependencies
step the report job beside it has. Its first real run — against Release PR
#1232, the exact under-bump it was built to catch — failed on this instead:

error: failed to run custom build command for `libdbus-sys v0.2.7`
  pkg_config failed
error: failed to build rustdoc for crate vta-cli-common v0.12.4

vta-cli-common pulls libdbus-sys, whose build script shells out to
pkg-config. Without the dbus headers rustdoc cannot build, and
semver-report.sh reads a failed baseline build as "a PUBLISHED CRATE DOES
NOT BUILD"
— deliberately loud, because that is a genuine production defect
when it is real. A missing build dependency on the runner produces the
identical message.

So the guard was red for a reason unrelated to version bumps, wearing the
wording of a far more serious finding. That is the same conflation this job
family has now had three times (#1252 twice over, and this). It matters more
here than anywhere else: the guard's entire value is being believed when it
goes red, and its first appearance on a real Release PR was a false positive.

The fix

The apt logic moves to scripts/install-semver-deps.sh; both jobs call it.

A second copy of the block inside the guard job would have been the shorter
diff and the wrong one — two lists of the same thing agree right up until
someone edits one, which is exactly the defect #1252 exists to fix. Same
reasoning that put the exclusion list and coverage assertion in one script
rather than two.

Verified both jobs now resolve to identical step lists:

semver-checks:       [checkout, Install system dependencies, toolchain, install-action, Check published crates for API breaks]
release-bump-guard:  [checkout, Install system dependencies, toolchain, install-action, Check the proposed versions cover their API changes]

What this does not yet prove

The guard still has not had a clean run against a real under-bump. #1232
remains the test case: once this merges and release-plz refreshes that branch,
the guard should fail with THE PROPOSED RELEASE UNDER-BUMPS A BREAKING CHANGE
and exit 100, naming vti-common 0.16.2 and vta-sdk 0.32.4. Locally, against
a checkout of that same branch, it does exactly that — and passes at 0.17.0 /
0.33.0 — but that is my reconstruction, not the job.

… needs

The guard shipped in #1256 without the "Install system dependencies" step the
report job beside it has, so its first real run — against Release PR #1232, the
under-bump it was built to catch — failed on:

    error: failed to run custom build command for `libdbus-sys v0.2.7`
      pkg_config failed
    error: failed to build rustdoc for crate vta-cli-common v0.12.4

`vta-cli-common` pulls `libdbus-sys`, whose build script shells out to
pkg-config. Without the dbus headers rustdoc cannot build, and semver-report.sh
reads a failed baseline build as "a PUBLISHED CRATE DOES NOT BUILD" — loud on
purpose, because that is a real production defect when it is real. A missing
build dependency on the runner produces the identical message.

So the guard was red, for a reason that had nothing to do with version bumps,
wearing the wording of a much more serious finding. That is the same conflation
this job family has now had three times, and the reason to be exact about it:
the guard's whole purpose is to be believed when it goes red.

The apt logic moves to scripts/install-semver-deps.sh and both jobs call it. A
second copy in the second job would have been the shorter diff and the wrong
one — two lists of the same thing agree until someone edits one, which is
precisely the defect #1252 was written to fix.

Verified: both jobs now resolve to identical step lists.
Signed-off-by: Glenn Gore <glenn.g@affinidi.com>
@stormer78
stormer78 requested a review from a team as a code owner September 6, 2026 04:45
@affinidi-appsecurity-bot

Copy link
Copy Markdown

🛡️ AI Agentic Security Code Review

1 finding needs a human to review/validate.

Mandatory to check: 🔒 Security Code Review Report

Details

🛡️ Security Code Review Report — PR #1259

Field Value
Repository OpenVTC/verifiable-trust-infrastructure
Branch fix/release-guard-system-depsmain
Validated 2026-09-06
Scan ID e51696ad
Validator AI Security Validation Agent

🗺️ Scan Coverage

Modules scanned: 2 · with findings: 2 · files: 2 · findings: 3

Module Files scanned Findings
.github 1 1
scripts 1 2

Executive Summary

Category Confirmed Must-Review-By-Human
Security Issues 0 1

⚠️ 1 finding(s) need human review. These could not be conclusively confirmed or dismissed automatically (insufficient evidence). They are not dismissed — a developer / security team member must read and decide.


🔒 Security Issues

⚠️ Must-Review-By-Human (1)

Validated up to a point, but inconclusive — a human must read the code and make the final call. Reported (not dismissed) so developers and the security team receive them.

🟡 Github Actions Mutable Action Tag (33 occurrences)

Field Detail
Severity MEDIUM
Location .github/workflows/ci.yml:58
Finding ID github_pr-9994b01369fc
OWASP A08:2021 - Software and Data Integrity Failures
CVSS 4.0 5.5
Exploit Maturity conceptual
Detection Source mcp_semgrep

🧠 AI Triage:

  • Triaged severity: MEDIUM
  • No CVSS score is assigned (this is a hardening/best-practice finding, not a CVE). Exploit maturity is 'conceptual' and exploitation depends on a third-party supply-chain compromise event, not direct attacker action against this repo. It meets medium criteria: real but conditional impact, no confirmed exploit in the wild against this specific action set, requires specific external conditions (upstream tag repointing) to materialize. Production CI/CD context and secrets exposure keep it from being downgraded to low, but lack of concrete active-exploitation evidence keeps it out of high/critical.
  • Composite score: 4.9
  • Environment: production

Summary: GitHub Actions step uses a mutable tag or branch reference. Tags and branch names can be silently repointed by the action owner, enabling supply-chain attacks — as seen in the trivy-action and kics-gi — 33 occurrence(s): ci.yml:58, ci.yml:98, ci.yml:100, ci.yml:136, ci.yml:193, ci.yml:195, ci.yml:267, ci.yml:307, ci.yml:311, ci.yml:341, ci.yml:343, ci.yml:358, ci.yml:414, ci.yml:418, ci.yml:459, ci.yml:460, ci.yml:495, ci.yml:537, ci.yml:538, ci.yml:578 (+13 more)

📝 Description:

GitHub Actions step uses a mutable tag or branch reference. Tags and branch names can be silently repointed by the action owner, enabling supply-chain attacks — as seen in the trivy-action and kics-gi

🌱 Root Cause: Github Actions Mutable Action Tag

🔧 Remediation:

⚠️ AI-generated fix. Review, test in staging, and validate against your architecture before applying.

Priority: Short-term

Github Actions Mutable Action Tag: GitHub Actions step uses a mutable tag or branch reference. Tags and branch names can be silently repointed by the action owner, enabling supply-chain attacks — as seen in the trivy-action and kics-gi

🔍 Validation Log

  • Verdict: ⚠️ Must-Review-By-Human
  • Confidence: 85%
  • AI Validation Evidence: EVIDENCE FOUND: ci.yml uses actions pinned by tag/major-version reference such as uses: actions/checkout@v7, uses: dtolnay/rust-toolchain@stable, uses: actions/cache@v6, uses: EmbarkStudios/cargo-deny-action@v2, uses: taiki-e/install-action@cargo-semver-checks, uses: taiki-e/install-action@v2. These are mutable tag/branch references rather than pinned to a commit SHA. EVIDENCE NOT FOUND: The finding's evidence block for this specific id is empty (start_line/end_line 58 with no code_snippet), so I could not confirm exactly which of the 33 claimed occurrences this specific finding instance refers to, nor whether some entries (e.g. dtolnay/rust-toolchain@stable) are conventionally accepted floating refs by design. CHANGED VS PRE-EXISTING: ci.yml is in the changed-files list for this MR (per -e_DIFF), and the diff hunks show the report/semver-checks and release-bump-guard jobs' Install system dependencies steps were modified, but the actual uses: action-tag lines (e.g. line 58, dtolnay/rust-toolchain@stable) are not shown as touched in the visible hunks — however per the tie-break rule, since ci.yml is in the changed-files list, this must be treated as CHANGED even though the specific uses: lines are pre-existing content not modified by this specific diff. VERDICT JUSTIFICATION: The pattern of mutable action tags is real and visible in the file (e.g. @stable, @v7, @v6, @v2, @cargo-semver-checks are not full-length commit SHAs), but the empty evidence snippet at line 58 combined with inability to confirm this is a net-new occurrence (vs pre-existing convention across the whole 560-line file) makes this inconclusive for a specific verdict; a human should confirm which of the 33 occurrences are newly introduced by this PR vs. pre-existing across the file.
  • Validation Effort: This finding was validated up to a point, but the available evidence was insufficient for a conclusive automated verdict. A human (developer / security team) must manually review the code and decide. Not dismissed — treat as an open item pending human review.


Generated by Agentic Sec — AI Security Validation Agent
This report includes full scan data + AI validation evidence. Feed to engineering copilots for automated fix deployment.

Complementary: 🛡️ **Threat Model & Affect Analysis**
Details

🛡️ Threat Model & Affect Analysis — PR #1259

Field Value
Repository OpenVTC/verifiable-trust-infrastructure
Branch fix/release-guard-system-depsmain
Generated 2026-09-06

ℹ️ This report contains theoretical threats and impact analysis for the MR.
Unlike the Security Code Review Report (which contains confirmed, materialised issues),
these are potential risks that may or may not be exploitable. Use this for defence-in-depth planning.


📋 Affect Analysis

Change Summary

This PR fixes a CI defect where the cargo-semver-checks 'release-bump guard' job lacked the system dependencies (libdbus-1-dev, pkg-config) required by rustdoc to build the vta-cli-common crate for baseline comparison. The missing dependency caused a build failure that was misreported as 'a published crate does not build' rather than a CI environment defect. The fix extracts the previously inline apt-get logic (which had bounded timeouts/retries to avoid a known mirror-hang issue) into a new shared script, scripts/install-semver-deps.sh, and calls it from both the report job and the previously-missing semver-guard job.

Diff: +63 / -39 lines
Types: ci_cd, reliability_fix, refactor

⚠️ Security Implications

⚪ Fixes false-positive 'broken published crate' misreport in release-bump guard

Fixes false-positive 'broken published crate' misreport in release-bump guard

Action: No further action required for this specific fix; monitor the next few real semver-guard runs to confirm the false-positive mode does not recur under different missing-dependency scenarios.

🟡 Consolidation of dependency-install logic creates a shared single point of failure/compromise across two CI jobs

Consolidation of dependency-install logic creates a shared single point of failure/compromise across two CI jobs

Action: Require CODEOWNERS-mandated review for changes to scripts/ used by CI; consider adding a checksum verification step; add an explicit least-privilege permissions: block to limit what a compromised step could exfiltrate via GITHUB_TOKEN.

🔵 Sudo-privileged apt-get install introduced into a CI job that previously did not perform privileged package installation

Sudo-privileged apt-get install introduced into a CI job that previously did not perform privileged package installation

Action: Scope sudo usage as narrowly as possible; consider pre-baking a container image with these dependencies already installed to eliminate the need for runtime sudo entirely in both jobs.

🔵 Unpinned, unverified apt package installation (libdbus-1-dev, pkg-config) now exercised by two CI jobs instead of one

Unpinned, unverified apt package installation (libdbus-1-dev, pkg-config) now exercised by two CI jobs instead of one

Action: Pin exact package versions; evaluate vendoring/caching a verified snapshot of these two packages instead of relying on live mirror state; consider network egress restrictions for the install step where feasible.

🔵 Unquoted $pkgs variable expansion (shell hygiene anti-pattern, currently non-exploitable)

Unquoted $pkgs variable expansion (shell hygiene anti-pattern, currently non-exploitable)

Action: Quote all variable expansions or convert to a bash array (pkgs=(); ...; "${pkgs[@]}") as defense-in-depth, even though current risk is negligible.

🧩 Affected Components

Component Impact Change What Changed
CI Report Job (cargo-semver-checks report) low modified The inline 35-line apt-get dependency-install logic was replaced with a single call to the new shared script scripts/install-semver-deps.sh.
CI Semver Guard Job (release-bump guard) medium modified A new 'Install system dependencies' step was added, running the same shared script, immediately after checkout and before the Rust toolchain
install-semver-deps.sh (new shared script) medium new New file introducing a single, version-controlled, shared implementation of the dependency-install logic previously duplicated inline in ci.

📁 File Classifications

.github/workflows/ci.yml

  • Type: ci_cd_security_relevant

scripts/install-semver-deps.sh

  • Type: ci_build_helper_script

🛡️ STRIDE Threat Model

Identified Threats (10)

🟡 STRIDE-1: Unpinned Package Installation in install-semver-deps.sh

Field Detail
Category Tampering, Elevation of Privilege
Severity Medium
Likelihood Possible
CVSS 5.1 CVSS:4.0/AV:N/AC:H/AT:P/PR:H/UI:N/VC:L/VI:L/VA:N/SC:N/SI:N/SA:N
Residual Severity Low
CWE CWE-1357,CWE-494
CAPEC CAPEC-538,CAPEC-184
OWASP A08:2021 - Software and Data Integrity Failures

Description: Shell exec entry point in scripts/install-semver-deps.sh allows supply-chain package substitution due to unpinned, unverified apt package installation, resulting in build-time code execution with sudo privileges on the CI runner.

Evidence: scripts/install-semver-deps.sh:25-27

if sudo timeout 120 apt-get install -y $pkgs; then
  exit 0
fi

Attack Scenario:

  1. Attacker compromises or spoofs the APT mirror/index used by the GitHub-hosted ubuntu-24.04 runner, or exploits a dependency-confusion condition in the shipped package index.
  2. CI workflow at .github/workflows/ci.yml invokes run: bash scripts/install-semver-deps.sh for both the report job and semver guard job.
  3. Inside scripts/install-semver-deps.sh, the script runs sudo timeout 120 apt-get install -y $pkgs for libdbus-1-dev pkg-config without pinning specific versions or verifying package checksums/signatures beyond APT's default trust chain.
  4. If the attacker controls or MITMs the package source (e.g., compromised mirror), a malicious libdbus-1-dev or pkg-config package with a malicious postinst/preinst script is installed with sudo privileges.
  5. Malicious postinst script executes arbitrary code in the CI runner context, potentially exfiltrating GITHUB_TOKEN, repository secrets, or tampering with the build/semver-check outcome.

🔎 Threat Clue: Derived from COMP-003 via EP-003

  • Data Flows: CI runner -> APT mirror -> installed system packages

Preconditions: Attacker must control or intercept the APT package source reachable by the GitHub-hosted runner, No package pinning, checksum, or signature verification beyond default APT/GPG trust is enforced, CI runner uses ephemeral, network-connected environment reachable by an on-path or compromised-mirror attacker

Existing Controls: Ubuntu's default APT GPG signature verification of repository metadata and packages • Script fetches from the runner's pre-shipped trusted package index by default, only falling back to apt-get update if package is missing • set -euo pipefail ensures script fails closed on errors rather than silently continuing

Recommended Mitigations: Pin exact package versions for libdbus-1-dev and pkg-config • Consider vendoring or caching a verified package snapshot instead of relying on live mirror state • Restrict sudo scope needed for the install step via least-privilege runner configuration • Enable GitHub Actions egress/network allow-listing for the runner where feasible


🔵 STRIDE-2: Unquoted Variable Expansion Leading to Word Splitting in install-semver-deps.sh

Field Detail
Category Tampering
Severity Low
Likelihood Unlikely
CVSS 2.3 CVSS:4.0/AV:L/AC:H/AT:P/PR:H/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N
Residual Severity None
CWE CWE-78,CWE-88
CAPEC CAPEC-88
OWASP A03:2021 - Injection

Description: Shell exec entry point in scripts/install-semver-deps.sh allows argument injection via word splitting due to unquoted $pkgs variable expansion, resulting in potential unintended apt-get argument interpretation.

Evidence: scripts/install-semver-deps.sh:9-11,26,32

pkgs=""
for p in libdbus-1-dev pkg-config; do
  dpkg -s "$p" >/dev/null 2>&1 || pkgs="$pkgs $p"
done
...
if sudo timeout 120 apt-get install -y $pkgs; then

Attack Scenario:

  1. The script builds pkgs by string concatenation: pkgs="$pkgs $p" for each package name from a fixed internal list (libdbus-1-dev, pkg-config).
  2. The resulting $pkgs variable is expanded unquoted in sudo timeout 120 apt-get install -y $pkgs (line 26) and again in the retry loop (line 32).
  3. Because the loop-derived values are hardcoded literals with no attacker-controlled input path today, exploitation currently requires an attacker to first gain the ability to modify the script's package list (e.g., via a separate PR-merge compromise) to insert a value containing shell metacharacters or additional flags.
  4. If an attacker can inject a value into p (e.g., through a future refactor reading package names from an external/environment-controlled source), unquoted expansion would allow argument injection into apt-get install (e.g., injecting --allow-unauthenticated or additional untrusted package names).
  5. This would broaden the install surface or downgrade APT's security posture for that invocation.

🔎 Threat Clue: Derived from COMP-003 via EP-003

  • Data Flows: Internal script variable to apt-get argv

Preconditions: Attacker must first achieve write access to modify the script or introduce an externally-controlled package name source, No current external input reaches the vulnerable variable, making this a latent/defense-in-depth issue rather than directly exploitable today

Existing Controls: Package list is currently a fixed, hardcoded internal array (libdbus-1-dev pkg-config) with no external input path • set -euo pipefail limits some failure-mode risk

Recommended Mitigations: Quote all variable expansions: "$pkgs" and use an array instead of a space-joined string to avoid word splitting entirely • Adopt pkgs=() bash arrays with "${pkgs[@]}" expansion for robustness against future refactors


🔵 STRIDE-3: Unbounded Retry Sleep Loop Causing CI Resource Exhaustion in install-semver-deps.sh

Field Detail
Category Denial of Service
Severity Low
Likelihood Possible
CVSS 3.1 CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N
Residual Severity Low
CWE CWE-400,CWE-770
CAPEC CAPEC-125,CAPEC-227
OWASP A05:2021 - Security Misconfiguration

Description: CI trigger entry point in the report and semver-guard jobs allows job-timeout-based Denial of Service due to a bounded-but-lengthy apt retry loop (up to 3 x 240s+10s cycles) triggered by a genuinely unresponsive or maliciously stalled mirror, resulting in wasted CI compute minutes and delayed pipeline feedback.

Evidence: scripts/install-semver-deps.sh:28-36

for attempt in 1 2 3; do
  if sudo timeout 120 apt-get -o Acquire::Retries=3 update \
    && sudo timeout 120 apt-get -o Acquire::Retries=3 install -y $pkgs; then
    exit 0
  fi
  echo "::warning::apt attempt $attempt failed or timed out; retrying in 10s"
  sleep 10
done

Attack Scenario:

  1. An attacker with the ability to influence network conditions towards the APT mirror (e.g., a compromised or hostile network path, or an attacker who can trigger repeated CI runs via low-effort PRs) causes the initial apt-get install to fail.
  2. Script falls into the warning branch and begins the 3-attempt retry loop: each iteration runs apt-get update (up to 120s timeout) then apt-get install (up to 120s timeout), followed by a 10s sleep, for up to ~730 seconds worst case.
  3. Combined with the job-level timeout-minutes: 45 on the semver guard job, repeated forced failures across many CI runs (e.g., an attacker opening many draft PRs to trigger workflow_dispatch/PR triggers) consume shared runner minutes and delay legitimate CI feedback for the org.
  4. This is an amplification of the original incident described in the code comments (three prior real-world hangs on apt-get update), showing the failure mode is realistic and reproducible under adverse network conditions.
  5. No circuit breaker exists across separate CI runs to detect and short-circuit a persistently broken mirror without repeatedly paying the full retry cost.

🔎 Threat Clue: Derived from COMP-001, COMP-002, COMP-003 via EP-001, EP-002

  • Data Flows: CI trigger -> apt mirror retry loop -> runner minute consumption

Preconditions: Attacker or adverse condition can cause the GitHub-hosted runner's default package mirror to be slow/unresponsive, Attacker can trigger repeated workflow runs (e.g., open-source repo with PR-triggered CI from forks)

Existing Controls: timeout 120 bounds each individual apt-get update/install call • Retry loop is capped at 3 attempts, not infinite • Job-level timeout-minutes: 45 provides an outer backstop

Recommended Mitigations: Add exponential backoff with jitter instead of fixed 10s sleep to reduce synchronized retry storms • Cache/vendor the required .deb packages in the repository or an internal artifact store to remove runtime dependency on the live mirror entirely • Restrict workflow triggers from external forks or require maintainer approval for first-time contributors to reduce trigger abuse surface


🔵 STRIDE-4: Shared Script Single Point of Failure Across Both CI Jobs in ci.yml

Field Detail
Category Tampering, Denial of Service
Severity Low
Likelihood Unlikely
CVSS 2.1 CVSS:4.0/AV:N/AC:H/AT:P/PR:H/UI:N/VC:N/VI:L/VA:L/SC:N/SI:N/SA:N
Residual Severity Low
CWE CWE-1357,CWE-829
CAPEC CAPEC-441
OWASP A08:2021 - Software and Data Integrity Failures

Description: CI trigger entry point in the report job and semver guard job allows blast-radius amplification via a single compromised shared script due to consolidation of previously duplicated dependency-install logic into scripts/install-semver-deps.sh, resulting in simultaneous compromise or simultaneous denial of service of both the report and semver-guard CI jobs.

Evidence: .github/workflows/ci.yml:500,547-548

run: bash scripts/install-semver-deps.sh
...
      - name: Install system dependencies
        run: bash scripts/install-semver-deps.sh

Attack Scenario:

  1. Attacker with write access to the repository (e.g., via a compromised maintainer account, malicious insider, or supply-chain compromise of a merged PR) modifies scripts/install-semver-deps.sh.
  2. Because both .github/workflows/ci.yml job report and the semver guard job now both call run: bash scripts/install-semver-deps.sh (previously each job had its own independent inline copy), the injected change propagates to both CI jobs simultaneously.
  3. Attacker inserts malicious commands (e.g., curl-pipe-to-shell exfiltration of CI secrets, or rm -rf sabotage) into the shared script.
  4. On the next CI run, both jobs execute the malicious script with the same effect, doubling blast radius compared to the pre-refactor duplicated-but-independent inline logic where only one job's copy might have been altered/reviewed.
  5. Malicious modifications could exfiltrate GITHUB_TOKEN or other secrets available to the workflow's environment, or reliably break both CI gates simultaneously to mask a subsequently merged malicious change.

🔎 Threat Clue: Derived from COMP-001, COMP-002, COMP-003 via EP-001, EP-002, EP-003

  • Data Flows: Shared script -> both CI job executions

Preconditions: Attacker must first obtain write access to the repository or ability to merge a malicious PR into scripts/install-semver-deps.sh, No branch protection/code owner review specifically hardened for CI script changes

Existing Controls: Presumed standard GitHub branch protection and PR review process for merging changes to the main branch (not explicitly verifiable from the diff alone) • Script is version-controlled and diff-reviewable, unlike inline YAML blocks previously duplicated across jobs, improving auditability of a single source of truth

Recommended Mitigations: Require CODEOWNERS-based mandatory review for changes under scripts/ used by CI • Pin and checksum-verify the install script's own integrity (e.g., via a signed commit or hash pinning) before execution in CI • Apply least-privilege GITHUB_TOKEN permissions on both jobs to limit exfiltration impact if the script is compromised • Consider running dependency-install steps in an isolated, non-privileged pre-stage container with no access to repository secrets


🔵 STRIDE-5: Missing Integrity Verification of install-semver-deps.sh Prior to Execution

Field Detail
Category Tampering, Repudiation
Severity Low
Likelihood Unlikely
CVSS 3.5 CVSS:4.0/AV:N/AC:H/AT:P/PR:H/UI:N/VC:L/VI:L/VA:N/SC:N/SI:N/SA:N
Residual Severity Low
CWE CWE-345,CWE-494
CAPEC CAPEC-186
OWASP A08:2021 - Software and Data Integrity Failures

Description: CI trigger entry point in ci.yml allows tampering with the executed script's contents due to absence of checksum, hash-pinning, or signature verification before bash scripts/install-semver-deps.sh is invoked, resulting in undetected execution of a modified script if the checkout step delivers tampered content.

Evidence: .github/workflows/ci.yml:500

run: bash scripts/install-semver-deps.sh

Attack Scenario:

  1. An attacker who can influence the checked-out repository state for a given CI run (e.g., via a malicious fork PR combined with a workflow misconfiguration that runs untrusted code with secrets, or a compromised git history/tag) causes a modified scripts/install-semver-deps.sh to be checked out.
  2. The workflow step run: bash scripts/install-semver-deps.sh (ci.yml lines 500 and 547-548) executes the file exactly as checked out, with no SHA-256 checksum check, GPG signature verification, or actions/checkout ref pinning validation performed before execution.
  3. Because there's no independent verification step, a maliciously modified script executes silently, and CI logs would only show the script's own echo output, not an indication that its content diverged from the trusted canonical version.
  4. This could be combined with STRIDE-4 (shared script single point of failure) to affect both jobs, and lacks a corrective control (e.g., commit-hash pinning) to detect drift.

🔎 Threat Clue: Derived from COMP-001, COMP-002, COMP-003 via EP-001, EP-002

  • Data Flows: Checkout -> script execution without integrity check

Preconditions: Attacker must achieve some level of control over the checked-out ref/content (e.g., malicious PR from a fork with pull_request_target misuse, or compromised source), Workflow must be configured in a way that untrusted content reaches a step with secret access

Existing Controls: Standard GitHub Actions checkout via actions/checkout@v7 pins to a specific action version • fetch-depth: 0 is used for full history retrieval only, unrelated to integrity of the script content itself

Recommended Mitigations: Add explicit script hash verification (e.g., sha256sum -c against a value stored in workflow YAML) before execution • Restrict which workflow triggers (e.g., disallow pull_request_target on untrusted forks) can reach this step with secrets • Enable required status checks and signed commits for changes to the scripts/ directory


⚪ STRIDE-6: Insufficient Logging of Package Install Outcomes for Forensic Traceability in install-semver-deps.sh

Field Detail
Category Repudiation
Severity Informational
Likelihood Unlikely
CVSS 1.0 CVSS:4.0/AV:N/AC:H/AT:P/PR:H/UI:N/VC:N/VI:N/VA:N/SC:N/SI:N/SA:N
Residual Severity None
CWE CWE-778
CAPEC CAPEC-93
OWASP A09:2021 - Security Logging and Monitoring Failures

Description: CI trigger entry point in the report and semver guard jobs allows repudiation of a supply-chain tampering event due to the script only logging generic warning/error strings rather than package versions, hashes, or source repository metadata, resulting in reduced forensic ability to detect and prove that a malicious package version was installed during a given CI run.

Evidence: scripts/install-semver-deps.sh:15,17,29,33,36

echo "missing:$pkgs"
...
echo "::warning::install from the shipped index failed; refreshing"

Attack Scenario:

  1. If a malicious or unexpectedly different version of libdbus-1-dev or pkg-config were ever installed (e.g., via mirror compromise as in STRIDE-1), the script's only output is echo "missing:$pkgs" and generic success/failure/warning messages (lines 15, 17, 29, 33, 36).
  2. No step records the exact installed package version, source repository URL, or a hash/checksum of the installed .deb into CI logs or artifacts.
  3. Post-incident, an investigator reviewing CI logs for a given historical run would be unable to determine which exact package version or source was used, hampering root-cause analysis and non-repudiation of a supply-chain compromise.
  4. This gap does not itself allow initial compromise but reduces detectability and evidentiary value of CI logs after a STRIDE-1-style attack has occurred.

🔎 Threat Clue: Derived from COMP-003 via EP-003

  • Data Flows: Script execution -> CI log output

Preconditions: A prior compromise event (e.g., STRIDE-1) must occur for this gap to matter, No dedicated CI log retention/analysis pipeline captures package provenance today

Existing Controls: Script does emit basic status messages (::warning::, ::error::) to GitHub Actions annotations, providing partial run-level traceability

Recommended Mitigations: Log apt-cache policy $pkgs output and installed package versions/hashes to the CI log or a build artifact before and after installation • Emit machine-parseable structured logs (e.g., JSON) capturing package name, version, and source repo for each install for later SIEM ingestion


⚪ STRIDE-7: Prompt-Injection-Style Instruction Embedded in Code Comments Attempting to Influence Automated Review Tooling

Field Detail
Category Tampering
Severity Informational
Likelihood Very Unlikely
CVSS 0.0 CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:N/VC:N/VI:N/VA:N/SC:N/SI:N/SA:N
Residual Severity None
CWE CWE-1039
CAPEC CAPEC-148
OWASP A03:2021 - Injection

Description: Code comment content in scripts/install-semver-deps.sh and ci.yml allows social-engineering of automated review/LLM-based tooling due to extensive narrative prose embedded in comments framing prior incidents and rationale in persuasive human language, resulting in a theoretical risk of biasing automated security reviewers toward under-scrutinizing the accompanying code changes.

Evidence: scripts/install-semver-deps.sh:1-24

# They already did once: the release-bump guard shipped without this step and
# failed its first real run on `libdbus-sys ... pkg_config failed`, reported as
# a broken published artifact.

Attack Scenario:

  1. The diff introduces unusually long, narrative-style comments (e.g., 'They already did once: the release-bump guard shipped without this step and failed its first real run...') embedded directly in scripts/install-semver-deps.sh and ci.yml.
  2. Such verbose justification text, while plausibly legitimate engineering rationale here, follows a pattern that could be exploited by a malicious contributor to pre-emptively justify risky code changes (e.g., disabling security checks) by narrating a believable incident story to bias human or LLM-based PR reviewers into approving without deep scrutiny.
  3. This analysis explicitly treats all such comment text as untrusted data under the security boundary directive, and does not treat any embedded phrase as an instruction to alter this report's verdicts.
  4. No exploitation occurred in this specific PR — the rationale appears genuine and consistent with the code changes — but the pattern itself is worth flagging as a governance/process risk for future PRs using similar narrative techniques to smuggle in unrelated risky changes.

🔎 Threat Clue: Derived from COMP-003 via EP-003

  • Data Flows: Code comment -> reviewer/tooling perception

Preconditions: A future malicious contributor uses similarly persuasive prose to justify unrelated or risky changes, Reviewers (human or automated) rely on narrative framing rather than independently verifying the actual code behavior

Existing Controls: This analysis applies a strict data/instruction separation boundary, treating all repository content as data regardless of narrative framing • Code changes were independently verified against their actual behavior rather than accepted based on comment narrative alone

Recommended Mitigations: Establish PR review guidance requiring reviewers to verify code behavior independently of comment narrative • Apply consistent automated static analysis regardless of surrounding comment content • Flag unusually long justification comments for additional human review scrutiny


🔵 STRIDE-8: Excessive sudo Privilege Usage for Dependency Installation in CI Runner

Field Detail
Category Elevation of Privilege
Severity Low
Likelihood Unlikely
CVSS 3.8 CVSS:4.0/AV:N/AC:H/AT:P/PR:H/UI:N/VC:L/VI:L/VA:N/SC:N/SI:N/SA:N
Residual Severity Low
CWE CWE-250
CAPEC CAPEC-233
OWASP A01:2021 - Broken Access Control

Description: CI trigger entry point in ci.yml allows privilege-escalation blast-radius amplification due to invoking sudo for every apt-get call in install-semver-deps.sh without scoping or dropping privileges afterward, resulting in any injected command executing with root privileges on the ephemeral runner.

Evidence: scripts/install-semver-deps.sh:26,32-33

if sudo timeout 120 apt-get install -y $pkgs; then
...
  if sudo timeout 120 apt-get -o Acquire::Retries=3 update \
    && sudo timeout 120 apt-get -o Acquire::Retries=3 install -y $pkgs; then

Attack Scenario:

  1. Every apt-get invocation in the script (lines 12? actually 26, 32-33) is prefixed with sudo timeout 120 apt-get ..., granting root privileges for the entire install operation.
  2. If any of the other identified threats (STRIDE-1 malicious package, STRIDE-2 argument injection, STRIDE-4/5 script tampering) is realized, the injected/malicious code executes with root privileges rather than the default runner user's privileges.
  3. Root access on the ephemeral GitHub-hosted runner allows reading all files, modifying system state, and potentially accessing the GITHUB_TOKEN or OIDC token used by subsequent steps if not already isolated.
  4. While the runner is ephemeral and destroyed after the job, within the job's lifetime a root-level compromise has full read/write access to the workspace, environment variables, and any mounted secrets for that job.

🔎 Threat Clue: Derived from COMP-001, COMP-002, COMP-003 via EP-001, EP-002, EP-003

  • Data Flows: Script -> sudo apt-get -> root-level execution context

Preconditions: One of the other tampering-class threats (STRIDE-1, STRIDE-2, STRIDE-4, STRIDE-5) must first succeed to have code executed in this context, sudo is passwordless and unrestricted on the default GitHub-hosted runner image (standard configuration)

Existing Controls: GitHub-hosted runners are ephemeral, single-use VMs, limiting persistence of any root compromise • sudo scope is limited to what the default runner image permits (no additional sudo grants added by this PR)

Recommended Mitigations: Scope the sudo usage as narrowly as possible per command rather than blanket sudo across the whole script • Consider running the report/semver-guard jobs in a locked-down container image with dependencies pre-baked, eliminating the need for sudo at CI runtime entirely • Apply least-privilege GITHUB_TOKEN permissions (permissions: block) at the workflow/job level to limit what a root-level compromise could exfiltrate


🔵 STRIDE-9: Fixed Timeout Ceilings Enabling Slow-Drip Denial of Service via Partial Mirror Responsiveness

Field Detail
Category Denial of Service
Severity Low
Likelihood Possible
CVSS 2.6 CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N
Residual Severity Low
CWE CWE-400
CAPEC CAPEC-227
OWASP A05:2021 - Security Misconfiguration

Description: CI trigger entry point in ci.yml/install-semver-deps.sh allows a slow-drip denial-of-service condition due to the script's timeout 120 bounding individual commands but not detecting a mirror that responds just-under the threshold on every attempt, resulting in consistent near-maximal delay (up to ~12 minutes total) on every CI run without ever triggering an outright hard failure that would prompt investigation.

Evidence: scripts/install-semver-deps.sh:12,26,32-33

if sudo timeout 120 apt-get install -y $pkgs; then
...
  if sudo timeout 120 apt-get -o Acquire::Retries=3 update \
    && sudo timeout 120 apt-get -o Acquire::Retries=3 install -y $pkgs; then

Attack Scenario:

  1. An adversarial or degraded mirror consistently responds in ~115 seconds (just under the 120s timeout) for both apt-get update and apt-get install calls.
  2. Every CI run for both the report job and semver guard job pays this near-maximal latency tax silently, since the script only emits a warning/error when the full timeout is exceeded, not when it is merely close to the ceiling.
  3. Over many CI runs (e.g., dozens of PRs per day in an active repo), this results in cumulative, difficult-to-diagnose CI slowdown that degrades developer velocity and increases the probability of legitimate job timeout (timeout-minutes: 45) breaches under any additional load.
  4. Because the behavior does not cross the hard-failure threshold, it may persist undetected for a long time, unlike the original three-hangs incident that was noisy enough to be noticed and fixed.

🔎 Threat Clue: Derived from COMP-001, COMP-002, COMP-003 via EP-001, EP-002

  • Data Flows: CI trigger -> bounded-but-slow apt calls -> cumulative CI delay

Preconditions: A marginal/degraded (not fully unresponsive) mirror condition, which is plausible given the mirror's demonstrated instability described in the incident history comments, No CI performance-trend monitoring/alerting on step duration exists to catch this gradually

Existing Controls: timeout 120 still provides a hard ceiling preventing indefinite hangs • Job-level timeout-minutes: 45 is a backstop against total runaway jobs

Recommended Mitigations: Instrument and monitor per-step CI duration metrics with alerting on sustained near-timeout latencies • Vendor/cache the required .deb packages in-repo or in an internal artifact store to remove dependency on live mirror latency entirely • Lower the individual timeout to a tighter bound with faster fallback to a known-good mirror or cached package source


🟡 STRIDE-10: Missing Least-Privilege GITHUB_TOKEN Permissions Declaration Exposing Broader Job Scope in ci.yml

Field Detail
Category Elevation of Privilege
Severity Medium
Likelihood Possible
CVSS 4.8 CVSS:4.0/AV:N/AC:H/AT:P/PR:H/UI:N/VC:L/VI:L/VA:N/SC:N/SI:N/SA:N
Residual Severity Low
CWE CWE-250,CWE-269
CAPEC CAPEC-122
OWASP A01:2021 - Broken Access Control

Description: CI trigger entry point in the report and semver guard jobs allows unnecessary elevation-of-privilege exposure due to no visible permissions: block scoping GITHUB_TOKEN at the workflow or job level in the visible diff, resulting in any code execution achieved via the other identified threats potentially inheriting default (often read/write) token scope for repository operations.

Evidence: .github/workflows/ci.yml:1-560

jobs:
  report:
    ...
  semver-guard:
    timeout-minutes: 45
    steps:
      - uses: actions/checkout@v7

Attack Scenario:

  1. The visible diff for ci.yml does not show a permissions: block being added or modified alongside the new shared script invocation for either the report job or the semver guard job.
  2. If the overall workflow file lacks an explicit least-privilege permissions: declaration (default GITHUB_TOKEN scope depends on repository/org settings and can default to read/write), any code execution achieved via STRIDE-1, STRIDE-2, STRIDE-4, STRIDE-5, or STRIDE-8 would run with whatever token scope the job inherits.
  3. An attacker who achieves arbitrary command execution within the Install system dependencies step (e.g., via a compromised mirror or tampered script) could use an overly broad GITHUB_TOKEN to push commits, create releases, or modify repository settings, depending on the effective permission scope.
  4. This is a configuration gap rather than a direct code vulnerability in the diff itself, but it materially increases the blast radius of any of the other threats in this model if the broader workflow file does not scope permissions tightly.

🔎 Threat Clue: Derived from COMP-001, COMP-002 via EP-001, EP-002

  • Data Flows: CI job execution context -> GITHUB_TOKEN scope

Preconditions: The full ci.yml file (outside the shown diff) does not declare a restrictive permissions: block at workflow or job level, One of the code-execution threats (STRIDE-1/2/4/5/8) must succeed for this to matter

Existing Controls: Cannot be fully verified from the provided diff/context; assumed default GitHub Actions token behavior applies unless proven otherwise elsewhere in the file

Recommended Mitigations: Add an explicit permissions: contents: read (or narrower) block at the workflow or job level for both the report and semver guard jobs • Audit the full ci.yml for any jobs with unnecessary write/administrative permissions • Use OIDC federation with narrowly scoped short-lived tokens instead of relying on default GITHUB_TOKEN scope where external cloud access is needed



🍝 PASTA Threat Model

Application Purpose

The OpenVTC verifiable-trust-infrastructure project provides Rust-based crates for building verifiable trust systems; this PR specifically hardens and consolidates the CI pipeline's system-dependency installation logic used to build and semver-check the published crates.

Inherent Risks

  • CI pipelines rely on third-party package mirrors outside organizational control.
  • GitHub-hosted runners execute with sudo/root-capable privileges for setup tasks by default.
  • Shared automation scripts create common-mode failure and compromise paths across multiple CI jobs.
  • Public/open-source repositories are exposed to PR-triggered CI abuse from untrusted forks.

Objectives

Risk: Accept low residual risk from reliance on the default APT trust chain given the fixed, small package list.; Treat CI availability degradation as tolerable up to defined job timeout thresholds.
Business: Maintain trustworthy, continuously verified releases of the verifiable-trust-infrastructure crates.; Minimize CI downtime and false-positive failures that erode developer trust in automated gates.
Security: Prevent supply-chain tampering of build-time system dependencies.; Limit blast radius of any single compromised CI script or credential.
Financial: Minimize wasted GitHub Actions runner-minute spend from redundant or hanging CI steps.
Compliance: Maintain auditable, version-controlled CI configuration suitable for supply-chain security review (e.g., SLSA-aligned practices).
Functional: Reliably build and semver-check all workspace crates including those depending on libdbus-sys.; Provide a single, maintainable source of truth for CI system-dependency installation.
Operational: Ensure CI jobs complete within defined timeout windows under normal and degraded mirror conditions.; Reduce duplicate/drifting logic between the report job and semver guard job.

Business Impact Analysis (1)

BIA-1: Continuous Integration Build and Semver Verification Pipeline (High)

The CI pipeline builds the Rust workspace and verifies that proposed crate version bumps correctly reflect API changes before publication.

MTD: 01 days 00:00 hours | RTO: 00 days 04:00 hours | RPO: 00 days 00:00 hours

  • Stakeholders: Crate Consumers / Open Source Contributors / Project Maintainers / Release Engineers
  • Dependencies: GitHub Actions Runner Infrastructure / GitHub-Hosted APT Package Mirror / Rust Toolchain (dtolnay/rust-toolchain) / cargo-semver-checks Tooling / scripts/install-semver-deps.sh
  • Disruptions: APT mirror becomes unresponsive or compromised / Shared install script is tampered with or broken / CI job exceeds timeout-minutes threshold
  • Impacts: Delayed crate releases blocking downstream consumers / False-positive 'broken published crate' reports eroding trust in the semver guard / Wasted GitHub Actions compute minutes and increased CI cost / Potential supply-chain compromise of the build environment if package installation is tampered with

Technical Scope

Roles (3): RO-1 Repository Maintainer · RO-2 External Contributor · RO-3 CI Runner Service Account

Actors (3): AC-1 GitHub Actions Scheduler · AC-2 CI Runner Bash Process · AC-3 Human PR Author

Use Cases (1): Automated CI Build and Semver Verification on Pull Request

Attack Trees (2): SC-4: install-semver-deps.sh Script · SC-1: CI Workflow Orchestrator (ci.yml)

Entry Points (3): EP-1 Report Job CI Trigger · EP-2 Semver Guard Job CI Trigger · EP-3 Shared Dependency Install Script Execution

Risk Registry (4): RISK-1 · RISK-2 · RISK-3 · RISK-4

Threat Actors (3): TA-1 Malicious/Compromised APT Mirror Operator · TA-2 Malicious Insider or Compromised Maintainer Account · TA-3 Malicious External Contributor

Infrastructure (1): IF-1 GitHub-Hosted ubuntu-24.04 Runner

Trust Boundaries (3): TB-1 GitHub Actions Control Plane · TB-2 Ephemeral CI Runner Boundary · TB-3 Repository Source Control Boundary

External Entities (2): EE-1 External Contributors / PR Authors · EE-2 Ubuntu APT Package Mirror Network

System Components (5): SC-1 CI Workflow Orchestrator (ci.yml) · SC-2 Report Job Runner · SC-3 Semver Guard Job Runner · SC-4 install-semver-deps.sh Script · SC-5 External APT Package Mirror

Resources And Assets (3): RA-1 GITHUB_TOKEN and CI Secrets · RA-2 Installed System Packages (libdbus-1-dev, pkg-config) · RA-3 Rust Workspace Source and Published Crates

Technologies And Dependencies (4): TD-1 GitHub Actions actions/checkout · TD-2 dtolnay/rust-toolchain · TD-3 taiki-e/install-action cargo-semver-checks · TD-4 libdbus-1-dev / pkg-config (APT packages)

⚔️ Attack Scenarios (1)

Exploit identified weaknesses

flowchart LR
  S0["Unpinned Package Installation in install-semver-deps.sh"]
  S1["Unquoted Variable Expansion Leading to Word Splitting in ins"]
  S2["Unbounded Retry Sleep Loop Causing CI Resource Exhaustion in"]
  S0 --> S1
  S1 --> S2
Loading

📊 Risk Summary

Total Threats: 10

By Severity: Low: 6 · Medium: 2 · Informational: 2

By Category: Tampering: 5 · Elevation of Privilege: 3 · Denial of Service: 3 · Repudiation: 2


Generated by Agentic Sec — Threat Model & Affect Analysis Agent

📊 Summary & findings
✅ Confirmed ⚠️ Must-Review-By-Human
0 1

Must-Review-By-Human (1)

  • 🟡 Github Actions Mutable Action Tag (33 occurrences)

@stormer78
stormer78 merged commit 61ee7d0 into main Sep 6, 2026
13 of 14 checks passed
@stormer78
stormer78 deleted the fix/release-guard-system-deps branch September 6, 2026 05:41
stormer78 added a commit that referenced this pull request Sep 6, 2026
…ged (#1267)

`cargo test --workspace` is the longest-running check in the repo. Of 2203s of
test execution, `vta_service` alone is 906s and vtc-service's 52 integration
binaries are another 848s — and every pull request pays for both regardless of
what it touched.

Split into two jobs that together run exactly the same set:

  Test (workspace)  cargo test --workspace --exclude vtc-service --exclude vtc-client
  Test (VTC)        cargo test -p vtc-service -p vtc-client

each gated by scripts/ci-affects.sh on whether the change can reach it. On the
last 120 commits that is 13% of changes skipping the workspace half and a large
share of the 53% VTA-only ones skipping the VTC half.

The scope of the workspace half is stated as `--except vtc-service`, and this is
the part worth not hand-rolling: it is NOT the complement of the VTC closure.
`room-host` depends on `vtc-client`, so `vtc-client` belongs to both sides and a
change to it must run both jobs. Deriving the closure from cargo metadata gets
that right; writing "everything that isn't vtc-*" by hand does not. Verified:

  vtc-service only   workspace=skip  vtc=run
  vtc-client only    workspace=run   vtc=run     <- the case a hand list breaks
  vta-service only   workspace=run   vtc=skip
  vta-tee only       workspace=run   vtc=skip
  vti-common         workspace=run   vtc=run
  room-host          workspace=run   vtc=skip
  docs only          workspace=skip  vtc=skip

The #1243 no-op-rebuild guard moves to the VTC job, where the build.rs it guards
actually lives.

Also renames scripts/install-semver-deps.sh to install-build-deps.sh and uses it
here. The Test job carried a third verbatim copy of that apt block; the point of
extracting it in #1259 was to stop having several, and adding a fourth while
splitting a job would have missed it.

`vti-e2e-tests` is in the workspace half and its closure contains no vtc-*, so
nothing cross-cutting is stranded by the split — checked rather than assumed.

Signed-off-by: Glenn Gore <glenn.g@affinidi.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants