codex_execpolicy/
execpolicycheck.rs1use std::fs;
2use std::path::PathBuf;
3
4use anyhow::Context;
5use anyhow::Result;
6use clap::Parser;
7use serde::Serialize;
8
9use crate::Decision;
10use crate::MatchOptions;
11use crate::Policy;
12use crate::PolicyParser;
13use crate::RuleMatch;
14
15#[derive(Debug, Parser, Clone)]
17pub struct ExecPolicyCheckCommand {
18 #[arg(short = 'r', long = "rules", value_name = "PATH", required = true)]
20 pub rules: Vec<PathBuf>,
21
22 #[arg(long)]
24 pub pretty: bool,
25
26 #[arg(long)]
29 pub resolve_host_executables: bool,
30
31 #[arg(
33 value_name = "COMMAND",
34 required = true,
35 trailing_var_arg = true,
36 allow_hyphen_values = true
37 )]
38 pub command: Vec<String>,
39}
40
41impl ExecPolicyCheckCommand {
42 pub fn run(&self) -> Result<()> {
44 let policy = load_policies(&self.rules)?;
45 let matched_rules = policy.matches_for_command_with_options(
46 &self.command,
47 None,
48 &MatchOptions {
49 resolve_host_executables: self.resolve_host_executables,
50 },
51 );
52
53 let json = format_matches_json(&matched_rules, self.pretty)?;
54 println!("{json}");
55
56 Ok(())
57 }
58}
59
60pub fn format_matches_json(matched_rules: &[RuleMatch], pretty: bool) -> Result<String> {
61 let output = ExecPolicyCheckOutput {
62 matched_rules,
63 decision: matched_rules.iter().map(RuleMatch::decision).max(),
64 };
65
66 if pretty {
67 serde_json::to_string_pretty(&output).map_err(Into::into)
68 } else {
69 serde_json::to_string(&output).map_err(Into::into)
70 }
71}
72
73pub fn load_policies(policy_paths: &[PathBuf]) -> Result<Policy> {
74 let mut parser = PolicyParser::new();
75
76 for policy_path in policy_paths {
77 let policy_file_contents = fs::read_to_string(policy_path)
78 .with_context(|| format!("failed to read policy at {}", policy_path.display()))?;
79 let policy_identifier = policy_path.to_string_lossy().to_string();
80 parser
81 .parse(&policy_identifier, &policy_file_contents)
82 .with_context(|| format!("failed to parse policy at {}", policy_path.display()))?;
83 }
84
85 Ok(parser.build())
86}
87
88#[derive(Serialize)]
89#[serde(rename_all = "camelCase")]
90struct ExecPolicyCheckOutput<'a> {
91 #[serde(rename = "matchedRules")]
92 matched_rules: &'a [RuleMatch],
93 #[serde(skip_serializing_if = "Option::is_none")]
94 decision: Option<Decision>,
95}