Skip to content

disk-hygiene: kill switch trusts /etc/claude-code managed settings on WSL, where that directory is user-writable #7287

Closed

Description

@kyle-sexton

Summary

disk-hygiene's kill switch treats /etc/claude-code/managed-settings.json as a root-owned path that cannot be forged, and lets a value configured there override the user's setting. On WSL the live docs say /etc/claude-code is user-writable, so any process running as the user can stand in for organization policy: it can replace an organization's audit-only setting, or force-enable the clean skill's execution tiers over the user's own audit-only choice.

Affected area / component

plugins/disk-hygiene/lib/killswitch_config.py

Steps to reproduce

  1. On WSL, as the ordinary user, write /etc/claude-code/managed-settings.json (or a file in /etc/claude-code/managed-settings.d/) that sets pluginConfigs["disk-hygiene@<marketplace>"].options.disk_hygiene_enabled to true.
  2. Set the same option to false (audit-only) in user settings.
  3. Call resolve_effective() with the platform's managed path. The managed value wins and the execution tiers are enabled.

(Repro is from reading the code path; not run.)

Expected behavior

On platforms where the managed directory is user-writable, the guard does not treat that path as an organization-controlled source for the kill switch.

Actual behavior

  • killswitch_config.py:45-78 (managed_settings_path): the docstring says the file "lives at a fixed, root-owned system path per platform, so a repo cannot forge it", and the function returns /etc/claude-code/managed-settings.json for every linux* platform, WSL included.
  • killswitch_config.py:291-317 (resolve_effective): a configured managed value is returned before user settings are read.
  • Live docs, https://code.claude.com/docs/en/managed-settings#present-admin-documents (read 2026-10-12): "On WSL, /etc/claude-code is user-writable as well", with the wslInheritsWindowsSettings entry saying when the Windows documents stand above it.

Suggested fix

Detect WSL and treat a user-writable managed path as untrusted for this switch (for example, honor it only when it is root-owned and not writable by the current user), and correct the docstring.

