Skip to content

Releases: scs0209/laya-guard

v0.4.1

Choose a tag to compare

@scs0209 scs0209 released this 14 Oct 03:58
07ecf34

Install or upgrade: pip install -U laya-guardrail (the PyPI name; the command is laya-guard).

The dashboard gets a Simulation view: the pipeline as a small 3D diorama you can watch, an attack lab that judges eight
scenarios with the real evaluator, and AI scenarios, where you describe any situation and the coding agent you already
have plays it out in a virtual world while laya-guard judges every command it proposes. Nothing in it is ever run.

Added

  • Simulation view in the dashboard (/dashboard#/simulation): the pipeline as an interactive isometric miniature
    built with three.js in the page (terminals, command queue, policy gate with one column per policy, the Laya engine, the
    decision gate, and the execution, review and incident zones). Calls from the verdict log fly through as they arrive
    (RECORDED); the engine is only visited when the model was really called. Real, mock and simulated data carry different
    labels everywhere (REAL, MOCK EVALUATOR, HEURISTIC FALLBACK, SIMULATION DATA, RECORDED).

  • Attack lab: eight predefined scenarios judged by the real evaluator and never executed or logged. Each shows the
    verdict, risk score, policy and reason as returned, which relevant policy checks matched, who decided and the stages
    passed, with a comparison table. GET /api/sim/config and POST /api/sim/evaluate/<scenario> (NDJSON: a line per
    pipeline event, then the result; same token, origin and JSON checks as the installers; only scenarios, never free text).

  • Inspector, replay (play, pause, restart, step, scrub), a guided tour, camera controls (pan, rotate, zoom, buttons and
    keyboard), reduced-motion support, and a text fallback when WebGL isn't available.

  • guard.judge_traced(): judge() that reports each stage (segment, policy, package script, engine) to an observer as it
    happens. Nothing is recorded unless an observer is installed, and the verdict is unchanged.

  • AI scenarios (Simulation, "AI scenario" tab): describe any situation in the window and the coding agent you already
    have (Claude Code, headless, all tools off, under your own login; no API key, hosted service or Ollama) interprets it,
    builds a virtual repository and plays the coding agent step by step. Each action is one of a fixed list, validated, and
    applied to an in-memory world; each proposed command is judged by the real evaluator and the verdict is fed back. Nothing
    is run, logged or alerted. Pause, resume, step, stop, run again, replay, a history of actions and decisions, held commands
    you approve or deny, and an outcome card that keeps the run's facts apart from the AI's account. The 3D scene plays the
    actual event sequence (the agent walks to the repository, a command leaves the terminal and waits for the real verdict).
    Limits: steps, time, cost, files. Consent is asked before first use. Routes: /api/sim/ai/{status,consent,runs}.

  • laya_guard.agentbridge (adapter registry; Claude Code adapter) and laya_guard.aisim (the engine).

  • A package script that was read and matched nothing now says so in the trace (package-script, clean), instead of
    looking like a script that was never read.

Changed

  • The dashboard serves the simulation's modules and a bundled, tree-shaken three.js r186 (MIT) from /static/ inside the
    package (read-only, .js/.css/.txt only). Its CSP now lists 'self' for scripts and styles next to the nonce;
    nothing external is allowed. Rebuild the bundle with scripts/vendor_three.sh.

v0.4.0

Choose a tag to compare

@scs0209 scs0209 released this 13 Oct 10:36
8879e94

Install or upgrade: pip install -U laya-guardrail (the PyPI name; the command is laya-guard).

An outage no longer turns the guard off. With the server down, what it can't judge is asked about or blocked instead
of passing, the server can run as a service that comes back after a crash, and an outage shows up on the dashboard and
can raise an alert.

Added

  • laya-guard service install | uninstall | status: opt-in start at login, a macOS LaunchAgent or a Linux systemd
    user unit that runs laya-guard serve (restarted after a crash, 30 seconds apart at most; serve exits 0 when it is
    told to stop, so a plain kill isn't taken for a crash). The file is shown as a
    diff first (--dry-run, --yes), is readable only by you, and a file that laya-guard didn't write is left alone
    unless --force (it is then backed up). --port, --no-warmup (load the model on the first call: about 90 MB
    idle instead of 1.4 GB) and --bin are options; the LAYA_GUARD_* settings the server reads are copied in. The
    dashboard's Overview has the same switch ("Start at login", GET /api/service, POST /api/service/{install|uninstall}
    with the same token, same-origin and diff-id checks as the agents). A dashboard served by the service itself gets its
    answer before the server is restarted or stopped. Windows isn't supported yet.
  • Degraded indicator. The dashboard turns amber ("Degraded", with a note and a "Judged without the model" card)
    when calls in the last hour were judged by the offline check or after a model error; each call shows who judged it
    ("Judged by") and the Calls search finds them. GET /api/stats gains degraded and, per recent call, via and
    degraded.
  • LAYA_GUARD_ALERT_CMD: a shell command started in the background when calls are judged without the model, once
    per kind of outage (server-down from the hook's offline fallback, model-error from the server) per
    LAYA_GUARD_ALERT_COOLDOWN seconds (default 600). The text comes in LAYA_GUARD_ALERT_KIND and
    LAYA_GUARD_ALERT_MESSAGE; a hook never waits for it or fails because of it.

Changed

  • LAYA_GUARD_FAIL now defaults to ask, not open. With the server down, a call the offline check can't judge
    (neither regex-flagged nor on the safe list) no longer passes. Claude Code, Cursor, Qwen Code, Copilot CLI and Amp's
    permissions helper ask you about it; Gemini CLI, Codex, Windsurf, Cline, OpenCode, Kilo and the Amp plugin have no
    ask lane, so they block it and say so. Hook input an adapter can't read, and a call made when the CLI can't be found
    either, follow the same rule. With LAYA_GUARD_REVIEW=pass nobody is asked, so an offline review is blocked.
    LAYA_GUARD_FAIL=open brings back the old behaviour; closed is unchanged. A model review with the server up is
    unchanged too. Raised in a Reddit thread: an outage shouldn't quietly turn the guard off.
  • laya-guard serve exits with an error right away when something already answers on the port, instead of loading
    the model and then failing to bind.

Fixed

  • The dashboard kept saying "Server unreachable" after one failed refresh until a new call arrived.
  • The Gemini CLI, Qwen Code, Cline and Kilo Code adapters sent the offline check the whole tool call
    (run_shell_command({"command": ...})), which never matches the safe-command list, so with the server down even
    git status counted as unjudged. They now give it the bare shell command (what the model sees is unchanged).

v0.3.0

Choose a tag to compare

@scs0209 scs0209 released this 13 Oct 05:55
011777c

Install or upgrade: pip install -U laya-guardrail (the PyPI name; the command is laya-guard).

A local dashboard, one-command setup for all eleven supported agents, and failure modes that tell an outage from
a verdict.

Added

  • laya-guard dashboard: a local dashboard at http://127.0.0.1:8787/dashboard (starts the server if it isn't
    running; --no-open prints the URL). A small app with a sidebar and four views. Overview: verdict counts for a
    window from 1 hour to all time, a tape with one fine line per judged call (blocks stand tall and red), and cards for
    what needs a look. Calls: the list on the left and the selected call on the right (policy, reason, source, timing,
    length and hash, never the command text) with a verdict filter and search. Policies: which rules decided the calls.
    Agents: see below. Keyboard shortcuts (j/k, a r b 0, 1-5, /, p, ?), light and dark. It is
    served by the existing server (GET /dashboard, GET /api/stats, GET /api/agents), answers only a loopback Host
    (DNS rebinding), is self-contained under a nonce-only CSP and loads nothing from the network. Roadmap: #37.
  • laya-guard install <agent>, uninstall <agent> and status for Claude Code, Codex CLI, Cursor, Gemini CLI, Qwen
    Code, Windsurf, Copilot CLI, Cline, OpenCode, Kilo Code and Amp. Every install prints a diff first, backs up what it
    replaces, writes absolute paths, writes atomically and then sends two test calls through the hook (--dry-run,
    --yes, --scope user|project|local, --force; status --check repeats the test calls, --agent and --all pick
    what to list). uninstall removes only what laya-guard added (marked LAYA_GUARD_MANAGED=1), and a hand-written
    entry is reported and left alone unless --force. Each agent follows its own documented mechanism: JSON hook entries
    merged next to other tools' (Claude Code, Codex, Cursor, Gemini CLI, Qwen Code, Windsurf), a marked block in Codex's
    config.toml for people who keep hooks there, a drop-in file of its own for Copilot CLI, plugin files for OpenCode,
    Kilo Code and Amp, and a wrapper script for Cline. Details that came from reading the agents' docs and real config files:
    Cursor runs every matching hook and takes the strictest answer, and its hooks.json often has trailing commas (read
    leniently, and the diff says they go); Gemini CLI 0.20 ships with hooks off, so tools.enableHooks is turned on and
    said so (and, if the file already holds other tools' hooks, that they start running too); Codex asks you to trust a
    new hook with /hooks; Cline's switch lives in its Hooks tab. What an install can't do for you is printed after it.
    The agents are not launched in the tests: formats come from their docs and from files other tools leave behind, and
    the install output says so for the ones it hasn't seen run.
  • Install from the dashboard. The Agents view finds the coding agents on the machine by itself (an executable on
    PATH, an app bundle or a config directory; read-only, it never opens those files) and shows each as a tile with its
    own logo and a status dot. Install hook, Remove and Repair open a sheet with the exact diff, and nothing is
    written until Apply change. A button pinned in the sidebar installs where the hook is missing. The write endpoint
    (POST /api/agents/{agent}/{action}, user scope only) needs a loopback Host, a same-origin Origin, a JSON body, a
    per-process token only the page carries, and the id of the diff that was shown (409 if a file changed since). The logos
    are the products' own marks, inline: Claude Code, Cursor, Gemini, Qwen, Windsurf, Copilot, Cline and OpenCode from
    Simple Icons (CC0), Codex, Kilo Code and Amp from Lobe Icons (MIT); they belong to their owners.
  • LAYA_GUARD_FAIL=ask: with the server down, a call the offline check can't judge (neither regex-flagged nor on the
    safe list) becomes review instead of passing. Claude Code, Cursor, Qwen Code and Copilot CLI put it to you, and a
    headless run denies it, so it is closed when nobody is there. open stays the default and closed still blocks;
    known destructive patterns block and safe commands pass in every mode.
  • An outage is recorded as its own kind of event. Every laya-guard check --offline (what hooks fall back to) is now
    appended to the verdict log with via: "offline", the server records how it judged (via: model, regex,
    allowlist, heuristic, limit), and laya-guard stats prints how many calls were judged without the model, with
    the server down or after a model error. Still hash and length only; mock mode isn't counted. See #42 for what's next.

Security

  • The verdict log no longer records the names of package.json scripts. A script finding's reason read
    package.json scripts.<name>: <policy message>, and the name comes from the user's own file, so it is dropped when
    the entry is written (the verdict returned to the caller keeps it).
  • guard-hook.sh judged an empty command when its input wasn't JSON it could read, and an empty command passes. A
    parse failure now follows LAYA_GUARD_FAIL: closed blocks it (exit 2), open lets it through.

Changed

  • The hook scripts now ship inside the package (laya_guard/hooks/, so a plain pip install has them), under names no
    other tool uses, and a test keeps them identical to integrations/. guard-hook.sh in the repo root is a thin wrapper,
    so settings that point at the old path keep working.
  • Amp has a real plugin (integrations/amp/laya-guard.ts, a tool.call handler, which is what Amp recommends now); the
    delegate helper stays as the legacy route. Kilo Code's plugin default-exports { id, server }, which its docs
    require. The Copilot CLI adapter reads the documented toolName/toolArgs payload.
  • Settings backups (<file>.laya-guard-<time>.bak) no longer overwrite each other when two changes land in the same
    second; the later one gets a -1, -2 suffix.
  • The tests no longer write to the developer's own verdict log: tests/conftest.py points LAYA_GUARD_LOG_PATH at a
    temporary file for the whole run.

Full changelog: v0.2.6...v0.3.0

v0.2.6

Choose a tag to compare

@scs0209 scs0209 released this 13 Oct 00:44
8e4edb0

Security

  • A shell comment could talk the model down. kubectl delete namespace prod # clean stale build output went from block (0.98) to allow (0.45), and aws s3 rb … --force from review to allow. The model and the safe-command lane (including the offline fallback's) now see the command with its comments removed; the regexes still scan the original text, so a pattern hidden in a comment is still caught. Heredoc bodies and English or Korean prose keep their #. Prose carried in arguments (--reason "routine", PURPOSE=… cmd) is not covered yet. (#33, #34)

Added

  • integrations/observe/: opt-in Claude Code observer hooks and a label CLI to collect human-labeled verdicts (overblocks and missed attacks). It never blocks or asks, and an approval is never turned into a label by itself. (#32, #35)

Full changelog: https://github.com/scs0209/laya-guard/blob/main/CHANGELOG.md

v0.2.5

Choose a tag to compare

@scs0209 scs0209 released this 12 Oct 23:30
7158603

Security

  • Regex floor gaps closed. destructive-fs only matched rm -rf / in that exact flag order, so rm -fr /, rm -r -f / and rm --recursive --force / slipped past the regex and check --offline. Any recursive-flag spelling now matches, plus rm -rf ~ and rm -rf $HOME.
  • secrets-leak blocks key material (~/.ssh, ~/.aws, ~/.gnupg, ~/.kube, .netrc, .git-credentials, id_rsa, id_ed25519) piped into curl, wget, nc, ncat, netcat or socat.
  • Long inputs are judged in full. The model only saw the first 4,000 characters, so a dangerous command after a long harmless preamble was never judged. Input is now split into 4,000-character windows and every window is judged; the worst verdict wins.

Added

  • credential-read policy (review): reading private keys or cloud credentials with cat/head/tail/base64/cp.
  • mass-delete also flags work-losing git commands: git reset --hard, git clean -f…, git checkout -- . (review).

Changed

  • Behavior change: the Claude Code hook now asks you on review. guard-hook.sh prints permissionDecision: "ask" with the policy id and reason, so Claude Code shows a permission prompt. Before, review passed through silently. Set LAYA_GUARD_REVIEW=pass to restore the old behavior. Non-interactive runs (claude -p) deny ask, so use pass for unattended runs.

Fixed

  • The resident registration number pattern no longer blocks any 123456-1234567-shaped number (file names, timestamps, IDs); it needs an RRN keyword on the same line.
  • Regex block false positives: DAN matching dandelion, jailbreak matching file names like test_jailbreak.py, and auth-bypass matching prose like "verify the fallback is off".
pip install -U laya-guardrail

Claude Code users: re-copy guard-hook.sh to get the review prompt.

Full changelog: CHANGELOG.md

v0.2.4

Choose a tag to compare

@scs0209 scs0209 released this 12 Oct 00:39
d920019

Security

  • package.json scripts are checked before safe commands run. npm test and other safe commands were allowed on the command string alone, but what they run is a script in package.json. An agent that first edited scripts.test (or a repo that shipped with a bad one) could run anything. Now, for npm/pnpm/yarn/bun script commands, the guard reads the nearest package.json and checks the scripts that would run:
    • pre<name>, <name>, and post<name>, following nested npm run and cd in chains
    • regex policies only, not the model, which blocks ordinary scripts like next build and jest
    • a hit blocks or reviews per its policy; if it can't tell which scripts run (--prefix, --filter, -w, cd -, deep nesting), the call goes to review
  • --script-shell, --node-options, --userconfig, and --globalconfig no longer qualify as safe-command flags.

Code those commands run (test files, conftest.py, build.rs, Makefile recipes) still isn't checked. See Limitations in the README and #5.

Added

  • cwd on POST /guard and laya-guard check --cwd. All 11 adapters send the directory the command runs in.
  • CONTRIBUTING.md, CODE_OF_CONDUCT.md, issue forms, and a PR template; PRs are labeled by area automatically; /assign and /unassign on issues; Conventional Commits PR titles; CodeRabbit review config.
pip install -U laya-guardrail

If you use an adapter, re-copy it from integrations/ (or guard-hook.sh for Claude Code) so it sends cwd. Without it, script checks don't run.

v0.2.3

Choose a tag to compare

@scs0209 scs0209 released this 11 Oct 09:09
90bc4a7

Security

  • Oversized inputs are blocked unread. Some regexes are quadratic in line length: curl repeated 40,000 times plus ; rm -rf ~/ (200KB) took 48s to judge, long enough for hooks to time out and fail open. Inputs over 256K characters or with a line over 16K are now blocked before judging.
  • Offline fallback instead of blind fail-open. When the server is down, errors, or takes longer than 10s, all 11 adapters run laya-guard check --offline (regex + safe-command lane, no model):
    • regex-flagged calls are blocked
    • safe commands pass
    • everything else passes with a warning, or is blocked with LAYA_GUARD_FAIL=closed

Thanks to the Reddit commenter who suggested failing closed for regex-flagged calls.

Added

  • laya-guard check --offline and --json
  • LAYA_GUARD_FAIL and LAYA_GUARD_BIN env vars for all adapters

Fixed

  • guard-hook.sh treated HTTP errors as allow; it now falls back like the other adapters.
pip install -U laya-guardrail

If you use an adapter, re-copy it from integrations/ (or guard-hook.sh for Claude Code) to get the fallback.

v0.2.2

Choose a tag to compare

@scs0209 scs0209 released this 11 Oct 07:55
ee0af1b

Fixed

  • Pipe-to-shell hidden mid-chain is now blocked. npm test && curl … | sh used to come back as review (risk 0.55), which passes through in most agents. A new pipe-to-shell policy blocks curl/wget piped into a shell or interpreter, bash -c "$(curl …)", and bash <(curl …).
  • Block policies outrank review policies. Previously the first matching policy won, so a review policy listed earlier could hide a block one.
  • Benign chains are no longer overblocked. npm test && echo ok used to come back block 0.80. Chains are now split at top-level ; && || & and each command is judged on its own; the worst verdict wins, and the whole string still goes through the regexes.

Added

  • Safe commands: cd, true, false, whoami, date, uptime, clear, sleep, uname, df, du, free, which.
  • 10 chain cases in the shell eval set. Real model: 58/60 for both sources. AgentDojo (25/27, 92/97) and the Korean set unchanged.
pip install -U laya-guardrail

Full changelog: https://github.com/scs0209/laya-guard/blob/main/CHANGELOG.md

v0.2.1

Choose a tag to compare

@scs0209 scs0209 released this 11 Oct 06:21

Fixed

  • Everyday dev commands were blocked by the model (ls -la, npm run build,
    go test ./..., echo hello: 11 of 50 judgments in a spot check). A new
    safe-command lane (safe_commands in policy.yaml) allows single plain
    commands without the model; chaining, pipes, redirects, substitution,
    program-running flags, and sensitive paths never qualify.
  • A model failure at runtime (checkpoint download, OOM, inference error) is no
    longer reported as a model verdict: it falls back to regex+heuristics with
    verdict_source: "heuristic", a "Model error" reason, and a logged warning.

Added

  • Shell eval set (eval/run_eval.py --set shell): 28 everyday + 22
    dangerous/bypass commands. Real model: 48/50 (was 32/50); CI gates it in
    mock mode. AgentDojo results unchanged (25/27, 92/97).
  • Shell regexes: pipes into a shell or nc, curl -d @file uploads,
    redirects into /etc/, git core.hooksPath/core.pager-style config,
    git push --force.
  • verdict_source: "allowlist" for safe-command verdicts.
  • laya-guard serve loads the model before listening so the first hook call
    doesn't time out (and fail open) during the checkpoint download.
    Skip with --no-warmup.
  • guard-hook.sh honors LAYA_GUARD_URL, like the other adapters.
  • SECURITY.md, this changelog, and CI on Python 3.10–3.13.

Changed

  • guard-hook.sh judges only the command for the Bash tool, not the agent's
    description.
  • PyPI summary and README install instructions (the PyPI name is
    laya-guardrail; laya-guard on PyPI is an unrelated project).

Install: pip install laya-guardrail==0.2.1