/**
 * Core sandbox API: command transformer.
 *
 * Takes a user command and resolved sandbox state,
 * returns a spawn-ready WrappedCommand.
 */
import { buildBwrapArgs } from './bwrap.js';
/** Port for the HTTP proxy socat bridge inside the sandbox network namespace. */
const HTTP_PROXY_PORT = 8899;
/** Port for the SOCKS5 proxy socat bridge inside the sandbox network namespace. */
const SOCKS_PROXY_PORT = 1080;
/** Path to the apply-seccomp binary inside the rootfs. */
const SECCOMP_BINARY = '/usr/local/bin/apply-seccomp';
/** Validate a socket path to prevent shell injection. */
function validateSocketPath(path) {
    if (!/^[a-zA-Z0-9._\-\/]+$/.test(path)) {
        throw new Error(`Invalid proxy socket path: ${path}`);
    }
}
/**
 * Wrap a command string with apply-seccomp.
 *
 * apply-seccomp loads the BPF filter, then exec's the command.
 * The filter persists across exec — all children inherit it.
 * Single quotes in the command are escaped for nested bash -c.
 */
function wrapWithSeccomp(command, filterPath) {
    // Escape single quotes for nesting inside bash -c '...'
    const escaped = command.replace(/'/g, "'\\''");
    return `${SECCOMP_BINARY} ${filterPath} bash -c '${escaped}'`;
}
/**
 * Wrap a shell command with WSL + bwrap isolation.
 *
 * Returns a WrappedCommand that the caller spawns with child_process.spawn().
 * The sandbox owns command construction; the caller owns process lifecycle.
 *
 * The `command` parameter is trusted — it comes from the agent, not from user input.
 * Socket paths are validated to prevent injection.
 *
 * @param options.cwd - Override the working directory (Linux path).
 * @param options.httpSocketPath - HTTP proxy socket (set by SandboxManager).
 * @param options.socksSocketPath - SOCKS5 proxy socket (set by SandboxManager).
 */
export function wrapCommand(command, state, options) {
    const bwrapArgs = buildBwrapArgs(state, options);
    const httpSocketPath = options?.httpSocketPath;
    const socksSocketPath = options?.socksSocketPath;
    const seccomp = state.seccompFilterPath;
    // The user command — optionally wrapped with seccomp.
    // In proxy mode, seccomp wraps ONLY the user command (not the socat setup),
    // because socat needs AF_UNIX sockets for the proxy bridges.
    // In non-proxy mode, seccomp wraps the entire command.
    let resolvedCommand = seccomp ? wrapWithSeccomp(command, seccomp) : command;
    if (state.networkMode === 'proxy' && httpSocketPath) {
        validateSocketPath(httpSocketPath);
        if (socksSocketPath)
            validateSocketPath(socksSocketPath);
        // Build socat bridges and env vars for both proxies.
        // Each bwrap has its own network namespace, so ports don't collide.
        const setup = [];
        // HTTP proxy: socat bridge
        setup.push(`socat TCP-LISTEN:${HTTP_PROXY_PORT},reuseaddr,fork UNIX-CONNECT:${httpSocketPath} & HTTP_PID=$!`);
        // SOCKS5 proxy: socat bridge
        if (socksSocketPath) {
            setup.push(`socat TCP-LISTEN:${SOCKS_PROXY_PORT},reuseaddr,fork UNIX-CONNECT:${socksSocketPath} & SOCKS_PID=$!`);
        }
        // Wait for bridges to be ready
        setup.push(`for _i in 1 2 3 4 5; do nc -z 127.0.0.1 ${HTTP_PROXY_PORT} 2>/dev/null && break; sleep 0.1; done`);
        if (socksSocketPath) {
            setup.push(`for _i in 1 2 3 4 5; do nc -z 127.0.0.1 ${SOCKS_PROXY_PORT} 2>/dev/null && break; sleep 0.1; done`);
        }
        // HTTP proxy env vars
        setup.push(`export HTTP_PROXY=http://127.0.0.1:${HTTP_PROXY_PORT}`, `export HTTPS_PROXY=http://127.0.0.1:${HTTP_PROXY_PORT}`, `export http_proxy=http://127.0.0.1:${HTTP_PROXY_PORT}`, `export https_proxy=http://127.0.0.1:${HTTP_PROXY_PORT}`);
        // SOCKS5 proxy env vars (for SSH, databases, gRPC, etc.)
        if (socksSocketPath) {
            setup.push(`export ALL_PROXY=socks5://127.0.0.1:${SOCKS_PROXY_PORT}`, `export all_proxy=socks5://127.0.0.1:${SOCKS_PROXY_PORT}`);
        }
        // Force all traffic through proxies — no bypass, even for localhost.
        // Sandboxed commands have their own network namespace; any external
        // connection must go through the proxy for domain filtering.
        setup.push('export no_proxy=', 'export NO_PROXY=');
        // Route SSH through SOCKS5 proxy for git-over-SSH
        // Use netcat's SOCKS5 client mode (-X 5) since the proxy speaks SOCKS5.
        if (socksSocketPath) {
            setup.push(`export GIT_SSH_COMMAND="ssh -o 'ProxyCommand=socat - SOCKS4A:127.0.0.1:%h:%p,socksport=${SOCKS_PROXY_PORT}'"`);
        }
        const proxySetup = setup.join('; ');
        // Cleanup: kill socat bridges after command finishes
        const cleanup = ['kill $HTTP_PID 2>/dev/null'];
        if (socksSocketPath)
            cleanup.push('kill $SOCKS_PID 2>/dev/null');
        // Two-stage execution: socat bridges start first (need AF_UNIX),
        // then resolvedCommand runs (already wrapped with apply-seccomp if enabled).
        resolvedCommand = `${proxySetup}; ${resolvedCommand}; EXIT=$?; ${cleanup.join('; ')}; true; exit $EXIT`;
    }
    const args = [
        '-d',
        state.distro,
        '--',
        'bwrap',
        ...bwrapArgs,
        '--',
        'bash',
        '-c',
        resolvedCommand
    ];
    return { file: 'wsl', args };
}
//# sourceMappingURL=wrap-command.js.map