Skip to content

macOS: release binaries are ad-hoc signed, so keychain "Always Allow" never persists (re: #247) #542

@montmanu

Description

@montmanu

Summary

The published macOS binaries are ad-hoc (linker) signed, with no Developer ID and
no Team ID. macOS keychain access control binds "Always Allow" to a binary's code
signature, and an ad-hoc signature gives the binary no stable identity to match
against. So after pup auth login, the keychain keeps prompting on later runs even
after you click "Always Allow", and the grant breaks again after every brew upgrade
when the binary changes.

This is the same symptom as #247 (closed as completed at 0.36.0), which still
reproduces on 0.63.0. The underlying cause looks unaddressed: it isn't a storage-path
problem, it's code signing.

Root cause

Both the installed 0.63.0 and the current 0.64.2 release binary are ad-hoc signed:

$ codesign -dv "$(command -v pup)"
CodeDirectory ... flags=0x20002(adhoc,linker-signed)
Signature=adhoc
TeamIdentifier=not set

$ spctl -a -t install "$(command -v pup)"
... : rejected

macOS records a keychain item's trusted apps by their Designated Requirement, which
comes from a real code signature. Ad-hoc binaries have no stable DR, so:

  • "Always Allow" can't reliably match pup on the next run, and the prompt returns.
  • brew upgrade pup installs a new binary at a new versioned Cellar path, which never
    matches the old ACL entry, so the prompt always comes back after an upgrade.

(The cosign/sigstore signature in the release notes verifies the download's
provenance. It's a separate layer from the Mach-O code signature that Gatekeeper and
keychain ACLs use.)

Steps to reproduce

  1. pup auth login on macOS (Apple Silicon) and complete the browser flow. The CLI
    prints an authorize URL like:
    https://app.us5.datadoghq.com/oauth2/v1/authorize?response_type=code&client_id=<redacted>&redirect_uri=http%3A%2F%2F127.0.0.1%3A8000%2Foauth%2Fcallback&state=<redacted>&scope=apm_read+…+user_access_read&code_challenge=<redacted>&code_challenge_method=S256
  2. At the keychain prompt, enter your login password and click "Always Allow".
  3. Run a command that reads the stored session (e.g. pup traces aggregate …). The
    keychain prompts again.
  4. brew upgrade pup, then run a command. The prompt returns regardless of the earlier
    "Always Allow".

Environment

  • OS: macOS, Apple Silicon (arm64)
  • pup: 0.63.0 (0.64.2 release binary verified to be the same)
  • Install: Homebrew tap datadog-labs/pack
  • Auth: OAuth2

Suggested fix

Sign the macOS release binaries with a Developer ID Application certificate and
notarize them (notarytool, with stapling) in the GoReleaser release pipeline. A stable
signature gives the binary a Designated Requirement the keychain ACL can match, so
"Always Allow" persists across runs and survives upgrades, and it clears the Gatekeeper
rejected result.

Workarounds today (both have downsides)

  • API-key env vars (DD_API_KEY / DD_APP_KEY / DD_SITE) so the keychain isn't
    involved, at the cost of giving up short-lived OAuth tokens for long-lived keys.
  • Re-grant the keychain item's ACL in Keychain Access after each upgrade — manual and
    easy to forget.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions