# apparmor.d - Full set of apparmor profiles
# Copyright (C) 2026 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 to **will** cause some extension to not work: all extension
#     that require privilege access to the system, like docker, kubernetes,
#     remote ssh, VM, etc... will not work with this profile.
#
# **Architecture**
#
# The `code` profile stack define a "world" with generic rules of what should be
# allowed in an IDE and what is not:
#
# - **Allowed:** compilation, running code, debugging, git, ssh, network access, etc...
#   As long as this takes place in `@{user_projects_dirs}`
# - **Not allowed:** access to hardware, access to users data (outside of projects directories), etc...
#
# We also ensure vscode can start a shell and we **confine** this shell to limit
# it to the same development related tasks. **Therefore, tasks such as installing
# system dependencies will not work** from this shell.
#
# As most development workflow is common to any IDE, this confinement is mostly
# handled by the [development](../abstractions/generic.md#development) and
# [devrun](../abstractions/core.md#devrun) abstractions.
#

abi <abi/4.0>,

include <tunables/global>

@{name} = code{,-oss} vscode{,-oss}{,-shared}
@{config} = Code Code?-?OSS Code?-?Insiders
@{domain} = org.chromium.Chromium
@{lib_dirs} = @{lib}/@{name} /usr/share/@{name}
@{config_dirs} = @{HOME}/.@{name} @{user_config_dirs}/@{config}
@{ext_dirs} = @{config_dirs}/extensions
@{cache_dirs} = @{user_cache_dirs}/@{name}

