mirror of
https://github.com/openai/codex.git
synced 2026-11-06 20:20:34 +00:00
Add opt-in Unicode canonical matching to exec policy rules (#59535)
## What changed - Support `canonical_matching=True` in `prefix_rule` and `canonical_matching = true` in managed TOML prefix rules. Apply Unicode canonical caseless matching to every prefix token, including alternatives, while keeping exact matching as the default. - Match case and canonically equivalent Unicode spellings, such as `bash` and `bAſh`, without resolving executables on the filesystem. - Preserve managed restrictions when checking absolute-path rule amendments, and enforce applicable `host_executable` path allowlists during basename fallback. ## Testing Add coverage for case folding, Unicode normalization, token alternatives, exact-match defaults, and host executable allowlists. Extend managed-policy and shell-execution tests to cover canonical matching. GitOrigin-RevId: c2f79069dc38d55c77028c457b22beb468d5c571
This commit is contained in:
@@ -256,6 +256,7 @@ fn add_prefix_rule_extends_policy() -> Result<()> {
|
||||
assert_eq!(
|
||||
vec![RuleSnapshot::Prefix(PrefixRule {
|
||||
pattern: PrefixPattern {
|
||||
canonical_matching: false,
|
||||
first: Arc::from("ls"),
|
||||
rest: vec![PatternToken::Single(String::from("-l"))].into(),
|
||||
},
|
||||
@@ -317,6 +318,7 @@ prefix_rule(
|
||||
vec![
|
||||
RuleSnapshot::Prefix(PrefixRule {
|
||||
pattern: PrefixPattern {
|
||||
canonical_matching: false,
|
||||
first: Arc::from("git"),
|
||||
rest: Vec::<PatternToken>::new().into(),
|
||||
},
|
||||
@@ -325,6 +327,7 @@ prefix_rule(
|
||||
}),
|
||||
RuleSnapshot::Prefix(PrefixRule {
|
||||
pattern: PrefixPattern {
|
||||
canonical_matching: false,
|
||||
first: Arc::from("git"),
|
||||
rest: vec![PatternToken::Single("commit".to_string())].into(),
|
||||
},
|
||||
@@ -394,6 +397,7 @@ prefix_rule(
|
||||
assert_eq!(
|
||||
vec![RuleSnapshot::Prefix(PrefixRule {
|
||||
pattern: PrefixPattern {
|
||||
canonical_matching: false,
|
||||
first: Arc::from("bash"),
|
||||
rest: vec![PatternToken::Alts(vec!["-c".to_string(), "-l".to_string()])].into(),
|
||||
},
|
||||
@@ -405,6 +409,7 @@ prefix_rule(
|
||||
assert_eq!(
|
||||
vec![RuleSnapshot::Prefix(PrefixRule {
|
||||
pattern: PrefixPattern {
|
||||
canonical_matching: false,
|
||||
first: Arc::from("sh"),
|
||||
rest: vec![PatternToken::Alts(vec!["-c".to_string(), "-l".to_string()])].into(),
|
||||
},
|
||||
@@ -459,6 +464,7 @@ prefix_rule(
|
||||
assert_eq!(
|
||||
vec![RuleSnapshot::Prefix(PrefixRule {
|
||||
pattern: PrefixPattern {
|
||||
canonical_matching: false,
|
||||
first: Arc::from("npm"),
|
||||
rest: vec![
|
||||
PatternToken::Alts(vec!["i".to_string(), "install".to_string()]),
|
||||
@@ -810,7 +816,7 @@ fn inherited_absolute_amendments_preserve_managed_basename_restrictions() -> Res
|
||||
let mut parser = PolicyParser::new();
|
||||
parser.parse(
|
||||
"managed.rules",
|
||||
r#"prefix_rule(pattern = ["git", ["push", "send-pack"]], decision = "forbidden")"#,
|
||||
r#"prefix_rule(pattern = ["git", ["push", "send-pack"]], canonical_matching=True, decision = "forbidden")"#,
|
||||
)?;
|
||||
let managed = parser.build();
|
||||
let requirements = RequirementsExecPolicy::new(managed.clone());
|
||||
@@ -819,6 +825,7 @@ fn inherited_absolute_amendments_preserve_managed_basename_restrictions() -> Res
|
||||
(&["status"][..], Decision::Allow, true),
|
||||
(&[], Decision::Allow, false),
|
||||
(&["send-pack", "origin"], Decision::Allow, false),
|
||||
(&["PUſH"], Decision::Allow, false),
|
||||
(&["push"], Decision::Forbidden, true),
|
||||
] {
|
||||
let mut snapshot = managed.clone();
|
||||
@@ -830,6 +837,20 @@ fn inherited_absolute_amendments_preserve_managed_basename_restrictions() -> Res
|
||||
accepted
|
||||
);
|
||||
}
|
||||
let path = starlark_string(&git);
|
||||
for (insensitive, accepted) in [("True", true), ("False", false)] {
|
||||
let mut parser = PolicyParser::new();
|
||||
parser.parse("path.rules", &format!(
|
||||
r#"prefix_rule(pattern=["{path}", ["push", "send-pack"]], canonical_matching={insensitive}, decision="forbidden")"#
|
||||
))?;
|
||||
let managed = managed.merge_overlay(&parser.build());
|
||||
let mut snapshot = managed.clone();
|
||||
snapshot.add_prefix_rule(std::slice::from_ref(&git), Decision::Allow)?;
|
||||
assert_eq!(
|
||||
snapshot.preserves_managed_requirements(&RequirementsExecPolicy::new(managed)),
|
||||
accepted
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -877,6 +898,31 @@ host_executable(name = "git", paths = ["{allowed_git_literal}"])
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn canonical_host_executable_resolution_uses_only_relevant_declarations() -> Result<()> {
|
||||
let bash_path = starlark_string(&host_absolute_path(&[
|
||||
"usr",
|
||||
"bin",
|
||||
&host_executable_name("bash"),
|
||||
]));
|
||||
// The long s keeps the executable lookup keys distinct on Windows too.
|
||||
let long_s_bash_path = starlark_string(&host_absolute_path(&[
|
||||
"usr",
|
||||
"bin",
|
||||
&host_executable_name("baſh"),
|
||||
]));
|
||||
let policy_src = format!(
|
||||
r#"
|
||||
prefix_rule(pattern = ["bash"], canonical_matching = True, decision = "forbidden",
|
||||
match = [["{bash_path}"]], not_match = [["{long_s_bash_path}"]])
|
||||
host_executable(name = "bash", paths = ["{bash_path}"])
|
||||
host_executable(name = "baſh", paths = ["{long_s_bash_path}"])
|
||||
"#
|
||||
);
|
||||
PolicyParser::new().parse("test.rules", &policy_src)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn host_executable_resolution_respects_explicit_empty_allowlist() -> Result<()> {
|
||||
let policy_src = r#"
|
||||
@@ -1011,3 +1057,30 @@ host_executable(name = "git", paths = ["{git_path_literal}"])
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn canonical_matching_prefix_matches_each_token() -> Result<()> {
|
||||
let mut parser = PolicyParser::new();
|
||||
parser.parse(
|
||||
"case.rules",
|
||||
r#"
|
||||
prefix_rule(pattern=[["Tool", "bash"], "Run", ["ONE", "two", "set"]], canonical_matching=True,
|
||||
match=["TOOL rUn Two extra", "bAſh RUN one", "bash run ſet"],
|
||||
not_match=["TOOL run three", "tool runner two"])
|
||||
prefix_rule(pattern=["café", ["résumé", "run"]], canonical_matching=True,
|
||||
match=["CAFÉ RÉSUMÉ"], not_match=["cafe resume", "café résumé"])
|
||||
# Full case folding can expand one character into two, within a single token.
|
||||
prefix_rule(pattern=["straße"], canonical_matching=True,
|
||||
match=["STRASSE"], not_match=["STRAS SE"])
|
||||
# U+0345 folds to a spacing iota. Reorder the acute accent before folding
|
||||
# so it stays attached to the alpha.
|
||||
prefix_rule(pattern=["α\u0345\u0301"], canonical_matching=True,
|
||||
match=["άι"], not_match=["αί"])
|
||||
prefix_rule(pattern=["café"], not_match=["café"])
|
||||
prefix_rule(pattern=["set-content"], canonical_matching=True, not_match=["set-cøntent"])
|
||||
prefix_rule(pattern=["bash"], not_match=["baſh"])
|
||||
prefix_rule(pattern=["Tool", "Run"], not_match=["tool Run", "Tool run"])
|
||||
"#,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user