Vulnerability Analysis

CVE-2026-32613: Spinnaker Echo SpEL Injection RCE — What It Is & How to Fix It

Executive Summary

CVE-2026-32613 is a critical remote code execution (RCE) vulnerability in Spinnaker's Echo microservice, disclosed on April 20, 2026, carrying a CVSS score of 9.9. The flaw stems from unrestricted Spring Expression Language (SPeL) evaluation when processing expected artifact information — allowing any user with pipeline access to invoke arbitrary JVM classes, execute OS commands, and read or modify files on the underlying host. Organizations running Spinnaker for CI/CD must patch or disable Echo immediately.


1. What Is This Vulnerability?

Spinnaker is Netflix's open-source, multi-cloud continuous delivery platform, widely used in enterprise DevOps pipelines to orchestrate deployments across AWS, GCP, Azure, and Kubernetes. Its Echo microservice handles event notifications and artifact matching, using Spring Expression Language (SpEL) to evaluate expressions tied to expected artifacts in pipeline definitions.

The vulnerability exists because Echo evaluates user-supplied SPeL expressions against the full JVM context without restricting which classes are accessible. Unlike Spinnaker's Orca service — which constrains SpEL evaluation to a safe allowlist of trusted classes — Echo performed no such sandboxing. This means a malicious actor who can define or influence pipeline artifact expectations can craft a SpEL expression that calls arbitrary Java classes, ultimately executing OS-level commands on the server running Echo.

Attack Vector

An authenticated user (or a compromised service account) with access to Spinnaker's UI or API can inject a malicious SpEL expression into an expected artifact's configuration. When the Echo service evaluates this expression, the attacker gains code execution in the context of the Echo process.

Example malicious SpEL payload (for illustrative/defensive purposes):

// Exploit: calling Runtime.exec() through SpEL
#{T(java.lang.Runtime).getRuntime().exec('id')}

// More dangerous variant reading files
#{new java.util.Scanner(T(java.lang.Runtime).getRuntime()
  .exec(new String[]{'/bin/bash','-c','cat /etc/passwd'})
  .getInputStream()).useDelimiter('\\A').next()}

Because Echo runs inside the Spinnaker deployment (often with broad cloud credentials or Kubernetes service account permissions), successful exploitation can pivot to cloud infrastructure, secrets stores (e.g., Vault, AWS Secrets Manager), or internal services reachable from the cluster network.

Real-World Impact

As of disclosure, no confirmed public breach has been attributed to CVE-2026-32613. However, the severity is high because:

  • Spinnaker instances are frequently deployed with elevated cloud IAM permissions to orchestrate deployments.
  • Many organizations expose Spinnaker's API internally with broad service account access, widening the blast radius.
  • SpEL injection vulnerabilities in Spring-based systems have a documented exploitation history (e.g., CVE-2022-22963, Spring4Shell CVE-2022-22965), and working proof-of-concept exploitation techniques are well-understood by attackers.

2. Who Is Affected?

Any Spinnaker deployment running the Echo service on an unpatched version is vulnerable:

Spinnaker Release Branch Vulnerable Versions Patched Version
2026.1.x < 2026.1.0 2026.1.0
2026.0.x < 2026.0.1 2026.0.1
2025.4.x < 2025.4.2 2025.4.2
2025.3.x < 2025.3.2 2025.3.2
2025.2.x and earlier All versions (EOL) No patch — upgrade required

Severity modifiers to consider:

  • Higher risk if Echo is accessible to untrusted users, exposed via an API gateway, or if Spinnaker service accounts have broad cloud permissions.
  • Lower risk if Echo is only reachable by a small, trusted set of internal operators and network access is tightly restricted.

3. How to Detect It (Testing)

Manual Testing Steps

  1. Identify your Echo version: Query the Spinnaker Gate API or check your Helm chart / Halconfig to determine which version of Echo is deployed.

    kubectl get pods -n spinnaker -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.containers[0].image}{"\n"}{end}' | grep echo
    
  2. Inspect artifact configuration for user-controlled expressions: Review pipeline definitions in Spinnaker's UI or via API for expected artifact fields that accept text input. Any free-form field that feeds into SpEL evaluation is a potential injection point.

    # Export all pipelines and grep for SpEL expression markers
    curl -s http://<spinnaker-gate>:8084/applications | \
      jq '.[].name' -r | while read app; do
        curl -s "http://<spinnaker-gate>:8084/applications/$app/pipelines"
      done | grep -o '#{\([^}]*\)}' | sort -u
    
  3. Attempt a safe canary SpEL expression (authorized testing only): In a non-production environment with permission, inject a benign expression like #{1 + 1} into an expected artifact name field and observe whether Echo evaluates it and returns 2. Successful evaluation confirms the unsandboxed execution context.

