If you discover a security vulnerability in Genesis, please report it responsibly:
- DO NOT open a public GitHub issue.
- Use GitHub Security Advisories to create a private report, or contact the project maintainers directly.
- Include:
- Description of the vulnerability
- Steps to reproduce
- Potential impact
- Suggested fix (if any)
We aim to acknowledge security reports within 48 hours and provide a substantive response within one week.
| Version | Supported |
|---|---|
| v3.x | Yes |
| < v3 | No |
Genesis v3 is a ground-up rebuild. Earlier versions (v1/v2) are not maintained and should not be used.
Genesis is an autonomous agent system. The same principle that governs its autonomy also governs its security: trust is earned through verified behavior, not assumed.
An autonomous system that can act on your behalf must also be a system you can trust with increasing responsibility over time. The security model is not a lockdown bolted onto an agent -- it is the autonomy model itself. Every layer described below exists because Genesis takes seriously the question: "what has this system demonstrated it can be trusted to do?"
Genesis implements a graduated autonomy framework. The shipped ladder is
four levels, L1-L4 (genesis.autonomy.types.AutonomyLevel); the design
document describes an eventual L5-L7, which is not built. Higher levels
are unlocked through demonstrated competence, verified by the system and
approved by the operator.
Each autonomy level gates specific capabilities:
- L1: Simple tool use — fully autonomous.
- L2: Known-pattern tasks — mostly autonomous.
- L3: Novel tasks — propose and execute with a checkpoint.
- L4: Proactive outreach — threshold-gated and governed.
Autonomy permissions are stored per-category and can be revoked instantly.
There are four categories (genesis.autonomy.types.AutonomyCategory):
direct_session, background_cognitive, sub_agent, outreach. The operator
always has final authority. See
docs/architecture/genesis-v3-autonomous-behavior-design.md for the full
framework, and read it as design intent rather than as a description of what
currently ships.
The autonomous-CLI approval gate is the hard boundary underneath all of it.
Every autonomous background Claude Code session must be rooted in an explicit
operator approval: manual_approval_required (autonomy/cli_policy.py)
defaults to True, and AutonomousCliApprovalGate refuses to dispatch without
one. No autonomy level unlocks past it, and it is not a tunable — a fork that
defaults it off has removed the guarantee the rest of this section describes.
Genesis is designed to run in an isolated environment (container or VM). This is not optional hardening -- it is the assumed deployment model. The container boundary limits blast radius: even if an autonomous action goes wrong, the damage is contained to the Genesis environment.
Recommendations:
- Run Genesis in a dedicated container or VM, not on a shared workstation.
- Use a non-root user account.
- Restrict network egress to required endpoints (LLM APIs, Qdrant, Ollama).
Genesis exposes a local dashboard over HTTP (and, optionally, a remote-desktop
/ noVNC console). So it can be reached through a reverse proxy or a private
overlay network, the bundled service unit binds the dashboard to all interfaces
(0.0.0.0) by default rather than to loopback. When a dashboard password is
set, state-changing /api requests are gated: check_api_mutation_auth
(dashboard/auth.py) runs as an app-level before_request and requires either
the internal bearer token or an authenticated same-origin cookie, with CSRF
checked from Sec-Fetch-Site/Origin/Referer and failing closed. /v1
enforces its own separate bearer.
Read the limits of that gate carefully, because they decide whether network isolation is still load-bearing for you — it is:
- It covers mutations only.
GET/HEAD/OPTIONSstay open by design, so health probes and dashboard polling keep working. Anything readable through the API is readable by anyone who can reach the port. - It is inert when no dashboard password is set, which is the default.
- It exempts everything under the
/api/genesis/auth/prefix — a prefix match, not a fixed list, so any route added there in future is exempt by default. Today that prefix holds only login, logout and an auth-status probe, none of which mutate a credential. - It can be disabled outright with
GENESIS_DASHBOARD_API_AUTH=off. - The built-in web terminal and the noVNC console are not covered by it.
This is safe only under the assumed deployment model: the host is not publicly exposed. The dashboard is meant to be reachable through one of:
- a private overlay network (e.g., Tailscale / WireGuard), where only your own devices can reach the port; and/or
- a host-side reverse proxy that forwards to the container.
The threat model is public exposure — not your LAN or private overlay. If you run Genesis on a host reachable from the public internet, you must restrict the management ports yourself; Genesis does not assume an authenticating gateway in front of them.
Operator checklist:
- Do not port-forward the dashboard or console ports from a public router.
- Bind or firewall the management ports to your private/overlay network — e.g.
restrict the dashboard port to your overlay's address range (Tailscale uses
the
100.64.0.0/10CGNAT range) withnftables/ufw, or change the service unit to bind a specific private interface instead of0.0.0.0. - Treat the built-in web terminal and the noVNC console as unauthenticated administrative access: anyone who can reach those ports can drive Genesis. For the dashboard API, assume the same for reads and for any install with no dashboard password set. Network isolation remains the primary control; the mutation gate above is a second layer, not a replacement for it.
Security audits should verify this network restriction (firewall / overlay)
rather than re-flagging the 0.0.0.0 bind, which is intentional for the
proxy/overlay deployment model.
Genesis uses PreToolUse hooks to enforce tool-level security policies at runtime. These hooks fire on every tool invocation, including autonomous sessions, and cannot be bypassed by the agent.
Examples of enforced policies:
- Blocking shell commands that match dangerous patterns (e.g.
rm -rf /) - Blocking destructive git operations, pushes and merges that have not met the repository's review gates, and writes to protected paths
- Blocking web fetches to known-problematic URLs
- Blocking editable installs pointed at a worktree, which would redirect system-wide imports
Hooks are configured in .claude/settings.json, and which hook fires is
decided by the tool matcher -- they are not one program:
Bashis matched by a family of dedicated guards underscripts/hooks/(destructive_command_guard.py,git_discard_guard.py,protected_paths_guard.py,git_push_guard.py,worktree_cwd_guard.py, and others) plus a small inline matcher.WebFetch/WebSearchis matched byscripts/hooks/web_tools_gate.py.Write/Editis matched byscripts/behavioral_linter.py, which lints file content againstconfig/behavioral_rules/*.yaml. It never sees a shell command or a URL.
They are the inner guardrail -- the last line of defense when autonomy permissions have already been granted.
Not every safety mechanism is a hook, and the distinction is a real difference
in guarantee. Process-group kill validation, for example, lives in
genesis.util.proc_kill and hardens Genesis's own subprocess management at
runtime; no hook inspects agent-authored code for it. A hook cannot be bypassed
by the agent; a runtime helper only protects the call sites that use it.
Genesis uses an environment-file approach for secrets:
- All API keys and tokens live in
secrets.envat the project root. - This file is gitignored and should be set to mode
0600(owner read/write only). - The
genesis.envmodule (src/genesis/env.py) resolves the secrets path at runtime, with support forSECRETS_PATHenvironment variable override. - A
detect-secretsscan and a blockinggitleaksscan (.gitleaks.tomlrules: API keys plus install-specific IP/hostname patterns) run in CI on every PR to verify no secrets leak into the public repo. - Scripts read
secrets.envas data (scripts/lib/load_secrets.shor the Python dotenv reader) — neversourceit; a sourced value containing$(...)would execute.
Rules:
- Never commit API keys, tokens, or credentials to version control.
- Never hardcode secrets in source files.
- Rotate keys regularly. Use separate keys for development and production.
Genesis stores episodic memory and knowledge in Qdrant vector collections. A delete guard in the collections module prevents accidental bulk deletion of production data. This was implemented after a real incident where test execution deleted production memory.
The guard:
- Blocks collection-level delete operations unless explicitly overridden.
- Ensures test fixtures use isolated collections that do not collide with production data.
Genesis ingests content it did not author -- fetched pages, mail, documents, third-party messages. The risk is not that this content is stored; it is that stored content is later auto-consumed into privileged state, where it becomes instruction rather than data.
Provenance stamping. Every stored memory and observation carries an
origin_class -- owner, first_party, or an external/untrusted class.
Two privileged-write paths are gated on it, fail-closed on NULL or an
unrecognised value (genesis.security.immunity.is_trusted_for_privileged_write):
- the user model, which will only fold in deltas from trusted origins
(
memory/user_model.py), and - the autonomy dispatcher, which will not pick up a
task_detectedobservation from an untrusted origin (autonomy/dispatcher.py).
Scope this claim precisely: those two paths are gated, not "all untrusted content is isolated." External-origin content can still reach a model's context through ordinary recall and summarisation surfaces. What the gate prevents is untrusted content auto-promoting itself into the user model or into an autonomous dispatch without an operator in the loop.
Irreversible memory operations require approval. Entity merges delete
mentions and links and cannot be un-merged, so they are no longer applied on
staleness alone: the applier consumes only approved proposals
(memory/entity_adjudication.py), and a pre-delete snapshot is journaled so an
applied merge can be reconstructed.
Session identifiers are validated before use as path components. Session ids
arrive from outside the process and several hooks interpolate them into
filesystem paths. A shared validator (is_safe_session_id in
scripts/hooks/hook_input.py) rejects traversal shapes, separators, null bytes,
the empty string, and over-long values. It exists because hooks had each
hand-rolled their own check and disagreed about what was unsafe; adoption is
widespread but not yet complete, so a hook that has not moved over still carries
a narrower check of its own.
Delivery to the operator -- Telegram, voice, mail addressed to you -- is never gated. You are the recipient; gating it would only obstruct you.
Autonomous egress to the outside world is a different matter, and the honest current state is mixed:
- Enforcing: mail sending passes a real gate that can hold a send
(
autonomy/email_gate.py). - Observe-only: the capability gate at the Discord and GitHub-issue doors
(
autonomy/shadow_gate.py) records what it would decide and does not hold anything. Its own contract says so. Treat it as instrumentation ahead of an enforcement stage, not as a control that is protecting you today. - Build-time backstop:
scripts/check_external_io.pyruns in CI and fails the build when a new hardcoded external endpoint appears outside an allowlist. It reasons about literal endpoints in source, so it cannot see egress routed through a browser session or a third-party integration layer.
If you are deciding whether to trust this system with an outbound channel, the first two bullets are the ones that matter: one channel enforces, the rest are watched.
Python dependencies are declared in pyproject.toml (the sole dependency
source — there is no requirements.txt). Known-CVE scanning runs automatically:
- CI — the
dependency-auditjob in.github/workflows/ci.ymlrunspip-auditagainst the resolved runtime tree on every PR/push and weekly, failing on any new (untriaged) advisory. Already-triaged, not-reachable advisories are listed with rationale in that job. - Dependabot — GitHub's dependency-graph security alerts are enabled for
the repo, and
.github/dependabot.ymlkeeps the GitHub Actions current.
To scan locally:
pip install pip-audit && pip-auditIf you suspect a security issue:
- Revoke any compromised API keys immediately.
- Review logs for unauthorized actions or unexpected tool calls.
- Check for unexpected file modifications in the Genesis directory.
- Rotate all credentials.
- Report the incident to project maintainers.
See LICENSE file for details.