This document describes the security posture of the logos system. It is intended to be honest and precise rather than aspirational.
This is a single-operator personal tool. The single_operator constitutional
axiom (weight: 100, hardcoded) explicitly prohibits authentication, authorization,
multi-user features, role management, and administrative interfaces. These are not
missing features -- they are T0 violations that must not be built (see
axioms/implications/single-operator.yaml, implications su-auth-001,
su-security-001, su-admin-001, su-feature-001).
The security model relies entirely on:
- Localhost-only network binding (no services exposed beyond 127.0.0.1)
- OS-level access control (only the operator has access to the machine)
- GPG-encrypted secret storage (
pass)
There is no defense-in-depth beyond these layers. If an attacker has local access to the machine, they have full access to all data and services.
All Docker services bind exclusively to 127.0.0.1. No service is reachable from
the LAN or WAN. Port bindings from llm-stack/docker-compose.yml:
| Service | Host Port(s) | Container Port(s) |
|---|---|---|
| Qdrant | 127.0.0.1:6433, 6434 | 6333, 6334 |
| Ollama | 127.0.0.1:11434 | 11434 |
| PostgreSQL | 127.0.0.1:5532 | 5432 |
| LiteLLM | 127.0.0.1:4100 | 4000 |
| ClickHouse | 127.0.0.1:8223, 9100 | 8123, 9000 |
| Langfuse Web | 127.0.0.1:3100 | 3000 |
| Langfuse Worker | 127.0.0.1:3130 | 3030 |
| MinIO | 127.0.0.1:9190, 9191 | 9000, 9001 |
| ntfy | 127.0.0.1:8190 | 80 |
| Chatterbox TTS | 127.0.0.1:4223 | 5123 |
| Logos Web | 127.0.0.1:8052 | 80 |
| Logos API | 127.0.0.1:8051 | 8050 |
All containers share a single Docker network (mgmt-logos). There is no
network segmentation between containers -- any container can reach any other
container by service name. This is acceptable for a single-operator system
but would be a concern in any shared environment.
Secrets are stored in pass (GPG-encrypted password store) and loaded into the
shell environment via direnv (see .envrc). The pattern:
export LITELLM_API_KEY="$(pass show litellm/master-key 2>/dev/null || echo 'changeme')"The .envrc file is gitignored. No .env files are committed. Docker containers
receive secrets via environment variables interpolated from the host shell into
docker-compose.yml. Secrets managed this way include:
ANTHROPIC_API_KEY-- Anthropic API accessGOOGLE_API_KEY-- Google API accessLITELLM_MASTER_KEY-- LiteLLM gateway authenticationLANGFUSE_PUBLIC_KEY,LANGFUSE_SECRET_KEY-- Langfuse trace ingestionLANGFUSE_SALT,LANGFUSE_ENCRYPTION_KEY,LANGFUSE_NEXTAUTH_SECRET-- Langfuse internal cryptoPOSTGRES_PASSWORD-- PostgreSQL accessCLICKHOUSE_PASSWORD-- ClickHouse accessREDIS_AUTH-- Redis authenticationMINIO_ROOT_PASSWORD-- MinIO object storageTAVILY_API_KEY-- Tavily web search
The shared/config.py module reads LITELLM_API_KEY from the environment with a
fallback default of "changeme". This fallback exists for development convenience
and would not protect anything in practice -- the LiteLLM proxy is only reachable
on localhost anyway.
The system handles several categories of data with varying sensitivity:
High sensitivity -- management data (DATA_DIR):
People notes, coaching records, feedback records, 1:1 prep documents, meeting
notes, performance review evidence. This is HR-adjacent content about real team
members. Stored as markdown files with YAML frontmatter on the local filesystem.
In Docker, mounted as a bind mount at /app/data/.
Medium sensitivity -- LLM traces (Langfuse): Every LLM call is traced through Langfuse, including prompts and responses. These traces contain synthesized management context -- team snapshots, briefings, prep documents. Stored in PostgreSQL + ClickHouse + MinIO within the Docker stack.
Medium sensitivity -- operator profile (Qdrant): The management profiler generates a 6-dimension self-assessment of the operator's management patterns. Stored as vector embeddings in Qdrant. The profile describes the operator, never team members.
Low sensitivity -- vector embeddings (Qdrant): Derived representations of documents, knowledge base content. Not directly human-readable but could be used for similarity-based reconstruction.
Low sensitivity -- infrastructure state: Service health, model availability, collection statistics. Generated by the introspect and system_check agents.
All LLM calls from agents route through the LiteLLM proxy at localhost:4100,
which forwards to Anthropic's API (Claude models) or local Ollama. The call chain:
Agent -> LiteLLM (localhost:4100) -> Anthropic API (cloud)
-> Ollama (localhost:11434, local GPU)
LiteLLM traces every call to Langfuse for observability. Management data (people
notes, coaching, feedback) is included in LLM prompts when agents synthesize
prep documents, briefings, and team snapshots. This means sensitive HR-adjacent
content is sent to Anthropic's API. Local Ollama models handle embeddings only
(nomic-embed-text), not management synthesis.
Anthropic's data retention and usage policies apply to all cloud LLM calls.
All containers run on a single shared Docker network (mgmt-logos). Resource
limits are set on every container:
| Service | Memory Limit |
|---|---|
| Qdrant | 4 GB |
| Ollama | 20 GB |
| PostgreSQL | 4 GB |
| LiteLLM | 2 GB |
| ClickHouse | 4 GB |
| Langfuse Web | 2 GB |
| Langfuse Worker | 2 GB |
| MinIO | 1 GB |
| Redis | 512 MB |
| ntfy | 256 MB |
| Chatterbox TTS | 4 GB |
| Logos Web | 256 MB |
Images use pinned digests (sha256) for reproducibility. The Ollama container has
GPU passthrough via nvidia-container-toolkit. Data is persisted in named Docker
volumes (mgmt_qdrant_data, mgmt_postgres_data, etc.) and bind mounts for
configuration files (read-only where possible).
- Not enterprise software. Built for one person on one machine.
- Not multi-tenant. The single_operator axiom constitutionally prohibits it.
- Not designed for network exposure. If any port is exposed beyond localhost, the entire security model breaks.
- Not audited. No external security review has been performed.
- Not compliant with any framework. SOC 2, ISO 27001, GDPR -- none apply. This is a personal tool on a personal workstation.
- No TLS between services. All inter-service communication is plaintext HTTP over the Docker network and localhost. Acceptable because nothing leaves the machine, but a compromised container could sniff traffic.
- No authentication on the logos API. Port 8051 serves 32 REST endpoints with no auth. Protected only by the 127.0.0.1 binding.
- No encryption at rest. Qdrant, PostgreSQL, ClickHouse, and MinIO store data unencrypted on Docker volumes. Full-disk encryption (if enabled at the OS level) is the only protection.
- No rate limiting. Any localhost process can make unlimited requests to any service.
- Sensitive data in LLM prompts. Management data about real team members is sent to Anthropic's cloud API. The operator accepts this tradeoff.
- Fallback credentials in code.
shared/config.pydefaultsLITELLM_API_KEYto"changeme"if the environment variable is unset. This is a development convenience, not a security control. - Shared Docker network. All containers can communicate freely. A compromised container could reach any other service.
This is a personal project. There is no bug bounty program or security team. If you find a security issue, open a GitHub issue or contact the repository owner directly.