Coverage for src / moai_adk / core / unified_permission_manager.py: 28.12%

313 statements  

« prev     ^ index     » next       coverage.py v7.12.0, created at 2025-11-20 20:52 +0900

1""" 

2Unified Permission Manager for MoAI-ADK 

3 

4Production-ready permission management system that addresses agent permission validation 

5errors identified in Claude Code debug logs. Provides automatic correction, validation, 

6and monitoring of agent permissions and access control. 

7 

8Author: MoAI-ADK Core Team 

9Version: 1.0.0 

10""" 

11 

12import json 

13import logging 

14import os 

15import time 

16from dataclasses import dataclass, field 

17from enum import Enum 

18from typing import Any, Dict, List, Optional 

19 

20# Configure logging 

21logger = logging.getLogger(__name__) 

22 

23 

24class PermissionMode(Enum): 

25 """Valid permission modes for agents""" 

26 ACCEPT_EDITS = "acceptEdits" 

27 BYPASS_PERMISSIONS = "bypassPermissions" 

28 DEFAULT = "default" 

29 DONT_ASK = "dontAsk" 

30 PLAN = "plan" 

31 

32 

33class PermissionSeverity(Enum): 

34 """Permission validation severity levels""" 

35 LOW = "low" 

36 MEDIUM = "medium" 

37 HIGH = "high" 

38 CRITICAL = "critical" 

39 

40 

41class ResourceType(Enum): 

42 """Types of resources that can be protected""" 

43 AGENT = "agent" 

44 TOOL = "tool" 

45 FILE = "file" 

46 COMMAND = "command" 

47 SETTING = "setting" 

48 

49 

50@dataclass 

51class PermissionRule: 

52 """Individual permission rule""" 

53 resource_type: ResourceType 

54 resource_name: str 

55 action: str 

56 allowed: bool 

57 conditions: Optional[Dict[str, Any]] = None 

58 expires_at: Optional[float] = None 

59 

60 

61@dataclass 

62class ValidationResult: 

63 """Result of permission validation""" 

64 valid: bool 

65 corrected_mode: Optional[str] = None 

66 warnings: List[str] = field(default_factory=list) 

67 errors: List[str] = field(default_factory=list) 

68 severity: PermissionSeverity = PermissionSeverity.LOW 

69 auto_corrected: bool = False 

70 

71 

72@dataclass 

73class PermissionAudit: 

74 """Audit log entry for permission changes""" 

75 timestamp: float 

76 user_id: Optional[str] 

77 resource_type: ResourceType 

78 resource_name: str 

79 action: str 

80 old_permissions: Optional[Dict[str, Any]] 

81 new_permissions: Optional[Dict[str, Any]] 

82 reason: str 

83 auto_corrected: bool 

84 

85 

86class UnifiedPermissionManager: 

87 """ 

88 Production-ready permission management system that addresses Claude Code 

89 agent permission validation errors with automatic correction and monitoring. 

90 

91 Key Features: 

92 - Automatic permission mode validation and correction 

93 - Role-based access control with inheritance 

94 - Real-time permission monitoring and auditing 

95 - Configuration file auto-recovery 

96 - Security-focused fail-safe behavior 

97 """ 

98 

99 # Valid permission modes from Claude Code 

100 VALID_PERMISSION_MODES = { 

101 "acceptEdits", 

102 "bypassPermissions", 

103 "default", 

104 "dontAsk", 

105 "plan" 

106 } 

107 

108 # Default permission mappings 

109 DEFAULT_PERMISSIONS = { 

110 "backend-expert": PermissionMode.ACCEPT_EDITS, 

111 "frontend-expert": PermissionMode.ACCEPT_EDITS, 

112 "security-expert": PermissionMode.ACCEPT_EDITS, 

113 "api-designer": PermissionMode.PLAN, 

114 "database-expert": PermissionMode.ACCEPT_EDITS, 

115 "docs-manager": PermissionMode.ACCEPT_EDITS, 

116 "tdd-implementer": PermissionMode.ACCEPT_EDITS, 

117 "spec-builder": PermissionMode.ACCEPT_EDITS, 

118 "quality-gate": PermissionMode.ACCEPT_EDITS, 

119 "default": PermissionMode.DEFAULT, 

120 } 

121 

122 def __init__(self, config_path: Optional[str] = None, enable_logging: bool = True): 

123 self.config_path = config_path or ".claude/settings.json" 

124 self.enable_logging = enable_logging 

125 self.permission_cache = {} 

126 self.audit_log: List[PermissionAudit] = [] 

127 self.stats = { 

128 'validations_performed': 0, 

129 'auto_corrections': 0, 

130 'security_violations': 0, 

131 'permission_denied': 0 

132 } 

133 

134 # Role hierarchy for inheritance 

135 self.role_hierarchy = { 

136 "admin": ["developer", "user"], 

137 "developer": ["user"], 

138 "user": [] 

139 } 

140 

141 # Load and validate current configuration 

142 self.config = self._load_configuration() 

143 self._validate_all_permissions() 

144 

145 def _load_configuration(self) -> Dict[str, Any]: 

146 """Load configuration from file with error handling""" 

147 try: 

148 if os.path.exists(self.config_path): 

149 with open(self.config_path, 'r', encoding='utf-8') as f: 

150 config = json.load(f) 

151 

152 if self.enable_logging: 

153 logger.info(f"Loaded configuration from {self.config_path}") 

154 

155 return config 

156 else: 

157 if self.enable_logging: 

158 logger.warning(f"Configuration file not found: {self.config_path}") 

159 return {} 

160 

161 except json.JSONDecodeError as e: 

162 if self.enable_logging: 

163 logger.error(f"Invalid JSON in configuration file: {e}") 

164 return {} 

165 except Exception as e: 

166 if self.enable_logging: 

167 logger.error(f"Error loading configuration: {e}") 

168 return {} 

169 

170 def _validate_all_permissions(self) -> None: 

171 """Validate all permissions in the current configuration""" 

172 corrections_made = False 

173 

174 # Check agent permissions 

175 agents_config = self.config.get('agents', {}) 

176 for agent_name, agent_config in agents_config.items(): 

177 result = self.validate_agent_permission(agent_name, agent_config) 

178 if result.auto_corrected: 

179 corrections_made = True 

180 if self.enable_logging: 

181 logger.info(f"Auto-corrected permissions for agent: {agent_name}") 

182 

183 # Check settings permissions 

184 settings_config = self.config.get('projectSettings', {}) 

185 if 'allowedTools' in settings_config: 

186 result = self.validate_tool_permissions(settings_config['allowedTools']) 

187 if result.auto_corrected: 

188 corrections_made = True 

189 

190 # Save corrections if any were made 

191 if corrections_made: 

192 self._save_configuration() 

193 if self.enable_logging: 

194 logger.info("Saved corrected configuration") 

195 

196 def validate_agent_permission(self, agent_name: str, agent_config: Dict[str, Any]) -> ValidationResult: 

197 """ 

198 Validate and auto-correct agent permission configuration. 

199 

200 Addresses the permissionMode validation errors from debug logs: 

201 - Lines 50-80: Multiple agents with invalid permission modes ('ask', 'auto') 

202 """ 

203 self.stats['validations_performed'] += 1 

204 

205 result = ValidationResult(valid=True) 

206 

207 # Extract current permission mode 

208 current_mode = agent_config.get('permissionMode', 'default') 

209 

210 # Validate permission mode 

211 if current_mode not in self.VALID_PERMISSION_MODES: 

212 # Auto-correct to appropriate default 

213 suggested_mode = self._suggest_permission_mode(agent_name) 

214 

215 result.errors.append( 

216 f"Invalid permissionMode '{current_mode}' for agent '{agent_name}'. " 

217 f"Valid options: {sorted(self.VALID_PERMISSION_MODES)}" 

218 ) 

219 

220 # Auto-correction 

221 agent_config['permissionMode'] = suggested_mode 

222 result.corrected_mode = suggested_mode 

223 result.auto_corrected = True 

224 result.severity = PermissionSeverity.HIGH 

225 

226 self.stats['auto_corrections'] += 1 

227 self._audit_permission_change( 

228 resource_type=ResourceType.AGENT, 

229 resource_name=agent_name, 

230 action="permission_mode_correction", 

231 old_permissions={"permissionMode": current_mode}, 

232 new_permissions={"permissionMode": suggested_mode}, 

233 reason=f"Invalid permission mode '{current_mode}' auto-corrected to '{suggested_mode}'", 

234 auto_corrected=True 

235 ) 

236 

237 if self.enable_logging: 

238 logger.warning( 

239 f"Auto-corrected agent '{agent_name}' permissionMode from " 

240 f"'{current_mode}' to '{suggested_mode}'" 

241 ) 

242 

243 # Validate other agent configuration 

244 if 'model' in agent_config: 

245 model = agent_config['model'] 

246 if not isinstance(model, str) or not model.strip(): 

247 result.errors.append(f"Invalid model configuration for agent '{agent_name}'") 

248 result.severity = PermissionSeverity.MEDIUM 

249 

250 # Check for required fields 

251 required_fields = ['description', 'systemPrompt'] 

252 for req_field in required_fields: 

253 if req_field not in agent_config or not agent_config[req_field]: 

254 result.warnings.append( 

255 f"Missing or empty '{req_field}' for agent '{agent_name}'" 

256 ) 

257 

258 return result 

259 

260 def _suggest_permission_mode(self, agent_name: str) -> str: 

261 """ 

262 Suggest appropriate permission mode based on agent name and function. 

263 

264 This addresses the core issue from the debug logs where agents had 

265 invalid permission modes like 'ask' and 'auto'. 

266 """ 

267 # Check if agent name matches known patterns 

268 agent_lower = agent_name.lower() 

269 

270 # Security and compliance focused agents should be more restrictive 

271 if any(keyword in agent_lower for keyword in ['security', 'audit', 'compliance']): 

272 return PermissionMode.PLAN.value 

273 

274 # Code execution and modification agents should accept edits 

275 if any(keyword in agent_lower for keyword in ['expert', 'implementer', 'builder']): 

276 return PermissionMode.ACCEPT_EDITS.value 

277 

278 # Planning and analysis agents should use plan mode 

279 if any(keyword in agent_lower for keyword in ['planner', 'analyzer', 'designer']): 

280 return PermissionMode.PLAN.value 

281 

282 # Management agents should have appropriate permissions 

283 if any(keyword in agent_lower for keyword in ['manager', 'coordinator']): 

284 return PermissionMode.ACCEPT_EDITS.value 

285 

286 # Check against our default mappings 

287 if agent_name in self.DEFAULT_PERMISSIONS: 

288 return self.DEFAULT_PERMISSIONS[agent_name].value 

289 

290 # Default to safe option 

291 return PermissionMode.DEFAULT.value 

292 

293 def validate_tool_permissions(self, allowed_tools: List[str]) -> ValidationResult: 

294 """Validate list of allowed tools for security compliance""" 

295 result = ValidationResult(valid=True) 

296 

297 # Define dangerous tools that should require explicit approval 

298 dangerous_tools = { 

299 'Bash(rm -rf:*)', 

300 'Bash(sudo:*)', 

301 'Bash(chmod -R 777:*)', 

302 'Bash(dd:*)', 

303 'Bash(mkfs:*)', 

304 'Bash(fdisk:*)', 

305 'Bash(reboot:*)', 

306 'Bash(shutdown:*)', 

307 'Bash(git push --force:*)', 

308 'Bash(git reset --hard:*)' 

309 } 

