Added
- Codex and Codex companion taxonomy — added agent action types plus Phase 2 classification for Codex CLI and Codex companion commands, including read-only metadata, write/state changes, local/remote agent execution, server startup, and bypass-flag escalation (mold-15)
- Threat-model coverage audit — added
nah audit-threat-modelCLI subcommand backed bysrc/nah/audit_threat_model.py, with module-level rule tests,TestContainerDestructiveCoverage, andTestPackageEscalationCoverageso threat-model claims can be mapped back to concrete pytest coverage and the container/package escalation gaps are exercised explicitly. Output formats:markdown(default),json,summary(mold-8) - Playwright MCP browser taxonomy expansion — added 6 new action types:
browser_read,browser_interact,browser_state,browser_navigate,browser_exec, andbrowser_file. Bundled classification now covers bothmcp__plugin_playwright_playwright__browser_*andmcp__playwright__browser_*tool names, eliminating prompts for the 58 read/interact/state tools while keeping navigate/exec/file tools on explicit ask paths with browser-specific reasons (mold-10) - Container + systemd taxonomy expansion — added 6 new action types:
container_read,container_write,container_exec,service_read,service_write, andservice_destructive. Full-profile docker/podman coverage now includes logs/inspect/stats/build/exec/compose/service flows,systemctl/journalctlno longer fall through tounknown, minimal profile gains read-only container/service coverage, and sensitive path defaults now cover Docker daemon and systemd config/socket paths (mold-2) - Unified LLM mode — merged 4 fragmented LLM entry points into 2 clean paths. Path 1 (ask refinement): combined safety+intent prompt runs in
main()for ask decisions, uses user-only transcript and CLAUDE.md for context, can only relax ask→allow. Path 2 (content veto): stays in handlers for write/script inspection, hard-capped to ask. Config simplified tollm.mode: off|on(one switch). LLM can never block — only allow or ask. Session state tracks consecutive denials (3→disable).nah log --llmfilter,nah testuses unified path. Backward compat:llm.enabled: truestill works. Deprecation warning for removedllm.max_decision(nah-5no) - Inline code inspection —
python3 -c 'print(1)',node -e,ruby -e,perl -e,php -rinline code is now content-scanned instead of blindly prompting. Safe inline → allow, dangerous patterns → ask/block. LLM veto gate fires on clean inline code (same defense-in-depth as script files). LLM prompt now includes inline code for enrichment (nah-koi.1) - Shell init file protection —
~/.bashrc,~/.zshrc,~/.bash_profile,~/.zshenv,~/.bash_aliases, and 8 more shell init files now guarded as sensitive paths (askpolicy). Prevents silent alias injection persistence. Includes.bashrc.d/and.zshrc.d/directories (nah-wdd) - Safety list hardening — expanded coverage for credential directories (
~/.kube,~/.docker,~/.config/az,~/.config/heroku), sensitive basenames (.pgpass,.boto,terraform.tfvars), exec sinks (lua,R,Rscript,make,julia,swift), and decode-to-exec pipe detection (gzip -d,zcat,bzip2 -d,openssl enc,unzip -p, and more) (nah-brq)
Removed
- Beads taxonomy — removed
beads_safe,beads_write, andbeads_destructiveaction types plus allbdclassify entries andbd dolt start/stop/killallprocess_signal entries. The beads CLI (bd) is superseded bymolds; users who classified molds commands under beads types should reclassify under generic types (filesystem_read,filesystem_write,filesystem_delete).
Changed
- Public docs readiness — refreshed README and site docs for the current guarded tool surface, LLM configuration/mechanics, database target behavior, safety-list defaults, profile counts, and
nah test --toolsupport. - LLM reasoning observability — LLM responses now carry both a short prompt-safe
reasoningsummary and a longerreasoning_longexplanation for logs andnah test, while Claude-visible prompts continue to use the compact summary. - Write/Edit LLM review mechanics — Write/Edit, MultiEdit, and NotebookEdit LLM handling can now relax eligible project-boundary asks to allow when the edit is narrow, safe, and clearly intended, while still escalating risky deterministic allows to ask and keeping sensitive/config/content-pattern asks human-gated (nah-858)
- LLM eligibility presets —
llm.eligible: strictpreserves the old conservative default,defaultnow includesunknown,lang_exec, non-sensitivecontext,package_uninstall,container_exec, andbrowser_exec, andallremains the opt-in route for every ask decision. Classified fallback/MCP tools now include stage metadata so taxonomy eligibility applies consistently (nah-856) - GitHub Actions now publishes a non-gating threat-model coverage report to the job summary after the main pytest run, so PRs show per-category audit counts without changing the enforcement gate (
pytest tests/) (mold-8) - Docker and podman read-only inspection commands like
ps,images,logs,inspect, and compose read ops now classify ascontainer_readinstead offilesystem_read. Default behavior staysallow; logs andnah typesnow use the container-specific action type. - Transcript-derived LLM context now reformats slash-command skill invocations, labels Claude Code skill meta blocks as
Skill expansion, deduplicates repeated expansions by skill name, and caps each captured skill body to 2048 chars (mold-3)
Fixed
- Codex companion script variables — same-command discovery patterns like
CODEX_SCRIPT=$(ls ~/.claude/plugins/cache/openai-codex/codex/*/scripts/codex-companion.mjs | head -1) && node "$CODEX_SCRIPT" ...now classify as Codex companion delegation instead of generic missing-scriptlang_execasks (nah-859) - Benign
export NAME=valueassignments —export PATH=/opt/bin:$PATHand similar assignment-only shell stages now classify as benign environment setup instead ofunknown, while exec-sink values, substitutions, redirects, and non-assignment export forms still take the stricter existing paths (nah-862) - Shell
sourceclassification —source <file>and POSIX. <file>now classify aslang_execand use the existing script path/content inspection path instead of falling through tounknown(nah-860) - Subshell group parsing — parenthesized command groups such as
cmd || (brew list ...; ls ...) 2>&1now classify by their inner commands, preserve group redirects, fail closed for grouped pipes, and no longer suggest invalidnah classify (cmd <type>hints (nah-861) - Sudo wrapper classification —
sudo-wrapped Bash commands now unwrap to the inner action type with asudo:reason prefix, preserving targeted hints, redirect/content inspection,trust_projectpassthrough behavior, composition rules, and fail-closed parsing for unsupported or malformed sudo options (mold-12) - Heredoc apostrophes inside
$()no longer false-block as "unbalanced substitution" —_match_parensand_extract_substitutionsnow recognize<<EOFheredoc operators (and<<-EOF,<<'EOF',<<"EOF"variants) and skip past their bodies as opaque literal content. A new_strip_heredoc_bodieshelper removes heredoc bodies beforeshlex.splitso the inner stage is shlex-friendly even when the body contains unbalanced apostrophes, backticks, or parens. This unblocks the Claude Code git-commit patterngit commit -m "$(cat <<EOF\n…can't…\nEOF\n)"which was previously hard-blocked any time the commit body contained a contraction (mold-9) - lang_exec veto silently ignored — when the LLM flagged a script as dangerous,
max_decisioncap converted block→ask, then the veto check (== block) failed, silently allowing the script. Now escalates to ask unconditionally when the LLM flags concern (nah-5no) - LLM decision always empty in logs —
_build_llm_meta()never set thellm_decisionfield, so every log entry had"decision": ""in the llm block. Now populated from the actual LLM response (nah-5no) - SSH-style host extraction now covers
rsyncandssh-copy-id—rsync user@host:pathandrsync host::modulenow resolve the remote host correctly for network context, andssh-copy-idis classified asnetwork_outboundwith SSH host extraction instead of falling through tounknownor malformed URL parsing (nah-vcz) - Heredoc input classification —
python3 << 'EOF' ... EOFno longer produces "script not found" errors. Heredoc-fed interpreters are now classified aslang_execwith content scanning viaheredoc_literal. Semicolons, pipes, and&&inside heredoc bodies no longer cause false stage splits. Works for all interpreters: python3, node, ruby, perl, php (nah-dhs) - Shell comment parsing —
#comment lines with apostrophes (e.g.# Check if there's a fix) no longer cause shlex parse errors. Layer 1 skips quote tracking inside comments in_split_on_operators; Layer 2 retriesshlex.splitwithcomments=TrueonValueError. Pure-comment commands correctly classify as empty/allow (nah-2zt) - LLM cascade failure no longer overrides deterministic allow — when all LLM providers fail (missing API keys, network errors), Write/Edit now returns the deterministic decision instead of escalating to
ask. Previously every edit prompted for confirmation even when content was safe and path was trusted. Cascade metadata preserved in logs for debugging (nah-yt9) - LLM observability for write-like tools — LLM metadata (provider, model, latency, reasoning) now always logged for Write/Edit/NotebookEdit/MultiEdit, even when LLM agrees with the deterministic decision or all providers fail. Missing API keys now logged to stderr (
nah: LLM: OPENROUTER_API_KEY not set) and to the structured log withprovider: (none)and cascade errors. Previously missing keys caused silent 34ms "uncertain — human review needed" with no trace of why - String-content transcript messages are no longer dropped, so slash-command invocations and other non-list transcript entries now reach the LLM context formatter (mold-3)
- LLM transcript tail reads no longer lose all context on giant JSONL lines —
_read_transcript_tail()now walks backward from EOF in newline-aligned chunks with a safety cap, so largetool_resultlines no longer consume the entire read window and produce(not available)conversation context in LLM prompts (mold-27) - Inspectable wrapper execution no longer slips through
package_run—uv run,uvx/uv tool run,npx, andnpm execnow re-route inspectable local code execution intolang_exec, whilemake/gmakeexecution paths also route tolang_execvia Makefile resolution. Read-only make forms remainfilesystem_read, and ordinary package-run fallthroughs stay unchanged (nah-vhy) - Env-only shell stages no longer default to
unknown -> ask— stages made entirely ofNAME=valueassignments now classify from an allow floor unless an env value is itself an exec sink or a substitution inner is stricter, so benign cases likeTOKEN=abc123andFOO=$(printf ok)no longer prompt spuriously (mold-17) npm createno longer falls through tounknown -> ask—npm create ...is now classified aspackage_run, matching the existingpnpm create,yarn create, andbun createscaffolding behavior so common forms likenpm create vite@latestno longer prompt unnecessarily (mold-4)