Skip to main content

The install guide that points at a bucket anyone can claim

Metadata

  • Title: The install guide that points at a bucket anyone can claim
  • Source of the case: Brave report #1835133
  • AWS service(s): S3, Route 53 / DNS
  • Risk archetype: ghost reference — DNS outlives the bucket it names
  • One-line hook: Can you prove this staging-repo subdomain can't be taken over?

0. The challenge (what the reader does first)

Scenario given to the reader:

A community Linux install guide tells Fedora users to add an RPM repository hosted on a staging S3 bucket. The staging bucket was deleted during cleanup, but the DNS record still points at the S3 endpoint and the guide still references it. Hitting the URL today returns a 404 NoSuchBucket.

Evidence they're handed (and nothing else):

{
"dns_record": {"name": "brave-browser-rpm-staging.s3.brave.com", "type": "CNAME", "value": "brave-browser-rpm-staging.s3.brave.com.s3.amazonaws.com"},
"s3_bucket_exists": false,
"referenced_in": ["community/linux-install-rpm.md"],
"http_response": {"status": 404, "body": "NoSuchBucket"}
}
  • The DNS record, the fact the bucket no longer exists, where it's referenced, and the current HTTP response.
  • No AWS credentials. No live account. No scripts.

The questions they must answer from the evidence alone:

  1. The URL returns 404 today — is the subdomain in an unsafe state right now, or only potentially?
  2. This looks harmless (just a 404), but what changes the instant someone claims the bucket name brave-browser-rpm-staging in S3 — who then controls what the subdomain serves, and what does the install guide cause users to run?
  3. Which path creates the exposure: the DNS record outliving its target, or the bucket name being globally claimable by anyone?
  4. Why is a staging bucket reference more dangerous than a production one in practice?
  5. What single rule would have prevented a DNS record from pointing at a bucket that no longer exists?

1. The manual problem

The reviewer has two facts that are individually boring: a DNS record exists, and a bucket does not. The danger is in their conjunction across time. To find it by hand, someone has to enumerate every DNS record that targets an S3 endpoint, then check — for each — whether the backing bucket still exists and whether the name is now claimable by an outsider. That is a join between the DNS zone and the S3 namespace, and the S3 side is global: any AWS account in the world can register brave-browser-rpm-staging once it's free.

Worse, the current evidence looks fine. A 404 reads like dead weight, not a live threat. The reviewer has to reason about a state that doesn't exist yet — the bucket re-registered by an attacker — and connect it to a community doc that instructs users to trust this repository as a software source. Nothing in the snapshot screams; the risk is latent and conditional.


