Skip to content

fix(ci): key web Metro cache on .env to stop stale EXPO_PUBLIC_* poisoning#560

Closed
ignaciosantise wants to merge 1 commit into
mainfrom
fix/web-metro-cache-env-poisoning
Closed

fix(ci): key web Metro cache on .env to stop stale EXPO_PUBLIC_* poisoning#560
ignaciosantise wants to merge 1 commit into
mainfrom
fix/web-metro-cache-env-poisoning

Conversation

@ignaciosantise

Copy link
Copy Markdown
Collaborator

Problem

The web leg's Cache Metro web build cache step warms node_modules/.cache (Expo/Metro's transform FileStore) across runs. Expo inlines EXPO_PUBLIC_* into the bundle at Babel transform time, and Metro's transform cache is not keyed on env values.

For callers that inject per-run ephemeral credentials — e.g. an external repo rotating EXPO_PUBLIC_TEST_PRIVATE_KEY (and therefore the wallet address) on every run — the restored cache still holds the previous run's address inlined into the bundle. The exported web app ships a stale wallet address, and the Maestro pay flows run against the wrong account.

Fix

.github/actions/walletkit-build-and-maestro/action.yml:

  1. Hash the written .env into the cache key (alongside yarn.lock), so a different EXPO_PUBLIC_* set produces a different key.
  2. Remove restore-keys. This is the actual bug. restore-keys: <os>-web-metro- is a prefix fallback that matches any older cache regardless of .env — so even with .env in the exact key, an exact-key miss falls back to a stale cache and re-poisons. Exact-match-only is what makes it safe.

Ordering is fine: .env is written by the Write wallet .env step, which runs before this cache step, so hashFiles(…/.env) resolves.

