Between 25 August and 11 September 2026, four critical vulnerabilities were disclosed across the AI infrastructure stack: NVIDIA NemoClaw, DeepSeek Harness, IBM Langflow and SGLang. None of them is a model flaw. None involves adversarial prompts, jailbreaks or alignment failures. Every one is an ordinary application security bug — a service bound to the wrong interface, an allowlist that was too broad, a denylist that was too narrow, a control API that never asked who was calling.

The interesting part is not that AI infrastructure has vulnerabilities. It is that these are the same vulnerabilities the industry spent twenty years learning to prevent everywhere else. Missing authentication on a control endpoint. Unsafe deserialisation. Code injection through a parser. If you removed the words "agent" and "inference" from the advisories, they would read like a 2009 web application pentest report.

This post breaks down each of the four with verified detail from the CNA records and vendor advisories, identifies the shared root cause, and — the part most coverage skips — sets out the enforcement controls that actually contain this class of problem. Not "review your AI deployments." Actual admission control policies, network policies, IaC checks and service control policies you can apply this week.

📊 The 18-Day Window

4 Critical CVEs, 25 Aug – 11 Sep
9.8 Highest scored (Langflow)
CWE-94 Code injection, 2 of 4
0 Caused by model behaviour

📋 Table of Contents

🗓ïļ The 18-Day Window: What Actually Shipped

Four disclosures, four different projects, four different vendors. What follows is drawn from the CNA records and vendor security bulletins rather than secondary coverage — the distinction matters, because at least one widely-shared summary of this cluster got the Langflow disclosure date wrong and attached a severity score to SGLang that does not exist in the CVE record.

The Four Disclosures

CVE-2026-65105  NVIDIA NemoClaw      CVSS 8.1   CWE-306   25 Aug 2026
                Missing authentication for critical function.
                NemoClaw for Linux 0 through 0.0.25. Patched 25 Aug.

CVE-2026-82533  DeepSeek Harness     CVSS 9.4   CWE-807   ~08 Sep 2026
                Reliance on untrusted inputs in a security decision.
                Fixed in DeepSeek Harness 0.1.2-alpha.1.

CVE-2026-81204  IBM Langflow OSS     CVSS 9.8   CWE-94    10 Sep 2026
                Code injection during graph construction.
                Langflow OSS 1.0.0 through 1.11.5. Fixed in 1.11.6.

CVE-2026-86793  SGLang               unscored   CWE-94    11 Sep 2026
                Unauthenticated pickle deserialisation, SafeUnpickler bypass.
                SGLang 0 through 0.5.18. Assigner: CERT/CC.

A note on that last row. SGLang's CVE record carries no CVSS score — the metrics object is empty and NVD analysis was still pending at the time of writing. Several outlets have described it as critical. That may well prove correct once it is scored, but until then, quoting a number for it means inventing one. Unauthenticated remote code execution on a model-serving host does not need a score to justify attention.

ðŸŽŊ Why This Cluster Is Worth Reading Together

  • Different layers, one pattern: deployment wrapper, agent runtime, orchestration framework, inference server — the same failure mode at each.
  • Defaults, not misconfiguration: in three of four cases, the shipped default configuration was the vulnerable one.
  • Two are repeat offenders: Langflow and SGLang had each already patched closely related bugs before these.
  • All four are containable with existing controls: nothing here needs a new category of tooling.

🔁 The Same Bug, Four Times

Strip away the product names and two CWE classes account for all four disclosures.

CWE-306, missing authentication for critical function — NemoClaw. The Ollama API on port 11434 has no authentication at all. It was designed for local use, where the loopback boundary is the authentication. Bind it to 0.0.0.0 and that boundary disappears, taking the entire security model with it.

CWE-94, improper control of generation of code — Langflow and SGLang. Both projects accept structured input, both attempt to restrict what that input can reach, and both restriction mechanisms are incomplete.

CWE-807, reliance on untrusted inputs in a security decision — DeepSeek Harness. A local API trusting a client-supplied Host header to decide whether a caller is legitimate.

Beneath the CWE labels sits something more specific, and it is the actual lesson of the cluster: three of these four are allowlist-too-broad or denylist-too-narrow failures. SGLang permits any name from the builtins module and then blocks a handful of dangerous ones. Langflow's code scanner uses a denylist that omits process-spawning primitives. In both cases the attacker never breaks a rule — they find something the rule did not enumerate.

This is the oldest known weakness of negative security models, and it is why the enforcement section of this post is built around denying by default and permitting narrowly, rather than around blocking specific known-bad patterns.

There is a second, structural factor. Every one of these four projects exists to make it easy for models, code and local environments to interact at high velocity. Ease of integration and strict access control pull in opposite directions, and in each case the default configuration resolved that tension in favour of ease. NemoClaw binds Ollama to all interfaces because container networking requires the host service to be reachable from inside a Docker sandbox. That is a reasonable engineering decision in isolation. It stops being reasonable the moment the service it exposes has no authentication.

