Files
dev/code-rs/execpolicy/src/policy_parser.rs
T
zeekayandHanzo Dev 6cbbea2d03 merge: just-every/code v0.6.169 (e4fe007150) — keep Hanzo identity
Our history is just-every's with author emails rewritten, so the graphs are
disjoint and git finds no merge base. Supplied one by hand: d1f1f51a41, the
just-every commit matching our Oct-2025 fork point (22 files apart). 9618
upstream commits, 7004 files, +1.81M/-159k, 37 conflicts.

Resolution:
- Upstream code where our side was the Oct-2025 prototype of a feature they
  have since reimplemented 3-5x larger (agent_runs, browser_sessions,
  history_cell/{agent,browser}, the vt100 harness and its snapshots).
- Upstream's config.rs module split (builder/defaults/sources/validation)
  kept; zen5-coder and the provider-follows-model rule re-applied on top.
- Ours where the file IS the identity: README, package.json, postinstall,
  release notes pipeline, integration-zed.
- Hybrid where upstream improved a file we had branded: updates.rs (their
  release-repo migration, our hanzoai/dev), release.yml (their fail-fast npm
  auth check, our package name), chatwidget.rs (their turn handling, our
  HANZO_USER_KEY hint), exec human output (their 237 lines, our banner).

Superseded and dropped deliberately: our resume_command_name() coder-alias
hack — upstream's derive_resume_command_name() reads the invocation basename
generically, which yields "dev" for us.

LLM.md now records the real topology and the sync point; AGENTS.md and
CLAUDE.md are symlinks to it again. Single parent on purpose: adopting
jecode/main as a second parent would re-import 13,742 commits carrying the
upstream author emails a prior rewrite deliberately removed.

Version 0.2.188 -> 0.2.189.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-05 23:09:12 -07:00

227 lines
7.4 KiB
Rust

