HOL Guard 3.0: extension-first managed controls and a Rust authority path

HOL Guard 3.0: extension-first managed controls and a Rust authority path

HOL Guard 3.0 makes Local Extensions the capability vocabulary, routes PreToolUse authority through a version-matched Rust runtime, and keeps Guard Cloud on Control Sets. Upgrade to 3.0.113 (do not pin yanked 3.0.0) and verify with hol-guard command explain.

13 min read2,809 words
Contents

If a coding agent can reach a shell, it can reach aws s3 rb, gcloud projects delete, az group delete, git push --force, and php artisan migrate:fresh. HOL Guard 3.0 stops treating that risk as one-off hooks and treats it as a product model: Local Extensions define what Guard can protect; Guard Cloud Control Sets define how that protection is governed across people, devices, and time.

Stable v3.0.0 cut on August 27, 2026. The current PyPI tip is 3.0.113 (Production/Stable). Warning: PyPI yanked 3.0.0, so do not pin the cut tag. Pin a current 3.0.x tip such as 3.0.113. This post is an operator write-up of the product model, grounded in ADR 0011, the managed-controls product decision, the extension directory, the harness support matrix, and the Rust authority boundary.

The problem 3.0 actually solves

Pre-3.0 Guard already detected harness config, paused on new or changed artifacts, routed approvals, wrote receipts, and kept Cloud optional. Teams still hit a conceptual gap: policy felt like one editor, detectors another, and hook config drifted per harness.

Presenting Local Extensions and Cloud policies as parallel systems duplicated concepts and obscured authority. That is the 2.x mental model that broke: operators could not tell which surface owned capability identity versus which surface owned governance.

Extensions define what HOL Guard can protect. Guard Cloud Control Sets define how that protection is governed across people, devices, agents, environments, and time.

That ADR 0011 sentence is the release. Local owns capability identity and detector facts. Cloud owns governance objects: Control Sets, Deployments, acknowledgement, drift, pause, rollback, and audit. ADR also rejected paywalling Local Extensions and giving every Cloud rule absolute precedence in 3.0.

Vocabulary that actually decides behavior

The product-decision.json contract locks shared vocabulary. Use these words precisely.

  • Extension: stable Local capability boundary for a protected tool or domain.
  • Permission: independently configurable capability inside an Extension.
  • Detector rule: Local detail that recognizes evidence, not authority.
  • Local setting: can strengthen protection, never weaken floors.
  • Remembered rule: approval memory; cannot bypass managed restrictions.
  • Control Set: versioned Cloud collection with simulation, review, sign, deploy, ack.
  • Deployment: signed Control Set version with drift, pause, rollback, audit state.
  • Managed restriction: disable-extension, disable-permission, or global-lockdown only.
  • Exception: time-bounded; cannot weaken managed-restrictive blocks in 3.0.

Cloud surface is Managed controls with Control Set as primary object. Local /extensions stays Extensions. Local /policy stays Rules and exceptions.

Authority modes and who wins

Authority modes: personal-shared, workspace-shared, and managed-restrictive. Local tightening remains valid in the first two. managed-restrictive is a signed non-weakenable floor.

Contract flags: local_registry_authoritative is true. cloud_must_not_redefine_detector_matchers is true. Cloud targets Extension and permission identities; it does not recreate Local detector matchers by default.

managed_restrictive actions in release 3.0 are only disable-extension, disable-permission, and global-lockdown. Composition is monotonic for non-weakenable authority.

Authority path diagram

harness tool call
  -> Guard hook (PreToolUse)
  -> version-matched Rust runtime
  -> CanonicalCommand parse-once
  -> Extension permission detectors (evidence only)
  -> local floors + device settings + remembered rules
  -> optional Cloud Control Set (managed-restrictive)
  -> allow | ask | deny (fail closed on authority faults)

Python stays on transport, harness rendering, approval coordination, dashboard control-plane, and bounded non-authoritative evidence. It is not the PreToolUse or PostToolUse authority engine on 3.x.

Parse once, explain without side effects

Command protection uses a parse-once CanonicalCommand model. Shell text becomes structured segments, wrappers, pipelines, redirects, and security identity before detectors run. Matchers emit evidence only.

A safe preview segment such as git clean -ndx does not wash away a later destructive segment in the same line. Help and describe forms suppress only their owning rule and segment.

