Skip to content

Release 0.4.x: file-write enforcement + hardening (security) - #40

Merged
FvdHMBAI merged 13 commits into
mainfrom
develop
Aug 16, 2026
Merged

Release 0.4.x: file-write enforcement + hardening (security)#40
FvdHMBAI merged 13 commits into
mainfrom
develop

Conversation

@FvdHMBAI

Copy link
Copy Markdown
Owner

Syncs develop into main. Published to npm across 0.4.0-0.4.3.

What changed

GuardRail enforced on Bash only; file writes via Write/Edit/MultiEdit/NotebookEdit
bypassed all deny-capable guards. This adds a deny-capable PreToolUse pre-edit
dispatcher and closes every bypass a 10-round adversarial cross-review surfaced.

Coverage now

  • pre-edit dispatcher (Write/Edit/MultiEdit/NotebookEdit), deny-capable
  • edit_path_guard: guardrail install tree, guards/custom, configured
    GUARDRAIL_CUSTOM_GUARDS_DIR, .claude/settings(.local).json, privileged
    paths, git hooks / shell rc
  • edit_secret_guard: live secrets in content, incl. non-string payloads
  • Path canonicalization (realpath -m -s) closes ./ // .. bypasses
  • Fail-closed: missing guard fn, malformed payload, jq errors
  • credential_leak_guard: hyphenated Anthropic keys
  • self_correction_loop: standalone TS errors

Verification

pre-edit 29/29, adversarial 49/49, regression 95/95, new-guards 43/43,
installer 3/3, typecheck clean. 10 cross-review rounds, final round clean.

🤖 Generated with Claude Code

Frederik and others added 13 commits August 16, 2026 09:08
Deny-capable guards previously ran only on PreToolUse(Bash). File writes
through Write/Edit/MultiEdit were seen only by an advisory PostToolUse hook,
after the write. An agent could create the disable file, overwrite a
privileged path, write to auto-executed files (git hooks, shell rc), or write
live secrets to disk without any blocking guard inspecting it.

New PreToolUse pre-edit dispatcher (Write|Edit|MultiEdit, deny-capable) plus
edit_path_guard and edit_secret_guard. install.sh registers the hook.
SECURITY.md documents the coverage. 17-case pre-edit suite (secret fixtures
assembled at runtime, no literals); adversarial (49) and regression (95) pass.

Reported by a reader (ANP2) on our Dev.to article.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ufhc1yA1eXGSjUKE5QGSdF
Cross-review of 0.4.0 found a hole: edit_path_guard protected guards/core
and guards/pro, but the pre-edit dispatcher also auto-sources custom guards.
An agent could Write a custom guard (uncovered), which would be sourced and
executed on the next tool call, bypassing the whole system. Now covers
guards/custom, dispatchers, and lib. Three regression tests added.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ufhc1yA1eXGSjUKE5QGSdF
…s (v0.4.2)

Two gaps found by the full test sweep:
- credential_leak_guard: sk-ant-[A-Za-z0-9]{20,} stopped at the first hyphen,
  so real keys (sk-ant-api03-...) were NOT detected. Now allows [-_] in the body.
- self_correction_loop: missed standalone "error TS####" output that has no
  literal "tsc" nearby. Added error TS[0-9]{3,} to the build-error pattern.

new-guards suite now 43/43 (was 41/2). adversarial 49, regression 95,
pre-edit 20 all green.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ufhc1yA1eXGSjUKE5QGSdF
Cross-review found the highest-value bypass still open: an agent could Write
~/.claude/settings.json (the hook registry install.sh writes) and remove the
PreToolUse entries, disabling pre-bash AND pre-edit (including this guard)
without touching any 'guardrail' path. edit_path_guard now blocks file-tool
writes to .claude/settings(.local).json. Two regression tests added.

pre-edit 22/22, adversarial 49, regression 95, new-guards 43 all green.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ufhc1yA1eXGSjUKE5QGSdF
Cross-review follow-up: _guardrail_run silently skipped a missing guard
function, so a future change that dropped hook_edit_path_guard would fail
open to allow. Now mirrors pre-bash: a missing or failing required guard
denies with an integrity error. Part of v0.4.3.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ufhc1yA1eXGSjUKE5QGSdF
Cross-review mediums:
- edit_path_guard logged "warned" then denied the git-hook/startup case;
  now logs "blocked" to keep the audit trail truthful.
- NotebookEdit is a file-write tool but was outside the matcher, while
  SECURITY.md claimed full file-write coverage. Added NotebookEdit to the
  install matcher; dispatcher now reads notebook_path + new_source. 3
  NotebookEdit regression tests (secret, settings.json, benign).

pre-edit 25/25, adversarial 49, regression 95, new-guards 43 all green.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ufhc1yA1eXGSjUKE5QGSdF
Cross-review: the dispatcher auto-sources preedit_*.sh from
$GUARDRAIL_CUSTOM_GUARDS_DIR, but the guard only matched the hardcoded
guards/custom path. A write into an operator-configured custom dir was the
same bypass. Guard now also blocks writes under the configured dir when set.
Every auto-sourced path is now covered.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ufhc1yA1eXGSjUKE5QGSdF
Load guardrail-common early so the payload-validation deny is logged, not
just blocked. Malformed-input probing is now visible in the audit trail.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ufhc1yA1eXGSjUKE5QGSdF
Cross-review KRITISCH: path checks matched $FILE_PATH literally, so
/a/./settings.json, /a//settings.json and /a/../a/settings.json wrote to a
protected file but slipped past the regex. Guard now canonicalizes with
realpath -m -s (normalizes ./ ../ // without resolving symlinks) before
matching. Three normalization-bypass regression tests added.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ufhc1yA1eXGSjUKE5QGSdF
…ads (v0.4.3)

Cross-review HOCH: a non-string content/new_string/new_source made jq error,
CONTENT went empty, and edit_secret_guard returned early, passing secrets
unscanned. Extraction now coerces non-string values to JSON (so embedded
secrets are still scanned) and denies if jq itself errors. Regression test
with an object-typed content carrying a key added.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ufhc1yA1eXGSjUKE5QGSdF
@FvdHMBAI
FvdHMBAI merged commit 77cf69b into main Aug 16, 2026
4 checks passed
@FvdHMBAI
FvdHMBAI deleted the develop branch August 16, 2026 08:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant