Missing UNIQUE constraint on shorten column allows duplicate short codes and silent wrong redirects #8

Open
opened 2026-05-14 20:30:23 +02:00 by Claude · 0 comments

Problem

The database migration creates the shorten column without a UNIQUE index. The random_str() method generates a random 9-character code but never checks for collisions before persisting the new row, and the Links entity declares no unique constraint either.

Location

  • migrations/Version20230703210818.php, line 22 — CREATE TABLE has no UNIQUE KEY on shorten
  • src/Entity/Links.php, line 20 — no #[ORM\Column(unique: true)]
  • src/Controller/Shortener.php, lines 29–35 — no collision check before persist/flush

Risk

Under concurrent load (or with deliberate brute-force), two requests may independently generate the same code. Both would be inserted successfully (no constraint violation). findOneBy(["shorten" => $code]) in Redirector returns whichever row MySQL fetches first — silently sending users to the wrong destination with no error indication.

Suggested fix direction

Add a UNIQUE constraint on shorten at both the database level (UNIQUE KEY) and in the Doctrine mapping (#[ORM\Column(unique: true)]). Wrap the insert in a retry loop that regenerates the code on UniqueConstraintViolationException.

Severity

minor

Found by

Automated audit by Claude Code

## Problem The database migration creates the `shorten` column without a `UNIQUE` index. The `random_str()` method generates a random 9-character code but never checks for collisions before persisting the new row, and the `Links` entity declares no unique constraint either. ## Location - `migrations/Version20230703210818.php`, line 22 — `CREATE TABLE` has no `UNIQUE KEY` on `shorten` - `src/Entity/Links.php`, line 20 — no `#[ORM\Column(unique: true)]` - `src/Controller/Shortener.php`, lines 29–35 — no collision check before `persist`/`flush` ## Risk Under concurrent load (or with deliberate brute-force), two requests may independently generate the same code. Both would be inserted successfully (no constraint violation). `findOneBy(["shorten" => $code])` in `Redirector` returns whichever row MySQL fetches first — silently sending users to the wrong destination with no error indication. ## Suggested fix direction Add a `UNIQUE` constraint on `shorten` at both the database level (`UNIQUE KEY`) and in the Doctrine mapping (`#[ORM\Column(unique: true)]`). Wrap the insert in a retry loop that regenerates the code on `UniqueConstraintViolationException`. ## Severity minor ## Found by Automated audit by Claude Code
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
bc1bb/OpenLink#8
No description provided.