Skip to content

Befehlsklassifikation (execpolicy)

源码版本rust-v0.145.0

Der execpolicy-Crate ist die Befehlsklassifikations-Engine von codex: Für ein gegebenes argv entscheidet er, ob allow, prompt oder forbidden. Die Regeln sind in Starlark unter rules/ geschrieben; zur Laufzeit parst PolicyParser sie in eine Policy, und Policy::check läuft vor jedem Befehl. Auf core-Seite legt ExecPolicyManager Approval-Policy und heuristische Fälle darüber.

Verantwortlichkeiten

  1. Mit der dreiwertigen Enum Decision die Entscheidung jeder Regel ausdrücken: codex-rs/execpolicy/src/decision.rs:9-27
  2. Mit PrefixRule / NetworkRule Präfix-Matching und Netzwerk-Domain-Matching durchführen: codex-rs/execpolicy/src/rule.rs:40-115
  3. Starlark-Parser kompiliert die Policy-Datei in eine Policy: codex-rs/execpolicy/src/parser.rs:38-83
  4. Auf core-Seite ExecPolicyManager: Policy-Ergebnis mit Approval-Policy und heuristischem Rückfall für gefährliche Befehle zusammenführen: codex-rs/core/src/exec_policy.rs:312-410
  5. Für Befehle ohne Regelmatch: Standard-Decision aus Profil und Befehlsquelle ableiten: codex-rs/core/src/exec_policy.rs:728-760

Entwurfsbeweggründe

Warum nicht Regex oder Glob? Weil die Befehlsstruktur selbst argv ist und Präfix-Matching präziser als String-Regex ist; zudem ist es einfacher zurückzuschreiben — klickt der Nutzer auf „ab immer erlaubt", hängt blocking_append_allow_prefix_rule einfach eine Zeile prefix_rule(pattern=["npm", "run"], decision="allow") an die Policy-Datei an; beim nächsten Mal matcht dasselbe argv. PrefixPattern unterstützt Single und Alts ([a|b]) als Token und deckt damit „Präfix + Entweder-Oder-Argument" ab.

Warum Starlark statt TOML? Weil Policy bedingte Logik braucht („host in bestimmter Liste → allow") und Funktionen (prefix_rule, network_rule), was TOML nicht ausdrücken kann. Starlark ist eine Python-Teilmenge, Policy-Dateien bleiben lesbar, und der starlark-Crate auf Rust-Seite kann sie sicher auswerten. PolicyParser bindet AST und PolicyBuilder über RefCell zusammen und baut nach dem Parsen in einem Schritt die unveränderliche Policy.

Decision ist drei- statt zweiwertig, um „explizit verboten" von „Approval nötig" zu unterscheiden. forbidden blockiert direkt, prompt löst die Approval-UI aus, allow überspringt die Approval. Treffen mehrere Regeln zu, gewinnt max — Allow < Prompt < Forbidden; die strengste Entscheidung gewinnt.

Wichtige Dateien

Decision ist drei- statt zweiwertig, um „explizit verboten" von „Approval nötig" zu unterscheiden.

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

Präfix-Matching ist präziser als String-Regex und leichter zurückzuschreiben — klickt der Nutzer auf „ab immer erlaubt", baut blocking_append_allow_prefix_rule einen Starlark-Aufruf zusammen und hängt ihn an die Policy-Datei an.

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

Auf core-Seite kommen nach dem Policy-Urteil noch Heuristiken dazu: Bei keinem Regelmatch leitet render_decision_for_unmatched_command aus Bedingungen wie is_known_safe_command und profile_has_managed_filesystem_restrictions eine Standard-Decision ab.

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 => /* 直接放行 */,
}

Datenfluss

Grenzen und Fehler

  • parse_shell_lc_plain_commands verarbeitet nur einfache Shell; Heredoc/Redirect/Pipe nutzen used_complex_parsing=true und erlauben keine automatische Regelableitung: codex-rs/core/src/exec_policy.rs:325-353
  • Bei Konflikten mehrerer Regeln wählt from_matches max(), also Allow < Prompt < Forbidden — die strengste Entscheidung gewinnt: codex-rs/execpolicy/src/policy.rs:357-374
  • Der Host einer network_rule darf kein Scheme oder Pfad enthalten; normalize_network_rule_host lehnt Schreibweisen wie https://example.com/foo ab: codex-rs/execpolicy/src/rule.rs:156-189
  • Der heuristische Rückfall render_decision_for_unmatched_command betrachtet windows_sandbox_level und die managed-FS-Beschränkung des Profils — ist die Windows-Sandbox deaktiviert, geht es auch bei sehr strengem Profil konservativ vor: codex-rs/core/src/exec_policy.rs:751-757
  • canonicalize_command_for_approval versieht bash -lc '...'-Befehle mit dem Präfix __codex_shell_script__, damit Abweichungen im Wrapper-Pfad nicht zu verpassten Regel-Matches führen: codex-rs/core/src/command_canonicalization.rs:21-35

Zusammenfassung

execpolicy ist der Kern der codex-Befehlsklassifikation: Starlark-Policy wird in Policy kompiliert, PrefixRule und NetworkRule matchen präzise, und Treffer ohne Regelmatch laufen über den heuristischen Rückfall auf core-Seite. Das dreiwertige Decision bringt „allow/approval/forbidden" in eine einheitliche Semantik, und bei Regelkonflikten gewinnt die strengste Entscheidung. Diese Kette zu verstehen ist wichtig, um „warum dieser Befehl Approval verlangt" und „warum eine gespeicherte Regel nicht zieht" zu debuggen; die Ausführung landet in Befehlsausführung exec-server und Plattformübergreifende Sandbox.