fix(deb): align AppArmor profile lifecycle with Debian Policy 10.7.3

The postinst unconditionally rewrote /etc/apparmor.d/claude-desktop on
every configure, and the postrm deleted it on remove. Policy 10.7.3
requires preserving local changes on upgrade and keeping config files
until purge.

- Write a marker header into the generated profile declaring it
  postinst-managed and pointing edits at local/claude-desktop.
- Only overwrite a profile that carries the marker; a hand-created or
  hand-edited profile (no marker) is preserved and best-effort reloaded.
- postrm keeps the apparmor_parser -R unload on remove (the confined
  binary is gone) but deletes the file only on purge — a profile for an
  absent binary is a harmless no-op, same as google-chrome.
- Clean up a truncated profile when the heredoc write fails:
  rm -f ... || true in the else branch, which is errexit-live; a bare
  rm would fail upgrades on a read-only /etc.

Addresses review findings 2 and 3 on PR #687.

Co-Authored-By: Claude <claude@anthropic.com>
This commit is contained in:
aaddrick
2026-06-09 18:49:17 -04:00
parent 6b02fbbfa6
commit 133ffcee86

View File

@@ -241,8 +241,18 @@ if command -v apparmor_parser >/dev/null 2>&1 \
echo "Configuring AppArmor profile for Chromium sandbox..."
# Writing the profile is best-effort: a read-only or atypical /etc must
# never abort the install (this postinst runs under set -e). Keeping the
# mkdir + heredoc in the if-condition exempts them from errexit.
if mkdir -p /etc/apparmor.d 2>/dev/null && cat > "\$APPARMOR_PROFILE" <<'APPARMOR_EOF'
# grep / mkdir + heredoc in the if/elif conditions exempts them from
# errexit. Debian Policy 10.7.3: a profile without our marker header was
# hand-created or hand-edited by the admin — preserve it, never overwrite.
if [ -e "\$APPARMOR_PROFILE" ] \
&& ! grep -qF "managed by the $package_name package" \
"\$APPARMOR_PROFILE" 2>/dev/null; then
echo "Preserving locally modified \$APPARMOR_PROFILE (no marker header)"
apparmor_parser -r "\$APPARMOR_PROFILE" >/dev/null 2>&1 || true
elif mkdir -p /etc/apparmor.d 2>/dev/null && cat > "\$APPARMOR_PROFILE" <<'APPARMOR_EOF'
# This profile is managed by the $package_name package (postinst); direct
# edits will be overwritten on upgrade. Put local changes in
# /etc/apparmor.d/local/$package_name instead.
abi <abi/4.0>,
include <tunables/global>
@@ -261,6 +271,10 @@ APPARMOR_EOF
echo "AppArmor on this system does not support the userns rule; skipping profile (not required here)."
fi
else
# A failed write may leave a truncated profile behind; clear it.
# The || true is mandatory: this branch is errexit-live, and a bare
# rm fails the upgrade on a read-only /etc.
rm -f "\$APPARMOR_PROFILE" 2>/dev/null || true
echo "Warning: could not write \$APPARMOR_PROFILE; skipping AppArmor profile."
fi
fi
@@ -277,6 +291,10 @@ echo 'Creating postrm script...'
# also fires on purge and abort-install. Skip on upgrade — the incoming
# postinst rewrites and reloads it. 'disappear' is deliberately not handled:
# matching it would also clean during the overwrite-by-another-package flow.
# Per Debian Policy 10.7.3 the profile is configuration: unload it whenever
# the confined binary goes away, but delete the file only on purge — a
# profile for an absent binary is a harmless no-op (google-chrome leaves
# its profile behind the same way).
cat > "$package_root/DEBIAN/postrm" << EOF
#!/bin/sh
set -e
@@ -284,11 +302,13 @@ set -e
case "\$1" in
remove|purge|abort-install)
APPARMOR_PROFILE="/etc/apparmor.d/$package_name"
if [ -e "\$APPARMOR_PROFILE" ]; then
if command -v apparmor_parser >/dev/null 2>&1; then
apparmor_parser -R "\$APPARMOR_PROFILE" >/dev/null 2>&1 || true
fi
rm -f "\$APPARMOR_PROFILE"
if [ -e "\$APPARMOR_PROFILE" ] \
&& command -v apparmor_parser >/dev/null 2>&1; then
apparmor_parser -R "\$APPARMOR_PROFILE" >/dev/null 2>&1 || true
fi
# Policy 10.7.3: config survives remove; delete on purge only.
if [ "\$1" = purge ]; then
rm -f "\$APPARMOR_PROFILE" 2>/dev/null || true
fi
;;
esac