mirror of
https://github.com/leonvanzyl/autocoder.git
synced 2026-01-30 06:12:06 +00:00
Merge pull request #100 from cabana8471-arch/fix/config-loading-diagnostics
fix: add diagnostic warnings for config loading failures
This commit is contained in:
51
security.py
51
security.py
@@ -499,58 +499,74 @@ def load_org_config() -> Optional[dict]:
|
|||||||
config = yaml.safe_load(f)
|
config = yaml.safe_load(f)
|
||||||
|
|
||||||
if not config:
|
if not config:
|
||||||
|
logger.warning(f"Org config at {config_path} is empty")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Validate structure
|
# Validate structure
|
||||||
if not isinstance(config, dict):
|
if not isinstance(config, dict):
|
||||||
|
logger.warning(f"Org config at {config_path} must be a YAML dictionary")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if "version" not in config:
|
if "version" not in config:
|
||||||
|
logger.warning(f"Org config at {config_path} missing required 'version' field")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Validate allowed_commands if present
|
# Validate allowed_commands if present
|
||||||
if "allowed_commands" in config:
|
if "allowed_commands" in config:
|
||||||
allowed = config["allowed_commands"]
|
allowed = config["allowed_commands"]
|
||||||
if not isinstance(allowed, list):
|
if not isinstance(allowed, list):
|
||||||
|
logger.warning(f"Org config at {config_path}: 'allowed_commands' must be a list")
|
||||||
return None
|
return None
|
||||||
for cmd in allowed:
|
for i, cmd in enumerate(allowed):
|
||||||
if not isinstance(cmd, dict):
|
if not isinstance(cmd, dict):
|
||||||
|
logger.warning(f"Org config at {config_path}: allowed_commands[{i}] must be a dict")
|
||||||
return None
|
return None
|
||||||
if "name" not in cmd:
|
if "name" not in cmd:
|
||||||
|
logger.warning(f"Org config at {config_path}: allowed_commands[{i}] missing 'name'")
|
||||||
return None
|
return None
|
||||||
# Validate that name is a non-empty string
|
# Validate that name is a non-empty string
|
||||||
if not isinstance(cmd["name"], str) or cmd["name"].strip() == "":
|
if not isinstance(cmd["name"], str) or cmd["name"].strip() == "":
|
||||||
|
logger.warning(f"Org config at {config_path}: allowed_commands[{i}] has invalid 'name'")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Validate blocked_commands if present
|
# Validate blocked_commands if present
|
||||||
if "blocked_commands" in config:
|
if "blocked_commands" in config:
|
||||||
blocked = config["blocked_commands"]
|
blocked = config["blocked_commands"]
|
||||||
if not isinstance(blocked, list):
|
if not isinstance(blocked, list):
|
||||||
|
logger.warning(f"Org config at {config_path}: 'blocked_commands' must be a list")
|
||||||
return None
|
return None
|
||||||
for cmd in blocked:
|
for i, cmd in enumerate(blocked):
|
||||||
if not isinstance(cmd, str):
|
if not isinstance(cmd, str):
|
||||||
|
logger.warning(f"Org config at {config_path}: blocked_commands[{i}] must be a string")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Validate pkill_processes if present
|
# Validate pkill_processes if present
|
||||||
if "pkill_processes" in config:
|
if "pkill_processes" in config:
|
||||||
processes = config["pkill_processes"]
|
processes = config["pkill_processes"]
|
||||||
if not isinstance(processes, list):
|
if not isinstance(processes, list):
|
||||||
|
logger.warning(f"Org config at {config_path}: 'pkill_processes' must be a list")
|
||||||
return None
|
return None
|
||||||
# Normalize and validate each process name against safe pattern
|
# Normalize and validate each process name against safe pattern
|
||||||
normalized = []
|
normalized = []
|
||||||
for proc in processes:
|
for i, proc in enumerate(processes):
|
||||||
if not isinstance(proc, str):
|
if not isinstance(proc, str):
|
||||||
|
logger.warning(f"Org config at {config_path}: pkill_processes[{i}] must be a string")
|
||||||
return None
|
return None
|
||||||
proc = proc.strip()
|
proc = proc.strip()
|
||||||
# Block empty strings and regex metacharacters
|
# Block empty strings and regex metacharacters
|
||||||
if not proc or not VALID_PROCESS_NAME_PATTERN.fullmatch(proc):
|
if not proc or not VALID_PROCESS_NAME_PATTERN.fullmatch(proc):
|
||||||
|
logger.warning(f"Org config at {config_path}: pkill_processes[{i}] has invalid value '{proc}'")
|
||||||
return None
|
return None
|
||||||
normalized.append(proc)
|
normalized.append(proc)
|
||||||
config["pkill_processes"] = normalized
|
config["pkill_processes"] = normalized
|
||||||
|
|
||||||
return config
|
return config
|
||||||
|
|
||||||
except (yaml.YAMLError, IOError, OSError):
|
except yaml.YAMLError as e:
|
||||||
|
logger.warning(f"Failed to parse org config at {config_path}: {e}")
|
||||||
|
return None
|
||||||
|
except (IOError, OSError) as e:
|
||||||
|
logger.warning(f"Failed to read org config at {config_path}: {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
@@ -564,7 +580,7 @@ def load_project_commands(project_dir: Path) -> Optional[dict]:
|
|||||||
Returns:
|
Returns:
|
||||||
Dict with parsed YAML config, or None if file doesn't exist or is invalid
|
Dict with parsed YAML config, or None if file doesn't exist or is invalid
|
||||||
"""
|
"""
|
||||||
config_path = project_dir / ".autocoder" / "allowed_commands.yaml"
|
config_path = project_dir.resolve() / ".autocoder" / "allowed_commands.yaml"
|
||||||
|
|
||||||
if not config_path.exists():
|
if not config_path.exists():
|
||||||
return None
|
return None
|
||||||
@@ -574,53 +590,68 @@ def load_project_commands(project_dir: Path) -> Optional[dict]:
|
|||||||
config = yaml.safe_load(f)
|
config = yaml.safe_load(f)
|
||||||
|
|
||||||
if not config:
|
if not config:
|
||||||
|
logger.warning(f"Project config at {config_path} is empty")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Validate structure
|
# Validate structure
|
||||||
if not isinstance(config, dict):
|
if not isinstance(config, dict):
|
||||||
|
logger.warning(f"Project config at {config_path} must be a YAML dictionary")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if "version" not in config:
|
if "version" not in config:
|
||||||
|
logger.warning(f"Project config at {config_path} missing required 'version' field")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
commands = config.get("commands", [])
|
commands = config.get("commands", [])
|
||||||
if not isinstance(commands, list):
|
if not isinstance(commands, list):
|
||||||
|
logger.warning(f"Project config at {config_path}: 'commands' must be a list")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Enforce 100 command limit
|
# Enforce 100 command limit
|
||||||
if len(commands) > 100:
|
if len(commands) > 100:
|
||||||
|
logger.warning(f"Project config at {config_path} exceeds 100 command limit ({len(commands)} commands)")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Validate each command entry
|
# Validate each command entry
|
||||||
for cmd in commands:
|
for i, cmd in enumerate(commands):
|
||||||
if not isinstance(cmd, dict):
|
if not isinstance(cmd, dict):
|
||||||
|
logger.warning(f"Project config at {config_path}: commands[{i}] must be a dict")
|
||||||
return None
|
return None
|
||||||
if "name" not in cmd:
|
if "name" not in cmd:
|
||||||
|
logger.warning(f"Project config at {config_path}: commands[{i}] missing 'name'")
|
||||||
return None
|
return None
|
||||||
# Validate name is a string
|
# Validate name is a non-empty string
|
||||||
if not isinstance(cmd["name"], str):
|
if not isinstance(cmd["name"], str) or cmd["name"].strip() == "":
|
||||||
|
logger.warning(f"Project config at {config_path}: commands[{i}] has invalid 'name'")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Validate pkill_processes if present
|
# Validate pkill_processes if present
|
||||||
if "pkill_processes" in config:
|
if "pkill_processes" in config:
|
||||||
processes = config["pkill_processes"]
|
processes = config["pkill_processes"]
|
||||||
if not isinstance(processes, list):
|
if not isinstance(processes, list):
|
||||||
|
logger.warning(f"Project config at {config_path}: 'pkill_processes' must be a list")
|
||||||
return None
|
return None
|
||||||
# Normalize and validate each process name against safe pattern
|
# Normalize and validate each process name against safe pattern
|
||||||
normalized = []
|
normalized = []
|
||||||
for proc in processes:
|
for i, proc in enumerate(processes):
|
||||||
if not isinstance(proc, str):
|
if not isinstance(proc, str):
|
||||||
|
logger.warning(f"Project config at {config_path}: pkill_processes[{i}] must be a string")
|
||||||
return None
|
return None
|
||||||
proc = proc.strip()
|
proc = proc.strip()
|
||||||
# Block empty strings and regex metacharacters
|
# Block empty strings and regex metacharacters
|
||||||
if not proc or not VALID_PROCESS_NAME_PATTERN.fullmatch(proc):
|
if not proc or not VALID_PROCESS_NAME_PATTERN.fullmatch(proc):
|
||||||
|
logger.warning(f"Project config at {config_path}: pkill_processes[{i}] has invalid value '{proc}'")
|
||||||
return None
|
return None
|
||||||
normalized.append(proc)
|
normalized.append(proc)
|
||||||
config["pkill_processes"] = normalized
|
config["pkill_processes"] = normalized
|
||||||
|
|
||||||
return config
|
return config
|
||||||
|
|
||||||
except (yaml.YAMLError, IOError, OSError):
|
except yaml.YAMLError as e:
|
||||||
|
logger.warning(f"Failed to parse project config at {config_path}: {e}")
|
||||||
|
return None
|
||||||
|
except (IOError, OSError) as e:
|
||||||
|
logger.warning(f"Failed to read project config at {config_path}: {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -455,6 +455,21 @@ commands:
|
|||||||
print(" FAIL: Non-allowed command 'rustc' should be blocked")
|
print(" FAIL: Non-allowed command 'rustc' should be blocked")
|
||||||
failed += 1
|
failed += 1
|
||||||
|
|
||||||
|
# Test 4: Empty command name is rejected
|
||||||
|
config_path.write_text("""version: 1
|
||||||
|
commands:
|
||||||
|
- name: ""
|
||||||
|
description: Empty name should be rejected
|
||||||
|
""")
|
||||||
|
result = load_project_commands(project_dir)
|
||||||
|
if result is None:
|
||||||
|
print(" PASS: Empty command name rejected in project config")
|
||||||
|
passed += 1
|
||||||
|
else:
|
||||||
|
print(" FAIL: Empty command name should be rejected in project config")
|
||||||
|
print(f" Got: {result}")
|
||||||
|
failed += 1
|
||||||
|
|
||||||
return passed, failed
|
return passed, failed
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user