#![allow(clippy::needless_lifetimes)]
use crate::Opt;
use crate::Policy;
use crate::ProgramSpec;
use crate::arg_matcher::ArgMatcher;
use crate::opt::OptMeta;
use log::info;
use multimap::MultiMap;
use regex_lite::Regex;
use starlark::any::ProvidesStaticType;
use starlark::environment::GlobalsBuilder;
use starlark::environment::LibraryExtension;
use starlark::environment::Module;
use starlark::eval::Evaluator;
use starlark::syntax::AstModule;
use starlark::syntax::Dialect;
use starlark::values::Heap;
use starlark::values::list::UnpackList;
use starlark::values::none::NoneType;
use std::cell::RefCell;
use std::collections::HashMap;
pub struct PolicyParser {
policy_source: String,
unparsed_policy: String,
}
impl PolicyParser {
pub fn new(policy_source: &str, unparsed_policy: &str) -> Self {
Self {
policy_source: policy_source.to_string(),
unparsed_policy: unparsed_policy.to_string(),
}
}
pub fn parse(&self) -> starlark::Result<Policy> {
let mut dialect = Dialect::Extended.clone();
dialect.enable_f_strings = true;
let ast = AstModule::parse(&self.policy_source, self.unparsed_policy.clone(), &dialect)?;
let globals = GlobalsBuilder::extended_by(&[LibraryExtension::Typing])
.with(policy_builtins)
.build();
let module = Module::new();
let heap = Heap::new();
module.set("ARG_OPAQUE_VALUE", heap.alloc(ArgMatcher::OpaqueNonFile));
module.set("ARG_RFILE", heap.alloc(ArgMatcher::ReadableFile));
module.set("ARG_WFILE", heap.alloc(ArgMatcher::WriteableFile));
module.set("ARG_RFILES", heap.alloc(ArgMatcher::ReadableFiles));
module.set(
"ARG_RFILES_OR_CWD",
heap.alloc(ArgMatcher::ReadableFilesOrCwd),
);
module.set("ARG_POS_INT", heap.alloc(ArgMatcher::PositiveInteger));
module.set("ARG_SED_COMMAND", heap.alloc(ArgMatcher::SedCommand));
module.set(
"ARG_UNVERIFIED_VARARGS",
heap.alloc(ArgMatcher::UnverifiedVarargs),
);
let policy_builder = PolicyBuilder::new();
{
let mut eval = Evaluator::new(&module);
eval.extra = Some(&policy_builder);
eval.eval_module(ast, &globals)?;
}
let policy = policy_builder.build();
policy.map_err(|e| starlark::Error::new_kind(starlark::ErrorKind::Other(e.into())))
}
}
#[derive(Debug)]
pub struct ForbiddenProgramRegex {
pub regex: regex_lite::Regex,
pub reason: String,
}
#[derive(Debug, ProvidesStaticType)]
struct PolicyBuilder {
programs: RefCell<MultiMap<String, ProgramSpec>>,
forbidden_program_regexes: RefCell<Vec<ForbiddenProgramRegex>>,
forbidden_substrings: RefCell<Vec<String>>,
}
impl PolicyBuilder {
fn new() -> Self {
Self {
programs: RefCell::new(MultiMap::new()),
forbidden_program_regexes: RefCell::new(Vec::new()),
forbidden_substrings: RefCell::new(Vec::new()),
}
}
fn build(self) -> Result<Policy, regex_lite::Error> {
let programs = self.programs.into_inner();
let forbidden_program_regexes = self.forbidden_program_regexes.into_inner();
let forbidden_substrings = self.forbidden_substrings.into_inner();
Policy::new(programs, forbidden_program_regexes, forbidden_substrings)
}
fn add_program_spec(&self, program_spec: ProgramSpec) {
info!("adding program spec: {program_spec:?}");
let name = program_spec.program.clone();
let mut programs = self.programs.borrow_mut();
programs.insert(name, program_spec);
}
fn add_forbidden_substrings(&self, substrings: &[String]) {
let mut forbidden_substrings = self.forbidden_substrings.borrow_mut();
forbidden_substrings.extend_from_slice(substrings);
}
fn add_forbidden_program_regex(&self, regex: Regex, reason: String) {
let mut forbidden_program_regexes = self.forbidden_program_regexes.borrow_mut();
forbidden_program_regexes.push(ForbiddenProgramRegex { regex, reason });
}
}
#[starlark_module]
fn policy_builtins(builder: &mut GlobalsBuilder) {
fn define_program<'v>(
program: String,
system_path: Option<UnpackList<String>>,
option_bundling: Option<bool>,
combined_format: Option<bool>,
options: Option<UnpackList<Opt>>,
args: Option<UnpackList<ArgMatcher>>,
forbidden: Option<String>,
should_match: Option<UnpackList<UnpackList<String>>>,
should_not_match: Option<UnpackList<UnpackList<String>>>,
eval: &mut Evaluator,
) -> anyhow::Result<NoneType> {
let option_bundling = option_bundling.unwrap_or(false);
let system_path = system_path.map_or_else(Vec::new, |v| v.items.to_vec());
let combined_format = combined_format.unwrap_or(false);
let options = options.map_or_else(Vec::new, |v| v.items.to_vec());
let args = args.map_or_else(Vec::new, |v| v.items.to_vec());
let mut allowed_options = HashMap::<String, Opt>::new();
for opt in options {
let name = opt.name().to_string();
if allowed_options
.insert(opt.name().to_string(), opt)
.is_some()
{
return Err(anyhow::format_err!("duplicate flag: {name}"));
}
}
let program_spec = ProgramSpec::new(
program,
system_path,
option_bundling,
combined_format,
allowed_options,
args,
forbidden,
should_match
.map_or_else(Vec::new, |v| v.items.to_vec())
.into_iter()
.map(|v| v.items.to_vec())
.collect(),
should_not_match
.map_or_else(Vec::new, |v| v.items.to_vec())
.into_iter()
.map(|v| v.items.to_vec())
.collect(),
);
#[expect(clippy::unwrap_used)]
let policy_builder = eval
.extra
.as_ref()
.unwrap()
.downcast_ref::<PolicyBuilder>()
.unwrap();
policy_builder.add_program_spec(program_spec);
Ok(NoneType)
}
fn forbid_substrings(
strings: UnpackList<String>,
eval: &mut Evaluator,
) -> anyhow::Result<NoneType> {
#[expect(clippy::unwrap_used)]
let policy_builder = eval
.extra
.as_ref()
.unwrap()
.downcast_ref::<PolicyBuilder>()
.unwrap();
policy_builder.add_forbidden_substrings(&strings.items.to_vec());
Ok(NoneType)
}
fn forbid_program_regex(
regex: String,
reason: String,
eval: &mut Evaluator,
) -> anyhow::Result<NoneType> {
#[expect(clippy::unwrap_used)]
let policy_builder = eval
.extra
.as_ref()
.unwrap()
.downcast_ref::<PolicyBuilder>()
.unwrap();
let compiled_regex = regex_lite::Regex::new(&regex)?;
policy_builder.add_forbidden_program_regex(compiled_regex, reason);
Ok(NoneType)
}
fn opt(name: String, r#type: ArgMatcher, required: Option<bool>) -> anyhow::Result<Opt> {
Ok(Opt::new(
name,
OptMeta::Value(r#type.arg_type()),
required.unwrap_or(false),
))
}
fn flag(name: String) -> anyhow::Result<Opt> {
Ok(Opt::new(name, OptMeta::Flag, false))
}
}