From 9da946906088673ffa1da3c55c5d2f4bb8d30a92 Mon Sep 17 00:00:00 2001 From: Sammy Harris <41593264+stegaBOB@users.noreply.github.com> Date: Thu, 14 May 2026 15:37:09 -0500 Subject: [PATCH 1/4] feat: pass --base-image override through build-verified and verify-build MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds an optional 'base-image' input to both actions, forwarded to solana-verify's --base-image flag in three call sites: - build-verified → solana-verify build - verify-build → solana-verify verify-from-repo (devnet path) - verify-build → solana-verify export-pda-tx (Squads path) Consumers that don't set the input keep the existing behavior (solana-verify falls back to [workspace.metadata.cli] solana in Cargo.toml). Consumers that DO set it can drive both the local verified build and the on-chain verify-PDA from a single value (e.g. Anchor.toml [toolchain] solana_version), avoiding the two-source-of-truth drift between Cargo.toml and Anchor.toml. Must be passed identically to both actions — if only one side gets the override, OtterSec re-builds with the PDA's image, which won't match the .so produced locally, and verification fails. --- build-verified/action.yaml | 11 ++++++++++- verify-build/action.yaml | 15 +++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/build-verified/action.yaml b/build-verified/action.yaml index 2c5c1db..7bc2c16 100644 --- a/build-verified/action.yaml +++ b/build-verified/action.yaml @@ -8,6 +8,11 @@ inputs: description: "Features to enable during build" required: false default: "" + base-image: + description: "Override solana-verify's --base-image. Pass the same value + to verify-build to keep the verify-PDA aligned." + required: false + default: "" runs: using: "composite" @@ -25,5 +30,9 @@ runs: if [ ! -z "$FEATURES" ]; then FEATURES="--features $FEATURES" fi + BASE_IMAGE_ARG="" + if [ -n "${{ inputs.base-image }}" ]; then + BASE_IMAGE_ARG="--base-image ${{ inputs.base-image }}" + fi # Run solana-verify directly without Docker - ~/.cargo/bin/solana-verify build --library-name ${{ inputs.program }} -- $FEATURES + ~/.cargo/bin/solana-verify build $BASE_IMAGE_ARG --library-name ${{ inputs.program }} -- $FEATURES diff --git a/verify-build/action.yaml b/verify-build/action.yaml index 53e2841..14ef818 100644 --- a/verify-build/action.yaml +++ b/verify-build/action.yaml @@ -40,6 +40,11 @@ inputs: vault-address: description: "The Squads vault address (required if use-squads is true)" required: false + base-image: + description: "Override solana-verify's --base-image. Must match + build-verified to keep the verify-PDA aligned with the built .so." + required: false + default: "" runs: using: "composite" @@ -85,6 +90,10 @@ runs: ARGS="$ARGS --remote" fi + if [ -n "${{ inputs.base-image }}" ]; then + ARGS="$ARGS --base-image ${{ inputs.base-image }}" + fi + solana-verify verify-from-repo \ --program-id ${{ inputs.program-id }} \ --url ${{ inputs.rpc-url }} \ @@ -100,6 +109,11 @@ runs: run: | echo "=== Exporting PDA Transaction ===" + BASE_IMAGE_ARG="" + if [ -n "${{ inputs.base-image }}" ]; then + BASE_IMAGE_ARG="--base-image ${{ inputs.base-image }}" + fi + # Get the last line which should be the base64 transaction TX=$(solana-verify export-pda-tx \ ${{ inputs.repo-url }} \ @@ -108,6 +122,7 @@ runs: --commit-hash ${{ inputs.commit-hash }} \ --url ${{ inputs.rpc-url }} \ --library-name ${{ inputs.program }} \ + $BASE_IMAGE_ARG \ --encoding base64 | tail -n 1) # Verify we got a base64 string From 5196ea2c3b4fe36bf447d1a67b78a7a4d7cf1620 Mon Sep 17 00:00:00 2001 From: Sammy Harris <41593264+stegaBOB@users.noreply.github.com> Date: Fri, 15 May 2026 11:03:41 -0500 Subject: [PATCH 2/4] feat(verify-build): pass --features through to verify-from-repo and export-pda-tx MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mirrors build-verified's features input. Forwarded as trailing '-- --features ' so the verify-PDA's 'args' field records the cargo args used — OtterSec then replays the build with the same features and the hash matches. Without this, build-verified with --features X produces a .so whose hash won't match what a verifier rebuilds without features: silent failure on any release that toggles a feature flag. --- verify-build/action.yaml | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/verify-build/action.yaml b/verify-build/action.yaml index 14ef818..80f6d2e 100644 --- a/verify-build/action.yaml +++ b/verify-build/action.yaml @@ -45,6 +45,11 @@ inputs: build-verified to keep the verify-PDA aligned with the built .so." required: false default: "" + features: + description: "Comma-separated cargo features. Must match build-verified + to keep the verify-PDA aligned with the built .so." + required: false + default: "" runs: using: "composite" @@ -94,13 +99,19 @@ runs: ARGS="$ARGS --base-image ${{ inputs.base-image }}" fi + CARGO_ARGS="" + if [ -n "${{ inputs.features }}" ]; then + CARGO_ARGS="-- --features ${{ inputs.features }}" + fi + solana-verify verify-from-repo \ --program-id ${{ inputs.program-id }} \ --url ${{ inputs.rpc-url }} \ --library-name ${{ inputs.program }} \ -k ./deploy-keypair.json \ $ARGS \ - ${{ inputs.repo-url }} + ${{ inputs.repo-url }} \ + $CARGO_ARGS - name: Export PDA Transaction id: export-pda @@ -114,6 +125,11 @@ runs: BASE_IMAGE_ARG="--base-image ${{ inputs.base-image }}" fi + CARGO_ARGS="" + if [ -n "${{ inputs.features }}" ]; then + CARGO_ARGS="-- --features ${{ inputs.features }}" + fi + # Get the last line which should be the base64 transaction TX=$(solana-verify export-pda-tx \ ${{ inputs.repo-url }} \ @@ -123,7 +139,8 @@ runs: --url ${{ inputs.rpc-url }} \ --library-name ${{ inputs.program }} \ $BASE_IMAGE_ARG \ - --encoding base64 | tail -n 1) + --encoding base64 \ + $CARGO_ARGS | tail -n 1) # Verify we got a base64 string if [[ ! "$TX" =~ ^[A-Za-z0-9+/=]*$ ]]; then From 7d6adb26a38698db0e85666c295e93aefd61c0ad Mon Sep 17 00:00:00 2001 From: Sammy Harris <41593264+stegaBOB@users.noreply.github.com> Date: Thu, 16 Jul 2026 14:10:58 -0500 Subject: [PATCH 3/4] style(build-verified): use [ -n ] for empty-check consistency Standardize on '[ -n ]' instead of the '[ ! -z ]' spelling used by the existing FEATURES guard. Functionally identical; addresses Greptile P2. Co-Authored-By: Claude Opus 4.8 --- build-verified/action.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build-verified/action.yaml b/build-verified/action.yaml index 7bc2c16..b206b0d 100644 --- a/build-verified/action.yaml +++ b/build-verified/action.yaml @@ -27,7 +27,7 @@ runs: shell: bash run: | FEATURES="${{ inputs.features }}" - if [ ! -z "$FEATURES" ]; then + if [ -n "$FEATURES" ]; then FEATURES="--features $FEATURES" fi BASE_IMAGE_ARG="" From 70503c7c3b5d2e0f7e4176bf7172a246715dadb6 Mon Sep 17 00:00:00 2001 From: Sammy Harris <41593264+stegaBOB@users.noreply.github.com> Date: Thu, 16 Jul 2026 14:12:51 -0500 Subject: [PATCH 4/4] fix(verify-build,build-verified): pass inputs via env to close shell injection Every ${{ inputs.* }} / ${{ github.event.inputs.* }} used inside a run: block is expanded into the script text by the Actions template engine before bash parses it, so shell metacharacters in a value execute. Move all such values to step-level env: blocks and reference them as quoted shell variables ($VAR), so values are data, not code. Single-value args are quoted; $ARGS/$CARGO_ARGS/$BASE_IMAGE_ARG stay unquoted for intentional flag word-splitting. Addresses Greptile P1 (security), and hardens the pre-existing interpolations in the same steps too. Co-Authored-By: Claude Opus 4.8 --- build-verified/action.yaml | 16 +++++--- verify-build/action.yaml | 83 +++++++++++++++++++++++++------------- 2 files changed, 64 insertions(+), 35 deletions(-) diff --git a/build-verified/action.yaml b/build-verified/action.yaml index b206b0d..c9a6a49 100644 --- a/build-verified/action.yaml +++ b/build-verified/action.yaml @@ -25,14 +25,18 @@ runs: - name: Build Verified shell: bash + env: + FEATURES_INPUT: ${{ inputs.features }} + BASE_IMAGE: ${{ inputs.base-image }} + PROGRAM: ${{ inputs.program }} run: | - FEATURES="${{ inputs.features }}" - if [ -n "$FEATURES" ]; then - FEATURES="--features $FEATURES" + FEATURES="" + if [ -n "$FEATURES_INPUT" ]; then + FEATURES="--features $FEATURES_INPUT" fi BASE_IMAGE_ARG="" - if [ -n "${{ inputs.base-image }}" ]; then - BASE_IMAGE_ARG="--base-image ${{ inputs.base-image }}" + if [ -n "$BASE_IMAGE" ]; then + BASE_IMAGE_ARG="--base-image $BASE_IMAGE" fi # Run solana-verify directly without Docker - ~/.cargo/bin/solana-verify build $BASE_IMAGE_ARG --library-name ${{ inputs.program }} -- $FEATURES + ~/.cargo/bin/solana-verify build $BASE_IMAGE_ARG --library-name "$PROGRAM" -- $FEATURES diff --git a/verify-build/action.yaml b/verify-build/action.yaml index 80f6d2e..9a70557 100644 --- a/verify-build/action.yaml +++ b/verify-build/action.yaml @@ -64,84 +64,109 @@ runs: - name: Debug Environment shell: bash + env: + EVENT_NETWORK: ${{ github.event.inputs.network }} + EVENT_VERIFY: ${{ github.event.inputs.verify }} + HAS_KEYPAIR: ${{ inputs.keypair != '' }} run: | - echo "Network: ${{ github.event.inputs.network }}" - echo "Verify: ${{ github.event.inputs.verify }}" - echo "Has keypair: ${{ inputs.keypair != '' }}" + echo "Network: $EVENT_NETWORK" + echo "Verify: $EVENT_VERIFY" + echo "Has keypair: $HAS_KEYPAIR" - name: Verify Build shell: bash if: inputs.use-squads == 'false' + env: + SKIP_BUILD: ${{ inputs.skip-build }} + SKIP_PROMPT: ${{ inputs.skip-prompt }} + MOUNT_PATH: ${{ inputs.mount-path }} + COMMIT_HASH: ${{ inputs.commit-hash }} + NETWORK: ${{ inputs.network }} + BASE_IMAGE: ${{ inputs.base-image }} + FEATURES_INPUT: ${{ inputs.features }} + PROGRAM_ID: ${{ inputs.program-id }} + RPC_URL: ${{ inputs.rpc-url }} + PROGRAM: ${{ inputs.program }} + REPO_URL: ${{ inputs.repo-url }} run: | ARGS="" - if [ "${{ inputs.skip-build }}" = "true" ]; then + if [ "$SKIP_BUILD" = "true" ]; then ARGS="$ARGS --skip-build" fi - if [ "${{ inputs.skip-prompt }}" = "true" ]; then + if [ "$SKIP_PROMPT" = "true" ]; then ARGS="$ARGS --skip-prompt" fi - if [ -n "${{ inputs.mount-path }}" ]; then - ARGS="$ARGS --mount-path ${{ inputs.mount-path }}" + if [ -n "$MOUNT_PATH" ]; then + ARGS="$ARGS --mount-path $MOUNT_PATH" fi - if [ -n "${{ inputs.commit-hash }}" ]; then - ARGS="$ARGS --commit-hash ${{ inputs.commit-hash }}" + if [ -n "$COMMIT_HASH" ]; then + ARGS="$ARGS --commit-hash $COMMIT_HASH" fi - if [ "${{ inputs.network }}" = "mainnet" ]; then + if [ "$NETWORK" = "mainnet" ]; then ARGS="$ARGS --remote" fi - if [ -n "${{ inputs.base-image }}" ]; then - ARGS="$ARGS --base-image ${{ inputs.base-image }}" + if [ -n "$BASE_IMAGE" ]; then + ARGS="$ARGS --base-image $BASE_IMAGE" fi CARGO_ARGS="" - if [ -n "${{ inputs.features }}" ]; then - CARGO_ARGS="-- --features ${{ inputs.features }}" + if [ -n "$FEATURES_INPUT" ]; then + CARGO_ARGS="-- --features $FEATURES_INPUT" fi solana-verify verify-from-repo \ - --program-id ${{ inputs.program-id }} \ - --url ${{ inputs.rpc-url }} \ - --library-name ${{ inputs.program }} \ + --program-id "$PROGRAM_ID" \ + --url "$RPC_URL" \ + --library-name "$PROGRAM" \ -k ./deploy-keypair.json \ $ARGS \ - ${{ inputs.repo-url }} \ + "$REPO_URL" \ $CARGO_ARGS - name: Export PDA Transaction id: export-pda if: inputs.use-squads == 'true' shell: bash + env: + BASE_IMAGE: ${{ inputs.base-image }} + FEATURES_INPUT: ${{ inputs.features }} + REPO_URL: ${{ inputs.repo-url }} + PROGRAM_ID: ${{ inputs.program-id }} + VAULT_ADDRESS: ${{ inputs.vault-address }} + COMMIT_HASH: ${{ inputs.commit-hash }} + RPC_URL: ${{ inputs.rpc-url }} + PROGRAM: ${{ inputs.program }} run: | echo "=== Exporting PDA Transaction ===" BASE_IMAGE_ARG="" - if [ -n "${{ inputs.base-image }}" ]; then - BASE_IMAGE_ARG="--base-image ${{ inputs.base-image }}" + if [ -n "$BASE_IMAGE" ]; then + BASE_IMAGE_ARG="--base-image $BASE_IMAGE" fi CARGO_ARGS="" - if [ -n "${{ inputs.features }}" ]; then - CARGO_ARGS="-- --features ${{ inputs.features }}" + if [ -n "$FEATURES_INPUT" ]; then + CARGO_ARGS="-- --features $FEATURES_INPUT" fi # Get the last line which should be the base64 transaction TX=$(solana-verify export-pda-tx \ - ${{ inputs.repo-url }} \ - --program-id ${{ inputs.program-id }} \ - --uploader ${{ inputs.vault-address }} \ - --commit-hash ${{ inputs.commit-hash }} \ - --url ${{ inputs.rpc-url }} \ - --library-name ${{ inputs.program }} \ + "$REPO_URL" \ + --program-id "$PROGRAM_ID" \ + --uploader "$VAULT_ADDRESS" \ + --commit-hash "$COMMIT_HASH" \ + --url "$RPC_URL" \ + --library-name "$PROGRAM" \ $BASE_IMAGE_ARG \ --encoding base64 \ $CARGO_ARGS | tail -n 1) - + # Verify we got a base64 string if [[ ! "$TX" =~ ^[A-Za-z0-9+/=]*$ ]]; then echo "Error: Invalid base64 transaction"