8 min read

Code signing for AI agents: approve sessions with evidence

Code signing for AI agents helps teams identify agent processes, review shared Mac approvals, and limit credential use without trusting signatures blindly.

Code signing for AI agents: approve sessions with evidence

An approval prompt that says only "AI agent requests access" asks a human to bless a mystery. On a shared development machine, that is how somebody approves a copied script, a forgotten terminal, or a process started by the wrong person. The useful approval question is narrower: which executable process is asking, who signed it, how did it get here, and what can it do during this run?

Code signing gives you evidence for that decision. It can tie a running program to a signing authority and show whether signed contents have changed. It cannot tell you whether an agent received a malicious prompt, whether a trusted publisher shipped a bad release, or whether the requested production action makes sense. Teams get into trouble when they mistake a signature for a behavioral guarantee.

On a shared Mac, use the signing authority to recognize known agent clients, reject surprises, and make each approval expire with the process that earned it. Pair that with a clear account boundary, scoped credentials, and records that let you reconstruct both the approval and the call that followed.

A signature identifies code, not the intent behind it

A code signature can establish who signed a specific piece of code and whether macOS can still verify the signed content; it does not establish that the code deserves access to your systems. That distinction needs to stay visible in every approval flow.

Apple's code-signing documentation, Technical Note TN2206, describes signatures as a way to validate code and express a designated requirement, which macOS can use to recognize the same code later. The designated requirement matters because it is more exact than a filename. An executable called agent can be copied anywhere and renamed. Its signed identity has stronger continuity if the signing chain and requirement still validate.

That continuity answers a practical question: "Is this the client we agreed to allow?" It does not answer these equally practical questions:

  • Did the agent receive instructions that should never reach production?
  • Did a user start this process deliberately, from the expected project directory?
  • Does the process hold an extension, plugin, or configuration that changes its behavior?
  • Does the requested API call belong to this task?
  • Does the credential behind the call have more access than the task needs?

People often reach for a publisher's name as if it were a verdict. It is not. A signing authority tells you who controlled the signing credential used for that artifact. A large publisher may sign many programs. A small internal team may sign a perfectly appropriate build. Your approval decision should compare the observed identity with an allowlist your team can explain, not with a vague sense that the issuer sounds familiar.

There is also a limit that is easy to miss: signing verifies the code that got signed. It does not automatically cover everything a process reads later. Configuration files, prompt files, environment variables, data in the repository, downloaded extensions, and remote responses can all change what a correctly signed program does. If you approve an agent because its binary looks familiar, you still need controls around its actions.

Read an approval card as an attribution record

An approval card should give the operator enough information to attribute a request to a real process before they approve it. The signing authority belongs at the top because it is harder to fake than a process label, but it must sit beside the process path and session details.

For a process requesting access on a shared machine, I want to see these facts in one place:

  • The executable or app identity and its full local path.
  • The signing authority or designated requirement used to recognize it.
  • The process ID and parent process, so I can see what launched it.
  • The macOS user account that owns it.
  • The action channel and destination, such as an API host or SSH host.

The first three facts catch different failures. A familiar display name with an unexpected path often means a copied binary or a wrapper. A familiar path with an unfamiliar authority often means somebody rebuilt it, replaced it, or pointed a symlink somewhere else. A familiar executable launched by an unexpected parent can mean another automation tool started it instead of the developer who is looking at the prompt.

On a shared Mac, the user account is not decoration. If two engineers share one login, an approval says very little about which person initiated the agent. The machine can still tell you which process made the call, but your team has thrown away a clean human boundary before the approval flow even begins. Separate macOS accounts are cheaper than arguing over a terminal history after an incident.

Do not train people to approve based on a logo, a short command name, or an authority string alone. Train them to recognize a complete expected tuple: approved agent client, expected signing authority, expected local location, their own account, and a task-related destination. A card that leaves out most of that evidence turns a one-click approval into guesswork.

Inspect the executable before you make it an approved identity

You should inspect the exact app or executable your team plans to approve before anyone relies on its signing authority in a live workflow. Do this during setup, record the expected result in your internal runbook, and repeat the check when you intentionally upgrade the client.

On macOS, codesign can display signing details. Its detailed output goes to standard error, so redirect it if you want to save a review artifact:

codesign -dv --verbose=4 /Applications/ApprovedAgent.app 2>&1

The output shape commonly includes fields like these:

Executable=/Applications/ApprovedAgent.app/Contents/MacOS/ApprovedAgent
Identifier=com.example.approved-agent
Format=app bundle with Mach-O thin (arm64)
CodeDirectory v=20500 size=...
Authority=Developer ID Application: Example Developer (ABCDE12345)
Authority=Developer ID Certification Authority
Authority=Apple Root CA
TeamIdentifier=ABCDE12345
Sealed Resources version=2 rules=13 files=...

Keep the Identifier, the leaf Authority, and TeamIdentifier together. The Team ID by itself is a poor approval rule because one organization can sign multiple applications. The identifier by itself is weaker because somebody can create an unsigned program with the same bundle identifier. The chain is what makes the statement useful.

Then verify the signed contents:

codesign --verify --deep --strict --verbose=2 /Applications/ApprovedAgent.app

A successful result is often silent. A failure identifies an altered nested component or another signing problem. --deep asks codesign to recurse through nested code. It is useful as an inspection aid, but do not treat it as proof that every bundled component fits your security policy. Apple documents that deep verification applies verification recursively and can mask the fact that a bundle's internal signing design deserves direct review.

For an app acquired outside your managed software channel, ask Gatekeeper for its assessment too:

spctl --assess --type execute --verbose=4 /Applications/ApprovedAgent.app

spctl and codesign answer related but different questions. codesign verifies signatures against the artifact. spctl asks whether the system's assessment policy accepts it. A passing assessment is a useful provenance signal. It does not mean the app's commands, scripts, or remote behavior are safe for production.

Record the path you reviewed. If a person later approves /Users/alex/bin/agent because its label resembles the reviewed app in /Applications, they have not repeated this check. Path is part of the evidence.

An interpreter signature does not bless the script it runs

A signed terminal, runtime, or shell cannot vouch for an arbitrary script handed to it at launch. This is the gap behind many approvals that sound sensible in conversation but fail in practice.

Consider a developer who starts an agent with a command like this:

/usr/bin/python3 /Users/dev/work/demo/tools/agent_runner.py

The system Python may have a known signature. That fact tells you about the interpreter binary. It says nothing about agent_runner.py, the files it imports from the repository, the .env file it reads, or the instructions passed through standard input. If the approval UI identifies only python3, an attacker needs to change the script or project state, not the interpreter.

The same problem appears with node, shells, editor extensions, and generic automation runners. A broad rule such as "approve processes signed by this runtime publisher" makes it easy to approve a lot of behavior that nobody reviewed. It is popular because it reduces approval friction. It is wrong for any channel that can reach meaningful credentials.

Use one of two models, and say which one your team chose. The stronger model approves a signed, purpose-built agent client whose session process is the program asking for access. The more flexible model allows interpreters but treats every script launch as a distinct session and displays the script path, project directory, arguments, and parent process alongside the interpreter's authority.

You can inspect a running process's basic context with standard tools:

ps -p 4821 -o pid=,ppid=,user=,command=
ps -p 4812 -o pid=,ppid=,user=,command=

The first command might show the agent process and the second its parent. Compare the command line with the developer's work. If the child came from a terminal in the expected project, the evidence fits. If it came from an unattended scheduler, a browser helper, or another agent, stop treating the request as a routine developer approval.

For a script-based client, make the script digest part of the approval record. A simple local check is enough to make changes visible:

shasum -a 256 /Users/dev/work/demo/tools/agent_runner.py

A digest does not make a script trustworthy. It gives your team a concrete answer when somebody asks whether the approved script changed between runs. Store the expected digest only for a reviewed release or controlled project state. Do not build a ritual around copying hashes from chat messages.

Shared machines need account boundaries before approval rules

Separate agents from credentials
Sallyport performs HTTP and SSH actions itself, returning results without exposing credentials to the agent.

A shared development Mac is manageable when each human uses a separate account and each agent run has a clear owner. If everybody uses one login, signing authority cannot repair the missing attribution.

Create separate macOS accounts for each developer and avoid a communal administrator login for daily work. The agent process should run under the person who started it. Their project files, terminal history, environment variables, and approval decisions then have a meaningful owner. Shared repositories do not require shared operating system accounts.

A workable setup also separates sensitive destinations. Give development, staging, and production distinct credential entries and labels that expose intent to the operator. An approval for inventory-staging should not quietly select the production credential because both point at the same API client. If your credential naming hides the environment, the human cannot make a sound choice at the moment it counts.

Physical access still matters. A person at an unlocked shared Mac can start a process under the active account and wait for the owner to click through an approval prompt. Lock the screen when leaving, require a fresh login after sleep, and avoid leaving a privileged terminal running in a common space. These are mundane controls, which is why teams skip them until they are cleaning up an avoidable mess.

Do not solve shared-machine risk by publishing one giant list of approved signing authorities. That list tends to grow until it includes editors, language runtimes, package managers, build tools, and helper applications. By then, the list says only that the machine is used for development. Keep the approved set narrow for agents that can request external actions, and document why every identity belongs there.

Session approval should bind one process, not one person forever

