# Does process replacement session approval survive exec?

A session approval should follow a verified execution identity, not an operating system process identifier. When a process calls `exec`, the kernel may retain its PID while replacing the code, arguments, environment, and often the practical purpose of that process. Letting the old approval survive that replacement gives unreviewed code the authority a person granted to something else.

The rule I would ship is plain: a replacement with a different executable identity requests approval again before it can make credentialed actions. Preserve continuity only for a narrowly verified re-exec of the same executable image. That policy creates an occasional extra prompt in a legitimate workflow. It closes a much uglier gap in every workflow where an agent, wrapper, updater, or compromised dependency can choose what runs next.

## An approval binds an actor, not a PID

A PID identifies a kernel bookkeeping slot, while an approval needs to identify the program that can spend a human decision. Those are different jobs, and treating them as the same object causes trouble as soon as code starts other code.

Sallyport describes per-session authorization as an approval for a new agent process that lasts until that run exits. That is a useful user-facing rule, but the implementation needs a sharper internal definition: the approved run must remain the approved executable, rather than merely retain the same PID.

The difference matters because the secrets stay in the gateway and the agent receives results, not credential material. That design removes the familiar disaster where a child process reads a token from an environment variable. It does not remove the ability of a substituted process to ask the gateway to call an API or run SSH with a stored credential. If that substituted process inherits approval, it can cause the same external harm without ever seeing the secret.

Make the approval subject explicit. It should include a stable executable identity, the process creation event or session nonce, the code-signing authority shown to the person, and enough launch context to explain why the process exists. Use the PID as an audit attribute and a useful troubleshooting handle. Do not use it as the bearer credential.

That distinction also keeps revocation honest. If a user revokes a run, the gateway should deny subsequent calls from that run even if it re-execs itself. If a replacement receives a new approval, it should receive a new session record that the user can revoke independently.

## Exec changes the executable even when the PID stays

`exec` should reset authorization when it loads a different program, because it replaces the program image even though the operating system may preserve the PID. POSIX says that an exec function "replaces the current process image." That wording is short, and it gets the important part right: the continuity is administrative, not proof that the same actor remains.

A shell makes this easy to miss. A developer starts an approved agent, the agent invokes a helper, and the helper calls `exec` to avoid leaving an extra parent process around. Process listings can show the same PID before and after the replacement. If your session lookup says `pid 4127 is approved`, the new helper now has the old agent's authority.

The executable change can also change the risk in ways a PID cannot describe. The replacement may speak to a different host, interpret an untrusted repository file, load extensions, accept data on standard input, or exist solely to relay action requests. None of those facts appear in a process number.

Do not base the decision on whether the new command line looks familiar. Command arguments are useful context, but a program can rewrite them, and a wrapper can make a harmless-looking command launch an unrelated target. Establish executable identity from the image the operating system actually started, then retain the observed path and arguments as evidence for the person reviewing the request.

`posix_spawn` belongs in the same design discussion, but it has a different outcome. It creates a child process rather than replacing the caller's image. The child should have no session approval by default. A fork followed by exec should land in the same place: the new process requests approval for the image that finally runs.

## A new executable needs a fresh approval

A gateway should require a fresh approval before a different executable can make any action through an already authorized session. Define "different" using a verified image identity, not a filename and not a display name.

A practical identity record can contain the platform's immutable executable identifier, a digest of the signed code when available, the signing authority, and the concrete executable path observed at launch. The first two answer whether the image changed. The signer tells the reviewer who took responsibility for the code. The path tells the reviewer where the launch came from. Each field answers a different question, so do not collapse them into a single string.

This is the failure that makes permissive inheritance look attractive until it is too late. An approved coding agent calls a repository helper. The helper sees an environment setting and replaces itself with a locally built utility at the same expected path. The utility does not need to extract an API credential. It asks for an HTTP action that deletes a deployment, changes a billing setting, or posts a release. The gateway sees the old PID and accepts the call. The person approved the coding agent, not the utility that selected itself after approval.

The usual argument for inherited approval is prompt fatigue. That concern is real, but allowing arbitrary replacement does not solve prompt fatigue. It hides a decision from the one person who can judge whether the replacement makes sense. Keep approval cards rare by making the normal agent run stable, then ask again at the moment the actor changes.

