| Version | Status |
|---|---|
| < 0.1.0-alpha | Not supported — spec / pre-release only |
| 0.1.0-alpha and later | Supported once released |
Only the latest minor version within a supported major line receives security patches.
Please do not disclose security vulnerabilities in public issues, pull requests, or discussions.
Report privately using GitHub's private vulnerability reporting:
- Go to the Security tab of this repository.
- Click Report a vulnerability to open a private security advisory visible only to the maintainers.
This keeps the report confidential until a fix is released. If private reporting is unavailable to you, open a regular GitHub issue that says only "requesting a security contact" — without any vulnerability details — and a maintainer will follow up.
If you do not receive an acknowledgment within 48 hours, or the issue is critical, add a comment to your advisory (or follow-up issue) prefixed with [URGENT].
- A clear description of the vulnerability and its impact.
- Steps to reproduce, ideally as a minimal test case or script.
- The affected component, version, and configuration.
- Any proposed mitigation or patch.
- Never include credentials, API keys, tokens, passwords, or private keys in the report.
- If logs or screenshots contain secrets, redact them first.
- If a reproduction requires secrets, say so in the advisory and we will arrange a secure transfer channel.
| Phase | Target |
|---|---|
| Initial acknowledgment | 48 hours |
| Severity assessment and reproduction confirmation | 5 business days |
| Patch development (Critical / High) | 14 calendar days |
| Patch development (Medium / Low) | 30 calendar days |
| Coordinated disclosure after fix release | 90 days from acknowledgment, or sooner by mutual agreement |
We follow a coordinated vulnerability disclosure process. We ask that reporters give us a reasonable time to address the issue before disclosing it publicly.
In scope:
- The Django backend (
backend/). - The host daemon (
host-agent/). - The connectors (
connectors/librecontrol-mcp/,connectors/messaging-gateway/). - Authentication flows, session management, and credential storage.
- Policy enforcement, approval workflows, and audit pipelines.
- Runtime adapters and PTY supervision.
- Docker Compose and deployment configurations.
Out of scope:
- Third-party dependencies unless the vulnerability is directly exploitable through our integration surface.
- Infrastructure outside our Docker Compose and documented deployment paths.
- Social engineering attacks against individual users.
- LLM provider platforms (Anthropic, OpenAI, Google, etc.) — report to them directly.
LibreControl's core feature is remote code execution by design: a message in
your chat surface drives a coding agent on your machine. The default engine runs
claude with --permission-mode bypassPermissions — no per-tool approval gate.
This is trusted-host mode, and it is a deliberate trade-off, not an oversight:
- The threat model assumes a single operator, self-hosting on a machine they own, driving their own coding sessions from their own chat account.
- The gate is identity, not per-action review: on the Telegram surface,
only user ids in
TELEGRAM_ALLOWED_CHAT_IDScan drive sessions, and the allowlist defaults to deny-all when unset. Host daemons must enroll and sign every WebSocket connection (HMAC overhost_id:ts:nonce, constant-time compare, nonce replay rejection —backend/apps/hostlink/security.py). - The messaging-gateway surface (WhatsApp/Slack/Discord/Signal/iMessage) is gated by the sidecar's bearer token, not by the Telegram allowlist — treat that token as equivalent to an allowlist entry, and do not enable gateway surfaces until you have reviewed who can message the linked accounts (see the threat model for the open review item on this path).
- Consequences you accept: anyone on the allowlist can execute code on the enrolled host, and a compromised Telegram account of an allowlisted user equals code execution. Keep the allowlist minimal, use a dedicated bot token, and rotate it on any suspicion.
- If you want a per-action gate, run
LCTL_HEADLESS_ENGINE=sdk, which routes every tool call through an Allow/Deny prompt in the chat before it executes.
host-agent/agent_host/input_policy.py is often mistaken for a general safety
gate. It is not. Be precise about its scope:
| Path | Engine | Classifier runs? | What actually gates it |
|---|---|---|---|
| Headless drive (the default) | LCTL_HEADLESS_ENGINE=interactive, or unset |
No | Telegram identity allowlist only. The agent runs with bypassPermissions. |
| Headless drive, opt-in | LCTL_HEADLESS_ENGINE=sdk |
No | Per-tool Allow/Deny prompt in chat |
| Raw PTY injection | librectl run / tmux |
Yes | Classifier + approval tap |
Two consequences worth stating plainly:
- On a default install the classifier never executes. Text sent to a
headless session is a natural-language prompt for the agent, not a shell
command, so it is not classified — and the agent it reaches has no per-tool
gate.
input_policy.pyis irrelevant to that path. - On the PTY path the classifier is a speed-bump, not a boundary. It is a
denylist over free-form shell text and is known-bypassable by construction:
$HOMEfor~, quoting to break token matches, splitting a download and its execution across two individually-benign commands, or indirect interpreters (perl -e,awk 'BEGIN{system()}',python -c). Adding patterns does not change this. It catches mistakes and careless pastes; it does not stop an adversary.
Treat any enrolled host as a machine on which the allowlisted chat identity can run arbitrary code, and isolate it accordingly. Do not rely on the classifier when deciding what a host is allowed to reach.
Do not deploy this multi-tenant. There is no per-connector "drive" scope yet
(scopes are stored on ConnectorKey but not enforced at authentication time);
the design target is one operator per backend.
- Threat model:
docs/security/threat-model.md - Security checklist:
docs/security/security-checklist.md