A session approval should authorize one observed agent process for the duration of that process, then expire when it exits. That is a useful compromise between forcing a decision for every harmless request and granting a standing permission that survives the work.

The process boundary matters more than a calendar timeout. An agent that exits and restarts is a new execution context. It may have a different working directory, a modified binary, different extensions, a different parent, or a different person at the keyboard. Requiring a new authorization on restart gives the operator another chance to see those changes.

This is where code signing earns its place. The approval flow can lead with the authority because it helps the operator recognize an expected client, while the session binding prevents that recognition from becoming an indefinite grant. If the agent process exits, the approval should disappear with it. If an operator sees suspicious behavior, they need an immediate way to revoke the session before investigating the code signature in detail.

Keep authorization separate from authentication. Touch ID or a password can prove that a present macOS user approved. It does not identify the process. Code signing can help identify the process. Neither one decides whether the destination and action are appropriate. A good approval card presents all three types of evidence rather than pretending they are interchangeable.

For ordinary repository work, a session decision may cover repeated read operations against a development API. For an operation that changes a deployment configuration, rotates credentials, removes records, or opens an SSH connection to a production host, require a fresh decision on that call. Friction belongs where the consequence changes, not randomly after a timer expires.

Signing authority does not stop the failures people expect it to stop

Approve the signed process
Sallyport leads new-session approval with the requesting process's code-signing authority.

A valid signing chain cannot stop a trusted client from receiving bad input, a compromised developer account from signing malicious code, or an approved process from making an unwise request. Treat these as separate failure paths and put the right control in front of each one.

The first path is the prompt or repository instruction attack. A code assistant reads a malicious comment that tells it to exfiltrate a configuration file through an HTTP request. The executable may be exactly the approved, correctly signed client. The approval needs to show the destination and requested channel, because the signature offers no evidence about the instruction it followed.

The second path is a legitimate update that your team has not reviewed. A publisher can sign a new release under the same authority. If you approve any future release from that authority without inspecting its identity and release source, you have made publisher ownership your entire policy. That may be acceptable for low-risk local tooling. It is not enough for an agent that can use production credentials.

The third path is local process manipulation. A signed app may load a plugin, inherit an environment variable, or run with a parent process that supplies surprising arguments. macOS protections reduce some forms of tampering, but an approval system still needs to show the actual process context. When evidence conflicts, deny the request and inspect the machine. Do not invent a benign explanation because the authority string is familiar.

The fourth path is excess privilege. A correctly identified client can use a credential that has permission to do far more than the task requires. Scope credentials to the API, host, repository, and environment the agent needs. Code signing can tell you which client made use of a credential; it cannot shrink the credential's authority after the fact.

This is why "signed equals safe" is a damaging recommendation. It sounds clean and produces fewer prompts. It also encourages approvals without a destination, action summary, or session boundary. A signing signal helps a human distinguish known code from unknown code. It should never erase the rest of the decision.

Per-call approval belongs on irreversible or high-impact credentials

Require a human decision for every use of a credential when one request can create a consequence that a session approval should not silently permit. The threshold is the action's impact, not whether the agent's executable looks trustworthy.

Use per-call approval for credentials that can write or delete production data, change identities or permissions, create external commitments, release software, or enter a sensitive SSH environment. An operator should see the credential label and destination at the time of use. A generic message such as "use secret" forces them to remember too much under pressure.

Keep low-consequence work usable. A developer who must approve every read of a test API will learn to click without reading. That behavior defeats the control and makes serious prompts easier to miss. Session approval works well when the agent is performing repetitive, bounded work against a development target and the process identity is expected.

The decision ladder should be simple enough that people can explain it after a rough week. First, a locked credential vault denies every action. Next, a new agent process needs session authorization. Finally, selected credentials require approval each time they are used. Do not bury those decisions in a custom policy language that only one person can interpret. Hidden exceptions are where shared-machine rules decay.

Sallyport applies this three-control model directly: a locked vault denies actions, a new agent process requests session authorization by default, and a selected credential can require approval on every use. Its approval card leads with the process code-signing authority, which is the right place to start when several users share a Mac.

Audit the approval and the call as separate events

Make approval expire with runs
A new agent process needs authorization by default, and that approval ends when the run exits.

You need separate records for agent sessions and individual actions because neither record can answer every incident question alone. A session record explains which process received authorization and when that authorization ended. An action record explains what it attempted after approval, against which channel or destination, and what result came back.

A useful investigation sequence looks like this:

  1. Find the session record for the process that requested access.
  2. Check the user account, executable path, signing authority, parent process, and approval time.
  3. Find the calls made during that session and compare destinations with the assigned task.
  4. Revoke the session if it remains active, then disable or rotate the affected credential if the calls show misuse.
  5. Preserve the records before changing project files or reinstalling the client.

