Download the product examples: API scripts, Power Query, OData flows, test CI files and Power BI connector source. The gateway archive includes its full deployment README.
For example:
- Releasing a payment on PROD needs a second person's approval.
- Nobody uses the RACF screens without an approved 30-minute window.
- Every SDSF job submission is recorded.
- The help desk never runs SPUFI on PROD.
The rules apply to sessions that run on your gateway: WND Terminal in a browser, terminals embedded in your own web applications, and AI agents and APIs working through the gateway. The desktop app doesn't apply them. Where these rules matter, have people use WND Terminal in a browser. Privileged access is a line on your license (privileged_access).
How it goes
- A person presses a key the rules hold. Say Enter on the PAYMENT RELEASE screen of PROD. The key isn't sent. If the rule wants a reason, a small dialog asks for one first. Then a banner shows on the session:
Waiting for payments-approvers to approve Enter on PAYMENT RELEASE in PROD · 4:58 · Cancel
The keyboard stays locked while it waits.
-
An approver decides. People in the approvers group see it on the gateway's Approvals page (
/approvals). It shows who pressed the key, which rule holds it, the screen with sensitive values hidden, what was typed (masked, and never from password fields), the reason, and how long is left. They approve or deny, with a note if they like. Nobody ever approves their own key. -
The key goes, or it doesn't. Approved, WND Terminal checks that the screen and what was typed are exactly as they were, then sends the key. If anything changed meanwhile, the key isn't sent and the person sees "the screen changed while it waited". Denied, cancelled, or not answered within the time limit (five minutes unless you say otherwise), nothing is sent and the banner says why.
From the first held key to the end of the session, the session is recorded, whatever the recording policy says, and the recording is tagged privileged.
The rules
Rules are a JSON list, in the policy's PrivilegedRules (see policy settings) or in a file the gateway reads ([privileged] rules_file), or both. The gateway uses the policy's rules first, then the file's.
[
{"name": "Payment release", "when": "connection = PROD AND title starts 'PAYMENT RELEASE'",
"keys": ["Enter", "PF5"], "action": "approve", "approvers": "payments-approvers", "reason": true},
{"name": "RACF", "when": "connection = PROD AND title starts 'RACF'", "action": "elevate", "approvers": "security"},
{"name": "Batch submit", "when": "title starts 'SDSF' AND aid = Enter", "action": "record"},
{"name": "No DB2 updates from the help desk", "when": "connection = PROD AND title starts 'SPUFI'", "action": "block"}
]
On a small screen, swipe the table to read all columns.
| Setting | What it does |
|---|---|
name |
The rule's name. People, approvers and the audit log see it. |
when |
Which screens it's about, in the same rule language as response time labels: connection, host, port, lu, type ("3270" or "5250"), title (the screen's first line with text), signature (the screen's layout) and aid (the key), with =, !=, starts, ends, contains and matches, joined with AND, OR, NOT and parentheses. Letter case doesn't matter. |
keys |
The keys it's about: "Enter", "PF5" (or "F5"), "PA1", "Clear", "PageDown". Left out, any key that could change data on that screen, as Ask WND and AI agents judge it: Enter with something typed into a data field, a key the screen labels Update or Release, and so on. PF3=Exit still works. A record rule without keys is about every key. |
action |
approve: the key waits for a second person's yes. elevate: the key goes only inside an approved window of elevated access, and otherwise waits for one to be approved. record: the key goes, and the session is recorded and tagged privileged. block: the key is refused, with the rule's name. |
approvers |
The group that approves this rule's keys. Left out, [privileged] approvers_group. |
reason |
true: the person says why before the key waits. |
minutes |
For elevate: how long a window approved from a held key lasts (30 when left out). |
When several rules fit a key, the strictest wins: block, then approve or elevate, then record.
The rules are tested where the session runs, on the gateway. The screen's title is never sent anywhere for this.
PrivilegedUsers and PrivilegedConnections, the recording settings, narrow who and where the rules apply. Leave them empty and the rules apply to everyone on every connection. logon: entries in PrivilegedConnections are about recording only.
A rule that can't be used is left out, and the gateway says so in its log and in wnd-gateway check-config. Check a list before you deploy it:
$ wnd-gateway check-privileged rules.json --connection PROD "PAYMENT RELEASE 01" "CICS SIGN ON"
4 rules can be used:
Payment release: approve Enter or PF5, when connection = PROD AND title starts 'PAYMENT RELEASE' (approved by payments-approvers, asks for a reason)
RACF: elevate any key that could change data, when connection = PROD AND title starts 'RACF' (approved by security, 30-minute windows from a held key)
Batch submit: record any key, when title starts 'SDSF' AND aid = Enter
No DB2 updates from the help desk: block any key that could change data, when connection = PROD AND title starts 'SPUFI'
"PAYMENT RELEASE 01" on PROD: Payment release (approve Enter or PF5)
"CICS SIGN ON" on PROD: no rule
It takes a rules file or a policy file with PrivilegedRules, and exits with 1 when a rule can't be used.
Which keys are checked
The gateway checks every key that would go to the host, whoever pressed it. That includes keys from a person, a macro, Fill from Excel, Ask WND, a program, and an auto-enter field on an IBM i screen.
Rules look at the key that actually goes to the host. On a mainframe RollUp sends PF8, RollDown sends PF7, Help sends PF1 and Attn sends PA1. Cursor Select on a light-pen field marked & sends Enter. So a rule about PF8 also holds RollUp.
A key that needs approval and wasn't pressed by a person is refused, with the rule's name. On IBM i, when a key the screen would send by itself is refused, the keyboard goes into error state and shows the message. Reset clears it.
Programs
AI agents, flows called as APIs, and macros and other programs running on the gateway can't wait for a person. A key that an approve or elevate rule holds is refused to them with the rule's name, and so is a key that a block rule refuses. record rules record their sessions too.
Elevated access
Some work needs a window of time rather than a yes for each key. A person asks for elevated access with Request elevated access… in the command palette (Ctrl+K, or ⌘K on a Mac), naming the connection, how many minutes (up to [privileged] max_elevation_minutes, 120 unless you say otherwise) and why. A held key on an elevate rule asks for a window too.
An approver approves it at /approvals. While it lasts:
- every session of that person on that connection shows a banner with the time it ends,
- the keys of the
elevaterules the window opens go without asking, - the sessions are recorded and tagged privileged.
A window opens only the rules whose approvers approved it. A window asked for from a held key opens that key's rule. A window asked for without a rule opens the elevate rules that the default approvers ([privileged] approvers_group) approve. A window that one group approved never opens a rule that another group approves.
While a person's request for elevated access waits for an approver, they can't ask for another.
It ends at its time, when the person signs out, or when an approver ends it with End at the bottom of /approvals. Windows survive a gateway restart (they're kept in elevations.json in the gateway's data folder).
The approvals page, the API and the webhook
/approvals lists everything waiting for a second person, with a filter for agents, APIs, people and elevated access. AI agents' changes in ask mode (see docs/mcp.md) are there too, decided as before.
Programs can approve with an approver's own token from your identity provider (a person's, never a service account's):
GET /api/v1/approvals what this approver may decide, and the open windows
POST /api/v1/approvals/{id} {"allow": true, "note": "Checked with the branch"}
So a ServiceNow flow or a chat bot can show requests and pass on the approver's answer. Each request in the list has its kind, who, the rule, the reason, the masked screen, what was typed (masked) and the time left.
[privileged] webhook_url gets a POST for each new request, so a channel gets pinged:
{"id": 41, "kind": "person", "rule": "Payment release", "who": "[email protected]", "session": "PROD",
"connection": "PROD", "approvers": "payments-approvers", "asked_at": "2026-10-17T14:02:11Z",
"until": "2026-10-17T14:07:11Z", "link": "https://terminal.bank.example/approvals#ask-41",
"text": "A key [email protected] pressed in PROD (the rule Payment release) needs approval (people). Answer it at https://terminal.bank.example/approvals"}
The webhook never gets the screen, what was typed or the reason.
The review queue
/review is for your reviewers ([privileged] reviewers_group). It lists every privileged recording: sessions with a held key, sessions a record rule recorded, and work done with elevated access. For each one you see the person, the connection, when it started, how long it was, how many keys, the approvals in it (by request number, the same number as in the audit log), and the recording's own tamper check:
On a small screen, swipe the table to read all columns.
| Tamper check | What it means |
|---|---|
| Verified | Every record is there, in order, unchanged, and the last one is signed. |
| Incomplete | Nothing missing or changed so far, but the recording has no closing record yet. The session may still be open. |
| Altered | Records are missing, changed, moved or added. |
| Unreadable | Part of the file can't be read at all. |
Open plays the recording in WND Terminal's recordings viewer, in the browser. Select recordings and Mark the selected recordings reviewed, with a note. Reviewers can't mark their own sessions reviewed. Download as CSV gives the whole list. The page's title shows how many aren't reviewed yet.
The queue reads [privileged] recordings_folder, or the policy's RecordingFolder, or the gateway's own recordings.
Roles
On a small screen, swipe the table to read all columns.
| Role | Who | What they can do |
|---|---|---|
| Person | Anyone who signs in | Works their sessions. Asks for approval (with a reason when the rule wants one) and for elevated access. Cancels their own request. Never approves their own. |
| Approver | [privileged] approvers_group, or a rule's own approvers |
Approves or denies at /approvals or with the API. Ends elevated access. Sees masked screens and masked typed values only. |
| Reviewer | [privileged] reviewers_group |
Reviews privileged recordings at /review, plays them, marks them reviewed, downloads the list. Doesn't approve anything, and never marks their own sessions reviewed. |
| Administrator | Your IT team | Changes the rules through policy (PrivilegedRules) and the gateway's configuration, never through the web pages. |
Least privilege, in practice:
- Approvers see what they need to decide, with card numbers, IT's MaskPatterns, passwords typed into commands and long numbers hidden. Password fields are never read.
- Reviewers see recordings, which are masked when they're written. They can't change a recording without the tamper check showing it.
- Nobody approves their own request, even when they're in the approvers group.
- Rules live in policy and configuration files that your administrators control and your change process covers.
What is logged, and where
Every step is in the audit log (the gateway's audit/ folder, and your AuditFolder, Splunk HTTP Event Collector or syslog CEF from the policy), attributed to the person:
On a small screen, swipe the table to read all columns.
| Event | When |
|---|---|
privilege.requested |
A key was held for approval. |
privilege.approved |
An approver approved it. |
privilege.denied |
An approver denied it, nobody answered in time, or the screen changed while it waited. |
privilege.cancelled |
The person cancelled it, or closed the session. |
privilege.sent |
The key went to the host after approval. |
privilege.blocked |
A rule refused a key, or the key needed approval and wasn't pressed by a person. |
privilege.elevation_requested, privilege.elevation_approved, privilege.elevation_denied, privilege.elevation_ended |
Elevated access asked for, approved, denied or not answered, and ended (by time, sign-out or an approver). |
review.viewed |
A reviewer opened the queue, played a recording or downloaded the CSV. |
review.marked |
A reviewer marked a recording reviewed. |
The details carry the rule (rule), the request's number (ask), the key (key), the approver (approver), the approver's note (note, masked), the connection and the minutes for elevated access, and the recording's file for reviews. Never the screen and never what was typed.
For your SIEM:
On a small screen, swipe the table to read all columns.
| Field | Audit log (JSON line) | CEF |
|---|---|---|
| What happened | event |
the event class |
| Outcome | outcome (success, pending, refused) |
severity |
| Who | user, computer |
suser, shost |
| Session | session, host, port |
cs2 (labelled session), dhost, dpt |
| Rule, request, approver | details.rule, details.ask, details.approver |
in msg, after the message |
The gateway's own log (standard output, one JSON object per line) has the same steps as privilege.approval, privilege.elevation_*, privilege.webhook, privilege.rules, review.viewed and review.marked, for monitoring.
Recordings go where your recording policy puts them, and RecordingRetentionDays applies to them as to any recording. The review marks are kept in reviews.json in the gateway's data folder. Keep the audit log as long as your own retention rules say. WND Terminal doesn't delete it.
Controls this supports
What WND Terminal provides toward common controls. Your auditors decide whether your use of it meets them.
On a small screen, swipe the table to read all columns.
| Control | SOC 2 | ISO/IEC 27001 | What WND Terminal provides |
|---|---|---|---|
| Restrict access to sensitive functions | CC6.1 | A.9.4.1 (2022: 8.3) | Rules on screens and keys: approve, elevate or block, per connection, per person or group. |
| Authorize access before it's used | CC6.2 | A.9.2.2 (2022: 5.18) | A second person approves each held key or each window of elevated access, with the reason on record. |
| Privileged access is limited and time-bound | CC6.3 | A.9.2.3 (2022: 8.2) | Elevated access only for an approved number of minutes, ended at its time, at sign-out or by an approver. |
| Separation of duties | CC6.3 | A.9.2.3 (2022: 8.2) | Nobody approves their own request. Approvers and reviewers are separate groups. |
| Use of privileged functions is controlled | CC6.1 | A.9.4.4 (2022: 8.18) | block rules refuse keys outright. Programs can't use keys that need a person. |
| Review of privileged activity | CC6.2, CC7.2 | A.9.2.5 (2022: 5.18) | The review queue with each privileged recording, marked reviewed by whom and when, and a CSV for your evidence. |
| Monitoring and evidence | CC7.2 | A.12.4.1, A.12.4.3 (2022: 8.15) | Audit events for every step to your SIEM, and tamper-evident recordings of privileged sessions. |
| Evaluating security events | CC7.3 | A.12.4.1 (2022: 8.15, 8.16) | Recordings that show Altered when changed, and events with the rule, request and approver to correlate. |
Setting it up
- Write the rules and check them with
wnd-gateway check-privileged. - Put them in the policy (
PrivilegedRules) or in[privileged] rules_file. - Name the approvers and reviewers in
gateway.toml:
toml
[privileged]
approvers_group = "Mainframe Approvers"
reviewers_group = "Security Reviewers"
approval_timeout_seconds = 300
max_elevation_minutes = 120
webhook_url = "https://hooks.bank.example/wnd-approvals"
Every setting is in gateway configuration.
- Run
wnd-gateway check-config. It says how many rules can be used, and whether your license includes privileged access. Without it the rules aren't applied.