7 min read

Approval notification previews: protect action details

Approval notification previews can expose agent actions on locked screens. Learn what to show, redact, test, and keep inside the authenticated app.

Approval notification previews: protect action details

Approval notification previews deserve the same threat modeling as the action they announce. A request to deploy code, call a customer API, or run a remote command can expose sensitive facts before anyone presses Approve. If that fact appears on a locked phone, a shared desktop, a conference-room display, or a mirrored wearable, the approval system has already disclosed part of the operation.

The usual mistake is treating the notification as harmless plumbing. It is an output channel with its own audience, retention behavior, and access controls. Design it as a deliberately limited prompt to enter a protected decision surface. Do not treat it as a miniature version of the approval screen.

A locked screen is an exposure boundary

A locked screen can be visible to colleagues, family members, visitors, camera systems, and anyone walking past a desk. The owner may be nearby, but nearby is not authenticated. That distinction sounds obvious until an approval alert displays a production hostname, a customer name, an incident label, or the first line of a shell command.

Many teams classify a notification as low sensitivity because it contains no secret value. That test is far too narrow. An endpoint such as billing-prod.internal, a path such as /customers/28471/refund, or a message such as Rotate compromised access token can reveal systems, relationships, and operational state. An attacker who collects those fragments does not need a bearer token to gain an advantage.

Think about what an observer can infer from each field:

  • A destination can identify a customer, region, product, or production service.
  • An operation can disclose that an account is being changed, a refund is pending, or an incident is underway.
  • An agent identity can reveal what repository or task a developer is working on.
  • A reason field often carries copied ticket text, user input, or incident notes.
  • A result can reveal data that the action retrieved before the user made a decision.

The lock screen is only the first exposure boundary. Operating systems may show the same text in a notification center after the device unlocks. A desktop notification can remain in history after a person leaves a shared workstation. A watch can mirror it. Screen recording, remote support software, and a video call can capture it. The preview must survive all of those contexts.

Apple’s Platform Security documentation describes the lock screen as a protected device state and places user authentication at the center of access to protected data. That model does not turn notification text into protected data by itself. Apps must choose what they send to the notification service and what they render before the person authenticates. Treat the operating system’s privacy setting as one layer, not permission to put sensitive content in the message.

The alert should request attention, not reveal the request

A safe preview tells someone that an action requires review and gives enough urgency to prioritize it. It does not reproduce the action. This is a sharp distinction that teams often blur: notification context is not approval context.

Approval context must let an operator make an informed decision. It may need the exact destination, operation, credential scope, agent process, arguments, expected effect, and expiry. Notification context exists to get the right person back to that protected view. It needs far less.

For an ordinary action, a useful locked-screen preview might say:

Action approval needed
A development agent requests access to a protected service.
Expires in 4 minutes. Open the app to review.

That text establishes urgency and broad scope. It does not say whether the service handles payroll, source code, payments, or an internal incident. It does not disclose the URL, method, command, branch, query, or identity of a customer.

Compare it with the kind of message that turns up in real systems:

Approve POST https://payments.example.internal/v1/refunds/28471
Agent requests bearer token for customer refund. Reason: duplicate charge.

The second alert never prints the bearer token, yet it exposes far too much. It identifies a sensitive service, an action, a customer-linked object, and a financial event. Anyone who can read the preview has learned something they were not authorized to learn.

Use a small vocabulary for preview copy. "Approval needed," "access request waiting," and "review required" are often enough. Add a coarse risk label if it changes how fast a person should respond: "external action," "production access," or "sensitive read." Do not make the label so specific that it defeats the purpose. "Production database export" is not a coarse label.

The authenticated screen should do the opposite. It should make the request concrete enough to reject safely or approve knowingly. Hiding detail there in the name of privacy produces blind approval, which is just a different failure.

Redaction must happen before the notification leaves the app

A notification payload needs its own schema. Do not build it by trimming a full approval record at the last moment, and do not depend on a string replacement list. Teams take that shortcut because the full record already exists and rendering it feels easy. It fails when a new field appears, a URL moves into a subtitle, or a reason string contains copied sensitive data.

Create two explicit projections from an action request. One projection feeds the authenticated approval view. The other feeds the preview. The preview model should not even have fields for raw URLs, headers, command arguments, response snippets, secret names, or free-form reasons.

