Skip to content

ChaosNexus Anvil Security Model ​

Single source of truth for trust boundaries, threat classes, and the capability taxonomy enforced by the engine.

Trust boundaries ​

ActorTrust levelNotes
Human operator via ChaosNexus ForgeTrustedCan approve pending plugins and grant capabilities
Bundled plugins in scripts/plugins/Semi-trustedLoaded only after human install or Forge scaffold
LLM-authored plugins (ChaosNexus Anvil_create_plugin)UntrustedWritten to scripts/.pending/; never loaded until human approval
Untrusted data (HTTP bodies, file contents, downstream MCP output)UntrustedMay steer the LLM (LITL); must not become executing code without HITL

Threat classes ​

LLM-in-the-loop (LITL) ​

An LLM reads untrusted data, generates a Rhai plugin, and the engine executes it with host privileges. Mitigations:

  • Fail-closed quarantine: ChaosNexus Anvil_create_plugin writes to .pending/, never loads
  • ChaosNexus Forge HITL approval before promotion to plugins/
  • Default-deny capability allowlist on every promoted plugin

Human-in-the-loop (HITL) ​

Dangerous actions execute without meaningful human review. Mitigations:

  • Pending approval UI shows full source + requested capabilities
  • Human selects granted capability subset on approve
  • No auto_reload on LLM create path

Topology ​

Two ChaosNexus Anvil processes may share one scripts/ directory:

  1. MCP server (stdio) - LLM-facing, includes ChaosNexus Anvil_create_plugin
  2. Supervised engine (--supervised) - ChaosNexus Forge child process

Approval bridge: on-disk .pending/ + ChaosNexus Forge promote + reload on both sides.

Capability taxonomy (default-deny) ​

All plugins start with no capabilities. Host config [plugins.<name>].granted_capabilities must authorize the plugin; effective caps are the intersection of that host grant and plugin.toml [capabilities]. Forge approval writes the host grant.

CapabilityGated natives
shellrun_command
process_spawnmcp_connect
net_httphttp_get, http_post, ntp_request
net_tcptcp_request
net_wsws_connect
host_readload_config_string
envget_env (hard error when denied) and CONFIG.secrets (per-var allowlist in manifest)
db_externalnon-sqlite:// db_connect
installsys_install_plugin (routes to staging only)
fs_cross_pluginfs_* when plugin_name != caller
kv_dumpkv_dump
shared_globalunprefixed set_global / get_global keys

SQLite db_connect remains confined to the caller's plugin directory without db_external.

Scoped secret/config delivery (CONFIG) ​

Every plugin callback runs with an immutable, identity-scoped CONFIG constant injected into its Rhai scope (no import needed), built per-invocation under CURRENT_PLUGIN:

  • CONFIG.plugin - the calling plugin's own name
  • CONFIG.cvars.<name> - only cvars owned by (scoped to) this plugin
  • CONFIG.secrets.<NAME> - host env vars the plugin was granted via env + env_allowlist

This is the preferred path for tokens/keys: secrets reach the script through CONFIG instead of ambient host state. A plugin can never observe another plugin's CONFIG, and the constant cannot be reassigned. get_env remains available (same env + allowlist gate) but is discouraged in favor of CONFIG.secrets. When a plugin calls get_env for a key outside its env_allowlist, the engine raises a Rhai error (fail-closed) instead of returning an empty string. Built in chaosnexus-anvil/src/scripting/config_inject.rs.

Caller identity ​

CURRENT_PLUGIN is set by the engine before every plugin callback. Natives that accept plugin_name verify it matches CURRENT_PLUGIN unless fs_cross_plugin is granted.

Reserved events ​

Only the engine may fire: on_cvar_changed. Scripts cannot spoof lifecycle hooks.

Engine limits ​

  • eval disabled
  • Bounded call depth, string/array/map sizes
  • Module imports rooted at plugins/ and lib/ (no bare CWD/FileModuleResolver::new())
  • Event/native recursion budget (same order as MCP hop limit)

OS defense in depth ​

See operations/os_sandbox.md. In-process Rhai cannot contain shell or process_spawn; run the engine as an unprivileged user with optional Landlock/seccomp.

Future hardening phase (backlog) ​

The following items are documented for a follow-up security phase; they are not yet enforced:

AreaCurrent behaviorTarget
run_commandargv run_command(exec, args) by default; legacy shell -c gated by CHAOSNEXUS_ANVIL_ALLOW_SHELL_CKeep argv-only in plugins; avoid granting interpreter bins with -c
http_get / http_postHost allowlist + no redirects; private/loopback blocked unless net-star opt-inPin resolved IPs / harden DNS-rebinding further
Capability promotion[capabilities] appended only when absent at approve time; later manual plugin.toml edits reload without re-approvalRe-approval or signed manifest when capabilities change post-install