From 79564486d33c84844af634bc2220ec25e2caa3fa Mon Sep 17 00:00:00 2001 From: archipelago Date: Tue, 14 Jul 2026 11:38:51 -0400 Subject: [PATCH] fix(demo): never leak the release-server IP in the public demo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Companion QR overlay: demo builds encode the demo's own origin (/packages/archipelago-companion.apk ships in the web image) instead of the vps2 raw URL. - Dockerfile.web/.backend: build-time scrub replaces every remaining occurrence of the release-server address with registry.demo.internal and fails the build if any survives. - Mock backend update-mirror list, sideload help text, and changelog prose no longer name the server address. - Copy demo-images workflow into .gitea/workflows/ — Gitea ignores .github/workflows when .gitea/workflows exists, so the CI never ran. Co-Authored-By: Claude Fable 5 --- .gitea/workflows/demo-images.yml | 68 +++++++++++++++++++ neode-ui/Dockerfile.backend | 13 ++++ neode-ui/Dockerfile.web | 14 ++++ neode-ui/mock-backend.js | 3 +- .../src/components/CompanionIntroOverlay.vue | 7 +- neode-ui/src/views/Apps.vue | 2 +- .../src/views/settings/AccountInfoSection.vue | 4 +- 7 files changed, 106 insertions(+), 5 deletions(-) create mode 100644 .gitea/workflows/demo-images.yml diff --git a/.gitea/workflows/demo-images.yml b/.gitea/workflows/demo-images.yml new file mode 100644 index 00000000..6a08fb8c --- /dev/null +++ b/.gitea/workflows/demo-images.yml @@ -0,0 +1,68 @@ +name: Demo images + +# Builds and pushes the public-demo images on every change to the UI / mock +# backend, so the separated `archy-demo` Portainer stack auto-tracks the real +# code (see demo-deploy/ and docs/demo-deployment-design.md). +# +# Required repo configuration: +# vars.DEMO_REGISTRY e.g. 146.59.87.168:3000/lfg2025 +# vars.DEMO_REGISTRY_HOST registry host for docker login (no org suffix) +# secrets.DEMO_REGISTRY_USER +# secrets.DEMO_REGISTRY_TOKEN +# Optional: +# secrets.PORTAINER_WEBHOOK redeploy hook called after a successful push + +on: + push: + branches: [main] + paths: + - 'neode-ui/**' + - 'docker-compose.demo.yml' + - '.github/workflows/demo-images.yml' + workflow_dispatch: + +jobs: + build: + name: Build & push demo images + runs-on: ubuntu-latest + # Skip cleanly on forks / before registry config is set. + if: ${{ vars.DEMO_REGISTRY != '' }} + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to registry + uses: docker/login-action@v3 + with: + registry: ${{ vars.DEMO_REGISTRY_HOST || vars.DEMO_REGISTRY }} + username: ${{ secrets.DEMO_REGISTRY_USER }} + password: ${{ secrets.DEMO_REGISTRY_TOKEN }} + + - name: Build & push backend + uses: docker/build-push-action@v6 + with: + context: . + file: neode-ui/Dockerfile.backend + push: true + tags: | + ${{ vars.DEMO_REGISTRY }}/archy-demo-backend:demo + ${{ vars.DEMO_REGISTRY }}/archy-demo-backend:${{ github.sha }} + + - name: Build & push web + uses: docker/build-push-action@v6 + with: + context: . + file: neode-ui/Dockerfile.web + push: true + build-args: | + VITE_DEMO=1 + tags: | + ${{ vars.DEMO_REGISTRY }}/archy-demo-web:demo + ${{ vars.DEMO_REGISTRY }}/archy-demo-web:${{ github.sha }} + + - name: Trigger Portainer redeploy + if: ${{ success() && secrets.PORTAINER_WEBHOOK != '' }} + run: curl -fsS -X POST "${{ secrets.PORTAINER_WEBHOOK }}" diff --git a/neode-ui/Dockerfile.backend b/neode-ui/Dockerfile.backend index 22ed353c..94e3f487 100644 --- a/neode-ui/Dockerfile.backend +++ b/neode-ui/Dockerfile.backend @@ -24,6 +24,19 @@ COPY docker/fedimint-ui /docker/fedimint-ui COPY demo/files /demo/files COPY demo/content /demo/content +# This image only ever serves the public demo — scrub the private release/registry +# server address from everything it serves (mock data, catalog.json, demo assets) +# and fail the build if any occurrence survives. +RUN find /app /docker /demo -type f \( -name '*.js' -o -name '*.mjs' -o -name '*.cjs' \ + -o -name '*.css' -o -name '*.html' -o -name '*.json' -o -name '*.md' -o -name '*.txt' \ + -o -name '*.yml' -o -name '*.yaml' \) -not -path '*/node_modules/*' \ + -exec sed -i \ + -e 's#146\.59\.87\.168:3000/lfg2025#registry.demo.internal/archy#g' \ + -e 's#146\.59\.87\.168:3000#registry.demo.internal#g' \ + -e 's#146\.59\.87\.168#registry.demo.internal#g' {} + && \ + if grep -rq '146\.59\.87\.168' /app/mock-backend.js /app/public /docker /demo; then \ + echo 'LEAK: release-server IP still in demo image'; exit 1; fi + # Expose port EXPOSE 5959 diff --git a/neode-ui/Dockerfile.web b/neode-ui/Dockerfile.web index f6be9924..9eafa714 100644 --- a/neode-ui/Dockerfile.web +++ b/neode-ui/Dockerfile.web @@ -29,6 +29,20 @@ ENV VITE_DEMO=$VITE_DEMO # Use npm script which handles build better RUN npm run build:docker || (echo "Build failed! Listing files:" && ls -la && echo "Checking vite config:" && cat vite.config.ts && exit 1) +# Demo builds must not leak the private release/registry server address anywhere +# in the served bundle (JS constants, catalog.json, prose). Replace every +# occurrence with a non-routable placeholder, then fail the build if any slipped +# through. (The companion-QR URL is handled in source — it needs a URL that works.) +RUN if [ "$VITE_DEMO" = "1" ] || [ "$VITE_DEMO" = "true" ]; then \ + find dist -type f \( -name '*.js' -o -name '*.mjs' -o -name '*.css' -o -name '*.html' \ + -o -name '*.json' -o -name '*.map' -o -name '*.txt' -o -name '*.webmanifest' \) \ + -exec sed -i \ + -e 's#146\.59\.87\.168:3000/lfg2025#registry.demo.internal/archy#g' \ + -e 's#146\.59\.87\.168:3000#registry.demo.internal#g' \ + -e 's#146\.59\.87\.168#registry.demo.internal#g' {} + && \ + if grep -rq '146\.59\.87\.168' dist; then echo 'LEAK: release-server IP still in dist'; exit 1; fi; \ + fi + # Production stage FROM nginx:alpine diff --git a/neode-ui/mock-backend.js b/neode-ui/mock-backend.js index 540f4862..b36d5f99 100755 --- a/neode-ui/mock-backend.js +++ b/neode-ui/mock-backend.js @@ -3561,7 +3561,8 @@ app.post('/rpc/v1', (req, res) => { case 'update.list-mirrors': { globalThis.__mockMirrors ||= [ - { url: 'http://146.59.87.168:3000/lfg2025/archy/raw/branch/main/releases/manifest.json', label: 'Origin (vps2)' }, + // Neutral placeholder — the public demo must not reveal the real release-server address. + { url: 'https://updates.archipelago.demo/releases/manifest.json', label: 'Origin' }, ] return res.json({ result: { mirrors: globalThis.__mockMirrors } }) } diff --git a/neode-ui/src/components/CompanionIntroOverlay.vue b/neode-ui/src/components/CompanionIntroOverlay.vue index 122a5a76..c7d9ea8a 100644 --- a/neode-ui/src/components/CompanionIntroOverlay.vue +++ b/neode-ui/src/components/CompanionIntroOverlay.vue @@ -77,12 +77,17 @@