fix: sql escape issues identified by CodeQL (#11338)

* fix: sql escape issues identified by CodeQL

* fix: random generation in sample code
This commit is contained in:
Lucian Mocanu 2025-04-01 10:38:36 +02:00 committed by GitHub
parent c3bebdcb4e
commit 863caf1471
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 56 additions and 35 deletions

View File

@ -1,24 +1,26 @@
import crypto from "node:crypto"
import { AfterInsert } from "../../../src/decorator/listeners/AfterInsert"
import { AfterLoad } from "../../../src/decorator/listeners/AfterLoad"
import { AfterRecover } from "../../../src/decorator/listeners/AfterRecover"
import { AfterRemove } from "../../../src/decorator/listeners/AfterRemove"
import { AfterSoftRemove } from "../../../src/decorator/listeners/AfterSoftRemove"
import { AfterUpdate } from "../../../src/decorator/listeners/AfterUpdate"
import { BeforeInsert } from "../../../src/decorator/listeners/BeforeInsert"
import { BeforeRecover } from "../../../src/decorator/listeners/BeforeRecover"
import { BeforeRemove } from "../../../src/decorator/listeners/BeforeRemove"
import { BeforeSoftRemove } from "../../../src/decorator/listeners/BeforeSoftRemove"
import { BeforeUpdate } from "../../../src/decorator/listeners/BeforeUpdate"
import { JoinTable } from "../../../src/decorator/relations/JoinTable"
import { ManyToOne } from "../../../src/decorator/relations/ManyToOne"
import {
Column,
Entity,
ManyToMany,
PrimaryGeneratedColumn,
} from "../../../src/index"
import { PostCategory } from "./PostCategory"
import { PostAuthor } from "./PostAuthor"
import { ManyToOne } from "../../../src/decorator/relations/ManyToOne"
import { AfterLoad } from "../../../src/decorator/listeners/AfterLoad"
import { AfterInsert } from "../../../src/decorator/listeners/AfterInsert"
import { BeforeInsert } from "../../../src/decorator/listeners/BeforeInsert"
import { BeforeUpdate } from "../../../src/decorator/listeners/BeforeUpdate"
import { AfterUpdate } from "../../../src/decorator/listeners/AfterUpdate"
import { BeforeRemove } from "../../../src/decorator/listeners/BeforeRemove"
import { AfterRemove } from "../../../src/decorator/listeners/AfterRemove"
import { AfterRecover } from "../../../src/decorator/listeners/AfterRecover"
import { BeforeRecover } from "../../../src/decorator/listeners/BeforeRecover"
import { AfterSoftRemove } from "../../../src/decorator/listeners/AfterSoftRemove"
import { BeforeSoftRemove } from "../../../src/decorator/listeners/BeforeSoftRemove"
import { JoinTable } from "../../../src/decorator/relations/JoinTable"
import { PostCategory } from "./PostCategory"
@Entity("sample9_post")
export class Post {
@ -31,12 +33,12 @@ export class Post {
@Column()
text: string
@ManyToOne((type) => PostAuthor, (post) => post.posts, {
@ManyToOne(() => PostAuthor, (post) => post.posts, {
cascade: true,
})
author: PostAuthor
@ManyToMany((type) => PostCategory, (category) => category.posts, {
@ManyToMany(() => PostCategory, (category) => category.posts, {
cascade: true,
})
@JoinTable()
@ -49,7 +51,7 @@ export class Post {
console.log(
`event: Post "${this.title}" entity has been loaded and callback executed`,
)
this.uid = Math.ceil(Math.random() * 1000)
this.uid = crypto.randomInt(0, 1000)
}
@BeforeInsert()

View File

@ -111,7 +111,7 @@ export class MigrationGenerateCommand implements yargs.CommandModule {
sqlInMemory.upQueries.forEach((upQuery) => {
upSqls.push(
" await queryRunner.query(`" +
upQuery.query.replace(new RegExp("`", "g"), "\\`") +
upQuery.query.replaceAll("`", "\\`") +
"`" +
MigrationGenerateCommand.queryParams(
upQuery.parameters,
@ -122,10 +122,7 @@ export class MigrationGenerateCommand implements yargs.CommandModule {
sqlInMemory.downQueries.forEach((downQuery) => {
downSqls.push(
" await queryRunner.query(`" +
downQuery.query.replace(
new RegExp("`", "g"),
"\\`",
) +
downQuery.query.replaceAll("`", "\\`") +
"`" +
MigrationGenerateCommand.queryParams(
downQuery.parameters,

View File

@ -461,16 +461,14 @@ export class CockroachDriver implements Driver {
// manually convert enum array to array of values (pg does not support, see https://github.com/brianc/node-pg-types/issues/56)
value = (value as string)
.substr(1, (value as string).length - 2)
.slice(1, -1)
.split(",")
.map((val) => {
// replace double quotes from the beginning and from the end
if (val.startsWith(`"`) && val.endsWith(`"`))
val = val.slice(1, -1)
// replace double escaped backslash to single escaped e.g. \\\\ -> \\
val = val.replace(/(\\\\)/g, "\\")
// replace escaped double quotes to non-escaped e.g. \"asd\" -> "asd"
return val.replace(/(\\")/g, '"')
// replace escaped backslash and double quotes
return val.replace(/\\(\\|")/g, "$1")
})
// convert to number if that exists in possible enum options

View File

@ -3863,7 +3863,7 @@ export class CockroachQueryRunner
): Query {
if (!enumName) enumName = this.buildEnumName(table, column)
const enumValues = column
.enum!.map((value) => `'${value.replace("'", "''")}'`)
.enum!.map((value) => `'${value.replaceAll("'", "''")}'`)
.join(", ")
return new Query(`CREATE TYPE ${enumName} AS ENUM(${enumValues})`)
}

View File

@ -774,16 +774,14 @@ export class PostgresDriver implements Driver {
// manually convert enum array to array of values (pg does not support, see https://github.com/brianc/node-pg-types/issues/56)
value = (value as string)
.substr(1, (value as string).length - 2)
.slice(1, -1)
.split(",")
.map((val) => {
// replace double quotes from the beginning and from the end
if (val.startsWith(`"`) && val.endsWith(`"`))
val = val.slice(1, -1)
// replace double escaped backslash to single escaped e.g. \\\\ -> \\
val = val.replace(/(\\\\)/g, "\\")
// replace escaped double quotes to non-escaped e.g. \"asd\" -> "asd"
return val.replace(/(\\")/g, '"')
// replace escaped backslash and double quotes
return val.replace(/\\(\\|")/g, "$1")
})
// convert to number if that exists in possible enum options

View File

@ -4284,7 +4284,7 @@ export class PostgresQueryRunner
): Query {
if (!enumName) enumName = this.buildEnumName(table, column)
const enumValues = column
.enum!.map((value) => `'${value.replace("'", "''")}'`)
.enum!.map((value) => `'${value.replaceAll("'", "''")}'`)
.join(", ")
return new Query(`CREATE TYPE ${enumName} AS ENUM(${enumValues})`)
}

View File

@ -26,6 +26,12 @@ export enum HeterogeneousEnum {
YES = "YES",
}
export enum EscapeCharEnum {
Backslash = "\\",
DoubleQuote = '"',
AllEscapeChars = `\\"`,
}
export type ArrayDefinedStringEnumType = "admin" | "editor" | "ghost"
export type ArrayDefinedNumericEnumType = 11 | 12 | 13
@ -51,6 +57,14 @@ export class EnumArrayEntity {
})
stringEnums: StringEnum[]
@Column({
type: "enum",
enum: EscapeCharEnum,
array: true,
default: [],
})
escapeCharEnums: EscapeCharEnum[]
@Column({
type: "enum",
enum: StringNumericEnum,

View File

@ -1,4 +1,5 @@
import "reflect-metadata"
import { DataSource } from "../../../../src"
import {
closeTestingConnections,
@ -7,9 +8,10 @@ import {
} from "../../../utils/test-utils"
import {
EnumArrayEntity,
EscapeCharEnum,
HeterogeneousEnum,
NumericEnum,
StringEnum,
HeterogeneousEnum,
StringNumericEnum,
} from "./entity/EnumArrayEntity"
@ -72,6 +74,11 @@ describe("database schema > enum arrays", () => {
NumericEnum.EDITOR,
]
enumEntity.stringEnums = [StringEnum.MODERATOR]
enumEntity.escapeCharEnums = [
EscapeCharEnum.Backslash,
EscapeCharEnum.DoubleQuote,
EscapeCharEnum.AllEscapeChars,
]
enumEntity.stringNumericEnums = [StringNumericEnum.FOUR]
enumEntity.heterogeneousEnums = [HeterogeneousEnum.NO]
enumEntity.arrayDefinedStringEnums = ["editor"]
@ -89,6 +96,11 @@ describe("database schema > enum arrays", () => {
loadedEnumEntity!.stringEnums.should.be.eql([
StringEnum.MODERATOR,
])
loadedEnumEntity!.escapeCharEnums.should.be.eql([
EscapeCharEnum.Backslash,
EscapeCharEnum.DoubleQuote,
EscapeCharEnum.AllEscapeChars,
])
loadedEnumEntity!.stringNumericEnums.should.be.eql([
StringNumericEnum.FOUR,
])