Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 4 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,14 +55,12 @@ deno add npm:sql-escaper

### [MySQL2](https://github.com/sidorares/node-mysql2)

🚧 For **MySQL2**, it already uses **SQL Escaper** as its default escaping library since version `3.17.0`, so you just need to update it to the latest version:
For **MySQL2**, it already uses **SQL Escaper** as its default escaping library since version `3.17.0`, so you just need to update it to the latest version:

```bash
npm i mysql2@latest # soon
npm i mysql2@latest
```

- Check the progress migration in [sidorares/node-mysql2#4054](https://github.com/sidorares/node-mysql2/pull/4054).

### [mysqljs/mysql](https://github.com/mysqljs/mysql)

You can use an overrides in your _package.json_:
Expand All @@ -83,6 +81,8 @@ You can use an overrides in your _package.json_:

## Usage

For _up-to-date_ documentation, always follow the [**README.md**](https://github.com/mysqljs/sql-escaper?tab=readme-ov-file#readme) in the **GitHub** repository.

### Quickstart

```js
Expand All @@ -104,8 +104,6 @@ escape(raw('NOW()'));
// => 'NOW()'
```

> For _up-to-date_ documentation, always follow the [**README.md**](https://github.com/mysqljs/sql-escaper?tab=readme-ov-file#readme) in the **GitHub** repository.

### Import

#### ES Modules
Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -464,7 +464,7 @@ export const format = (
) {
escapedValue = objectToValues(currentValue, timezone);
setIndex = -1;
} else escapedValue = escape(currentValue, stringifyObjects, timezone);
} else escapedValue = escape(currentValue, true, timezone);
} else escapedValue = escape(currentValue, stringifyObjects, timezone);

result += sql.slice(chunkIndex, placeholderPosition);
Expand Down
71 changes: 71 additions & 0 deletions test/escape.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { assert, describe, it } from 'poku';
import { escape } from '../src/index.ts';

describe("Can't bypass via object injection using escape directly", () => {
const value = { password: 1 };
const expected = "'[object Object]'";

it('should stringify object when stringifyObjects is true', () => {
assert.strictEqual(escape(value, true), expected);
});

it('should stringify object when stringifyObjects is false', () => {
assert.strictEqual(escape(value, false), expected);
});

it('should stringify object when stringifyObjects is 0', () => {
// @ts-expect-error: testing 0 as a falsy runtime value
assert.strictEqual(escape(value, 0), expected);
});

it('should stringify object when stringifyObjects is empty string', () => {
// @ts-expect-error: testing empty string as a falsy runtime value
assert.strictEqual(escape(value, ''), expected);
});
});

describe('Object expansion when stringifyObjects is nullish', () => {
const value = { password: 1 };
const expanded = '`password` = 1';

it('should expand object when stringifyObjects is undefined', () => {
assert.strictEqual(escape(value, undefined), expanded);
});

it('should expand object when stringifyObjects is null', () => {
// @ts-expect-error: testing null as a falsy runtime value
assert.strictEqual(escape(value, null), expanded);
});

it('should expand object when stringifyObjects is omitted', () => {
assert.strictEqual(escape(value), expanded);
});
});

describe('Safe object to key-value expansion for SET clauses', () => {
it('should expand single key-value pair', () => {
assert.strictEqual(escape({ name: 'foo' }), "`name` = 'foo'");
});

it('should expand multiple key-value pairs', () => {
assert.strictEqual(
escape({ name: 'foo', email: 'bar@test.com' }),
"`name` = 'foo', `email` = 'bar@test.com'"
);
});

it('should expand mixed value types', () => {
assert.strictEqual(
escape({ name: 'foo', active: true, age: 30 }),
"`name` = 'foo', `active` = true, `age` = 30"
);
});

it('should skip function values', () => {
assert.strictEqual(escape({ name: 'foo', fn: () => {} }), "`name` = 'foo'");
});

it('should return empty string for empty object', () => {
assert.strictEqual(escape({}), '');
});
});
69 changes: 69 additions & 0 deletions test/falsy.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { assert, describe, it } from 'poku';
import { format } from '../src/index.ts';

describe('Safe SET with object parameter', () => {
const sql = 'UPDATE users SET ?';
const values = [{ name: 'foo', email: 'bar@test.com' }];
const expected = "UPDATE users SET `name` = 'foo', `email` = 'bar@test.com'";

it('should expand object to key-value pairs when stringifyObjects is undefined', () => {
assert.strictEqual(format(sql, values, undefined), expected);
});

it('should expand object to key-value pairs when stringifyObjects is null', () => {
// @ts-expect-error: testing null as a falsy runtime value
assert.strictEqual(format(sql, values, null), expected);
});

it('should expand object to key-value pairs when stringifyObjects is false', () => {
assert.strictEqual(format(sql, values, false), expected);
});

it('should expand object to key-value pairs when stringifyObjects is 0', () => {
// @ts-expect-error: testing 0 as a falsy runtime value
assert.strictEqual(format(sql, values, 0), expected);
});

it('should expand object to key-value pairs when stringifyObjects is empty string', () => {
// @ts-expect-error: testing empty string as a falsy runtime value
assert.strictEqual(format(sql, values, ''), expected);
});

it('should expand object to key-value pairs when stringifyObjects is omitted', () => {
assert.strictEqual(format(sql, values), expected);
});
});

describe("Can't bypass via object password injection", () => {
const sql = 'SELECT * FROM `users` WHERE `username` = ? AND `password` = ?';
const values: [string, { password: boolean }] = ['admin', { password: true }];
const expected =
"SELECT * FROM `users` WHERE `username` = 'admin' AND `password` = '[object Object]'";

it('should not generate a SQL fragment when stringifyObjects is undefined', () => {
assert.strictEqual(format(sql, values, undefined), expected);
});

it('should not generate a SQL fragment when stringifyObjects is null', () => {
// @ts-expect-error: testing null as a falsy runtime value
assert.strictEqual(format(sql, values, null), expected);
});

it('should not generate a SQL fragment when stringifyObjects is false', () => {
assert.strictEqual(format(sql, values, false), expected);
});

it('should not generate a SQL fragment when stringifyObjects is 0', () => {
// @ts-expect-error: testing 0 as a falsy runtime value
assert.strictEqual(format(sql, values, 0), expected);
});

it('should not generate a SQL fragment when stringifyObjects is empty string', () => {
// @ts-expect-error: testing empty string as a falsy runtime value
assert.strictEqual(format(sql, values, ''), expected);
});

it('should not generate a SQL fragment when stringifyObjects is omitted', () => {
assert.strictEqual(format(sql, values), expected);
});
});
Loading