hol-guard command test 'aws s3 rb s3://prod-bucket --force'
hol-guard command explain 'gcloud sql instances delete my-db --quiet'
hol-guard command extensions

command test and command explain are side-effect-free. They do not execute the command, create an approval, or write a receipt. Test Lab in the dashboard follows the same contract.

Rust owns PreToolUse and PostToolUse security semantics

Every supported harness routes security semantics through the bundled, version-matched Rust runtime. Generic PreToolUse decisions and supported PostToolUse output review do not use a Python semantic fallback.

Identity, protocol, rule-digest, policy-snapshot, overload, timeout, transport, and response failures fail closed on the authority path. Version-matched Rust wheels ship inside the Guard package so native binary and Python package cannot drift.

The v3.0.0 release notes put the resident Rust runtime at roughly 29,000 lines shipped in-wheel as of that cut. After upgrade, repair harnesses so hooks point at the installed boundary.

Extension catalog honesty

Required core Extensions cover filesystem, git, system, windows, and guard-self-protection. They are built-in capability boundaries over the same parser used by harness hooks, not downloadable regex bundles.

From the v3.0.0 notes, built-in cloud destructive-operation matrices land at 200 AWS, 100 gcloud, and 100 Azure matrix-reviewed operations. The AWS matrix ships as two non-overlapping 100-operation batches.

Do not confuse the directory 1 rule column on command.cloud.* with operation counts. Those Extensions wrap a matrix. Verify on your installed version with hol-guard command extensions.

Package-manager protection delegates through Package Firewall for package.* surfaces.

External community contribs stay off until local enable.

Protection Center postures and Test Lab

Primary posture control is Protected, Extra careful, or Watch. Default remains Protected.

hol-guard settings set protection protected
hol-guard command controls status
hol-guard command controls list
hol-guard command controls show command.git
hol-guard command controls patterns --tool command.git
hol-guard command controls preview command.git --target-kind extension --state disabled

Those inspect local state only. Device settings can tighten; they cannot lower immutable floors or weaken managed restrictions.

Extra careful also asks on first new site or tool install. Watch records without stopping; use only while debugging false positives. Test Lab rehearses without executing.

Local floor vs Cloud governance

Local Guard without sign-in still intercepts, blocks on supported actions, writes receipts, and resolves approvals. Cloud outage, trial end, past due, or never connected must not be translated into protection expired.

Sync paused is not unprotected. Codes like feature_not_in_plan and device_limit_reached describe Cloud state only.

Solo two-device limits are Cloud-sync limits only. A third machine still installs and protects locally. Managed Controls stages are independently gated and fail-closed.

Cloud adds sync, history, Control Sets, simulation, staged rollout, acknowledgement, drift, rollback, and shared review. ADR 0011 rejected paywalling Local Extensions.

Core stays headless; Desktop owns the tray

The tray moved to hashgraph-online/hol-guard-desktop. Guard Core stays headless and owns enforcement: policy, approvals, receipts, daemon, browser dashboard, updates, repair, diagnostics.

Desktop is optional. If the tray vanished after upgrade, you lost a UI process, not the authority path. Desktop is not required for blocking.

Harness variance, stated honestly

Support is not identical across harnesses. Treat the matrix as contracts, not slogans.

  • Codex: Guard-owned PreToolUse and PermissionRequest hooks are authoritative, including deny in YOLO mode. Wrapper launch refuses when native hooks are missing or disabled.
  • Grok: waits on original PreToolUse after queuing approval, then returns allow so Grok resumes the same tool call. Fails open if a hook crashes or times out.
  • Kimi: fails open if a hook crashes or times out, so Kimi Code keeps working when Guard is unreachable.
  • ZCode: fails open if a hook crashes or times out.
  • Cline: evaluates PreToolUse synchronously and fails closed when Guard is unavailable or payloads are contradictory.

No VS Code Copilot extension-host claim. A VS Code inline permission sheet alone is not Guard proof. No Goose adapter in this repository. No Cisco AIBOM runtime policy in this pass.

Harness     | Fail mode on Guard crash/unavail | Resume note
Codex       | refuses missing native hooks     | thread resume when bound
Grok        | fail-open on crash/timeout       | same-call resume
Kimi        | fail-open on crash/timeout       | keeps working
ZCode       | fail-open on crash/timeout       | keeps working
Cline       | fail-closed on unavail/bad payload| sync PreToolUse

