diff --git a/cmd/probo-agent/installer/macos/build.sh b/cmd/probo-agent/installer/macos/build.sh
index 1cfb49c9e..2ffa9bf42 100755
--- a/cmd/probo-agent/installer/macos/build.sh
+++ b/cmd/probo-agent/installer/macos/build.sh
@@ -149,6 +149,59 @@ team_id_option() {
printf '"%s"' "${APPLE_TEAM_ID}"
}
+# Build AppIcon.icns from the single committed master PNG
+# (enroll-ui/Resources/icon-original.png), matching auditor-mode's
+# pad-then-resize pipeline. Writes under STAGE; does not touch the
+# source tree.
+generate_app_icon_icns() {
+ local icon_original="$1"
+ local icns_out="$2"
+ local icon_dir padded tmp iconset
+
+ if [ ! -f "${icon_original}" ]; then
+ echo "error: missing app icon source ${icon_original}" >&2
+ exit 1
+ fi
+
+ icon_dir="${STAGE}/app-icon"
+ padded="${icon_dir}/icon-padded.png"
+ tmp="${icon_dir}/icon-padded.tmp.png"
+ iconset="${icon_dir}/AppIcon.iconset"
+ rm -rf "${icon_dir}"
+ mkdir -p "${iconset}"
+
+ # 10% padding on all sides (960 content inside 1200 canvas).
+ sips -z 960 960 "${icon_original}" --out "${tmp}" >/dev/null
+ sips --padToHeightWidth 1200 1200 "${tmp}" --out "${padded}" >/dev/null
+ rm -f "${tmp}"
+
+ # Write through temp names: sips mishandles @2x suffixes in --out paths.
+ sips -z 16 16 "${padded}" --out "${icon_dir}/16.png" >/dev/null
+ sips -z 32 32 "${padded}" --out "${icon_dir}/32.png" >/dev/null
+ sips -z 64 64 "${padded}" --out "${icon_dir}/64.png" >/dev/null
+ sips -z 128 128 "${padded}" --out "${icon_dir}/128.png" >/dev/null
+ sips -z 256 256 "${padded}" --out "${icon_dir}/256.png" >/dev/null
+ sips -z 512 512 "${padded}" --out "${icon_dir}/512.png" >/dev/null
+ sips -z 1024 1024 "${padded}" --out "${icon_dir}/1024.png" >/dev/null
+
+ # Build @2x names via concatenation so the shell never treats @ as a
+ # glob qualifier (and sips is never asked to write those paths).
+ local at2x
+ at2x='@2x.png'
+ cp "${icon_dir}/16.png" "${iconset}/icon_16x16.png"
+ cp "${icon_dir}/32.png" "${iconset}/icon_16x16${at2x}"
+ cp "${icon_dir}/32.png" "${iconset}/icon_32x32.png"
+ cp "${icon_dir}/64.png" "${iconset}/icon_32x32${at2x}"
+ cp "${icon_dir}/128.png" "${iconset}/icon_128x128.png"
+ cp "${icon_dir}/256.png" "${iconset}/icon_128x128${at2x}"
+ cp "${icon_dir}/256.png" "${iconset}/icon_256x256.png"
+ cp "${icon_dir}/512.png" "${iconset}/icon_256x256${at2x}"
+ cp "${icon_dir}/512.png" "${iconset}/icon_512x512.png"
+ cp "${icon_dir}/1024.png" "${iconset}/icon_512x512${at2x}"
+
+ iconutil -c icns "${iconset}" -o "${icns_out}"
+}
+
# Build Probo Agent.app (URL handler + embedded privileged helper) into
# parent_dir. Signs nested Mach-Os then the .app bundle (bottom-up).
build_probo_agent_app() {
@@ -156,9 +209,9 @@ build_probo_agent_app() {
local build_dir render_dir
local helper_info_plist helper_launchd_plist
local helper_binary url_handler_binary bin_dir
- local app_root contents macos launch_services launch_daemons
+ local app_root contents macos resources launch_services launch_daemons
local plist embedded_helper embedded_launchd
- local helper_requirement
+ local helper_requirement app_icon_original app_icon_icns
local -a helper_linker_flags swift_arch_args
build_dir="${STAGE}/enroll-ui-build"
@@ -218,18 +271,24 @@ build_probo_agent_app() {
app_root="${parent_dir}/${APP_NAME}"
contents="${app_root}/Contents"
macos="${contents}/MacOS"
+ resources="${contents}/Resources"
launch_services="${contents}/Library/LaunchServices"
launch_daemons="${contents}/Library/LaunchDaemons"
plist="${contents}/Info.plist"
embedded_helper="${launch_services}/${HELPER_LABEL}"
embedded_launchd="${launch_daemons}/${HELPER_LABEL}.plist"
+ app_icon_original="${ENROLL_UI_DIR}/Resources/icon-original.png"
+ app_icon_icns="${STAGE}/AppIcon.icns"
rm -rf "${app_root}"
- mkdir -p "${macos}" "${launch_services}" "${launch_daemons}"
+ mkdir -p "${macos}" "${resources}" "${launch_services}" "${launch_daemons}"
+
+ generate_app_icon_icns "${app_icon_original}" "${app_icon_icns}"
install -m 0755 "${url_handler_binary}" "${macos}/${URL_HANDLER_NAME}"
install -m 0755 "${helper_binary}" "${embedded_helper}"
install -m 0644 "${helper_launchd_plist}" "${embedded_launchd}"
+ ditto --norsrc --noextattr "${app_icon_icns}" "${resources}/AppIcon.icns"
# Sign helper before writing Info.plist so SMPrivilegedExecutables
# can embed the helper's designated requirement.
diff --git a/cmd/probo-agent/installer/macos/enroll-ui/.gitignore b/cmd/probo-agent/installer/macos/enroll-ui/.gitignore
index 709c71f8c..08a02ed3c 100644
--- a/cmd/probo-agent/installer/macos/enroll-ui/.gitignore
+++ b/cmd/probo-agent/installer/macos/enroll-ui/.gitignore
@@ -4,3 +4,9 @@
# Rendered by build.sh from *.tmpl (do not commit)
Shared/*.generated.swift
+
+# Generated from Resources/icon-original.png (build staging preferred;
+# ignore if produced locally under enroll-ui).
+Resources/AppIcon.icns
+Resources/AppIcon.iconset/
+Resources/icon-padded.png
diff --git a/cmd/probo-agent/installer/macos/enroll-ui/Info.plist.tmpl b/cmd/probo-agent/installer/macos/enroll-ui/Info.plist.tmpl
index 3b11bb923..60a6aeefb 100644
--- a/cmd/probo-agent/installer/macos/enroll-ui/Info.plist.tmpl
+++ b/cmd/probo-agent/installer/macos/enroll-ui/Info.plist.tmpl
@@ -21,6 +21,8 @@
Probo Agent
CFBundleDisplayName
Probo Agent
+ CFBundleIconFile
+ AppIcon
CFBundlePackageType
APPL
CFBundleShortVersionString
diff --git a/cmd/probo-agent/installer/macos/enroll-ui/Resources/icon-original.png b/cmd/probo-agent/installer/macos/enroll-ui/Resources/icon-original.png
new file mode 100644
index 000000000..65615e8f8
Binary files /dev/null and b/cmd/probo-agent/installer/macos/enroll-ui/Resources/icon-original.png differ
diff --git a/contrib/claude/release/probo-agent.md b/contrib/claude/release/probo-agent.md
index ab090f18a..fa451566b 100644
--- a/contrib/claude/release/probo-agent.md
+++ b/contrib/claude/release/probo-agent.md
@@ -64,6 +64,14 @@ then the app bundle, optionally signs the product with
profile; submits use `--keychain-profile` so the secret is not on
`notarytool submit` argv).
+The Finder/Dock icon for `Probo Agent.app` comes from a single master
+PNG, `cmd/probo-agent/installer/macos/enroll-ui/Resources/icon-original.png`
+(Probo square mark). At PKG build time `build.sh` pads/resizes it with
+`sips`, compiles `AppIcon.icns` with `iconutil` under the build stage
+directory, and installs it into `Contents/Resources/`. `Info.plist`
+sets `CFBundleIconFile` to `AppIcon`. Do not commit generated
+`.icns` / iconset PNGs.
+
There is no unsigned PKG path and no osascript elevation fallback.
Local testing of browser enrollment requires a Developer ID–signed
build. CLI enrollment without the app uses `sudo probo-agent install`.
diff --git a/pkg/dnsverify/names.go b/pkg/dnsverify/names.go
index 2ed6da077..e2f25548b 100644
--- a/pkg/dnsverify/names.go
+++ b/pkg/dnsverify/names.go
@@ -51,6 +51,7 @@ func EqualNames(a, b string) bool {
// itself, not its apex.
func CheckNames(hostname string) ([]string, error) {
hostname = strings.ToLower(strings.TrimSpace(hostname))
+
hostname = strings.TrimSuffix(hostname, ".")
if hostname == "" {
return nil, fmt.Errorf("cannot build DNS check names: empty hostname")