Menu β–Ύ β–΄

07-Security-Model

mirror (20)
Nokido

Mirror of the public Nokido wiki (source of truth: https://github.com/Nokido-labs/nokido/wiki/07-Security-Model). Edits belong there.

07 β€” Security model

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).

🎯 Threat model

Nokido defends against :

  1. Cloud provider data exfiltration β€” when you ask("groq", ...), the
    prompt may contain hostnames, tokens, paths, IPs. The
    SovereignMembrane replaces them with HMAC aliases before any cloud call.
  2. Prompt injection attacking the LLM β€” SemanticFirewall.pre_flight()
    detects 15+ patterns (EN + FR) including jailbreak attempts, role
    injection, instruction override.
  3. SSRF / beacon leak in LLM output β€” post_flight() scans for
    suspicious URLs, ICP token leaks, callback patterns.
  4. Local secret leak via shell tools β€” tools/bash_guard.py blocks
    git remote -v, cat .env, printenv, gci env:, etc. unconditionally.
  5. Untrusted skills auto-install β€” SkillGuardian reviews any new skill
    before it can be installed by forge_clawhub_autoinstall.
  6. Hub auth bypass β€” every MCP request validates Authorization against
    the vault-loaded _AGENT_TOKENS. Fail-closed.
  7. Indirect prompt injection via web content β€” a crawled/ingested page can
    hide instructions ("ignore previous…"). The web pipeline
    (forge_crawl_tool.firewall_web + the :7779 egress gateway) scans fetched
    content with detect_injection, wraps it as untrusted data, and downweights
    it in the RAG. SSRF-guarded against internal / cloud-metadata targets.
  8. PII / secrets leaking via local logs at rest β€” every log/trace sink
    scrubs sensitive data before write (redact_for_log, deterministic HMAC
    tags). A leaked mcp_audit.log / execution_traces.db / RAG dump reveals no
    cleartext. Reversible for authorized debug <7 days via a DPAPI vault.

Nokido does not defend against :

  • A compromised local user account (we assume the local filesystem and
    process tree are trusted).
  • A physical attacker with hardware access.
  • Vulnerabilities in upstream LLM providers (Anthropic, OpenAI, Google…).

πŸ›‘οΈ The 11 defensive layers

1. Semantic Firewall

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).

2. Sovereign Membrane

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.

3. 6-ring RBAC

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).

4. Vault β€” DPAPI / Keychain / libsecret

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).

See 08 β€” Vault & secrets.

5. Pre-commit secret scan

Two layers :

  • Gitleaks via .gitleaks.toml β€” regex over default rules + Nokido
    allowlist (sample.env, templates, docs).
  • Nokido AST guard (forge_secret_guard) β€” runs in the pre-commit
    hook installed by install.sh / install.ps1.

Both fail-closed : a suspected leak blocks the commit.

6. Bash guard hook

tools/bash_guard.py

PreToolUse hook for Bash/PowerShell. Two layers :

  • Layer 1 (unconditional) β€” denylist of secret-leaking patterns
    (git remote -v, cat .env, printenv, gci env:, URLs with userinfo,
    echo of *TOKEN / *SECRET / *KEY).
  • Layer 2 (routing) β€” every command segment must be a passthrough
    (curl :8766, nssm restart Nokido*, git, gh, schtasks Nokido-*).
    Otherwise blocked.

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.

6.b Commit gate β€” tools/forge_git_gate.py

Runs 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/.
    Both matter: a file that parses but has a type error still kills the
    supervisor, and every service it runs with it. Type-checking was added on
    2026-07-30 after deno check surfaced 18 errors the linter reported as clean.
  • secrets β€” pre-commit scan (see layer 5) plus an entropy check.
  • muted error paths β€” flags except: pass blocks that swallow a failure
    without a trace.

Note: 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.

7. Execution under dedicated accounts

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 script
    (the executed code is exactly the reviewed code).
  • SYSTEM (ps_clm) β€” only while the owner has armed the dev mode (TTL), in
    PowerShell constrained language.

Docker stays available as an on-demand prosthesis (docker_action, woken by
docker.wanted, switched off when nobody asks) β€” it is not the default sandbox.

8. Local-first by default

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.

9. Log DLP at rest

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 :

  • Deterministic HMAC tags (john@acme.com β†’ [EMAIL:7f3a]) β€” same value β†’
    same tag, so debug correlation survives, but the value is never stored. A
    leaked mcp_audit.log / execution_traces.db / RAG reveals no cleartext.
  • Covers EMAIL / SECRET / SSH key + credit-card (Luhn) / IBAN / SSN / phone /
    title-anchored names β€” pure regex, no heavy ML model.
  • Reversible opt-in : net_log stores the value encrypted in a DPAPI vault
    (deanonymize_log() <7 days for authorized local debug), pruned by
    forge_log_retention thereafter β†’ irreversible at rest.

10. Web egress firewall

app/forge_crawl_tool.py + tools/forge_web_egress.py (:7779)

Web content is the classic indirect-injection vector. Three levels :

  • Cooperative β€” the MCP crawl tool cleans (trafilatura β†’ markdown), scans
    injection, and indexes oversized pages locally on the NPU instead of dumping
    them (token-frugal).
  • Gateway β€” :7779/fetch exposes the same pipeline to non-MCP host CLIs
    (aichat / llm / curl), with an SSRF guard (blocks RFC1918 / loopback /
    cloud metadata).
  • Hard β€” sandboxed agents have outbound sockets blocked, so they reach the
    web only through the firewalled hub.

See Web Egress Gateway.

11. Cloud peers β€” owner quarantine

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.

πŸ” Reporting a vulnerability

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.

⚠️ Known limitations

  • Alpha software β€” APIs change between commits. Pin to a tag for stability.
  • Pre-public branches may contain test credentials. Always rotate hub
    bearers before deploying anywhere visible.
  • SemanticFirewall reduces but does not eliminate prompt injection risk.
  • Ring 0 grants RCE. Never expose master-token over the network without
    TLS + mTLS.
  • The query tool is raw SQL, ring 0 only. Other rings must use rag.
  • The cloud-peer server runs as LaForgeSbxOffline, which can read the non-reserved
    secrets of the machine vault (vault hardening step 2b in progress).

βœ… Hardening checklist (post-install)

  • [ ] Rotated hub bearers from defaults : nokido-vault list.
  • [ ] .gitleaks.toml runs in pre-commit hook : check .githooks/.
  • [ ] No tokens in Nokido.env (they should be in vault).
  • [ ] Docker compose binds 127.0.0.1:8766 not 0.0.0.0:8766 (verify ports:).
  • [ ] Sandbox accounts created (Windows) : LaForgeSbxOnline, LaForgeSbxOffline.
  • [ ] Hub healthcheck enabled (cf. compose) β€” auto-restart on hang.
  • [ ] CI gitleaks workflow passing : .github/workflows/gitleaks.yml.

Related

Wiki: 08-Vault-and-Secrets
Wiki: 10-Docker-Profiles
Wiki: 15-Glossary