This pseudocode shows the shape:

type ApprovalRecord {
  requestId
  agentAuthority
  destination
  operation
  arguments
  credentialReference
  userReason
  expiry
  riskClass
}

type NotificationPreview {
  requestId
  title
  body
  expiryText
  riskClass
}

function makePreview(record):
  return NotificationPreview(
    requestId = opaqueId(record.requestId),
    title = "Action approval needed",
    body = previewBody(record.riskClass),
    expiryText = formatExpiry(record.expiry),
    riskClass = record.riskClass
  )

The important property is not the wording. It is the one-way shape of the data. NotificationPreview cannot accidentally contain destination because the field is absent. A reviewer can inspect that boundary. A test can reject any new preview field that includes an unbounded string.

Do not pass raw reason text through a sanitizer and call it done. Reasons routinely contain issue titles, pasted commands, email addresses, account identifiers, and internal names. Redaction patterns miss formats that nobody predicted. A fixed phrase drawn from an enum is safer than a cleaned user-controlled sentence.

Opaque identifiers need care too. An approval ID such as APR-10482 may look harmless, but a predictable number gives an observer a record they can correlate with a visible ticket or later conversation. Use an identifier that has no business meaning and cannot act as an authorization token. Better yet, omit it from the preview unless support workflows truly require it.

Keep the complete request in the encrypted application store or another authenticated record, not in the notification body. A notification framework can preserve text longer than the alert itself remains visible. Your data retention choices do not apply if another subsystem holds a copy.

Device privacy settings are helpful but cannot carry the design

Operating systems usually let people hide notification previews when a device is locked. That setting is useful, but an approval product cannot assume it is enabled, understood, or consistently applied across a person’s devices.

Some people need visible previews to triage messages. Some organizations manage settings. Some devices have no lock code. Some users read alerts on a desktop where the screen is already unlocked while a colleague stands behind them. An app that sends sensitive text and says "users can turn previews off" has handed a security decision to the least reliable moment in the system setup.

Build for three conditions:

  1. The operating system shows the full notification on a locked display.
  2. The operating system hides the body but shows a title or app name.
  3. The display is unlocked but other people can see it.

The first condition determines your payload. If the text is safe there, the other two become easier to reason about. If it is unsafe there, a user setting only makes the flaw intermittent.

Do not infer too much from device state. An application may know that its own window is unlocked, but it often cannot know who is looking at the notification. Even a reliable locked-state signal does not address a projector, an external monitor, or screen sharing. The safe preview should remain safe after authentication because physical viewing conditions are outside the app’s control.

There is one exception worth naming: a local, authenticated notification inside an application window can show the same detail as the approval view because the app already controls access to that window. That is not a system notification. Do not confuse a protected in-app inbox with a lock-screen banner simply because both use the word notification.

Approval buttons in notifications weaken the decision boundary

Identify each agent process
The first call from a new agent process shows its code-signing authority before that session runs.

An Approve button in a notification feels efficient. It is also an attractive shortcut for accidental confirmation, coerced approval, and lost context. A person sees a truncated banner, taps a familiar button, and authorizes an action without reviewing its actual target or effect.

The notification should offer only actions that preserve the decision boundary. "Open for review" is safe because it takes the operator to the authenticated app. "Dismiss" is safe if dismissal does not deny, approve, or quietly extend the request. A button that says "Approve" is not safe for meaningful actions, even if the operating system asks for a device unlock before it runs.

Authentication and informed consent are different checks. Device authentication establishes that someone who can unlock the device pressed the button. It does not establish that they saw the complete request or had time to assess it. The approval screen should bind the decision to the request details, show whether anything changed since the alert appeared, and require a fresh confirmation for a sensitive action.

This matters more with agent requests. An agent can produce many actions that look similar from a distance. A notification title such as "SSH access request" does not distinguish a read-only status check from a destructive command. The protected view must show the concrete intent before the operator releases the action.

Sallyport’s authorization flow keeps the actual action decision inside the authenticated app rather than turning an operating-system notification into a remote control for agent access. That separation is less flashy than a one-tap approval, and it holds up better when requests are consequential.