🧎 NemoClaw: Model Template Poisoning Is Not Prompt Injection

CVE-2026-65105 was disclosed on 25 August by Elad Luz and Ofek Itach of Oasis Security, now part of Cyera. NVIDIA rates it High at CVSS 8.1 and classifies it as CWE-306, missing authentication for critical function. NemoClaw for Linux versions 0 through 0.0.25 are affected, and NVIDIA's bulletin describes the impact as information disclosure or denial of service.

That impact description undersells what the researchers actually demonstrated.

The Configuration

NemoClaw runs OpenClaw agents inside NVIDIA OpenShell sandboxes and can use Ollama as a local inference backend, so models run on the developer's own hardware rather than a cloud API. To let the Docker-based sandbox reach Ollama on the host, NemoClaw launches it with:

The Vulnerable Default

OLLAMA_HOST=0.0.0.0:11434

Users are told Ollama is available on localhost:11434. In practice it is reachable from the entire local network segment. Ollama's API requires no authentication; it relies on CORS controls and Host-header validation to restrict browser-originated requests. And the researchers found that Ollama skips Host-header validation when its bind address is non-loopback. Setting 0.0.0.0 therefore disables the one browser-facing control that was protecting it.

The Attack Path

Two paths, and the simpler one is worth stating first: any device on the same network segment can reach the Ollama API directly. No exploit chain required. The 0.0.0.0 bind turns every NemoClaw instance into an open endpoint for anyone on that network. NVIDIA's CVSS vector reflects this with an adjacent-network attack vector, low complexity, no privileges and no user interaction.

The second path reaches machines that are not on the attacker's network, using DNS rebinding — a browser technique that has been understood for well over a decade:

DNS Rebinding Against a Local Inference Server

