Skip to content

Bug: Corrupted permission.yaml silently resets all permissions — denied tools become allowed #7432

@marlonbarreto-git

Description

@marlonbarreto-git

Bug Description

When permission.yaml contains malformed YAML (from a partial write, manual editing error, or disk corruption), PermissionManager::new silently replaces the entire permission map with an empty HashMap. No warning is logged. Tools that were explicitly set to never_allow become implicitly allowed.

Affected Code

File: crates/goose/src/config/permission.rs, line 48

pub fn new(config_dir: PathBuf) -> Self {
    let permission_path = config_dir.join(PERMISSION_FILE);
    let permission_map = if permission_path.exists() {
        let file_contents =
            fs::read_to_string(&permission_path).expect("Failed to read permission.yaml");
        serde_yaml::from_str(&file_contents).unwrap_or_else(|_| HashMap::new())
        //                                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        //  Parse error silently discarded — returns empty map, no log
    } else {
        fs::create_dir_all(&config_dir).expect("Failed to create config directory");
        HashMap::new()
    };
    // ...
}

Security Impact

  1. User configures permission.yaml with never_allow entries for sensitive tools (e.g., file system access, shell execution)
  2. The YAML file gets corrupted (partial write during crash, manual edit with syntax error, disk error)
  3. On next process start, serde_yaml::from_str returns Err(...)
  4. unwrap_or_else(|_| HashMap::new()) silently swallows the error
  5. permission_map is now {} — all never_allow entries are gone
  6. Previously blocked tools are now permitted with zero user notification

This is a fail-open behavior where security configuration corruption leads to elevated permissions.

Steps to Reproduce

  1. Configure permission.yaml with restricted permissions:
    shell_execute:
      permission: never_allow
    file_write:
      permission: never_allow
  2. Corrupt the file (e.g., truncate it, add invalid YAML characters)
  3. Restart goose
  4. Observe: no error/warning in logs, all tools are now permitted

Expected Behavior

At minimum, a tracing::warn! or tracing::error! should be emitted when the parse fails, alerting the user that their permission configuration is being reset. Ideally, the process should refuse to start with corrupted security configuration (fail-closed).

Proposed Solution

Option A (recommended — fail-closed):

let permission_map: HashMap<String, PermissionConfig> = match serde_yaml::from_str(&file_contents) {
    Ok(map) => map,
    Err(e) => {
        tracing::error!(
            "Failed to parse permission.yaml: {}. Refusing to start with corrupted permission config.",
            e
        );
        return Err(anyhow::anyhow!("Corrupted permission.yaml"));
    }
};

Option B (fail-open with loud warning):

serde_yaml::from_str(&file_contents).unwrap_or_else(|e| {
    tracing::warn!(
        "Failed to parse permission.yaml: {}. Starting with empty permissions — all tools permitted.",
        e
    );
    HashMap::new()
})
Aspect Option A Option B
Security Fail-closed — no silent degradation Fail-open — degraded but warned
UX Blocks startup (may frustrate users) Allows startup with visible warning

Happy to submit a PR for this fix.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions