agentguard/commands/
init.rs

1use agentguard_core::{init_store, PolicyStore};
2use anyhow::{anyhow, Result};
3use std::path::Path;
4
5/// Validate an organization name for safe interpolation into a
6/// Cedar string literal. Cedar is permissive about identifier chars
7/// but a name containing `"`, `\`, or newlines would let a CLI
8/// caller inject policy text. We allow ASCII letters/digits/hyphen/
9/// underscore/dot, plus spaces. Everything else is rejected.
10fn sanitize_org_name(name: &str) -> Result<&str> {
11    if name.is_empty() {
12        return Err(anyhow!("--name cannot be empty"));
13    }
14    if name.len() > 64 {
15        return Err(anyhow!("--name is too long (max 64 chars)"));
16    }
17    for c in name.chars() {
18        let ok = c.is_ascii_alphanumeric()
19            || matches!(c, '-' | '_' | '.' | ' ')
20            || c.is_ascii_punctuation() && c != '"' && c != '\\';
21        if !ok {
22            return Err(anyhow!(
23                "--name contains an unsafe character {:?}; allowed: ASCII alphanumeric, '-', '_', '.', ' ', and basic punctuation except quotes and backslashes",
24                c
25            ));
26        }
27    }
28    Ok(name)
29}
30
31pub fn run(store: impl AsRef<Path>, name: &str) -> Result<()> {
32    let path = store.as_ref();
33    let name = sanitize_org_name(name)?;
34    init_store(path)?;
35    let store = PolicyStore::open(path)?;
36
37    // The auto-generated `10_admin` policy grants the `User::"admin"`
38    // principal ANY action on ANY resource — effectively a global
39    // superuser. This is the documented default; operators MUST
40    // replace it before any non-development deployment.
41    eprintln!(
42        "warning: `10_admin.cedar` grants `User::\"admin\"` superuser privileges. \
43         Replace this file before deploying to production."
44    );
45    store.write_policy(
46        "10_admin",
47        &format!(
48            r#"// Auto-generated by `agentguard init` for org `{name}`.
49// SECURITY: This policy grants the `User::"admin"` principal ANY
50// action on ANY resource. It is a development default only;
51// replace this file before deploying to production.
52permit (
53  principal in User::"admin",
54  action,
55  resource
56);
57"#
58        ),
59    )?;
60
61    // Agents can call any ToolCall action.
62    eprintln!(
63        "warning: `20_agents.cedar` permits any `Agent` to invoke any declared \
64         ToolCall action. Tighten with `when {{ ... }}` clauses before deploying."
65    );
66    store.write_policy(
67        "20_agents",
68        r#"// AI agents can invoke any declared ToolCall action.
69// SECURITY: This is a development default that grants every agent
70// unrestricted tool access. Tighten with `when { ... }` clauses
71// before deploying.
72permit (
73  principal is Agent,
74  action,
75  resource
76);
77"#,
78    )?;
79
80    println!("Initialized agentguard store at {}", store.root.display());
81    println!("  schema: {}", store.schema_path().display());
82    println!("  policies: {}", store.policies_dir().display());
83    println!("Try:");
84    println!("  agentguard validate");
85    Ok(())
86}
87
88#[cfg(test)]
89mod tests {
90    use super::*;
91
92    #[test]
93    fn sanitize_allows_safe_names() {
94        assert!(sanitize_org_name("acme-corp").is_ok());
95        assert!(sanitize_org_name("Acme Corp.").is_ok());
96        assert!(sanitize_org_name("acme_corp.io").is_ok());
97    }
98
99    #[test]
100    fn sanitize_rejects_unsafe() {
101        assert!(sanitize_org_name("").is_err());
102        assert!(sanitize_org_name(&"x".repeat(65)).is_err());
103        assert!(sanitize_org_name(r#"acme"; permit (any); "#).is_err());
104        assert!(sanitize_org_name("acme\\inject").is_err());
105    }
106}