// hosts/codex/workspace-scope.ts — Per-user project path scoping (SPEC-592)
// Prevents cross-workspace spec leakage in list_specs, search_specs,
// and resource enumeration by restricting accessible paths per user.
import { existsSync } from 'node:fs';
import { join, resolve } from 'node:path';
/**
 * Detect Codex workspace markers from filesystem and environment.
 * Called by the adapter; also used directly in tests.
 */
export function detectCodexWorkspace(projectPath) {
    const configDir = join(projectPath, '.openai');
    const hasConfigDir = existsSync(configDir);
    return {
        configDir: hasConfigDir ? configDir : undefined,
        hasWorkspaceEnv: Boolean(process.env.OPENAI_WORKSPACE_ID),
        hasCodexEnv: Boolean(process.env.CODEX_ENV ?? process.env.CODEX_AGENT_ID),
        isEnterprise: Boolean(process.env.OPENAI_ENTERPRISE_TENANT),
        workspaceId: process.env.OPENAI_WORKSPACE_ID,
    };
}
/**
 * Resolve the scoped project path for a user.
 * Ensures no user can access another user's specs by rewriting the path
 * to: <workspaceRoot>/<tenantId>/<userId>
 *
 * If the requestedPath is already within the user's allowed scope,
 * it is returned unchanged. Otherwise, it is rewritten.
 */
export function resolveUserScope(requestedPath, identity, workspaceRoot) {
    const allowedPrefix = resolve(join(workspaceRoot, identity.tenantId, identity.userId));
    const resolved = resolve(requestedPath);
    const isWithinScope = resolved.startsWith(allowedPrefix);
    return {
        userId: identity.userId,
        tenantId: identity.tenantId,
        scopedProjectPath: isWithinScope ? resolved : allowedPrefix,
        requestedPath: resolved,
        pathWasRewritten: !isWithinScope,
    };
}
/**
 * Check whether a given projectPath is accessible to the identity.
 * Used to gate list_specs and search_specs results.
 */
export function isPathAllowed(projectPath, identity, workspaceRoot) {
    const allowedPrefix = resolve(join(workspaceRoot, identity.tenantId, identity.userId));
    const resolved = resolve(projectPath);
    return resolved.startsWith(allowedPrefix);
}
/**
 * Filter a list of project paths to only those accessible to the identity.
 * Zero-leakage guarantee: paths outside the user's scope are silently excluded.
 */
export function filterPathsForUser(paths, identity, workspaceRoot) {
    return paths.filter((p) => isPathAllowed(p, identity, workspaceRoot));
}
/**
 * Guard a tool invocation against cross-workspace leakage in Codex enterprise.
 * No-op when Codex identity is not present (standard dev / other hosts).
 *
 * Used by list_specs, search_specs, and search_suggestions to prevent
 * a Codex user from reading another user's projectPath.
 *
 * Reads `OPENAI_WORKSPACE_ROOT` env var to determine the tenant workspace root.
 * If not set, gating cannot be enforced and the call is allowed (fail-open on
 * misconfiguration — logging should surface this to the enterprise admin).
 */
export function guardProjectPathForCodex(projectPath) {
    // Lazy import to avoid cross-layer coupling at module load time
    const userId = process.env.OPENAI_USER_ID;
    const tenantId = process.env.OPENAI_ENTERPRISE_TENANT;
    if (!userId || !tenantId) {
        return { allowed: true }; // non-Codex or unauthenticated — no gating
    }
    const workspaceRoot = process.env.OPENAI_WORKSPACE_ROOT;
    if (!workspaceRoot) {
        return { allowed: true }; // Codex but no workspace root configured — can't enforce
    }
    const allowedPrefix = resolve(join(workspaceRoot, tenantId, userId));
    const resolvedProjectPath = resolve(projectPath);
    if (resolvedProjectPath.startsWith(allowedPrefix)) {
        return { allowed: true };
    }
    return {
        allowed: false,
        reason: `Path ${projectPath} is outside your Codex workspace scope (${tenantId}/${userId}). Contact your enterprise admin if you believe this is a misconfiguration.`,
    };
}
//# sourceMappingURL=workspace-scope.js.map