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

# Profile for Flatpak. Multiple modes of operation are supported, depending on
# the [security goals](https://apparmor.pujol.io/security/architecture/#targets).
#
# **Security objectives**
#
# There are multiple ways to confine Flatpak (and Flatpak applications):
#
# 1. **`default`** *(wip)*: You trust Flatpak for application sandboxing. Flatpak
#    (and bwrap) are confined as much as possible. However, application separation
#    is left entirely to Flatpak. The apps are confined, but the profile purposely
#    allows everything.
#
#     AppArmor does not restrict anything on the application side.
#
# 2. **`strict`**: You trust Flatpak for application sandboxing, but you would
#    like it to be somewhat stricter in terms of access. You still want most of
#    your applications to run without issue. AppArmor confinement goes a bit
#    further than Flatpak:
#     - System D-Bus communications are more limited than a plain `dbus talk`
#     - Flatpak helpers such as `flatpak-spawn --host` are controlled: apps such
#       as an IDE or htop will work, nothing more.
#
#     Flatpak is confined as in `default` mode, but apps are confined with a
#     generic profile (`fapp`), shared with all apps, that limits access to well
#     known safe activities.
#
#     Enabled with `${WITH_FLATPAKAPPS} == false` *(default)*
#
# 3. **`extreme`** *(wip)*: Flatpak application sandboxing is a good base, but by
#    design (usability) it is limited. You want to restrict it as much as
#    possible, such that:
#     - All D-Bus communications are more limited than a plain `dbus talk`
#     - Flatpak helpers such as `flatpak-spawn --host` are blocked (apps such as
#       an IDE or htop will not work).
#     - The profile supports many more separations than default Flatpak (in terms
#       of devices, shares, sockets and features), such that the use of broad
#       access modes (e.g. `devices/all`) can be extremely limited *`[wip]`*
#     - User files are controlled by AppArmor, not by the application developer *`[planned]`*
#
#     For each app, a dedicated AppArmor profile is created. Separation between
#     Flatpak, bwrap and apps is also cleaner and stricter (no profile stacking
#     is required). This requires `aa-flatpak` to be installed, as well as
#     [a patch for both `bwrap` and `flatpak`](https://github.com/roddhjav/apparmor.d/tree/main/dists/patches).
#
#     Enabled with `${WITH_FLATPAKAPPS} == true`
#
# **All of these security levels are valid (including Flatpak without AppArmor at all).** It is only a tradeoff between
# usability and security.
#
# **Overall architecture of the flatpak profiles:**
# ```
# aa-flatpak                         # Generate the app profiles (`${WITH_FLATPAKAPPS} == true`)
#
# flatpak                            # Manage the applications, the runtimes and the remotes
# ├── fbwrap                         # Create the sandbox, start the app (`${WITH_FLATPAKAPPS} == false`)
# │   ├── fbwrap//&fapp              # Generic profile for all applications
# │   ├── fbwrap//&glycin//loaders   # Image loaders (`&:glycin:loaders` on apparmor >= 5.0)
# │   ├── fbwrap//&xdg-dbus-proxy    # Filter the app access to the session bus (allow any dbus)
# │   └── fbwrap//&ldconfig          # Build the app library cache in the sandbox
# ├── bwrap                          # Create the sandbox, start the app (`${WITH_FLATPAKAPPS} == true`)
# │   ├── flatpak.dbus.<appid>       # Dbus proxy profile for each applications
# │   ├── flatpak.<appid>            # Application specific profile
# │   ├── flatpak.build              # Run a command in a build sandbox (`flatpak build`)
# │   ├── flatpak.icon-validator     # Validate the icons exported by the app
# │   ├── flatpak.install            # Run the install script shipped by the app
# │   ├── flatpak.ldconfig           # Build the app library cache
# │   └── flatpak.trigger            # Run the scripts in /usr/share/flatpak/triggers
# │       ├── &gtk-update-icon-cache # Refresh the icon cache
# │       └── &update-mime-database  # Refresh the mime database
# ├── flatpak//&enter                # `flatpak enter`, not confined on purpose
# ├── flatpak//fusermount            # Mount the revokefs-fuse install cache
# └── flatpak//gpg                   # Check the ostree repository signatures
#
# flatpak-validate-icon              # Validate an icon before it is exported
# flatpak-oci-authenticator          # Authenticate on OCI remotes (`org.flatpak.Authenticator.Oci`)
# flatpak-portal                     # Spawn commands on the host (`org.freedesktop.portal.Flatpak`)
# flatpak-session-helper             # Session services for the sandbox (`org.freedesktop.Flatpak`)
# ├── flatpak-session-helper-app     # App binary run on the host by the helper
# │   └── udevadm                    # Query the host devices
# ├── p11-kit                        # Forward the host PKCS#11 modules to the sandbox
# └── pkexec                         # Ask for privileges through polkit
# flatpak-system-helper              # System wide install (`org.freedesktop.Flatpak.SystemHelper`)
# ├── bwrap                          # Sandbox running the deploy triggers
# │   ├── &gtk-update-icon-cache     # Refresh the icon cache
# │   ├── &update-desktop-database   # Refresh the desktop file database
# │   └── &update-mime-database      # Refresh the mime database
# ├── bwrap_apps                     # Sandbox running the app install script
# └── gpg                            # Check the ostree repository signatures
# ```

abi <abi/4.0>,

include <tunables/global>

@{appid} = @{word}.@{word}.@{word}{,.@{word}}

@{exec_path} = @{bin}/flatpak
@{att} = /att/flatpak/
profile flatpak /{,usr/}bin/flatpak flags=(attach_disconnected,attach_disconnected.path=@{att},mediate_deleted) {
  include <abstractions/attached/base>
  include <abstractions/accessibility>
  include <abstractions/accounts-observe>
  include <abstractions/bus-session>
  include <abstractions/bus-system>
  include <abstractions/bus/session/org.freedesktop.impl.portal.PermissionStore>
  include <abstractions/bus/session/org.gtk.vfs.MountTracker>
  include <abstractions/bus/system/org.freedesktop.locale1>
  include <abstractions/attached/consoles>
  include <abstractions/dconf-write>
  include <abstractions/desktop-files>
  include <abstractions/gschemas>
  include <abstractions/mime>
  include <abstractions/attached/nameservice-strict>
  include <abstractions/ssl_certs>

  capability sys_ptrace,

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

  mount fstype=fuse.revokefs-fuse options=(rw, nosuid, nodev) -> /var/tmp/flatpak-cache-*/*/,

  ptrace read peer=flatpak//bwrap,
  ptrace read peer=gnome-software//bwrap,

  signal send peer=polkit-agent-helper,

  unix (send receive) type=seqpacket peer=(label=flatpak-system-helper),
  unix (send receive) type=seqpacket peer=(label=flatpak.*),
  unix (send receive) type=stream    peer=(label=flatpak.*),

  #aa/dbus talk bus=system name=org.freedesktop.Flatpak.SystemHelper label=flatpak-system-helper
  # Unix: allow connection to the profile
  unix type=stream peer=(label=flatpak-system-helper),
  # org.freedesktop.Flatpak.SystemHelper: send and receive anything to the interface on the specific peer label
  dbus (send receive) bus=system path=/org/freedesktop/Flatpak/SystemHelper{,/**}
       interface=org.freedesktop.Flatpak.SystemHelper{,.*}
       peer=(name="{@{busname},org.freedesktop.Flatpak.SystemHelper{,.*},org.freedesktop.DBus}", label=flatpak-system-helper),
  dbus send bus=system path=/org/freedesktop/Flatpak/SystemHelper{,/**}
       interface=org.freedesktop.Flatpak.SystemHelper{,.*}
       peer=(name="org.freedesktop.Flatpak.SystemHelper{,.*}"),
  # DBus.Properties: read and send properties
  dbus (send receive) bus=system path=/org/freedesktop/Flatpak/SystemHelper{,/**}
       interface=org.freedesktop.DBus.Properties
       member={Get,GetAll,Set,PropertiesChanged}
       peer=(name="{@{busname},org.freedesktop.Flatpak.SystemHelper{,.*},org.freedesktop.DBus}", label=flatpak-system-helper),
  # DBus.Introspectable: allow service introspection
  dbus send bus=system path=/org/freedesktop/Flatpak/SystemHelper{,/**}
       interface=org.freedesktop.DBus.Introspectable
       member=Introspect
       peer=(name="{@{busname},org.freedesktop.Flatpak.SystemHelper{,.*},org.freedesktop.DBus}", label=flatpak-system-helper),
  # DBus.ObjectManager: allow clients to enumerate sources
  dbus send bus=system path=/org/freedesktop/Flatpak/SystemHelper{,/**}
       interface=org.freedesktop.DBus.ObjectManager
       member=GetManagedObjects
       peer=(name="{@{busname},org.freedesktop.Flatpak.SystemHelper{,.*},org.freedesktop.DBus}", label=flatpak-system-helper),
  dbus receive bus=system path=/org/freedesktop/Flatpak/SystemHelper{,/**}
       interface=org.freedesktop.DBus.ObjectManager
       member={InterfacesAdded,InterfacesRemoved}
       peer=(name="{@{busname},org.freedesktop.Flatpak.SystemHelper{,.*},org.freedesktop.DBus}", label=flatpak-system-helper),

  #aa/dbus talk bus=system name=org.freedesktop.PolicyKit1 label="@{p_polkitd}"
  # Unix: allow connection to the profile
  unix type=stream peer=(label="@{p_polkitd}"),
  # org.freedesktop.PolicyKit1: send and receive anything to the interface on the specific peer label
  dbus (send receive) bus=system path=/org/freedesktop/PolicyKit1{,/**}
       interface=org.freedesktop.PolicyKit1{,.*}
       peer=(name="{@{busname},org.freedesktop.PolicyKit1{,.*},org.freedesktop.DBus}", label="@{p_polkitd}"),
  dbus send bus=system path=/org/freedesktop/PolicyKit1{,/**}
       interface=org.freedesktop.PolicyKit1{,.*}
       peer=(name="org.freedesktop.PolicyKit1{,.*}"),
  # DBus.Properties: read and send properties
  dbus (send receive) bus=system path=/org/freedesktop/PolicyKit1{,/**}
       interface=org.freedesktop.DBus.Properties
       member={Get,GetAll,Set,PropertiesChanged}
       peer=(name="{@{busname},org.freedesktop.PolicyKit1{,.*},org.freedesktop.DBus}", label="@{p_polkitd}"),
  # DBus.Introspectable: allow service introspection
  dbus send bus=system path=/org/freedesktop/PolicyKit1{,/**}
       interface=org.freedesktop.DBus.Introspectable
       member=Introspect
       peer=(name="{@{busname},org.freedesktop.PolicyKit1{,.*},org.freedesktop.DBus}", label="@{p_polkitd}"),
  # DBus.ObjectManager: allow clients to enumerate sources
  dbus send bus=system path=/org/freedesktop/PolicyKit1{,/**}
       interface=org.freedesktop.DBus.ObjectManager
       member=GetManagedObjects
       peer=(name="{@{busname},org.freedesktop.PolicyKit1{,.*},org.freedesktop.DBus}", label="@{p_polkitd}"),
  dbus receive bus=system path=/org/freedesktop/PolicyKit1{,/**}
       interface=org.freedesktop.DBus.ObjectManager
       member={InterfacesAdded,InterfacesRemoved}
       peer=(name="{@{busname},org.freedesktop.PolicyKit1{,.*},org.freedesktop.DBus}", label="@{p_polkitd}"),


  #aa/dbus talk bus=session name=org.freedesktop.Flatpak.SessionHelper label=flatpak-session-helper
  # Unix: allow connection to the profile
  unix type=stream peer=(label=flatpak-session-helper),
  # org.freedesktop.Flatpak.SessionHelper: send and receive anything to the interface on the specific peer label
  dbus (send receive) bus=session path=/org/freedesktop/Flatpak/SessionHelper{,/**}
       interface=org.freedesktop.Flatpak.SessionHelper{,.*}
       peer=(name="{@{busname},org.freedesktop.Flatpak.SessionHelper{,.*},org.freedesktop.DBus}", label=flatpak-session-helper),
  dbus send bus=session path=/org/freedesktop/Flatpak/SessionHelper{,/**}
       interface=org.freedesktop.Flatpak.SessionHelper{,.*}
       peer=(name="org.freedesktop.Flatpak.SessionHelper{,.*}"),
  # DBus.Properties: read and send properties
  dbus (send receive) bus=session path=/org/freedesktop/Flatpak/SessionHelper{,/**}
       interface=org.freedesktop.DBus.Properties
       member={Get,GetAll,Set,PropertiesChanged}
       peer=(name="{@{busname},org.freedesktop.Flatpak.SessionHelper{,.*},org.freedesktop.DBus}", label=flatpak-session-helper),
  # DBus.Introspectable: allow service introspection
  dbus send bus=session path=/org/freedesktop/Flatpak/SessionHelper{,/**}
       interface=org.freedesktop.DBus.Introspectable
       member=Introspect
       peer=(name="{@{busname},org.freedesktop.Flatpak.SessionHelper{,.*},org.freedesktop.DBus}", label=flatpak-session-helper),
  # DBus.ObjectManager: allow clients to enumerate sources
  dbus send bus=session path=/org/freedesktop/Flatpak/SessionHelper{,/**}
       interface=org.freedesktop.DBus.ObjectManager
       member=GetManagedObjects
       peer=(name="{@{busname},org.freedesktop.Flatpak.SessionHelper{,.*},org.freedesktop.DBus}", label=flatpak-session-helper),
  dbus receive bus=session path=/org/freedesktop/Flatpak/SessionHelper{,/**}
       interface=org.freedesktop.DBus.ObjectManager
       member={InterfacesAdded,InterfacesRemoved}
       peer=(name="{@{busname},org.freedesktop.Flatpak.SessionHelper{,.*},org.freedesktop.DBus}", label=flatpak-session-helper),

  #aa/dbus talk bus=session name=org.freedesktop.portal.Documents path=/org/freedesktop/portal/documents label=xdg-document-portal
  # Unix: allow connection to the profile
  unix type=stream peer=(label=xdg-document-portal),
  # org.freedesktop.portal.Documents: send and receive anything to the interface on the specific peer label
  dbus (send receive) bus=session path=/org/freedesktop/portal/documents
       interface=org.freedesktop.portal.Documents{,.*}
       peer=(name="{@{busname},org.freedesktop.portal.Documents{,.*},org.freedesktop.DBus}", label=xdg-document-portal),
  dbus send bus=session path=/org/freedesktop/portal/documents
       interface=org.freedesktop.portal.Documents{,.*}
       peer=(name="org.freedesktop.portal.Documents{,.*}"),
  # DBus.Properties: read and send properties
  dbus (send receive) bus=session path=/org/freedesktop/portal/documents
       interface=org.freedesktop.DBus.Properties
       member={Get,GetAll,Set,PropertiesChanged}
       peer=(name="{@{busname},org.freedesktop.portal.Documents{,.*},org.freedesktop.DBus}", label=xdg-document-portal),
  # DBus.Introspectable: allow service introspection
  dbus send bus=session path=/org/freedesktop/portal/documents
       interface=org.freedesktop.DBus.Introspectable
       member=Introspect
       peer=(name="{@{busname},org.freedesktop.portal.Documents{,.*},org.freedesktop.DBus}", label=xdg-document-portal),
  # DBus.ObjectManager: allow clients to enumerate sources
  dbus send bus=session path=/org/freedesktop/portal/documents
       interface=org.freedesktop.DBus.ObjectManager
       member=GetManagedObjects
       peer=(name="{@{busname},org.freedesktop.portal.Documents{,.*},org.freedesktop.DBus}", label=xdg-document-portal),
  dbus receive bus=session path=/org/freedesktop/portal/documents
       interface=org.freedesktop.DBus.ObjectManager
       member={InterfacesAdded,InterfacesRemoved}
       peer=(name="{@{busname},org.freedesktop.portal.Documents{,.*},org.freedesktop.DBus}", label=xdg-document-portal),


  dbus send bus=system path=/org/freedesktop/Flatpak/SystemHelper
       interface=org.freedesktop.Flatpak.SystemHelper
       member=GetRevokefsFd
       peer=(name=org.freedesktop.Flatpak.SystemHelper),

  dbus send bus=session path=/org/freedesktop/DBus
       interface=org.freedesktop.DBus
       member=ReloadConfig
       peer=(name=org.freedesktop.DBus, label="@{p_systemd_user}"),

  @{exec_path} mr,

    # Default flatpak: bwrap stacks a default profile for all apps.
    @{bin}/bwrap                px -> fbwrap,


  @{bin}/fusermount{,3}         cx -> fusermount,
  @{bin}/gpg                    cx -> gpg,
  @{bin}/gpgconf                cx -> gpg,
  @{bin}/gpgsm                  cx -> gpg,
  @{lib}/revokefs-fuse         rix,
  @{lib}/flatpak-validate-icon  px,

  # For flatpack enter, the shell is not confined on purpose.
  @{bin}/@{shells}              cx -> enter,

  @{lib}/polkit-[0-9]/polkit-agent-helper-[0-9]  px,
  @{lib}/polkit-agent-helper-[0-9]               px,

  /usr/share/flatpak/{,**} r,

  /etc/flatpak/{,**} r,
  /etc/pulse/client.conf r,

        / r,
  @{att}/ r,

  /var/lib/flatpak/{,**} rwlk,

        /var/tmp/#@{int} rw,
        /var/tmp/flatpak-cache-@{rand6}/{,**/} r,
  owner /var/tmp/flatpak-cache-@{rand6}/ rw,
  owner /var/tmp/flatpak-cache-@{rand6}/** rwlk -> /var/tmp/flatpak-cache-@{rand6}/**,

  owner @{HOME}/.var/ w,
  owner @{HOME}/.var/app/{,**} rw,
  owner @{att}@{HOME}/.var/app/@{appid}/.local/share/**/var/tmp-@{rand6}/{,**} r,
  owner @{att}@{HOME}/.var/app/@{appid}/.local/share/*/**/usr/.ref rw,
  owner @{att}@{HOME}/.var/app/@{appid}/.local/share/*/logs/* rw,

  # Can create dotfile directories for any app
  owner @{user_cache_dirs}/*/ w,
  owner @{user_config_dirs}/*/ w,
  owner @{user_share_dirs}/*/ w,
  owner @{user_games_dirs}/{,**/} w,
  owner @{user_documents_dirs}/ w,

        @{user_config_dirs}/dconf/user r,
  owner @{user_cache_dirs}/flatpak/{,**} rw,
  owner @{user_config_dirs}/pulse/client.conf r,
  owner @{user_config_dirs}/user-dirs.dirs r,

        @{user_share_dirs}/flatpak/{,**} r,
  owner @{user_share_dirs}/ r,
  owner @{user_share_dirs}/flatpak/ rw,
  owner @{user_share_dirs}/flatpak/** rwlk,

  owner @{tmp}/#@{int} rw,
  owner @{tmp}/ostree-gpg-@{rand6}/{,**} rw,
  owner @{tmp}/remote-summary-sig.@{rand6} rw,
  owner @{tmp}/remote-summary.@{rand6} rw,
  owner /dev/shm/flatpak*/{,**} rw,

  @{att}@{run}/.userns r,

  @{run}/polkit/agent-helper.socket rw,

        @{run}/user/@{uid}/.dbus-proxy/ w,
        @{run}/user/@{uid}/dconf/user rw,
  owner @{run}/user/@{uid}/.dbus-proxy/* rw,
  owner @{run}/user/@{uid}/.flatpak-cache rw,
  owner @{run}/user/@{uid}/.flatpak/ rw,
  owner @{run}/user/@{uid}/.flatpak/** rwlk -> @{run}/user/@{uid}/.flatpak/**,
  owner @{run}/user/@{uid}/.mutter-Xwaylandauth.@{rand6} r,
  owner @{run}/user/@{uid}/app/ w,
  owner @{run}/user/@{uid}/app/*/ w,
  owner @{run}/user/@{uid}/systemd/private rw,
  owner @{run}/user/@{uid}/wayland-@{int} rw,

  @{sys}/module/nvidia/version r,

        @{PROC}/modules r,
        @{PROC}/sys/fs/pipe-max-size r,
  owner @{PROC}/@{pid}/cgroup r,
  owner @{PROC}/@{pid}/fdinfo/@{int} r,
  owner @{PROC}/@{pid}/stat r,

  /dev/fuse rw,
  /dev/tty rw,
  /dev/tty@{u8} rw,

  profile bwrap flags=(attach_disconnected,attach_disconnected.path=@{att},mediate_deleted) {
    include <abstractions/attached/base>
    include <abstractions/bwrap>
    include <abstractions/attached/consoles>

    capability dac_override,
    capability dac_read_search,
    capability sys_resource,

    change_profile -> flatpak.*,

    signal receive peer=flatpak-portal,
    signal receive peer=flatpak,

    unix (send receive) type=seqpacket peer=(label=flatpak.*),
    unix (send receive) type=stream    peer=(label=flatpak.*),

    /app/.ref rk,
    /usr/.ref rk,

    owner /bindfile@{rand6} rw,

    owner @{HOME}/.var/app/@{appid}/ r,
    owner @{HOME}/.var/app/@{appid}/.ld.so/@{hex64} r,
    owner @{att}@{HOME}/.var/app/@{appid}/**/usr/.ref rw,

    owner @{user_cache_dirs}/flatpak/ld.so/@{hex64}.@{rand6} r,

    owner @{run}/user/@{uid}/.flatpak/@{int}/bwrapinfo.json rw,
    owner @{run}/user/@{uid}/.flatpak/@{int}/info r,

          @{run}/parent/app/.ref rk,
          @{run}/parent/usr/.ref rk,
    owner @{run}/flatpak/.flatpak/@{int}/.ref rk,
    owner @{run}/flatpak/per-app-dirs-ref rk,

    owner /tmp/#@{int} rw,

    include if exists <local/flatpak_bwrap>
  }

  profile enter {
    include <abstractions/attached/base>

    # Used for flatpak enter, the shell is not confined on purpose.
    # We also avoid conflicts with hosts programs / profiles.
    all,

    include if exists <local/flatpak_enter>
  }

  profile gpg {
    include <abstractions/attached/base>
    include <abstractions/attached/consoles>

    capability dac_read_search,

    @{bin}/gpg{,2}    mr,
    @{bin}/gpgconf    mr,
    @{bin}/gpgsm      mr,
    @{bin}/gpg-agent  rix,
    @{lib}/gnupg/scdaemon rix,

    @{HOME}/@{XDG_GPG_DIR}/*.conf r,

    owner @{tmp}/ostree-gpg-@{rand6}/ rw,
    owner @{tmp}/ostree-gpg-@{rand6}/** rwlk -> /tmp/ostree-gpg-@{rand6}/**,

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

    include if exists <local/flatpak_gpg>
  }

  profile fusermount {
    include <abstractions/attached/base>
    include <abstractions/app/fusermount>

    capability setuid,

    unix type=seqpacket peer=(label=flatpak-system-helper),
    unix type=stream    peer=(label=flatpak),

    mount fstype=fuse.revokefs-fuse options=(rw, nosuid, nodev) revokefs-fuse -> /var/tmp/flatpak-cache-*/*/,
    umount /var/tmp/flatpak-cache-*/*/,

      include if exists <local/flatpak_fusermount>
  }

  include if exists <local/flatpak>
}

# vim:syntax=apparmor
