Mirror of the public Nokido wiki (source of truth: https://github.com/Nokido-labs/nokido/wiki/07-Security-Model). Edits belong there.
Updated: 2026-09-29
Nokido operates on a Zero-Trust local architecture. This page summarizes
the threat model and defensive layers. The canonical disclosure policy is in
SECURITY.md (github.com).
Nokido defends against :
ask("groq", ...), theSovereignMembrane replaces them with HMAC aliases before any cloud call.SemanticFirewall.pre_flight()post_flight() scans fortools/bash_guard.py blocksgit remote -v, cat .env, printenv, gci env:, etc. unconditionally.SkillGuardian reviews any new skillforge_clawhub_autoinstall.Authorization against_AGENT_TOKENS. Fail-closed.forge_crawl_tool.firewall_web + the :7779 egress gateway) scans fetcheddetect_injection, wraps it as untrusted data, and downweightsredact_for_log, deterministic HMACmcp_audit.log / execution_traces.db / RAG dump reveals noNokido does not defend against :
app/forge_semantic_firewall.py
fw = get_firewall()
pf = fw.pre_flight(prompt, context=system_prompt, ring=2, provider="auto")
if not pf.ok: raise VetoSecurity(pf.reason)
# safe_task + safe_context have PII replaced
response = await llm_call(pf.safe_task, pf.safe_context)
pfr = fw.post_flight(response, task=prompt, session_id=session_id)
if not pfr.ok: return handle_drift(response, pfr.reason)
clean = fw.restore(response, pf.mapping)
return clean
Two checks per call : before (anonymize) and after (sniff for drift).
app/forge_sovereign_membrane.py
HMAC alias-based anonymization with persistent aliasing per mission :
membrane = SovereignMembrane(mission_id="recon_20260424")
wrapped = membrane.wrap(raw_data)
# wrapped.content has hostnames -> [host:xxx], paths -> [path:yyy], etc.
response = await cloud_call(wrapped.content)
clean = membrane.unwrap(response, tool_calls=response.get("tool_calls"))
Aliases survive in membrane.db (SQLite WAL) so the same hostname maps to
the same alias across requests.
app/forge_integrity.py::IntegrityRing
Lower ring = more rights. Values of the IntegrityRing enum (compare with
is_at_least(), never ==) :
| Ring | Name | Meaning |
|---|---|---|
| -1 | MASTER | Absolute supervisor, destructive actions ; time-locked (5 min), never handed to external agents |
| 0 | SYSTEM | Nokido core, DB migrations |
| 1 | DEV | Maintenance agents via MCP (token required) |
| 2 | TRUSTED | Workflows (TUI @audit, @ci, @workflow) |
| 3 | COLLAB | LLM committee, external agents |
| 4 | UNTRUSTED | Raw inputs, agents without a token |
Enforced at hub middleware (_resolve_ring() in nokido_hub.py) : the identity Γ ring
of each caller comes from ONE source, forge_videur (agent registry), or from a
short-lived CapabilityToken ; an unknown identity falls to an isolated ring, never 0.
Every tool has a min_ring ; the registry filters per-agent (see the table in
04 β Auth model).
app/forge_machine_vault.py + app/forge_secrets.py
Encrypted-at-rest, machine-wide on Windows (multi-user sandbox accounts
share access), per-user on macOS/Linux. Never .env committed. Since the vault
hardening of 2026-09-28 (2b), the reserved names (master token, JWT secrets...)
live in a SYSTEM-only reserved vault and are closed to every other account.
Chain : get_secret() β reserved vault (reserved names) β machine vault β WCM β
Nokido.env β os.environ (last resort with warning).
Two layers :
.gitleaks.toml β regex over default rules + Nokidoforge_secret_guard) β runs in the pre-commitinstall.sh / install.ps1.Both fail-closed : a suspected leak blocks the commit.
tools/bash_guard.py
PreToolUse hook for Bash/PowerShell. Two layers :
git remote -v, cat .env, printenv, gci env:, URLs with userinfo,*TOKEN / *SECRET / *KEY).:8766, nssm restart Nokido*, git, gh, schtasks Nokido-*).Dev mode (sandbox/.bash_guard_armed, TTL 30 min) bypasses layer 2 only β
secrets layer always applies.
The other client-side guards. bash_guard is one of many hooks (43 wirings checked
at session start on 2026-09-29) ; the main ones are below. They matter
because agent CLIs are often run with permission prompts disabled: the hooks are
then the only net, and a hook whose script is missing fails silently.
| Hook | Event | Role |
|---|---|---|
forge_tool_gate.py |
PreToolUse | routes reads/searches to the governed hub instead of raw native tools |
hook_search_guard.py |
PreToolUse | denies unbounded reads (>14 kB without limit, head_limit:0) |
hook_recon_first.py |
PreToolUse | forces a memory lookup before instrumenting a domain β once per domain per day |
hook_pretool_guard.py |
PreToolUse | anti-regression: asks for confirmation when an edit reintroduces an anchored bug |
hook_posttool_validate.py |
PostToolUse | validates the AST of every edited .py, at zero token cost |
hook_integrity_check.py |
SessionStart | verifies the net itself: each wired hook must exist and compile, three states (ok / dead / unreadable) |
hook_bash_compact.py |
PreToolUse | pipes verbose command output through a compactor before it enters context |
hook_capability_gate.py |
PreToolUse | recalls the form that WORKS for a capability (measured traps), before the wrong form is paid again |
hook_integrity_check.py exists because of a specific failure mode: a deleted or
broken hook is ignored without a word, so the protection disappears while the
configuration still reads as protected.
tools/forge_git_gate.pyRuns on every commit. Blocks on positive proof only, never on an inability to
look β "I could not check" is reported, never silently treated as "clean".
.py β AST parse of staged files. A broken .py fail-closes the hub..ts β deno lint (parse) and deno check (types) on proxy_deno/.deno check surfaced 18 errors the linter reported as clean.except: pass blocks that swallow a failureNote: deno must be readable by the account that commits. When it only exists
in the owner profile, service accounts cannot see it and the .ts gate skips β
announced, never a false green. A machine-wide copy closes that gap.
Code execution via run (shell, python, run_job) runs under dedicated
low-privilege Windows accounts, not the owner's :
LaForgeSbxOffline β default ; outbound network blocked by firewall.LaForgeSbxOnline β only when network is asked (network=true, online=true jobs).LaForgeTrusted β trusted_script, which only runs a committed scriptps_clm) β only while the owner has armed the dev mode (TTL), inDocker stays available as an on-demand prosthesis (docker_action, woken by
docker.wanted, switched off when nobody asks) β it is not the default sandbox.
The hub binds 127.0.0.1. Cloud egress is opt-in per provider (vault
key must be present + provider explicitly chosen). No telemetry. No
remote attestation. No "phone home" pings. The only public entry point, when the owner
opens it, is the separate cloud-peer connector (layer 11) β never the hub's /mcp.
For multi-host Nokido deployments, wrap with Tailscale or WireGuard.
Do not bind 0.0.0.0 directly.
app/forge_semantic_firewall.py::redact_for_log + 5 sinks
The Semantic Firewall covered cloud egress only β local logs persisted raw.
Now every log/trace sink (net_log, record_trace, anchor_error/solution,
critical_events.persist) scrubs PII/secrets before write :
john@acme.com β [EMAIL:7f3a]) β same value βmcp_audit.log / execution_traces.db / RAG reveals no cleartext.net_log stores the value encrypted in a DPAPI vaultdeanonymize_log() <7 days for authorized local debug), pruned byforge_log_retention thereafter β irreversible at rest.app/forge_crawl_tool.py + tools/forge_web_egress.py (:7779)
Web content is the classic indirect-injection vector. Three levels :
crawl tool cleans (trafilatura β markdown), scans:7779/fetch exposes the same pipeline to non-MCP host CLIsSee Web Egress Gateway.
tools/forge_pair_mcp.py + tools/forge_pair_quarantaine.py (:8793, HTTPS tunnel)
claude.ai / ChatGPT are peers, not operators : fixed read-only view, and every
deposit (fact, message, task) waits in quarantine. Approval and replies require the
owner account with an elevated token (UAC) β SYSTEM, hub accounts and the
non-elevated console where agents run are refused. Peers may only carry a whitelist
of intents (never OK_DONE, LOCK_*, AUTHZ_*β¦). See 24 β Cloud peers.
Private disclosure first :
Security tab β Report a vulnerability (GitHub private vulnerability reporting).
See SECURITY.md (github.com) for the full policy. We aim for
72-hour acknowledgement and 7-day initial assessment.
SemanticFirewall reduces but does not eliminate prompt injection risk.query tool is raw SQL, ring 0 only. Other rings must use rag.LaForgeSbxOffline, which can read the non-reservednokido-vault list..gitleaks.toml runs in pre-commit hook : check .githooks/.Nokido.env (they should be in vault).127.0.0.1:8766 not 0.0.0.0:8766 (verify ports:).LaForgeSbxOnline, LaForgeSbxOffline..github/workflows/gitleaks.yml.
Wiki: 08-Vault-and-Secrets
Wiki: 10-Docker-Profiles
Wiki: 15-Glossary