---
title: "The Desktop Was Hidden. The Execution Boundary Was Not."
description: "Part three of the ATM assessment series: validate kiosk containment, application control, service identities, maintenance states, secrets, updates, and off-host telemetry without turning UI escape testing into a payload exercise."
date: 2026-09-01
author: Sevban Dönmez (@jankesec)
canonical: https://jankesec.com/posts/atm-kiosk-execution-boundary/
---

## Kiosk containment in 60 seconds

A full-screen ATM application is a presentation decision. It is not proof that the operating
system underneath has become a single-purpose appliance. The real boundary is the set of code,
identities, files, services, interaction paths, and maintenance states that the platform permits.

The useful pentest question is not “Can I make the desktop appear?” It is:

> If the public session reaches an unexpected operating-system function, which independent
> control prevents that function from becoming code execution, privilege, secrets access, device
> authority, persistence, or an invisible change?

This methodology uses benign test artifacts and vendor-approved lab states. It does not require
weaponised payloads, credential dumping, destructive policy changes, or access to production
payment data. A denied process start, a blocked file transition, and a correlated off-host event
usually prove the boundary more cleanly than a dramatic kiosk escape.

## The shell and the policy are different controls

Windows provides kiosk and restricted-user experiences such as Assigned Access and Shell
Launcher. Those features narrow the user experience, but they solve a different problem from
application control. A shell can hide entry points while an application-control policy decides
which binaries, scripts, installers, libraries, drivers, and interpreters may execute.

An ATM needs both. It also needs least-privileged service identities, protected writable paths,
restricted management interfaces, a controlled update channel, and telemetry that survives
endpoint compromise. Otherwise a kiosk can look perfectly locked while trusting a path the user
never sees.

<figure class="diagram">
<svg viewBox="0 0 760 350" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="ATM kiosk execution model. Public inputs enter a restricted kiosk session and approved ATM application. Every transition to operating-system capability passes through an application-control and identity policy. Approved services receive only narrow capabilities. Updates enter through a separate signed and approved deployment gate. Denied execution, policy changes, service events, and health state are sent to off-host monitoring. Maintenance and recovery must preserve equivalent controls.">
<defs><marker id="atmk-a" viewBox="0 0 8 8" refX="7" refY="4" markerWidth="7" markerHeight="7" orient="auto"><path d="M0,0 L8,4 L0,8 z" class="dg-arrow" /></marker><marker id="atmk-c" viewBox="0 0 8 8" refX="7" refY="4" markerWidth="7" markerHeight="7" orient="auto"><path d="M0,0 L8,4 L0,8 z" class="dg-arrow-crit" /></marker></defs>
<text x="0" y="18" class="dg-muted">VISIBLE KIOSK ≠ AUTHORITATIVE EXECUTION BOUNDARY</text>
<rect x="0" y="42" width="132" height="64" rx="8" class="dg-box"/><text x="14" y="66" class="dg-label">Public surface</text><text x="14" y="86" class="dg-accent">INPUT + UI</text><text x="14" y="100" class="dg-muted dg-compact">touch · reader · accessibility</text>
<line x1="132" y1="74" x2="154" y2="74" class="dg-line" marker-end="url(#atmk-a)"/>
<rect x="158" y="42" width="132" height="64" rx="8" class="dg-box"/><text x="172" y="66" class="dg-label">Session</text><text x="172" y="86" class="dg-accent">KIOSK SHELL</text><text x="172" y="100" class="dg-muted dg-compact">restricted experience</text>
<line x1="290" y1="74" x2="312" y2="74" class="dg-line" marker-end="url(#atmk-a)"/>
<rect x="316" y="42" width="144" height="64" rx="8" class="dg-box-accent"/><text x="330" y="66" class="dg-label">Authority</text><text x="330" y="86" class="dg-accent">CODE + IDENTITY</text><text x="330" y="100" class="dg-muted dg-compact">allow · deny · constrain</text>
<line x1="460" y1="74" x2="482" y2="74" class="dg-line" marker-end="url(#atmk-a)"/>
<rect x="486" y="42" width="132" height="64" rx="8" class="dg-box"/><text x="500" y="66" class="dg-label">Host</text><text x="500" y="86" class="dg-accent">OS SERVICES</text><text x="500" y="100" class="dg-muted dg-compact">least privilege</text>
<line x1="618" y1="74" x2="640" y2="74" class="dg-line" marker-end="url(#atmk-a)"/>
<rect x="644" y="42" width="116" height="64" rx="8" class="dg-box"/><text x="658" y="66" class="dg-label">Capability</text><text x="658" y="86" class="dg-accent">DEVICES</text><text x="658" y="100" class="dg-muted dg-compact">narrow broker</text>

<rect x="0" y="150" width="212" height="68" rx="8" class="dg-box"/><text x="14" y="176" class="dg-label">Maintenance + recovery</text><text x="14" y="196" class="dg-accent">EQUIVALENT POLICY</text><text x="14" y="210" class="dg-muted dg-compact">no general-purpose exception</text>
<line x1="212" y1="184" x2="310" y2="184" class="dg-line" marker-end="url(#atmk-a)"/>
<rect x="316" y="150" width="212" height="68" rx="8" class="dg-box-accent"/><text x="330" y="176" class="dg-label">Deployment gate</text><text x="330" y="196" class="dg-accent">SIGN + APPROVE + TARGET</text><text x="330" y="210" class="dg-muted dg-compact">package · policy · rollback</text>
<line x1="422" y1="150" x2="422" y2="112" class="dg-line dg-dash" marker-end="url(#atmk-a)"/>
<line x1="528" y1="184" x2="550" y2="184" class="dg-line" marker-end="url(#atmk-a)"/>
<rect x="554" y="150" width="206" height="68" rx="8" class="dg-box"/><text x="568" y="176" class="dg-label">Independent evidence</text><text x="568" y="196" class="dg-accent">OFF-HOST MONITORING</text><text x="568" y="210" class="dg-muted dg-compact">actor · hash · policy · result</text>

<rect x="0" y="264" width="760" height="86" rx="8" class="dg-box-crit"/><text x="16" y="292" class="dg-crit">FAIL-CLOSED CLAIM</text><text x="16" y="316" class="dg-label">Unexpected interaction may expose an entry point; it must not silently acquire execution or a stronger identity.</text><text x="16" y="338" class="dg-muted">The same assertion must hold during normal use, service, update, failure, and recovery.</text>
</svg>
<figcaption>The kiosk shell reduces exposure. Code policy and identity policy decide whether an exposed path has authority.</figcaption>
</figure>

## Phase zero: model every operating state

Begin with the states the terminal can legitimately enter: normal customer session, startup,
application restart, degraded connectivity, cash or paper service, authorised maintenance, remote
support, software deployment, operating-system recovery, and return to service.

For each state, identify the interactive account, shell, allowed processes, effective application-
control policy, service identities, writable locations, network policy, telemetry destination, and
exit condition. The dangerous gap is often not the production kiosk profile. It is a diagnostic or
recovery state that temporarily restores a general-purpose shell, broader network access, or a
high-privilege account and never proves that the restriction was reinstated.

Define stop conditions before testing. Use synthetic data, inert files, approved accounts, and a
lab device. Do not collect credentials, disable the endpoint protection stack, or use a real device
operation to prove local execution. The finding is the unjustified authority transition.

## Phase one: separate interaction exposure from execution

Catalogue every public input path: touch, function keys, accessibility features, reader events,
printer prompts, external links, help flows, file dialogs, error handlers, and application restart
behavior. Also review indirect content such as images, receipts, language packs, configuration,
and remotely supplied messages.

An exposed operating-system surface is a hypothesis, not yet a critical finding. Attempt only a
benign transition to an organisation-controlled sink or inert artifact. Record which process
handled the input, which identity it used, what policy decision occurred, and whether the event
reached central monitoring. Stop when the authority decision is known.

This prevents the report from confusing “desktop visible” with “system compromised.” A visible
dialog may be serious if it can invoke an ungoverned interpreter or write to a trusted execution
path. It may be low impact if code policy, file permissions, network policy, and service boundaries
remain authoritative. Evidence decides.

## Phase two: make application control authoritative

Application control should convert the terminal from “anything runs unless detected as bad” to
“only code selected by policy runs.” On a fixed-workload ATM, the trust set can be narrow, but the
assessment must include more than executable files.

Inventory applications, libraries, drivers, scripts, installers, interpreters, plug-ins, COM
components, scheduled tasks, and service binaries. Map how each is identified: publisher,
certificate, catalog, hash, managed installer, path, or another rule. A broad publisher rule may
trust far more code than the baseline intends. A path rule may become unsafe when a lower-privilege
identity can write to that path.

Use a set of inert test artifacts representing distinct cases: unknown binary, unknown script,
renamed file, allowed publisher outside the approved product, and approved code placed in an
unapproved writable location. The expected output is a decision matrix, not execution. Verify
enforcement—not merely audit mode—and correlate each denial with policy version, file identity,
caller, terminal, and trusted time.

Test the same set after update, during maintenance, and after recovery. A supplemental policy or
emergency exception that remains in place can expand the trust set silently. Every exception needs
an owner, reason, target group, start, expiry, and regression test.

## Phase three: follow identity, not process names

A kiosk application often relies on privileged services to reach hardware or operating-system
functions. That is a sound design only if the broker enforces a narrow contract. Process names and
local connectivity are not authorization.

Record the identity and privileges of the kiosk account, application process, broker, service,
updater, monitoring agent, and remote-support component. Determine which resources each can read,
write, start, configure, or impersonate. Review service executable and configuration permissions,
named-object permissions, local IPC authorization, token boundaries, and recovery identities.

Use harmless out-of-context requests or test doubles in the lab. A public-session process should
not inherit a sensitive capability simply because it can reach a local broker. A service should
validate caller identity and policy state, not assume that any local caller belongs to the approved
application. Part four follows that device boundary in depth; here the objective is to prove that a
UI-layer mistake cannot automatically become SYSTEM-level authority.

## Phase four: test writable paths, secrets, and persistence

Build a write map for each identity. Include application directories, data folders, temporary
locations, logs, update staging, service configuration, startup locations, policy files, and any
path consumed by a privileged component.

The critical condition is not that a low-privilege account can write somewhere. It is that a more
trusted component later interprets, loads, executes, or authenticates that content without a
separate integrity decision. Test with inert markers and file hashes. Do not replace production
binaries or insert executable content.

Handle secrets as another authority path. Determine where service credentials, API material,
certificates, recovery data, and configuration secrets originate, which identity can retrieve
them, whether they are machine-bound, how they rotate, and whether use is logged. Evidence should
record access decisions and metadata, never the secret itself.

Persistence testing should similarly stop at policy. Demonstrate that an unapproved identity can
or cannot create a start condition, then remove the inert marker and verify reconciliation. There
is no need to install a backdoor to prove that startup policy trusts a writable object.

## Phase five: validate update and exception governance

The update system deliberately crosses the application-control boundary. That makes it part of
the security architecture: package creation, signing, approval, targeting, staging, installation,
rollback, policy refresh, and final inventory all need accountable owners.

Use vendor test packages or metadata to verify rejection of an unsigned, incorrectly targeted,
expired, or out-of-policy update. Confirm that the updater itself cannot convert any locally
writable file into trusted code. Confirm that an approved package cannot silently expand the
application-control policy beyond its change record.

After installation, require agreement between local version, code-policy state, deployment
record, endpoint telemetry, and asset inventory. “Job succeeded” is a deployment result, not proof
that the ATM returned to a trusted state.

## Phase six: prove detection and recovery

Each negative control should produce an off-host event that includes terminal identity, user or
service identity, file or object identity, policy version, decision, operating state, and trusted
time. The local endpoint cannot be the sole custodian of evidence about its own loss of trust.

Then rehearse containment. Can the operator isolate the terminal's service and transaction paths
without losing the evidence trail? Can they distinguish a policy false positive from an actual
integrity failure? Can they restore a known-good image, reapply the correct kiosk and application-
control policies, rotate exposed authority, and reconcile the device before it handles another
customer session?

The recovery gate should be machine-verifiable. The terminal returns only when boot trust,
application policy, versions, identities, monitoring, network policy, and operational state agree.

## Evidence matrix