@{exec_path} = @{bin}/@{name} @{lib_dirs}/@{name}
@{att} = /att/code/
profile code /{{,usr/}bin/code{,-oss},{,usr/}bin/vscode{,-oss}{,-shared},{,usr/}lib{,exec,32,64}/code{,-oss}/code{,-oss},{,usr/}lib{,exec,32,64}/code{,-oss}/vscode{,-oss}{,-shared},{,usr/}lib{,exec,32,64}/vscode{,-oss}{,-shared}/code{,-oss},{,usr/}lib{,exec,32,64}/vscode{,-oss}{,-shared}/vscode{,-oss}{,-shared},usr/share/code{,-oss}/code{,-oss},usr/share/code{,-oss}/vscode{,-oss}{,-shared},usr/share/vscode{,-oss}{,-shared}/code{,-oss},usr/share/vscode{,-oss}{,-shared}/vscode{,-oss}{,-shared}} flags=(attach_disconnected,attach_disconnected.path=@{att},mediate_deleted) {
  include <abstractions/attached/base>
  include <abstractions/audio-client>
  include <abstractions/cgroup-limits>
  include <abstractions/common/electron>
  include <abstractions/attached/consoles>
  include <abstractions/devtools>
  include <abstractions/ibus-strict>
  include <abstractions/login-observe>
  include <abstractions/path>
  include <abstractions/python>
  include <abstractions/screen-inhibit>
  include <abstractions/secrets-service>
  include <abstractions/trash-strict>

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

  ptrace read,

  unix (send receive) type=stream peer=(label=code-*),
  unix (send receive) type=stream peer=(label=git),
  unix (send receive) type=stream peer=(label=gitstatusd),
  unix (send receive) type=stream peer=(label=nproc),
  unix (send receive) type=stream peer=(label=ps),
  unix type=seqpacket peer=(label=code-shells),

  signal send peer=claude,
  signal send peer=code-*,
  signal send peer=git,
  signal send peer=gitstatusd,

  @{exec_path} mrix,
  @{PROC}/self/exe mrix,

  @{bin}/rg      rix,
  @{bin}/env      mr,
  @{ldd_path}   mrix,
  @{lib_dirs}/**  mr,
  @{sh_path}       r,

  #aa:lint ignore=too-wide
  # Extensions
  priority=-10 /**                                  px -> code-extensions,

  @{bin}/git                                        px,
  @{open_path}                                      cx -> open,

             @{lib_dirs}/{,resources/}app/node_modules/** ix,
  priority=1 @{lib_dirs}/{,resources/}app/node_modules/**/vsce-sign rcx -> sign,

  owner @{ext_dirs}/*/**                            mr,
  owner @{ext_dirs}/anthropic.claude-code-*/**      px -> claude,
  owner @{ext_dirs}/valentjn.vscode-ltex-*/**       px -> code-extension-ltex,

  # Terminal
  # Some extension may need to run shells command directly. These command would also
  # run trhough this shell profile, and may be limited by it. It is a feature.
  @{shells_path}                                    px -> code-shells,

  /opt/ r,

  /etc/shells r,
  /etc/lsb-release r,

  owner @{HOME}/ r,
  owner @{HOME}/.claude/{,**} rw,
  owner @{HOME}/.copilot/{,**} rw,
  owner @{HOME}/@{XDG_SSH_DIR}/config r,

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

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

  owner @{user_cache_dirs}/Microsoft/ rw,
  owner @{user_cache_dirs}/Microsoft/** rwlk,
  owner @{user_cache_dirs}/typescript/ rw,
  owner @{user_cache_dirs}/typescript/** rwlk,

  owner @{run}/user/@{uid}/@{name}-*.sock rw,
  owner @{run}/user/@{uid}/git-graph-askpass-@{rand32}.sock w,

  /var/tmp/ r,

  owner @{tmp}/@{name}-*/ rw,
  owner @{tmp}/@{name}-*/** rwlk,
  owner @{tmp}/@{user}-@{name}-*/{,**} rw,
  owner @{tmp}/exthost-@{hex6}.cpuprofile w,
  owner @{tmp}/mcp-@{rand6}/{,**} rw,
  owner @{tmp}/node-compile-cache/{,**} rw,
  owner @{tmp}/ovsx-@{rand6}/{,**} rw,
  owner @{tmp}/tmp-@{int}-@{rand12}/ w,

        @{PROC}/loadavg r,
        @{PROC}/sys/kernel/osrelease r,
  owner @{PROC}/@{pid}/clear_refs w,
  owner @{PROC}/@{pid}/comm w,

  /dev/ptmx rw,

  deny dbus send bus=system path=/
                 interface=org.freedesktop.DBus.ObjectManager
                 member=GetManagedObjects
                 peer=(label=bluetoothd),

  profile sign {
    include <abstractions/attached/base>
    include <abstractions/cgroup-limits>
    include <abstractions/attached/nameservice-strict>
    include <abstractions/ssl_certs>

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

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

    @{lib_dirs}/**  mr,

    owner @{HOME}/.dotnet/corefx/cryptography/{,**} rw,

    owner @{config_dirs}/CachedExtensionVSIXs/* rk,

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

    owner @{tmp}/.@{domain}.@{rand6} rw,
    owner @{tmp}/{,c}dotnet-diagnostic-*-socket rw,
    owner @{tmp}/clr-debug-pipe-* rw,

    @{sys}/devices/system/cpu/cpu@{int}/cache/index@{int}/level r,
    @{sys}/devices/system/cpu/cpu@{int}/cache/index@{int}/size r,
    @{sys}/devices/system/node/ r,

    owner @{PROC}/@{pid}/mountinfo r,
    owner @{PROC}/@{pid}/stat r,
    owner @{PROC}/@{pid}/task/@{tid}/comm rw,

    include if exists <local/code_sign>
  }

  profile open {
    include <abstractions/attached/base>
    include <abstractions/app/open>
    include <abstractions/trash-strict>

    @{browsers_path}              px,
    @{file_explorers_path}        px,
    @{bin}/snap                   px,
    @{bin}/flatpak                px,

    @{lib}/@{multiarch}/glib-@{version}/gio-launch-desktop mrix,

    owner @{user_projects_dirs}/** rw,

    include <abstractions/bus/session/org.gtk.vfs.Metadata>
    owner @{user_share_dirs}/gvfs-metadata/* r,

    include if exists <local/code_open>
  }

  include if exists <local/code>
}

# vim:syntax=apparmor
