docs/en/latest/security-threat-model.md
This is a maintainer-stewarded threat model owned by the Apache APISIX PMC. It follows the rubric at https://gist.github.com/potiuk/da14a826283038ddfe38cc9fe6310573. A historical first draft is preserved as a gist at https://gist.github.com/potiuk/43aa334087cf1fdb2908662e592a192a for reference; this in-repo document is the canonical version. Last revised 2026-05-30.
Provenance legend — every claim is tagged:
Confidence: ~70 (documented) / 28 (maintainer) / 0 (inferred). All §4.14 open questions have been resolved with the PMC; q-21 (dashboard admin-key storage) was resolved by reducing scan scope (see §4.11a, §4.14 q-21).
apache/apisix@main
and apache/apisix-ingress-controller@main at the date above.One-paragraph description. Apache APISIX is a dynamic, cloud-native API gateway built on OpenResty (nginx + LuaJIT). A single APISIX deployment terminates HTTP/HTTPS/gRPC/TCP/UDP client traffic, applies route-matched plugin logic (auth, rate limiting, transformation, observability), and forwards to upstream services. Configuration is held in etcd (or in YAML in standalone mode) and mutated through the Admin API. The apisix-ingress-controller translates Kubernetes Ingress / Gateway-API resources into Admin API calls. (The apisix-dashboard — a React SPA that does the same on behalf of a human operator — is part of the broader APISIX project but is out of the current scan scope; see §4.11a.)
Intended deployments (documented — README, deployment-modes.md):
role: data_plane and
role: control_plane.Caller roles. A gateway has more than one "caller":
| Role | Trust level | Typical channel |
|---|---|---|
| External client | untrusted | HTTPS / HTTP / TCP / UDP traffic to data-plane port (default 9080). |
| Operator / admin | trusted | Admin API (default 9180) or Kubernetes CRDs via the ingress controller. |
| Upstream service | operator-trusted | HTTP / HTTPS / gRPC to backend; operator chose to put it behind APISIX. |
| etcd peer | operator-trusted | gRPC to etcd (TLS optional). |
| Plugin runner subprocess (external plugins) | operator-trusted | Unix-socket RPC; subprocess inherits APISIX user. (documented — external-plugin.md) |
| Operator-supplied Lua / serverless code | operator-trusted | embedded in route config; executes inside the worker. (documented — plugin-develop.md, serverless-pre-function plugin) |
Component-family table.
| Family | Representative entry point | Process-external side effects | In/out of model |
|---|---|---|---|
| Gateway data-plane (Lua, OpenResty) | POST /any-route on node_listen | network egress to upstream, log sinks, etcd reads | in |
| Gateway control-plane (Admin API) | PUT /apisix/admin/routes/<id> on admin_listen | etcd writes, plugin reloads | in |
| etcd watch + reload | apisix/core/config_etcd.lua | etcd reads, in-process state mutation | in |
| Plugin host (Lua) | each route's plugins: config | filesystem / network access available to any plugin | in |
| External plugin runner | unix socket RPC | subprocess, runs as APISIX uid | in (documented — external-plugin.md) |
| Stream proxy (raw TCP/UDP) | stream_proxy listener | upstream TCP / UDP | in |
| Ingress controller | Kubernetes CRD watcher → Admin API | K8s API, etcd writes via APISIX | in |
bin/, t/, example/, ci/, .github/, demo configs | n/a | n/a | out (maintainer — confirmed by Ming Wen 2026-05-15) |
Vendored Lua runtime dependencies under deps/ and lua_modules/ | varies | varies | in (per apisix-master-0.rockspec runtime deps) (maintainer — confirmed by Ming Wen 2026-05-15) |
Test fixtures under t/lib/ | varies | varies | out (maintainer — confirmed by Ming Wen 2026-05-15) |
The following are not threat-model scope. A report whose root
cause sits here is closed as OUT-OF-MODEL with a pointer to the
specific bullet below.
0.0.0.0 without
changing admin_listen.allow_admin from its default
127.0.0.0/24; running with the documented-as-known
default Admin API key edd1c9f034335f136f87ad84b625c8f1
(the conf/config.yaml warning calls this out explicitly).
(documented — admin-api.md, conf/config.yaml:59-63)extra_lua_path, serverless-pre-function /
serverless-post-function route-embedded code, custom external
plugin runners). This code is loaded into the same worker
process with full ngx.* access — by design (documented —
plugin-develop.md, external-plugin.md). The PMC is responsible
for the Apache-maintained plugins under apisix/plugins/; the
operator is responsible for their own. Exception: the example
snippets in docs/ are PMC responsibility — an exploitable
documented example is a documentation bug, triaged as
VALID-HARDENING. (maintainer — confirmed by Ming Wen 2026-05-15)plugins: block (or
in an ApisixPluginConfig / ApisixRoute CRD). There is no
"default-enabled plugins subset". Enabling a plugin and finding
a bug in it is in scope of §4.8 for any plugin shipped under
apisix/plugins/. (maintainer — confirmed by Ming Wen 2026-05-15)ApisixRoute / ApisixConsumer / ApisixPluginConfig
resources (documented duty: creators of ApisixRoute must be
the namespace owner or equivalent trust tier). The
controller's own RBAC requirements are documented in
apisix-ingress-controller/config/rbac/role.yaml.
(maintainer — confirmed by Ming Wen 2026-05-15)crypto/tls). APISIX's own
credential comparisons (admin key, JWT secret, HMAC signature,
etc.) are expected to be constant-time — that is a §4.8
property, not a §4.3 exclusion. (maintainer — confirmed by
Ming Wen 2026-05-15)bin/, t/, example/, benchmark/, ci/,
.github/ — test fixtures, smoke scripts, benchmark
harnesses, CI configuration. Not deployed to production.
t/lib/ test fixtures are likewise out of model. (maintainer
— confirmed by Ming Wen 2026-05-15)Primary trust boundaries (where untrusted data crosses into the trusted core):
(operator)
|
v
External client --HTTPS--> [data-plane <--+
(untrusted) worker process |
— Lua plugins, | Admin API
route table, | (X-API-KEY)
upstream pool] |
^ | |
| | |
(etcd | |
config | [Admin
reads) | handler]
| v |
etcd <---+---> etcd writes
(operator-secured, TLS optional)
^
|
(apisix-ingress-controller,
curl / human operator)
(documented — architecture-design/apisix.md, admin-api.md, deployment-modes.md)
Boundary transitions:
X-API-KEY. After
auth, the JSON body is validated against the resource's JSON
Schema before being written to etcd. Trust transition:
"authenticated operator input" → "schema-validated config".X-API-KEY (configured via a Kubernetes Secret). Trust
transition: "Kubernetes-authorized CRD" → "Admin API call".Reachability preconditions per family (the test a triager applies to a finding before anything else):
| Family | A finding here is in-model only if reachable from… |
|---|---|
| Data-plane Lua plugins | a client request on node_listen that matches a route that has the plugin enabled. |
| Admin API handlers | an authenticated Admin API call from a caller inside allow_admin. |
| etcd watch handler | an etcd write whose key is under prefix (default /apisix). |
| Stream proxy | a TCP/UDP packet on stream_proxy.*_listen. |
| Ingress controller | a CRD a Kubernetes RBAC-authorized user created in a watched namespace. |
| External-plugin runner | the gateway worker is calling the runner via unix socket. |
OUT-OF-MODEL: unsupported-platform.
(maintainer — confirmed by Ming Wen 2026-05-15)core.table.new patterns)conf/config.yaml and any files
named under extra_lua_path. SSL cert/key material can be
stored on filesystem or in etcd; etcd-stored keys are
plaintext-in-etcd by default unless etcd encryption-at-rest is
configured by the operator, or apisix.data_encryption is
enabled for field-level encryption. (documented —
apisix/core/etcd.lua; see §4.8 point 11)Negative claims — what APISIX does not do to its host: (maintainer — confirmed by Ming Wen 2026-05-15; all 5 hold as written.)
logs/ and the OpenResty / etcd cache
directories.The following config knobs change which §4.8 properties hold. Note: all claims below are (documented) unless tagged otherwise.
| Knob | Default | Effect when flipped from default | Maintainer stance |
|---|---|---|---|
deployment.admin.admin_key[].key | edd1c9f0… (in docs / sample config) | Without changing it, the Admin API has no real auth. | Documented as required to change in production. A report against an unchanged default is OUT-OF-MODEL: operator-misconfig per §4.3. (maintainer — confirmed by Ming Wen 2026-05-15) |
deployment.admin.allow_admin | 127.0.0.0/24 | Widening to 0.0.0.0/0 exposes Admin API to the network. | OUT-OF-MODEL: operator-misconfig if widened and then exploited. (maintainer — confirmed by Ming Wen 2026-05-15) |
deployment.admin.enable_admin | true | false disables the Admin API; the data plane reads etcd directly (decoupled mode). | Both supported. Decoupled mode is recommended for internet-facing / public-edge deployments; traditional mode is appropriate for internal-network deployments. (maintainer — confirmed by Ming Wen 2026-05-15) |
deployment.etcd.tls.{cert,key,verify} | unset (plaintext) | Enabling TLS protects etcd traffic. | Required if etcd is networked (multi-host or cross-zone). Optional for single-host loopback deployments. (maintainer — confirmed by Ming Wen 2026-05-15) |
apisix.ssl.listen / ssl_protocols | TLS 1.2 / 1.3, no 1.0/1.1 | Re-enabling 1.0/1.1 weakens transport. | Documented; not recommended. (documented — ssl-protocol.md) |
plugins: list | (no enabled plugins; all opt-in) | Naming a plugin on a route enables it. | All plugins are opt-in; a CVE on any Apache-maintained plugin under apisix/plugins/ is CVE-equivalent regardless of how widely it tends to be enabled in the wild. (maintainer — confirmed by Ming Wen 2026-05-15) |
extra_lua_path | unset | Loading operator-supplied Lua plugins. Those are out of model per §4.3 point 4. | (documented — plugin-develop.md) |
apisix.role | traditional | data_plane / control_plane change which surface is exposed. | Documented; security implications laid out in deployment-modes.md. (documented) |
apisix.data_encryption | unset / disabled | Enables field-level encryption of sensitive consumer-credential fields in etcd. | Optional in 3.x; not enabled by default. See §4.8 point 11. (maintainer — confirmed by Ming Wen 2026-05-15) |
The insecure-default case (per the rubric): the Admin API
ships with a documented-as-known weak key. The conf/config.yaml
sample explicitly says "using fixed API token has security
risk, please update it when you deploy to production
environment." Reading (b) is confirmed: the default is a
dev-convenience and operators are documented as required to flip
it. A report that exploits the unchanged default key is
OUT-OF-MODEL: operator-misconfig. (maintainer — confirmed by
Ming Wen 2026-05-15)
Per-component input table (grouped by family from §4.2).
| Endpoint / message | Field | Attacker-controllable? | Caller / operator must enforce |
|---|---|---|---|
Any route on node_listen | request line (method, URI, query) | yes | route regex must not be catastrophic backtracking (project applies an Anti-ReDoS policy on input regex). (documented — admin API validation) |
| Any route | request headers (incl. X-Forwarded-*) | yes | not to be trusted as authentication ground truth unless an ip-restriction / real-ip plugin is on. (documented — real-ip plugin doc) |
| Any route | request body | yes | size bounded by client_max_body_size; content trusted only insofar as the route's plugins validated it. |
stream_proxy | TCP / UDP payload | yes | upstream is responsible for protocol handling; APISIX forwards bytes. |
| TLS handshake | SNI | yes | SNI is used for cert selection but not treated as authenticated identity. (documented — ssl-protocol.md) |
| Endpoint | Field | Attacker-controllable? | Caller must enforce |
|---|---|---|---|
PUT /apisix/admin/routes/<id> | plugins.{*}.{*} | yes if Admin API exposed | JSON-Schema validated before persist; schema must reject unsafe shapes. (documented — plugin schema validation) |
PUT /apisix/admin/ssls/<id> | cert, key | trusted (operator) | private key is stored in etcd; etcd security is the protective layer (see §4.8 point 11 for optional apisix.data_encryption). |
PUT /apisix/admin/consumers/<id> | username, plugin creds | trusted (operator) | Stored in etcd under /apisix/consumers/<username>, organized per plugin (key-auth: key; jwt-auth: key+secret+algorithm; basic-auth: username+password; hmac-auth: access_key+secret_key; etc.). Optional field-level encryption via apisix.data_encryption. (maintainer — confirmed by Ming Wen 2026-05-15) |
POST /apisix/admin/routes | script (legacy) | trusted (operator) | arbitrary Lua execution by design — same trust posture as extra_lua_path. The script field is currently still present and is planned for removal in the next release; semantically equivalent to extra_lua_path (out-of-model per §4.3 point 4). (maintainer — confirmed by Ming Wen 2026-05-15) |
| Surface | Field | Attacker-controllable? | Cluster operator must enforce |
|---|---|---|---|
ApisixRoute.spec.http[*].plugins[*] | plugin config | depends on RBAC | restrict CRD creation to trusted namespaces / users; creators of ApisixRoute must be the namespace owner or equivalent trust tier. (maintainer — confirmed by Ming Wen 2026-05-15) |
ApisixConsumer.spec.plugins[*] | credential material | depends on RBAC | same as above. |
ApisixPluginConfig | shared plugin block | depends on RBAC | same. |
Size, shape, rate assumptions. Request size bounded by
client_max_body_size (nginx-level; default 8KB but the project
ships 1m). Connection rate bounded by kernel + worker_connections.
Rate-limiting and circuit-breaker plugins are opt-in — APISIX
does not rate-limit by default. (documented — limit-req,
limit-count, limit-concurrency plugin docs)
Adversaries in scope.
node_listen.
They can send arbitrary, malformed, or oversized requests.
They aim to: bypass route auth; reach an upstream they
shouldn't; crash a worker; cause CPU / memory exhaustion;
read configured-secret material (e.g., an Authorization
header that another plugin sets).apisix/plugins/, since that's the trust boundary the
PMC owns. All Apache-maintained plugin bugs are CVE-equivalent;
since all plugins are opt-in (§4.3 point 5), there is no
priority differentiation between "default-loaded" and
"opt-in". Operator-supplied plugins are out per §4.3 point 4.
(maintainer — confirmed by Ming Wen 2026-05-15)ApisixRoute in some namespace, attempting to influence
traffic outside that namespace via the ingress controller.
The cluster operator's documented duty is to gate CRD
creation appropriately (§4.3 point 6). (maintainer —
confirmed by Ming Wen 2026-05-15)Adversaries out of scope (cross-ref §4.3):
For each property: the property, violation symptom, severity tier, provenance.
HTTP request parsing safety.
node_listen cause
400 rejection, not worker memory corruption or RCE.
HTTP/1.1 / HTTP/2 wire-level parsing bugs are owned by
upstream nginx (APISIX tracks and ships fixed versions);
APISIX's own Lua-phase parsing (custom header / query /
body handling, gRPC metadata, etc.) is APISIX-owned.Admin API authentication gate.
X-API-KEY (matching admin_key[].key) returns 401 and
does not mutate state.Admin API IP allowlist.
deployment.admin.allow_admin is rejected at network level
(returns 403 or connection refused).JSON-Schema validation of plugin configs.
TLS termination.
ssl_protocols; TLS 1.0 / 1.1 disabled by
default.Plugin chain authorization.
serverless-pre-function,
which is by design able to rewrite, short-circuit, or
bypass later auth plugins — operators enabling it take on
responsibility for the resulting trust model, concrete
instance of §4.3 point 4).serverless-* interactions are by design).Rate-limit / circuit-break plugin integrity.
limit-req / limit-count /
limit-concurrency are configured, their counters are
consulted on the request hot path and exceeding the limit
yields 503 / 429 (per config).Ingress-controller fidelity.
apisix.apache.org
CRD spec maps to a defined Admin API target. Silent drop,
injection, or renaming is an apisix-ingress-controller
bug — not operator misconfiguration. An e2e contract test
enforcing this invariant is recommended.Constant-time comparison of credentials.
crypto/tls.)X-API-KEY,
JWT verification, HMAC verification, etc.Log-sink failure / malicious-response resilience.
Optional field-level encryption of sensitive consumer
fields (apisix.data_encryption).
apisix.data_encryption is enabled (3.x;
not enabled by default), sensitive fields in consumer /
ssl resources are encrypted before being written to etcd.X-API-KEY-header auth, not session cookies; CSRF as
typically understood is not the threat. But: a malicious
page loaded in the operator's browser cannot directly read
Admin API responses (same-origin) unless the operator has
widened CORS — APISIX does not set permissive CORS by
default. (maintainer — confirmed by Ming Wen 2026-05-15)ngx.* access; external-plugin runners
inherit the APISIX uid. A compromised in-tree plugin is RCE
in the gateway. (documented — plugin-develop.md,
external-plugin.md)kafka-logger / http-logger to a SIEM —
see §4.10). (maintainer — confirmed by Ming Wen 2026-05-15)What the operator must do to inherit the §4.8 properties:
config.yaml (env var, K8s Secret,
external secret manager). (documented)allow_admin to operator workstations or
bastions; do not widen to 0.0.0.0/0. (documented)deployment.etcd.tls.{cert,key,verify} accordingly.
(maintainer — confirmed by Ming Wen 2026-05-15)extra_lua_path,
serverless-pre/post, external plugin runners) before deploy
— this code is in the trusted-by-design tier. (documented)data_plane / control_plane
roles, or standalone mode) for any internet-facing /
public-edge deployment; this removes the Admin API from the
data-plane's blast radius. Traditional mode remains
appropriate for internal-network deployments. (maintainer
— confirmed by Ming Wen 2026-05-15)ApisixRoute /
ApisixConsumer / ApisixPluginConfig CRDs. Documented
duty: creators of ApisixRoute must be the namespace
owner or equivalent trust tier. (maintainer — confirmed
by Ming Wen 2026-05-15)kafka-logger / http-logger to a SIEM. (maintainer —
confirmed by Ming Wen 2026-05-15; §4.14 q-22)Server: APISIX/x.y.z banner if banner-grabbing
is a concern. Use the response-rewrite plugin or a
custom error_page directive. (Version disclosure via
default error page is itself a KNOWN-NON-FINDING — see
§4.11a — but operators who want to harden against it have
a documented path.) (maintainer — confirmed by Ming Wen
2026-05-15; §4.14 q-27)Each is a pattern the PMC has seen often enough to warrant a heads-up. (maintainer — confirmed by Ming Wen 2026-05-15: all 7 below match PMC experience.)
0.0.0.0 with the default
allow_admin widened to 0.0.0.0/0.extra_lua_path or
serverless-pre-function without an internal review.X-Forwarded-For / X-Real-IP as authenticated
identity without the real-ip plugin or the equivalent
upstream-PROXY-protocol handling.apisix.data_encryption).(maintainer — confirmed by Ming Wen 2026-05-15: all 8 below are consistent with the PMC's historical security-report archive.)
conf/config.yaml —
intentional dev-convenience; flagged in the same file's
comment. Reports: OUT-OF-MODEL: operator-misconfig.X-API-KEY-style header auth on the Admin API — the
project's auth scheme by design. Reports asking "why not
OAuth?" are OUT-OF-MODEL: by-design.response-rewrite (see §4.10 point 12).
(maintainer — confirmed by Ming Wen 2026-05-15: confirmed
KNOWN-NON-FINDING.)apache/apisix-dashboard. The dashboard
is excluded from the current scan run per the PMC's
2026-05-26 message. Findings against the dashboard repo
are not part of this model. The previously-tracked
localStorage admin-key persistence finding (q-21) is being
addressed in a separate PR against apache/apisix-dashboard
(atomWithStorage → atom change). When the dashboard
re-enters scan scope, §4.8 will regain its "dashboard does
not persist the admin key" property.gateway_dashboard_unified
mode where the dashboard is served by APISIX itself).apache/apisix-dashboard in scan scope (would
restore the dashboard-related §4.8 property and the
dashboard-specific §4.2 / §4.6 rows).A report against any of the in-scope repos is closed under one of these labels. (maintainer — confirmed by Ming Wen 2026-05-15: the 9 dispositions below are sufficient; no additions or removals.)
| Disposition | When | Body of reply |
|---|---|---|
VALID | violates a §4.8 property; reachable per §4.4. | Acknowledged; coordinated disclosure timeline; fix planned for release. |
VALID-HARDENING | not a §4.8 violation but tightens a §4.9 property in a way the PMC accepts (includes exploitable docs/ example snippets). | Acknowledged; lower priority; may merge as hardening PR. |
OUT-OF-MODEL: operator-misconfig | the issue is an instance of §4.3 point 1 or §4.11 (operator widened a default, did not rotate the admin key, ran etcd unauth, etc.). | Cite the relevant §4.5a / §4.10 row. |
OUT-OF-MODEL: trusted-input | report supplies attacker-controlled bytes through a parameter the §4.6 table marks "not attacker-controllable" (e.g., a format literal, a plugin's script field — note that the script example is valid until the next-release removal lands, per §4.14 q-15). | Cite the §4.6 row. |
OUT-OF-MODEL: adversary-not-in-scope | the threat requires capabilities the §4.7 adversary doesn't have (operator credentials, etcd compromise, host compromise, side-channel, compromised log sink). | Cite the §4.7 adversary list. |
OUT-OF-MODEL: out-of-tree-code | finding is in operator-supplied Lua / external-plugin code / a vendored library that is itself out of model. | Cite §4.3 point 4 or §4.2 component-family table. |
OUT-OF-MODEL: unsupported-platform | report is against a non-Linux deployment (macOS dev/test, Windows, BSD, etc.). | Cite §4.5 OS row. |
BY-DESIGN: property-disclaimed | finding hits an explicit §4.9 non-property (no plugin sandbox, no RBAC, etc.). | Cite the §4.9 line. |
KNOWN-NON-FINDING | exact match to a §4.11a entry. | Cite §4.11a line; one-paragraph reply. |
MODEL-GAP | the report describes a real bug, but this document does not have a §4.8 property covering it. | The PMC will decide whether to add a §4.8 line (turning into VALID) or a §4.9 line (turning into BY-DESIGN). |
All 28 open questions raised in the 2026-05-15 draft were answered by the PMC (Ming Wen, 2026-05-15). The corresponding (inferred) tags in the body have been promoted to (maintainer — confirmed by Ming Wen 2026-05-15). The answer summaries are kept below for traceability.
deps/ and lua_modules/ are
in-model — APISIX runtime dependencies per
apisix-master-0.rockspec. t/lib/ is out-of-model (test
fixtures).docs/) Operator-supplied Lua is out-of-
model. Exception: docs/ example snippets are PMC
responsibility — an exploitable documented example is a
documentation bug triaged as VALID-HARDENING.apisix-ingress-controller/config/rbac/role.yaml. Cluster
operator's duty: creators of ApisixRoute must be the
namespace owner or equivalent trust tier.crypto/tls). APISIX's own
credential comparisons must be constant-time — added as
§4.8 point 9.bin/, t/, etc.) ci/ and .github/ added to the
out-of-model directory list (§4.3 point 10).OUT-OF-MODEL: unsupported-platform
(§4.13).allow_admin widening) OUT-OF-MODEL: operator-misconfig. Same rationale as the default admin
key.deployment.etcd.tls.{cert,key,verify}). Optional for
single-host loopback. Added to §4.10 point 3.OUT-OF-MODEL: operator-misconfig. See §4.5a./apisix/consumers/<username>, per-plugin. Optional
apisix.data_encryption for field-level encryption in 3.x
(not enabled by default). Added as §4.8 point 11.script field) Currently present on
apisix/admin/routes; planned for removal in the next
release. Semantically equivalent to extra_lua_path (out-
of-model per §4.3 point 4). The §4.13 OUT-OF-MODEL: trusted-input example using script stays until the
next-release removal lands.serverless-* interactions) By-design.
serverless-pre-function can rewrite / short-circuit /
bypass later auth plugins; concrete instance of §4.3
point 4. Folded into §4.8 point 6.KNOWN-NON-FINDING-for-current-scope: dashboard is OUT
OF SCAN SCOPE per PMC decision 2026-05-26; the
localStorage admin-key persistence finding is tracked in
a separate PR against apache/apisix-dashboard
(atomWithStorage → atom change). Re-add the
in-memory-only property to §4.8 only when dashboard
re-enters scope. See §4.11a point 8.KNOWN-NON-FINDING.
Operator hardening via response-rewrite / error_page
added to §4.10 point 12.unsupported-platform row was promoted
to a first-class entry on the same pass (per q-7).Revision 2026-05-30 — all originally-inferred claims resolved;
scope reduced to apache/apisix + apache/apisix-ingress-controller
per PMC 2026-05-26 decision (apisix-dashboard tracked separately).