Who is NOT in scope

  • Not a cloud WAF, EDR, or production network firewall.
  • Not VS Code Copilot extension-host interception.
  • Not automatic enablement of External community extensions.
  • Not a requirement to connect Guard Cloud for Local blocking.
  • Not a Goose adapter in this repository.
  • Not Python as the authoritative PreToolUse engine on 3.x.
  • Not a silent 2.x to 3.x hook-free upgrade.

Upgrade runbook: pin 3.0.113 and prove it

Prefer the current tip. Do not pin yanked 3.0.0. Repair harnesses you use. Prove recognition with command explain. Migrate local decision integrity if coming from older local memory.

uv tool install --force "hol-guard[cisco]==3.0.113"
hol-guard --version
hol-guard init
hol-guard install codex
hol-guard apps repair grok
hol-guard status
hol-guard command explain 'aws s3 rb s3://prod-bucket --force'
hol-guard command test 'az vm delete -g rg -n vm1 --yes'
hol-guard command controls status
hol-guard policies integrity-status
hol-guard policies migrate-local-integrity --preserve-all-local

Green-bar checks

  • Package reports a 3.0.x version at or above 3.0.113.
  • status shows the harness protected with hooks present.
  • command explain names a built-in Extension and permission evidence for a destructive class you care about.
  • policies integrity-status is clean or successfully migrated.

Then walk Protection Center once and set posture deliberately rather than inheriting muscle memory from 2.x.

Local path operators actually run

Day one on a laptop still looks the same: install Guard Core, run init or install for the harness you use, confirm status, then rehearse a destructive class with command explain before you trust muscle memory.

The local path does not require a Guard Cloud account. Launch interception, Local Extensions, blocking and warnings on supported actions, receipts, approvals, Package Firewall where supported, and decision-integrity tooling all continue offline.

When Cloud is signed out, paused, past due, or unreachable, local interception continues. Clients must keep those Cloud plan codes as Cloud state. Translating them into protection expired is a product bug, not an operator expectation.

Operator rehearsal is intentional. command test returns a concise classification. command explain returns the evaluation trace. Neither executes the shell text. That matters when you are validating that aws s3 rb, gcloud projects delete, or az group delete still map to the Extension and permission you expect after an upgrade.

The same parse-once CanonicalCommand artifact feeds policy, approval, memory, and receipts. Compound commands keep every match. Safe help or dry-run segments do not erase an unrelated destructive segment later in the line. Cloud matrices match executable and subcommand structure rather than English words like delete.

Cloud Managed controls caveats

Guard Cloud is valuable when you need shared history, Control Sets, simulation, staged rollout, acknowledgement, drift detection, rollback, and review across people and devices. It is not the detector editor. It targets Extension and permission identities published by the local registry.

In managed-restrictive mode, release 3.0 only allows disable-extension, disable-permission, and global-lockdown. Exceptions cannot weaken those blocks. Local settings and remembered rules may tighten protection; they cannot lower required floors or managed restrictions.

Deployment is a signed Control Set version delivered to an eligible runtime cohort with acknowledgement, drift, pause, rollback, and audit state. Stages fail closed independently. A paused sync stage does not mean Local Guard stopped protecting the shell.

Solo is the personal continuity tier. The two-device limit is a Cloud-sync limit only. A third machine still installs, intercepts, and blocks locally. When the client returns device_limit_reached or cloud_sync_paused_plan_limit, treat that as sync entitlement, not as a firewall off switch.

The 2.x mental model that broke

Before extension-first managed controls, teams often treated hooks as the product. Each harness got its own install folklore. Detectors lived in one place, approvals in another, and Cloud policy language drifted from local capability names. When an agent proposed a destructive cloud operation, the answer lived in tribal knowledge instead of an explicit Extension permission with attributable evidence.

Operators asked which editor was authoritative and got two answers. That duplication is what ADR 0011 rejected. Extensions are not a second policy language. Control Sets are not a second matcher store. If a Cloud authoring path starts redefining detector matchers, it violates cloud_must_not_redefine_detector_matchers and breaks the local registry authority contract.

Paywalling Local Extensions was also rejected. Local safety stays the floor whether or not Cloud is connected. Cloud monetization has to come from governance, continuity, simulation, rollout, drift, exceptions, and evidence, not intentionally weaker local enforcement.

Catalog and directory reading notes

