diff --git a/src/node_sqlite.cc b/src/node_sqlite.cc index 9c3aa6e0b4dc5f..e03ac41efc2954 100644 --- a/src/node_sqlite.cc +++ b/src/node_sqlite.cc @@ -912,6 +912,19 @@ bool DatabaseSync::Open() { return false; } + // Permission checks: skip for in-memory databases, enforce FS permissions + // for file-backed databases. + std::string_view db_path = open_config_.location(); + if (db_path != ":memory:" && !db_path.empty()) { + if (open_config_.get_read_only()) { + THROW_IF_INSUFFICIENT_PERMISSIONS( + env(), permission::PermissionScope::kFileSystemRead, db_path, false); + } else { + THROW_IF_INSUFFICIENT_PERMISSIONS( + env(), permission::PermissionScope::kFileSystemWrite, db_path, false); + } + } + // TODO(cjihrig): Support additional flags. int default_flags = SQLITE_OPEN_URI; int flags = open_config_.get_read_only() diff --git a/test/fixtures/permission/sqlite.db b/test/fixtures/permission/sqlite.db new file mode 100644 index 00000000000000..2321b9eac82af6 Binary files /dev/null and b/test/fixtures/permission/sqlite.db differ diff --git a/test/parallel/test-permission-sqlite.js b/test/parallel/test-permission-sqlite.js new file mode 100644 index 00000000000000..9dafe5e7abe11d --- /dev/null +++ b/test/parallel/test-permission-sqlite.js @@ -0,0 +1,146 @@ +// Flags: --permission --allow-fs-read=* --allow-fs-write=* --allow-child-process +'use strict'; + +const common = require('../common'); +common.skipIfSQLiteMissing(); + +const { isMainThread } = require('worker_threads'); + +if (!isMainThread) { + common.skip('This test only works on a main thread'); +} + +const assert = require('node:assert'); +const { spawnSync } = require('node:child_process'); +const fixtures = require('../common/fixtures'); + +const spawnOpts = { + encoding: 'utf8', +}; + +const sqliteCode = { + // Open database in read-only mode (should require fs.read) + readOnly: `const sqlite = require('node:sqlite'); + const db = new sqlite.DatabaseSync(process.env.DB_PATH, { readOnly: true }); + db.close();`, + + // Open database in read-write mode (should require fs.write) + readWrite: `const sqlite = require('node:sqlite'); + const db = new sqlite.DatabaseSync(process.env.DB_PATH); + db.close();`, + + // Open database with options.open = false (no FS access expected at construction) + noOpen: `const sqlite = require('node:sqlite'); + const db = new sqlite.DatabaseSync(process.env.DB_PATH, { open: false }); + db.open(); + db.close();`, +}; + +const dbPath = fixtures.path('permission', 'sqlite.db'); + +{ + // Opening a database read-only should be blocked when fs.read is not granted + const code = sqliteCode.readOnly.replace(/\n/g, ' '); + const { status, stderr } = spawnSync( + process.execPath, + [ + '--permission', + '--allow-fs-read=/nonexistent', + '--eval', code, + ], + { + ...spawnOpts, + env: { + ...process.env, + DB_PATH: dbPath, + }, + }, + ); + assert.strictEqual(status, 1, `stderr: ${stderr}`); + assert.match(stderr, /Access to this API has been restricted/); +} + +{ + // Opening a database read-write should be blocked when fs.write is not granted + const code = sqliteCode.readWrite.replace(/\n/g, ' '); + const { status, stderr } = spawnSync( + process.execPath, + [ + '--permission', + '--allow-fs-write=/nonexistent', + '--eval', code, + ], + { + ...spawnOpts, + env: { + ...process.env, + DB_PATH: dbPath, + }, + }, + ); + assert.strictEqual(status, 1, `stderr: ${stderr}`); + assert.match(stderr, /Access to this API has been restricted/); +} + +{ + // Opening a database read-write should be allowed when permissions are granted + const code = sqliteCode.readWrite.replace(/\n/g, ' '); + const { status, stderr } = spawnSync( + process.execPath, + [ + '--permission', + '--allow-fs-write=*', + '--allow-fs-read=*', + '--eval', code, + ], + { + ...spawnOpts, + env: { + ...process.env, + DB_PATH: dbPath, + }, + }, + ); + assert.strictEqual(status, 0, `stderr: ${stderr}`); +} + +{ + // Opening with open: false should not check permissions at construction, + // but db.open() should still check permissions + const code = sqliteCode.noOpen.replace(/\n/g, ' '); + const { status, stderr } = spawnSync( + process.execPath, + [ + '--permission', + '--allow-fs-read=/nonexistent', + '--allow-fs-write=/nonexistent', + '--eval', code, + ], + { + ...spawnOpts, + env: { + ...process.env, + DB_PATH: dbPath, + }, + }, + ); + assert.strictEqual(status, 1, `stderr: ${stderr}`); + assert.match(stderr, /Access to this API has been restricted/); +} + +{ + // Memory database should not be restricted + const code = `const sqlite = require('node:sqlite'); + const db = new sqlite.DatabaseSync(':memory:'); + db.close();`.replace(/\n/g, ' '); + const { status, stderr } = spawnSync( + process.execPath, + [ + '--permission', + '--allow-fs-read=/nonexistent', + '--eval', code, + ], + spawnOpts, + ); + assert.strictEqual(status, 0, `stderr: ${stderr}`); +}