"use strict";
/**
 * ASI-05 (OWASP Agentic Top 10) — Unsafe Code Execution: shell-sandbox policy.
 *
 * Microsoft AGT parity for the code-execution gap. The runtime can BLOCK or ALLOW
 * a shell call, but a *permitted* command still runs with full process authority.
 * This is the sandbox-POLICY decision layer the runtime enforces before handing a
 * command to its OS sandbox: confine to an allowlist, deny network egress,
 * privilege escalation, filesystem escapes, and shell-injection chaining (which
 * otherwise defeats the allowlist).
 *
 * Scope: this decides what is permitted. OS-PRIMITIVE isolation (namespaces,
 * seccomp, cgroups, a container/firejail/bubblewrap jail) is the runtime's job —
 * this policy is what it should allow INTO that jail. Pure compute; never throws.
 *
 * @connexum/ai-governance
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.DEFAULT_SANDBOX_POLICY = void 0;
exports.evaluateSandboxedCommand = evaluateSandboxedCommand;
/** Restrictive default: no allowlist (deny unknown binaries), no network, no priv-esc. */
exports.DEFAULT_SANDBOX_POLICY = Object.freeze({
    allowedBinaries: [],
    allowNetwork: false,
    allowPrivilegeEscalation: false,
});
const NETWORK_BINARIES = new Set(['curl', 'wget', 'nc', 'ncat', 'telnet', 'ssh', 'scp', 'sftp', 'ftp', 'rsync']);
const PRIV_ESC_BINARIES = new Set(['sudo', 'su', 'doas', 'pkexec']);
/** Shell metacharacters that chain/inject a second command, defeating the allowlist. */
const INJECTION_METACHARS = /[;&|`]|\$\(|\|\||&&|>|<|\n/;
function basename(token) {
    const t = token.trim();
    const slash = t.lastIndexOf('/');
    return slash >= 0 ? t.slice(slash + 1) : t;
}
/**
 * Evaluate whether a permitted shell command is acceptable to run inside the
 * sandbox under `policy`. NEVER throws — bad input is denied with a reason.
 */
function evaluateSandboxedCommand(command, policy = exports.DEFAULT_SANDBOX_POLICY) {
    const violations = [];
    try {
        if (typeof command !== 'string' || command.trim().length === 0) {
            return { allowed: false, reason: 'empty or malformed command', violations: ['empty command'] };
        }
        const cmd = command.trim();
        // 1. Injection / chaining metacharacters — a chained second command bypasses
        //    every per-binary check, so this is a hard violation.
        if (INJECTION_METACHARS.test(cmd)) {
            violations.push('shell metacharacter chaining/injection detected (defeats the allowlist)');
        }
        const tokens = cmd.split(/\s+/);
        const binary = basename(tokens[0] ?? '');
        // 2. Denylist + allowlist.
        if ((policy.blockedBinaries ?? []).map(basename).includes(binary)) {
            violations.push(`binary "${binary}" is explicitly blocked`);
        }
        const allow = policy.allowedBinaries;
        if (allow && !allow.map(basename).includes(binary)) {
            violations.push(`binary "${binary}" is not on the sandbox allowlist (not permitted)`);
        }
        // 3. Network egress.
        if (!policy.allowNetwork && (NETWORK_BINARIES.has(binary) || tokens.some((t) => NETWORK_BINARIES.has(basename(t))))) {
            violations.push(`network command "${binary}" denied (sandbox allowNetwork=false)`);
        }
        // 4. Privilege escalation (incl. setuid via chmod +s / u+s).
        const isSetuid = binary === 'chmod' && /(\+s|u\+s|g\+s|[24]\d{3})/.test(cmd);
        if (!policy.allowPrivilegeEscalation && (PRIV_ESC_BINARIES.has(binary) || isSetuid)) {
            violations.push(`privilege escalation "${binary}" denied (sandbox allowPrivilegeEscalation=false)`);
        }
        // 5. Filesystem confinement: any absolute-path arg must be under an allowed prefix.
        const prefixes = policy.allowedPathPrefixes;
        if (prefixes && prefixes.length > 0) {
            for (const t of tokens.slice(1)) {
                if (t.startsWith('/') && !prefixes.some((p) => t === p || t.startsWith(p.endsWith('/') ? p : p + '/'))) {
                    violations.push(`path "${t}" is outside the sandbox confinement (${prefixes.join(', ')})`);
                }
            }
        }
        const allowed = violations.length === 0;
        return {
            allowed,
            binary,
            violations,
            reason: allowed ? 'within sandbox policy' : `denied by sandbox policy: ${violations.join('; ')}`,
        };
    }
    catch {
        return { allowed: false, reason: 'sandbox evaluation failed; denied', violations: ['evaluation error'] };
    }
}
//# sourceMappingURL=shell-sandbox.js.map