Files
Aetheel/docs/security-audit.md
tanmay11k 6d73f74e0b feat: config-driven architecture, install wizard, live runtime switching, usage tracking, auto-failover
Major changes:
- Config-driven adapters: all channels (Slack, Discord, Telegram, WebChat, Webhooks) controlled via config.json with enabled flags and token auto-detection, no CLI flags required
- Runtime engine field: runtime.engine selects opencode/claude from config
- Interactive install script: 8-phase setup wizard with AI runtime detection/installation, token setup, identity file personalization (personality presets), aetheel CLI command, background service (launchd/systemd)
- Live runtime switching: /engine, /model, /provider commands hot-swap the AI runtime from chat without restart, changes persisted to config.json
- Usage tracking: per-request cost extraction from Claude Code JSON output, cumulative stats via /usage command
- Auto-failover: rate limit detection on both runtimes, automatic switch to other engine on quota errors with user notification
- Chat commands work without / prefix (Slack intercepts / in channels), commands: engine, model, provider, config, usage, reload, cron, subagents, status, help
- /config set for editing config.json from chat with dotted key notation
- Security audit saved to docs/security-audit.md
- Full command reference in docs/commands.md
- Future changes doc with NanoClaw agent teams analysis
- Logo added to README and WebChat UI
- README fully rewritten with all features documented
2026-02-18 01:07:12 -05:00

6.8 KiB

Aetheel Security Audit

Date: February 17, 2026 Scope: Full codebase review of all modules


CRITICAL

1. Path Traversal in memory/manager.pyread_file()

The method accepts absolute paths and resolves them with os.path.realpath() but never validates the result is within the workspace directory. An attacker (or the AI itself) could read arbitrary files:

# Current code — no containment check
if os.path.isabs(raw):
    abs_path = os.path.realpath(raw)

Fix: Add a check like if not abs_path.startswith(self._workspace_dir): raise ValueError("path outside workspace")

2. Arbitrary Code Execution via Hook handler.py Loading

hooks/hooks.py_load_handler uses importlib.util.spec_from_file_location to dynamically load and execute arbitrary Python from handler.py files found in the workspace. If an attacker can write a file to ~/.aetheel/workspace/hooks/<name>/handler.py, they get full code execution. There's no sandboxing, signature verification, or allowlisting.

3. Webhook Auth Defaults to Open Access

webhooks/receiver.py_check_auth:

if not self._config.token:
    return True  # No token configured = open access

If the webhook receiver is enabled without a token, anyone on the network can trigger AI actions. The default config writes "token": "" which means open access.

4. AI-Controlled Action Tags Execute Without Validation

main.py_process_action_tags parses the AI's response text for action tags like [ACTION:cron|...], [ACTION:spawn|...], and [ACTION:remind|...]. The AI can:

  • Schedule arbitrary cron jobs with any expression
  • Spawn unlimited subagent tasks
  • Set reminders with any delay

There's no validation that the AI was asked to do this, no user confirmation, and no rate limiting. A prompt injection attack via any adapter could trigger these.


HIGH

5. No Input Validation on Webhook POST Bodies

webhooks/receiver.py — JSON payloads are parsed but never schema-validated. Fields like channel_id, sender, channel are passed through directly. The body dict is stored in raw_event and could contain arbitrarily large data.

6. No Request Size Limits on HTTP Endpoints

Neither the webhook receiver nor the WebChat adapter set client_max_size on the aiohttp Application. Default is 2MB but there's no explicit limit, and no per-request timeout.

7. WebSocket Has No Authentication

adapters/webchat_adapter.py — Anyone who can reach the WebSocket endpoint at /ws can interact with the AI. No token, no session cookie, no origin check. If the host is changed from 127.0.0.1 to 0.0.0.0, this becomes remotely exploitable.

8. No Rate Limiting Anywhere

No rate limiting on:

  • Webhook endpoints
  • WebSocket messages
  • Adapter message handlers
  • Subagent spawning (only a concurrent limit of 3, but no cooldown)
  • Scheduler job creation

9. Cron Expression Not Validated Before APScheduler

scheduler/scheduler.py_register_cron_job only checks len(parts) != 5. Malformed values within fields (e.g., 999 999 999 999 999) are passed directly to CronTrigger, which could cause unexpected behavior or exceptions.

10. Webhook Token in Query Parameter

webhooks/receiver.py:

if request.query.get("token") == self._config.token:
    return True

Query parameters are logged in web server access logs, browser history, and proxy logs. This leaks the auth token.


MEDIUM

11. SQLite Databases Created with Default Permissions

sessions.db, scheduler.db, and memory.db are all created under ~/.aetheel/ with default umask permissions. On multi-user systems, these could be world-readable.

12. Webhook Token Stored in config.json

The webhooks.token field in config.py is read from and written to config.json, which is a plaintext file. Secrets should only live in .env.

13. No HTTPS on Any HTTP Endpoint

Both WebChat (port 8080) and webhooks (port 8090) run plain HTTP. Even on localhost, this is vulnerable to local network sniffing.

14. Full Environment Passed to Subprocesses

_build_cli_env() in both runtimes copies os.environ entirely to the subprocess, which may include sensitive variables beyond what the CLI needs.

15. Session Logs Contain Full Conversations in Plaintext

memory/manager.pylog_session() writes unencrypted markdown files to ~/.aetheel/workspace/daily/. No access control, no encryption, no retention policy.

16. XSS Partially Mitigated in chat.html but Fragile

The renderMarkdown() function escapes <, >, & first, then applies regex-based markdown rendering. User messages use textContent (safe). AI messages use innerHTML with the escaped+rendered output. The escaping happens before markdown processing, which is the right order, but the regex-based approach is fragile — edge cases in the markdown regexes could potentially bypass the escaping.

17. No CORS Headers on WebChat

The aiohttp app doesn't configure CORS. If exposed beyond localhost, cross-origin requests could interact with the WebSocket.


LOW

18. Loose Dependency Version Constraints

pyproject.toml:

  • python-telegram-bot>=21.0 — no upper bound
  • discord.py>=2.4.0 — no upper bound
  • fastembed>=0.7.4 — no upper bound

These could pull in breaking or vulnerable versions on fresh installs.

19. No Security Scanning in CI/Test Pipeline

No bandit, safety, pip-audit, or similar tools in the test suite or project config.

20. config edit Uses $EDITOR Without Sanitization

cli.py:

editor = os.environ.get("EDITOR", "nano")
subprocess.run([editor, CONFIG_PATH], check=True)

If $EDITOR contains spaces or special characters, this could behave unexpectedly (though subprocess.run with a list is safe from shell injection).

21. No Data Retention/Cleanup for Session Logs

Session logs accumulate indefinitely in daily/. No automatic pruning.

22. SubagentBus Has No Authentication

The pub/sub bus allows any code in the process to publish/subscribe to any channel. No isolation between subagents.


The most impactful changes to make first:

  1. Add path containment check in read_file() — one-line fix, prevents file system escape
  2. Make webhook auth mandatory when webhooks.enabled = true — refuse to start without a token
  3. Add input schema validation on webhook POST bodies
  4. Validate cron expressions more strictly before passing to APScheduler
  5. Add rate limiting to webhook and WebSocket endpoints (e.g., aiohttp middleware)
  6. Move webhooks.token to .env only, remove from config.json
  7. Add WebSocket origin checking or token auth to WebChat
  8. Set explicit client_max_size on aiohttp apps
  9. Pin dependency upper bounds in pyproject.toml
  10. Add bandit to the test pipeline