From 1b1009e88aad9cf120408e6fd5b09f21ed940566 Mon Sep 17 00:00:00 2001 From: Gorka Date: Wed, 6 May 2026 09:12:10 -0300 Subject: [PATCH 1/2] ci: harden deploy.yml against silent-empty + YAML-mangled secrets Same hardening as council-console / moonlight-pay / provider-console PRs: - Single-quote every ${{ secrets.X }} substitution so YAML always treats the value as a scalar string. - Add a "Validate config inputs" step before bundle generation that fail-fasts on empty values, non-https:// URLs, and STELLAR_NETWORK not in {testnet,mainnet}. The previous inline deno-eval already had non-empty checks but duplicated them across both jobs and lacked shape validation. The new structure separates validation (one bash step) from rendering (deno-eval reads validated env vars) and adds URL-prefix shape checks. Symmetric on testnet + mainnet jobs. --- .github/workflows/deploy.yml | 94 ++++++++++++++++++++++++------------ 1 file changed, 62 insertions(+), 32 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 6f0485f..2f7d325 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -7,6 +7,10 @@ on: jobs: deploy: runs-on: ubuntu-latest + env: + COUNCIL_PLATFORM_URL: "${{ secrets.COUNCIL_PLATFORM_URL }}" + STELLAR_NETWORK: "${{ secrets.STELLAR_NETWORK }}" + RPC_URL: "${{ secrets.RPC_URL }}" steps: - uses: actions/checkout@v4 @@ -14,28 +18,39 @@ jobs: with: deno-version: v2.x + - name: Validate testnet config inputs + run: | + set -euo pipefail + for var in COUNCIL_PLATFORM_URL STELLAR_NETWORK RPC_URL; do + if [ -z "${!var}" ]; then + echo "::error::Required env var $var is empty" + exit 1 + fi + done + case "$STELLAR_NETWORK" in + testnet|mainnet) ;; + *) echo "::error::STELLAR_NETWORK must be 'testnet' or 'mainnet' (got: $STELLAR_NETWORK)"; exit 1 ;; + esac + for var in COUNCIL_PLATFORM_URL RPC_URL; do + val="${!var}" + case "$val" in + https://*) ;; + *) echo "::error::$var must start with https:// (got: $val)"; exit 1 ;; + esac + done + - name: Generate production config - env: - COUNCIL_PLATFORM_URL: ${{ secrets.COUNCIL_PLATFORM_URL }} - STELLAR_NETWORK: ${{ secrets.STELLAR_NETWORK }} - RPC_URL: ${{ secrets.RPC_URL }} run: | deno eval " - const url = Deno.env.get('COUNCIL_PLATFORM_URL') || ''; - if (!url) { console.error('COUNCIL_PLATFORM_URL secret is empty'); Deno.exit(1); } - const stellarNetwork = Deno.env.get('STELLAR_NETWORK') || ''; - if (!stellarNetwork) { console.error('STELLAR_NETWORK secret is empty'); Deno.exit(1); } - const rpcUrl = Deno.env.get('RPC_URL') || ''; - if (!rpcUrl) { console.error('RPC_URL secret is empty'); Deno.exit(1); } const config = { environment: 'production', - stellarNetwork, - rpcUrl, - councilPlatformUrl: url, + stellarNetwork: Deno.env.get('STELLAR_NETWORK'), + rpcUrl: Deno.env.get('RPC_URL'), + councilPlatformUrl: Deno.env.get('COUNCIL_PLATFORM_URL'), }; Deno.writeTextFileSync('public/config.js', 'window.__DASHBOARD_CONFIG__ = ' + JSON.stringify(config, null, 2) + ';\n'); - console.log('Generated config.js pointing at', url); + console.log('Generated config.js'); " - name: Build production bundle @@ -43,8 +58,8 @@ jobs: - name: Deploy to Tigris (testnet) env: - AWS_ACCESS_KEY_ID: ${{ secrets.TIGRIS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.TIGRIS_SECRET_ACCESS_KEY }} + AWS_ACCESS_KEY_ID: "${{ secrets.TIGRIS_ACCESS_KEY_ID }}" + AWS_SECRET_ACCESS_KEY: "${{ secrets.TIGRIS_SECRET_ACCESS_KEY }}" AWS_REGION: auto run: | aws s3 sync public/ s3://network-dashboard/ \ @@ -56,6 +71,10 @@ jobs: deploy-mainnet: runs-on: ubuntu-latest environment: mainnet + env: + COUNCIL_PLATFORM_URL: "${{ secrets.MAINNET_COUNCIL_PLATFORM_URL }}" + STELLAR_NETWORK: "${{ secrets.MAINNET_STELLAR_NETWORK }}" + RPC_URL: "${{ secrets.MAINNET_RPC_URL }}" steps: - uses: actions/checkout@v4 @@ -63,28 +82,39 @@ jobs: with: deno-version: v2.x + - name: Validate mainnet config inputs + run: | + set -euo pipefail + for var in COUNCIL_PLATFORM_URL STELLAR_NETWORK RPC_URL; do + if [ -z "${!var}" ]; then + echo "::error::Required env var $var is empty" + exit 1 + fi + done + case "$STELLAR_NETWORK" in + testnet|mainnet) ;; + *) echo "::error::STELLAR_NETWORK must be 'testnet' or 'mainnet' (got: $STELLAR_NETWORK)"; exit 1 ;; + esac + for var in COUNCIL_PLATFORM_URL RPC_URL; do + val="${!var}" + case "$val" in + https://*) ;; + *) echo "::error::$var must start with https:// (got: $val)"; exit 1 ;; + esac + done + - name: Generate mainnet config - env: - COUNCIL_PLATFORM_URL: ${{ secrets.MAINNET_COUNCIL_PLATFORM_URL }} - STELLAR_NETWORK: ${{ secrets.MAINNET_STELLAR_NETWORK }} - RPC_URL: ${{ secrets.MAINNET_RPC_URL }} run: | deno eval " - const url = Deno.env.get('COUNCIL_PLATFORM_URL') || ''; - if (!url) { console.error('MAINNET_COUNCIL_PLATFORM_URL secret is empty'); Deno.exit(1); } - const stellarNetwork = Deno.env.get('STELLAR_NETWORK') || ''; - if (!stellarNetwork) { console.error('MAINNET_STELLAR_NETWORK secret is empty'); Deno.exit(1); } - const rpcUrl = Deno.env.get('RPC_URL') || ''; - if (!rpcUrl) { console.error('MAINNET_RPC_URL secret is empty'); Deno.exit(1); } const config = { environment: 'production', - stellarNetwork, - rpcUrl, - councilPlatformUrl: url, + stellarNetwork: Deno.env.get('STELLAR_NETWORK'), + rpcUrl: Deno.env.get('RPC_URL'), + councilPlatformUrl: Deno.env.get('COUNCIL_PLATFORM_URL'), }; Deno.writeTextFileSync('public/config.js', 'window.__DASHBOARD_CONFIG__ = ' + JSON.stringify(config, null, 2) + ';\n'); - console.log('Generated config.js pointing at', url); + console.log('Generated config.js'); " - name: Build production bundle @@ -92,8 +122,8 @@ jobs: - name: Deploy to Tigris (mainnet) env: - AWS_ACCESS_KEY_ID: ${{ secrets.MAINNET_TIGRIS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.MAINNET_TIGRIS_SECRET_ACCESS_KEY }} + AWS_ACCESS_KEY_ID: "${{ secrets.MAINNET_TIGRIS_ACCESS_KEY_ID }}" + AWS_SECRET_ACCESS_KEY: "${{ secrets.MAINNET_TIGRIS_SECRET_ACCESS_KEY }}" AWS_REGION: auto run: | aws s3 sync public/ s3://mainnet-network-dashboard/ \ From 3baa3f1b908524ea6d312dd91a3420bd048181b8 Mon Sep 17 00:00:00 2001 From: Gorka Date: Wed, 6 May 2026 09:52:41 -0300 Subject: [PATCH 2/2] chore: bump version to 0.2.7 --- deno.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deno.json b/deno.json index 7372d44..9f5cc3d 100644 --- a/deno.json +++ b/deno.json @@ -1,5 +1,5 @@ { - "version": "0.2.6", + "version": "0.2.7", "license": "MIT", "tasks": { "dev": "deno run --allow-all --watch src/server.ts",