Engineering research / distributed

Dapr 1.19.1 Security Fixes: Percent-Encoded Paths Past Access Control, Cross-App Reminders and What to Check

Dapr 1.19.1 and 1.18.10, released on 31 October 2026, fix a cluster of authorization bugs: deny rules bypassed with percent-encoded and semicolon paths, a token check skipped for an encoded health path, relayed gRPC calls, and workflow reminders delivered to the wrong app.

By Sandeep Belgavi · Published 2026-11-01

Recent development · Practical technical guide · 1 November 2026

Dapr 1.19.1 and 1.18.10, released on 31 October 2026, fix a cluster of authorization bugs: deny rules bypassed with percent-encoded and semicolon paths, a token check skipped for an encoded health path, relayed gRPC calls, and workflow reminders delivered to the wrong app.

What was released

On 31 October 2026 the Dapr project published 1.19.1, 1.18.10 and 1.17.19. The 1.19.1 notes list two reliability fixes and eleven security fixes, and the 1.18.10 and 1.17.19 notes list the same eleven security items. Most share one root cause: two components disagreed about what a request was. An access check looked at one spelling of a path and the application routed another; a token check compared the decoded path while the router matched the escaped one; a proxy trusted caller metadata that a crafted request could leave out. This guide walks through the fixes most likely to matter to operators, with the conditions under which each one applied. Everything here comes from the release notes; no exploit was run.

Deny rules and the path the app actually routes

Service invocation access control lets a target app allow or deny operations per calling app, optionally per HTTP verb. Dapr matched the invoked method as a literal string, lowercased for HTTP apps, and passed it to the app unchanged. The app's HTTP server then decoded percent-encoded characters before routing. So a rule denying /admin/secret did not match %61dmin/secret, but the app routed that request to /admin/secret. Some servers, such as Java servlet containers, also drop ; path parameters, so admin;x/secret worked on those apps too.

The notes say this completes an earlier fix in 1.17.16 and 1.18.6 that rejected only %2F, %5C and %2E. The new rule matches policies against the method as the app routes it: decoded once, then lowercased, allowed both with and without ; parameters. Invalid escapes, and ; tricks that leave an empty, . or .. segment, are denied, and an encoded * never acts as a wildcard.

Policy styleAffected?Why
Deny-list: defaultAction allow, some operations denyYes, for HTTP appsAn encoded method matched no deny rule and fell through to allow
Allow-list: defaultAction deny, operations allowNoAn encoded method matched no allow rule and was denied
gRPC app protocolNogRPC apps are unchanged by this fix

An attacker needed an app identity the policy allows to call the target. The general lesson is old and worth repeating: authorise the same canonical form the handler will act on, and prefer allow-lists, which fail closed when spellings disagree.

Checking your own policies

Illustrative Dapr access control configuration in the documented shape; app names are examples.

apiVersion: dapr.io/v1alpha1
kind: Configuration
metadata:
  name: returns-api
