Clears all 23 open CodeQL alerts:
- actions/missing-workflow-permissions (19, medium): add a top-level
least-privilege `permissions: contents: read` to ci.yml and the 7
deploy workflows. The one CI job that needs `packages: write` already
declares its own block; all deploys authenticate via static secrets
(no OIDC / id-token, no repo writes), so read is sufficient.
- js/insecure-randomness (1, high): `genPassword()` built share
passwords with Math.random(); switch to crypto.getRandomValues() over
the same unambiguous alphabet (length/charset/uniqueness preserved).
- js/incomplete-url-substring-sanitization (2, high): two test fetch
stubs routed on `String(url).includes('api.github.com')`; tighten to
`new URL(url).hostname === 'api.github.com'` — precise and no longer
flagged.
- js/stack-trace-exposure (1, medium): the E2E S3 mock echoed
error.message in 500 responses; log server-side and return a generic
body instead.
Verified: typecheck green; share-dialog/changelog/system.integration
tests pass.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Previously, push to master triggered 6 separate deploy workflows (CF +
5 new v2.5.0 targets). If the fork hadn't configured, say, AWS or Azure
secrets, those workflows would run just to fail on "Check required
secrets" — producing 5 red X's in Actions tab per push, 5 failure
notifications, 5 wasted runner allocations.
Collapse to one top-level `deploy.yml` dispatcher that:
1. Runs a lightweight `detect` job (~5s) probing which platform secret
bundles are fully present — without invoking secrets.* in job-level
`if:` (which GH disallows).
2. Invokes the corresponding reusable child workflow via `uses:` +
`secrets: inherit` only when that platform's flag is true.
Each child workflow (`deploy-<target>.yml`) is now a reusable workflow:
- `push: [master]` trigger → removed (dispatcher owns push)
- `workflow_call:` trigger → added (invoked by dispatcher)
- `workflow_dispatch:` trigger → kept (manual runs via Actions UI)
- `if: github.repository != 'saltbo/zpan'` job guard → removed
(dispatcher enforces this once)
The old `deploy.yml` (CF Workers flow) is renamed to
`deploy-cloudflare.yml` for consistency with the other 5. Content of
the CF flow is unchanged.
For a fork with only CF configured: 1 dispatcher run + 1 cloudflare run.
For a fork with nothing configured: 1 dispatcher run with all 6 child
jobs shown as "Skipped" (not failed), and a ::notice:: pointing at the
README secrets table.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>