1. Victim visits attacker-controlled domain.
   evil.example  ->  A  203.0.113.10   (attacker's server, short TTL)

2. Page loads. JavaScript begins polling its own origin.

3. TTL expires. Attacker's DNS re-answers for the same hostname:
   evil.example  ->  A  127.0.0.1      (or a 192.168.x.x address)

4. Browser still considers requests to be same-origin for
   evil.example, so it sends them without CORS restriction.

5. Requests now land on the victim's local Ollama API.
   Host-header validation is skipped because the bind address
   is non-loopback. The request is served.

A single visit to a malicious page is enough. No credentials, no user interaction beyond loading the page, no network position.

Why the Payload Matters More Than the Path

DNS rebinding is old. What the researchers did once inside is not.

Ollama exposes /api/show, which returns a model's existing chat template, and /api/create, which writes one. The chat template is a Go text/template that controls how messages are rendered before they reach the inference engine. An attacker fetches the template, injects instructions into it, and re-uploads the poisoned version:

Template Poisoning Flow

# 1. Read the current template
GET  /api/show          {"name": "llama3"}

# 2. Modify the Go text/template that renders every message
#    Injected instructions are appended to the system message
#    render path, not to any single conversation.

# 3. Write it back
POST /api/create        {"name": "llama3", "modelfile": ""}

# Result: every future render of a system message through this
# model carries the attacker's instructions. The change survives
# process restarts and new sessions.

This is categorically different from prompt injection, and the distinction is worth being precise about because the two get conflated constantly.

Prompt injection is per-query. Malicious content enters a single conversation, influences that conversation, and ends with it. It is a content problem, and content-layer guardrails are the appropriate control.

Template poisoning is structural. The template sits between the client's messages and the inference engine — one layer beneath where guardrails and operator tooling observe. Elad Luz described the poisoned template as sitting below guardrails and operator visibility, and that is the precise problem. Your prompt-inspection layer sees the message the user sent. It does not see what the template wraps around that message on the way to the model. The compromise persists across every future session until someone thinks to diff the template against a known-good copy, which is not a check most teams run or even know exists.

🔍 The Practitioner's Takeaway

  • Model artefacts are mutable state, not static config. If an API can rewrite a chat template at runtime, that template belongs in your integrity monitoring scope.
  • Loopback is not an access control. It is an accident of network topology that a single environment variable can remove.
  • Guardrails inspect the wrong layer. Content filters operate above the template. A poisoned template is invisible to them by construction.
  • The root cause was a deployment decision. Ollama did nothing wrong for its intended use; NemoClaw's wrapper changed the threat model without changing the controls.

Both of the controls that would have stopped this are boring and well understood: bind local services to loopback and route sandbox traffic explicitly, or put authentication in front of the API. The enforcement section later in this post covers how to make the first one non-negotiable in a Kubernetes environment.

🔓 DeepSeek Harness: The Agent That Freed Itself

CVE-2026-82533 was found by OX Research and disclosed to VulnCheck as CNA on 24 August. It carries CVSS 9.4 and CWE-807, reliance on untrusted inputs in a security decision. It was remediated in DeepSeek Harness 0.1.2-alpha.1.

DeepSeek Harness — dsh — is DeepSeek's open-source, local-first harness for running AI coding agents. It presents a browser UI backed by a local HTTP API on port 3080, and it is built on a plugin architecture; its own tagline is that everything is a plugin. Released in August 2026, it passed 215,000 GitHub stars within weeks.

Why a Coding Harness Is a High-Value Target

A coding-agent harness is worth attacking for exactly the reason it is worth using: it holds a shell. The agent reads and writes source trees, runs build and test commands, and operates with the ambient authority of the developer who launched it. That authority potentially includes SSH keys, cloud credentials, package registry tokens, and every internal system reachable from that workstation.

Which is why the harness ships a sandbox. And why the sandbox mattered.

The Escape

The harness exposed an unauthenticated API on a local port and relied solely on client-supplied Host headers to decide whether a caller was legitimate. Separately, the OS-level sandbox that confined the agent left loopback networking open.

Those two decisions are individually defensible and jointly fatal. The control API that governs the sandbox was reachable from inside the sandbox, and it did not authenticate its callers. In OX Security's description, the agent ran a single command from inside the sandbox to call that API and elevate its session to full access with approval prompts disabled — effectively disabling its own confinement on the shipped default configuration.

The Shape of the Escape

Inside the sandbox:
  - filesystem restricted
  - approval prompts required for privileged actions
  - loopback networking:  OPEN          <-- the gap

The harness control API:
  - listening on 127.0.0.1:3080
  - authentication:  none
  - caller identity:  taken from the client-supplied Host header

Result:
  one HTTP request from the confined process to the control API
  -> session elevated to full access
  -> approval prompts disabled
  -> confinement removed

No network exposure required. No credentials required.
Shipped defaults.

The Principle This Violates

This is a control-plane-in-the-data-plane failure, and it is one of the oldest architectural mistakes in confinement design. If a sandboxed process can reach the interface that configures its own sandbox, the sandbox is advisory rather than enforced.

The same principle is why you do not mount the Docker socket into a container you do not fully trust, why the Kubernetes API server should not be freely reachable from every pod, and why an EC2 instance role should not carry permission to modify the security group protecting it. The confined thing must not be able to reach the thing doing the confining.

Two independent controls would each have prevented it. Authenticating the local API means the request fails regardless of where it originates. Closing loopback networking inside the sandbox means the request never reaches the API at all. Neither was in place, and note that the second is a network-layer control — which is why the enforcement section of this post leans heavily on network policy rather than on application configuration you have to trust an upstream project to get right.

⚠ïļ Adoption Velocity Is a Security Variable

A project reaching 215,000 stars within weeks of release means insecure defaults propagate to a very large installed base before anyone has run a security review against them. The disclosure timeline here is roughly one month from release to critical CVE — faster than most organisations' process for evaluating a new developer tool, and far faster than most vulnerability management cycles.

For anyone maintaining an approved-tooling list, the practical implication is that "widely adopted" and "reviewed" have decoupled. Star count is a popularity signal, not an assurance signal.

🔁 Langflow: A Repeat Offender Case Study

CVE-2026-81204 carries CVSS 9.8 and CWE-94. IBM's CNA record describes it as code injection during graph construction, affecting Langflow OSS 1.0.0 through 1.11.5, with the fix in 1.11.6. It was reserved on 26 August and published on 10 September.

Taken alone, that is one more critical RCE in a busy month. Taken in context, it is the fourth in a series, and the series is the story.

The Sequence

Langflow OSS: Escalating Disclosures

CVE-2025-3248     affects < 1.3.0
                  Unauthenticated RCE via /api/v1/validate/code.
                  Crafted HTTP request reaches the code validation
                  endpoint. Public PoC exploits available.

CVE-2026-9198     CVSS 9.8    fixed in 1.10.1
                  Chain: /api/v1/auto_login mints SUPERUSER bearer
                  tokens to any network caller; /api/v1/validate/code
                  executes user code via exec(). Full RCE on default
                  deployments.

CVE-2026-10561    CVSS 10.0   PythonREPLComponent
                  get_globals() builds a restricted globals dict from
                  a global_imports allowlist (default: "math") but
                  never sets the builtins key to an empty dict.
                  CPython's exec() then inserts the full builtins
                  module automatically. import, open and eval become
                  reachable regardless of the allowlist.

CVE-2026-81204    CVSS 9.8    fixed in 1.11.6
                  Code injection during graph construction.
                  One of ten CVEs in a single September advisory.

That last line deserves emphasis. IBM's September security bulletin for Langflow references ten CVEs at once — CVE-2026-76059, 78569, 78571, 78575, 79724, 79742, 81204, 81211, 81940 and 81941 — with two rated 9.8 and the remainder at 8.8. CVE-2026-81204 is not an isolated finding. It is one entry in a batch disclosure.

What Keeps Going Wrong

Read the sequence and a single design decision propagates through all of it: Langflow executes user-supplied Python by design, and each fix has patched a specific execution path rather than the fact that execution paths exist.

The CVSS 10.0 entry is the clearest illustration. The developers built a restricted execution environment by constructing a globals dictionary from an allowlist. That is a reasonable idea. But CPython inserts the full builtins module into any globals dictionary that does not explicitly contain the builtins key — a documented interpreter behaviour, not an exotic edge case. The allowlist was therefore decorative. Anything in builtins remained reachable, and builtins includes import, open and eval.

The severity multiplier across the whole sequence is a default setting. LANGFLOW_AUTO_LOGIN defaults to true, and /api/v1/auto_login issues a superuser JWT to any caller with no credentials. That single default converts every code-execution bug in the product from "authenticated RCE" into "unauthenticated RCE." IBM's own bulletin describes the impact of the PythonREPL flaw as arbitrary OS command execution at backend process privilege — root in the default Docker image — along with LLM provider key exfiltration from the environment and database, flow definition theft and tampering, vector store credential access, and persistence.

ðŸŽŊ What This Means Operationally

  • Patch velocity is not the metric here. IBM patched each of these promptly. The class of bug survived every patch.
  • Treat Langflow as an execution engine, not an application. Anything that runs user-supplied Python is a code execution service. Scope its permissions accordingly.
  • Audit the default that amplifies. A single auto-login default is what makes every other flaw unauthenticated. That is the highest-leverage thing to change.
  • Version-pinning is not enough. A batch of ten CVEs in one advisory means the next one is likely already in the code.

For the record, EPSS puts CVE-2026-81204 at roughly a 0.6% chance of exploitation in the next 30 days, and it is not currently on CISA's KEV list. That is a reasonable input to prioritisation, but it should not be read as reassurance: EPSS models observed exploitation activity, and a batch disclosure with public PoCs for earlier entries in the same series is not a stable base rate.

ðŸŠĪ SGLang: When the Mitigation Inherits the Flaw

CVE-2026-86793 was assigned by CERT/CC, reserved on 8 September and published on 11 September, with the technical analysis published by VicOne. It is classified CWE-94 and affects SGLang versions 0 through 0.5.18. As noted earlier, the CVE record carries no CVSS score at the time of writing.

The CNA description is unusually precise, so it is worth taking in full rather than paraphrasing: SGLang allows unauthenticated pickle deserialisation through /update_weights_from_tensor when no auth keys are configured, and the SafeUnpickler policy can be bypassed because the import and getattr builtins are resolvable, enabling code execution via pickle REDUCE.

Two independent failures, and the second is the interesting one.

The Endpoint

/update_weights_from_tensor does what its name says: it accepts tensor data and updates the served model's weights. It is marked as admin-optional, which in practice means it accepts unauthenticated requests when no API key is configured — and no API key is the default for a framework most people first run locally.

SGLang has been here before. CERT/CC advisory VU#665416 covered CVE-2026-3059 and CVE-2026-3060, unsafe pickle deserialisation in the multimodal generation module and the Encoder Parallel Disaggregation system, plus CVE-2026-3989 in a crash-dump replay script. CERT/CC's recommendation in that advisory was blunt: project maintainers should avoid implementing pickle functions at all, given the inherent risks.

The Mitigation That Repeated the Mistake

SGLang introduced SafeUnpickler in response to an earlier deserialisation vulnerability, CVE-2025-10164. It works by restricting which Python modules can be loaded during deserialisation, using an allowlist of module prefixes and a denylist of specific classes.

Both halves were wrong in the same direction.

The Bypass

# The allowlist permits the prefix "builtins."
#   -> ANY name in the builtins module is allowed
#      unless explicitly denied.

# The denylist blocks:
#   eval, exec, compile, open
#
# The denylist does NOT block:
#   __import__, getattr        <-- the gap

# Gadget chain, staying entirely within the rules:

  builtins.__import__("os")                 # allowed: not denied
  builtins.getattr(os_module, "system")     # allowed: not denied
  os.system("touch /tmp/poc_confirmed")     # arbitrary execution

# Why the guard never fires:
#   find_class() inspects only the module and name it is handed.
#   It sees ("builtins", "__import__") and ("builtins", "getattr").
#   It never sees ("os", "system") — that pair is resolved at
#   runtime by the gadgets themselves, after the check has passed.

The restrictions are not broken. They are satisfied. Every call in the chain is permitted by the policy as written, and the dangerous capability is assembled from permitted parts after the check completes.

Why This Generalises

This is a textbook demonstration of why denylists fail, and specifically why they fail against composition. A denylist can only block capabilities its author enumerated. eval and exec are obviously dangerous, so they were listed. getattr is a mundane attribute lookup that appears in ordinary code constantly — right up until you hand it a module object and a method name.

The allowlist half compounds it. Permitting the entire builtins namespace and then subtracting known-bad names inverts the security model: the default is allow, and safety depends on the completeness of a list that can never be complete. An allowlist of the specific classes actually required for weight deserialisation — likely a short list of tensor and numeric types — would have made the gadget chain unavailable without anyone needing to anticipate it.

As the research put it, secure deserialisation cannot rely only on blocking known dangerous functions.

🔍 The Structural Lesson

  • Pickle is not securable by filtering. CERT/CC's advice to avoid it entirely predates this bypass and is the correct reading.
  • A mitigation is code, and code has bugs. SafeUnpickler was written to close a deserialisation hole and opened another. Mitigations need the same review as features.
  • Prefix allowlists are almost always too broad. Allowing a namespace and denying members of it is a negative model wearing a positive model's clothing.
  • Authentication would have made the parser bug unreachable. Two independent failures had to line up. Fixing either breaks the chain — which is the argument for defence in depth stated concretely.

That last point is the bridge to the rest of this post. Every one of these four vulnerabilities required a specific configuration to be exploitable, and in every case that configuration was the shipped default. You cannot review the source of every AI framework your teams adopt. What you can do is make the vulnerable configurations impossible to deploy in your environment, regardless of what the upstream project ships.

ðŸ›Ąïļ Enforcement, Not Guidance

Most coverage of these four CVEs ends with advice: review your AI deployments, apply least privilege, keep components patched. All true, all unactionable. Here is the version you can apply.

The organising principle comes straight out of the SGLang bypass. Every one of these vulnerabilities was a negative security model failing — a denylist that did not enumerate enough, an allowlist scoped too broadly, a default that permitted rather than denied. So the controls below are all positive models. Deny everything, permit the specific thing required, and enforce it at a layer the application cannot override.

1. Default-Deny Egress for Inference Workloads

This is the highest-value single control, and it addresses the DeepSeek Harness class directly. If a workload cannot open outbound connections it did not need, a sandbox escape yields a shell with nowhere to go.

NetworkPolicy: default-deny in the inference namespace

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: inference
spec:
  podSelector: {}          # every pod in the namespace
  policyTypes:
    - Ingress
    - Egress
---
# Then permit only what is actually needed.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-inference-ingress
  namespace: inference
spec:
  podSelector:
    matchLabels:
      app: model-server
  policyTypes:
    - Ingress
    - Egress
  ingress:
    - from:
        - namespaceSelector:
            matchLabels:
              name: api-gateway
      ports:
        - protocol: TCP
          port: 8000
  egress:
    # DNS only. No general internet egress.
    - to:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: kube-system
          podSelector:
            matchLabels:
              k8s-app: kube-dns
      ports:
        - protocol: UDP
          port: 53
        - protocol: TCP
          port: 53

Two caveats worth stating plainly. NetworkPolicy is enforced by your CNI, not by Kubernetes itself — Calico, Cilium and most managed CNIs support it, but a cluster running a CNI without NetworkPolicy support will accept these objects and silently enforce nothing. Verify with a connectivity test, not by checking that the object exists. And NetworkPolicy operates on pods and namespaces; it does not restrict loopback traffic inside a single pod, which is precisely the gap the DeepSeek Harness escape used. For that you need the sandbox's own network namespace configured to close loopback, which is a runtime concern rather than a Kubernetes one.

2. Admission Control: Make the Vulnerable Configuration Undeployable

NemoClaw's bug was a service bound to all interfaces. In Kubernetes the equivalent exposures are hostNetwork, hostPort and, indirectly, a Service of type NodePort or LoadBalancer pointing at an unauthenticated backend. Block them at admission and the configuration cannot reach the cluster.

Gatekeeper ConstraintTemplate: deny hostNetwork and hostPort

apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
  name: k8sdenyhostnetworking
spec:
  crd:
    spec:
      names:
        kind: K8sDenyHostNetworking
      validation:
        openAPIV3Schema:
          type: object
          properties:
            exemptImages:
              type: array
              items:
                type: string
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package k8sdenyhostnetworking

        violation[{"msg": msg}] {
          input.review.object.spec.hostNetwork == true
          msg := sprintf(
            "hostNetwork is not permitted: %v/%v",
            [input.review.object.metadata.namespace,
             input.review.object.metadata.name]
          )
        }

        violation[{"msg": msg}] {
          container := input_containers[_]
          port := container.ports[_]
          port.hostPort
          msg := sprintf(
            "hostPort %v is not permitted on container %v",
            [port.hostPort, container.name]
          )
        }

        input_containers[c] {
          c := input.review.object.spec.containers[_]
        }

        input_containers[c] {
          c := input.review.object.spec.initContainers[_]
        }

A note on Rego syntax: the template above uses the v0 partial-set style that Gatekeeper's own documentation uses, because that is what most existing ConstraintTemplates in the wild look like. If your Gatekeeper deployment is configured for Rego v1, the rule heads need the contains and if keywords — violation contains msg if — and the partial-set definitions for the container helper need the same treatment. Check which version your installation expects before copying; a v0 policy submitted to a v1 parser fails to compile rather than failing open, so you will know quickly.

Kyverno equivalent, if that is your engine

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: restrict-ai-workload-exposure
spec:
  validationFailureAction: Enforce
  background: true
  rules:
    - name: deny-host-network
      match:
        any:
          - resources:
              kinds:
                - Pod
              namespaces:
                - inference
                - agents
      validate:
        message: "hostNetwork is not permitted for AI workloads."
        pattern:
          spec:
            =(hostNetwork): "false"

    - name: require-non-root-and-readonly-fs
      match:
        any:
          - resources:
              kinds:
                - Pod
              namespaces:
                - inference
                - agents
      validate:
        message: >-
          AI workloads must run as non-root with a read-only
          root filesystem.
        pattern:
          spec:
            containers:
              - securityContext:
                  runAsNonRoot: true
                  readOnlyRootFilesystem: true
                  allowPrivilegeEscalation: false

The second rule is aimed at the Langflow class. IBM's bulletin noted that the default Docker image runs as root, which turns a code-execution bug into host compromise. runAsNonRoot does not stop the RCE, but it substantially changes what the attacker gets.

3. Catch the Bind Address Before It Deploys

NemoClaw's vulnerable configuration was a single environment variable. That is exactly the kind of thing IaC scanning is good at, and it costs nothing to check on every pull request.

Rego: reject all-interfaces binds in Kubernetes manifests

package aiinfra.bindaddress

# Environment variables that commonly carry a bind address.
bind_vars := {"OLLAMA_HOST", "HOST", "BIND_ADDRESS",
              "SERVER_HOST", "LISTEN_ADDR", "UVICORN_HOST"}

# Values that expose a service on every interface.
wildcard_binds := {"0.0.0.0", "::", "[::]", "*"}

deny contains msg if {
  some c in input.spec.template.spec.containers
  some e in c.env
  e.name in bind_vars
  some w in wildcard_binds
  startswith(e.value, w)
  msg := sprintf(
    "container %q sets %s=%q, exposing the service on all interfaces",
    [c.name, e.name, e.value]
  )
}

# Catch the same thing passed as a command-line argument.
deny contains msg if {
  some c in input.spec.template.spec.containers
  some arg in c.args
  contains(arg, "0.0.0.0")
  msg := sprintf(
    "container %q passes an all-interfaces bind in args: %q",
    [c.name, arg]
  )
}

This one is written in Rego v1 — deny contains msg if, with some ... in iteration and the in membership operator. Running it under conftest or a recent OPA needs no import; under OPA v0 you would add import future.keywords or rewrite the heads.

One honest limitation: matching on environment variable names is a heuristic. It catches the common cases and will miss a project that spells its bind address differently, or sets it in a config file baked into the image rather than in the manifest. Treat it as one layer, not the control. The admission policies above are the enforcement; this is the early warning.

Checkov custom check, for Terraform-managed deployments

# .checkov/custom_checks/ai_bind_address.py
from checkov.common.models.enums import CheckCategories, CheckResult
from checkov.terraform.checks.resource.base_resource_check import (
    BaseResourceCheck,
)

WILDCARD = ("0.0.0.0", "::", "[::]")
BIND_VARS = {
    "OLLAMA_HOST", "HOST", "BIND_ADDRESS",
    "SERVER_HOST", "LISTEN_ADDR", "UVICORN_HOST",
}


class AIServiceBindAddress(BaseResourceCheck):
    def __init__(self):
        super().__init__(
            name="AI inference service must not bind all interfaces",
            id="CKV_AI_1",
            categories=[CheckCategories.NETWORKING],
            supported_resources=["kubernetes_deployment",
                                 "kubernetes_deployment_v1"],
        )

    def scan_resource_conf(self, conf):
        specs = conf.get("spec") or []
        for spec in specs:
            for tmpl in spec.get("template", []):
                for tspec in tmpl.get("spec", []):
                    for container in tspec.get("container", []):
                        for env in container.get("env", []):
                            name = env.get("name")
                            value = str(env.get("value", ""))
                            if name in BIND_VARS and value.startswith(WILDCARD):
                                return CheckResult.FAILED
        return CheckResult.PASSED


check = AIServiceBindAddress()

Terraform's Kubernetes provider nests blocks as lists, which is why the traversal looks the way it does. Verify the shape against your own plan output before relying on it — provider versions have changed this structure before, and a check that silently traverses nothing returns PASSED, which is the worst possible failure mode for a security control. Write a failing test fixture first and confirm the check actually fails on it.

4. Bound the Blast Radius with SCPs

Everything above is Kubernetes-layer. The AWS-layer question is different: when an agent runtime is compromised, what can the credentials it holds actually do?

The DeepSeek Harness advisory is explicit that the agent operates with the ambient authority of the developer who launched it — potentially SSH keys, cloud credentials and package registry tokens. If that developer's role can assume a production role, so can the attacker.

SCP: prevent AI workload roles from altering their own controls

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DenySelfModificationOfNetworkControls",
      "Effect": "Deny",
      "Action": [
        "ec2:AuthorizeSecurityGroupIngress",
        "ec2:AuthorizeSecurityGroupEgress",
        "ec2:RevokeSecurityGroupIngress",
        "ec2:ModifyInstanceAttribute",
        "ec2:CreateVpcPeeringConnection",
        "ec2:ModifyVpcEndpoint"
      ],
      "Resource": "*",
      "Condition": {
        "StringLike": {
          "aws:PrincipalArn": "arn:aws:iam::*:role/ai-workload-*"
        }
      }
    },
    {
      "Sid": "DenyIamMutationFromAIWorkloads",
      "Effect": "Deny",
      "Action": [
        "iam:CreateRole",
        "iam:AttachRolePolicy",
        "iam:PutRolePolicy",
        "iam:CreateAccessKey",
        "iam:UpdateAssumeRolePolicy",
        "iam:CreateUser"
      ],
      "Resource": "*",
      "Condition": {
        "StringLike": {
          "aws:PrincipalArn": "arn:aws:iam::*:role/ai-workload-*"
        }
      }
    }
  ]
}

