Skip to content

feat(guardrails): add the identity-domain guard with domain classification and its kill switch #9743

Description

@kyle-sexton

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-profile and 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.sh and plugins/guardrails/hooks/block-cross-domain.test.sh, the Bash|PowerShell row of plugins/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.sh exits 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=false gives empty stdout; a malformed map exits 0 with no deny.
  • grep -n "block-cross-domain" plugins/guardrails/hooks/hooks.json prints the Bash|PowerShell row line, and jq '.userConfig.block_cross_domain_enabled' plugins/guardrails/.claude-plugin/plugin.json is not null.
  • grep -n "block-cross-domain" plugins/guardrails/README.md prints 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.sh prints nothing (placeholders only).
  • scripts/run-test-file.sh plugins/guardrails/hooks/run-guards.test.sh exits 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-budget describes, inside that budget.
  • scripts/check-changelog-fragments.sh --check-required origin/main exits 0.

Activity

  1. added
    priority: mediumReal value, no hard deadline; normal backlog flow.
    status: readyTriaged, unblocked, and fully specified; eligible to pick up.
    area: securitySecurity-relevant: vulnerability, hardening, or disclosure follow-up.
    on Oct 24, 2026
  2. added
    agent-readyFully specified and briefed; eligible for autonomous pickup from the frontier.
    and removed on Oct 24, 2026
  3. melodic-automation-lanes commented on Oct 25, 2026

    @melodic-automation-lanes
    Contributor

    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 removed status: ready and agent-ready, so the hourly triage sweep sends it back to intake triage, which decides what work is left and admits it again.

  4. 13 remaining items

  5. melodic-automation-lanes commented on Oct 27, 2026

    @melodic-automation-lanes
    Contributor

    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-ready started, before it did any work; that run ended at 2026-10-27T17:52:40Z. work-unblock has now removed and added agent-ready back 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 added agent-ready or a run last reported, work-unblock waits longer before each one, up to 24 hours.

  6. added
    agent-readyFully specified and briefed; eligible for autonomous pickup from the frontier.
    and removed
    agent-readyFully specified and briefed; eligible for autonomous pickup from the frontier.
    on Oct 27, 2026
  7. melodic-automation-lanes commented on Oct 28, 2026

    @melodic-automation-lanes
    Contributor

    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-ready started, before it did any work; that run ended at 2026-10-27T22:59:19Z. work-unblock has now removed and added agent-ready back 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 added agent-ready or a run last reported, work-unblock waits longer before each one, up to 24 hours.

  8. added
    agent-readyFully specified and briefed; eligible for autonomous pickup from the frontier.
    host-runNeeds an unattended run on a host; the host runner takes it, not a person or the issue lane.
    and removed
    agent-readyFully specified and briefed; eligible for autonomous pickup from the frontier.
    on Oct 28, 2026
  9. melodic-automation-lanes commented on Oct 28, 2026

    @melodic-automation-lanes
    Contributor

    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-run and removed agent-ready: a maintainer session on a host builds it, and it waits on no decision from a person.

    In the run's words:

    host-only: the guard this issue asks for already ships on main (guardrails 0.57.0, CHANGELOG entry 'block-cross-domain ... (#9743)'). The only Done-when item not met in the tree is the Bash|PowerShell row's added wall time in S, measured as docs/conventions/hook-budget describes. That measurement uses the Windows reference host and the dotfiles fan-out harness, and plugins/guardrails/README.md 'Hook budget accounting' still says 'The row's added wall time on the Windows reference host, in S, is not measured yet.' This lane cannot run that measurement. Once someone measures it on that host and records it in that README paragraph, the issue can close.

    What the run says it did not do:

    • The Bash|PowerShell row's added wall time in S, measured as docs/conventions/hook-budget describes. It needs the Windows reference host and the dotfiles fan-out harness, which this lane does not have
    • scripts/run-test-file.sh plugins/guardrails/hooks/block-cross-domain.test.sh and plugins/guardrails/hooks/run-guards.test.sh were not run here: this lane runs only its named wrappers. Both suites are on main and run in the repository's CI
    • No review: there was no diff to review, and review-venue.txt is absent from the scratchpad

    Run: https://github.com/melodic-software/claude-code-plugins/actions/runs/40617450320

    To hand it back to the lane, remove host-run and add agent-ready again.

  10. added
    needs-triageNot yet classified. Floor until a type and one priority tier are set.
    on Oct 28, 2026
  11. added
    needs-triageNot yet classified. Floor until a type and one priority tier are set.
    on Oct 28, 2026
  12. kyle-sexton commented on Oct 29, 2026

    @kyle-sexton
    MemberAuthor

    Host run. I measured the guardrails PreToolUse Bash 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. No claude session ran: the dotfiles fan-out harness tools/measure/measure-claude-hook-fanout.sh (sha256 e1bbebb9…) calls the hooks directly, against a scratch CLAUDE_CONFIG_DIR that enables only guardrails.

    What ran:

    • Guardrails 0.61.2 at main, with --runs 7 per run.
    • block_cross_domain_enabled set three ways through CLAUDE_PLUGIN_OPTION_BLOCK_CROSS_DOMAIN_ENABLED: false (control), true with no map, and true with a placeholder identity-domains.json.
    • 15 rounds with the arm order rotated each round. All 75 runs were valid: S was 20 to 29 ms, with a median of 21 ms.
    • A control in every round confirmed that the map arm wrote the compiled cache and the disabled arm did not.

    Results, as the paired per-round median against the control:

    Arm Bash Bash $(…) Write (untouched) Edit (untouched)
    on, no map +0 ms −6 ms +1 ms −1 ms
    on, map +5 ms (0.2 S) +5 ms +4 ms +4 ms

    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:

    1. Measured added time recorded in the README budget section: done in docs(guardrails): record the measured Bash row cost of block-cross-domain and protected trees #10807.
    2. Guard, test suite, hooks.json row, userConfig key and README row: already on main from 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.sh PASS=310 FAIL=0.
      • The hooks.json row and the block_cross_domain_enabled key are present.
      • The guard files contain no org or user names.
    3. Changelog fragment: a guardrails patch fragment ships with docs(guardrails): record the measured Bash row cost of block-cross-domain and protected trees #10807.

    Target labels: add none; remove host-run.

  13. removed
    host-runNeeds an unattended run on a host; the host runner takes it, not a person or the issue lane.
    on Oct 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: securitySecurity-relevant: vulnerability, hardening, or disclosure follow-up.needs-triageNot yet classified. Floor until a type and one priority tier are set.priority: mediumReal value, no hard deadline; normal backlog flow.status: readyTriaged, unblocked, and fully specified; eligible to pick up.work-class: scopedA briefed fix or small feature; blast radius bounded by the brief, tests exist.

    Fields

    No fields configured for Feature.

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions