The repository nobody deleted from the install guide
Metadata
- Title: The repository nobody deleted from the install guide
- Source of the case: Real HackerOne report #1791558 (Brave)
- AWS service(s): S3, DNS (Route 53 / CNAME)
- Risk archetype: Ghost reference — a live DNS name and a published guide point at a bucket that no longer exists and can be re-registered by anyone
- One-line hook: Can you prove this DNS name resolves to a bucket an attacker can claim?
0. The challenge (what the reader does first)
Scenario given to the reader:
Brave's APT repository — the bucket Linux users add to install the browser
via apt-get — was deleted during some infrastructure change. The DNS name
brave-browser-apt-release.s3.brave.com still exists and still CNAMEs to the
S3 endpoint. A community install guide checked into a Markdown file still tells
people to add that repository. You have the export below and nothing else.
Evidence they're handed (and nothing else):
{
"dns_record": {"name": "brave-browser-apt-release.s3.brave.com", "type": "CNAME", "value": "brave-browser-apt-release.s3.brave.com.s3.amazonaws.com"},
"s3_bucket_exists": false,
"referenced_in": ["community-install-guide.md"],
"http_response": {"status": 404, "body": "NoSuchBucket"}
}
- The JSON export above. No AWS credentials. No live account. No scripts.
The questions they must answer from the evidence alone:
- The DNS record still resolves, but the bucket is gone (
404 NoSuchBucket) — is this name dangling, and is the underlying bucket name free to register right now? - Nobody has claimed the bucket yet, but the name is unowned and globally registerable — what is the blast radius the moment someone does, given
apt-getruns package installs as root? - Which path makes this exploitable — the live CNAME pointing at an unclaimed S3 endpoint?
- Is there a second amplifier beyond DNS — the published
community-install-guide.mdthat keeps sending new victims to the dead reference? - What single rule, enforced at teardown, would have made it impossible to delete a bucket while a DNS record or a doc still references it?
1. The manual problem
To answer by hand you have to correlate three independent facts that live in
three different systems. The DNS zone says the name resolves. The S3 control
plane says the bucket does not exist (404 NoSuchBucket). The docs repo says
people are still told to trust it. None of those systems knows about the other
two. A DNS audit shows a healthy record. An S3 inventory shows the bucket is
simply absent — and absence does not raise an alarm, because "this bucket is
not here" looks identical to "this bucket was never here." Nobody owns the join.
And the dangerous part — that S3 bucket names are a global namespace, so the
freed name is immediately registerable by anyone in any account — is knowledge
you have to bring; it is not in any of the three exports.
2. The reasoning wall
| What they hit | What they said / would say |
|---|---|
| The DNS record looks healthy in isolation | "The zone file was fine. The record resolved. I had no reason to look at what was on the other end." |
| An absent bucket reads as a non-event, not a finding | "S3 just says the bucket isn't there. That's not red. Nothing told me 'this name is now up for grabs.'" |
| The real risk lives in the gap between DNS, S3, and a docs file | "Three green dashboards, and the vulnerability was in the seam between them that nobody owned." |
The insight the reader should reach on their own:
A reference that outlives the thing it points to is not missing data — it is an open invitation, and proving that requires joining DNS to ownership, not checking either one alone.
3. Why scanners miss or flatten it
A per-setting scanner checks one resource at a time. The DNS scanner validates
that brave-browser-apt-release.s3.brave.com is a syntactically valid CNAME and
that it resolves — it passes. The S3 scanner enumerates buckets you own; a bucket
you don't own and that doesn't exist simply never appears in the inventory,
so there is nothing to flag. Neither tool can see the edge: a live name pointing
at a target that no account currently owns, in a global namespace where the name
is free to claim. The specific thing no single-resource scanner can express is
"this CNAME's target is registerable by a stranger." That is a relationship
between a record that exists and a resource that does not — and a checklist that
walks resources one at a time has no row for a resource that is absent.
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 export the reader had — no new privileges, no live cloud:
{
"dns_record": {"name": "brave-browser-apt-release.s3.brave.com", "type": "CNAME", "value": "brave-browser-apt-release.s3.brave.com.s3.amazonaws.com"},
"s3_bucket_exists": false,
"referenced_in": ["community-install-guide.md"],
"http_response": {"status": 404, "body": "NoSuchBucket"}
}
Stave normalizes this into an observation snapshot holding two correlated
facts on one logical asset: a DNS record whose target is an S3 endpoint, and the
existence state of the bucket that endpoint names (exists = false).
5. The reasoning Stave performs
- Control / invariant:
CTL.DNS.DANGLING.001— no DNS record may point at a resource that does not exist.CTL.S3.BUCKET.TAKEOVER.001— no referenced S3 bucket name may be unowned and claimable. - What it evaluates:
CTL.DNS.DANGLING.001joins the CNAME's target to the existence of the resource on the other end and fires when the target resolves to a bucket whoses3_bucket_existsis false.CTL.S3.BUCKET.TAKEOVER.001confirms the named bucket is absent from any owned inventory and therefore registerable in S3's global namespace — encoding the takeover risk the reader had to infer. - Verdict produced: Both controls fire and consolidate into one Issue. An unknown bucket-existence value is treated as a violation, not as "safe by default," so a missing fact never silently clears the finding.
Issue: brave-browser-apt-release.s3.brave.com — dangling reference to claimable bucket
CTL.DNS.DANGLING.001 NON_COMPLIANT
asset: dns://brave-browser-apt-release.s3.brave.com (CNAME)
evidence: CNAME target brave-browser-apt-release.s3.brave.com.s3.amazonaws.com
resolves; bucket existence = false; http 404 NoSuchBucket
verdict: live DNS record points at a non-existent resource
CTL.S3.BUCKET.TAKEOVER.001 NON_COMPLIANT
asset: s3://brave-browser-apt-release.s3.brave.com
evidence: bucket does not exist and name is unowned in the global namespace;
referenced_in = community-install-guide.md
verdict: bucket name is claimable — APT repo can be served by an attacker
security_state: NON_COMPLIANT
6. The prevention artifact Stave produces
- Artifact: A teardown guardrail (SCP-style policy) that forbids deleting an S3 bucket while any DNS record or tracked document still references it, plus the manual remediation: remove the dangling CNAME and the guide reference, or reclaim the bucket name in the owning account.
- What it forecloses: The latent state from question 2 — the moment a stranger registers the freed name and starts serving
.debpackages toapt-get. With the guardrail, the bucket cannot be deleted until its references are gone, so the name is never left dangling and claimable.
# Teardown guardrail: deny bucket deletion while references remain (SCP)
{
"Sid": "DenyDeleteWhileReferenced",
"Effect": "Deny",
"Action": "s3:DeleteBucket",
"Resource": "arn:aws:s3:::brave-browser-apt-release.s3.brave.com",
"Condition": {
"StringEquals": {"stave:reference-count": "0"}
}
}
# stave:reference-count is set from the DNS+docs reference index;
# deletion is permitted only once it reaches 0.
# Manual fix until references are cleared — choose one:
# (a) remove the dangling reference
aws route53 change-resource-record-sets --hosted-zone-id <zone> \
--change-batch '{"Changes":[{"Action":"DELETE","ResourceRecordSet":{
"Name":"brave-browser-apt-release.s3.brave.com","Type":"CNAME",
"TTL":300,"ResourceRecords":[{"Value":"brave-browser-apt-release.s3.brave.com.s3.amazonaws.com"}]}}]}'
# ...and delete the repo line from community-install-guide.md
# (b) reclaim the name so an attacker cannot
aws s3api create-bucket --bucket brave-browser-apt-release.s3.brave.com --region us-east-1
7. What the team no longer does manually
| Before | After Stave |
|---|---|
| Cross-check DNS zones against S3 inventory by hand to find records with no live target | One control joins record-to-existence and proves the dangling reference deterministically |
| Recognize from memory that a freed S3 name is globally registerable | The takeover invariant encodes "absent bucket name = claimable" and re-checks it every run |
| Hope a deleted bucket's references were also cleaned up | A teardown guardrail blocks deletion until DNS and doc references reach zero |
Positioning line for this case
Stave proves that
brave-browser-apt-release.s3.brave.compoints at a bucket that no longer exists, proves the freed name is claimable by anyone serving root-level.debpackages, and emits the teardown guardrail that makes deleting a referenced bucket impossible.
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