Three things to keep straight about SCPs, because they are the most commonly misunderstood control in this list. They set a permissions ceiling; they never grant anything, so the role still needs identity-based policies for what it should do. They do not apply to the management account at all — a deliberate design choice that surprises people the first time they test there. And the aws:PrincipalArn condition above depends entirely on a naming convention actually being followed; if someone creates an agent role outside the ai-workload- prefix, the SCP does not apply to it. Enforcing the naming convention is a separate control, typically an IAM permissions boundary or a policy on role creation.

Test any new SCP against a non-critical account in a dedicated OU before attaching it anywhere that matters. A too-broad Deny in an SCP is one of the faster ways to take down a production workload.

🔎 Detecting Exposed Inference Endpoints

Prevention handles what you deploy from now on. Detection handles what is already running.

In the Cluster

Find the exposures these CVEs depend on

# Pods using the host network namespace
kubectl get pods -A -o json \
  | jq -r '.items[]
      | select(.spec.hostNetwork == true)
      | "\(.metadata.namespace)/\(.metadata.name)"'

# Any container declaring a hostPort
kubectl get pods -A -o json \
  | jq -r '.items[]
      | . as $p
      | .spec.containers[]
      | select(.ports != null)
      | .ports[]
      | select(.hostPort != null)
      | "\($p.metadata.namespace)/\($p.metadata.name) hostPort=\(.hostPort)"'

