Skip to content

fix(control-plane): add a collision-resistant suffix to Neon branch names that would otherwise truncate - #8134

Merged
JSONbored merged 1 commit into
mainfrom
fix/neon-branch-name-collision-guard
Jul 23, 2026
Merged

fix(control-plane): add a collision-resistant suffix to Neon branch names that would otherwise truncate#8134
JSONbored merged 1 commit into
mainfrom
fix/neon-branch-name-collision-guard

Conversation

@JSONbored

Copy link
Copy Markdown
Owner

Summary

  • branchNameFor (control-plane/src/neon-database-driver.ts) sanitizes a tenant/product-derived name, then unconditionally .slice(0, 63)s it (Neon's branch-name length limit) with no check that the truncated result stays unique.
  • Two distinct tenant names sharing the same first ~52 characters (after the tenant-<product>- prefix and sanitization) truncated to the identical Neon branch name. provisionNeonDatabase's findBranchByName would then resolve the OTHER tenant's already-existing branch and hand back its connection/role/password to the new tenant — a cross-tenant data-isolation bug.
  • Fix: a name that would actually be truncated gets a short (8 hex char) suffix of a SHA-256 hash of the full untruncated sanitized name, keeping the total at or under 63 characters and using only the already-established [a-z0-9_-] character set. A short, unambiguous tenant name's branch name is completely unchanged — this only engages when truncation would otherwise happen.

Note on the issue's live-infra verification deliverable: #8026 explicitly calls for validating this scheme against Neon's real API constraints before merging. This repo has no live Neon credentials anywhere (confirmed via the file's own header comment: "the test suite mocks every call; no live Neon credentials are used anywhere in this repo") and control-plane/ has never been deployed against a live Neon project (provisionNeonDatabase isn't wired into a deployable service yet — see #7654). I can't perform that live-account verification from this environment. The chosen suffix scheme stays entirely within the length/character constraints this module already enforces ([a-z0-9_-], ≤63 chars), which are conservative relative to Neon's actual documented limits, but a maintainer should do a final live-account sanity check before or shortly after the first real deploy, same as the file's pre-existing "verify against a live account before the first real deploy" disclaimer already calls for independent of this fix.

Since this repo has zero pre-existing tenants provisioned against a real Neon project, there's no already-provisioned long-name branch this change could orphan by changing its derived name.

Closes #8026.

Test plan

  • New tests in control-plane/test/neon-database-driver.test.ts: two long, prefix-similar tenant names (identical for the first 60 characters) now produce different provisionNeonDatabase branch-create POST bodies; a short tenant name's branch name is unaffected.
  • Verified the tests actually catch the bug: reverted the source fix locally, confirmed the collision test fails (both names resolved to the identical truncated branch name), then restored the fix and confirmed green.
  • npm run build (control-plane workspace)
  • npm run test (control-plane workspace, 182/182 passing, node --test runner)
  • npx tsc --noEmit (repo root)
  • npm run engine-parity:drift-check — clean, not an engine twin-pair file

…ames that would otherwise truncate

branchNameFor's unconditional .slice(0, 63) had no collision guard --
two tenant names sharing the same first ~52 characters (after the
"tenant-<product>-" prefix and sanitization) truncated to the identical
Neon branch name. provisionNeonDatabase's findBranchByName would then
resolve the OTHER tenant's already-existing branch and hand back its
connection/role/password to the new tenant, a cross-tenant isolation
bug.

Only names that actually need truncating get a hash-of-the-untruncated-
name suffix; a short tenant name's branch name is byte-identical to
before. This repo has never provisioned against a live Neon project
(no live credentials anywhere in this codebase, per this file's own
header comment), so there is no pre-existing long-name branch a suffix
could orphan.

Closes #8026.
@JSONbored JSONbored self-assigned this Jul 22, 2026
@superagent-security

Copy link
Copy Markdown
Contributor

Superagent didn't find any vulnerabilities or security issues in this PR.

@JSONbored
JSONbored merged commit 5c9cc9e into main Jul 23, 2026
6 checks passed
@JSONbored
JSONbored deleted the fix/neon-branch-name-collision-guard branch July 23, 2026 00:06
@codecov

codecov Bot commented Jul 23, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 91.49%. Comparing base (c02a277) to head (f4f5c6b).
⚠️ Report is 6 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #8134      +/-   ##
==========================================
- Coverage   92.02%   91.49%   -0.54%     
==========================================
  Files         760      760              
  Lines       77394    77410      +16     
  Branches    23392    23395       +3     
==========================================
- Hits        71225    70826     -399     
- Misses       5061     5523     +462     
+ Partials     1108     1061      -47     
Flag Coverage Δ
control-plane 99.84% <100.00%> (+<0.01%) ⬆️
shard-1 56.55% <ø> (-0.55%) ⬇️
shard-2 53.56% <ø> (-0.78%) ⬇️
shard-3 49.67% <ø> (-0.65%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
control-plane/src/neon-database-driver.ts 100.00% <100.00%> (ø)

... and 3 files with indirect coverage changes

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Neon branchNameFor's 63-char truncation has no collision guard — two long, prefix-similar tenant names could resolve to the same branch

1 participant