Description
Parent
Refs #5945
Depends on
- none: on the frontier now
Outcome
A new guard, block-cross-domain, runs on the guardrails Bash|PowerShell row. It reads a machine-local
domain map (${CLAUDE_PLUGIN_DATA}/identity-domains.json) with this shape: domains.<name> holding roots
(per-OS absolute roots), owners, identity_markers, optional gh_config_dir and az_config_dir;
identity_files; shared_exceptions; messages. Keys follow docs/conventions/plugin-option-naming.
It classifies the session cwd into a domain or neutral, and runs a cheap substring prefilter on the configured
roots and markers before any parse. With no map it emits nothing and parses nothing. A
block_cross_domain_enabled userConfig option turns it off, and it writes telemetry like every other guard.
This slice ships the plumbing; the deny rules come in later slices.
Decisions
- Epic feat(guardrails): generalize machine-local safety guards into plugin guards #5943 defaults, adopted: the guard lives in guardrails behind its dispatcher and a per-guard kill switch;
bash under the node entry point; port behavior, not the local Python. - The domain map is derived from
/harness-ops:machine-profileand is machine-local: it never sits in a tracked
file, fixture or doc. Placeholders only. - Fail open on any internal error; every exit is 0 with the decision in stdout JSON.
- The guard reads command text: a deterrent, not a sandbox. Declared gaps: computed paths, scripts run by name.
Key interfaces
plugins/guardrails/hooks/block-cross-domain.shandplugins/guardrails/hooks/block-cross-domain.test.sh, theBash|PowerShellrow ofplugins/guardrails/hooks/hooks.json,
plugins/guardrails/README.md,docs/conventions/hook-precision,docs/conventions/hook-budget.
Done when
-
scripts/run-test-file.sh plugins/guardrails/hooks/block-cross-domain.test.shexits 0 with cases: no map file gives empty stdout and exit 0; a map present and a command naming no root or marker returns before the parser runs (asserted by a test hook such as a trace variable);block_cross_domain_enabled=falsegives empty stdout; a malformed map exits 0 with no deny. -
grep -n "block-cross-domain" plugins/guardrails/hooks/hooks.jsonprints theBash|PowerShellrow line, andjq '.userConfig.block_cross_domain_enabled' plugins/guardrails/.claude-plugin/plugin.jsonis not null. -
grep -n "block-cross-domain" plugins/guardrails/README.mdprints the guard row with the config shape, kill switch and declared gaps. -
git grep -nE "melodic|kyle|sexton" -- plugins/guardrails/hooks/block-cross-domain.sh plugins/guardrails/hooks/block-cross-domain.test.shprints nothing (placeholders only). -
scripts/run-test-file.sh plugins/guardrails/hooks/run-guards.test.shexits 0, including the guardrails: Bash|PowerShell PreToolUse row takes 30+s on a ~70 KB command; its 60s timeout fails open #4528 long-command ceiling case. - The PR reports the Bash|PowerShell row's added time, measured as
docs/conventions/hook-budgetdescribes, inside that budget. -
scripts/check-changelog-fragments.sh --check-required origin/mainexits 0.
Activity
melodic-automation-lanes commentedon Oct 25, 2026
The lanes App's pull request #9894 merged with
Refs:, so this issue stays open and nothing starts the lane on it again. work-unblock removedstatus: readyandagent-ready, so the hourly triage sweep sends it back to intake triage, which decides what work is left and admits it again.13 remaining items
melodic-automation-lanes commentedon Oct 27, 2026
This records a re-fire of an earlier refusal, not a new refusal. The lanes kill switch stopped run 40545192600, the work-resolve-issue run that
agent-readystarted, before it did any work; that run ended at 2026-10-27T17:52:40Z. work-unblock has now removed and addedagent-readyback to start a fresh run. A kill-switch refusal is no fault in this issue or the lanes, so it never counts against later re-fires; after limits.limit-stops-per-issue of them since a person last addedagent-readyor a run last reported, work-unblock waits longer before each one, up to 24 hours.melodic-automation-lanes commentedon Oct 28, 2026
This records a re-fire of an earlier refusal, not a new refusal. The lanes kill switch stopped run 40597927201, the work-resolve-issue run that
agent-readystarted, before it did any work; that run ended at 2026-10-27T22:59:19Z. work-unblock has now removed and addedagent-readyback to start a fresh run. A kill-switch refusal is no fault in this issue or the lanes, so it never counts against later re-fires; after limits.limit-stops-per-issue of them since a person last addedagent-readyor a run last reported, work-unblock waits longer before each one, up to 24 hours.melodic-automation-lanes commentedon Oct 28, 2026
The work-resolve-issue lane stopped on this issue (
host-only): the run reports the work needs a host or attended run the lane cannot make.It added
host-runand removedagent-ready: a maintainer session on a host builds it, and it waits on no decision from a person.In the run's words:
What the run says it did not do:
Run: https://github.com/melodic-software/claude-code-plugins/actions/runs/40617450320
To hand it back to the lane, remove
host-runand addagent-readyagain.kyle-sexton commentedon Oct 29, 2026
Host run. I measured the guardrails
PreToolUseBash row on the Windows reference host's native Windows lane (Windows 11, Git Bash 5.3.15, Node 24.21.0, jq 1.8.2, on AC power). This host runs Claude Code 2.1.312. Noclaudesession ran: the dotfiles fan-out harnesstools/measure/measure-claude-hook-fanout.sh(sha256e1bbebb9…) calls the hooks directly, against a scratchCLAUDE_CONFIG_DIRthat enables only guardrails.What ran:
main, with--runs 7per run.block_cross_domain_enabledset three ways throughCLAUDE_PLUGIN_OPTION_BLOCK_CROSS_DOMAIN_ENABLED:false(control),truewith no map, andtruewith a placeholderidentity-domains.json.Results, as the paired per-round median against the control:
$(…)The added time is inside the ±0.3 S that the untouched rows moved. k is 0 on this path, so the row stays inside its k × S budget.
Done-when:
hooks.jsonrow, userConfig key and README row: already onmainfrom feat(guardrails): add the identity-domain guard with domain classification and its kill switch #9894. Checked on that PR's head:block-cross-domain.test.shPASS=310 FAIL=0.hooks.jsonrow and theblock_cross_domain_enabledkey are present.Target labels: add none; remove host-run.