import { randomUUID } from 'node:crypto';
import path from 'node:path';
const DEFAULT_DECISION = {
    kind: 'ask',
    reason: 'No security rule matched this tool call.',
};
const SANDBOX_CAPABILITIES = {
    'read-only': {
        allow: ['read', 'ls', 'find', 'grep'],
        deny: ['write', 'edit', 'bash'],
    },
    'workspace-write': {
        allow: ['read', 'ls', 'find', 'grep', 'write', 'edit', 'bash'],
        deny: [],
    },
    'full-access': { allow: ['*'] },
};
const BUILTIN_PROFILES = {
    'read-only': { sandbox: 'read-only', approval: 'never' },
    default: { sandbox: 'workspace-write', approval: 'on-request' },
    auto: { sandbox: 'workspace-write', approval: 'on-failure' },
    'full-access': { sandbox: 'full-access', approval: 'never' },
};
const DEFAULT_BUILTIN_PROFILE = BUILTIN_PROFILES.default;
const DENY_SECRETS_RULE = {
    id: 'deny-secrets',
    priority: 600,
    sensitivity: ['secret', 'credential'],
    decision: { kind: 'deny', reason: 'Secret or credential resources are protected.' },
};
const BASELINE_SECURITY_RULES = [DENY_SECRETS_RULE];
const ASK_WORKSPACE_WRITE_RULE = {
    id: 'ask-workspace-write',
    priority: 260,
    resources: ['file'],
    operations: ['write', 'delete'],
    scopes: ['workspace'],
    decision: {
        kind: 'ask',
        reason: 'Workspace file modifications require approval.',
        prompt: 'Approve this file modification?',
    },
};
const ASK_HIGH_RISK_RULES = [
    {
        id: 'ask-critical-risk',
        priority: 500,
        risk: ['critical'],
        decision: { kind: 'ask', reason: 'Critical risk operation requires approval.' },
    },
    {
        id: 'ask-high-risk',
        priority: 420,
        risk: ['high'],
        decision: { kind: 'ask', reason: 'High risk operation requires approval.' },
    },
];
function builtinRulesForApproval(approval) {
    switch (approval) {
        case 'never':
            return [...BASELINE_SECURITY_RULES];
        case 'on-failure':
            return [...BASELINE_SECURITY_RULES, ...ASK_HIGH_RISK_RULES];
        case 'on-request':
            return [...BASELINE_SECURITY_RULES, ASK_WORKSPACE_WRITE_RULE];
        case 'untrusted':
            return [...BASELINE_SECURITY_RULES, ...ASK_HIGH_RISK_RULES, ASK_WORKSPACE_WRITE_RULE];
    }
}
function defaultDecisionForApproval(approval) {
    if (approval === 'untrusted') {
        return { kind: 'ask', reason: 'Untrusted profile requires approval for unknown tool calls.' };
    }
    return { kind: 'allow' };
}
export class SecurityPolicyEngine {
    entries;
    defaultDecision;
    constructor(options = {}) {
        this.entries = [...(options.rules ?? [])].sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
        this.defaultDecision = options.defaultDecision ?? DEFAULT_DECISION;
    }
    decide(input) {
        return this.evaluate(input).decision;
    }
    evaluate(input) {
        const resources = classifySecurityResources(input);
        const risk = assessRisk({ ...input, resources });
        const context = { ...input, resources, risk };
        const matchedRuleIds = [];
        for (const entry of this.entries) {
            if (matchesSecurityRule(entry, context)) {
                matchedRuleIds.push(entry.id);
                return {
                    evaluationId: randomUUID(),
                    decision: normalizeForInteraction(entry.decision, input.request.interactive),
                    resources,
                    risk,
                    matchedRuleIds,
                };
            }
        }
        return {
            evaluationId: randomUUID(),
            decision: normalizeForInteraction(this.defaultDecision, input.request.interactive),
            resources,
            risk,
            matchedRuleIds,
        };
    }
}
export class SecurityGate {
    engine;
    approvalHandler;
    auditSink;
    constructor(options) {
        this.engine =
            options.engine ??
                createSecurityPolicyEngineForProfile(options.profile, options.config, options.filePolicies);
        this.approvalHandler = options.approvalHandler;
        this.auditSink = options.auditSink;
    }
    async authorize(input) {
        const initial = this.engine.evaluate(input);
        const decision = await this.resolveDecision(input, initial);
        const evaluation = { ...initial, decision };
        await this.audit(input, evaluation);
        return evaluation;
    }
    async resolveDecision(input, evaluation) {
        if (evaluation.decision.kind !== 'ask') {
            return evaluation.decision;
        }
        if (!this.approvalHandler) {
            return evaluation.decision;
        }
        const resolved = await this.approvalHandler({
            request: input.request,
            toolCall: input.toolCall,
            decision: evaluation.decision,
            evaluation,
        });
        return resolved;
    }
    async audit(input, evaluation) {
        if (!this.auditSink) {
            return;
        }
        await this.auditSink({
            ...evaluation,
            sessionId: input.request.sessionId,
            conversationId: input.request.conversationId,
            ...(input.request.traceId ? { traceId: input.request.traceId } : {}),
            toolCallId: input.toolCall.id,
            toolName: input.toolCall.name,
            createdAt: new Date().toISOString(),
        });
    }
}
export function createSecurityGate(options) {
    return new SecurityGate(options);
}
export async function assertSecurityAllowed(input) {
    const evaluation = await input.securityGate.authorize({
        request: input.request,
        toolCall: input.toolCall,
        ...(input.workspaceDir ? { workspaceDir: input.workspaceDir } : {}),
    });
    assertSecurityDecisionAllowed(evaluation.decision);
    return evaluation;
}
export function assertSecurityDecisionAllowed(decision) {
    switch (decision.kind) {
        case 'allow':
            return;
        case 'ask':
            throw new Error(`Tool call requires approval: ${decision.reason}`);
        case 'deny':
            throw new Error(`Tool call denied by security policy: ${decision.reason}`);
    }
}
export function resolveCapabilityPolicy(profile, config = {}, filePolicies = {}) {
    return resolveSecurityProfile(profile, config, [], filePolicies).capabilities;
}
export function isCapabilityExposed(toolName, policy) {
    const normalized = normalizePatternValue(toolName);
    if (policy.deny?.some((entry) => matchesPattern(normalized, entry))) {
        return false;
    }
    if (!policy.allow?.length) {
        return false;
    }
    return policy.allow.some((entry) => matchesPattern(normalized, entry));
}
export function createSecurityPolicyEngineForProfile(profile, config = {}, filePolicies = {}) {
    const resolvedProfile = resolveSecurityProfile(profile, config, [], filePolicies);
    const denyCapabilityRules = resolvedProfile.capabilities.deny?.map((toolName) => ({
        id: `deny-capability-${profile}-${toolName}`,
        priority: 110,
        tools: [toolName],
        decision: {
            kind: 'deny',
            reason: `Tool '${toolName}' is denied by sandbox '${resolvedProfile.sandbox}'.`,
        },
    })) ?? [];
    const allowCapabilityRules = resolvedProfile.capabilities.allow?.map((toolName) => ({
        id: `allow-capability-${profile}-${toolName}`,
        priority: 100,
        tools: [toolName],
        decision: { kind: 'allow' },
    })) ?? [];
    return new SecurityPolicyEngine({
        rules: resolvedProfile.rules.concat(denyCapabilityRules).concat(allowCapabilityRules),
        defaultDecision: resolvedProfile.defaultDecision ?? defaultDecisionForApproval(resolvedProfile.approval),
    });
}
export function classifySecurityResources(input) {
    const { toolCall } = input;
    if (toolCall.name === 'read') {
        return [fileResource('read', stringArg(toolCall.args, 'path'), input.workspaceDir)];
    }
    if (toolCall.name === 'ls') {
        return [fileResource('read', stringArg(toolCall.args, 'path'), input.workspaceDir)];
    }
    if (toolCall.name === 'find' || toolCall.name === 'grep') {
        return [
            fileResource('search', stringArg(toolCall.args, 'path') ?? stringArg(toolCall.args, 'cwd'), input.workspaceDir),
        ];
    }
    if (toolCall.name === 'write' || toolCall.name === 'edit') {
        return [fileResource('write', stringArg(toolCall.args, 'path'), input.workspaceDir)];
    }
    if (toolCall.name === 'bash') {
        const command = stringArg(toolCall.args, 'command') ?? toolCall.name;
        const resources = [
            {
                kind: 'shell',
                operation: 'execute',
                target: command,
                scope: fileScope(stringArg(toolCall.args, 'cwd'), input.workspaceDir),
                sensitivity: 'normal',
            },
        ];
        if (/https?:\/\/|(^|\s)(curl|wget|ssh|scp|rsync)\b/i.test(command)) {
            resources.push({
                kind: 'network',
                operation: 'connect',
                target: command,
                scope: 'external',
                sensitivity: 'normal',
            });
        }
        return resources;
    }
    return [
        {
            kind: 'shell',
            operation: 'execute',
            target: toolCall.name,
            scope: 'unknown',
            sensitivity: 'normal',
        },
    ];
}
export function assessRisk(input) {
    const reasons = [];
    let level = 'low';
    const raise = (next, reason) => {
        if (riskRank(next) > riskRank(level)) {
            level = next;
        }
        reasons.push(reason);
    };
    for (const resource of input.resources) {
        if (resource.sensitivity === 'secret' || resource.sensitivity === 'credential') {
            raise('critical', `Sensitive ${resource.sensitivity} resource`);
        }
        if (resource.kind === 'file' && resource.operation === 'write') {
            raise(resource.scope === 'workspace' ? 'medium' : 'high', `File write in ${resource.scope} scope`);
        }
        if (resource.kind === 'shell') {
            raise('medium', 'Shell/code execution');
            const command = resource.target ?? '';
            if (/\brm\s+-rf\b|:\(\)\s*\{|\bmkfs\b|\bdd\s+if=|\bshutdown\b|\breboot\b/i.test(command)) {
                raise('critical', 'Destructive shell command');
            }
            if (/\bsudo\b|\bchmod\b|\bchown\b|\bkill(all)?\b|\blaunchctl\b/i.test(command)) {
                raise('high', 'Privileged or permission-changing shell command');
            }
            if (/\b(npm|pnpm|yarn|pip|brew|apt|apt-get)\s+(install|add|upgrade|update)\b/i.test(command)) {
                raise('high', 'Dependency or package manager mutation');
            }
            if (/curl\b.*\|\s*(sh|bash)|wget\b.*\|\s*(sh|bash)/i.test(command)) {
                raise('critical', 'Remote script execution');
            }
        }
        if (resource.kind === 'network') {
            raise('high', 'External network access');
        }
    }
    return {
        level,
        reasons: reasons.length ? [...new Set(reasons)] : ['Low risk read-only operation'],
    };
}
export function securityEvaluationDetails(evaluation) {
    return {
        decision: evaluation.decision.kind,
        risk: evaluation.risk.level,
        riskReasons: evaluation.risk.reasons,
        matchedRules: evaluation.matchedRuleIds,
        resources: evaluation.resources.map((resource) => ({
            kind: resource.kind,
            operation: resource.operation,
            scope: resource.scope,
            sensitivity: resource.sensitivity,
            ...(resource.target ? { target: summarizeTarget(resource.target) } : {}),
        })),
    };
}
function resolvedFromBuiltin(name) {
    const builtin = BUILTIN_PROFILES[name] ?? DEFAULT_BUILTIN_PROFILE;
    return {
        sandbox: builtin.sandbox,
        approval: builtin.approval,
        capabilities: SANDBOX_CAPABILITIES[builtin.sandbox],
        rules: builtinRulesForApproval(builtin.approval),
    };
}
function resolveSecurityProfile(profile, config, seen = [], filePolicies = {}) {
    const normalizedProfile = profile.trim();
    if (seen.includes(normalizedProfile)) {
        return resolvedFromBuiltin('default');
    }
    const configured = filePolicies[normalizedProfile] ?? config.profiles?.[normalizedProfile];
    const parent = configured?.extends
        ? resolveSecurityProfile(configured.extends, config, seen.concat(normalizedProfile), filePolicies)
        : resolvedFromBuiltin(normalizedProfile);
    if (!configured) {
        return parent;
    }
    const sandbox = configured.sandbox ?? parent.sandbox;
    const approval = configured.approval ?? parent.approval;
    const sandboxChanged = configured.sandbox !== undefined && configured.sandbox !== parent.sandbox;
    const approvalChanged = configured.approval !== undefined && configured.approval !== parent.approval;
    const inheritedDefault = configured.defaultDecision ?? parent.defaultDecision;
    return {
        sandbox,
        approval,
        capabilities: sandboxChanged ? SANDBOX_CAPABILITIES[sandbox] : parent.capabilities,
        rules: (approvalChanged ? builtinRulesForApproval(approval) : parent.rules).concat(normalizeConfiguredRules(configured.rules)),
        ...(inheritedDefault ? { defaultDecision: inheritedDefault } : {}),
    };
}
function normalizeConfiguredRules(rules) {
    return (rules ?? []).map((rule) => ({
        ...rule,
        priority: rule.priority ?? 300,
    }));
}
function matchesSecurityRule(rule, context) {
    if (rule.tools?.length &&
        !rule.tools.some((tool) => matchesPattern(context.toolCall.name, tool))) {
        return false;
    }
    if (rule.sources?.length && !rule.sources.includes(context.toolCall.source)) {
        return false;
    }
    if (rule.triggers?.length && !rule.triggers.includes(context.request.trigger)) {
        return false;
    }
    if (rule.senderTrusts?.length && !rule.senderTrusts.includes(context.request.senderTrust)) {
        return false;
    }
    if (rule.args && !matchesArgs(rule.args, context.toolCall.args)) {
        return false;
    }
    if (rule.argsRegex && !matchesArgsRegex(rule.argsRegex, context.toolCall.args)) {
        return false;
    }
    if (rule.risk?.length && !rule.risk.includes(context.risk.level)) {
        return false;
    }
    if (hasResourceCriteria(rule) &&
        !context.resources.some((resource) => matchesResourceCriteria(rule, resource))) {
        return false;
    }
    return true;
}
function hasResourceCriteria(rule) {
    return Boolean(rule.resources?.length ||
        rule.operations?.length ||
        rule.scopes?.length ||
        rule.sensitivity?.length);
}
function matchesResourceCriteria(rule, resource) {
    if (rule.resources?.length && !rule.resources.includes(resource.kind)) {
        return false;
    }
    if (rule.operations?.length && !rule.operations.includes(resource.operation)) {
        return false;
    }
    if (rule.scopes?.length && !rule.scopes.includes(resource.scope)) {
        return false;
    }
    if (rule.sensitivity?.length && !rule.sensitivity.includes(resource.sensitivity)) {
        return false;
    }
    return true;
}
function normalizeForInteraction(decision, interactive) {
    if (decision.kind === 'ask' && !interactive) {
        return {
            kind: 'deny',
            reason: `Security policy requires approval but the context is non-interactive: ${decision.reason}`,
        };
    }
    return decision;
}
function fileResource(operation, value, workspaceDir) {
    return {
        kind: 'file',
        operation,
        ...(value ? { target: value } : {}),
        scope: fileScope(value, workspaceDir),
        sensitivity: classifyPathSensitivity(value),
    };
}
function fileScope(value, workspaceDir) {
    if (!value) {
        return 'unknown';
    }
    if (!path.isAbsolute(value)) {
        return 'workspace';
    }
    const normalized = path.resolve(value);
    if (workspaceDir && pathInside(normalized, path.resolve(workspaceDir))) {
        return 'workspace';
    }
    const home = process.env.HOME;
    if (home && pathInside(normalized, path.resolve(home))) {
        return 'home';
    }
    return 'system';
}
function pathInside(value, parent) {
    const relative = path.relative(parent, value);
    return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
}
function classifyPathSensitivity(value) {
    const lower = value?.toLowerCase() ?? '';
    if (!lower) {
        return 'normal';
    }
    if (/(^|\/)\.ssh(\/|$)|id_rsa|id_ed25519|private[_-]?key|credential|password|token|secret|\.pem$|\.key$/i.test(lower)) {
        return 'credential';
    }
    if (/(^|\/)\.env(\.|$)|(^|\/)\.npmrc$|(^|\/)\.pypirc$|(^|\/)secrets?(\/|$)/i.test(lower)) {
        return 'secret';
    }
    if (/(^|\/)(package\.json|pnpm-lock\.yaml|package-lock\.json|yarn\.lock|tsconfig\.json|dockerfile|docker-compose\.ya?ml)$/i.test(lower)) {
        return 'config';
    }
    if (/\.(ts|tsx|js|jsx|py|go|rs|java|kt|swift|c|cc|cpp|h|hpp|css|html|md)$/i.test(lower)) {
        return 'source';
    }
    return 'normal';
}
function matchesArgs(patterns, args) {
    for (const [key, expected] of Object.entries(patterns)) {
        const actual = args[key];
        if (typeof actual !== 'string' || !matchesPattern(actual, expected)) {
            return false;
        }
    }
    return true;
}
function matchesArgsRegex(patterns, args) {
    for (const [key, pattern] of Object.entries(patterns)) {
        const actual = args[key];
        if (typeof actual !== 'string' || !new RegExp(pattern).test(actual)) {
            return false;
        }
    }
    return true;
}
function matchesPattern(value, pattern) {
    const normalizedValue = normalizePatternValue(value);
    const normalizedPattern = normalizePatternValue(pattern);
    if (normalizedPattern === '*') {
        return true;
    }
    if (normalizedPattern.endsWith(':*')) {
        return normalizedValue.startsWith(normalizedPattern.slice(0, -1));
    }
    if (normalizedPattern.endsWith('*')) {
        return normalizedValue.startsWith(normalizedPattern.slice(0, -1));
    }
    return normalizedValue === normalizedPattern;
}
function normalizePatternValue(value) {
    return value.trim().toLowerCase();
}
function stringArg(args, key) {
    const value = args[key];
    return typeof value === 'string' ? value : undefined;
}
function riskRank(level) {
    return { low: 1, medium: 2, high: 3, critical: 4 }[level];
}
function summarizeTarget(value) {
    return value.length > 200
        ? `${value.slice(0, 200)}...[${value.length - 200} chars truncated]`
        : value;
}
export { default } from './extension.js';
//# sourceMappingURL=index.js.map