Skip to content

Clasificación de comandos (execpolicy)

源码版本rust-v0.145.0

El crate execpolicy es el motor de clasificación de comandos de codex: dado un argv, decide si se allow, prompt o forbidden. Las reglas se escriben en Starlark en el directorio rules/; en runtime, PolicyParser lo parsea a Policy, y Policy::check se ejecuta antes de cada comando. El ExecPolicyManager de core añade por encima la política de aprobación y los heurísticos de retroceso.

Responsabilidades

  1. Usar el enum de tres valores Decision para expresar la sentencia de cada regla: codex-rs/execpolicy/src/decision.rs:9-27
  2. Aplicar PrefixRule / NetworkRule para matching por prefijo y por dominio de red: codex-rs/execpolicy/src/rule.rs:40-115
  3. Compilar el archivo policy Starlark a Policy con el parser: codex-rs/execpolicy/src/parser.rs:38-83
  4. Combinar en core (ExecPolicyManager) el resultado de la policy con la política de aprobación y los heurísticos de comandos peligrosos: codex-rs/core/src/exec_policy.rs:312-410
  5. Para comandos sin regla que coincida, derivar el Decision por defecto a partir del profile y del origen del comando: codex-rs/core/src/exec_policy.rs:728-760

Motivación de diseño

¿Por qué no regex o glob? Porque la estructura del comando es en sí un argv, y el matching por prefijo es más preciso que un regex sobre la cadena, y también más fácil de reescribir: cuando el usuario marca «de ahora en adelante permite siempre», blocking_append_allow_prefix_rule añade directamente al archivo de policy una línea prefix_rule(pattern=["npm", "run"], decision="allow"), y la próxima vez el mismo argv hace match. PrefixPattern soporta dos tokens, Single y Alts ([a|b]), cubriendo el escenario de «prefijo + parámetro con dos opciones».

¿Por qué Starlark y no TOML? Porque la policy necesita lógica condicional («allow solo si host está en tal lista») y funciones (prefix_rule, network_rule), y TOML no lo expresa. Starlark es un subconjunto de Python, los archivos de policy son legibles y además el crate starlark de Rust puede evaluarlos de forma segura. PolicyParser ata el AST y el PolicyBuilder con RefCell, y al terminar de parsear hace un build único que produce un Policy inmutable.

Decision tiene tres valores y no dos, para distinguir «prohibido explícitamente» de «requiere aprobación». forbidden bloquea directamente; prompt dispara la UI de aprobación del usuario; allow salta la aprobación. Cuando varias reglas coinciden a la vez, se toma el máximo — Allow < Prompt < Forbidden —, gana la sentencia más estricta.

Archivos clave

Decision con tres valores y no dos, para distinguir «prohibido explícitamente» de «requiere aprobación».

rust
pub enum Decision {
    /// Command may run without further approval.
    Allow,
    /// Request explicit user approval; rejected outright when running with `approval_policy="never"`.
    Prompt,
    /// Command is blocked without further consideration.
    Forbidden,
}

El matching por prefijo es más preciso que el regex sobre la cadena y también más fácil de reescribir: cuando el usuario marca «de ahora en adelante permite siempre», blocking_append_allow_prefix_rule construye una llamada Starlark y la añade al archivo de policy.

rust
pub fn blocking_append_allow_prefix_rule(
    policy_path: &Path,
    prefix: &[String],
) -> Result<(), AmendError> {
    if prefix.is_empty() {
        return Err(AmendError::EmptyPrefix);
    }
    let tokens = prefix
        .iter()
        .map(serde_json::to_string)
        .collect::<Result<Vec<_>, _>>()
        .map_err(|source| AmendError::SerializePrefix { source })?;
    let pattern = format!("[{}]", tokens.join(", "));
    let rule = format!(r#"prefix_rule(pattern={pattern}, decision="allow")"#);
    append_rule_line(policy_path, &rule)
}

core, tras recibir la sentencia de la policy, aún añade heurísticos: si ninguna regla coincidió, render_decision_for_unmatched_command mira condiciones como is_known_safe_command o profile_has_managed_filesystem_restrictions para derivar el Decision por defecto.

rust
let evaluation = exec_policy.check_multiple_with_options(
    commands.iter(),
    &exec_policy_fallback,
    &match_options,
);
// ...
match evaluation.decision {
    Decision::Forbidden => ExecApprovalRequirement::Forbidden { reason: /* ... */ },
    Decision::Prompt => { /* flujo de aprobación */ }
    Decision::Allow => /* pase directo */,
}

Flujo de datos

Bordes y fallos

  • parse_shell_lc_plain_commands solo maneja shell simple; heredoc/redirección/pipe van por used_complex_parsing=true y no se permite derivar correcciones automáticas de reglas: codex-rs/core/src/exec_policy.rs:325-353
  • Cuando varias reglas entran en conflicto, from_matches toma max(), es decir, Allow < Prompt < Forbidden; gana la sentencia más estricta: codex-rs/execpolicy/src/policy.rs:357-374
  • El host de un network_rule no puede llevar scheme ni path; normalize_network_rule_host rechaza escrituras tipo https://example.com/foo: codex-rs/execpolicy/src/rule.rs:156-189
  • El retroceso heurístico render_decision_for_unmatched_command mira windows_sandbox_level y las restricciones FS managed del profile: si el sandbox de Windows está apagado, aunque el profile sea muy estricto, se va por una ruta conservadora: codex-rs/core/src/exec_policy.rs:751-757
  • canonicalize_command_for_approval añade el prefijo __codex_shell_script__ a los comandos tipo bash -lc '...', para evitar que las diferencias de ruta del wrapper hagan que las reglas no coincidan: codex-rs/core/src/command_canonicalization.rs:21-35

Resumen

execpolicy es el núcleo de la clasificación de comandos en codex: la policy Starlark se compila a Policy, PrefixRule y NetworkRule hacen matching exacto, y lo que no coincide se va al retroceso heurístico del lado core. Decision con tres valores permite expresar «permitir/aprobar/prohibir» en una misma semántica; ante conflictos, gana la sentencia más estricta. Entender esta cadena es clave para depurar «por qué este comando pide aprobación» o «por qué la regla guardada por el usuario no surte efecto». La ejecución asociada cae en Ejecución de comandos exec-server y Sandbox multiplataforma.