310 

311 for tool in allowed_tools: 

312 if tool in dangerous_tools: 

313 result.warnings.append( 

314 f"Dangerous tool allowed: {tool}. Consider restricting access." 

315 ) 

316 result.severity = PermissionSeverity.HIGH 

317 self.stats['security_violations'] += 1 

318 

319 return result 

320 

321 def check_tool_permission(self, user_role: str, tool_name: str, operation: str) -> bool: 

322 """ 

323 Check if a user role is permitted to use a specific tool. 

324 

325 Implements unified permission checking with role hierarchy support. 

326 """ 

327 self.stats['validations_performed'] += 1 

328 

329 # Check cache first 

330 cache_key = f"{user_role}:{tool_name}:{operation}" 

331 if cache_key in self.permission_cache: 

332 return self.permission_cache[cache_key] 

333 

334 # Check direct permissions 

335 permitted = self._check_direct_permission(user_role, tool_name, operation) 

336 

337 # If not directly permitted, check role hierarchy 

338 if not permitted: 

339 for subordinate_role in self.role_hierarchy.get(user_role, []): 

340 if self._check_direct_permission(subordinate_role, tool_name, operation): 

341 permitted = True 

342 break 

343 

344 # Cache the result 

345 self.permission_cache[cache_key] = permitted 

346 

347 if not permitted: 

348 self.stats['permission_denied'] += 1 

349 if self.enable_logging: 

350 logger.warning(f"Permission denied: {user_role} cannot {operation} with {tool_name}") 

351 

352 return permitted 

353 

354 def _check_direct_permission(self, role: str, tool_name: str, operation: str) -> bool: 

355 """Check direct permissions for a specific role""" 

356 # Default permissions by role 

357 role_permissions = { 

358 "admin": ["*"], # All tools 

359 "developer": ["Task", "Read", "Write", "Edit", "Bash", "AskUserQuestion"], 

360 "user": ["Task", "Read", "AskUserQuestion"] 

361 } 

362 

363 allowed_tools = role_permissions.get(role, []) 

364 

365 # Wildcard permission 

366 if "*" in allowed_tools: 

367 return True 

368 

369 # Exact match 

370 if tool_name in allowed_tools: 

371 return True 

372 

373 # Pattern matching for Bash commands 

374 if tool_name.startswith("Bash(") and "Bash" in allowed_tools: 

375 return True 

376 

377 return False 

378 

379 def validate_configuration(self, config_path: Optional[str] = None) -> ValidationResult: 

380 """ 

381 Validate Claude Code configuration file for security and compliance. 

382 

383 This addresses the configuration security gaps identified in the analysis. 

384 """ 

385 config_to_validate = config_path or self.config_path 

386 result = ValidationResult(valid=True) 

387 

388 try: 

389 with open(config_to_validate, 'r', encoding='utf-8') as f: 

390 config = json.load(f) 

391 except FileNotFoundError: 

392 result.errors.append(f"Configuration file not found: {config_to_validate}") 

393 result.valid = False 

394 result.severity = PermissionSeverity.CRITICAL 

395 return result 

396 except json.JSONDecodeError as e: 

397 result.errors.append(f"Invalid JSON in configuration file: {e}") 

398 result.valid = False 

399 result.severity = PermissionSeverity.CRITICAL 

400 return result 

401 except Exception as e: 

402 result.errors.append(f"Error reading configuration file: {e}") 

403 result.valid = False 

404 result.severity = PermissionSeverity.HIGH 

405 return result 

406 

407 # Security validations 

408 security_checks = [ 

409 self._validate_file_permissions, 

410 self._validate_allowed_tools, 

411 self._validate_sandbox_settings, 

412 self._validate_mcp_servers 

413 ] 

414 

415 for check in security_checks: 

416 check_result = check(config) 