The check must happen before credential injection, SSH execution, or any other outbound action. It must also happen before the gateway returns metadata that can help a replacement plan an action. A pending approval is not a partially authorized state.

## Same-image re-exec is the narrow exception

A verified re-exec of the exact same executable image can retain session approval, because it does not introduce a new actor. Programs use self-re-exec for clean restarts, changed file descriptors, or a deliberate handoff after updating their own environment. Forcing a new card in that case adds noise without adding a meaningful decision.

Keep the exception narrow. The gateway should compare the current observed image against the image that received approval. If the identity is identical, it can retain the session nonce and write a continuity event to the audit log. If the gateway cannot establish that identity, it should ask again. Ambiguity is not evidence that the replacement is safe.

Do not turn this into a signer-wide exception. A signer may publish an agent, an installer, a diagnostic tool, and a network utility. Those programs can have very different action authority. The visible signing authority helps the user judge the request, but it should not silently extend approval to every binary that carries the same authority.

Do not turn it into a path-wide exception either. A self-update can replace bytes at a fixed path. A symlink can point somewhere else after approval. A script can keep its filename while its contents change. The identity comparison needs to survive all three cases.

When a legitimate update installs a new agent version, let it ask again. That prompt communicates a real fact: the executable that will act has changed. A user who considers the update routine can approve it in one click. A user who did not expect a change has a chance to stop it.

## Code-signing authority helps people judge, but does not grant scope

Show code-signing authority prominently because it answers a question people actually have: who shipped this executable? Do not mistake that answer for a complete authorization rule.

Apple's code-signing model gives macOS a way to identify signed code and verify its integrity under the applicable trust rules. That is strong evidence for the approval card. It does not say that all code from one authority shares one operational purpose, and it does not tell the gateway whether a parent process selected the executable through an untrusted repository setting.

A good approval card puts the executable name and path first, then the signing authority, parent identity, and the reason for the new request. For an exec replacement, it should state both sides of the change in one sentence: the approved agent replaced itself with this executable. The person should not need to infer the transition by comparing two disconnected prompts.

Signed and unsigned cases deserve the same boundary. An unsigned local developer build can be an expected part of a development workflow, and a signed utility can still be the wrong process to inherit authority. The card should describe the evidence without pretending that a signature converts a new executable into the old one.

Avoid vague labels such as "trusted process." They encourage people to approve a category rather than a concrete request. Name the executable and show the relationship to the already approved process. That gives a reviewer something they can recognize or reject.

## Scripts and launchers expose the weak boundary

A script-driven launch needs two identities: the interpreter that executes and the script whose contents control it. If you inspect only the interpreter, every shell script looks like the same shell. If you inspect only the script, you can miss an interpreter selected through a shebang line or wrapper.

For a shell script, record the interpreter executable identity, the resolved script path, and a digest of the script contents. If the script runs through an approved shell and then that shell execs another binary, the binary replacement still requires a new approval. A script should not become a tunnel through the session boundary.

Launchers create a related problem. An approved launcher may inspect a config file, discover a tool on `PATH`, download a helper, or choose a version directory. It is popular to approve the launcher and treat its selected target as part of the same run. That rule is wrong when the launcher makes the security-relevant choice after the user has approved it.

Use one of two approaches. If the launcher knows its target before its first gateway call, let the card show the final target and approve that execution identity. If the launcher chooses later, let it run without action authority and demand approval when the selected target first asks to act. The second option produces a more honest audit trail.

The same rule applies to runtime plug-ins and embedded interpreters. A native host may remain unchanged while it loads code from a project directory. If that loaded code can formulate gateway requests, the host's image identity alone does not describe the practical actor. The gateway cannot inspect every runtime safely, so the safer default is to limit session approval to a defined agent executable and require a new decision when it yields action control to an external program.

## Children and exec form different delegation cases

A child process should not inherit session approval simply because its parent has it. Process creation introduces a new actor, while exec replaces the current actor. Both cases need a new approval when a different executable will make gateway calls, but they need different audit relationships.

For a child, create a new session candidate with a parent-session reference. Display the parent in the approval card because it provides useful context, not because it grants permission. If the user approves the child, give it its own session nonce and its own revocation handle.

For exec, close or supersede the old executable identity and create a replacement candidate tied to the prior session. If the new image matches the approved image exactly, retain continuity and write that result. If it differs, stop at the gate and wait for a decision. This avoids one oversized session record that contains several unrelated programs.