The related fail-open gap (the guard's hook rows do not set onFailure: "block") is tracked in #6755 and is not repeated here.

Environment

WSL; disk-hygiene on main as of 2026-10-12.

Found during the upstream-pointer sweep (PRs #7061-#7274).

Activity

  1. added theissue type on Oct 12, 2026
  2. added
    needs-triageNot yet classified. Floor until a type and one priority tier are set.
    on Oct 12, 2026
  3. github-actions commented on Oct 12, 2026

    @github-actions
    Contributor

    Thanks for the report. Automated triage handed this issue to a maintainer.

    Automated triage. A maintainer reviews it.

  4. added
    needs-humanHuman-in-the-loop required; autonomous sessions must not resolve items carrying this.
    on Oct 12, 2026
  5. removed
    needs-triageNot yet classified. Floor until a type and one priority tier are set.
    on Oct 12, 2026
  6. kyle-sexton commented on Oct 13, 2026

    @kyle-sexton
    MemberAuthor

    Decision recorded. The owner approved applying independently verified recommendations (high-risk set, reviewed) on 2026-10-13.

    • How does the kill switch treat a managed file that the current user can write? → Asymmetric: a managed value that restricts (audit-only) is honored from any source; a managed value that enables execution is honored only when the file is root-owned and not writable by the current user
      Basis: https://code.claude.com/docs/en/managed-settings#present-admin-documents (fetched 2026-10-12, line 167: 'On WSL, /etc/claude-code is user-writable as well'); plugins/disk-hygiene/lib/killswitch_config.py:51-94 (Linux/WSL path), :291-317 resolve_effective per issue

    disk-hygiene's kill switch returns a managed-settings value before user settings, and reads /etc/claude-code on WSL, which the docs say is user-writable, so a user process can force-enable the execution tiers. The docstring on main already notes the path is not admin-only everywhere, but the code still trusts it.

    Verification notes:

    • resolve_effective is at killswitch_config.py:313-337 on main, not :291-317 (record took the issue's range); behavior as described.
    • Implementation note: apply the ownership check per file, including each managed-settings.d drop-in, since _managed_effective lets the last configured file win.

    Verified by an independent fresh-context check (confirmed, risk high): gh issue view 7287 (open, no owner decision); origin/main plugins/disk-hygiene/lib/killswitch_config.py:51-96 (docstring already says not admin-only everywhere; linux* returns /etc/claude-code path); origin/main killswitch_config.py:265-337 (_managed_settings_files, _managed_effective, resolve_effective returns managed verdict before user settings; default closed to enabled); WebFetch https://code.claude.com/docs/en/managed-settings line 167: 'On WSL, /etc/claude-code is user-writable as well'; git log killswitch_config.py: latest #7252 docs only

    Admission to an agent lane stays with the work-class rules and owner ratification.

  7. kyle-sexton commented on Oct 18, 2026

    @kyle-sexton
    MemberAuthor

    Decision recorded.

    Question: How should the disk-hygiene kill switch treat a managed settings file the current user can write, such as /etc/claude-code on WSL?

    Owner's answer: Asymmetric trust: a managed value that restricts is honored from any file, but a managed value that enables execution is honored only from a file that is root-owned and not writable by the current user, checked per file including each drop-in.

    The owner confirmed this decision, first recorded at #7287 (comment), in an attended backlog-shaping session on 2026-10-18. The question was "Confirm the 10 decisions, #7287's kill-switch hardening included, and admit them to the paced queue?" and the owner answered "Yes, all 10 (Recommended)".

    Posted by AI from the owner's answer in that session.

  8. kyle-sexton commented on Oct 18, 2026

    @kyle-sexton
    MemberAuthor

    Agent Brief

    Type: Bug
    Summary: The disk-hygiene kill switch honors an enabling managed-settings value only from a file that is root-owned and not writable by the current user.

    Current behavior:
    The kill switch treats /etc/claude-code/managed-settings.json (and its managed-settings.d drop-ins) as an organization-controlled source on every linux platform, WSL included, and returns a configured managed value before reading user settings. On WSL that directory is user-writable, so any user process can force-enable the clean skill's execution tiers.

    Desired behavior:

    • A managed value that restricts (audit-only, disk_hygiene_enabled false) is honored from any managed file, whoever owns it.
    • A managed value that enables execution is honored only when that specific file is root-owned and not writable by the current user. The check runs per file, including each drop-in, because the last configured file wins.
    • A managed file that fails the check is skipped for enabling purposes and resolution continues to user settings.
    • The managed_settings_path docstring no longer claims the path cannot be forged.

    Key interfaces:

    • resolve_effective(): same signature; an enabling managed value from an untrusted file no longer wins.
    • Managed-file trust check: per file, owner uid 0 and not writable by the current uid.

    Acceptance criteria:

    • scripts/run-test-file.sh on the disk-hygiene pytest file covering the kill switch passes, with new cases: a user-owned managed file setting enabled=true does not enable when user settings say false; a user-owned managed file setting enabled=false still restricts; a root-owned non-writable file setting enabled=true still enables (ownership stubbed).
    • A drop-in case shows the check applies to each managed-settings.d file.
    • scripts/check-changelog-fragments.sh passes.

    Out of scope:


    This was generated by AI during backlog shaping. The owner approved its admission in an attended backlog-shaping session on 2026-10-18.

  9. added
    agent-readyFully specified and briefed; eligible for autonomous pickup from the frontier.
    work-class: scopedA briefed fix or small feature; blast radius bounded by the brief, tests exist.
    and removed
    needs-humanHuman-in-the-loop required; autonomous sessions must not resolve items carrying this.
    on Oct 18, 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

    agent-readyFully specified and briefed; eligible for autonomous pickup from the frontier.work-class: scopedA briefed fix or small feature; blast radius bounded by the brief, tests exist.

    Type

    Fields

    No fields configured for Bug.

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions