T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/aegis_onboard.py:113
- Finding
- API credentials are stored in a plaintext configuration file without enforced restrictive permissions<![CDATA[ ## Vulnerability Details **File Location**: `scripts/aegis_onboard.py:113-153` and `scripts/aegis_onboard.py:168-173` **Vulnerability Type**: Plaintext credential storage and secret disclosure **Risk Level**: Medium ### Vulnerable Code ```python elif llm_choice == "2": llm_endpoint = input(" API base URL (e.g. https://openrouter.ai/api): ").strip() llm_model = input(" Model name (e.g. meta-llama/llama-3-8b-instruct): ").strip() llm_key = input(" API key: ").strip() if llm_endpoint and llm_model: llm_config = {"enabled": True, "provider": "openai", "endpoint": llm_endpoint, "model": llm_model, "api_key": llm_key} else: print(" ⚠️ Missing endpoint or model — LLM disabled.") print("\n🔑 API KEYS (optional — press Enter to skip)") newsapi_key = input(" NewsAPI.org key (free at newsapi.org/register): ").strip() or None config = { "version": "1.1.0", "location": { "country": country, "country_name": country_name, "city": city, "timezone": tz }, "language": lang, "alerts": { "critical_instant": True, "high_batch_minutes": int(batch_min), "medium_digest_hours": int(digest_hrs) }, "briefings": { "morning": morning, "evening": evening }, "scan_interval_minutes": int(interval), "llm": llm_config, "api_keys": {} } if newsapi_key: config["api_keys"]["newsapi"] = newsapi_key # Save CONFIG_PATH.parent.mkdir(parents=True, exist_ok=True) with open(CONFIG_PATH, 'w') as f: json.dump(config, f, indent=2) ``` The same file also prints the complete configuration, including stored credentials: ```python def show_config(): """Display current configuration.""" if not CONFIG_PATH.exists(): print("No AEGIS configuration found. Run setup first.") return with open(CONFIG_PATH) as f: config = json.load(f) print(json.dumps(config, indent=2)) ``` ### Technical Analysis Th ...[truncated 1954 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Store API keys in a dedicated operating-system secret manager or the existing OpenClaw secret service rather than in the general JSON configuration. 2. If file-based storage is unavoidable, create the file atomically with owner-only permissions: ```python import os fd = os.open(CONFIG_PATH, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600) with os.fdopen(fd, "w") as f: json.dump(config, f, indent=2) ``` 3. Apply and verify mode `0600` on existing configuration files before reading secrets from them. 4. Use `getpass.getpass()` rather than `input()` when collecting credentials. 5. Redact known secret fields in `show_config()`: ```python display = copy.deepcopy(config) display.get("llm", {}).pop("api_key", None) for key in display.get("api_keys", {}): display["api_keys"][key] = "<redacted>" if "telegram" in display: display["telegram"]["bot_token"] = "<redacted>" ``` 6. Document credential storage, rotation, and revocation procedures. ]]>
