fix: allowlist bypass for developers (#2149)
This commit is contained in:
@@ -19,6 +19,16 @@ export GOOSE_ALLOWLIST=https://example.com/goose-allowlist.yaml
|
|||||||
|
|
||||||
If this environment variable is not set, no allowlist restrictions will be applied (all commands will be allowed).
|
If this environment variable is not set, no allowlist restrictions will be applied (all commands will be allowed).
|
||||||
|
|
||||||
|
## Bypassing the Allowlist
|
||||||
|
|
||||||
|
In certain development or testing scenarios, you may need to bypass the allowlist restrictions. You can do this by setting the `GOOSE_ALLOWLIST_BYPASS` environment variable to `true`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export GOOSE_ALLOWLIST_BYPASS=true
|
||||||
|
```
|
||||||
|
|
||||||
|
When this environment variable is set to `true` (case insensitive), the allowlist check will be bypassed and all commands will be allowed, even if the `GOOSE_ALLOWLIST` environment variable is set.
|
||||||
|
|
||||||
## Allowlist File Format
|
## Allowlist File Format
|
||||||
|
|
||||||
The allowlist file should be a YAML file with the following structure:
|
The allowlist file should be a YAML file with the following structure:
|
||||||
|
|||||||
@@ -396,6 +396,16 @@ fn get_allowed_extensions() -> &'static Option<AllowedExtensions> {
|
|||||||
|
|
||||||
/// Checks if a command is allowed based on the allowlist
|
/// Checks if a command is allowed based on the allowlist
|
||||||
fn is_command_allowed(cmd: &str, args: &[String]) -> bool {
|
fn is_command_allowed(cmd: &str, args: &[String]) -> bool {
|
||||||
|
// Check if bypass is enabled
|
||||||
|
if let Ok(bypass_value) = env::var("GOOSE_ALLOWLIST_BYPASS") {
|
||||||
|
if bypass_value.to_lowercase() == "true" {
|
||||||
|
// Bypass the allowlist check
|
||||||
|
println!("Allowlist check bypassed due to GOOSE_ALLOWLIST_BYPASS=true");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Proceed with normal allowlist check
|
||||||
is_command_allowed_with_allowlist(&make_full_cmd(cmd, args), get_allowed_extensions())
|
is_command_allowed_with_allowlist(&make_full_cmd(cmd, args), get_allowed_extensions())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1075,4 +1085,81 @@ mod tests {
|
|||||||
// Wait for the server thread to complete
|
// Wait for the server thread to complete
|
||||||
handle.join().unwrap();
|
handle.join().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_allowlist_bypass() {
|
||||||
|
// We need to directly test is_command_allowed_with_allowlist with our test allowlist
|
||||||
|
// since get_allowed_extensions() might return None in the test environment
|
||||||
|
|
||||||
|
// Create a restrictive allowlist
|
||||||
|
let allowlist = create_test_allowlist(&["uvx mcp_slack"]);
|
||||||
|
|
||||||
|
// Command not in allowlist
|
||||||
|
let cmd = "uvx unauthorized_command";
|
||||||
|
|
||||||
|
// Without bypass, command should be denied with our test allowlist
|
||||||
|
assert!(!is_command_allowed_with_allowlist(cmd, &allowlist));
|
||||||
|
|
||||||
|
// Set the bypass environment variable
|
||||||
|
env::set_var("GOOSE_ALLOWLIST_BYPASS", "true");
|
||||||
|
|
||||||
|
// With bypass enabled, any command should be allowed regardless of allowlist
|
||||||
|
assert!(is_command_allowed(
|
||||||
|
"uvx",
|
||||||
|
&vec!["unauthorized_command".to_string()]
|
||||||
|
));
|
||||||
|
|
||||||
|
// Test case insensitivity
|
||||||
|
env::set_var("GOOSE_ALLOWLIST_BYPASS", "TRUE");
|
||||||
|
assert!(is_command_allowed(
|
||||||
|
"uvx",
|
||||||
|
&vec!["unauthorized_command".to_string()]
|
||||||
|
));
|
||||||
|
|
||||||
|
// Clean up
|
||||||
|
env::remove_var("GOOSE_ALLOWLIST_BYPASS");
|
||||||
|
|
||||||
|
// Create a mock function to test with allowlist and bypass
|
||||||
|
let test_with_allowlist_and_bypass = |bypass_value: &str, expected: bool| {
|
||||||
|
if bypass_value.is_empty() {
|
||||||
|
env::remove_var("GOOSE_ALLOWLIST_BYPASS");
|
||||||
|
} else {
|
||||||
|
env::set_var("GOOSE_ALLOWLIST_BYPASS", bypass_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is what we're testing - a direct call that simulates what happens in is_command_allowed
|
||||||
|
let result = if let Ok(bypass) = env::var("GOOSE_ALLOWLIST_BYPASS") {
|
||||||
|
if bypass.to_lowercase() == "true" {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
is_command_allowed_with_allowlist(cmd, &allowlist)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
is_command_allowed_with_allowlist(cmd, &allowlist)
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
result,
|
||||||
|
expected,
|
||||||
|
"With GOOSE_ALLOWLIST_BYPASS={}, expected allowed={}",
|
||||||
|
if bypass_value.is_empty() {
|
||||||
|
"not set"
|
||||||
|
} else {
|
||||||
|
bypass_value
|
||||||
|
},
|
||||||
|
expected
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Test various bypass values
|
||||||
|
test_with_allowlist_and_bypass("true", true);
|
||||||
|
test_with_allowlist_and_bypass("TRUE", true);
|
||||||
|
test_with_allowlist_and_bypass("True", true);
|
||||||
|
test_with_allowlist_and_bypass("false", false);
|
||||||
|
test_with_allowlist_and_bypass("0", false);
|
||||||
|
test_with_allowlist_and_bypass("", false);
|
||||||
|
|
||||||
|
// Final cleanup
|
||||||
|
env::remove_var("GOOSE_ALLOWLIST_BYPASS");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user