The order matters. Teams often start by reading code and lose the evidence of what actually happened. First establish the sequence of authorization and action. Then inspect the executable, repository state, shell history, and relevant configuration.

Tamper evidence has value here. An audit record that a local process can rewrite offers little comfort when that process is part of the event under review. Hash chaining gives a verifier a way to detect alteration or removal within the recorded sequence, although it does not prove that every possible event was captured. Be precise about that limit. Tamper evidence is not omniscience.

Sallyport projects session and activity journals from one encrypted, hash-chained audit log, and sp audit verify checks the chain offline without needing a vault credential. That makes routine verification practical after an incident or before handing records to another reviewer.

Make approval decisions reproducible instead of personal

A team should be able to explain why one agent session earned approval without relying on the memory of the person who clicked it. Write a short approval profile for each allowed agent client and keep it near the repository's operating instructions.

The profile should name the expected app or executable path, identifier, signing authority, normal parent process, intended user accounts, permitted environments, and the credentials that require a decision on every call. This is not bureaucracy for its own sake. It gives a new engineer an observable standard and gives the person on call a way to reject an odd request without debating taste.

Review the profile when any of these change: the agent client updates, the team adopts a new runtime wrapper, a credential gains a stronger permission, or a formerly local workflow reaches a shared service. If the signing identity changes, stop and verify the change through the software source your team trusts. Do not normalize an unexpected authority by clicking once and planning to investigate later.

Run a deliberate failure drill on the shared Mac. Start the approved client from the expected account and confirm the displayed authority and session behavior. Then start an unapproved script through a signed interpreter, launch the client from a different account, and request a credential marked for per-call approval. The correct behavior should be obvious to the operator in each case. If the prompts look too similar, improve the information shown before people face a real mistake.

Code signing is useful because it replaces a vague process name with evidence that can be checked. Keep its job that narrow. Approve the known process for the work in front of you, keep the credential scope small, and make a suspicious session easy to revoke while the evidence is still intact.

FAQ

What does code signing prove for an AI agent process?

It is an identity statement attached to executable code. On macOS, a valid signature can identify the signing authority and detect that signed contents changed after signing. It does not prove that the software is safe, appropriate for your repository, or acting within the scope you intended.

Is an Apple Team ID enough to approve an agent session?

No. A Team ID tells you which Apple developer account signed code, but it is too broad to approve on its own. Check the executable or bundle identity, the signing authority, the process path, and whether that identity matches a tool your team deliberately permits.

Should I approve an unsigned local AI agent?

Treat an unsigned process as an exception that needs a tighter review, not as an automatic safe choice because it runs locally. Confirm who built it, where it came from, what parent launched it, and restrict it to low-consequence work until your team has a repeatable way to identify it.

Can I trust a signed shell process running an agent script?

Usually no. A shell process may be a generic interpreter running a script from an arbitrary directory, and the signature on the shell says very little about that script. Inspect the command arguments, working directory, parent process, and script source before you approve it.

What is the difference between codesign verification and Gatekeeper?

A valid signature means macOS can verify signed code and its signing chain under the conditions of that verification. Gatekeeper assessment adds provenance and policy checks, but neither result evaluates the agent's prompt, its environment variables, installed extensions, or the actions it will request.

When should an agent need approval on every call?

Approve a session once when the process identity matches an approved tool and its requested actions fit the work you are supervising. Require a decision for every use when a credential can change production data, move money, alter access, publish artifacts, or expose sensitive records.

What happens if an approved agent process restarts or is replaced?

A process replacement starts a new process identity and should require a fresh session decision. If your approval mechanism cannot distinguish the replacement from the prior process, revoke the existing session and investigate before allowing more actions.

How can a team safely use one shared development Mac?

Start by separating the workspaces and accounts rather than trying to infer intent from one crowded machine. Give each developer a separate macOS account, use distinct agent processes, and keep credentials scoped so a mistaken approval cannot reach every environment.

Does a valid signature mean the agent is trustworthy?

No. A developer account can sign many unrelated applications, and a signed application can still contain a defect or behave badly after you authorize it. Signing lets you make a more informed attribution decision; it never substitutes for reviewing the requested action.

What should an audit record capture for agent approvals?

Record the time, process identity, signing authority, session outcome, requested destination or host, credential label, and action result. Keep session records separate from individual action records, because the answer to "who did this" often depends on both.

Sallyport

Sallyport runs API calls and SSH commands for your AI agent. The keys stay in a local vault on your Mac; you approve each run and every action lands in a sealed journal.

© 2026 Sallyport · Open source under Apache-2.0 · Oleg Sotnikov