When should agent binary updates lose their trust?
Agent binary updates need a fresh trust decision. Learn how to verify macOS signing, publisher provenance, process context, and access.

An approval for a local agent should apply to the executable you examined, in the process you approved, for the access it requested at that moment. When the executable changes, the old approval has expired in every practical sense, even if its filename, bundle identifier, and icon remain unchanged.
This sounds strict until you have investigated an approval that was granted to version 1.8 and quietly exercised by version 1.9. Most bad decisions begin with an overly broad identity check: a path, a product name, or a familiar signing Team ID. Those checks can contribute evidence. None should carry privileged trust across a code change by themselves.
Trust belongs to an observed executable
A local agent is not a publisher identity. It is a specific signed program, loaded at a particular path, with a particular parent process, arguments, environment, and set of requested actions. A publisher may release many programs. One program may change substantially between releases. A stable name says almost nothing about either fact.
This distinction matters most when the agent can call paid APIs, use SSH credentials, modify a repository, or send data outside the machine. An approval is not a compliment to the vendor. It is permission for a process to perform actions with consequences.
Keep these identities separate:
- The file identity is the signed code and its digest on disk.
- The signing identity is the authority macOS reports for that code.
- The publisher provenance is the evidence that the file came through the release path you expected.
- The runtime identity includes the process that launched it and the access it asks to use.
Teams often collapse all four into "this is our agent." That is how a replacement at the same pathname inherits access it never earned. The safe rule is simple: an executable replacement starts a new trust decision.
A hash detects replacement but does not describe who signed the replacement. A signature identifies a signer but does not prove the download channel. A clean download does not tell you whether the new version needs broader access. You need all three checks because each catches a different failure.
Do not make the opposite mistake and pin every byte forever. Builds legitimately change, certificates rotate, and releases need updates. The point is not permanence. The point is that somebody sees the change, establishes what changed, and explicitly decides whether the requested authority still makes sense.
Code signing answers a narrower question than most people think
macOS code signing lets the operating system verify that signed code has not changed since the signer produced that signature. For Developer ID distribution, Gatekeeper also evaluates the developer identity and its assessment of the item. This is useful evidence. It is not a complete supply-chain verdict.
Apple's Technical Note TN2206, "macOS Code Signing In Depth," separates code identity from the broader conditions under which the system accepts code. Its discussion of designated requirements is especially relevant here: macOS can use a requirement to recognize a future version as belonging to the same code identity. That continuity helps ordinary app updates. It is too weak as the only condition for an agent that can spend money or use private infrastructure.
A valid signature does not answer these questions:
- Did the publisher intend this exact release to reach your machine?
- Did a compromised publisher release credential sign it?
- Did the new version add a capability that changes its risk?
- Did an installer, updater, or launch script replace one component without changing the part you checked?
Gatekeeper and notarization reduce the chance that macOS runs obviously untrusted software. They do not turn a validly signed agent into an approved holder of your operational authority. Treat operating-system admission and privileged-action approval as separate decisions.
The same applies to a certificate renewal. A renewed certificate may be a normal administrative event. It still changes the evidence you rely on. If the Team ID, identifier, and expected publisher path remain consistent, an operator can approve the update after review. If the signing authority moves to a different organization, stop and find a clear explanation from the publisher. Do not accept a surprise identity shift because the app launches without a warning.
Publisher provenance is separate from the signature
Provenance asks how the binary reached you and whether that route matches the publisher's normal release practice. A signature on a file copied from an unknown chat attachment tells you who signed that copy. It does not explain why you received it there.
For a production agent, record a small release receipt when you install or update it. It can be a text file in the repository that manages the agent, a change record, or an entry in an internal release log. The receipt should capture the version, install source, observed signing authority, Team ID, digest, date of review, and the person who accepted it. If the publisher provides a release digest, record that too.
The release channel deserves a human sanity check. Did the updater originate from the expected application? Did the publisher publish release notes for this version? Does the archive name, package signature, and destination path match the normal installation method? A surprise update delivered through a new channel deserves the same suspicion as a surprise signer.
Do not confuse a public source repository with a release artifact. A repository can show source history while a release pipeline builds a different binary. Conversely, a signed binary can be legitimate even when you cannot reproduce its build. Those are different assurance levels. State which level you have instead of pretending that one proves the other.
Digest comparison helps when the publisher gives you an authenticated checksum. It does not help when you copy the checksum and binary from the same untrusted page. The useful comparison comes from an independent publisher-controlled release record, a trusted package manager record, or a previously established internal source.
Requested access must be reviewed with the update
A binary update can preserve the same signing authority and still deserve less access than the old release had. The reason for review is not only fear of malicious code. New behavior can make an old permission unreasonable.
Ask what external actions the process will perform after the update. An agent that used to read issue metadata may now create pull requests. One that used a disposable test token may now invoke SSH commands against a shared host. A new plugin, a changed configuration format, or a different default command can alter the practical reach of the process without asking the operating system for a new entitlement.
The requested-access review should cover the actual action boundary:
- Which HTTP hosts, account scopes, and credential records will the process use?
- Which SSH destinations and remote commands can it reach?
- Which working directory, repository hooks, arguments, and environment variables launch it?
- Does it now receive input from a different source, such as a pull request comment or build log?
- Can it invoke another local executable that was not part of the prior review?
This is where broad standing approval fails. A decision like "permit this agent" hides the part that matters: permit it to do what, with whose credential, in response to what input?
For agent processes, input provenance deserves equal attention. An update that lets a coding agent act on untrusted issue text can turn ordinary repository access into a path for prompt injection. Code signing does not inspect the instructions the process receives. It only covers the program that interprets them.
Keep approval screens and internal records concrete. Name the process authority, the destination, the credential class, and whether the action changes remote state. An operator cannot make a sound decision from an alert that only says "Agent requests access."
End the old session before the new code can act
The cleanest rule is to bind approval to one running process and end it when that process exits. A replacement binary gets a new process, so it gets a new approval. This avoids the brittle exercise of trying to decide which package update is small enough to inherit a previous grant.
Do not let a process update itself in place and continue using the authorization it obtained before the replacement. Some updaters do exactly this: the old process downloads an archive, writes a new executable over the old path, then launches a helper or reexecutes itself. If the authorization layer checks only a pathname or a long-lived client record, the new code continues under the old decision.
A practical decision record can look like this:
process path: /Users/dev/tools/agent/bin/agent
observed digest: 8a4b...e19c
identifier: dev.example.agent
TeamIdentifier: A1B2C3D4E5
parent: interactive shell in approved repository
requested actions: issue API read, test-host SSH command
approval scope: this process only
expires: process exit
Do not treat the digest as a permanent allowlist entry. Record it so you can tell that the next approval concerns different code. When the process restarts after an update, compare the fresh observation with the last one, then show the operator the differences that matter.
A same-version restart can also need review when its launch context changes. A binary launched by an interactive shell in a checked-out repository is not equivalent to the same binary launched unattended by a build job with a larger environment. The executable is one part of the subject. The process context completes it.
Inspect macOS evidence before you approve a replacement
You can inspect a candidate executable with built-in macOS tools before you let it make a privileged call. Run the commands against the executable that will launch, not a similarly named copy in Downloads and not an outer application bundle you assume contains it.
codesign -d -vvv /Users/dev/tools/agent/bin/agent 2>&1
spctl -a -t exec -vv /Users/dev/tools/agent/bin/agent
shasum -a 256 /Users/dev/tools/agent/bin/agent
Typical codesign output includes fields shaped like these:
Identifier=dev.example.agent
Authority=Developer ID Application: Example Publisher (A1B2C3D4E5)
TeamIdentifier=A1B2C3D4E5
CDHash=8a4b9c0d...
spctl reports its assessment and commonly reports an origin for accepted Developer ID software. shasum prints a full SHA-256 digest followed by the pathname. Save the relevant output with the release receipt. The exact wording varies by macOS release, so compare the identity fields and assessment, not whitespace or field order.
For a bundled application, inspect nested code too. A signed outer bundle can contain helpers, frameworks, or command-line tools. If the agent launches a helper directly, inspect that helper directly. The file that requests access is the file whose identity you must bind to the decision.
A successful codesign verification does not mean an executable is notarized or suitable for your use. It means the signature checks according to the command's verification rules. That distinction is worth preserving in incident notes. Otherwise someone later reads "signature valid" as "release reviewed and approved," which is a much larger claim.
A stable path is an easy way to approve the wrong code
Consider a local wrapper at /Users/dev/bin/agent. A developer approves it once because it calls a read-only project API. The wrapper later runs an automatic update, downloads a new helper, and preserves the same path. The approval component recognizes the path and gives the new helper the old grant.
Nothing in this sequence requires a malicious attacker. The release may be genuine. The problem is that the approval record says "path equals allowed path" while the actual decision was made about an older program with narrower behavior.
The failure gets worse when the wrapper itself is a script. Shell scripts often invoke versioned binaries beneath a stable symlink, read configuration from a writable directory, or choose a helper from PATH. A signature check on the wrapper tells you little about the final executable if the wrapper can redirect execution after the check.
Fix the order of operations. Resolve the final executable. Inspect its signature and digest. Capture the parent process and arguments. Then authorize that running process. If a launcher can replace or select another executable after approval, bind the action gateway to the process it observes at connection time and reject a mismatch.
The popular alternative is automatic inheritance for any update signed by the same Team ID. Teams choose it because prompts annoy developers and release certificates usually remain stable. It is wrong for privileged agents because it treats a publisher credential as a blank check for every future behavior. Reduce prompt fatigue with session-scoped approval, narrow access, and clear update notices, not by making updates invisible.
Publisher changes need an explicit migration record
A signing-authority change can be legitimate. Companies acquire products, move to a different Developer ID account, or replace an old distribution process. Treat those events as migrations, not routine updates.
Require an explicit record that states the prior identity, the new identity, the version where the change occurred, and the evidence you used to accept it. Good evidence includes a signed announcement in the publisher's established release channel, matching release notes in the expected repository, and a package obtained through the normal delivery path. One unexplained popup is not evidence.
A changed Team ID should default to deny until someone reviews the migration. A changed certificate under the same Team ID can proceed to a fresh approval after the ordinary provenance and access review. A changed digest with the same certificate also needs a fresh approval because it is new code, even if all other fields match.
Do not grant a broad exception such as "accept all future identities for this app name." That exception turns a one-time migration into a permanent hole. Store the new identity only after the reviewer accepts a particular release. The next change should trigger the same review again.
If your team distributes internal agents, publish the expected signing identity, release digest, and update procedure where operators can find them without relying on the agent itself. An agent cannot credibly attest to its own replacement while that replacement is the object under review.
A small approval protocol beats a giant exception list
You do not need a complicated rules engine to make update decisions predictable. You need a short protocol that applies whenever the executable changes.
- Stop the old process and revoke its active session.
- Resolve the final executable that will connect or act.
- Compare its digest, identifier, signing authority, and Team ID with the prior release receipt.
- Check the expected release channel and record the reason for any identity change.
- Review requested destinations and credentials, then approve the new process session or deny it.
This protocol separates a normal maintenance update from a real anomaly. A new digest with the same authority and ordinary release provenance is a review event, not an emergency. An unfamiliar authority, an unexpected installer, and a request for a production SSH credential should stop the rollout until someone investigates.
Sallyport's session authorization can make the process boundary visible because it identifies a new agent process before allowing its run, while its per-call approval setting can keep sensitive credentials under a tighter decision. That does not remove the need to inspect an updated agent, but it prevents an old run from quietly lending its approval to a later one.
Keep the record short enough that people will maintain it. The useful evidence is the observed file, its signer, its source, its launch context, and the actions you approved. If a release changes any of those, make the person who owns the risk see the change before the agent acts.
Audit records must distinguish the actor from the publisher
An audit trail that says only "agent used credential" makes post-incident review unnecessarily hard. Record which process made the request, which signing authority macOS reported, which session authorized it, and which action actually happened. The publisher identity explains who signed the code. The process record explains who acted.
This distinction matters when an agent run goes bad. You need to determine whether a new release changed behavior, whether an old binary ran from an unexpected location, whether a person approved a different access request than they thought, or whether untrusted input steered an otherwise expected process. A publisher field alone cannot answer those questions.
Keep integrity separate from readability. A tamper-evident record can establish whether prior entries changed. It cannot create missing facts after the event. Capture the executable digest and decision context at approval time, when you can still observe them.
Sallyport projects its Sessions and Activity journals from one encrypted, hash-chained audit log, and sp audit verify can check that chain offline over ciphertext. Use that sort of evidence to verify the record, then pair it with a release receipt that explains why the executable received access in the first place.
The first operational change I would make is to remove any approval rule that matches only a path, display name, or publisher. Replace it with approval for the current process, expire it at process exit, and require a fresh decision after a binary replacement. That one boundary catches the update path that broad allowlists routinely miss.
FAQ
Is the same Team ID enough to trust an updated agent?
No. A stable Team ID or developer certificate says that the signer remains associated with the new file. It does not say that the new code deserves the old process's access, or that the release arrived through the expected publisher channel.
Should an agent need approval again after every update?
Treat the new executable as a new subject. End the old process session, inspect the new signature and provenance, then ask for a fresh approval before it can use credentials or act externally.
What does macOS code signing actually prove?
A code signature binds a signer identity to the contents of a file and lets macOS detect modification after signing. It does not prove that the publisher's release account was not compromised, that the installer was obtained from the right place, or that the program's requested access remains appropriate.
What is a CDHash and should I store it?
CDHash identifies the signed code directory used by macOS and changes when relevant signed code changes. It is useful for detecting that an executable differs from an approved build, but it is not a substitute for checking the signing authority and the release source.
What should I do if an agent update has a different signing certificate?
Do not silently carry that approval over. A change in signing authority needs an explicit review, and a change in Team ID should usually block execution until someone confirms a documented publisher transfer or a deliberate migration.
When should I verify an agent binary on macOS?
Inspect the executable after it lands on disk, before the first privileged action. The launch path matters because an updater, archive tool, or replacement script can put a different file at the same location later.
How do I verify the provenance of a local agent executable?
Check the publisher's normal release channel, the signed installer or archive, any published digest, and the version notes that explain the change. If you cannot establish where the file came from, a valid signature alone is a weak reason to give it access to production credentials.
Can a session approval survive an in-place self-update?
It should expire when the process exits and it should not move to a replacement binary. A running process that changes itself needs special scrutiny because the identity that received approval no longer matches the code that will make later calls.
Is it safe to allowlist an agent by file path?
An allowlist can reduce prompts, but it fails if it matches only a pathname, bundle name, or Team ID. Match a specific observed binary for the current run, then require a new decision when its hash, signing requirement, launch context, or requested access changes.
How does Sallyport handle trust after an agent update?
Yes, if the system keeps the secret outside the agent process and binds approval to the current process. Sallyport does this through a vault gate and session authorization, but the operator still needs to treat a newly changed executable as a new run worth reviewing.