DigitalOcean Spaces: who can read your bucket, and with which key

A Space is private until you change it, and there are several separate ways to change it. Check object permissions, file listing, bucket policy and CORS one by one, then replace any key that can touch every bucket.

Object storage is where small teams keep the things they care about most and look at least often: user uploads, database backups, the build artifacts for the site. If one of those buckets is open to the internet, you usually find out from someone else.

What is private by default, and what is not

DigitalOcean's documentation says that files in a Spaces bucket are private by default. Public files are visible to anyone on the internet, and who can see a given file is controlled by its object ACL. That setting is per object, so a bucket can hold ten private files and one public one.

File listing is a second, separate switch. The file listing setting defaults to private, which means only users who connect with access keys can list the contents. If someone selects Enable File Listing, the same page says anyone can list the contents of the bucket. A public listing is worse than a public file, because it turns "one file nobody knows the name of" into "every file, with names".

The third route is a bucket policy. DigitalOcean's bucket policy page gives an example with "Principal": "*" and s3:GetObject that allows anyone to read objects in the Space. Policies are set through the API with a full-access key, and the page notes that limited-access keys do not work with the call that sets one.

So a Space can be exposed three ways: an object ACL, the listing switch, or a policy. Checking one of them tells you nothing about the other two.

Checking what is open

Start with the plain test. Open the bucket's address in a private browser window, with no credentials. The bucket host takes the form <bucket>.<region>.digitaloceanspaces.com, as the s3cmd setup page shows. If you get a listing of your files, public listing is on. Then request a single object URL the same way. If it loads, that object is public, which is what the documentation means by visible to anyone.

To list your buckets with credentials, s3cmd uses a plain s3cmd ls. To make an object private again, the same page documents:

s3cmd setacl s3://<your-space-name>/file.txt --acl-private

Here is a place where DigitalOcean does not give you what you might want. The Spaces API documents an operation for reading an object's ACL, but the pages we read show no ready-made command-line example for it. The documentation also says you cannot set file listing permissions using the DigitalOcean CLI, so that switch has to be checked and changed in the control panel. The workaround is the unauthenticated request above: it asks the question the way an attacker would.

One more thing to know if you serve the Space through the CDN. DigitalOcean says changed files are not visible until the cached copy expires, with a default of one hour, and describes flushing the cache per file, per folder or for the whole bucket. The page does not discuss making a file private. Reading it plainly, though, a file you just made private may still be served from the edge until it expires or you flush it. The documented command looks like doctl compute cdn flush <cdn-id> --files <path>.

CORS, and what a wildcard does

CORS is configured per bucket, either in the control panel's Settings tab or with an XML file through s3cmd setcors, as the CORS page describes. It lets you set the allowed origins, methods and headers. DigitalOcean's examples use a bare * for the origin, and a single wildcard in a hostname such as https://*.example.com is accepted.

It helps to be exact about what CORS controls. It tells a browser which websites are allowed to read responses from your bucket and which methods they may use. It does not make a private object public. The same page says that other bucket permissions, such as ACLs and bucket policies, still apply.

What a wildcard origin does is widen who can use your bucket from a browser. With *, a script on any website can make cross-origin requests to it using the methods you allowed. For public assets that is usually harmless, and for a bucket that allows PUT or DELETE from a browser it is not. If a user's browser holds a signed URL or credentials for your Space, a hostile page is no longer blocked from using them. DigitalOcean's security guide for Spaces warns that overly permissive CORS can expose your data to unauthorized applications.

List your real origins, allow only the methods the site uses, and keep PUT and DELETE out unless an upload form needs them. If you serve through the CDN, purge the cache after you change CORS rules, as DigitalOcean advises.

Access keys: move off the key that opens everything

This is the part most worth your time. A Spaces key pair is the credential that goes into your application, your backup job and your deploy pipeline. The access management page describes two kinds. Full access allows all supported S3 APIs on all buckets. Limited access grants permissions for specific buckets, at either Read or Read/Write/Delete.

Limited keys are recent. DigitalOcean's release notes record per-bucket keys on 6 December 2024, and its blog announced them as generally available in January 2025. Mixed permissions across several buckets on one key arrived in March 2025. If your keys were created before that, treat them as all-bucket keys until you have checked: one leaked key from a backup script could read, change or delete every bucket in the account.

Moving to limited keys takes a few steps.

  1. List the keys you have. The doctl reference shows doctl spaces keys list, with columns for name, access key, grants and creation date.
  2. Decide which workload needs which bucket, and at what level. A site that only serves images needs Read on one bucket. A backup job needs Read/Write on the backup bucket and nothing else.
  3. Create one key per workload. The doctl create reference gives the form:
doctl spaces keys create backup-job \
  --grants 'bucket=my-backups;permission=readwrite'
  1. Put the new key in that workload's configuration, confirm it works, and only then delete the old key.

Two honest limits apply. First, DigitalOcean's own pages disagree. The control panel page still says keys can only be created in the control panel, while the doctl reference and API reference document creating and listing them from the command line. Trust the command reference, and expect the older page to be corrected.

Second, we found no last-used timestamp for Spaces keys. The key schema in the API reference lists the access key, creation date, grants and name, and nothing about use. That means you cannot ask DigitalOcean which old key is still active before you delete it. The practical workaround is to switch one workload at a time and watch for failures. DigitalOcean has announced Spaces access logs, in public preview in April 2025, which may help if you turn them on before the cutover.

What DigitalOcean does not document

S3 users sometimes ask for a block-public-access switch that overrides every ACL and policy on a bucket. We did not find an equivalent in the Spaces documentation we read. Without one, no single setting guarantees a Space stays private. The checks above are the control, and they need repeating whenever someone changes a bucket.

It is also worth knowing that a Spaces key is a different credential from an API token. A token manages the account. A key reads and writes objects. But an API token with the right scopes can create Spaces keys, which makes a leaked token a route to your data. That is the subject of the DigitalOcean API tokens article.

FAQ

Are DigitalOcean Spaces public by default?

No. DigitalOcean documents that files are private by default and that file listing defaults to private. Spaces become public through object ACLs, the listing setting or a bucket policy.

What is the difference between a public file and public file listing?

A public file can be read by anyone who knows or guesses its URL. Public listing lets anyone see the names of every file in the bucket, which makes the rest much easier to find.

Does CORS make a private Space public?

No. CORS decides which websites a browser lets read responses from your bucket. ACLs and bucket policies still apply on top of it.

Can I limit a Spaces key to one bucket?

Yes. DigitalOcean added limited-access keys in December 2024, with Read or Read/Write/Delete permissions on specific buckets, and doctl can create them.

Secorvia accepts Spaces credentials as an optional extra when you connect DigitalOcean. The connection guide explains why they are separate from the API token, and the DigitalOcean page says what the connection covers.

Keep reading
Fundamentals · 5 min read

Toxic combinations: two safe settings that are dangerous together

Examples · 7 min read

Four AWS attack paths, and the one change that breaks each

Fundamentals · 7 min read

What is Cloud Security Posture Management?

Try it against your own cloud account.