Do not offer a "remember this choice" control in the notification either. Persistent permission changes deserve their own explicit surface, a clear scope, and a way to inspect or revoke them. A sleepy tap on a lock screen is a poor place to create lasting authority.

Risk labels should describe consequences without naming assets

A vague alert trains people to open every notification. An overly detailed one leaks the asset being protected. Risk labels solve part of that tension when they describe the consequence category instead of the resource.

Use categories built around what the action can do. For example, an action may read protected information, modify an internal service, send an external request, or perform an operation that cannot be easily reversed. Those categories give the reviewer a reason to interrupt their work without disclosing the database, customer, or hostname involved.

Avoid labels that mix access sensitivity with operational urgency. "High priority" tells a person little about the effect of approval. "Production write" communicates more, but can still reveal that a production system is involved. Whether that wording is safe depends on the environment. A person working alone on a personal device may accept it; a shared support desk should use a broader label.

Define the vocabulary in writing and attach it to the action before notification rendering. If developers can improvise title text, the most careful schema in the world will not save you. A review rule can be simple: any string that comes from an agent, a user, a URL, a command, a request body, or a remote response is forbidden in previews.

A practical mapping looks like this:

Action propertyPreview wordingProtected view wording
Reads a protected serviceSensitive readExact service, method, path, scope
Changes internal stateInternal changeTarget, fields changed, expected effect
Sends data outside the organizationExternal actionRecipient, payload summary, destination
May be difficult to reverseElevated impactFull command or request and recovery notes

The table is a policy for writers and engineers, not a promise that every action fits neatly into four boxes. When an action has mixed effects, choose the more serious category. A notification that asks for attention too soon costs a moment. A notification that hides an external transfer under "access request" causes a bad decision.

Notification history and mirrored devices need their own review

Keep secrets out of agents
Sallyport injects API and SSH credentials itself, so agents never receive the secrets.

Teams often test the first banner and stop there. The full exposure path includes retained notifications, notification summaries, wearable mirrors, desktop relays, and any managed device that receives the same account alerts.

Start with a real request that contains deliberately recognizable test data: a fake customer name, a fake internal host, a fake email address, and a fake command argument. Do not use actual production values for privacy testing. Trigger the request, then inspect every place the text can surface.

Use this test sequence before a release:

  1. Lock the primary device and trigger the approval request.
  2. Check the banner, lock-screen list, and notification center after unlocking.
  3. Enable any configured notification relay or wearable mirror and inspect its history.
  4. Capture the screen through the remote-support and screen-sharing paths your team uses.
  5. Expire or resolve the request, then check whether stale text remains visible.

Record the exact rendered title and body at each point. A successful test is not "preview hidden on my phone." It is "none of the test markers appeared outside the authenticated app." This catches localization bugs too. A safe English template can become unsafe when a translated string grows and pushes an internal identifier into a visible line.

Pay special attention to grouped notifications. A system may show the most recent message, a count, or a summary assembled from several alerts. If each individual preview is safe, the group should be safe too. If your grouping code uses an action name such as "three refund requests," it has reintroduced sensitive detail through a side path.

Notification persistence changes incident response as well. Revoking an agent session prevents future requests, but it cannot retract text already copied to a user’s notification history or a mirrored device. That is why preview minimization belongs before authorization and audit review, not after an incident.

Audit records need detail, previews need restraint

Revoke a session quickly
Revoke an agent run instantly from the Sessions journal when its authority should end.

Security teams sometimes make previews vague because they also made their audit records vague. They fear that detailed records will leak. That mixes two separate systems with different audiences.

An audit record should be complete enough to reconstruct the authorization decision: which agent process initiated the action, what authority it ran under, the destination, operation, time, decision, and result. Sensitive values still require careful handling, but investigators need factual detail. A preview should contain none of that unless the person has authenticated into the app.

The distinction matters during a failure. Imagine an agent requests a remote command. The alert says "Approval needed" and the reviewer opens the app. The protected view displays the command, host, session identity, and expiry. The reviewer rejects it. Later, an engineer investigates why and needs the corresponding record. The audit trail can answer that question without forcing the original notification to carry the command across every display surface.

Sallyport separates its session and activity journals from notification handling, with both journals projected from an encrypted, hash-chained audit log. That design lets an operator inspect actions and verify the audit chain offline without using lock-screen previews as a substitute for evidence.

