Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 2e55c6a

Browse filesBrowse files
louwersaduh95
authored andcommitted
sqlite: allow setting defensive flag
PR-URL: #60217 Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com> Reviewed-By: Ulises Gascón <ulisesgascongonzalez@gmail.com>
1 parent 95644a4 commit 2e55c6a
Copy full SHA for 2e55c6a

File tree

Expand file treeCollapse file tree

5 files changed

+130
-0
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

5 files changed

+130
-0
lines changed
Open diff view settings
Collapse file

‎doc/api/sqlite.md‎

Copy file name to clipboardExpand all lines: doc/api/sqlite.md
+22Lines changed: 22 additions & 0 deletions
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,10 @@ exposed by this class execute synchronously.
9898
<!-- YAML
9999
added: v22.5.0
100100
changes:
101+
- version:
102+
- REPLACEME
103+
pr-url: https://github.com/nodejs/node/pull/60217
104+
description: Add `defensive` option.
101105
- version:
102106
- v24.4.0
103107
- v22.18.0
@@ -140,6 +144,10 @@ changes:
140144
character (e.g., `foo` instead of `:foo`). **Default:** `true`.
141145
* `allowUnknownNamedParameters` {boolean} If `true`, unknown named parameters are ignored when binding.
142146
If `false`, an exception is thrown for unknown named parameters. **Default:** `false`.
147+
* `defensive` {boolean} If `true`, enables the defensive flag. When the defensive flag is enabled,
148+
language features that allow ordinary SQL to deliberately corrupt the database file are disabled.
149+
The defensive flag can also be set using `enableDefensive()`.
150+
**Default:** `false`.
143151

144152
Constructs a new `DatabaseSync` instance.
145153

@@ -261,6 +269,19 @@ Enables or disables the `loadExtension` SQL function, and the `loadExtension()`
261269
method. When `allowExtension` is `false` when constructing, you cannot enable
262270
loading extensions for security reasons.
263271

272+
### `database.enableDefensive(active)`
273+
274+
<!-- YAML
275+
added:
276+
- REPLACEME
277+
-->
278+
279+
* `active` {boolean} Whether to set the defensive flag.
280+
281+
Enables or disables the defensive flag. When the defensive flag is active,
282+
language features that allow ordinary SQL to deliberately corrupt the database file are disabled.
283+
See [`SQLITE_DBCONFIG_DEFENSIVE`][] in the SQLite documentation for details.
284+
264285
### `database.location([dbName])`
265286

266287
<!-- YAML
@@ -1306,6 +1327,7 @@ callback function to indicate what type of operation is being authorized.
13061327
[Type conversion between JavaScript and SQLite]: #type-conversion-between-javascript-and-sqlite
13071328
[`ATTACH DATABASE`]: https://www.sqlite.org/lang_attach.html
13081329
[`PRAGMA foreign_keys`]: https://www.sqlite.org/pragma.html#pragma_foreign_keys
1330+
[`SQLITE_DBCONFIG_DEFENSIVE`]: https://www.sqlite.org/c3ref/c_dbconfig_defensive.html#sqlitedbconfigdefensive
13091331
[`SQLITE_DETERMINISTIC`]: https://www.sqlite.org/c3ref/c_deterministic.html
13101332
[`SQLITE_DIRECTONLY`]: https://www.sqlite.org/c3ref/c_deterministic.html
13111333
[`SQLITE_MAX_FUNCTION_ARG`]: https://www.sqlite.org/limits.html#max_function_arg
Collapse file

‎src/env_properties.h‎

Copy file name to clipboardExpand all lines: src/env_properties.h
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@
130130
V(cwd_string, "cwd") \
131131
V(data_string, "data") \
132132
V(default_is_true_string, "defaultIsTrue") \
133+
V(defensive_string, "defensive") \
133134
V(deserialize_info_string, "deserializeInfo") \
134135
V(dest_string, "dest") \
135136
V(destroyed_string, "destroyed") \
Collapse file