The extension directory is generated from the same validated registry used by runtime hooks, command explain, the dashboard, and catalog APIs. Required core is an immutable floor. Built-in detectors live in the canonical local registry. Package Firewall owns package.* delegated protection. External stays opt-in and off by default.

Release notes for v3.0.0 quantify cloud destructive matrices as 200 AWS operations, 100 gcloud operations, and 100 Azure operations, plus focused high-blast-radius deletes that already existed. Neighboring help, describe, and scoped dry-run forms stay safe where documented. Reordered global flags do not change recognition.

Directory UIs may show 1 rule on a command.cloud.* Extension. That column is not the operation count. The Extension wraps a matrix. If you count directory rows and conclude Guard only knows one AWS delete shape, you misread the catalog. Always confirm with hol-guard command extensions on the installed tip.

Tip pin and yanked cut tag

Stable v3.0.0 cut on August 27, 2026. The current PyPI tip is 3.0.113 Production/Stable. PyPI yanked 3.0.0. Do not pin the cut tag in lockfiles, Dockerfiles, or CI. Pin a current 3.0.x tip such as 3.0.113, then re-run harness install or repair so hooks match the installed package.

Some top-level README and marketing surfaces may still describe early 3.x language. Treat the installed package version, release notes for the cut, ADR 0011, and product-decision.json as the operator sources of truth for this post. hol.org/guard schema labels can lag; do not use a stale marketing number as your pin.

After pin, green bars are version, harness status, command explain evidence, and integrity status. Skip changelog laundry for OCI or gVisor side quests until those four are green.

Why Rust on the authority path

Agents are fast. Approval windows are short. The authority path has to be local, attributable, and hard to race. Shipping a version-matched Rust runtime inside the wheel keeps PreToolUse and PostToolUse security semantics on one binary that cannot silently fall back to a Python evaluator when something is inconvenient.

Fail closed on identity, protocol, rule-digest, policy-snapshot, overload, timeout, transport, and response faults. That is intentional. A soft miss that allows the tool call is not a degraded mode worth keeping on the authority path. Harness-level fail-open behavior, where it exists, is a harness contract difference, not a Guard slogan that everything fails closed everywhere.

Python remains useful for authenticated transport, harness-specific rendering, approval coordination, dashboard control-plane work, and bounded non-authoritative evidence persistence. The ownership gate exists specifically to stop Python command evaluation or output-scanning fallback from being reintroduced as PreToolUse or PostToolUse authority.

Protection Center as the operator control plane

Protection Center replaces informal mode matrices with posture, a tool map, plain-language module detail, and Test Lab. Compatibility routes for /extensions remain. Reads through hol-guard command controls status, list, show, patterns, and preview stay local and do not contact Cloud.

Writes still go through preview, proof, approval, and apply. A device setting can preserve or tighten protection. It cannot lower an immutable HOL floor, override Emergency Lockdown, or weaken a workspace managed restriction. Package Firewall Extensions are configured on that surface rather than duplicated as ordinary command-policy rules.

Posture choices are operational. Protected is the default floor most teams should keep. Extra careful adds first-seen friction that catches surprise tool installs. Watch is for debugging false positives, not for production YOLO. If Watch becomes your standing posture, you are collecting receipts without stopping the blast radius.

Treat harness variance as part of the threat model. Codex refusing a launch without native hooks is a feature. Grok, Kimi, and ZCode failing open on crash means Guard unreachable is not the same as Guard deny. Cline failing closed means an unavailable Guard stops action-bearing tools. Operators should read harness-support.md for the harness they actually run, then prove the path with doctor, status, and command explain on that same machine.

Community External extensions such as blitcp, Noodle, Probe, repo2nb, and Skill Sunset remain off until a local-admin enable. First-party floors and HOL-curated trusted libraries stay on by default. Cloud cannot remotely flip External on by itself.

What this is not

Guard 3.0 is not a promise that every agent action is safe. It is a local-first runtime firewall that makes destructive classes explicit, attributable, and interruptible before the shell starts.

It does not replace change management, IAM least privilege, or human judgment on production credentials. It does replace the fiction that the model usually asks first is a control.

It is not a Cloud upsell disguised as antivirus. Local Extensions are the capability vocabulary. Rust is the non-bypassable decision path for supported pre-tool and post-tool security semantics. Cloud Managed controls govern those identities instead of acting as a second detector editor.

References

Continue reading

All posts