417 if not check_result: 

418 result.valid = False 

419 result.severity = PermissionSeverity.CRITICAL 

420 

421 return result 

422 

423 def _validate_file_permissions(self, config: Dict[str, Any]) -> bool: 

424 """Validate file permission settings""" 

425 permissions = config.get('permissions', {}) 

426 

427 # Check for overly permissive settings 

428 if 'deniedTools' in permissions: 

429 denied_tools = permissions['deniedTools'] 

430 # Ensure dangerous operations are denied 

431 dangerous_patterns = ['rm -rf', 'sudo', 'chmod 777', 'format', 'mkfs'] 

432 

433 for pattern in dangerous_patterns: 

434 found = any(pattern in tool for tool in denied_tools) 

435 if not found: 

436 logger.warning(f"Dangerous operation not denied: {pattern}") 

437 # Don't fail validation for this - just warn 

438 # return False 

439 

440 return True 

441 

442 def _validate_allowed_tools(self, config: Dict[str, Any]) -> bool: 

443 """Validate allowed tools configuration""" 

444 permissions = config.get('permissions', {}) 

445 allowed_tools = permissions.get('allowedTools', []) 

446 

447 # Ensure essential tools are available (but don't fail validation) 

448 essential_tools = ['Task', 'Read', 'AskUserQuestion'] 

449 for tool in essential_tools: 

450 if tool not in allowed_tools: 

451 logger.warning(f"Essential tool not allowed: {tool}") 

452 # Don't fail validation for this - just warn 

453 # return False 

454 

455 return True 

456 

457 def _validate_sandbox_settings(self, config: Dict[str, Any]) -> bool: 

458 """Validate sandbox security settings""" 

459 sandbox = config.get('sandbox', {}) 

460 

461 # Ensure sandbox is enabled 

462 if not sandbox.get('allowUnsandboxedCommands', False): 

463 return True 

464 

465 # If sandbox is disabled, ensure validated commands are restricted 

466 validated_commands = sandbox.get('validatedCommands', []) 

467 dangerous_commands = ['rm -rf', 'sudo', 'format', 'mkfs'] 

468 

469 for dangerous_cmd in dangerous_commands: 

470 if any(dangerous_cmd in validated_cmd for validated_cmd in validated_commands): 

471 logger.warning(f"Dangerous command in validated commands: {dangerous_cmd}") 

472 return False 

473 

474 return True 

475 

476 def _validate_mcp_servers(self, config: Dict[str, Any]) -> bool: 

477 """Validate MCP server configuration for security""" 

478 mcp_servers = config.get('mcpServers', {}) 

479 

480 for server_name, server_config in mcp_servers.items(): 

481 # Ensure command doesn't use dangerous flags 

482 if 'command' in server_config: 

483 command = server_config['command'] 

484 dangerous_flags = ['--insecure', '--allow-all', '--disable-ssl'] 

485 

486 for flag in dangerous_flags: 

487 if flag in command: 

488 logger.warning(f"Dangerous flag in MCP server {server_name}: {flag}") 

489 return False 

490 

491 return True 

492 

493 def auto_fix_agent_permissions(self, agent_name: str) -> ValidationResult: 

494 """ 

495 Automatically fix agent permission configuration. 

496 

497 This is the main method to address the permissionMode errors 

498 from the debug logs (Lines 50-80). 

499 """ 

500 # Get current agent configuration 

501 agents_config = self.config.setdefault('agents', {}) 

502 agent_config = agents_config.get(agent_name, {}) 

503 

504 # Validate and fix 

505 result = self.validate_agent_permission(agent_name, agent_config) 

506 

507 # Save configuration if corrections were made 

508 if result.auto_corrected: 

509 agents_config[agent_name] = agent_config 

510 self._save_configuration() 

511 

512 if self.enable_logging: 

513 logger.info(f"Fixed permissions for agent: {agent_name}") 

