Skip to content

Commit 8ffad93

Browse files
committed
sqlite: add permission model checks to DatabaseSync
Add permission model enforcement to DatabaseSync::Open(). File-backed databases now check kFileSystemRead (readOnly) or kFileSystemWrite (read-write) before calling sqlite3_open_v2. In-memory databases (:memory:) are exempt. Refs: https://hackerone.com/reports/3686625
1 parent 2b74812 commit 8ffad93

3 files changed

Lines changed: 159 additions & 0 deletions

File tree

src/node_sqlite.cc

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -912,6 +912,19 @@ bool DatabaseSync::Open() {
912912
return false;
913913
}
914914

915+
// Permission checks: skip for in-memory databases, enforce FS permissions
916+
// for file-backed databases.
917+
std::string_view db_path = open_config_.location();
918+
if (db_path != ":memory:" && !db_path.empty()) {
919+
if (open_config_.get_read_only()) {
920+
THROW_IF_INSUFFICIENT_PERMISSIONS(
921+
env(), permission::PermissionScope::kFileSystemRead, db_path, false);
922+
} else {
923+
THROW_IF_INSUFFICIENT_PERMISSIONS(
924+
env(), permission::PermissionScope::kFileSystemWrite, db_path, false);
925+
}
926+
}
927+
915928
// TODO(cjihrig): Support additional flags.
916929
int default_flags = SQLITE_OPEN_URI;
917930
int flags = open_config_.get_read_only()

test/fixtures/permission/sqlite.db

8 KB
Binary file not shown.
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
// Flags: --allow-fs-read=* --allow-fs-write=* --allow-child-process
2+
'use strict';
3+
4+
const common = require('../common');
5+
common.skipIfSQLiteMissing();
6+
7+
const { isMainThread } = require('worker_threads');
8+
9+
if (!isMainThread) {
10+
common.skip('This test only works on a main thread');
11+
}
12+
13+
const assert = require('node:assert');
14+
const { spawnSync } = require('node:child_process');
15+
const fixtures = require('../common/fixtures');
16+
17+
const spawnOpts = {
18+
encoding: 'utf8',
19+
};
20+
21+
const sqliteCode = {
22+
// Open database in read-only mode (should require fs.read)
23+
readOnly: `const sqlite = require('node:sqlite');
24+
const db = new sqlite.DatabaseSync(process.env.DB_PATH, { readOnly: true });
25+
db.close();`,
26+
27+
// Open database in read-write mode (should require fs.write)
28+
readWrite: `const sqlite = require('node:sqlite');
29+
const db = new sqlite.DatabaseSync(process.env.DB_PATH);
30+
db.close();`,
31+
32+
// Open database with options.open = false (no FS access expected at construction)
33+
noOpen: `const sqlite = require('node:sqlite');
34+
const db = new sqlite.DatabaseSync(process.env.DB_PATH, { open: false });
35+
db.open();
36+
db.close();`,
37+
};
38+
39+
const dbPath = fixtures.path('permission', 'sqlite.db');
40+
41+
{
42+
// Opening a database read-only should be blocked when fs.read is not granted
43+
const code = sqliteCode.readOnly.replace(/\n/g, ' ');
44+
const { status, stderr } = spawnSync(
45+
process.execPath,
46+
[
47+
'--permission',
48+
'--allow-fs-read=/nonexistent',
49+
'--eval', code,
50+
],
51+
{
52+
...spawnOpts,
53+
env: {
54+
...process.env,
55+
DB_PATH: dbPath,
56+
},
57+
},
58+
);
59+
assert.strictEqual(status, 1, `stderr: ${stderr}`);
60+
assert.match(stderr, /Access to this API has been restricted/);
61+
}
62+
63+
{
64+
// Opening a database read-write should be blocked when fs.write is not granted
65+
const code = sqliteCode.readWrite.replace(/\n/g, ' ');
66+
const { status, stderr } = spawnSync(
67+
process.execPath,
68+
[
69+
'--permission',
70+
'--allow-fs-write=/nonexistent',
71+
'--eval', code,
72+
],
73+
{
74+
...spawnOpts,
75+
env: {
76+
...process.env,
77+
DB_PATH: dbPath,
78+
},
79+
},
80+
);
81+
assert.strictEqual(status, 1, `stderr: ${stderr}`);
82+
assert.match(stderr, /Access to this API has been restricted/);
83+
}
84+
85+
{
86+
// Opening a database read-write should be allowed when permissions are granted
87+
const code = sqliteCode.readWrite.replace(/\n/g, ' ');
88+
const { status, stderr } = spawnSync(
89+
process.execPath,
90+
[
91+
'--permission',
92+
'--allow-fs-write=*',
93+
'--allow-fs-read=*',
94+
'--eval', code,
95+
],
96+
{
97+
...spawnOpts,
98+
env: {
99+
...process.env,
100+
DB_PATH: dbPath,
101+
},
102+
},
103+
);
104+
assert.strictEqual(status, 0, `stderr: ${stderr}`);
105+
}
106+
107+
{
108+
// Opening with open: false should not check permissions at construction,
109+
// but db.open() should still check permissions
110+
const code = sqliteCode.noOpen.replace(/\n/g, ' ');
111+
const { status, stderr } = spawnSync(
112+
process.execPath,
113+
[
114+
'--permission',
115+
'--allow-fs-read=/nonexistent',
116+
'--allow-fs-write=/nonexistent',
117+
'--eval', code,
118+
],
119+
{
120+
...spawnOpts,
121+
env: {
122+
...process.env,
123+
DB_PATH: dbPath,
124+
},
125+
},
126+
);
127+
assert.strictEqual(status, 1, `stderr: ${stderr}`);
128+
assert.match(stderr, /Access to this API has been restricted/);
129+
}
130+
131+
{
132+
// Memory database should not be restricted
133+
const code = `const sqlite = require('node:sqlite');
134+
const db = new sqlite.DatabaseSync(':memory:');
135+
db.close();`.replace(/\n/g, ' ');
136+
const { status, stderr } = spawnSync(
137+
process.execPath,
138+
[
139+
'--permission',
140+
'--allow-fs-read=/nonexistent',
141+
'--eval', code,
142+
],
143+
spawnOpts,
144+
);
145+
assert.strictEqual(status, 0, `stderr: ${stderr}`);
146+
}

0 commit comments

Comments
 (0)