# apparmor.d - Full set of apparmor profiles
# Copyright (C) 2025 Alexandre Pujol <alexandre@pujol.io>
# SPDX-License-Identifier: GPL-2.0-only

# !!! warning
#
#     This profile is **not** installed by default as it breaks the POLA principle
#     in the way it **will** break some cappabilities of claude.
#
# **Architecture**
#
# Similar to `code` and `code-extensions`, the `claude` profile stack define a "world"
# with generic rules of what should be allowed for an AI assistant and what is not.

abi <abi/4.0>,

include <tunables/global>

@{code_name} = code{,-oss} vscode{,-oss}
@{code_config} = Code Code?-?OSS Code?-?Insiders
@{code_config_dirs} = @{HOME}/.@{code_name} @{user_config_dirs}/@{code_config}
@{code_ext_dirs} = @{code_config_dirs}/extensions

@{exec_path}  = @{bin}/claude /opt/claude-code/bin/claude
@{exec_path} += @{user_bin_dirs}/claude
@{exec_path} +=  @{user_config_dirs}/Claude/claude-code/@{version}/claude
@{exec_path} += @{user_share_dirs}/claude/versions/@{version}
@{exec_path} += @{code_ext_dirs}/anthropic.claude-code-*/resources/native-binary/claude
@{att} = /att/claude/
profile claude /{{,usr/}bin/claude,opt/claude-code/bin/claude,home/*/.local/bin/claude,home/*/.config/Claude/claude-code/[0-9]{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],}{.[0-9]{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],},}{.[0-9]{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],},}{-[0-9a-zA-Z]{[0-9a-zA-Z],}{[0-9a-zA-Z],}{[0-9a-zA-Z],}{[0-9a-zA-Z],}{[0-9a-zA-Z],}{[0-9a-zA-Z],}{[0-9a-zA-Z],}{[0-9a-zA-Z],}{[0-9a-zA-Z],},}/claude,home/*/.local/share/claude/versions/[0-9]{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],}{.[0-9]{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],},}{.[0-9]{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],},}{-[0-9a-zA-Z]{[0-9a-zA-Z],}{[0-9a-zA-Z],}{[0-9a-zA-Z],}{[0-9a-zA-Z],}{[0-9a-zA-Z],}{[0-9a-zA-Z],}{[0-9a-zA-Z],}{[0-9a-zA-Z],}{[0-9a-zA-Z],},},home/*/.code{,-oss}/extensions/anthropic.claude-code-*/resources/native-binary/claude,home/*/.vscode{,-oss}/extensions/anthropic.claude-code-*/resources/native-binary/claude,home/*/.config/Code/extensions/anthropic.claude-code-*/resources/native-binary/claude,home/*/.config/Code?-?OSS/extensions/anthropic.claude-code-*/resources/native-binary/claude,home/*/.config/Code?-?Insiders/extensions/anthropic.claude-code-*/resources/native-binary/claude,home/*/.local/bin/claude,home/*/.config/Claude/claude-code/[0-9]{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],}{.[0-9]{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],},}{.[0-9]{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],},}{-[0-9a-zA-Z]{[0-9a-zA-Z],}{[0-9a-zA-Z],}{[0-9a-zA-Z],}{[0-9a-zA-Z],}{[0-9a-zA-Z],}{[0-9a-zA-Z],}{[0-9a-zA-Z],}{[0-9a-zA-Z],}{[0-9a-zA-Z],},}/claude,home/*/.local/share/claude/versions/[0-9]{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],}{.[0-9]{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],},}{.[0-9]{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],},}{-[0-9a-zA-Z]{[0-9a-zA-Z],}{[0-9a-zA-Z],}{[0-9a-zA-Z],}{[0-9a-zA-Z],}{[0-9a-zA-Z],}{[0-9a-zA-Z],}{[0-9a-zA-Z],}{[0-9a-zA-Z],}{[0-9a-zA-Z],},},home/*/.code{,-oss}/extensions/anthropic.claude-code-*/resources/native-binary/claude,home/*/.vscode{,-oss}/extensions/anthropic.claude-code-*/resources/native-binary/claude,home/*/.config/Code/extensions/anthropic.claude-code-*/resources/native-binary/claude,home/*/.config/Code?-?OSS/extensions/anthropic.claude-code-*/resources/native-binary/claude,home/*/.config/Code?-?Insiders/extensions/anthropic.claude-code-*/resources/native-binary/claude} flags=(attach_disconnected,attach_disconnected.path=@{att}) {
  include <abstractions/attached/base>
  include <abstractions/attached/consoles>
  include <abstractions/devtools>
  include <abstractions/attached/nameservice-strict>
  include <abstractions/ssl_certs>

  network inet dgram,
  network inet stream,
  network inet6 dgram,
  network inet6 stream,
  network netlink raw,

  ptrace read peer=claude//*,

  signal send set=term peer=git,
  signal receive set=term peer=code,

  unix (send receive) type=stream peer=(label=code),

  @{exec_path} mrix,

  @{bin}/git      cx -> git,
  @{open_path}    px -> child-open-strict,
  @{shells_path}  cx -> shell,

  @{bin}/env                    m,
  @{bin}/rg                    ix,
  @{bin}/uname                 ix,
  @{bin}/which{,.debianutils} rix,

  # Nodes
  @{bin}/node                             rix,
  @{lib}/node_modules/npm/bin/npm-cli.js  rix,
  @{lib}/node_modules/npm/bin/npx-cli.js  rix,
  /usr/share/nodejs/npm/bin/npm-cli.js    rix,

  #aa:lint ignore=too-wide
  # TODO: should transition to a dev/plugin profile, not the shell one
  priority=-1 /** cx -> shell,

  owner @{HOME}/ r,
  owner @{HOME}/.claude.* rw,
  owner @{HOME}/.claude.*/{,**} rw,
  owner @{HOME}/.claude/ rw,
  owner @{HOME}/.claude/** rwlk -> @{HOME}/.claude/**,
  owner @{HOME}/.npm/{,**} rw,

  owner @{user_share_dirs}/applications/claude-code-url-handler.desktop r,

  # TODO: deny this as self update is an abberation
  owner @{user_bin_dirs}/claude w,
  owner @{user_bin_dirs}/claude.tmp.* rw,

  owner @{user_lib_dirs}/node_modules/{,**} r,

  owner @{user_projects_dirs}/ r,
  owner @{user_projects_dirs}/** rwlk -> @{user_projects_dirs}/**,

  owner @{user_cache_dirs}/claude/{,**} rw,
  owner @{user_cache_dirs}/claude-cli-nodejs/ rw,
  owner @{user_cache_dirs}/claude-cli-nodejs/** rwlk,

  owner @{user_state_dirs}/claude/{,**} rw,

  owner @{user_config_dirs}/git/ignore r,
  owner @{user_config_dirs}/git/config r,

  owner @{user_share_dirs}/claude/{,**} rw,

  owner @{run}/user/@{uid}/cc-socks/ w,
  owner @{run}/user/@{uid}/cc-socks/@{int}.sock w,

        /tmp/ r,
  owner @{tmp}/.@{hex16}-@{int8}.node mrw,
  owner @{tmp}/claude{,-*} rw,
  owner @{tmp}/claude{,-*}/{,**} rw,
  owner @{tmp}/node-compile-cache/** rwlk,
  owner @{tmp}/playwright-artifacts-@{rand6}/{,**} rw,

  #aa:lint ignore=too-wide
  owner @{tmp}/** rwlk -> @{tmp}/**,

  @{sys}/devices/system/cpu/cpufreq/policy@{int}/scaling_cur_freq r,
  @{sys}/devices/system/cpu/cpufreq/policy@{int}/scaling_max_freq r,

        @{sys}/fs/cgroup/user.slice/cpu.max r,
        @{sys}/fs/cgroup/user.slice/user-@{uid}.slice/cpu.max r,
        @{sys}/fs/cgroup/user.slice/user-@{uid}.slice/session-@{int}.scope/cpu.max r,
        @{sys}/fs/cgroup/user.slice/user-@{uid}.slice/session-@{int}.scope/memory.high r,
        @{sys}/fs/cgroup/user.slice/user-@{uid}.slice/session-@{int}.scope/memory.max r,
        @{sys}/fs/cgroup/user.slice/user-@{uid}.slice/user@@{uid}.service/cpu.max r,
  owner @{sys}/fs/cgroup/user.slice/user-@{uid}.slice/user@@{uid}.service/app.slice/*-@{uuid}.scope/memory.* r,
  owner @{sys}/fs/cgroup/user.slice/user-@{uid}.slice/user@@{uid}.service/app.slice/app-*-@{int}.scope/memory.* r,
  owner @{sys}/fs/cgroup/user.slice/user-@{uid}.slice/user@@{uid}.service/app.slice/cpu.max r,

        @{PROC}/sys/vm/mmap_min_addr r,
        @{PROC}/version r,
        @{PROC}/version_signature r,
  owner @{PROC}/@{pid}/cgroup r,
  owner @{PROC}/@{pid}/mountinfo r,
  owner @{PROC}/@{pid}/stat r,
  owner @{PROC}/@{pid}/statm r,
  owner @{PROC}/@{pid}/uid_map r,

  # Safety mechanisms
  deny @{bin}/ssh x,
  deny /etc/machine-id r,
  deny / r,
  deny /home/ r,

  # Dark patterns: if you want integration with browsers, install it explicitly.
  # https://www.thatprivacyguy.com/blog/anthropic-spyware/
  deny owner @{user_config_dirs}/google-chrome/{,**} rw,
  deny owner @{user_config_dirs}/microsoft-edge/{,**} rw,

  # file_inherit
  deny owner @{code_config_dirs}/logs/{,**} w,
  deny owner /dev/shm/.org.chromium.Chromium.@{rand6} rw,

  profile shell flags=(attach_disconnected,attach_disconnected.path=@{att}) {
    include <abstractions/attached/base>
    include <abstractions/attached/consoles>
    include <abstractions/development>
    include <abstractions/devrun>
    include <abstractions/attached/nameservice-strict>
    include <abstractions/shells>
    include <abstractions/ssl_certs>

    capability sys_ptrace,

    network inet dgram,
    network inet stream,
    network inet6 dgram,
    network inet6 stream,
    network netlink raw,

    ptrace read,

    unix (send receive) type=stream peer=(label=claude),
    unix (send receive) type=stream peer=(label=claude-desktop),

    signal receive peer=claude,

    priority=1 @{bin}/flatpak     px -> claude//flatpak,
    priority=1 @{bin}/git         px -> claude//git,
    priority=1 @{bin}/scp         pux -> claude//ssh,
    priority=1 @{bin}/ssh         px -> claude//ssh,
    priority=1 @{ldd_path}        px -> claude//ldd,

    owner @{HOME}/.claude/{,**} r,
    owner @{HOME}/.claude/* w,
    owner @{HOME}/.claude/plugins/{,**} rwlk,
    owner @{HOME}/.claude/shell-snapshots/* rw,

    owner @{code_config_dirs}/logs/{,**} w,
    owner @{user_config_dirs}/gh/*.yml r,
    owner @{user_config_dirs}/git/* r,

    owner @{user_projects_dirs}/ r,
    owner @{user_projects_dirs}/** rwlk,

    /var/tmp/@{word8} rw,

    #aa:lint ignore=too-wide
    # It is not good, but claude / dev proccess use any files in /tmp.
    owner @{tmp}/** rwlk -> @{tmp}/**,

    # Required to copy result to clipboard
    owner @{run}/user/@{uid}/wayland-@{int} rw,

          @{sys}/devices/system/cpu/cpufreq/policy@{int}/scaling_cur_freq r,
          @{sys}/devices/system/cpu/cpufreq/policy@{int}/scaling_max_freq r,
    owner @{sys}/fs/cgroup/user.slice/user-@{uid}.slice/user@@{uid}.service/app.slice/app-gnome-code-@{int}.scope/memory.* r,
    owner @{sys}/fs/cgroup/user.slice/user-@{uid}.slice/user@@{uid}.service/app.slice/vte-spawn-@{uuid}.scope/memory.* r,

          @{PROC}/ r,
          @{PROC}/@{pids}/cmdline r,
          @{PROC}/@{pids}/stat r,
          @{PROC}/sys/kernel/pid_max r,
          @{PROC}/tty/drivers r,
          @{PROC}/version_signature r,
    owner @{PROC}/@{pid}/environ r, # This raise the sys_ptrace capability requirement, only allowed on user processes
    owner @{PROC}/@{pid}/loginuid r,
    owner @{PROC}/@{pid}/stat r,

    # Safety mechanisms
    deny @{bin}/poweroff x,
    deny @{bin}/reboot x,
    deny @{bin}/rm x,
    deny @{bin}/rmdir x,
    deny @{bin}/shutdown x,
    deny /etc/dpkg/dpkg.cfg r,
    deny /etc/dpkg/dpkg.cfg.d/{,**} r,
    deny /etc/pacman.conf r,
    deny /etc/pacman.d/{,**} r,
    deny /etc/rpm/{,**} r,
    deny /var/lib/pacman/** r,
    deny /var/lib/rpm/{,**} r,
    deny /home/ r,
    deny owner @{HOME}/ r,
    deny owner @{HOME}/@{XDG_GPG_DIR}/*.conf r,

    # file_inherit
    deny /etc/debuginfod/ r,
    deny owner @{code_config_dirs}/logs/{,**} w,
    deny owner /dev/shm/.org.chromium.Chromium.@{rand6} rw,

    include if exists <local/claude_shell>
  }

  profile git flags=(attach_disconnected,attach_disconnected.path=@{att},complain) {
    include <abstractions/attached/base>
    include <abstractions/app/git>

    signal receive set=term peer=claude,

    unix (send receive) type=stream peer=(label=claude),

    owner @{HOME}/.claude/ r,
    owner @{HOME}/.claude/** rwlk,

    owner @{user_config_dirs}/Claude/** rw,

    owner @{user_projects_dirs}/** rw,

    owner @{tmp}/claude{,-*}/ rw,
    owner @{tmp}/claude{,-*}/** rwlk,

    #aa:lint ignore=too-wide
    owner @{tmp}/** rwlk -> @{tmp}/**,

    include if exists <local/claude_git>
  }

  profile ssh {
    include <abstractions/attached/base>
    include <abstractions/attached/consoles>
    include <abstractions/attached/nameservice-strict>

    @{bin}/ssh mr,

    @{etc_ro}/ssh/ssh_config r,
    @{etc_ro}/ssh/ssh_config.d/{,*} r,

    owner @{user_projects_dirs}/**/ssh/{,*} r,
    owner @{user_projects_dirs}/**/config r,

    owner @{PROC}/@{pid}/fd/ r,

    deny owner @{HOME}/@{XDG_SSH_DIR}/{,**} rwlk,

    # file_inherit
    deny owner /dev/shm/.org.chromium.Chromium.@{rand6} rw,

    include if exists <local/claude_ssh>
  }

  profile flatpak {
    include <abstractions/attached/base>

    unix (send receive) type=stream peer=(label=claude),
    unix (send receive) type=stream peer=(label=claude-desktop),

    # Only runs: `flatpak --installations`
    @{bin}/flatpak mr,

    # file_inherit
    deny network netlink raw,
    deny owner @{HOME}/.claude/** r,
    deny owner @{code_config_dirs}/logs/** w,
    deny owner @{user_projects_dirs}/** r,
    deny owner @{user_config_dirs}/** r,
    deny owner @{code_config_dirs}/logs/{,**} w,
    deny owner /dev/shm/.org.chromium.Chromium.@{rand6} rw,
    deny owner /tmp/claude-@{uid}/*/tasks/* rw,

    include if exists <local/claude_flatpak>
  }

  profile ldd {
    include <abstractions/attached/base>
    include <abstractions/attached/consoles>
    include <abstractions/attached/nameservice-strict>

    unix (send receive) type=stream peer=(label=claude),

    @{ldd_path} mrix,

    @{bin}/*               mr,
    @{sbin}/*              mr,
    @{lib}/**              mr,

    # file_inherit
    deny network netlink raw,
    deny owner @{HOME}/.claude/** r,
    deny owner @{code_config_dirs}/logs/** w,
    deny owner @{user_projects_dirs}/** r,
    deny owner @{user_config_dirs}/** r,
    deny owner @{code_config_dirs}/logs/{,**} w,
    deny owner /dev/shm/.org.chromium.Chromium.@{rand6} rw,

    include if exists <local/claude_ldd>
  }

  include if exists <local/claude>
}

# vim:syntax=apparmor