Do not put audit hashes, record excerpts, or raw correlation IDs in the notification. They are useful in the protected interface and investigation tooling. On a public surface, they create identifiers that observers can collect and correlate.

A notification should expire when the underlying decision expires, and its protected record should state that expiry plainly. If a person opens an old alert, the app must fetch the current request state. Never let a cached notification body persuade someone that they are approving the same request that existed five minutes ago.

Write preview rules as tests, then try to break them

A prose guideline such as "avoid sensitive content" will fail under delivery pressure. Convert it into assertions that run with the notification builder and into test cases that reviewers can read.

The most useful test is a denylist of data origins, not a brittle denylist of words. Reject any preview field derived from a URL, host, command text, header, body, credential label, free-form reason, remote response, email address, or account identifier. Then allow only a narrow set of fixed templates and bounded category values.

A compact test fixture might look like this:

record.destination = "https://claims-prod.internal/cases/FAKE-784"
record.arguments = "--account [email protected] --export"
record.userReason = "Customer Northstar reports a disputed claim"
record.riskClass = EXTERNAL_ACTION

preview = makePreview(record)

assert preview.title == "Action approval needed"
assert preview.body == "An external action requires review."
assert preview doesNotContain "claims"
assert preview doesNotContain "FAKE-784"
assert preview doesNotContain "fake.person"
assert preview doesNotContain "Northstar"

The positive assertions matter as much as the negative ones. They prevent a later edit from replacing a safe fixed message with a vague blank alert that users learn to dismiss. Test expiry wording, localization, grouped alerts, and accessibility rendering too. Screen readers may read content that visual truncation hides, so accessibility output must use the same constrained preview model.

Finally, have someone who did not write the feature read the previews while looking for operational clues. They will spot what the author normalizes: a project codename, a familiar environment label, an internal service nickname. If an informed outsider can infer the protected action, the notification carries too much.

Set the default preview to the least specific message that still gets a timely human response. Put the evidence needed for a real approval behind authentication, and make every shortcut lead back to that evidence. That design may cost a tap. It avoids turning every locked screen into a quiet disclosure channel.

FAQ

What should an approval notification show on a lock screen?

Treat a lock screen as a public or semi-public surface. Display that an approval needs attention, the risk class, and a short expiry, but keep targets, credentials, request bodies, command arguments, and results inside the authenticated app.

Is it safe to show an API endpoint in a notification preview?

In most cases, no. The resource name can reveal a customer, an internal project, a production environment, or a security incident. Use a neutral resource class outside the app and show the exact destination only after authentication.

Can approval alerts include an action ID?

A plain approval identifier can be acceptable if it has no meaning outside your system and cannot authorize anything. Do not use a URL, account number, customer name, command fragment, or predictable sequence as the identifier.

Do generic approval notifications cause approval fatigue?

Generic alerts create their own danger because people approve them without context. Put the meaningful context in the authenticated approval view, then keep the preview limited to enough information for the person to decide whether to open it.

Should users be able to approve an action directly from a notification?

No. A notification action should only open the authenticated decision screen or dismiss the alert. It should never approve an action, extend a session, expose hidden details, or pass an approval token through the notification framework.

How do I classify risky agent actions for notifications?

Classify the operation before building the message. A read may reveal data, a write changes state, and an irreversible or external action deserves stronger wording and a more prominent alert even if the preview remains private.

How do I test whether notification previews leak sensitive details?

Test locked, unlocked, and shared-display conditions separately. Also capture the notification center history, wearable mirrors, screenshots, and any device that receives relayed alerts, because each surface can preserve more than the first banner shows.

What happens when an app cannot tell whether the screen is locked?

The app should render a safe fallback when it cannot determine the device state or the viewer’s privacy setting. A delayed or less specific alert is preferable to showing a production command or customer data on an uncontrolled display.

When is it acceptable to show full notification previews?

Only when the recipient has no useful source of context elsewhere and the message exposes nothing sensitive. For approval systems, the better default is a short alert that tells the person to open the protected app.

How should notification data differ from the audit record?

Keep the preview data separate from the authenticated approval record. The preview should be a deliberately small, redacted projection, while the protected record contains the destination, scope, identity, reason, and final decision.

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