fix(ci): harden workflow supply-chain and injection surface - #43
Conversation
- security-003: skill-check.yml — move ${{ github.event_name }} and
${{ github.base_ref }} out of the "Detect changed SKILL.md files"
run: body into env: (EVENT_NAME/BASE_REF), referenced as quoted
shell variables, closing the script-injection surface.
- supply-chain-001: skill-check.yml — drop the unused `anthropic`
PyPI install (darwin-skill scoring is heuristic-only and never
imports it); pin pyyaml to an exact hash-verified version via the
new requirements-ci.txt, installed with `pip install
--require-hashes` in both the score-skills and frontmatter-check
jobs. One pinned wheel hash was independently re-verified by
downloading the exact manylinux cp312 artifact ubuntu-latest would
fetch and recomputing its sha256 locally.
- supply-chain-002: release.yml — pin `choco install ripgrep` to an
exact version (14.1.0) instead of floating latest.
- supply-chain-004: ci.yml, release.yml, skill-check.yml — pin every
`uses:` action (actions/checkout, actions/setup-python,
actions/upload-artifact, and the new actions/attest-build-provenance)
to a full 40-hex commit SHA resolved from the GitHub API, with the
version kept as a trailing comment.
- supply-chain-005: ci.yml, release.yml — add --locked to every
`cargo check` / `cargo test` / `cargo build --release` invocation.
`cargo fmt` is deliberately excluded: it never resolves dependencies
(no Cargo.lock read) and `cargo fmt --locked` is rejected outright
by cargo-fmt's own argument parser (verified locally) — adding it
there would break the job rather than harden it.
- supply-chain-006: skill-check.yml — add the missing top-level
`permissions: contents: read` block (ci.yml and release.yml already
had one).
- supply-chain-007: release.yml — attest the release zip with
actions/attest-build-provenance (workflow permissions gain
id-token: write / attestations: write), and drop --clobber from the
`gh release upload` re-run path so a leaked contents:write token can
no longer silently replace already-published assets; a rerun now
fails loudly instead, with a comment explaining the manual override
path.
Addresses security-003 from #33 and supply-chain-001/002/004/005/006/007
from #35.
|
Caution The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased. |
|
Warning Review limit reached
Next review available in: 18 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughThe workflows pin GitHub Actions, enforce Cargo lockfiles, hash-pin PyYAML CI dependencies, add release artifact provenance attestation, and prevent release uploads from silently overwriting existing assets. ChangesWorkflow hardening
Estimated code review effort: 3 (Moderate) | ~20 minutes Possibly related PRs
Poem
Sequence Diagram(s)sequenceDiagram
participant ReleaseWorkflow
participant Cargo
participant AttestationService
participant GitHubRelease
ReleaseWorkflow->>Cargo: run locked check, test, and build
ReleaseWorkflow->>AttestationService: attest Windows zip artifacts
ReleaseWorkflow->>GitHubRelease: upload assets without clobbering
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
requirements-ci.txt (1)
5-10: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winPin the Python runtime that these hashes target.
The file only documents CPython 3.10-3.14 manylinux x86_64 artifacts, while the workflow uses the mutable
ubuntu-latestdefault Python. Pin an explicit Python version with a pinnedsetup-pythonaction, or maintain hashes for every supported runtime and platform.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@requirements-ci.txt` around lines 5 - 10, Update the CI workflow’s Python setup to use an explicit CPython version within the hashed 3.10–3.14 range and pin the setup-python action revision, replacing reliance on ubuntu-latest’s mutable default. Keep the requirements-ci.txt hashes aligned with that selected runtime.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/skill-check.yml:
- Line 51: Replace both requirements-ci.txt installation steps at
.github/workflows/skill-check.yml lines 51 and 164 with installation from a
trusted lock/manifest copied from the base branch or another immutable trusted
source; do not install the PR-supplied requirements-ci.txt, while retaining hash
verification for the trusted dependency set.
- Around line 48-51: Remove the unpinned `python -m pip install --upgrade pip`
command from the “Setup Python + pinned deps” workflow step, leaving the
hash-verified `requirements-ci.txt` installation unchanged.
- Around line 22-24: Disable persisted checkout credentials for all three
actions/checkout steps in .github/workflows/skill-check.yml at lines 22-24,
139-139, and 160-160 by adding persist-credentials: false to each checkout
configuration. Keep fetch-depth: 0 unchanged.
---
Nitpick comments:
In `@requirements-ci.txt`:
- Around line 5-10: Update the CI workflow’s Python setup to use an explicit
CPython version within the hashed 3.10–3.14 range and pin the setup-python
action revision, replacing reliance on ubuntu-latest’s mutable default. Keep the
requirements-ci.txt hashes aligned with that selected runtime.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: f2216d2c-77b9-41dc-a7b0-3e5c2dab9cc8
📒 Files selected for processing (4)
.github/workflows/ci.yml.github/workflows/release.yml.github/workflows/skill-check.ymlrequirements-ci.txt
| - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 | ||
| with: | ||
| fetch-depth: 0 |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== workflow relevant sections =="
cat -n .github/workflows/skill-check.yml | sed -n '1,230p'
echo
echo "== all checkout usages in workflow =="
python3 - <<'PY'
from pathlib import Path
p=Path('.github/workflows/skill-check.yml')
for i,line in enumerate(p.read_text().splitlines(),1):
if 'uses: actions/checkout' in line:
# print context up to 6 lines and check with block continuation
start=max(1,i-2); end=min(p.read_text().count('\n'), i+4)
print(f'--- {p}:{i} ---')
for n,l in enumerate(p.read_text().splitlines(),1):
if start <= n <= end:
print(f'{n:4}: {l}')
PY
echo
echo "== action checkout persist-credentials default context search =="
rg -n "persist-credentials|checkout token|GITHUB_TOKEN|permissions:" .github/workflows/skill-check.ymlRepository: 2233admin/code-intel-pipeline
Length of output: 10791
🌐 Web query:
actions/checkout v4 persist-credentials default true README
💡 Result:
In the actions/checkout v4 action, the persist-credentials input defaults to true [1][2]. When persist-credentials is set to true (the default behavior), the authentication token or SSH key is persisted in the local Git configuration [3][4]. This allows subsequent scripts in your workflow to run authenticated Git commands [3][5]. The action ensures the token is removed during post-job cleanup [3]. If you wish to opt out of this behavior, you must explicitly set persist-credentials to false in your workflow file [3][4]: - uses: actions/checkout@v4 with: persist-credentials: false
Citations:
- 1: https://github.com/actions/checkout/blob/v4/action.yml
- 2: https://github.com/actions/checkout/blob/v4.1.1/action.yml
- 3: https://github.com/actions/checkout/blob/v4/README.md
- 4: https://github.com/actions/checkout/blob/main/README.md
- 5: https://github.com/marketplace/actions/checkout
🌐 Web query:
GitHub Actions pull_request GITHUB_TOKEN permissions default read contents
💡 Result:
By default, the GITHUB_TOKEN used in GitHub Actions workflows is granted permissions based on settings configured at the repository, organization, or enterprise level [1]. Administrators can choose between two default permission configurations: 1. Permissive: The GITHUB_TOKEN has read/write access for all scopes [1][2]. 2. Restricted: The GITHUB_TOKEN only has read access for the contents and packages scopes [3][1]. When a workflow is triggered by a pull request from a forked repository, GitHub enforces stricter security measures [4][2]. In this scenario, the GITHUB_TOKEN is automatically downgraded to read-only access for all scopes [1][2], regardless of the default settings, unless specific administrative settings ("Send write tokens to workflows from pull requests") are enabled (which is not recommended for public forks due to security risks) [1][5]. The pull_request_target event is an exception to this automatic downgrade [5][6]. To follow the principle of least privilege, you can explicitly define the permissions required for your workflow or individual jobs using the permissions key in your YAML workflow file [7][2]. Any permissions not explicitly listed in this key are set to none [2]. Example configuration: permissions: contents: read pull-requests: write For more detailed control or cases where the GITHUB_TOKEN lacks sufficient permissions, you may need to use a personal access token (stored as a secret) or a GitHub App installation access token [7].
Citations:
- 1: https://docs.github.com/en/enterprise-server@3.2/actions/reference/authentication-in-a-workflow
- 2: https://github.blog/changelog/2021-04-20-github-actions-control-permissions-for-github_token/
- 3: https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/enabling-features-for-your-repository/managing-github-actions-settings-for-a-repository
- 4: https://docs.github.com/en/actions/concepts/security/github_token
- 5: Clarify Permissions Calculation for Workflow Jobs in Documentation github/docs#33566
- 6: Clarify
How the permissions are calculated for a workflow jobgithub/docs#32398 - 7: https://docs.github.com/en/actions/tutorials/authenticate-with-github_token
Disable persisted checkout credentials in all checkout steps.
actions/checkout persists the workflow token by default, and subsequent steps execute repository-controlled code. Set persist-credentials: false on the three checkout usages; fetch-depth: 0 still supplies all needed history without leaving credentials in local Git config.
🧰 Tools
🪛 zizmor (1.26.1)
[warning] 22-24: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false
(artipacked)
📍 Affects 1 file
.github/workflows/skill-check.yml#L22-L24(this comment).github/workflows/skill-check.yml#L139-L139.github/workflows/skill-check.yml#L160-L160
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/skill-check.yml around lines 22 - 24, Disable persisted
checkout credentials for all three actions/checkout steps in
.github/workflows/skill-check.yml at lines 22-24, 139-139, and 160-160 by adding
persist-credentials: false to each checkout configuration. Keep fetch-depth: 0
unchanged.
Sources: MCP tools, Linters/SAST tools
| - name: Setup Python + pinned deps | ||
| run: | | ||
| python -m pip install --upgrade pip | ||
| python -m pip install anthropic pyyaml | ||
| python -m pip install --require-hashes -r requirements-ci.txt |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== workflow excerpt =="
sed -n '1,90p' .github/workflows/skill-check.yml 2>/dev/null || true
echo
echo "== dependency files =="
git ls-files | rg '(^|/)(requirements.*\.txt|pyproject\.toml|poetry\.lock|Pipfile\.lock|uv\.lock|pip-tools|requirements_ci|requirements-ci\.txt)$' || true
echo
echo "== pip install occurrences =="
rg -n 'pip install|--upgrade pip|--require-hashes|requirements-ci\.txt' .github . -g '!node_modules' -g '!dist' -g '!build' || trueRepository: 2233admin/code-intel-pipeline
Length of output: 4439
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== requirements-ci.txt =="
cat -n requirements-ci.txt
echo
echo "== pip dependency declaration occurrences =="
rg -n '^[ \t]*(pip|pyyaml|yaml|anthropic|pytest)' requirements-ci.txt .github/workflows/skill-check.yml || true
echo
echo "== pip install commands with hashes nearby =="
python3 - <<'PY'
from pathlib import Path
import re
path = Path('requirements-ci.txt')
text = path.read_text()
for i,line in enumerate(text.splitlines(),1):
s=line.strip()
if not s or s.startswith('#'): continue
if re.search(r'(--hash=|hash=)', s):
print(f'{i}: {s}')
PYRepository: 2233admin/code-intel-pipeline
Length of output: 2107
Do not install an unpinned pip version.
python -m pip install --upgrade pip runs before the hash-verified install and is not covered by requirements-ci.txt. Remove this upgrade or pin pip, including its hashes, in the trusted lock file.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/skill-check.yml around lines 48 - 51, Remove the unpinned
`python -m pip install --upgrade pip` command from the “Setup Python + pinned
deps” workflow step, leaving the hash-verified `requirements-ci.txt`
installation unchanged.
| run: | | ||
| python -m pip install --upgrade pip | ||
| python -m pip install anthropic pyyaml | ||
| python -m pip install --require-hashes -r requirements-ci.txt |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== workflow files =="
git ls-files ".github/workflows/*" || true
echo "== skill-check relevant sections =="
wc -l .github/workflows/skill-check.yml
sed -n '1,220p' .github/workflows/skill-check.yml
echo "== requirements manifests =="
for f in requirements-ci.txt requirements.txt pyproject.toml setup.py setup.cfg; do
if [ -f "$f" ]; then echo "--- $f ---"; sed -n '1,220p' "$f"; fi
done
echo "== all pip install with require-hashes and requirements-ci.txt =="
rg -n "pip install --require-hashes -r requirements-ci.txt|pip install --require-hashes|requirements-ci\.txt" .Repository: 2233admin/code-intel-pipeline
Length of output: 9647
Do not install the PR-supplied requirements-ci.txt in skill-check.yml.
Both pip install --require-hashes -r requirements-ci.txt steps load the manifest from the PR branch, so --require-hashes cannot prevent a changed manifest from installing modified dependencies. Install a trusted lock/manifest copy from the base branch or another immutable trusted source at both .github/workflows/skill-check.yml#L51 and .github/workflows/skill-check.yml#L164.
📍 Affects 1 file
.github/workflows/skill-check.yml#L51-L51(this comment).github/workflows/skill-check.yml#L164-L164
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/skill-check.yml at line 51, Replace both
requirements-ci.txt installation steps at .github/workflows/skill-check.yml
lines 51 and 164 with installation from a trusted lock/manifest copied from the
base branch or another immutable trusted source; do not install the PR-supplied
requirements-ci.txt, while retaining hash verification for the trusted
dependency set.
Source: MCP tools
test_pipeline_workflows_checkout_complete_git_history matched the literal string "uses: actions/checkout@v4", which the supply-chain-004 fix in this same branch replaced with a SHA-pinned form (actions/checkout@<sha> # v4.x.y). Match on the stable "uses: actions/checkout@" prefix instead so the test survives either pinning style.
…ing/facades, CI gates that gate, docs drift (#60) * fix(security): disarm scanned-repo git config execution in live sibling scripts Extend run-code-intel.ps1's GitHardening pattern (core.fsmonitor, core.hooksPath, core.sshCommand, diff.external, core.pager pinned empty + GIT_CONFIG_NOSYSTEM=1) to every git invocation in Invoke-ScopedRepowise.ps1 (incl. the worktree-mutating checkout/reset/clean path), Invoke-CodeNexusLite.ps1, OpenSpec-Detector.ps1, and Invoke-SentruxAgentTool.ps1. Booby-trapped fixture in the scoped-repowise security test proves fsmonitor/hooksPath no longer fire. Closes the security-001 residual documented in #33. * fix(rust): correctness and contract fixes in the code-intel core - structured_edit/providers: resolve ast-grep and repowise through tool_path to absolute paths (with .cmd/.bat shim wrapping) instead of bare-name spawns with cwd inside the scanned repo - tool_path: skip relative PATH entries so locate() only returns absolute paths - hardened_git: inject -c core.quotepath=false so non-ASCII paths are emitted verbatim (fixes silent drops in audit scope and sentrux git signals) - change_impact/artifact_ref: replace element-shape unwrap panics with exit-65 contract errors; validate_native_array_artifact now validates element fields - capability_inventory: resolve the workflow-recommendation facade via discover_manifest instead of compile-time CARGO_MANIFEST_DIR - main: add four missing routes to FULL_HELP_TEXT (audit, compatibility retirement-ticket, provider codenexus-adapt, repository survival-scan) - sentrux_gate: require gated metric keys present+numeric in baselines instead of silently comparing against 0.0 - artifact_index: replace remove-then-rename with bak-swap so index.json is recoverable at every instant - audit_report tests: adversarial fixtures now assert the specific rejection reason, not just exit 65 - orchestration records: re-sync toolchain/conformance digest pins to the edited sources * fix(ps1): launcher and wrapper defects on the live path - run-code-intel.ps1: model-routing adoption was dead code (provider normalized to non-empty before the intake guard checked for emptiness) - routed provider/model now win unless the operator explicitly overrode them - update-code-intel-index.ps1: resolve the binary via CODE_INTEL_RUST_CLI, target/release, target/debug, then PATH instead of requiring a debug build that no installer or release ever produces - invoke-code-intel.ps1: load the default pipeline.config.json for the plain -RepoPath shape (artifactRoot was silently dropped); stop rejecting PowerShell common parameters as unsupported options - check-code-intel-tools.ps1: CODE_INTEL_HOME check compared the env var against itself; now checks the directory exists and flags divergence from the default derivation - install-code-intel-pipeline.ps1: pin repowise (==0.36.0) instead of unpinned --upgrade (supply-chain-003 in #35) * fix(ci): make the gates gate - ci.yml: execute the ten previously never-run PS1 suites (incl. the PR #44 model-delegate ai-safety regression suite and the conformance-evidence suites); add the two large PS1 entry points to both parser-check lists; pin choco ripgrep to 14.1.0 in both install sites (supply-chain-002) - parity-observe.yml: provision sentrux + PATH before the parity harness (its only evidenced comparison was structurally vacuous without it); pin actions to commit SHAs and ripgrep to 14.1.0 (restores #43 hardening) - skill-check.yml: link check now actually fails on broken relative links; darwin score gains the missing checkpoint_design scoring branch and drops the unused ANTHROPIC_API_KEY test-run-commit-contract.ps1 is wired continue-on-error: it fails at HEAD with 'facade published an incoherent A07 marker' - pre-existing defect, tracked separately. * docs: align claims with shipped behavior - README: the un-flagged run-code-intel.ps1 default is the legacy scanner (Rust DAG is opt-in via -DagCoordinate), not the inverse; artifact/read-flow sections now distinguish primary-entry DAG outputs from legacy-path artifacts; document the 0.6.0 'code-intel audit' command - follow-up-automation: emission and policy reading happen only on the legacy runner; the compiled primary entry does neither - code-intel-architecture: sentrux Pro activation is SENTRUX_AUTO_PRO=1 opt-in, not automatic - public-beta: document release attestation verification with gh attestation verify (supply-chain-007 in #35) * style: rustfmt the new test and validation code
Summary
Fixes a batch of security/supply-chain findings scoped entirely to the three GitHub Actions workflow files (
ci.yml,release.yml,skill-check.yml), surfaced by this repo's own self-audit.Addresses security-003 from #33 and supply-chain-001/002/004/005/006/007 from #35.
Findings fixed
security-003 (
skill-check.yml, "Detect changed SKILL.md files" step) —${{ github.event_name }}and${{ github.base_ref }}were interpolated directly into a bashrun:block (git diff --name-only origin/${{ github.base_ref }}...HEAD), the canonical GH Actions script-injection shape. Fixed by moving both into the step'senv:block (EVENT_NAME,BASE_REF) and referencing them as quoted shell variables instead.supply-chain-001 (
skill-check.yml) — theanthropicPyPI package was installed at runtime with no pin, in a job that also carriesANTHROPIC_API_KEY, but is never imported (the darwin-skill scoring in this job is heuristic-only; the comment even says LLM scoring is future work) — deleted the install entirely.pyyamlwas also installed unpinned in two places; both now install from a newrequirements-ci.txtpinningpyyaml==6.0.3with--hash=sha256:...for the sdist plus manylinux x86_64 wheels covering cp310-cp314, viapip install --require-hashes -r requirements-ci.txt. One of the pinned wheel hashes was independently re-verified by downloading the exact manylinux cp312 artifact ubuntu-latest would fetch and recomputing its sha256 locally (not just trusting the PyPI JSON API round-trip).supply-chain-002 (
release.yml) —choco install ripgrephad no version pin, in the samecontents:writejob that packages and hashes the release binary. Pinned to--version 14.1.0(current stable).supply-chain-004 (all three workflows) — every third-party action was referenced by mutable major-version tag (
@v4,@v5). Alluses:lines areactions/*first-party, but per the finding's literal ask (first-party only lowers severity, doesn't exempt), all are now pinned to a full 40-hex commit SHA resolved from the GitHub API, with the version kept as a trailing comment:actions/checkout@11d5960a326750d5838078e36cf38b85af677262# v4.4.0actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065# v5.6.0actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02# v4.6.2actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373# v4.1.1 (new, see supply-chain-007)supply-chain-005 (
ci.yml,release.yml) — added--lockedto everycargo check,cargo test -p code-intel(including the benchmark test invocation), andcargo build -p code-intel --releaseinvocation in both files.cargo fmt -p code-intel -- --checkis deliberately left unchanged: verified locally thatcargo fmtnever resolves dependencies (no Cargo.lock read) and rejects--lockedoutright witherror: unexpected argument '--locked' found— adding it there would break the job, not harden it.supply-chain-006 (
skill-check.yml) — this was the only one of the three workflows missing a top-levelpermissions:block. Addedpermissions: contents: readat the workflow level, matching the pattern already used inci.yml/release.yml.supply-chain-007 (
release.yml) — the release zip, its.sha256, and its manifest were published with no build-provenance attestation, andgh release uploadused--clobberfor existing tags, meaning a leakedcontents:writetoken could silently replace already-published assets with no trail. Fixed by:actions/attest-build-provenancestep for the release zip, placed after packaged-bootstrap validation and before the GitHub Release step. Verified this action'sruns.usingchain (composite ->actions/attest,using: node24) is a pure Node.js action and therefore runs onwindows-latest(this job's runner), not just plausible-by-assumption.permissions:gainsid-token: writeandattestations: writealongside the existingcontents: write.--clobberfrom the re-run upload path; a rerun for a tag whose assets already exist now fails loudly instead of silently overwriting, with a comment explaining the intended manual override (gh release delete-asset) if a replacement is genuinely needed.Test plan
python -c "import yaml,sys; yaml.safe_load(open(sys.argv[1]))") — all pass.git diff --statconfirms scope: only the three workflow files + one newrequirements-ci.txt.cargo fmt --lockedempirically confirmed to fail;cargo check --locked/cargo test --locked/cargo build --lockedempirically confirmed to be accepted (ran a realcargo build -p code-intel --lockedto completion, exit 0).pip install --require-hashessemantics confirmed viapip install --help; the pinned pyyaml wheel hash independently reproduced by downloading the real artifact and recomputing sha256.gh api repos/<owner>/<repo>/git/refs/tags/<tag>(not hand-typed/recalled).actions/attest-build-provenance@v4.1.1'saction.yml(and its inneractions/attest@v4.1.1) fetched and read directly to confirmsubject-pathis a valid standalone input and the runtime (node24) is cross-platform, given this step runs onwindows-latest.--lockedadditions and repinned actions don't regress the existing build/test/package pipeline.