Short answer: keep backup objects private, verify them with a HEAD request, and issue a short-lived signed URL only from an admin-only backend route. The restore target should carry the tenant identity all the way through the authorization check; a bucket policy alone is not a tenant boundary.
This is the pattern I would use for a marketplace that stores product images, creates app-data backups, and offers a tenant-scoped export from an admin panel. The panel needs to tell an operator whether an archive exists and how large it is before offering a restore action. It should not download a multi-gigabyte file through the application server just to show a button.
The decision record: invariants and failure boundaries
The invariants are deliberately boring: no public-read backup keys, no browser-visible storage credential, and no restore operation that can cross a tenant prefix. An admin session authorizes the request; the storage provider only receives a server-side bearer credential.…