Repository navigation
Open
Labels
infrastructureInfrastructure, deployment, and DevOpsInfrastructure, deployment, and DevOpsneeds-triageMaintainer needs to evaluate this issueMaintainer needs to evaluate this issuesecuritySecurity improvements and vulnerability fixesSecurity improvements and vulnerability fixes
Description
Staging's CONFIG_JSON is the prod config with only domain, WebAuthn and disableEmails overridden (docs/INFRASTRUCTURE.md). So staging has the prod R2 key, and per docs/INFRASTRUCTURE.md:122 that one key covers both buckets:
basny-bap-data: image uploads. Anything uploaded or deleted through staging lands in prod's images.basny-db-replica: the Litestream WAL replica. Staging only avoids writing to it becausestart.shskipslitestream replicatewhenSTAGING=1. The key itself could write to or delete prod's generation history.
This came up in the #360 review, where public test credentials briefly gave admin access to staging.
Want
- A staging image bucket (e.g.
basny-bap-staging-data); staging'sstorageconfig points at it. - Split the DB replica credentials from the image credentials:
- Prod: a Litestream-only key scoped to
basny-db-replica, separate from the image-upload key onbasny-bap-data. A leaked upload key then can't touch the DB replica. - Staging: a read-only token on
basny-db-replica, enough forlitestream restoreon refresh and nothing else. Staging doesn't need its own replica bucket, because it never replicates and refresh has to read prod's generation anyway.
- Prod: a Litestream-only key scoped to
- Staging's
CONFIG_JSONno longer contains any prod read/write key.
Open questions
- Should staging still show prod images? The refreshed DB references image keys in
basny-bap-data. Options: serve them read-only from the prod bucket's public URL, copy the bucket on refresh, or accept broken images on staging. litestream.ymlhardcodes the bucket and endpoint, andstart.shderivesLITESTREAM_*fromCONFIG_JSON.storage.s3*. Give Litestream its own config keys (or separate Fly secrets) so its credential can differ from the upload one, in prod as well as staging.
Metadata
Metadata
Assignees
Labels
infrastructureInfrastructure, deployment, and DevOpsInfrastructure, deployment, and DevOpsneeds-triageMaintainer needs to evaluate this issueMaintainer needs to evaluate this issuesecuritySecurity improvements and vulnerability fixesSecurity improvements and vulnerability fixes
Activity
[-]Staging gets its own R2 bucket and scoped keys[/-][+]Separate R2 buckets and scoped keys: staging images, DB replica[/+]jra3 commentedon Oct 9, 2026
Decisions from 2026-10-08/09 (these were drafted as #493, now closed as a duplicate):
basny-bap-data→ the staging bucket, byrclone syncor an SDK copy script.start.shrestore, orscripts/staging.sh refresh), so staging's DB and images come from the same point in time.basny-db-replicaandbasny-bap-data.STAGING=1,start.shuses that read-only pair for Litestream rather thanstorage.*.species_images.image_urlstores full prod r2.dev URLs. Those keep reading from prod's public URL, which is fine.Stopgap already done: #492 stops staging's scheduled jobs. Before it, the R2 orphan sweep ran against prod's bucket using staging's stale DB.
Still open: rclone in the image (~50MB, prod too) vs a small SDK copy script; bucket size.
jra3 commentedon Oct 9, 2026
Created 2026-10-09 with wrangler: bucket
basny-bap-staging-data, location ENAM like prod, r2.dev public access athttps://pub-842096e60f5b4512a0e730e6c0d6ae76.r2.dev. Empty so far. Prod'sbasny-bap-datais 231 MB / 4,188 objects, so a full copy on every refresh is cheap. Prod has no CORS config (uploads go through the server), so none was added here. Next: two scoped R2 tokens (dashboard), staging secrets, then start.sh and the copy script.Give staging its own image bucket, mirrored from prod (#494)