# Services exposed beyond the cluster
kubectl get svc -A -o json \
  | jq -r '.items[]
      | select(.spec.type == "NodePort" or .spec.type == "LoadBalancer")
      | "\(.metadata.namespace)/\(.metadata.name) \(.spec.type)"'

# Namespaces with no NetworkPolicy at all
for ns in $(kubectl get ns -o jsonpath='{.items[*].metadata.name}'); do
  count=$(kubectl get netpol -n "$ns" --no-headers 2>/dev/null | wc -l)
  [ "$count" -eq 0 ] && echo "no NetworkPolicy: $ns"
done

# Wildcard binds set via environment variable
kubectl get deploy -A -o json \
  | jq -r '.items[]
      | . as $d
      | .spec.template.spec.containers[]
      | select(.env != null)
      | .env[]
      | select(.value != null and (.value | startswith("0.0.0.0")))
      | "\($d.metadata.namespace)/\($d.metadata.name) \(.name)=\(.value)"'

In the Cloud Account

Ollama's default port is 11434 and the DeepSeek Harness API listens on 3080. Other frameworks vary, so check the ports your own deployments actually use rather than trusting a list.

Security groups permitting broad access to inference ports

aws ec2 describe-security-groups \
  --filters Name=ip-permission.cidr,Values=0.0.0.0/0 \
  --query 'SecurityGroups[].{
      Id:GroupId,
      Name:GroupName,
      Ports:IpPermissions[?FromPort!=`null`].[FromPort,ToPort]
    }' \
  --output json