514 

515 return result 

516 

517 def auto_fix_all_agents(self) -> Dict[str, ValidationResult]: 

518 """Auto-fix all agent permissions in the configuration""" 

519 results = {} 

520 

521 agents_config = self.config.get('agents', {}) 

522 for agent_name in agents_config: 

523 results[agent_name] = self.auto_fix_agent_permissions(agent_name) 

524 

525 # Also check for agents mentioned in the debug log that might not be in config 

526 debug_log_agents = [ 

527 "backend-expert", "security-expert", "api-designer", "monitoring-expert", 

528 "performance-engineer", "migration-expert", "mcp-playwright-integrator", 

529 "quality-gate", "frontend-expert", "debug-helper", "ui-ux-expert", 

530 "trust-checker", "project-manager", "mcp-context7-integrator", 

531 "mcp-figma-integrator", "tdd-implementer", "format-expert", 

532 "mcp-notion-integrator", "devops-expert", "docs-manager", 

533 "implementation-planner", "skill-factory", "component-designer", 

534 "database-expert", "agent-factory", "git-manager", "sync-manager", 

535 "spec-builder", "doc-syncer", "accessibility-expert", "cc-manager" 

536 ] 

537 

538 for agent_name in debug_log_agents: 

539 if agent_name not in agents_config: 

540 # Create default configuration for missing agents 

541 agents_config[agent_name] = { 

542 "permissionMode": self._suggest_permission_mode(agent_name), 

543 "description": f"Auto-generated configuration for {agent_name}", 

544 "systemPrompt": f"Default system prompt for {agent_name}" 

545 } 

546 

547 results[agent_name] = ValidationResult( 

548 valid=True, 

549 auto_corrected=True, 

550 warnings=[f"Created default configuration for agent: {agent_name}"] 

551 ) 

552 

553 if any(result.auto_corrected for result in results.values()): 

554 self._save_configuration() 

555 

556 return results 

557 

558 def _save_configuration(self) -> None: 

559 """Save current configuration to file""" 

560 try: 

561 # Create backup 

562 if os.path.exists(self.config_path): 

563 backup_path = f"{self.config_path}.backup.{int(time.time())}" 

564 os.rename(self.config_path, backup_path) 

565 if self.enable_logging: 

566 logger.info(f"Created configuration backup: {backup_path}") 

567 

568 # Save updated configuration 

569 with open(self.config_path, 'w', encoding='utf-8') as f: 

570 json.dump(self.config, f, indent=2, ensure_ascii=False) 

571 

572 if self.enable_logging: 

573 logger.info(f"Saved configuration to {self.config_path}") 

574 

575 except Exception as e: 

576 if self.enable_logging: 

577 logger.error(f"Error saving configuration: {e}") 

578 

579 def _audit_permission_change(self, resource_type: ResourceType, resource_name: str, 

580 action: str, old_permissions: Optional[Dict[str, Any]], 

581 new_permissions: Optional[Dict[str, Any]], reason: str, 

582 auto_corrected: bool) -> None: 

583 """Log permission changes for audit trail""" 

584 audit_entry = PermissionAudit( 

585 timestamp=time.time(), 

586 user_id=None, # System correction 

587 resource_type=resource_type, 

588 resource_name=resource_name, 

589 action=action, 

590 old_permissions=old_permissions, 

591 new_permissions=new_permissions, 

592 reason=reason, 

593 auto_corrected=auto_corrected 

594 ) 

595 

596 self.audit_log.append(audit_entry) 

597 

598 # Keep audit log size manageable 

599 if len(self.audit_log) > 1000: 

600 self.audit_log = self.audit_log[-1000:] 

601 

602 def get_permission_stats(self) -> Dict[str, Any]: 

603 """Get permission management statistics""" 

604 return { 

605 **self.stats, 

606 'cached_permissions': len(self.permission_cache), 

607 'audit_log_entries': len(self.audit_log), 

608 'configured_agents': len(self.config.get('agents', {})) 

609 } 