Automated Scanning

Tool: Nuclei with a custom template

Command:

nuclei -t spinnaker-spel-rce.yaml -u http://<spinnaker-gate>:8084 \
  -H "Authorization: Bearer <token>"

Custom Nuclei template (spinnaker-spel-rce.yaml):

id: CVE-2026-32613-spinnaker-spel
info:
  name: Spinnaker Echo SpEL RCE
  severity: critical
  tags: spinnaker,spel,rce,cve-2026-32613

requests:
  - method: POST
    path:
      - "{{BaseURL}}/pipelines"
    headers:
      Content-Type: application/json
    body: |
      {"expectedArtifacts": [{"matchArtifact": {"name": "#{T(java.lang.System).getProperty('os.name')}"}}]}
    matchers:
      - type: word
        words:
          - "Linux"
          - "Windows"
        condition: or

Expected output indicating vulnerability: Response includes the evaluated OS name string rather than the literal expression.

Tool: Semgrep (for static code review of custom Spinnaker extensions)

semgrep --config=p/spring \
  --pattern 'new SpelExpressionParser().parseExpression($EXPR).getValue($CTX)' \
  ./your-spinnaker-extensions/

Code Review Checklist

For teams running custom Spinnaker extensions or evaluating SpEL internally:

  • Verify all SpEL evaluations use SimpleEvaluationContext (not StandardEvaluationContext) unless full JVM access is explicitly required
  • Confirm evaluation context is restricted to an allowlist of safe types via TypeLocator or SimpleEvaluationContext.forReadOnlyDataBinding()
  • Check that user-supplied strings are never passed directly to SpelExpressionParser.parseExpression() without validation
  • Ensure Echo microservice version is ≥ the patched version for your release branch
  • Review Spinnaker network policies so Echo is not directly reachable from untrusted network segments

4. How to Fix It (Mitigation)

Step-by-Step Remediation

  1. Identify your current Spinnaker version using the Halyard CLI or your Helm values:

    hal version current
    # or
    helm list -n spinnaker
    
  2. Update to a patched version. If using Halyard:

    hal config version edit --version 2026.1.0
    hal deploy apply
    

    If using the Spinnaker Helm chart, update your values.yaml:

    spinnakerVersion: "2026.1.0"
    

    Then apply:

    helm upgrade spinnaker spinnaker/spinnaker \
      -f values.yaml -n spinnaker
    
  3. If immediate patching is not possible — disable Echo as a temporary workaround:

    # Scale Echo to zero replicas
    kubectl scale deployment spin-echo --replicas=0 -n spinnaker
    

    Note: Disabling Echo will stop pipeline event notifications and some trigger functionality. Assess operational impact before doing this in production.

  4. Validate the rollout (see Section 5) before restoring full traffic.

Code Fix Example

The upstream patch restricts SpEL evaluation in Echo to use SimpleEvaluationContext, preventing arbitrary class resolution. The conceptual change mirrors this pattern:

Before (vulnerable):

ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext ctx = new StandardEvaluationContext(artifactContext);
// No class restriction — full JVM access available
Object result = parser.parseExpression(userExpression).getValue(ctx);

After (patched):

ExpressionParser parser = new SpelExpressionParser();
// Restricted context: no arbitrary type resolution, no method invocation
EvaluationContext ctx = SimpleEvaluationContext
    .forReadOnlyDataBinding()
    .withInstanceMethods()
    .build();
Object result = parser.parseExpression(userExpression).getValue(ctx, artifactContext);

Configuration Hardening

Even after patching, apply these hardening measures:

  • Network policy: Restrict access to the Echo service so it is only reachable from trusted internal services, not from user-facing network segments.

    # Kubernetes NetworkPolicy example
    apiVersion: networking.k8s.io/v1
    kind: NetworkPolicy
    metadata:
      name: restrict-echo-access
      namespace: spinnaker
    spec:
      podSelector:
        matchLabels:
          app: spin-echo
      ingress:
        - from:
          - podSelector:
              matchLabels:
                app: spin-gate
          - podSelector:
              matchLabels:
                app: spin-orca
    
  • Least-privilege service accounts: Audit and trim cloud IAM permissions granted to Spinnaker's service accounts to the minimum required for deployments.

  • Authentication & authorization: Ensure Spinnaker's Gate API requires authentication for all endpoints; disable anonymous access.


5. How to Test the Fix (Validation)

