Skip to content

Classification de commande (execpolicy)

源码版本rust-v0.145.0

Le crate execpolicy est le moteur de classification de commande de codex : étant donné un argv, il décide s'il faut allow, prompt ou forbidden. Les règles sont écrites en Starlark dans le répertoire rules/ ; au runtime, PolicyParser les compile en Policy, et Policy::check est exécuté avant chaque commande. Côté core, ExecPolicyManager superpose une stratégie d'approval et un heuristique de repli.

Responsabilités

  1. Utiliser une énum Decision à trois valeurs pour exprimer le verdict de chaque règle : codex-rs/execpolicy/src/decision.rs:9-27
  2. Faire du prefix matching et du network domain matching avec deux types de règles PrefixRule / NetworkRule : codex-rs/execpolicy/src/rule.rs:40-115
  3. Le parser Starlark compile le fichier policy en Policy : codex-rs/execpolicy/src/parser.rs:38-83
  4. Côté core, ExecPolicyManager fusionne le verdict de la policy avec la stratégie d'approval et l'heuristique sur commandes dangereuses : codex-rs/core/src/exec_policy.rs:312-410
  5. Pour les commandes sans règle匹配ée, dériver une Decision par défaut selon le profile et la source de la commande : codex-rs/core/src/exec_policy.rs:728-760

Motivations de conception

Pourquoi pas regex ou glob ? Parce que la structure d'une commande est un argv ; le prefix matching est plus précis qu'un regex sur la chaîne, et aussi plus facile à réécrire — quand l'utilisateur clique « toujours autoriser », blocking_append_allow_prefix_rule ajoute directement une ligne prefix_rule(pattern=["npm", "run"], decision="allow") au fichier policy, et le même argv suivra hit la règle au coup suivant. PrefixPattern supporte Single et Alts ([a|b]), couvrant le cas « préfixe + paramètre au choix ».

Pourquoi Starlark plutôt que TOML ? Parce que la policy a besoin de logique conditionnelle (« allow seulement si host est dans telle liste ») et de fonctions (prefix_rule, network_rule), ce que TOML ne sait pas exprimer. Starlark est un sous-ensemble de Python, lisible, tout en pouvant être évalué sûrement côté Rust par le crate starlark. PolicyParser attache l'AST et PolicyBuilder via RefCell, et un build final produit une Policy immutable.

Decision à trois valeurs plutôt que binaire, pour distinguer « interdit explicitement » de « requiert approval ». forbidden bloque direct ; prompt déclenche l'UI d'approval ; allow skip l'approval. Quand plusieurs règles matchent, on prend le max — Allow < Prompt < Forbidden, le verdict le plus strict gagne.

Fichiers clés

Decision à trois valeurs plutôt que binaire, pour distinguer « interdit explicitement » de « requiert approval ».

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,
}

Le prefix matching est plus précis qu'un regex sur la chaîne, et aussi plus facile à réécrire — quand l'utilisateur clique « toujours autoriser », blocking_append_allow_prefix_rule fabrique directement une ligne Starlark et l'ajoute au fichier 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)
}

Côté core, après le verdict de la policy, il superpose encore un heuristique : en cas de non-match, render_decision_for_unmatched_command regarde is_known_safe_command, profile_has_managed_filesystem_restrictions, etc., pour dériver une Decision par défaut.

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 => { /* 走审批流程 */ }
    Decision::Allow => /* 直接放行 */,
}

Flux de données

Limites et échecs

  • parse_shell_lc_plain_commands ne gère que du shell simple ; heredoc/redirection/pipe passent par used_complex_parsing=true et ne peuvent pas dériver de règle corrective automatique : codex-rs/core/src/exec_policy.rs:325-353
  • En cas de conflit entre règles, from_matches prend max(), soit Allow < Prompt < Forbidden, le verdict le plus strict gagne : codex-rs/execpolicy/src/policy.rs:357-374
  • Le host d'network_rule ne peut pas contenir de scheme ou de path ; normalize_network_rule_host refuse une écriture comme https://example.com/foo : codex-rs/execpolicy/src/rule.rs:156-189
  • L'heuristique de repli render_decision_for_unmatched_command regarde windows_sandbox_level et la restriction FS managed du profile — quand le sandbox Windows est désactivé, même un profile très restrictif prend un chemin conservateur : codex-rs/core/src/exec_policy.rs:751-757
  • canonicalize_command_for_approval préfixe les commandes type bash -lc '...' par __codex_shell_script__, pour éviter que des différences de chemin wrapper ne court-circuitent la règle : codex-rs/core/src/command_canonicalization.rs:21-35

Récapitulatif

execpolicy est le cœur de la classification de commande codex : la policy Starlark est compilée en Policy, PrefixRule et NetworkRule font du matching précis, et en cas de non-match, l'heuristique côté core prend le relais. Les trois valeurs de Decision expriment « allow/approval/forbidden » dans une même sémantique ; en cas de conflit de règles, le verdict le plus strict gagne. Comprendre cette chaîne est clé pour déboguer « pourquoi cette commande requiert-elle approval » ou « l'utilisateur a enregistré une règle, pourquoi ne s'applique-t-elle pas » ; l'exécution effective est décrite dans Exécution exec-server et Bac à sable multiplateforme.