"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getPlatformTrustedPrefixes = exports.validateEnvOverridePath = exports.resolveTrustedSqlitePath = exports.querySqliteJson = void 0;
const child_process_1 = require("child_process");
const fs = __importStar(require("fs"));
const util_1 = require("util");
const execFileAsync = (0, util_1.promisify)(child_process_1.execFile);
const SQLITE_JSON_MAX_BUFFER = 10 * 1024 * 1024;
// Trusted system paths per platform. These are fixed, non-user-writable
// locations managed by the OS or a system package manager.
// PATH-hijack threat model: we never resolve from $PATH; we only accept
// binaries whose realpath resolves under one of these prefixes.
const TRUSTED_SQLITE_PATHS_UNIX = [
    '/usr/bin/sqlite3',
    '/usr/local/bin/sqlite3',
    '/opt/homebrew/bin/sqlite3',
];
// Windows has no single canonical system install path for sqlite3
// (winget, Chocolatey, and Scoop all use different locations). An empty
// list means Windows falls through to the CCS_SQLITE_BIN env-var path.
const TRUSTED_SQLITE_PATHS_WINDOWS = [];
// Trusted path prefixes used to validate env-var overrides. A realpath that
// does not start with one of these prefixes is rejected to prevent users or
// CI from pointing CCS_SQLITE_BIN at a writable/untrusted location.
const TRUSTED_PREFIX_UNIX = [
    '/usr/bin/',
    '/usr/local/bin/',
    '/usr/sbin/',
    '/usr/local/sbin/',
    '/opt/homebrew/',
    '/opt/local/', // MacPorts
    '/nix/store/', // Nix / NixOS immutable store
    '/run/current-system/', // NixOS system activation symlink target
    '/snap/', // Snap packages
];
const TRUSTED_PREFIX_WINDOWS = [
    'C:\\Program Files\\',
    'C:\\Program Files (x86)\\',
    'C:\\Windows\\System32\\',
    'C:\\Windows\\SysWOW64\\',
    'C:\\ProgramData\\chocolatey\\bin\\', // Chocolatey managed bin dir
];
function isCommandMissing(error) {
    if (!(error instanceof Error))
        return false;
    const nodeError = error;
    return nodeError.code === 'ENOENT' || /not found/i.test(nodeError.message);
}
function getPlatformTrustedPaths() {
    return process.platform === 'win32' ? TRUSTED_SQLITE_PATHS_WINDOWS : TRUSTED_SQLITE_PATHS_UNIX;
}
function getPlatformTrustedPrefixes() {
    return process.platform === 'win32' ? TRUSTED_PREFIX_WINDOWS : TRUSTED_PREFIX_UNIX;
}
exports.getPlatformTrustedPrefixes = getPlatformTrustedPrefixes;
/**
 * Validate a CCS_SQLITE_BIN override path.
 *
 * Security invariant: the resolved (symlink-expanded) path must start with
 * at least one trusted prefix. This prevents pointing at a binary in a
 * user-writable location such as /tmp, $HOME/.local, or a relative PATH
 * entry, which would reintroduce the PATH-hijack vector closed in #1347.
 *
 * Returns the validated path on success, or throws with an explanation.
 */
function validateEnvOverridePath(rawPath) {
    let resolved;
    try {
        resolved = fs.realpathSync(rawPath);
    }
    catch {
        throw new Error(`CCS_SQLITE_BIN path "${rawPath}" could not be resolved: file not found or inaccessible`);
    }
    // Verify executable bit (or file existence on Windows where X_OK is unreliable).
    try {
        if (process.platform === 'win32') {
            fs.accessSync(resolved, fs.constants.F_OK);
        }
        else {
            fs.accessSync(resolved, fs.constants.X_OK);
        }
    }
    catch {
        throw new Error(`CCS_SQLITE_BIN path "${resolved}" is not executable`);
    }
    const normalizedResolved = process.platform === 'win32' ? resolved.toLowerCase() : resolved;
    const trusted = getPlatformTrustedPrefixes().some((prefix) => {
        const normalizedPrefix = process.platform === 'win32' ? prefix.toLowerCase() : prefix;
        return normalizedResolved.startsWith(normalizedPrefix);
    });
    if (!trusted) {
        throw new Error(`CCS_SQLITE_BIN path "${resolved}" does not resolve under a trusted system prefix. ` +
            `Paths under user-writable locations (e.g. /tmp, $HOME/.local) are rejected ` +
            `to prevent PATH-hijack attacks.`);
    }
    return resolved;
}
exports.validateEnvOverridePath = validateEnvOverridePath;
/**
 * Resolve the sqlite3 binary to use.
 *
 * Resolution order:
 * 1. CCS_SQLITE_BIN env var override (validated against trusted prefixes)
 * 2. First accessible path from the platform's hardcoded trusted list
 * 3. Throw "sqlite3 command not available"
 */
function resolveTrustedSqlitePath(env = process.env) {
    const envOverride = env['CCS_SQLITE_BIN'];
    if (envOverride && envOverride.trim().length > 0) {
        // May throw — caller surfaces the error.
        return validateEnvOverridePath(envOverride.trim());
    }
    const trustedPath = getPlatformTrustedPaths().find((candidate) => {
        try {
            // Resolve symlinks so the check is on the real binary.
            const real = fs.realpathSync(candidate);
            fs.accessSync(real, fs.constants.X_OK);
            return true;
        }
        catch {
            return false;
        }
    });
    if (!trustedPath) {
        throw new Error('sqlite3 command not available');
    }
    // Return the realpath to avoid double-hop symlink confusion at exec time.
    return fs.realpathSync(trustedPath);
}
exports.resolveTrustedSqlitePath = resolveTrustedSqlitePath;
async function querySqliteJson(dbPath, sql, env = process.env) {
    if (!fs.existsSync(dbPath)) {
        return [];
    }
    let sqlitePath;
    try {
        sqlitePath = resolveTrustedSqlitePath(env);
    }
    catch (error) {
        const message = error instanceof Error ? error.message : String(error);
        throw new Error(message);
    }
    try {
        const { stdout } = await execFileAsync(sqlitePath, ['-json', dbPath, sql], {
            maxBuffer: SQLITE_JSON_MAX_BUFFER,
        });
        const trimmed = stdout.trim();
        if (!trimmed) {
            return [];
        }
        const parsed = JSON.parse(trimmed);
        return Array.isArray(parsed) ? parsed : [];
    }
    catch (error) {
        if (isCommandMissing(error)) {
            throw new Error('sqlite3 command not available');
        }
        const message = error instanceof Error ? error.message : String(error);
        throw new Error(`sqlite3 query failed for ${dbPath}: ${message}`);
    }
}
exports.querySqliteJson = querySqliteJson;
//# sourceMappingURL=sqlite-cli.js.map