610 

611 def get_recent_audits(self, limit: int = 50) -> List[PermissionAudit]: 

612 """Get recent permission audit entries""" 

613 return self.audit_log[-limit:] 

614 

615 def export_audit_report(self, output_path: str) -> None: 

616 """Export audit report to file""" 

617 report = { 

618 'generated_at': time.time(), 

619 'stats': self.get_permission_stats(), 

620 'recent_audits': [ 

621 { 

622 'timestamp': audit.timestamp, 

623 'resource_type': audit.resource_type.value, 

624 'resource_name': audit.resource_name, 

625 'action': audit.action, 

626 'reason': audit.reason, 

627 'auto_corrected': audit.auto_corrected 

628 } 

629 for audit in self.get_recent_audits() 

630 ] 

631 } 

632 

633 with open(output_path, 'w', encoding='utf-8') as f: 

634 json.dump(report, f, indent=2, ensure_ascii=False) 

635 

636 if self.enable_logging: 

637 logger.info(f"Exported audit report to {output_path}") 

638 

639 

640# Global instance for easy import 

641permission_manager = UnifiedPermissionManager() 

642 

643 

644def validate_agent_permission(agent_name: str, agent_config: Dict[str, Any]) -> ValidationResult: 

645 """Convenience function to validate agent permissions""" 

646 return permission_manager.validate_agent_permission(agent_name, agent_config) 

647 

648 

649def check_tool_permission(user_role: str, tool_name: str, operation: str) -> bool: 

650 """Convenience function to check tool permissions""" 

651 return permission_manager.check_tool_permission(user_role, tool_name, operation) 

652 

653 

654def auto_fix_all_agent_permissions() -> Dict[str, ValidationResult]: 

655 """Convenience function to auto-fix all agent permissions""" 

656 return permission_manager.auto_fix_all_agents() 

657 

658 

659def get_permission_stats() -> Dict[str, Any]: 

660 """Convenience function to get permission statistics""" 

661 return permission_manager.get_permission_stats() 

662 

663 

664if __name__ == "__main__": 

665 # Demo script for testing the permission manager 

666 print("🔧 MoAI-ADK Unified Permission Manager Demo") 

667 print("=" * 50) 

668 

669 # Test agent permission validation 

670 test_agents = [ 

671 { 

672 "name": "backend-expert", 

673 "config": {"permissionMode": "ask", "description": "Backend expert agent"} 

674 }, 

675 { 

676 "name": "security-expert", 

677 "config": {"permissionMode": "auto", "description": "Security expert agent"} 

678 }, 

679 { 

680 "name": "api-designer", 

681 "config": {"permissionMode": "plan", "description": "API designer agent"} 

682 } 

683 ] 

684 

685 print("Testing agent permission validation and auto-correction...") 

686 

687 for agent in test_agents: 

688 print(f"\nTesting agent: {agent['name']}") 

689 print(f"Original permissionMode: {agent['config'].get('permissionMode', 'default')}") 

690 

691 result = permission_manager.validate_agent_permission(agent['name'], agent['config']) 

692 

693 print(f"Valid: {result.valid}") 

694 print(f"Auto-corrected: {result.auto_corrected}") 

695 

696 if result.corrected_mode: 

697 print(f"Corrected to: {result.corrected_mode}") 

698 

699 if result.errors: 

700 print(f"Errors: {result.errors}") 

701 

702 if result.warnings: 

703 print(f"Warnings: {result.warnings}") 

704 

705 print("\n📊 Permission Statistics:") 

706 stats = permission_manager.get_permission_stats() 

707 for key, value in stats.items(): 

708 print(f" {key}: {value}") 

709 

710 print("\n✨ Demo completed! The Unified Permission Manager addresses") 

711 print(" the agent permission validation errors from the debug logs.")