use super::{generalize, ProfileReport};
use serde::Serialize;
use std::collections::{BTreeMap, BTreeSet};
use std::path::PathBuf;
#[derive(Debug, Clone, Default, Serialize)]
pub struct SuggestConfig {
pub widen_dirs_to_glob: bool,
}
#[derive(Debug, Clone, Default, Serialize)]
pub struct PolicySuggestion {
pub api_version: u32,
pub extends: Vec<String>,
pub fs: FsPolicy,
pub net: NetPolicy,
pub env: EnvPolicy,
pub processes: ProcessPolicy,
pub meta: MetaPolicy,
}
#[derive(Debug, Clone, Default, Serialize)]
pub struct FsPolicy {
pub allow: BTreeSet<String>,
pub deny: BTreeSet<String>,
}
#[derive(Debug, Clone, Default, Serialize)]
pub struct NetPolicy {
pub allow: BTreeSet<String>,
pub deny: BTreeSet<String>,
}
#[derive(Debug, Clone, Default, Serialize)]
pub struct EnvPolicy {
pub allow: BTreeSet<String>,
}
#[derive(Debug, Clone, Default, Serialize)]
pub struct ProcessPolicy {
pub allow: BTreeSet<String>,
}
#[derive(Debug, Clone, Default, Serialize)]
pub struct MetaPolicy {
pub notes: Vec<String>,
pub counters: BTreeMap<String, u64>,
}
pub fn build_policy_suggestion(report: &ProfileReport, cfg: SuggestConfig) -> PolicySuggestion {
let mut out = PolicySuggestion {
api_version: 1,
..PolicySuggestion::default()
};
out.meta.counters = report.agg.counters.clone();
out.meta.notes = report.agg.notes.clone();
for k in report.agg.env_provided.keys() {
if !crate::env_filter::matches_any_pattern(k, crate::env_filter::SAFE_BASE_PATTERNS) {
out.env.allow.insert(k.clone());
}
}
for cmd in report.agg.execs.keys() {
let p = PathBuf::from(cmd);
let g = generalize::generalize_path(
&p,
&report.config.cwd,
report.config.home.as_deref(),
report.config.assay_tmp.as_deref(),
);
out.processes.allow.insert(g.rendered);
}
for (op, raw_path, _backend) in &report.agg.fs {
let p = PathBuf::from(raw_path);
let g = generalize::generalize_path(
&p,
&report.config.cwd,
report.config.home.as_deref(),
report.config.assay_tmp.as_deref(),
);
match op {
super::events::FsOp::Read => {
out.fs.allow.insert(g.rendered);
}
super::events::FsOp::Exec => {
out.fs.allow.insert(g.rendered);
}
super::events::FsOp::Write => {
out.fs.allow.insert(g.rendered);
}
}
}
out.net.deny.insert("*:*".to_string());
if cfg.widen_dirs_to_glob && report.config.assay_tmp.is_some() {
let tmp_prefix = "${ASSAY_TMP}/";
let has_tmp = out.fs.allow.iter().any(|p| p.starts_with(tmp_prefix));
if has_tmp {
out.fs.allow.retain(|p| !p.starts_with(tmp_prefix));
out.fs.allow.insert("${ASSAY_TMP}/**".to_string());
}
}
out
}