‎src/node_sqlite.cc‎

Copy file name to clipboardExpand all lines: src/node_sqlite.cc
+45Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -753,6 +753,14 @@ bool DatabaseSync::Open() {
753753
CHECK_ERROR_OR_THROW(env()->isolate(), this, r, SQLITE_OK, false);
754754
CHECK_EQ(foreign_keys_enabled, open_config_.get_enable_foreign_keys());
755755

756+
int defensive_enabled;
757+
r = sqlite3_db_config(connection_,
758+
SQLITE_DBCONFIG_DEFENSIVE,
759+
static_cast<int>(open_config_.get_enable_defensive()),
760+
&defensive_enabled);
761+
CHECK_ERROR_OR_THROW(env()->isolate(), this, r, SQLITE_OK, false);
762+
CHECK_EQ(defensive_enabled, open_config_.get_enable_defensive());
763+
756764
sqlite3_busy_timeout(connection_, open_config_.get_timeout());
757765

758766
if (allow_load_extension_) {
@@ -1065,6 +1073,21 @@ void DatabaseSync::New(const FunctionCallbackInfo<Value>& args) {
10651073
allow_unknown_named_params_v.As<Boolean>()->Value());
10661074
}
10671075
}
1076+
1077+
Local<Value> defensive_v;
1078+
if (!options->Get(env->context(), env->defensive_string())
1079+
.ToLocal(&defensive_v)) {
1080+
return;
1081+
}
1082+
if (!defensive_v->IsUndefined()) {
1083+
if (!defensive_v->IsBoolean()) {
1084+
THROW_ERR_INVALID_ARG_TYPE(
1085+
env->isolate(),
1086+
"The \"options.defensive\" argument must be a boolean.");
1087+
return;
1088+
}
1089+
open_config.set_enable_defensive(defensive_v.As<Boolean>()->Value());
1090+
}
10681091
}
10691092

