Skip to content

fix(cli): prevent SQL injection into platform Supabase via package.json name - #460

Open
0xcucumbersalad wants to merge 1 commit into
mainfrom
fix/migrate-supabase-sql-injection
Open

fix(cli): prevent SQL injection into platform Supabase via package.json name#460
0xcucumbersalad wants to merge 1 commit into
mainfrom
fix/migrate-supabase-sql-injection

Conversation

@0xcucumbersalad

@0xcucumbersalad 0xcucumbersalad commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Summary

provisionAnalytics() in migrate.ts read name from the migrated repo's package.json and interpolated it, unescaped, into a single-quoted SQL literal sent to the decocms Supabase Management API /database/query endpoint:

siteName = pkg.name; // untrusted repo content
const sql = `UPDATE public.sites SET metadata = ... WHERE name = '${siteName}'`;
fetch(`https://api.supabase.com/v1/projects/${DECOCMS_SUPABASE_REF}/database/query`, { body: JSON.stringify({ query: sql }) });

That endpoint runs raw SQL (no bound parameters), against the central public.sites table that holds a row for every site.

Impact

SQL injection on the platform database, cross-tenant. A crafted package.json:

{ "name": "x'; UPDATE public.sites SET metadata = metadata || '{\"pwn\":1}'::jsonb WHERE name='victim-store'; --" }

produces:

... WHERE name = 'x'; UPDATE public.sites ... WHERE name='victim-store'; --'

The ' closes the literal, ; starts a new statement, -- comments the tail — attacker's repo rewrites/reads another tenant's row. Runs whenever an operator migrates the repo with SUPABASE_ACCESS_TOKEN set.

Fix

New migrate/sql-safety.ts:

  • isValidNpmPackageName() — reject any name that isn't a valid npm package name before it can reach the query; provisioning is skipped for invalid names. The grammar forbids ', ;, spaces, newlines — everything needed for breakout.
  • escapeSqlLiteral() — SQL-standard single-quote doubling, applied to both the executed query and the printAnalyticsSQL manual-run output, as defense-in-depth.

Both applied in provisionAnalytics(). A valid name path is byte-for-byte unchanged; only malicious/invalid names are blocked.

Tests

New migrate/sql-safety.test.ts (6 tests, passing): valid names accepted; injection payloads / uppercase / spaces / newlines / over-length / empty rejected; non-string input rejected; quote-doubling verified, including the full breakout string neutralized inside a single-quoted literal.

Test Files  1 passed (1)
     Tests  6 passed (6)

Scope

Finding F4 from the source audit. Companion PRs: #459 (F5, git-clone command injection) and the F6 codegen-injection PR. Same class — untrusted input reaching a raw sink (SQL / shell / generated code) without escaping.

Note: the endpoint is a raw-SQL runner with no parameter binding, so the correct mitigation is strict input validation + literal escaping rather than a parameterized query. If the platform later exposes a bound-parameter RPC, migrate this call to it.

🤖 Generated with Claude Code


Summary by cubic

Fixes a SQL injection in the CLI migrator by validating and escaping the repo package.json name before using it in raw SQL against the platform Supabase. Prevents cross-tenant writes via crafted package names and makes the manual SQL output safe.

  • Bug Fixes
    • Added isValidNpmPackageName() to reject invalid/malicious names; skip analytics provisioning when invalid.
    • Added escapeSqlLiteral() and applied it to both the executed query and printAnalyticsSQL output.
    • Introduced sql-safety.test.ts covering valid names, injection payloads, non-string input, length, and quote-doubling.

Written for commit aa34a6d. Summary will update on new commits.

Review in cubic

…on name

provisionAnalytics() in migrate.ts read `name` from the migrated repo's
package.json and interpolated it, unescaped, into a single-quoted SQL
literal sent to the decocms Supabase Management API `/database/query`
endpoint (raw SQL, no bound parameters) against the central
`public.sites` table that tracks every site. A crafted name such as

  "name": "x'; UPDATE public.sites SET metadata=...::jsonb WHERE name='victim'; --"

broke out of the literal and ran arbitrary SQL cross-tenant on the
platform DB (runs whenever an operator migrates the repo with
SUPABASE_ACCESS_TOKEN set).

Fix (new migrate/sql-safety.ts):
- isValidNpmPackageName() — reject any name that is not a valid npm
  package name before it can reach the query; provisioning is skipped
  for invalid names. The grammar forbids quotes, `;`, spaces, newlines.
- escapeSqlLiteral() — SQL-standard quote doubling, applied to both the
  executed query and the printAnalyticsSQL manual-run output as
  defense-in-depth.

Adds migrate/sql-safety.test.ts covering valid names, injection
payloads, non-string input, and quote-escaping.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

1 participant