Regression Test Scenarios

  • Scenario A: Confirm patched Echo evaluates legitimate artifact match expressions correctly and pipeline triggers still function.
  • Scenario B: Confirm the original SpEL injection payload no longer executes arbitrary code (expression is rejected or returned literally rather than evaluated).
  • Scenario C: Confirm pipeline notifications (Slack, email, webhooks) continue to fire after patching or re-enabling Echo.

Security Test Cases

Test Case 1: Verify the vulnerability no longer exists

  • Precondition: Echo is updated to a patched version and deployed.
  • Steps: In a non-production Spinnaker environment, submit a pipeline with an expected artifact whose name contains #{T(java.lang.Runtime).getRuntime().exec('id')}.
  • Expected Result: Echo either throws an evaluation error or returns the literal string — it does NOT execute the id command or return its output.

Test Case 2: Verify legitimate SpEL still works as intended

  • Precondition: Patched Echo is deployed.
  • Steps: Configure an expected artifact that uses a permitted SpEL expression for artifact matching (e.g., matching on artifactAccount property).
  • Expected Result: Pipeline proceeds normally; the artifact is matched correctly.

Test Case 3: Verify network-level access controls

  • Precondition: NetworkPolicy applied restricting Echo access.
  • Steps: Attempt to reach the Echo service port (8089) from a pod outside the allowed set.
  • Expected Result: Connection is refused or timed out.

Automated Tests

import requests
import pytest

GATE_URL = "http://spinnaker-gate:8084"
AUTH_TOKEN = "your-test-token"

SPEL_PAYLOAD = "#{T(java.lang.Runtime).getRuntime().exec('id')}"

def test_spel_injection_blocked():
    """CVE-2026-32613: SpEL expressions must not result in command execution."""
    resp = requests.post(
        f"{GATE_URL}/pipelines/test-app/test-pipeline",
        json={
            "expectedArtifacts": [{
                "matchArtifact": {"name": SPEL_PAYLOAD}
            }]
        },
        headers={"Authorization": f"Bearer {AUTH_TOKEN}"},
        timeout=10
    )
    # The response must not contain output typical of command execution
    response_text = resp.text
    assert "uid=" not in response_text, "SpEL command execution succeeded — PATCH NOT APPLIED"
    assert "root" not in response_text.lower() or "matchArtifact" in response_text

6. Prevention & Hardening

Best Practices

  • Always sandbox user-controlled expression evaluation. Any time user input flows into a SpEL (or similar) expression evaluator, use the most restrictive evaluation context available. Default to SimpleEvaluationContext; only escalate to StandardEvaluationContext when absolutely necessary and with careful review.

  • Apply Spinnaker updates promptly. Subscribe to the Spinnaker security advisories on GitHub and integrate Spinnaker version updates into your standard patch management cycle.

  • Rotate credentials after any suspected exploitation. Because Echo runs with cloud credentials, treat any confirmed exploitation as a full credential compromise event: rotate cloud IAM keys, invalidate tokens, and audit cloud activity logs for the Echo service's identity.

  • Implement regular dependency auditing. Use tools like trivy or grype to scan Spinnaker container images for known vulnerabilities as part of CI pipelines:

    trivy image us-docker.pkg.dev/spinnaker-community/docker/echo:2026.1.0
    

Monitoring & Detection

Set up alerting for signs of exploitation or reconnaissance targeting Echo:

  • Audit SpEL expression content: Log all artifact match expressions processed by Echo and alert on patterns matching SpEL injection signatures (T(, getRuntime, exec():

    # Example: grep Echo logs for injection patterns
    kubectl logs -n spinnaker -l app=spin-echo --since=1h | \
      grep -E 'T\(java\.lang|getRuntime|\.exec\('
    
  • Cloud activity anomaly detection: Alert on unexpected API calls from the Spinnaker service account — especially ListRoles, CreateRole, AssumeRole, or any data exfiltration-related calls.

  • Kubernetes audit logs: Monitor for unusual subprocess activity spawned from the spin-echo container using a runtime security tool like Falco:

    # Falco rule: detect shell spawned from Echo
    - rule: Shell Spawned from Spinnaker Echo
      desc: A shell was spawned from the spin-echo container
      condition: >
        spawned_process and container.name = "echo" and
        proc.name in (shell_binaries)
      output: "Shell spawned from Spinnaker Echo (user=%user.name cmd=%proc.cmdline)"
      priority: CRITICAL
    
  • Network egress monitoring: Alert on unexpected outbound connections from the Echo pod, which may indicate a reverse shell or data exfiltration attempt.


References

Latest from the blog

See all →