use openmoat_core::Kind;
use super::{Filesystem, Generated, Report};
use crate::sandbox::patterns::push_unique;
impl Filesystem {
pub(super) fn static_rules(&self, report: &mut Report) -> Vec<String> {
let mut rules = Vec::new();
for (tool, denied, listed) in [
("Read", &self.denied_read, &self.deny_read),
("Edit", &self.denied_write, &self.deny_write),
] {
for entry in denied.iter().filter(|e| listed.contains(e)) {
if let Some(path) = rule_path(entry) {
push_unique(&mut rules, format!("{tool}({path})"));
}
}
}
if !rules.is_empty() {
report.loss(
Kind::FsRead,
"claude-code.static-denies",
"Claude Code's own deny rules for these paths hold for its file tools when the \
hook does not run, and refuse an exception the hook allows (such as \
`.env.example`) too"
.into(),
);
}
rules
}
}
pub fn deny_edit(generated: &mut Generated, paths: &[String]) {
for path in paths.iter().filter_map(|p| rule_path(p)) {
push_unique(&mut generated.deny_rules, format!("Edit({path})"));
}
}
fn rule_path(entry: &str) -> Option<String> {
if entry.starts_with("//") {
return None;
}
if entry.starts_with('/') {
return Some(format!("/{entry}"));
}
let mut chars = entry.chars();
match (chars.next(), chars.next(), chars.next()) {
(Some(drive), Some(':'), Some('/')) if drive.is_ascii_alphabetic() => {
Some(format!("//{}/{}", drive.to_ascii_lowercase(), &entry[3..]))
}
_ => None,
}
}
#[cfg(test)]
mod tests;