2. The reasoning wall (capture, don't invent)

What they hitWhat they said / would say
404 reads as harmless"It's just a dead link — it returns NoSuchBucket, who cares?"
Latent, not current"Nothing's wrong now, so it didn't come up in the audit."
Global claimability"I didn't realize anyone could just register that bucket name and our DNS would hand them the subdomain."

The insight the reader should reach on their own:

A dangling reference isn't a dead link — it's an unsafe state waiting for someone to claim the name.


3. Why scanners miss or flatten it

A per-setting scanner checks the bucket and finds nothing — the bucket doesn't exist, so there is no node to inspect and no misconfiguration to flag. It checks the DNS record and sees a syntactically valid CNAME. Each tool reports "all clear" about the half it can see. What neither can represent is the edge between a live DNS record and a non-existent, globally claimable bucket — the latent takeover. The risk is not a property of any resource that currently exists; it's the relationship between a record that resolves and a target anyone can register. A node scanner has no row for "points at a name an attacker can claim."


Pivot point. Everything above is the gap. Everything below is Stave filling it. The reader has now done the work and hit the wall. Only now does the tool appear.


4. The evidence Stave consumes

The same static facts the reader had — the DNS record, the bucket's non-existence, and the live HTTP response — captured as an observation snapshot. No live AWS calls, no new privileges.

{
"dns_record": {"name": "brave-browser-rpm-staging.s3.brave.com", "type": "CNAME", "value": "brave-browser-rpm-staging.s3.brave.com.s3.amazonaws.com"},
"s3_bucket_exists": false,
"referenced_in": ["community/linux-install-rpm.md"],
"http_response": {"status": 404, "body": "NoSuchBucket"}
}
  • Normalization: the CNAME target is resolved to an S3 bucket name, and that name is joined against bucket existence — recording s3_bucket_exists: false as a dangling, claimable target rather than an inert dead link.

5. The reasoning Stave performs

  • Control / invariant: CTL.DNS.DANGLING.001 — no DNS record may point at an S3 target that no longer exists; CTL.S3.BUCKET.TAKEOVER.001 — no referenced bucket name may be in a deleted, claimable state.
  • What it evaluates: does a DNS record resolve to an S3 bucket name whose bucket does not exist (so the name is globally claimable)? Both the DNS-side dangling reference and the S3-side claimable name are evaluated as one takeover condition.
  • Verdict produced: NON_COMPLIANT when the record resolves to a non-existent, claimable bucket. The 404 is treated as confirming the unsafe latent state, not as evidence of safety.
control: CTL.DNS.DANGLING.001
dns: brave-browser-rpm-staging.s3.brave.com (CNAME -> ...s3.amazonaws.com)
evidence: s3_bucket_exists = false; http 404 NoSuchBucket; referenced in community/linux-install-rpm.md
verdict: NON_COMPLIANT — live DNS points at a deleted, claimable bucket

control: CTL.S3.BUCKET.TAKEOVER.001
asset: s3://brave-browser-rpm-staging
evidence: bucket deleted; name globally registrable by any account
verdict: NON_COMPLIANT — subdomain takeover possible; attacker could serve trojaned RPMs

6. The prevention artifact Stave produces

  • Artifact: a guardrail/SCP that forbids creating or retaining a DNS record whose S3 target does not resolve to an existing bucket in the account, paired with the manual remediation.
  • What it forecloses: the exact latent state from question 2 — an attacker claiming brave-browser-rpm-staging and serving malicious RPMs to anyone following the guide. Manual fix: delete the orphaned DNS record brave-browser-rpm-staging.s3.brave.com (and the guide reference), or re-claim the bucket so the name cannot fall to an outsider.
# SCP / guardrail: deny dangling S3-backed DNS references
deny:
action: route53:ChangeResourceRecordSets
when:
record.type in ["CNAME", "ALIAS"]
record.target matches "*.s3.amazonaws.com" or "*.s3.<region>.amazonaws.com"
resolve_s3_bucket(record.target).exists == false
reason: "DNS record points at a non-existent, claimable S3 bucket (dangling takeover risk)"

# Manual remediation (one of):
# 1. Remove the orphaned record:
# route53 ChangeResourceRecordSets DELETE brave-browser-rpm-staging.s3.brave.com
# 2. Re-claim the bucket so the name cannot be registered by an attacker:
# aws s3api create-bucket --bucket brave-browser-rpm-staging

7. What the team no longer does manually

BeforeAfter Stave
Join the DNS zone against the S3 namespace by handOne control resolves each record's target and checks existence
Dismiss 404s as harmless dead linksA dangling, claimable target is reported as an active takeover risk
Re-audit after every bucket decommissionThe guardrail blocks dangling references at creation, deterministically

Positioning line for this case

Stave proves that a community-referenced repo subdomain can't be taken over — by evaluating the edge between a live DNS record and a deleted, globally claimable bucket — and emits the guardrail and remediation that retire the dangling reference before an attacker serves trojaned packages from it.


Reuse checklist

  • A reader could attempt section 0 with zero Stave knowledge
  • Stave is not named or shown before the pivot point
  • Section 2 quotes are real (or honestly plausible), not slogans
  • Section 3 names the specific thing per-setting tools can't see
  • Section 6 closes the exact latent state raised in section 0, question 2
  • The title names the failure, not the product