spec:
  accessControl:
    defaultAction: deny            # allow-list: unknown spellings fail closed
    trustDomain: "public"
    policies:
    - appId: storefront
      defaultAction: deny
      trustDomain: "public"
      namespace: "shop"
      operations:
      - name: /returns/*
        httpVerb: ['GET', 'POST']
        action: allow

If you run deny-list policies, list every Configuration with defaultAction: allow at the policy or global level and operations set to deny. Those are the deployments that were exposed before the upgrade, and the ones to convert to allow-lists afterwards.

The other fixes, by condition

FixYou were affected if
API token skipped for GET /v1.0/healt%68zDAPR_API_TOKEN is set and untrusted clients can reach the Dapr HTTP API port
Sidecars relayed gRPC proxy calls under their own identityAny workload with a Dapr identity could reach another sidecar's internal port (50002 by default)
Verb rules ignored for requests without metadataPolicies deny some HTTP verbs under defaultAction allow, for HTTP apps
Scheduler delivered other apps' workflow remindersUntrusted workloads share a namespace with workflow apps
Outbox accepted forged messages from its internal topicTransactional outbox is on and other clients can publish to the same broker without topic scopes

The token bypass deserves a closer look. The middleware compared the decoded path with the two exempt health routes, while the router matched the escaped path, so /v1.0/healt%68z skipped the token check but matched no route, and unmatched requests are handled by service invocation. The notes describe the result as server-side request forgery: a GET to the local app, any app in the mesh or any reachable URL, sent with the sidecar's identity. In Kubernetes the injector binds the API to loopback by default; in self-hosted mode it listens on all interfaces by default, which widens exposure.

The reminder fix matters for workflow users. A sidecar connecting to the Scheduler declares the actor types it hosts, and the Scheduler did not check that the caller owned the internal workflow and activity actor types it declared. Any app identity in the namespace could receive another app's workflow reminder triggers, including inputs and history events, and stall those workflows by acknowledging them without acting. The Scheduler now rejects such streams with PermissionDenied.

Generating encoded spellings for policy tests

Illustrative python, checked for syntax; adapt names and storage to your system.

def spellings(path):
    """Alternative spellings of a protected path that an HTTP app may route to the same handler."""
    out = set()
    for i, ch in enumerate(path):
        if ch.isalnum():
            out.add(path[:i] + '%%%02X' % ord(ch) + path[i + 1:])          # one character encoded
            out.add(path[:i] + '%%25%02X' % ord(ch) + path[i + 1:])        # double-encoded, for HTTP APIs
    segments = path.strip('/').split('/')
    out.add('/' + segments[0] + ';x/' + '/'.join(segments[1:]))           # path parameter
    out.add(path.upper())
    return sorted(out)

for s in spellings('/admin/secret')[:4]:
    print(s)
# /%2561dmin/secret
# /%61dmin/secret
# /ADMIN/SECRET
# /a%2564min/secret

Feed every spelling of every denied operation through the sidecar from an allowed caller in a test environment and assert a denial. The same test catches the class of bug in any gateway or mesh, not only Dapr, because the bug is a disagreement between the authorisation check and the router.

Upgrade and verification

  1. Upgrade the control plane and sidecars to 1.19.1, or to 1.18.10 or 1.17.19 on older lines, and read the notes for your line.
  2. Inventory deny-list access control policies and plan their conversion to allow-lists.
  3. Confirm the Dapr HTTP API is bound to loopback where that is intended, especially in self-hosted mode, and that an API token is set where it is reachable.
  4. Check that pub/sub components used with the transactional outbox have topic scopes, so other apps cannot publish to its internal topic.
  5. After upgrading, send an encoded spelling of a denied path, such as /%61dmin, from an allowed caller in a test environment and expect a denial, and request /v1.0/healt%68z without a token and expect a rejection.

Why these bugs keep appearing

Each fix closes a gap between two parsers or two trust assumptions: decoded versus escaped paths, a method with and without ; parameters, metadata the caller can omit, and identities checked by app ID but not namespace. Agent platforms built on sidecars inherit the same risk, because agents generate unusual requests far more often than people do. Test authorization with alternative encodings of every protected path as part of the policy test suite, not only after an advisory.

Sources and scope

Checked against these sources on 1 November 2026. Event: Dapr v1.19.1 and v1.18.10 releases, 31 October 2026; recheck it before acting. Facts are from the Dapr release notes; no GitHub security advisories for these items were published at the time of reading. No Dapr deployment was tested for this guide.

Dapr 1.19.1, 1.18.10 and 1.17.19 fix deny rules bypassed by encoded and semicolon paths, an API token skip, relayed gRPC calls and cross-app reminders. Upgrade, move deny-lists to allow-lists, keep the API on loopback, scope outbox topics and test encoded spellings of protected paths.

Views are my own and do not represent any employer, past or present.