This continues our attack-research series on the runtime trust boundaries that static tooling cannot see. The MCP unauthenticated tool-invocation piece walked a server that executed tools for a caller it never authenticated. This is the same root cause, CWE-306, Missing Authentication for Critical Function, moved into the gap between a product's components: not the API the vendor documents and defends, but the auxiliary process they ship alongside it and quietly assume nobody else can reach. The flagship's authentication is correct. The auxiliary's absence of authentication is the whole finding. Verifiable security.
The attack pattern in one paragraph
A modern product is rarely one process. It is a primary daemon plus a handful of helpers: a database, a metrics exporter, a license manager, a search head, a queue, a sidecar that does some narrow job the main process delegates. The vendor authenticates the primary daemon because that is the surface customers see and auditors test. The helpers speak a private protocol to each other, so the team reasons that "it only listens for the main process" and skips authentication entirely. That reasoning holds exactly as long as the helper is bound to loopback and nothing else can reach it. The instant the helper binds to 0.0.0.0, or sits on a host segment an attacker can reach, or is exposed by a misconfigured firewall rule that was scoped for the primary port, the private protocol becomes a public one. In CVE-2026-20253 the helper is a PostgreSQL sidecar bundled with Splunk, and the exposed primitive is file creation and truncation: an unauthenticated, network-reachable caller can create a new file at a path of their choosing or truncate an existing one to zero bytes. Truncation needs no write-content primitive and no code execution to be devastating. Truncate the running config and the service restarts into a default or broken state. Truncate the license file and the product degrades or stops. Truncate the audit log and the trail of what just happened is gone. The attacker never authenticated to anything, because the thing they talked to never asked.
The unifying observation: a product's security boundary is the union of every process it ships, not the perimeter of the one process it documents. The auxiliary service that was "internal by intent" is internal only while the network agrees, and the network is configured by someone who was looking at the front door.
Why this still ships in 2026
If the primary application gets authentication right, why does the helper beside it ship with none? Three structural reasons, each verifiable against your own hosts in an afternoon.
- The trust boundary is assumed at the loopback, not enforced at the listener. A helper written to be called only by its sibling is coded against
127.0.0.1in the developer's mind. But the bind address is a config value, and config values drift: a containerized build that needs the sidecar reachable from another container sets the bind to0.0.0.0, a Helm chart exposes the port as a Service, an operator opens the port "to debug" and forgets. The code never gained authentication because the deployment was supposed to guarantee isolation. CVE-2026-20253 is exactly this shape: the PostgreSQL sidecar endpoint had no authentication because the architecture assumed only the main process would ever speak to it. - Truncate is a forgotten primitive. Security review fixates on arbitrary file write with attacker-controlled content, because that is the path to code execution. File create and truncate look harmless by comparison: you cannot choose the bytes, only that the file now exists or is now empty. But integrity and availability do not require chosen bytes. An empty config, an empty license, an empty audit log, a newly created lock file or trigger file in a watched directory: each is an impact in its own right. The CVSS 9.8 on CVE-2026-20253 reflects exactly this, network-reachable, unauthenticated, high impact to integrity and availability without ever needing to write a single chosen byte.
- Front-door scanners model the documented surface, not the bundled one. A web scanner crawls the application's authenticated API and login flow. A version scanner fingerprints the product banner and matches CVEs to the primary daemon. Neither enumerates every port the install opened, asks which process owns each one, and tests whether that process authenticates. The auxiliary listener is, from the front-door scanner's point of view, just an open port with an unfamiliar protocol, easy to log and easy to ignore. The dangerous fact, that this specific port belongs to a bundled helper that authenticates nothing, requires modelling the host as a set of trust boundaries between co-located processes, which is precisely what a front-door crawl does not do.
The attacker decision tree
The path from a second open port to an integrity and availability compromise. The decisive crossing is step 1 to step 2: the moment an unauthenticated helper answers, the front-door login that protects the primary daemon is irrelevant.
The decisive junction is step 1 to step 2. Everything before it is reconnaissance an unauthenticated attacker can do from the network: enumerate listeners, attribute each to a process, and ask each one a question without credentials. The instant the bundled helper answers a request it should have rejected, the primary application's authentication, however correct, is bypassed, because the attacker never went near it. A scanner that confirmed the login page enforced MFA, the API rejected anonymous calls, and the documented endpoints were patched saw three green checks. The attacker who spoke to the port next door owns the file system the product trusts.
A walk through the boundary crossing
To make the crossing concrete, here is the path against a deployment of the kind CVE-2026-20253 describes, with the destructive last step omitted. The target runs the primary product daemon on its documented, authenticated port, and a bundled PostgreSQL sidecar helper on a second port that the container build bound to a routable interface rather than loopback.
Step 1, enumerate every listener. The attacker, holding a network position that can reach the host (an adjacent segment, a compromised neighbor, or in the worst case the public internet behind a firewall rule scoped only for the primary port), maps the open ports and attributes each to a process. The documented application port answers with an authenticated login. A second port answers a different protocol with no challenge.
# The documented app port enforces auth; the sidecar next to it does not.
$ curl -s -o /dev/null -w "%{http_code}\n" https://target:8000/ # 401, front door is gated
401
$ curl -s http://target:8191/health # the bundled helper, no auth
{"status":"ok","role":"pg-sidecar"} # answers an unauthenticated caller
Step 2, probe the primitive. The helper exposes a file operation that was meant for the primary process to call during normal housekeeping. Reachable without authentication, it answers the attacker just as readily. The CVE-2026-20253 primitive is create-or-truncate at a caller-supplied path.
# The sidecar endpoint creates or truncates a file at a path the caller chooses.
# No credentials, no chosen content needed: existence and zero-length are the impact.
$ curl -s -X POST http://target:8191/internal/file/truncate \
--data 'path=/opt/product/etc/audit/audit.log'
{"result":"truncated","path":"/opt/product/etc/audit/audit.log","bytes":0}
Step 3, target a sensitive file. The attacker does not need to write chosen bytes. They pick a file whose emptiness or sudden existence changes the system's behavior. Truncating the running configuration forces the service to restart into a default or broken state. Truncating the license file degrades or halts the product. Truncating the audit log erases the record of the request that just happened. Creating a file in a directory the product watches, a queue, a trigger directory, a lock path, induces the product to act on a file the attacker placed.
# Three high-impact targets for a create-or-truncate primitive, no write-content needed:
# /opt/product/etc/server.conf -> truncate: restart into default/broken state
# /opt/product/etc/splunk.license -> truncate: licensing degrades or halts
# /opt/product/var/log/audit.log -> truncate: the trail of this attack is gone
# Availability and integrity fall without a single attacker-chosen byte of content.
Step 4, the impact, and the omitted step. At this point the integrity and availability loss is already real: the audit log is empty, or the config is gone, or the license is zeroed. We stop here. The destructive escalation, repeating the primitive across a fleet that all shipped the same default bind, or chaining a now-degraded primary daemon, is left to the imagination and not to this page. The point is made at step 2: the helper answered a request it should have rejected.
No memory corruption, no zero-day in the flagship binary, no credential theft. Each step is a bundled helper doing exactly what it was built to do, for a caller it was never built to expect. A row of "login gated, API authenticated, primary daemon patched" green checks, and the file system the product trusts is an attacker's to empty.
Why scanners miss the auxiliary trust boundary
The reason this class survives a clean front-door report is mechanical. The dangerous property is a relationship between two runtime facts: which process owns each open port, and whether that process authenticates the caller. Neither fact lives in the documented API surface a front-door scanner crawls.
- Version scanners fingerprint the primary daemon. They read the product banner, match CVEs to the flagship version, and report it patched. They do not enumerate the other processes the install started, so the bundled sidecar with its own version and its own missing-authentication flaw is invisible to a banner match on the main product.
- Web and API scanners crawl the authenticated surface. They exercise the login flow and the documented endpoints, and they correctly report those gated. The auxiliary listener speaks a different protocol on a different port and is not part of the crawl, so its lack of authentication is never tested. A clean API report says nothing about the helper next to the API.
- Port scanners log the open port without attributing the trust boundary. A network scan lists the second port as open and unrecognized. That is a fact, not a finding. Turning it into a finding requires knowing that this port belongs to a bundled helper that authenticates nothing, which is a model of the host as co-located processes with trust boundaries between them, not a list of open sockets.
The honest read: unauthenticated auxiliary-service exposure is a high-impact, structurally-missed finding because the vulnerability is the runtime relationship between a port, the process behind it, and that process's authentication, and no single-artifact front-door scan models all three.
What to do about it: gate the helper, not just the front door
The leverage in defense is that you do not have to harden every endpoint the helper exposes. You have to cut one of two edges: make the helper unreachable by untrusted callers, or make it authenticate the ones that reach it. Sever either and the tree above collapses even if the file primitive itself is never fixed.
Auxiliary-service hardening contract: edges to cut
- Bind every internal helper to loopback, not to a routable interface. A sidecar that only the primary process calls should listen on
127.0.0.1or a Unix domain socket, never0.0.0.0. Audit the bind address of every co-located helper, especially in containerized and Helm-deployed builds where the default drifted to reachable. - Authenticate internal services as if they were external. "Only the main process calls it" is a deployment assumption, not a control. Put a token, mutual TLS, or a Unix-socket peer-credential check on the helper's endpoints so that reaching the port is not the same as being trusted by it. This closes the CWE-306 root cause regardless of bind drift.
- Put a default-deny network policy in front of the helper. Whether on a host firewall or a Kubernetes NetworkPolicy, deny all ingress to the auxiliary port except from the named components that legitimately call it. Scope the firewall rule to the helper, not just the primary daemon, the common gap that exposes the sidecar while gating the front door.
- Run the helper with least privilege over the file system. A file create-and-truncate primitive is only as dangerous as the paths the helper's process can write. Run it under an account that cannot touch the config, license, or audit-log directories of the primary product, so even an exposed endpoint cannot reach the files that matter.
- Protect integrity-critical files independently of the app. Make audit logs append-only or ship them off-host in real time, so a local truncate cannot erase the trail. Treat the config and license as integrity-monitored assets that alert on unexpected size-to-zero.
- Patch the bundled component and verify the boundary, do not assume it. Update Splunk to the fixed releases (10.2.4 or 10.0.7 and later, per the advisory) so the sidecar endpoint requires authentication. Then prove an unauthenticated caller cannot reach or invoke the helper, by attempting both in a controlled, evidence-producing way, and re-attempting after the fix.
A product's attack surface is every process it ships, not the one it documents. The helper that was "internal by intent" is internal only while the network agrees, and the network was configured by someone looking at the front door.
The cheapest first move for an infrastructure lead this week: list every listening socket on the hosts that run the product, attribute each to a process, and for any that is not the documented primary daemon, confirm it is bound to loopback or fronted by a default-deny rule and that it authenticates its callers. That single inventory, done honestly, surfaces the exact edge in every walk above, and it does not require waiting on a vendor patch.
How Celvex catches this
Find. Prove. Fix. Verify.
We enumerate every listener on a scoped asset, not just the documented application port, attribute each to its owning component, and map which auxiliary services answer an unauthenticated caller, using read-only public-vantage and in-scope structural probes.
A confirmed exposure ships a signed Proof Capsule documenting the full path: the reachable helper port, its missing authentication, and the exact unauthenticated request the endpoint answered, captured in a controlled, non-destructive replay that never touches a sensitive file.
The Capsule's remediation block names the cheapest edge to cut, usually the loopback bind or the default-deny rule in front of the helper, so the customer kills the whole chain with one control instead of chasing every endpoint the sidecar exposes.
After the fix, we re-probe the boundary. The auxiliary port is unreachable or now demands authentication, the unauthenticated call is rejected, and the dashboard records a verified-fix event with the severed edge for the audit trail.
Where we sit honestly: today our corpus models the auxiliary-service boundary as a named trust crossing, open listener to owning process to missing authentication to file or integrity primitive, and ships a reproducible Capsule for the exposures it can confirm against scoped assets without ever invoking the destructive primitive. Our near-term work is breadth: extending the listener-attribution probe across more bundled-helper patterns, database sidecars, metrics exporters, license managers, and queue helpers, the same Missing-Authentication primitive on different plumbing. We do not claim to model every product's process tree. We do claim to model the boundary crossing that a front-door scanner structurally cannot: the one where the port next to the authenticated app authenticates nobody. You can see the deeper architecture in our continuous attack surface capability and the evidence format on the Proof Capsule page.
Bottom line
The auxiliary service is the trust boundary nobody owns. The flagship daemon authenticates because that is the surface customers test; the bundled helper beside it authenticates nothing because the team assumed only the flagship would ever call it. CVE-2026-20253 (CVSS 9.8) is the clean case: an unauthenticated, network-reachable PostgreSQL sidecar endpoint bundled with Splunk that lets any caller create or truncate arbitrary files, and create-or-truncate is enough to empty a config, a license, or an audit log without a single chosen byte. Front-door scanners miss it because the vulnerability is the runtime relationship between a port, the process behind it, and that process's missing authentication, not a flaw in the documented API. The fix is leverage: bind the helper to loopback, authenticate it as if it were external, and front it with a default-deny rule, and the whole tree collapses even before the patch lands. Then prove the helper rejects the unauthenticated call, and verify the proof held.
Verifiable security. Find it. Prove it. Fix it. Verify the fix held. That is what we ship.
Sources
- Splunk Advisory SVD-2026-0603: Unauthenticated Arbitrary File Creation and Truncation in a PostgreSQL Sidecar Service Endpoint (CVE-2026-20253, CVSS 9.8)
- NVD: CVE-2026-20253 (unauthenticated file create/truncate in Splunk PostgreSQL sidecar, CWE-306)
- CWE-306: Missing Authentication for Critical Function
- CELVEX Group: Proof Capsule format
- CELVEX Group: Continuous Attack Surface capability
Find out which ports next to your app authenticate nobody.
Free Exposure Check, no signup required. We enumerate every listener on your scoped assets, attribute each to its process, and ship a signed Proof Capsule for the highest-confidence unauthenticated auxiliary service we can reach.
Run a Free Scan →