From 469c9ffde44ca7bba8c9e33365d92b9a819cdec7 Mon Sep 17 00:00:00 2001 From: "Kai (Tam Nhu) Tran" <61256810+kaitranntt@users.noreply.github.com> Date: Sat, 23 May 2026 16:56:20 -0400 Subject: [PATCH] fix(analytics): harden sqlite3 invocation for native Droid usage scan (#1347) * fix(analytics): use trusted sqlite3 binary path * style: apply prettier formatting --- src/web-server/usage/sqlite-cli.ts | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/web-server/usage/sqlite-cli.ts b/src/web-server/usage/sqlite-cli.ts index cde5afcb..ea09a1e0 100644 --- a/src/web-server/usage/sqlite-cli.ts +++ b/src/web-server/usage/sqlite-cli.ts @@ -4,6 +4,11 @@ import { promisify } from 'util'; const execFileAsync = promisify(execFile); const SQLITE_JSON_MAX_BUFFER = 10 * 1024 * 1024; +const TRUSTED_SQLITE_PATHS = [ + '/usr/bin/sqlite3', + '/usr/local/bin/sqlite3', + '/opt/homebrew/bin/sqlite3', +]; export type SqliteJsonRow = Record; @@ -13,13 +18,31 @@ function isCommandMissing(error: unknown): boolean { return nodeError.code === 'ENOENT' || /not found/i.test(nodeError.message); } +function resolveTrustedSqlitePath(): string { + const trustedPath = TRUSTED_SQLITE_PATHS.find((candidate) => { + try { + fs.accessSync(candidate, fs.constants.X_OK); + return true; + } catch { + return false; + } + }); + + if (!trustedPath) { + throw new Error('sqlite3 command not available'); + } + + return trustedPath; +} + export async function querySqliteJson(dbPath: string, sql: string): Promise { if (!fs.existsSync(dbPath)) { return []; } try { - const { stdout } = await execFileAsync('sqlite3', ['-json', dbPath, sql], { + const sqlitePath = resolveTrustedSqlitePath(); + const { stdout } = await execFileAsync(sqlitePath, ['-json', dbPath, sql], { maxBuffer: SQLITE_JSON_MAX_BUFFER, }); const trimmed = stdout.trim();