# Narrow to a specific port once you know what you run
aws ec2 describe-security-groups \
  --filters Name=ip-permission.from-port,Values=11434 \
            Name=ip-permission.cidr,Values=0.0.0.0/0 \
  --query 'SecurityGroups[].GroupId' --output text

In Your CSPM

If you run Wiz, Prisma, Defender for Cloud or similar, the query you want is a compound one. Not "find internet-exposed resources" — you will get thousands. The useful shape is the toxic combination:

  • A compute resource running a known AI framework image — match on image repository or a workload label — that is network-exposed via a public IP, a LoadBalancer, or a security group open to a wide CIDR, and holds a cloud identity with write permissions or the ability to assume another role.
  • Any container image from an AI framework registry whose tag has not changed in more than 30 days. Given the disclosure cadence described here, an unchanged AI framework image is a stale one.
  • Nodes running inference workloads where the instance profile grants iam:PassRole or sts:AssumeRole without a resource constraint.

Each platform expresses this differently and the exact query syntax changes between versions, so the descriptions above are deliberately platform-neutral. The point is the shape: exposure alone is noise, and identity alone is noise. Exposure plus identity plus a framework with an active disclosure cadence is a finding worth paging someone about.

✅ What to Check This Week

Ordered by effort-to-value, not by severity.