| Signal | What it proves | Safe negative control | Defender verification |
| --- | --- | --- | --- |
| A public interaction opens an unexpected OS surface | The shell exposes an entry point; impact is not yet known | Navigate only to an inert local artifact or controlled sink | Correlate handler process, identity, code-policy result, network result, and alert |
| Unknown binary is blocked but script, library, installer, or interpreter paths are not governed | Application control covers only part of the execution model | Present inert artifacts for each code class without payload behavior | Verify enforced policy, policy version, caller, hash or signer, and off-host denial |
| A low-privilege identity writes content later consumed by a privileged service | File integrity becomes an authority escalation path | Place a non-executable marker and observe the privileged consumption decision | Fix ACLs, validate content integrity, and regression-test the boundary |
| Maintenance or recovery uses a broader code policy without expiry | A temporary operating state creates a persistent exception | Compare the same inert test set across normal and service states | Require scoped exception, approval, expiry, policy rollback, and reconciliation |
| Update job succeeds while local policy, version, and inventory disagree | Deployment success is being treated as trust | Apply an approved lab package and compare all sources of truth | Gate return to service on code policy, version, telemetry, and inventory agreement |
| Execution denial remains only in local logs | Endpoint compromise can erase detection evidence | Trigger one approved denial on a lab terminal | Require off-host delivery with actor, terminal, policy, object, result, and time |

## What a finding should look like

> The lab kiosk account could write an inert marker into a directory consumed by the privileged
> maintenance service at startup. Application control denied an unknown executable in the public
> session, but the service's data-consumption path was not integrity-checked and no central event
> identified the lower-privilege writer. No payload, credential, device command, payment data, or
> persistence mechanism was used. The boundary therefore relies on path location rather than
> producer identity and content integrity. Restrict write access, authenticate the consumed object,
> record producer and decision off-host, and regression-test the same transition in normal,
> maintenance, and recovery states.

That finding names the authority transition and the missing invariant. “Kiosk escape achieved”
does neither.

## What I would not call proof

- A hidden desktop or disabled key combination presented as application control.
- Audit-mode events presented as enforced denial.
- A blocked executable with no coverage for scripts, libraries, drivers, and interpreters.
- A privileged service account described as safe because the service is vendor-supplied.
- An EDR agent shown as healthy without an off-host test event and response path.
- A successful update without final-state reconciliation.
- A screenshot of local secrets or customer data collected to make the finding look serious.

## Defender priority order

**First, make code policy authoritative.** Cover every executable code class and every operating
state, then protect the policy from local weakening.

**Second, reduce broker authority.** Give kiosk and service identities only the resources and
device operations their contracts require; validate the caller at every local boundary.

**Third, remove writable-to-trusted transitions.** Privileged components must not consume lower-
trust content without integrity and producer checks.

**Fourth, govern updates and exceptions as security changes.** Scope them, approve them, expire
them, and reconcile the terminal afterward.

**Fifth, test recovery as often as prevention.** A known-good image and policy baseline are useful
only if the organization can restore and verify them without losing the audit trail.

## Closing thought

The ATM does not become an appliance because the desktop is hidden. It becomes appliance-like
when every code path, identity, writable object, update, and recovery state is constrained by an
explicit policy that produces independent evidence.

A strong assessment can acknowledge a kiosk escape without stopping there. The decisive result is
what the next control did.

## Public sources and standards context

- [Microsoft — Windows kiosk configuration](https://learn.microsoft.com/en-us/windows/configuration/kiosk/)
- [Microsoft — Application Control for Windows](https://learn.microsoft.com/en-us/windows/security/application-security/application-control/app-control-for-business/appcontrol)
- [Microsoft — Application Control policy and file rules](https://learn.microsoft.com/en-us/windows/security/application-security/application-control/app-control-for-business/design/select-types-of-rules-to-create)
- [NIST SP 800-115 — Technical Guide to Information Security Testing and Assessment](https://csrc.nist.gov/pubs/sp/800/115/final)
- [PCI Security Standards Council — ATM Security Guidelines](https://listings.pcisecuritystandards.org/pdfs/PCI_ATM_Security_Guidelines_Info_Supplement.pdf)

The exact kiosk and application-control technologies vary by ATM generation and operating system.
Test the implemented control and its vendor-supported lifecycle; do not infer production behavior
from a generic desktop build.