10701093
new DatabaseSync(
@@ -1835,6 +1858,26 @@ void DatabaseSync::EnableLoadExtension(
18351858
CHECK_ERROR_OR_THROW(isolate, db, load_extension_ret, SQLITE_OK, void());
18361859
}
18371860

1861+
void DatabaseSync::EnableDefensive(const FunctionCallbackInfo<Value>& args) {
1862+
DatabaseSync* db;
1863+
ASSIGN_OR_RETURN_UNWRAP(&db, args.This());
1864+
Environment* env = Environment::GetCurrent(args);
1865+
THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open");
1866+
1867+
auto isolate = args.GetIsolate();
1868+
if (!args[0]->IsBoolean()) {
1869+
THROW_ERR_INVALID_ARG_TYPE(isolate,
1870+
"The \"active\" argument must be a boolean.");
1871+
return;
1872+
}
1873+
1874+
const int enable = args[0].As<Boolean>()->Value();
1875+
int defensive_enabled;
1876+
const int defensive_ret = sqlite3_db_config(
1877+
db->connection_, SQLITE_DBCONFIG_DEFENSIVE, enable, &defensive_enabled);
1878+
CHECK_ERROR_OR_THROW(isolate, db, defensive_ret, SQLITE_OK, void());
1879+
}
1880+
18381881
void DatabaseSync::LoadExtension(const FunctionCallbackInfo<Value>& args) {
18391882
DatabaseSync* db;
18401883
ASSIGN_OR_RETURN_UNWRAP(&db, args.This());
@@ -3316,6 +3359,8 @@ static void Initialize(Local<Object> target,
33163359
db_tmpl,
33173360
"enableLoadExtension",
33183361
DatabaseSync::EnableLoadExtension);
3362+
SetProtoMethod(
3363+
isolate, db_tmpl, "enableDefensive", DatabaseSync::EnableDefensive);
33193364
SetProtoMethod(
33203365
isolate, db_tmpl, "loadExtension", DatabaseSync::LoadExtension);
33213366
SetProtoMethod(
Collapse file

‎src/node_sqlite.h‎

Copy file name to clipboardExpand all lines: src/node_sqlite.h
+6Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,10 @@ class DatabaseOpenConfiguration {
6565
return allow_unknown_named_params_;
6666
}
6767

68+
inline void set_enable_defensive(bool flag) { defensive_ = flag; }
69+
70+
inline bool get_enable_defensive() const { return defensive_; }
71+
6872
private:
6973
std::string location_;
7074
bool read_only_ = false;
@@ -75,6 +79,7 @@ class DatabaseOpenConfiguration {
7579
bool return_arrays_ = false;
7680
bool allow_bare_named_params_ = true;
7781
bool allow_unknown_named_params_ = false;
82+
bool defensive_ = false;
7883
};
7984

8085
class DatabaseSync;
@@ -140,6 +145,7 @@ class DatabaseSync : public BaseObject {
140145
static void ApplyChangeset(const v8::FunctionCallbackInfo<v8::Value>& args);
141146
static void EnableLoadExtension(
142147
const v8::FunctionCallbackInfo<v8::Value>& args);
148+
static void EnableDefensive(const v8::FunctionCallbackInfo<v8::Value>& args);
143149
static void LoadExtension(const v8::FunctionCallbackInfo<v8::Value>& args);
144150
static void SetAuthorizer(const v8::FunctionCallbackInfo<v8::Value>& args);
145151
static int AuthorizerCallback(void* user_data,
Collapse file
+56Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
'use strict';
2+
const { skipIfSQLiteMissing } = require('../common/index.mjs');
3+
const { test } = require('node:test');
4+
const assert = require('node:assert');
5+
const { DatabaseSync } = require('node:sqlite');
6+
skipIfSQLiteMissing();
7+
8+
function checkDefensiveMode(db) {
9+
function journalMode() {
10+
return db.prepare('PRAGMA journal_mode').get().journal_mode;
11+
}
12+
13+
assert.strictEqual(journalMode(), 'memory');
14+
db.exec('PRAGMA journal_mode=OFF');
15+
16+
switch (journalMode()) {
17+
case 'memory': return true; // journal_mode unchanged, defensive mode must be active
18+
case 'off': return false; // journal_mode now 'off', so defensive mode not active
19+
default: throw new Error('unexpected journal_mode');
20+
}
21+
}
22+
23+
test('by default, defensive mode is off', (t) => {
24+
const db = new DatabaseSync(':memory:');
25+
t.assert.strictEqual(checkDefensiveMode(db), false);
26+
});
27+
28+
test('when passing { defensive: true } as config, defensive mode is on', (t) => {
29+
const db = new DatabaseSync(':memory:', {
30+
defensive: true
31+
});
32+
t.assert.strictEqual(checkDefensiveMode(db), true);
33+
});
34+
35+
test('defensive mode on after calling db.enableDefensive(true)', (t) => {
36+
const db = new DatabaseSync(':memory:');
37+
db.enableDefensive(true);
38+
t.assert.strictEqual(checkDefensiveMode(db), true);
39+
});
40+
41+
test('defensive mode should be off after calling db.enableDefensive(false)', (t) => {
42+
const db = new DatabaseSync(':memory:', {
43+
defensive: true
44+
});
45+
db.enableDefensive(false);
46+
t.assert.strictEqual(checkDefensiveMode(db), false);
47+
});
48+
49+
test('throws if options.defensive is provided but is not a boolean', (t) => {
50+
t.assert.throws(() => {
51+
new DatabaseSync(':memory:', { defensive: 42 });
52+
}, {
53+
code: 'ERR_INVALID_ARG_TYPE',
54+
message: 'The "options.defensive" argument must be a boolean.',
55+
});
56+
});

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.