Effect

  • Per-run-creds callers (the affected external repo): .env differs every run → key always misses → cold build → correct address inlined. No more poisoning.
  • Stable-creds callers (this repo's own E2E CI, which uses fixed secrets.*): .env identical across runs → exact key hit → full transform-cache speedup preserved.

Note: this repo's in-repo CI uses stable secrets, so it was not actively poisoned; the fix is correct and defensive for the reusable composite action and harmless to in-repo cache performance.

Follow-up to #555 (which added the Expo web leg).

🤖 Generated with Claude Code

…oning

Expo inlines EXPO_PUBLIC_* into the web bundle at Babel transform time,
but Metro's transform cache (node_modules/.cache) is not keyed on env
values. Callers that inject per-run ephemeral creds (e.g. a rotating
wallet private key/address) therefore restored a cache built with a
different .env and shipped the previous run's address baked into the
bundle.

Hash the written .env into the cache key and drop restore-keys: the
prefix fallback matched any older cache regardless of .env, so an
exact-key miss re-poisoned from a stale entry. Exact-match-only makes it
safe. Stable-creds callers keep a full cache hit (identical .env =>
identical key); per-run-creds callers cold-build each run, as intended.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 1, 2026 18:12
@vercel

vercel Bot commented Jul 1, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
deposit-app-demo Error Error Jul 1, 2026 6:13pm
pos-demo Ready Ready Preview, Comment Jul 1, 2026 6:13pm

Request Review

@claude

claude Bot commented Jul 1, 2026

Copy link
Copy Markdown

Claude finished @ignaciosantise's task in 1m 38s —— View job


Review Tasks

  • Read changed files and gather context
  • Understand the request (code review)
  • Spawn breaking-changes subagent
  • Compile and post review

The fix is mechanically correct: hashFiles() accepts multiple glob patterns and hashes all matched files; .env is written by the "Write wallet .env" step (line 173, unconditional) before the cache step; removing restore-keys closes the prefix-fallback hole that was the root cause. Breaking-changes subagent found no issues.

Found 1 issue(s)

Issue 1: Per-run-creds callers accumulate unreusable cache entries on every run

ID: action-web-metro-cache-churn-b3f1
File: .github/actions/walletkit-build-and-maestro/action.yml:493
Severity: LOW
Category: performance

Context:

  • Pattern: actions/cache@v4 always saves the cache in its post-step when the restore key missed. For callers with rotating EXPO_PUBLIC_*, every run produces a unique .env hash → perpetual miss → perpetual save of a cache entry that will never be reused.
  • Risk: Callers' GitHub Actions cache fills with one-shot entries at ~the transform cache size per run (typically tens to hundreds of MB). GitHub evicts entries not accessed in 7 days, so the churn is bounded, but active CI pipelines will permanently occupy significant cache quota.
  • Impact: Wasted cache quota in external caller repos; no correctness impact.
  • Trigger: Any caller injecting per-run ephemeral credentials (the scenario this PR targets).

Recommendation: Split into restore + conditional save using the v4 separate actions, or add a save-cache input (default true) to let callers opt out of saving when they know their key will never repeat:

- uses: actions/cache/restore@v4   # restore only
  with:
    path: ...
    key: ${{ runner.os }}-web-metro-${{ hashFiles(...) }}

# only save when not using ephemeral creds (caller sets input)
- if: inputs.save-web-metro-cache != 'false'
  uses: actions/cache/save@v4
  with:
    path: ...
    key: ${{ runner.os }}-web-metro-${{ hashFiles(...) }}

This is a follow-up improvement, not a blocker for merging — the current behavior is safe and correct.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Updates the reusable WalletKit CI composite action to prevent stale EXPO_PUBLIC_* values from being inlined into Expo web bundles due to Metro’s persisted transform cache being restored across runs.

Changes:

  • Add the generated wallet .env file to the actions/cache key for the web Metro cache.
  • Remove restore-keys so cache restores are exact-match-only and cannot fall back to older .env variants.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@ignaciosantise

Copy link
Copy Markdown
Collaborator Author

Folding the Metro web-cache env-poisoning fix into #559 so there's a single SHA to test both changes against from the downstream repo.

key: ${{ runner.os }}-web-metro-${{ hashFiles(format('{0}/yarn.lock', steps.paths.outputs.wallet_root)) }}
restore-keys: |
${{ runner.os }}-web-metro-
key: ${{ runner.os }}-web-metro-${{ hashFiles(format('{0}/yarn.lock', steps.paths.outputs.wallet_root), format('{0}/.env', steps.paths.outputs.wallet_root)) }}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Auto Review Issue: Per-run-creds callers accumulate unreusable cache entries on every run

Severity: LOW
Category: performance
Tool: Claude Auto Review

Context:

  • Pattern: actions/cache@v4 always saves the cache in its post-step when the restore key missed. For callers with rotating EXPO_PUBLIC_*, every run produces a unique .env hash → perpetual miss → perpetual save of a cache entry that will never be reused.
  • Risk: Callers' GitHub Actions cache fills with one-shot entries at ~the transform cache size per run (typically tens to hundreds of MB). GitHub evicts entries not accessed in 7 days, so the churn is bounded, but active CI pipelines will permanently occupy significant cache quota.
  • Impact: Wasted cache quota in external caller repos; no correctness impact.
  • Trigger: Any caller injecting per-run ephemeral credentials (the scenario this PR targets).

Recommendation: Split into restore + conditional save using the v4 separate actions, or add a save-cache input (default true) to let callers opt out of saving when they know their key will never repeat:

- uses: actions/cache/restore@v4   # restore only
  with:
    path: ...
    key: ${{ runner.os }}-web-metro-${{ hashFiles(...) }}

# only save when not using ephemeral creds (caller sets input)
- if: inputs.save-web-metro-cache != 'false'
  uses: actions/cache/save@v4
  with:
    path: ...
    key: ${{ runner.os }}-web-metro-${{ hashFiles(...) }}

This is a follow-up improvement, not a blocker for merging — the current behavior is safe and correct.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants