mirror of
https://github.com/openai/codex.git
synced 2026-10-30 19:18:49 +00:00
feat: tokenizing whole strings in match and not_match
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
- Policy engine and CLI built around `prefix_rule(pattern=[...], decision?, match?, not_match?)`.
|
||||
- This release covers only the prefix-rule subset of the planned execpolicy v2 language; a richer language will follow.
|
||||
- Tokens are matched in order; any `pattern` element may be a list to denote alternatives. `decision` defaults to `allow`; valid values: `allow`, `prompt`, `forbidden`.
|
||||
- `match` / `not_match` supply example invocations that are validated at load time (think of them as unit tests).
|
||||
- `match` / `not_match` supply example invocations that are validated at load time (think of them as unit tests); examples can be token arrays or strings (strings are tokenized with `shlex`).
|
||||
- The CLI always prints the JSON serialization of the evaluation result (whether a match or not).
|
||||
|
||||
## Policy shapes
|
||||
@@ -13,8 +13,8 @@
|
||||
prefix_rule(
|
||||
pattern = ["cmd", ["alt1", "alt2"]], # ordered tokens; list entries denote alternatives
|
||||
decision = "prompt", # allow | prompt | forbidden; defaults to allow
|
||||
match = [["cmd", "alt1"]], # examples that must match this rule
|
||||
not_match = [["cmd", "oops"]], # examples that must not match this rule
|
||||
match = [["cmd", "alt1"], "cmd alt2"], # examples that must match this rule
|
||||
not_match = [["cmd", "oops"], "cmd alt3"], # examples that must not match this rule
|
||||
)
|
||||
```
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use multimap::MultiMap;
|
||||
use parking_lot::Mutex;
|
||||
use shlex;
|
||||
use starlark::any::ProvidesStaticType;
|
||||
use starlark::environment::GlobalsBuilder;
|
||||
use starlark::environment::Module;
|
||||
@@ -123,34 +124,55 @@ fn parse_pattern_token<'v>(value: Value<'v>) -> Result<PatternToken> {
|
||||
}
|
||||
|
||||
fn parse_examples<'v>(examples: UnpackList<Value<'v>>) -> Result<Vec<Vec<String>>> {
|
||||
examples
|
||||
.items
|
||||
.into_iter()
|
||||
.map(|example| {
|
||||
let list = ListRef::from_value(example).ok_or_else(|| {
|
||||
Error::InvalidExample("example must be a list of strings".to_string())
|
||||
})?;
|
||||
let tokens: Vec<String> = list
|
||||
.content()
|
||||
.iter()
|
||||
.map(|value| {
|
||||
value
|
||||
.unpack_str()
|
||||
.ok_or_else(|| {
|
||||
Error::InvalidExample("example tokens must be strings".to_string())
|
||||
})
|
||||
.map(str::to_string)
|
||||
})
|
||||
.collect::<Result<_>>()?;
|
||||
examples.items.into_iter().map(parse_example).collect()
|
||||
}
|
||||
|
||||
match tokens.as_slice() {
|
||||
[] => Err(Error::InvalidExample(
|
||||
"example cannot be an empty list".to_string(),
|
||||
)),
|
||||
_ => Ok(tokens),
|
||||
}
|
||||
fn parse_example<'v>(value: Value<'v>) -> Result<Vec<String>> {
|
||||
if let Some(raw) = value.unpack_str() {
|
||||
return parse_string_example(raw);
|
||||
}
|
||||
|
||||
if let Some(list) = ListRef::from_value(value) {
|
||||
return parse_list_example(&list);
|
||||
}
|
||||
|
||||
Err(Error::InvalidExample(format!(
|
||||
"example must be a string or list of strings (got {})",
|
||||
value.get_type()
|
||||
)))
|
||||
}
|
||||
|
||||
fn parse_string_example(raw: &str) -> Result<Vec<String>> {
|
||||
let tokens = shlex::split(raw).ok_or_else(|| {
|
||||
Error::InvalidExample("example string has invalid shell syntax".to_string())
|
||||
})?;
|
||||
|
||||
match tokens.as_slice() {
|
||||
[] => Err(Error::InvalidExample(
|
||||
"example cannot be an empty string".to_string(),
|
||||
)),
|
||||
_ => Ok(tokens),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_list_example(list: &ListRef) -> Result<Vec<String>> {
|
||||
let tokens: Vec<String> = list
|
||||
.content()
|
||||
.iter()
|
||||
.map(|value| {
|
||||
value
|
||||
.unpack_str()
|
||||
.ok_or_else(|| Error::InvalidExample("example tokens must be strings".to_string()))
|
||||
.map(str::to_string)
|
||||
})
|
||||
.collect()
|
||||
.collect::<Result<_>>()?;
|
||||
|
||||
match tokens.as_slice() {
|
||||
[] => Err(Error::InvalidExample(
|
||||
"example cannot be an empty list".to_string(),
|
||||
)),
|
||||
_ => Ok(tokens),
|
||||
}
|
||||
}
|
||||
|
||||
fn policy_builder<'v, 'a>(eval: &Evaluator<'v, 'a, '_>) -> &'a PolicyBuilder {
|
||||
|
||||
@@ -112,8 +112,11 @@ fn match_and_not_match_examples_are_enforced() {
|
||||
let policy_src = r#"
|
||||
prefix_rule(
|
||||
pattern = ["git", "status"],
|
||||
match = [["git", "status"]],
|
||||
not_match = [["git", "--config", "color.status=always", "status"]],
|
||||
match = [["git", "status"], "git status"],
|
||||
not_match = [
|
||||
["git", "--config", "color.status=always", "status"],
|
||||
"git --config color.status=always status",
|
||||
],
|
||||
)
|
||||
"#;
|
||||
let parser = PolicyParser::new("test.codexpolicy", policy_src);
|
||||
|
||||
Reference in New Issue
Block a user