Do not create a general delegation token for a parent to hand to children or replacements. A token that says "anything I start may act" becomes an easy prize for a compromised agent or a confused wrapper. A parent reference in the journal is enough to tell the story without converting ancestry into authority.

This separation also improves incident review. You can answer whether the parent started a child, whether it replaced itself, and whether a person approved the resulting executable. A flat record of allowed requests cannot answer those questions after a bad action.

## Record the replacement as its own event

An audit log should show an exec replacement as a distinct event, whether the gateway preserved approval or requested it again. Without that event, a reviewer sees actions from one session and may assume one stable executable performed all of them.

A design contract can look like this. It is an example of the information to persist, not a required wire format:

```json
{
  "event": "execution_replaced",
  "session_id": "sess_8f2c",
  "previous_image": {
    "identity": "image:4f19...",
    "path": "/work/agent/bin/agent"
  },
  "current_image": {
    "identity": "image:b66a...",
    "path": "/work/agent/bin/release-helper",
    "signing_authority": "Example Development Team"
  },
  "decision": "approval_required",
  "parent_relation": "exec"
}
```

The event needs the prior and current identities, not merely a statement that exec occurred. It also needs the decision outcome. A later action record should refer to the current session identity so an investigator can connect the action to the approval that permitted it.

Keep this record append-only with the rest of the action history. Sallyport's encrypted, hash-chained audit log and offline `sp audit verify` check are particularly useful here because a replacement event and the actions after it can share one verifiable sequence. The verification result establishes that the stored sequence has not changed; it does not make an overly broad approval policy acceptable in the first place.

When a user revokes a session, record the revocation against the executable identity that held the session. If a replacement has received fresh approval, it should remain separately visible. That detail prevents a revoke control from implying broader coverage than it actually has.

## An approval card needs the before and after facts

A fresh-approval card after exec should explain the replacement in one glance and make the safe choice easy. Generic prompts train people to click through them. A card that names the changed executable gives them a reason to pause only when something has changed.

Lead with the new executable's identity and the action channel it wants to use. Then show that an already approved process initiated it, the old executable name and path, the new path, and the code-signing authority. If the replacement comes from a script or launcher, include that relationship in plain language.

Use a decision model that exposes exactly what the user is approving. Approving the replacement should authorize that executable's run until it exits, subject to the vault gate and any credential marked for per-call approval. It should not retroactively authorize sibling processes, future replacements, or an updated file at the same path.

There is a useful test for the wording: could a tired developer tell the difference between an expected helper and a surprise downloader? If not, the card lacks the facts that matter. More decorative security language will not fix that.

Keep the prompt count low through stable execution, not through broad inheritance. A normal agent that keeps one image should show one session card. A process that changes actors should show a card because it has crossed the exact boundary the session concept exists to enforce.

## Build tests around the evasions people actually use

Test the authorization boundary with replacements that preserve superficial identifiers. Happy-path testing with a stable binary will not find the errors that hand authority to the wrong executable.

Start with these cases:

- An approved binary re-execs an exact copy of itself. The gateway preserves the session and records a continuity event.
- The binary re-execs a different signed executable from the same authority. The gateway denies actions until the person approves the new run.
- A symlink still has the same path string but resolves to a different executable. The gateway requests approval again.
- A shell script changes contents after the session begins. The next script-controlled request does not reuse the earlier decision.
- A launcher starts a child and the child attempts an HTTP or SSH action. The child needs its own session decision.

Then test the ordering. Have the replacement issue an action request immediately after exec. Confirm that the gateway denies it or holds it pending, and confirm that neither credential injection nor SSH execution begins before the approval result arrives. This catches implementations that update their journal after they have already dispatched work.

Test revocation in the same suite. Revoke an approved run, attempt a same-image re-exec, and confirm that the revoked state wins. Approve a new replacement, revoke only the original session, and confirm that the two records behave according to the product's stated controls. A session model earns trust when its edge cases behave like its visible language.

Finally, inspect the audit output as a human would. You should be able to trace one action back through a replacement event to the approval card that covered the executable. If that answer requires correlating PIDs, guessing from timestamps, or trusting a path that may have changed, the model still has a hole.

A process replacement is the moment to be strict. The old code had a decision. The new code needs one of its own.
