Why this exists
Uploaded photos, exports, attachments, and crash artifacts are usually the part of a product that ops questions reach for last, because the answer normally requires bucket access nobody wants to hand out for a one-off look.Two governance controls
Storage differs from the other families in having a blast radius worth tuning, so it carries two controls of its own.Bucket allowlist
A runtime service account routinely has object access to buckets that have
nothing to do with the product: build artifacts, Terraform state, backups.
buckets names the ones in scope, so the capability surface is narrower
than the IAM grant behind it.Signed-URL ceiling
storage.getSignedUrl mints a bearer credential that outlives the audited
call that created it. limits.maxSignedUrlMinutes caps its life, and
setting it to 0 withdraws the capability entirely.1. Bind the runtime identity
Grant the runner’s service accountroles/storage.objectViewer on the buckets
in scope. Add roles/storage.objectAdmin only if you intend to enable mutations
with ambient credentials.
2. Name the default bucket
<project-id>.firebasestorage.app and older ones to
<project-id>.appspot.com; a wrong guess does not fail loudly, it reads to a
caller as an empty bucket.
3. Enable Storage queries
polycore.json
"storage": {} enables the read surface across every bucket the service
account can reach, at default ceilings. buckets and limits narrow it.
Available capabilities
read
Lists the buckets this runner can reach, marking the project default. Under an
allowlist, the list is the complete scope.
read
Reads a bucket’s location, storage class, versioning, lifecycle rules, and
public-access prevention.
read
Lists objects under a prefix. With
delimiter: "/" it browses one level at a
time, returning folder-like prefixes alongside objects.read
Reads one object’s size, content type, timestamps, md5, and custom metadata.
Returns
null when nothing is at that path, which doubles as the existence
check.read
Totals objects under a prefix without listing them: count, bytes, per
content-type breakdown, oldest and newest timestamps.
read
Returns a small object’s content inline, as text or base64.
read
Mints a temporary read-only link to one object. Present only when
maxSignedUrlMinutes is above zero.Cloud Storage has no server-side aggregation, so
storage.summarize walks
objects and stops at maxObjectsPerCall, reporting complete: false when it
did. It never presents a partial total as a complete one.Optional: generic mutations
Mutations are off until you grant them explicitly.polycore.json
write-hazard, resolves its bucket through the same
allowlist as reads — a write can never reach a bucket a read could not — and
names its objects by exact path.
write
Permanently deletes one object by exact path.
write
Deletes up to 100 objects by explicit path. Every path appears in the
approval.
write
Copies one object to another path, optionally into another in-scope bucket.
write
Renames or relocates one object within a bucket.
write
Changes content type, cache control, disposition, or custom metadata. The
bytes are untouched.
write
Grants
allUsers read access to one object.write
Removes public read access from one object.
write
Writes a small file from inline content. Overwrites an existing object at that
path.

