Conversation
Bring solana-verify-based verified build infrastructure to this repo, mirroring the scope of mpl-foundation/mpl-hybrid#24 and #25. - Add verify-program.yml workflow that uploads the verified-build PDA and submits a remote verification job for either candy-guard or candy-machine-core on mainnet-beta. - Wire solana-verify into deploy-program.yml: install on mainnet-beta ref deploys, initialize Solana CLI config (works around solana-verify 0.4.15 unconditionally loading ~/.config/solana/cli/config.yml during PDA upload), upload the verified-build PDA, submit a remote verification job, and clean up signer files in `always()`. - Replace `echo \$SECRET > file` with `printf '%s' \"\$SECRET\" > file` via step-level env: prevents shell expansion of key contents. - Rewrite build.sh to drive solana-verify build per program. Each program is its own workspace, so we resolve library/package names from programs/<prog>/program/Cargo.toml, invoke solana-verify from the program's workspace root, and copy <workspace>/target/deploy/<lib>.so into programs/.bin/. - Add verify-from-repo.sh wrapper with --program candy-guard|candy-machine-core defaults and ssh-to-https origin normalization. - Harden workflow env loading: read .env into both \$GITHUB_ENV and step outputs to avoid GitHub Actions parsing quirks with quoted values. - Bump RUST_VERSION to 1.85.0, add IDL_RUST_VERSION=1.75.0 (used by main.yml's IDL generation), bump SOLANA_VERSION to 1.18.26 to match SOLANA_VERIFY_BASE_IMAGE, and add SOLANA_VERIFY_VERSION=0.4.15. - Add `pnpm programs:verify` script.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (1)
Summary by CodeRabbit
WalkthroughAdds solana-verify to program build/deploy workflows, centralizes .github/.env parsing into workflow step outputs, updates toolchain pins and workspace layout, rewrites build scripts to use solana-verify, adds a verify wrapper and npm script, and provides a standalone verify workflow plus deploy-time verification steps. ChangesSolana Program Verification Infrastructure
Sequence DiagramsequenceDiagram
actor User
participant Deploy as Deployment Job
participant Verify as solana-verify
participant RPC as Solana RPC
User->>Deploy: trigger verify or deploy (git_ref)
Deploy->>Deploy: load .github/.env -> step outputs
Deploy->>Verify: solana-verify verify-from-repo (upload PDA)
Verify-->>RPC: upload PDA
Deploy->>Verify: solana-verify remote submit-job
Verify-->>RPC: submit job
Deploy->>Deploy: cleanup signer files
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 8
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
.github/workflows/deploy-program.yml (1)
280-292:⚠️ Potential issue | 🟠 Major | ⚡ Quick winUndefined workflow inputs
publish_crateandbump.This step references two undefined inputs:
github.event.inputs.publish_crate(line 282) — not defined in workflow inputs, so the condition will always be falseinputs.bump(line 289) — not defined in workflow inputs, would pass undefined value tocargo releaseThis step is effectively dead code. Either add the missing inputs to the workflow definition, or remove this step if crate publishing is no longer needed.
🤖 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/deploy-program.yml around lines 280 - 292, The Publish crate step references undefined workflow inputs publish_crate and bump so it never runs or passes an undefined bump to cargo; fix by adding workflow_dispatch inputs named publish_crate (e.g., type: boolean or choice with default 'false') and bump (e.g., choice: patch/minor/major with a sensible default) to the workflow definition, and make the step consistently read those inputs (use github.event.inputs.publish_crate in the if and github.event.inputs.bump for cargo release) so the Publish crate step can be executed with valid values..github/workflows/build-programs.yml (1)
19-28:⚠️ Potential issue | 🟠 Major | ⚡ Quick winUpdate manual-dispatch toolchain defaults to match the new baseline.
workflow_dispatchstill defaults to Rust1.65.0and Solana1.14.13, so manual runs will bypass the new.github/.envversions and build with mismatched toolchains.Suggested patch
rust: description: Rust version - default: 1.65.0 + default: 1.85.0 required: true type: string solana: description: Solana version - default: 1.14.13 + default: 1.18.26 required: true type: string🤖 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/build-programs.yml around lines 19 - 28, The workflow_dispatch inputs still use old defaults; update the "rust" and "solana" input default values in the build-programs.yml workflow to match the new baseline toolchain versions used by the repo (i.e., change the default for the rust input and the solana input under the workflow_dispatch section), ensuring manual runs use the same versions as the baseline..github/workflows/test-js.yml (1)
36-78:⚠️ Potential issue | 🟠 Major | ⚡ Quick winUse SHA-pinned actions in both jobs.
uses: ...@v1/@v4tag references are mutable; pin to commit SHAs to satisfy blanket policy and reduce supply-chain risk.🤖 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/test-js.yml around lines 36 - 78, The workflow uses mutable action tags (e.g., metaplex-foundation/actions/start-validator@v1, metaplex-foundation/actions/install-node-dependencies@v1, actions/checkout@v4, metaplex-foundation/actions/install-node-with-pnpm@v1); update each "uses: <repo>@<tag>" to a specific commit SHA to pin the action versions for both the test and lint jobs: locate the uses entries for start-validator, install-node-dependencies, checkout, and install-node-with-pnpm in the diff and replace their @<tag> with the corresponding full commit SHA (keep the repo names intact) so the workflow references immutable SHAs.
🤖 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/build-programs.yml:
- Around line 55-67: The workflow uses mutable tags for third-party actions
(install-rust@v1, install-solana@v1, and cache-programs@v1); replace each uses:
metaplex-foundation/actions/<action>`@v1` reference with the corresponding
immutable git SHA (full 40-character commit) for that action repository (or a
documented immutable release ref), preserving the existing with: inputs
(toolchain/version/cache) and if conditions; ensure you update install-rust@v1,
install-solana@v1, and cache-programs@v1 to their pinned commit SHAs so the
workflow is deterministic and complies with the pinning policy.
In @.github/workflows/deploy-program.yml:
- Around line 128-132: The workflow references an undefined input
github.event.inputs.publish_crate in the conditional for the "Install
cargo-release" step and the related "Publish crate" step; either add a new
workflow input named publish_crate in the workflow's inputs block (matching the
other inputs' pattern and defaulting to 'false' or appropriate value) so the
condition can evaluate correctly, or remove both the "Install cargo-release"
step and the "Publish crate" step (and any related if: conditions) if crate
publishing is not required; update any references to publish_crate elsewhere to
remain consistent.
- Around line 134-151: The "Set RPC" step currently interpolates secrets
directly inside the shell script using `${{ secrets.* }}`; change it to use
step-level env to supply the secret and then reference that env var in the run
block (keep the existing printf 'RPC=%s\n' "$RPC" >> "$GITHUB_ENV"). Implement
one step per cluster variant (or multiple conditional steps) using the same step
name "Set RPC", with an if: condition on inputs.cluster and an env: block like
RPC: ${{ secrets.DEVNET_RPC }} (or the corresponding secret for
SONIC/ECLIPSE/MAINNET), so the run section only reads $RPC and no longer injects
`${{ secrets.* }}` directly in the shell.
In @.github/workflows/main.yml:
- Around line 87-92: The workflow uses mutable tag refs for third-party actions:
replace metaplex-foundation/actions/install-rust@v1 and
metaplex-foundation/actions/install-solana@v1 with their corresponding pinned
commit SHAs (e.g., metaplex-foundation/actions/install-rust@<sha> and
metaplex-foundation/actions/install-solana@<sha>); locate the two uses of those
action identifiers in the workflow and update them to the exact commit SHAs to
make the workflow immutable and comply with the policy.
In @.github/workflows/test-programs.yml:
- Around line 39-44: The workflow uses mutable action tags
metaplex-foundation/actions/install-rust@v1 and
metaplex-foundation/actions/install-solana@v1; replace both with their immutable
commit SHAs (pin the actions) so the workflow is deterministic—update the two
uses entries (those exact strings install-rust@v1 and install-solana@v1) to the
corresponding SHA@<commit> values from each action repo and commit the change.
In @.github/workflows/verify-program.yml:
- Around line 36-40: The Git checkout step ("Git checkout" using
actions/checkout@v4) currently leaves persisted credentials in local git config;
add the with setting persist-credentials: false to the actions/checkout
invocation so the GITHUB_TOKEN is not written to the repo config and credentials
are only available to the checkout action.
- Line 37: Replace mutable action tags with immutable commit SHAs: find each
"uses:" entry currently using floating tags (e.g., uses: actions/checkout@v4 and
the other two occurrences referenced in the review) and pin them to the full
commit SHA from the corresponding action release, adding a short version comment
for readability (e.g., "# v4.1.1"). Update the three uses entries mentioned in
the comment so all actions are referenced by their full commit SHAs instead of
`@vX` tags.
In `@configs/program-scripts/build.sh`:
- Around line 24-28: Add explicit preflight checks that fail fast if required
commands are missing: before parsing PROGRAMS into PROGRAM_LINES, verify that
both "jq" and "solana-verify" are available (e.g., using command -v or which)
and exit with a clear error message if not; similarly add/ensure a check before
the later usage referenced around line 136 where solana-verify is invoked.
Reference the PROGRAM_LINES/PROGRAMS parsing block and the solana-verify
invocation so the checks are placed immediately prior to those uses and provide
actionable error text like "jq is required" or "solana-verify is required"
before exiting.
---
Outside diff comments:
In @.github/workflows/build-programs.yml:
- Around line 19-28: The workflow_dispatch inputs still use old defaults; update
the "rust" and "solana" input default values in the build-programs.yml workflow
to match the new baseline toolchain versions used by the repo (i.e., change the
default for the rust input and the solana input under the workflow_dispatch
section), ensuring manual runs use the same versions as the baseline.
In @.github/workflows/deploy-program.yml:
- Around line 280-292: The Publish crate step references undefined workflow
inputs publish_crate and bump so it never runs or passes an undefined bump to
cargo; fix by adding workflow_dispatch inputs named publish_crate (e.g., type:
boolean or choice with default 'false') and bump (e.g., choice:
patch/minor/major with a sensible default) to the workflow definition, and make
the step consistently read those inputs (use github.event.inputs.publish_crate
in the if and github.event.inputs.bump for cargo release) so the Publish crate
step can be executed with valid values.
In @.github/workflows/test-js.yml:
- Around line 36-78: The workflow uses mutable action tags (e.g.,
metaplex-foundation/actions/start-validator@v1,
metaplex-foundation/actions/install-node-dependencies@v1, actions/checkout@v4,
metaplex-foundation/actions/install-node-with-pnpm@v1); update each "uses:
<repo>@<tag>" to a specific commit SHA to pin the action versions for both the
test and lint jobs: locate the uses entries for start-validator,
install-node-dependencies, checkout, and install-node-with-pnpm in the diff and
replace their @<tag> with the corresponding full commit SHA (keep the repo names
intact) so the workflow references immutable SHAs.
🪄 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: ASSERTIVE
Plan: Pro
Run ID: 41136b07-1e3a-4f92-b67c-248c45c13b4e
📒 Files selected for processing (10)
.github/.env.github/workflows/build-programs.yml.github/workflows/deploy-program.yml.github/workflows/main.yml.github/workflows/test-js.yml.github/workflows/test-programs.yml.github/workflows/verify-program.ymlconfigs/program-scripts/build.shconfigs/program-scripts/verify-from-repo.shpackage.json
| uses: metaplex-foundation/actions/install-rust@v1 | ||
| with: | ||
| toolchain: ${{ inputs.rust || env.RUST_VERSION }} | ||
| toolchain: ${{ inputs.rust || steps.env_vars.outputs.rust_version }} | ||
|
|
||
| - name: Install Solana | ||
| uses: metaplex-foundation/actions/install-solana@v1 | ||
| with: | ||
| version: ${{ inputs.solana || env.SOLANA_VERSION }} | ||
| version: ${{ inputs.solana || steps.env_vars.outputs.solana_version }} | ||
| cache: ${{ env.CACHE }} | ||
|
|
||
| - name: Cache program dependencies | ||
| if: env.CACHE == 'true' | ||
| uses: metaplex-foundation/actions/cache-programs@v1 |
There was a problem hiding this comment.
Pin action references to immutable SHAs.
These uses: ...@v1 references are mutable tags and violate the repository’s blanket pinning policy; this is a supply-chain hardening gap.
🧰 Tools
🪛 zizmor (1.25.2)
[error] 55-55: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
[error] 60-60: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
[error] 67-67: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
🤖 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/build-programs.yml around lines 55 - 67, The workflow uses
mutable tags for third-party actions (install-rust@v1, install-solana@v1, and
cache-programs@v1); replace each uses: metaplex-foundation/actions/<action>`@v1`
reference with the corresponding immutable git SHA (full 40-character commit)
for that action repository (or a documented immutable release ref), preserving
the existing with: inputs (toolchain/version/cache) and if conditions; ensure
you update install-rust@v1, install-solana@v1, and cache-programs@v1 to their
pinned commit SHAs so the workflow is deterministic and complies with the
pinning policy.
| - name: Install cargo-release | ||
| uses: metaplex-foundation/actions/install-cargo-release@v1 | ||
| if: github.event.inputs.publish_crate == 'true' | ||
| if: github.event.inputs.publish_crate == 'true' && github.event.inputs.dry_run == 'false' | ||
| with: | ||
| cache: ${{ env.CACHE }} |
There was a problem hiding this comment.
Undefined workflow input publish_crate.
The condition references github.event.inputs.publish_crate, but this input is not defined in the workflow's inputs section (lines 4-34). This step will never execute because the condition will always evaluate to false.
Either add the publish_crate input to the workflow, or remove this step and the related "Publish crate" step (line 280) if crate publishing is no longer needed.
🧰 Tools
🪛 actionlint (1.7.12)
[error] 130-130: property "publish_crate" is not defined in object type {cluster: string; dry_run: string; git_ref: string; program: string}
(expression)
🪛 zizmor (1.25.2)
[error] 129-129: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
🤖 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/deploy-program.yml around lines 128 - 132, The workflow
references an undefined input github.event.inputs.publish_crate in the
conditional for the "Install cargo-release" step and the related "Publish crate"
step; either add a new workflow input named publish_crate in the workflow's
inputs block (matching the other inputs' pattern and defaulting to 'false' or
appropriate value) so the condition can evaluate correctly, or remove both the
"Install cargo-release" step and the "Publish crate" step (and any related if:
conditions) if crate publishing is not required; update any references to
publish_crate elsewhere to remain consistent.
| - name: Set RPC | ||
| run: | | ||
| # We do this if waterfall because github actions does not allow dynamic access to secrets | ||
| if [ "${{ inputs.cluster }}" == "devnet" ]; then | ||
| echo RPC=${{ secrets.DEVNET_RPC }} >> $GITHUB_ENV | ||
| printf 'RPC=%s\n' "${{ secrets.DEVNET_RPC }}" >> "$GITHUB_ENV" | ||
| elif [ "${{ inputs.cluster }}" == "mainnet-beta" ]; then | ||
| echo RPC=${{ secrets.MAINNET_RPC }} >> $GITHUB_ENV | ||
| printf 'RPC=%s\n' "${{ secrets.MAINNET_RPC }}" >> "$GITHUB_ENV" | ||
| elif [ "${{ inputs.cluster }}" == "sonic-devnet" ]; then | ||
| echo RPC=${{ secrets.SONIC_DEVNET_RPC }} >> $GITHUB_ENV | ||
| printf 'RPC=%s\n' "${{ secrets.SONIC_DEVNET_RPC }}" >> "$GITHUB_ENV" | ||
| elif [ "${{ inputs.cluster }}" == "sonic-testnet" ]; then | ||
| echo RPC=${{ secrets.SONIC_TESTNET_RPC }} >> $GITHUB_ENV | ||
| printf 'RPC=%s\n' "${{ secrets.SONIC_TESTNET_RPC }}" >> "$GITHUB_ENV" | ||
| elif [ "${{ inputs.cluster }}" == "eclipse-devnet" ]; then | ||
| echo RPC=${{ secrets.ECLIPSE_DEVNET_RPC }} >> $GITHUB_ENV | ||
| printf 'RPC=%s\n' "${{ secrets.ECLIPSE_DEVNET_RPC }}" >> "$GITHUB_ENV" | ||
| elif [ "${{ inputs.cluster }}" == "eclipse-testnet" ]; then | ||
| echo RPC=${{ secrets.ECLIPSE_TESTNET_RPC }} >> $GITHUB_ENV | ||
| printf 'RPC=%s\n' "${{ secrets.ECLIPSE_TESTNET_RPC }}" >> "$GITHUB_ENV" | ||
| elif [ "${{ inputs.cluster }}" == "eclipse-mainnet" ]; then | ||
| echo RPC=${{ secrets.ECLIPSE_MAINNET_RPC }} >> $GITHUB_ENV | ||
| printf 'RPC=%s\n' "${{ secrets.ECLIPSE_MAINNET_RPC }}" >> "$GITHUB_ENV" | ||
| fi |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial | ⚖️ Poor tradeoff
Consider using step-level env: for secrets (consistency improvement).
The secrets are interpolated directly in the shell script via ${{ secrets.* }}. While this works and the risk is lower since these are controlled secrets (not user input), the "Identify Program" step (lines 153-160) demonstrates a more secure pattern using step-level env: block.
This would also silence the zizmor template-injection warnings.
♻️ Example using step-level env
- name: Set RPC
+ env:
+ DEVNET_RPC: ${{ secrets.DEVNET_RPC }}
+ MAINNET_RPC: ${{ secrets.MAINNET_RPC }}
+ # ... other RPC secrets
run: |
if [ "${{ inputs.cluster }}" == "devnet" ]; then
- printf 'RPC=%s\n' "${{ secrets.DEVNET_RPC }}" >> "$GITHUB_ENV"
+ printf 'RPC=%s\n' "$DEVNET_RPC" >> "$GITHUB_ENV"
# ... etc🧰 Tools
🪛 zizmor (1.25.2)
[warning] 137-137: code injection via template expansion (template-injection): may expand into attacker-controllable code
(template-injection)
[warning] 138-138: code injection via template expansion (template-injection): may expand into attacker-controllable code
(template-injection)
[warning] 139-139: code injection via template expansion (template-injection): may expand into attacker-controllable code
(template-injection)
[warning] 140-140: code injection via template expansion (template-injection): may expand into attacker-controllable code
(template-injection)
[warning] 141-141: code injection via template expansion (template-injection): may expand into attacker-controllable code
(template-injection)
[warning] 142-142: code injection via template expansion (template-injection): may expand into attacker-controllable code
(template-injection)
[warning] 143-143: code injection via template expansion (template-injection): may expand into attacker-controllable code
(template-injection)
[warning] 144-144: code injection via template expansion (template-injection): may expand into attacker-controllable code
(template-injection)
[warning] 145-145: code injection via template expansion (template-injection): may expand into attacker-controllable code
(template-injection)
[warning] 146-146: code injection via template expansion (template-injection): may expand into attacker-controllable code
(template-injection)
[warning] 147-147: code injection via template expansion (template-injection): may expand into attacker-controllable code
(template-injection)
[warning] 148-148: code injection via template expansion (template-injection): may expand into attacker-controllable code
(template-injection)
[warning] 149-149: code injection via template expansion (template-injection): may expand into attacker-controllable code
(template-injection)
[warning] 150-150: code injection via template expansion (template-injection): may expand into attacker-controllable code
(template-injection)
🤖 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/deploy-program.yml around lines 134 - 151, The "Set RPC"
step currently interpolates secrets directly inside the shell script using `${{
secrets.* }}`; change it to use step-level env to supply the secret and then
reference that env var in the run block (keep the existing printf 'RPC=%s\n'
"$RPC" >> "$GITHUB_ENV"). Implement one step per cluster variant (or multiple
conditional steps) using the same step name "Set RPC", with an if: condition on
inputs.cluster and an env: block like RPC: ${{ secrets.DEVNET_RPC }} (or the
corresponding secret for SONIC/ECLIPSE/MAINNET), so the run section only reads
$RPC and no longer injects `${{ secrets.* }}` directly in the shell.
| uses: metaplex-foundation/actions/install-rust@v1 | ||
| with: | ||
| toolchain: ${{ env.RUST_VERSION }} | ||
| toolchain: ${{ steps.env_vars.outputs.idl_rust_version }} | ||
|
|
||
| - name: Install Solana | ||
| uses: metaplex-foundation/actions/install-solana@v1 |
There was a problem hiding this comment.
Pin reusable actions by commit SHA.
metaplex-foundation/actions/install-rust@v1 and .../install-solana@v1 are tag-based and mutable; pinning is required by the blanket policy.
🧰 Tools
🪛 zizmor (1.25.2)
[error] 87-87: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
[error] 92-92: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
🤖 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/main.yml around lines 87 - 92, The workflow uses mutable
tag refs for third-party actions: replace
metaplex-foundation/actions/install-rust@v1 and
metaplex-foundation/actions/install-solana@v1 with their corresponding pinned
commit SHAs (e.g., metaplex-foundation/actions/install-rust@<sha> and
metaplex-foundation/actions/install-solana@<sha>); locate the two uses of those
action identifiers in the workflow and update them to the exact commit SHAs to
make the workflow immutable and comply with the policy.
| uses: metaplex-foundation/actions/install-rust@v1 | ||
| with: | ||
| toolchain: ${{ env.RUST_VERSION }} | ||
| toolchain: ${{ steps.env_vars.outputs.rust_version }} | ||
|
|
||
| - name: Install Solana | ||
| uses: metaplex-foundation/actions/install-solana@v1 |
There was a problem hiding this comment.
Pin action references to immutable SHAs.
These @v1 action tags are mutable and do not meet the repository’s required pinning posture.
🧰 Tools
🪛 zizmor (1.25.2)
[error] 39-39: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
[error] 44-44: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
🤖 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/test-programs.yml around lines 39 - 44, The workflow uses
mutable action tags metaplex-foundation/actions/install-rust@v1 and
metaplex-foundation/actions/install-solana@v1; replace both with their immutable
commit SHAs (pin the actions) so the workflow is deterministic—update the two
uses entries (those exact strings install-rust@v1 and install-solana@v1) to the
corresponding SHA@<commit> values from each action repo and commit the change.
| contents: read | ||
| steps: | ||
| - name: Git checkout | ||
| uses: actions/checkout@v4 |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n .github/workflows/verify-program.ymlRepository: metaplex-foundation/mpl-core-candy-machine
Length of output: 6224
🌐 Web query:
GitHub Actions security best practice pinning commit SHA supply chain
💡 Result:
Pinning GitHub Actions to a full-length commit SHA is a critical security best practice for protecting against supply chain attacks [1][2][3]. Why SHA Pinning is Essential: GitHub Actions referenced by tags (e.g., @v4) or branches (e.g., @main) are mutable, meaning a repository maintainer or an attacker who compromises an account can silently redirect those references to different, malicious code without changing the visible version name in your workflow [2][4][5]. Pinning to an immutable, full-length commit SHA (e.g., @abc123def...) ensures that your workflow always executes the exact code you have audited and validated [1][6][7]. Best Practice Implementation: 1. Pin to full SHAs: Always use the full commit SHA, not a shortened version, to maximize integrity [3][6]. 2. Use descriptive comments: Since commit SHAs are not human-readable, append the original version tag as a comment in your workflow file to maintain readability and simplify maintenance [8][7]: uses: actions/checkout@b4ffde6 # v4.1.1 3. Automate Maintenance: Because pinned SHAs do not automatically receive updates, use tools such as Dependabot, Renovate, or specialized CLI tools like 'ratchet' or 'pinact' to automate the process of keeping your pinned actions current and secure [9][2][10]. 4. Enforce via Policy: GitHub allows organization and repository administrators to enforce SHA pinning through "Allowed actions and reusable workflows" policies [11][7]. When enabled, any workflow attempting to use a non-pinned action will fail, effectively mandating secure practices across the organization [5][7]. Considerations: While SHA pinning provides the highest level of security, it introduces a maintenance burden as updates must be managed explicitly [2][10]. Maintainers of GitHub Actions should also ensure that any internal references to other actions are pinned to SHAs to prevent breaking downstream users who have enabled strict organizational pinning policies [5]. Additionally, consider vetting third-party actions by checking for active maintenance, a 'Verified creator' badge, and minimal transitive dependencies before adopting them [2][6].
Citations:
- 1: https://www.stepsecurity.io/blog/pinning-github-actions-for-enhanced-security-a-complete-guide
- 2: https://www.aikido.dev/blog/checklist-github-actions
- 3: https://dev.to/nkuziev-sec/why-fixed-container-image-versions-matter-lessons-from-the-trivy-supply-chain-attack-29ke
- 4: https://gbhackers.com/compromised-github-action/
- 5: https://www.romainlespinasse.dev/posts/github-actions-commit-sha-pinning/
- 6: https://docs.github.com/en/actions/reference/security/secure-use
- 7: https://dev.to/ciberconscientes/github-actions-why-you-should-pin-your-actions-to-a-specific-version-1513
- 8: Pin GitHub Actions to commit SHAs in release workflow github/gh-aw#10021
- 9: Pin all GitHub Actions to immutable SHA commits for supply chain security github/gh-aw#5691
- 10: https://dev.to/jonasbn/pinning-github-actions-and-using-ratchet-3lmc
- 11: https://github.blog/changelog/2025-08-15-github-actions-policy-now-supports-blocking-and-sha-pinning-actions/
Pin all uses: actions to immutable commit SHAs.
Floating tags (@v4, @v1) are mutable and allow attackers to redirect workflows to malicious code. Pin each action to its full commit SHA from the release, with a version comment for readability. For example:
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
Automate maintenance with Dependabot or similar tools. This applies to lines 37, 56, and 61.
🧰 Tools
🪛 zizmor (1.25.2)
[error] 37-37: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
🤖 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/verify-program.yml at line 37, Replace mutable action tags
with immutable commit SHAs: find each "uses:" entry currently using floating
tags (e.g., uses: actions/checkout@v4 and the other two occurrences referenced
in the review) and pin them to the full commit SHA from the corresponding action
release, adding a short version comment for readability (e.g., "# v4.1.1").
Update the three uses entries mentioned in the comment so all actions are
referenced by their full commit SHAs instead of `@vX` tags.
| # parse the JSON array into a bash array | ||
| PROGRAM_LINES="$( | ||
| printf '%s\n' "${PROGRAMS}" | | ||
| jq -cer 'if type == "array" and length > 0 then .[] else error("PROGRAMS must be a non-empty JSON array") end' | ||
| )" |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial | ⚡ Quick win
Add explicit dependency preflight checks for better failure mode.
The script assumes jq and solana-verify exist; failing fast with a clear message avoids opaque CI/local errors.
Suggested patch
set -euo pipefail
SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)
OUTPUT="./programs/.bin"
+
+command -v jq >/dev/null 2>&1 || { echo "error: jq is required" >&2; exit 1; }
+command -v solana-verify >/dev/null 2>&1 || { echo "error: solana-verify is required" >&2; exit 1; }Also applies to: 136-136
🤖 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 `@configs/program-scripts/build.sh` around lines 24 - 28, Add explicit
preflight checks that fail fast if required commands are missing: before parsing
PROGRAMS into PROGRAM_LINES, verify that both "jq" and "solana-verify" are
available (e.g., using command -v or which) and exit with a clear error message
if not; similarly add/ensure a check before the later usage referenced around
line 136 where solana-verify is invoked. Reference the PROGRAM_LINES/PROGRAMS
parsing block and the solana-verify invocation so the checks are placed
immediately prior to those uses and provide actionable error text like "jq is
required" or "solana-verify is required" before exiting.
Fix the verified-build failure on candy-guard caused by its path
dependency on candy-machine-core. solana-verify mounts only one
program subdirectory inside Docker, so `../../candy-machine-core/program`
escaped the mount and `cargo metadata` failed:
failed to read /candy-machine-core/program/Cargo.toml
Replace the two per-program workspaces with a single workspace at
programs/Cargo.toml so candy-guard's path dependency stays inside the
mounted directory. Mount programs/ instead of programs/<program> in the
deploy and verify workflows.
- Add programs/Cargo.toml workspace with members candy-machine-core/program,
candy-guard/program, and candy-guard/macro.
- Delete the per-program workspace Cargo.toml/Cargo.lock files (the
candy-guard lockfile already pinned the full transitive closure, so
promoting it to the workspace root keeps the same version graph and
works under both RUST_VERSION=1.85.0 and IDL_RUST_VERSION=1.75.0).
- Update build.sh to invoke `solana-verify build` from programs/ and copy
programs/target/deploy/<lib>.so into programs/.bin/.
- Update verify-from-repo.sh default --mount-path to programs.
- Update deploy-program.yml --mount-path to programs and drop the no
longer needed PROGRAM_DIR env var.
- Rewrite verify-program.yml to verify both candy-guard and
candy-machine-core by default via a matrix. A new `program` choice
input (`all` | `candy-guard` | `candy-machine-core`, default `all`)
lets operators re-verify a single program when needed.
- Update test-programs.yml fmt/clippy invocations to use
--manifest-path ./programs/Cargo.toml with -p mpl-core-<program>.
Local verification: `cargo check --workspace --tests --locked` succeeds
against programs/, and `cargo fmt`/`clippy` with the new -p flags pass
for both packages.
Replace the matrix-based verify workflow with the same shape as deploy-program.yml so both workflows handle the two programs in this repo the same way. A single verify run targets one program, just like a deploy run targets one program. To verify both, dispatch the workflow once per program (the deploy workflow already auto-submits verification on the mainnet-beta path, so manual verify runs are usually for re-verification of a specific program after a previous failure). - Replace the `program` choice (`all`/`candy-guard`/`candy-machine-core`) with `candy-guard`/`candy-machine-core`, matching deploy's options. - Replace the prepare_matrix job and matrix strategy with a `check_tag` job copied from deploy: parses `release/<program>@x.y.z` tags to infer the program, falls back to the `program` input for ref-style git_refs. - Drop the matrix strategy on `verify_program` and read the program from `needs.check_tag.outputs.program`. - Default `git_ref` to `release/candy-guard@0.2.2` and align input descriptions with deploy-program.yml.
|
@blockiosaurus need the same force merge here, gen clients doesn't work bcuz anchor issue |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 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/test-programs.yml:
- Around line 57-67: The workflow uses the archived actions-rs/cargo@v1 steps
for formatting and linting (the steps with command: fmt and command: clippy),
which uses deprecated runtimes; replace each of those two steps with plain shell
runs invoking cargo directly — i.e., remove the uses: actions-rs/cargo@v1 blocks
and replace the fmt step with a run: cargo fmt --manifest-path
./programs/Cargo.toml -p mpl-core-${{ matrix.program }} -- --check and replace
the clippy step with a run: cargo clippy --all-targets --all-features --no-deps
--manifest-path ./programs/Cargo.toml -p mpl-core-${{ matrix.program }} so the
workflow no longer depends on the archived action.
In @.github/workflows/verify-program.yml:
- Around line 30-35: The check_tag job currently runs with default GitHub
permissions; add an explicit empty permissions block to restrict access by
inserting permissions: {} under the check_tag job (the job named "check_tag"
that defines outputs program and type and uses steps.set_program) so the job
runs with no GitHub API permissions.
- Around line 30-52: The Check tag step currently interpolates inputs.git_ref
directly into bash conditionals (in the set_program step), creating a
template-injection risk; change the step to assign inputs.git_ref (and
inputs.program if used) to environment variables (e.g., GIT_REF and
INPUT_PROGRAM) via the GitHub Actions env mapping and then reference those env
vars inside the bash run (use [[ "$GIT_REF" =~ ... ]] and "$INPUT_PROGRAM"
comparisons), ensure all variable expansions are quoted, and continue writing
outputs to GITHUB_OUTPUT as before; update the set_program step id (set_program)
logic to use the env variables instead of ${ { inputs.git_ref } } inline to
eliminate direct template expansion in the shell.
🪄 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: ASSERTIVE
Plan: Pro
Run ID: a867ee44-8236-4401-83f3-f8144fc36662
⛔ Files ignored due to path filters (2)
programs/Cargo.lockis excluded by!**/*.lockprograms/candy-machine-core/Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (8)
.github/workflows/deploy-program.yml.github/workflows/test-programs.yml.github/workflows/verify-program.ymlconfigs/program-scripts/build.shconfigs/program-scripts/verify-from-repo.shprograms/Cargo.tomlprograms/candy-guard/Cargo.tomlprograms/candy-machine-core/Cargo.toml
💤 Files with no reviewable changes (2)
- programs/candy-machine-core/Cargo.toml
- programs/candy-guard/Cargo.toml
| uses: actions-rs/cargo@v1 | ||
| with: | ||
| command: fmt | ||
| args: --manifest-path ./programs/${{ matrix.program }}/Cargo.toml --all -- --check | ||
| args: --manifest-path ./programs/Cargo.toml -p mpl-core-${{ matrix.program }} -- --check | ||
|
|
||
| - name: Run cargo clippy | ||
| uses: actions-rs/cargo@v1 | ||
| with: | ||
| command: clippy | ||
| args: --all-targets --all-features --no-deps --manifest-path ./programs/${{ matrix.program }}/Cargo.toml | ||
| args: --all-targets --all-features --no-deps --manifest-path ./programs/Cargo.toml -p mpl-core-${{ matrix.program }} | ||
|
|
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Inspecting action metadata..."
curl -fsSL https://raw.githubusercontent.com/actions-rs/cargo/v1/action.yml | sed -n '1,120p'
echo
echo "Inspecting repository status..."
curl -fsSL https://api.github.com/repos/actions-rs/cargo | jq '{archived, disabled, pushed_at, updated_at}'Repository: metaplex-foundation/mpl-core-candy-machine
Length of output: 803
🏁 Script executed:
cd /tmp && git clone --depth 1 https://github.com/metaplex-foundation/mpl-core-candy-machine.git repo && cd repoRepository: metaplex-foundation/mpl-core-candy-machine
Length of output: 171
🏁 Script executed:
# Read the workflow file to verify the snippet and check for other instances
cat -n .github/workflows/test-programs.yml | sed -n '50,75p'Repository: metaplex-foundation/mpl-core-candy-machine
Length of output: 1060
🏁 Script executed:
# Check for all uses of actions-rs/cargo in the workflow
rg "actions-rs/cargo" .github/workflows/test-programs.ymlRepository: metaplex-foundation/mpl-core-candy-machine
Length of output: 154
Replace archived actions-rs/cargo@v1 with direct cargo invocations.
This action is archived and uses the deprecated node12 runtime, which can cause job failures. Switching these two steps (lines 57–60 and 62–66) to plain run: cargo ... removes this dependency risk.
Proposed patch
- - name: Run cargo fmt
- uses: actions-rs/cargo@v1
- with:
- command: fmt
- args: --manifest-path ./programs/Cargo.toml -p mpl-core-${{ matrix.program }} -- --check
+ - name: Run cargo fmt
+ run: cargo fmt --manifest-path ./programs/Cargo.toml -p mpl-core-${{ matrix.program }} -- --check
- - name: Run cargo clippy
- uses: actions-rs/cargo@v1
- with:
- command: clippy
- args: --all-targets --all-features --no-deps --manifest-path ./programs/Cargo.toml -p mpl-core-${{ matrix.program }}
+ - name: Run cargo clippy
+ run: cargo clippy --all-targets --all-features --no-deps --manifest-path ./programs/Cargo.toml -p mpl-core-${{ matrix.program }}🧰 Tools
🪛 actionlint (1.7.12)
[error] 57-57: the runner of "actions-rs/cargo@v1" action is too old to run on GitHub Actions. update the action's version to fix this issue
(action)
[error] 63-63: the runner of "actions-rs/cargo@v1" action is too old to run on GitHub Actions. update the action's version to fix this issue
(action)
🪛 zizmor (1.25.2)
[error] 57-57: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
[error] 63-63: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
[warning] 57-57: action or reusable workflow from archived repository (archived-uses): repository is archived
(archived-uses)
[warning] 63-63: action or reusable workflow from archived repository (archived-uses): repository is archived
(archived-uses)
🤖 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/test-programs.yml around lines 57 - 67, The workflow uses
the archived actions-rs/cargo@v1 steps for formatting and linting (the steps
with command: fmt and command: clippy), which uses deprecated runtimes; replace
each of those two steps with plain shell runs invoking cargo directly — i.e.,
remove the uses: actions-rs/cargo@v1 blocks and replace the fmt step with a run:
cargo fmt --manifest-path ./programs/Cargo.toml -p mpl-core-${{ matrix.program
}} -- --check and replace the clippy step with a run: cargo clippy --all-targets
--all-features --no-deps --manifest-path ./programs/Cargo.toml -p mpl-core-${{
matrix.program }} so the workflow no longer depends on the archived action.
Three defense-in-depth fixes that matter most if a maintainer's
workflow_dispatch credentials or a third-party action in this job
were ever compromised.
- Pipe `inputs.git_ref` and `inputs.program` through step `env:` and
reference them as `$GIT_REF`/`$INPUT_PROGRAM` instead of letting
GitHub Actions expand `${{ ... }}` directly into the shell. Without
this, a caller could craft a dispatch input containing shell
metacharacters and execute arbitrary code inside `check_tag`.
- Add `permissions: {}` to `check_tag` so the job runs with no
GitHub API capabilities at all. The job only reads workflow inputs
and writes to GITHUB_OUTPUT, so it does not need the default
`contents: read` (or any write scope on push events).
- Set `persist-credentials: false` on the `actions/checkout@v4` step
in `verify_program`. The workflow never runs git push or fetch
after checkout, so leaving GITHUB_TOKEN in .git/config only
widens the blast radius for any compromised step in the same job.
Action SHA pinning (and the same hardening on other workflows like
deploy-program.yml) intentionally deferred to a repo-wide follow-up
PR so this PR stays scoped to verified-build infrastructure.
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
.github/workflows/verify-program.yml (2)
106-113: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick winUse step-level env for
inputs.clusterto match security hardening pattern.For consistency with the template injection mitigation applied to
check_tag, pipeinputs.clusterthrough a step env variable. While currently constrained to a single choice, this guards against future expansion and API dispatch edge cases.🔒 Proposed fix
- name: Set RPC + env: + CLUSTER: ${{ inputs.cluster }} run: | - if [ "${{ inputs.cluster }}" == "mainnet-beta" ]; then + if [ "${CLUSTER}" == "mainnet-beta" ]; then printf 'RPC=%s\n' "${{ secrets.MAINNET_RPC }}" >> "$GITHUB_ENV" else - echo "Invalid cluster: ${{ inputs.cluster }}" + echo "Invalid cluster: ${CLUSTER}" exit 1 fi🤖 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/verify-program.yml around lines 106 - 113, Update the "Set RPC" step to read the workflow input via a step-level environment variable instead of directly expanding ${{ inputs.cluster }}; create an env entry (e.g., CLUSTER: ${{ inputs.cluster }}) for the step and reference that env var (e.g., $CLUSTER) in the shell condition and messages so the step uses the step-level env for inputs.cluster, keeping the existing logic that sets RPC from secrets.MAINNET_RPC or exits with an error.
115-132: 🧹 Nitpick | 🔵 Trivial | 💤 Low valueConsider piping job output through env for defense-in-depth.
needs.check_tag.outputs.programis directly interpolated in the shell conditional. While constrained by the upstream job's validation, using a step env variable would maintain consistency with the hardening pattern applied elsewhere.🛡️ Proposed fix
- name: Identify Program env: + PROGRAM: ${{ needs.check_tag.outputs.program }} CORE_CANDY_GUARD_ID: ${{ secrets.CORE_CANDY_GUARD_ID }} CORE_CANDY_GUARD_DEPLOY_KEY: ${{ secrets.CORE_CANDY_GUARD_DEPLOY_KEY }} CORE_CANDY_MACHINE_CORE_ID: ${{ secrets.CORE_CANDY_MACHINE_CORE_ID }} CORE_CANDY_MACHINE_CORE_DEPLOY_KEY: ${{ secrets.CORE_CANDY_MACHINE_CORE_DEPLOY_KEY }} run: | - if [ "${{ needs.check_tag.outputs.program }}" == "candy-guard" ]; then + if [ "${PROGRAM}" == "candy-guard" ]; then printf 'PROGRAM_NAME=%s\n' "mpl_core_candy_guard" >> "$GITHUB_ENV"🤖 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/verify-program.yml around lines 115 - 132, Assign needs.check_tag.outputs.program to a step environment variable and use that env var in the shell conditional instead of interpolating the output directly; specifically, export a PROGRAM (or PROGRAM_TAG) env from the step (so the conditional reads "$PROGRAM") and then keep the existing branches that set PROGRAM_NAME, PROGRAM_PACKAGE and write program-id.json / deploy-key.json (refer to needs.check_tag.outputs.program, PROGRAM_NAME, PROGRAM_PACKAGE, program-id.json, deploy-key.json to locate the logic). Ensure the conditional uses the quoted env var ("$PROGRAM") for comparison to avoid word-splitting and preserve the existing printf >> "$GITHUB_ENV" behavior.
🤖 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/verify-program.yml:
- Around line 54-56: The branch that sets outputs program and type incorrectly
checks GIT_REF against program names (GIT_REF) so non-release commit hashes
never match; update the condition to test INPUT_PROGRAM (e.g., check [[ -n
"${INPUT_PROGRAM}" && ( "${INPUT_PROGRAM}" == "candy-machine-core" ||
"${INPUT_PROGRAM}" == "candy-guard" ) ]]) or otherwise validate INPUT_PROGRAM
before emitting echo program="${INPUT_PROGRAM}" and echo type="ref" so the
branch triggers for non-release verification using INPUT_PROGRAM instead of
comparing GIT_REF.
---
Outside diff comments:
In @.github/workflows/verify-program.yml:
- Around line 106-113: Update the "Set RPC" step to read the workflow input via
a step-level environment variable instead of directly expanding ${{
inputs.cluster }}; create an env entry (e.g., CLUSTER: ${{ inputs.cluster }})
for the step and reference that env var (e.g., $CLUSTER) in the shell condition
and messages so the step uses the step-level env for inputs.cluster, keeping the
existing logic that sets RPC from secrets.MAINNET_RPC or exits with an error.
- Around line 115-132: Assign needs.check_tag.outputs.program to a step
environment variable and use that env var in the shell conditional instead of
interpolating the output directly; specifically, export a PROGRAM (or
PROGRAM_TAG) env from the step (so the conditional reads "$PROGRAM") and then
keep the existing branches that set PROGRAM_NAME, PROGRAM_PACKAGE and write
program-id.json / deploy-key.json (refer to needs.check_tag.outputs.program,
PROGRAM_NAME, PROGRAM_PACKAGE, program-id.json, deploy-key.json to locate the
logic). Ensure the conditional uses the quoted env var ("$PROGRAM") for
comparison to avoid word-splitting and preserve the existing printf >>
"$GITHUB_ENV" behavior.
🪄 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: ASSERTIVE
Plan: Pro
Run ID: 03491f86-7ce4-4341-b163-584dd8905b3d
📒 Files selected for processing (1)
.github/workflows/verify-program.yml
The third branch in check_tag previously gated on GIT_REF equaling the literal strings "candy-guard" or "candy-machine-core". Any other non-release git_ref (commit hash, branch name, arbitrary tag) fell through to the else and failed with "Non-release tag and program not specified", even when the dispatch supplied `program`. That made non-release verification effectively impossible — you could only re-verify what was deployed from a release tag, which is the opposite of when re-verification is most useful. Gate the branch on INPUT_PROGRAM instead so any git_ref works as long as the dispatch specifies which program to verify. The choice input already restricts INPUT_PROGRAM to "candy-guard" or "candy-machine-core", so the inner equality check is belt-and-braces. This intentionally diverges from deploy-program.yml's check_tag, which has the same unreachable branch. Verify's use case (re-verify a specific commit) needs the more permissive form; deploy's pattern is preserved for the rest of the workflow shape.
Bring solana-verify-based verified build infrastructure to this repo, mirroring the scope of mpl-foundation/mpl-hybrid#24 and #25.
always().echo \$SECRET > filewithprintf '%s' \"\$SECRET\" > filevia step-level env: prevents shell expansion of key contents.pnpm programs:verifyscript.