Audit checklist

[ ] Inventory. Which AI frameworks are actually running?
    Search image repositories and Helm releases for: ollama,
    langflow, sglang, vllm, and any agent harness. You cannot
    patch what is not on a list.

[ ] Version check against the four disclosures:
      NemoClaw for Linux   <= 0.0.25      -> patched 25 Aug 2026
      DeepSeek Harness     < 0.1.2-alpha.1
      Langflow OSS         1.0.0 - 1.11.5 -> fix in 1.11.6
      SGLang               <= 0.5.18

[ ] LANGFLOW_AUTO_LOGIN. If you run Langflow anywhere, confirm
    this is not true. It is the single setting that makes every
    other Langflow flaw unauthenticated.

[ ] Bind addresses. Grep manifests and compose files for
    0.0.0.0 alongside any inference service.

[ ] SGLang auth keys. The vulnerable endpoint is unauthenticated
    only when no API key is configured. Configure one.

[ ] Ollama model templates. If a NemoClaw or Ollama instance was
    ever network-reachable, diff current chat templates against
    a known-good copy. Poisoning persists across restarts and
    is invisible to prompt-layer inspection.

[ ] NetworkPolicy coverage. Any namespace running inference with
    zero NetworkPolicy objects is a default-allow namespace.

[ ] Identity scope. What can the AI workload's role do? If the
    answer includes modifying security groups or assuming other
    roles, fix that before anything else on this list.

