Codex Handbook
prompts/src/permissions_instructions.rs 388 lines
use codex_context_fragments::ContextualUserFragment;use codex_execpolicy::Policy;use codex_protocol::config_types::ApprovalsReviewer;use codex_protocol::config_types::SandboxMode;use codex_protocol::models::PermissionProfile;use codex_protocol::models::format_allow_prefixes;use codex_protocol::permissions::FileSystemSandboxPolicy;use codex_protocol::permissions::NetworkSandboxPolicy;use codex_protocol::protocol::AskForApproval;use codex_protocol::protocol::GranularApprovalConfig;use codex_protocol::protocol::NetworkAccess;use codex_protocol::protocol::WritableRoot;use codex_utils_template::Template;use std::path::Path;use std::sync::LazyLock;const APPROVAL_POLICY_NEVER: &str =    include_str!("../templates/permissions/approval_policy/never.md");const APPROVAL_POLICY_UNLESS_TRUSTED: &str =    include_str!("../templates/permissions/approval_policy/unless_trusted.md");const APPROVAL_POLICY_ON_FAILURE: &str =    include_str!("../templates/permissions/approval_policy/on_failure.md");const APPROVAL_POLICY_ON_REQUEST_RULE: &str =    include_str!("../templates/permissions/approval_policy/on_request.md");const APPROVAL_POLICY_ON_REQUEST_RULE_REQUEST_PERMISSION: &str =    include_str!("../templates/permissions/approval_policy/on_request_rule_request_permission.md");const AUTO_REVIEW_APPROVAL_SUFFIX: &str = "`approvals_reviewer` is `auto_review`: Sandbox escalations with require_escalated will be reviewed for compliance with the policy. If a rejection happens, you should proceed only with a materially safer alternative, or inform the user of the risk and send a final message to ask for approval.";const SANDBOX_MODE_DANGER_FULL_ACCESS: &str =    include_str!("../templates/permissions/sandbox_mode/danger_full_access.md");const SANDBOX_MODE_WORKSPACE_WRITE: &str =    include_str!("../templates/permissions/sandbox_mode/workspace_write.md");const SANDBOX_MODE_READ_ONLY: &str =    include_str!("../templates/permissions/sandbox_mode/read_only.md");static SANDBOX_MODE_DANGER_FULL_ACCESS_TEMPLATE: LazyLock<Template> = LazyLock::new(|| {    Template::parse(SANDBOX_MODE_DANGER_FULL_ACCESS.trim_end())        .unwrap_or_else(|err| panic!("danger-full-access sandbox template must parse: {err}"))});static SANDBOX_MODE_WORKSPACE_WRITE_TEMPLATE: LazyLock<Template> = LazyLock::new(|| {    Template::parse(SANDBOX_MODE_WORKSPACE_WRITE.trim_end())        .unwrap_or_else(|err| panic!("workspace-write sandbox template must parse: {err}"))});static SANDBOX_MODE_READ_ONLY_TEMPLATE: LazyLock<Template> = LazyLock::new(|| {    Template::parse(SANDBOX_MODE_READ_ONLY.trim_end())        .unwrap_or_else(|err| panic!("read-only sandbox template must parse: {err}"))});struct PermissionsPromptConfig<'a> {    approval_policy: AskForApproval,    approvals_reviewer: ApprovalsReviewer,    exec_policy: &'a Policy,    exec_permission_approvals_enabled: bool,    request_permissions_tool_enabled: bool,}#[derive(Debug, Clone, PartialEq)]/// Developer instructions that describe the active sandbox and approval policy.pub struct PermissionsInstructions {    text: String,}impl PermissionsInstructions {    /// Builds permissions instructions from the effective permission profile and approval policy.    pub fn from_permission_profile(        permission_profile: &PermissionProfile,        approval_policy: AskForApproval,        approvals_reviewer: ApprovalsReviewer,        exec_policy: &Policy,        cwd: &Path,        exec_permission_approvals_enabled: bool,        request_permissions_tool_enabled: bool,    ) -> Self {        let file_system_sandbox_policy = permission_profile.file_system_sandbox_policy();        let (sandbox_mode, writable_roots) =            sandbox_prompt_from_policy(&file_system_sandbox_policy, cwd);        Self::from_permissions_with_network_and_denied_reads(            sandbox_mode,            network_access_from_policy(permission_profile.network_sandbox_policy()),            PermissionsPromptConfig {                approval_policy,                approvals_reviewer,                exec_policy,                exec_permission_approvals_enabled,                request_permissions_tool_enabled,            },            writable_roots,            denied_reads_text(&file_system_sandbox_policy, cwd),        )    }    pub fn body(&self) -> String {        self.text.clone()    }    #[cfg(test)]    fn from_permissions_with_network(        sandbox_mode: SandboxMode,        network_access: NetworkAccess,        config: PermissionsPromptConfig<'_>,        writable_roots: Option<Vec<WritableRoot>>,    ) -> Self {        Self::from_permissions_with_network_and_denied_reads(            sandbox_mode,            network_access,            config,            writable_roots,            /*denied_reads*/ None,        )    }    fn from_permissions_with_network_and_denied_reads(        sandbox_mode: SandboxMode,        network_access: NetworkAccess,        config: PermissionsPromptConfig<'_>,        writable_roots: Option<Vec<WritableRoot>>,        denied_reads: Option<String>,    ) -> Self {        let mut text = String::new();        append_section(&mut text, &sandbox_text(sandbox_mode, network_access));        append_section(            &mut text,            &approval_text(                config.approval_policy,                config.approvals_reviewer,                config.exec_policy,                config.exec_permission_approvals_enabled,                config.request_permissions_tool_enabled,            ),        );        if let Some(writable_roots) = writable_roots_text(writable_roots) {            append_section(&mut text, &writable_roots);        }        if let Some(denied_reads) = denied_reads {            append_section(&mut text, &denied_reads);        }        if !text.ends_with('\n') {            text.push('\n');        }        Self { text }    }}impl ContextualUserFragment for PermissionsInstructions {    fn role(&self) -> &'static str {        "developer"    }    fn markers(&self) -> (&'static str, &'static str) {        Self::type_markers()    }    fn type_markers() -> (&'static str, &'static str) {        ("<permissions instructions>", "</permissions instructions>")    }    fn body(&self) -> String {        PermissionsInstructions::body(self)    }}fn sandbox_prompt_from_policy(    file_system_policy: &FileSystemSandboxPolicy,    cwd: &Path,) -> (SandboxMode, Option<Vec<WritableRoot>>) {    if file_system_policy.has_full_disk_write_access() {        return (SandboxMode::DangerFullAccess, None);    }    let writable_roots = file_system_policy.get_writable_roots_with_cwd(cwd);    if writable_roots.is_empty() {        (SandboxMode::ReadOnly, None)    } else {        (SandboxMode::WorkspaceWrite, Some(writable_roots))    }}fn network_access_from_policy(network_policy: NetworkSandboxPolicy) -> NetworkAccess {    if network_policy.is_enabled() {        NetworkAccess::Enabled    } else {        NetworkAccess::Restricted    }}fn append_section(text: &mut String, section: &str) {    if !text.ends_with('\n') {        text.push('\n');    }    text.push_str(section);}fn approval_text(    approval_policy: AskForApproval,    approvals_reviewer: ApprovalsReviewer,    exec_policy: &Policy,    exec_permission_approvals_enabled: bool,    request_permissions_tool_enabled: bool,) -> String {    let with_request_permissions_tool = |text: &str| {        if request_permissions_tool_enabled {            format!("{text}\n\n{}", request_permissions_tool_prompt_section())        } else {            text.to_string()        }    };    let on_request_instructions = || {        let on_request_rule = if exec_permission_approvals_enabled {            APPROVAL_POLICY_ON_REQUEST_RULE_REQUEST_PERMISSION.to_string()        } else {            APPROVAL_POLICY_ON_REQUEST_RULE.to_string()        };        let mut sections = vec![on_request_rule];        if request_permissions_tool_enabled {            sections.push(request_permissions_tool_prompt_section().to_string());        }        if let Some(prefixes) = approved_command_prefixes_text(exec_policy) {            sections.push(format!(                "## Approved command prefixes\nThe following prefix rules have already been approved: {prefixes}"            ));        }        sections.join("\n\n")    };    let text = match approval_policy {        AskForApproval::Never => APPROVAL_POLICY_NEVER.to_string(),        AskForApproval::UnlessTrusted => {            with_request_permissions_tool(APPROVAL_POLICY_UNLESS_TRUSTED)        }        AskForApproval::OnFailure => with_request_permissions_tool(APPROVAL_POLICY_ON_FAILURE),        AskForApproval::OnRequest => on_request_instructions(),        AskForApproval::Granular(granular_config) => granular_instructions(            granular_config,            exec_policy,            exec_permission_approvals_enabled,            request_permissions_tool_enabled,        ),    };    if approvals_reviewer == ApprovalsReviewer::AutoReview        && approval_policy != AskForApproval::Never    {        format!("{text}\n\n{AUTO_REVIEW_APPROVAL_SUFFIX}")    } else {        text    }}fn sandbox_text(mode: SandboxMode, network_access: NetworkAccess) -> String {    let template = match mode {        SandboxMode::DangerFullAccess => &*SANDBOX_MODE_DANGER_FULL_ACCESS_TEMPLATE,        SandboxMode::WorkspaceWrite => &*SANDBOX_MODE_WORKSPACE_WRITE_TEMPLATE,        SandboxMode::ReadOnly => &*SANDBOX_MODE_READ_ONLY_TEMPLATE,    };    let network_access = network_access.to_string();    template        .render([("network_access", network_access.as_str())])        .unwrap_or_else(|err| panic!("sandbox template must render: {err}"))}fn writable_roots_text(writable_roots: Option<Vec<WritableRoot>>) -> Option<String> {    let mut roots = writable_roots?;    if roots.is_empty() {        return None;    }    roots.sort_by(|left, right| left.root.as_path().cmp(right.root.as_path()));    let roots_list: Vec<String> = roots        .iter()        .map(|r| format!("`{}`", r.root.to_string_lossy()))        .collect();    Some(if roots_list.len() == 1 {        format!(" The writable root is {}.", roots_list[0])    } else {        format!(" The writable roots are {}.", roots_list.join(", "))    })}fn denied_reads_text(file_system_policy: &FileSystemSandboxPolicy, cwd: &Path) -> Option<String> {    let mut entries = file_system_policy        .get_unreadable_roots_with_cwd(cwd)        .into_iter()        .map(|root| format!("- path `{}`", root.to_string_lossy()))        .collect::<Vec<_>>();    entries.extend(        file_system_policy            .get_unreadable_globs_with_cwd(cwd)            .into_iter()            .map(|glob| format!("- glob `{glob}`")),    );    if entries.is_empty() {        return None;    }    Some(format!(        "## Denied filesystem reads\nThe active permission profile denies reading these paths/globs. Do not request escalation or additional permissions to read them; these denials are policy restrictions.\n{}",        entries.join("\n")    ))}fn approved_command_prefixes_text(exec_policy: &Policy) -> Option<String> {    format_allow_prefixes(exec_policy.get_allowed_prefixes())        .filter(|prefixes| !prefixes.is_empty())}fn granular_prompt_intro_text() -> &'static str {    "# Approval Requests\n\nApproval policy is `granular`. Categories set to `false` are automatically rejected instead of prompting the user."}fn request_permissions_tool_prompt_section() -> &'static str {    "# request_permissions Tool\n\nThe built-in `request_permissions` tool is available in this session. Invoke it when you need to request additional `network` or `file_system` permissions before later shell-like commands need them. Request only the specific permissions required for the task."}fn granular_instructions(    granular_config: GranularApprovalConfig,    exec_policy: &Policy,    exec_permission_approvals_enabled: bool,    request_permissions_tool_enabled: bool,) -> String {    let sandbox_approval_prompts_allowed = granular_config.allows_sandbox_approval();    let shell_permission_requests_available =        exec_permission_approvals_enabled && sandbox_approval_prompts_allowed;    let request_permissions_tool_prompts_allowed =        request_permissions_tool_enabled && granular_config.allows_request_permissions();    let categories = [        Some((            granular_config.allows_sandbox_approval(),            "`sandbox_approval`",        )),        Some((granular_config.allows_rules_approval(), "`rules`")),        Some((granular_config.allows_skill_approval(), "`skill_approval`")),        request_permissions_tool_enabled.then_some((            granular_config.allows_request_permissions(),            "`request_permissions`",        )),        Some((            granular_config.allows_mcp_elicitations(),            "`mcp_elicitations`",        )),    ];    let prompted_categories = categories        .iter()        .flatten()        .filter(|&&(is_allowed, _)| is_allowed)        .map(|&(_, category)| format!("- {category}"))        .collect::<Vec<_>>();    let rejected_categories = categories        .iter()        .flatten()        .filter(|&&(is_allowed, _)| !is_allowed)        .map(|&(_, category)| format!("- {category}"))        .collect::<Vec<_>>();    let mut sections = vec![granular_prompt_intro_text().to_string()];    if !prompted_categories.is_empty() {        sections.push(format!(            "These approval categories may still prompt the user when needed:\n{}",            prompted_categories.join("\n")        ));    }    if !rejected_categories.is_empty() {        sections.push(format!(            "These approval categories are automatically rejected instead of prompting the user:\n{}",            rejected_categories.join("\n")        ));    }    if shell_permission_requests_available {        sections.push(APPROVAL_POLICY_ON_REQUEST_RULE_REQUEST_PERMISSION.to_string());    }    if request_permissions_tool_prompts_allowed {        sections.push(request_permissions_tool_prompt_section().to_string());    }    if let Some(prefixes) = approved_command_prefixes_text(exec_policy) {        sections.push(format!(            "## Approved command prefixes\nThe following prefix rules have already been approved: {prefixes}"        ));    }    sections.join("\n\n")}#[cfg(test)]#[path = "permissions_instructions_tests.rs"]mod permissions_instructions_tests;