[ ] Admission control. Are hostNetwork and hostPort blocked at
    admission, or only discouraged in documentation?

ðŸŽŊ Key Takeaways

  • None of these were model flaws. Four critical CVEs across the AI stack, zero involving model behaviour, prompts or alignment. The attack surface is ordinary infrastructure.
  • Defaults are the vulnerability. In three of four cases the shipped default configuration was the exploitable one. Nobody misconfigured anything.
  • Negative security models keep failing the same way. SGLang's denylist omitted getattr. Langflow's allowlist was defeated by documented interpreter behaviour. Enumerate what is permitted, not what is forbidden.
  • Template poisoning is not prompt injection. It is structural, persistent, and sits below the layer your guardrails inspect. Treat model artefacts as mutable state under integrity monitoring.
  • Patch velocity did not save Langflow. Four escalating CVEs and a ten-CVE batch advisory. Each fix closed an instance; the class survived.
  • Enforce at a layer the application cannot override. You cannot audit the source of every framework your teams adopt. You can make the vulnerable configuration undeployable.
  • Adoption velocity outpaces review. 215,000 stars in weeks means insecure defaults reach a large installed base before anyone reviews them. Popularity is not assurance.

ðŸ”Ū What to Expect Next

Two things follow from this cluster, and neither requires much speculation.

  • The pattern will continue moving down the stack. It has already progressed from agent runtimes to orchestration frameworks to the inference server itself. The layers below — model registries, weight storage, GPU scheduling — have the same properties: designed for trusted internal use, now reachable from places their authors did not anticipate.
  • Pickle will keep producing CVEs. CERT/CC has already advised maintainers to avoid it outright. Until serving frameworks move to a format that is not code-by-design, filtering-based mitigations will keep being bypassed the way SafeUnpickler was.

The defensive posture that survives all of this is the same one that has always worked: assume the component will be vulnerable, and constrain what it can reach when it is.