Skip to content

Bug: router.route() does not inherit Router's protection setting, OAC not applied to Lambda URL routes #6881

@hito-nm

Description

@hito-nm

Bug

When a Router is configured with protection: "oac-with-edge-signing", routes added via router.route(lambdaUrl) do not have OAC applied. The
originAccessControlConfig is never set in RouterUrlRoute, so CloudFront does not sign requests to the Lambda URL.

The docs say:

When set, all Functions and SSR sites routing through this Router automatically inherit the protection mode.

But the implementation never passes this._protectionMode to RouterUrlRoute.

Reproduction

const router = new sst.aws.Router("MyRouter", {
  protection: "oac-with-edge-signing",
});

// OAC is NOT applied — Lambda URL receives unsigned requests
router.route("app1.example.com", lambdaFunctionUrl);

Root cause

In router.ts, route() creates RouterUrlRoute without passing protection:

new RouterUrlRoute(`${this.constructorName}Route${pattern}`, {
  store: this.kvStoreArn!,
  routerNamespace: this.kvNamespace!,
  pattern,
  url,
  routeArgs: args,
  // ← this._protectionMode is never passed
});

And RouterUrlRoute never sets originAccessControlConfig for Lambda URLs.

Workaround

Patching .sst/platform/src/components/aws/router-url-route.ts on every deploy:

...(host.includes("lambda-url") ? {
  originAccessControlConfig: {
    enabled: true,
    signingBehavior: "always",
    signingProtocol: "sigv4",
    originType: "lambda",
  },
} : {}),

This works but applies OAC unconditionally regardless of the Router's protection setting.

Proposed fix

Pass the Router's protection mode to RouterUrlRoute, and apply originAccessControlConfig only when the mode is oac or oac-with-edge-signing. Also add a per-route
protection field to RouterUrlRouteArgs for explicit override.

diff --git a/platform/src/components/aws/router-url-route.ts b/platform/src/components/aws/router-url-route.ts
index a817c5e33..3e7656ad5 100644
--- a/platform/src/components/aws/router-url-route.ts
+++ b/platform/src/components/aws/router-url-route.ts
@@ -19,6 +19,11 @@ export interface Args extends RouterBaseRouteArgs {
    * Additional arguments for the route.
    */
   routeArgs?: Input<RouterUrlRouteArgs>;
+  /**
+   * The protection mode for this route, inherited from the Router's `protection`
+   * setting. Can be overridden per-route via `RouterUrlRouteArgs.protection`.
+   */
+  protection?: Input<"none" | "oac" | "oac-with-edge-signing">;
 }

@@ -37,12 +42,17 @@ export class RouterUrlRoute extends Component {
-    all([args.url, args.pattern, args.routeArgs]).apply(
-      ([url, pattern, routeArgs]) => {
+    all([args.url, args.pattern, args.routeArgs, args.protection]).apply(
+      ([url, pattern, routeArgs, protection]) => {
         const u = new URL(url);
         const host = u.host;
         const protocol = u.protocol.slice(0, -1);

+        const isLambdaUrl = host.includes("lambda-url");
+        const useOac =
+          isLambdaUrl &&
+          (protection === "oac" || protection === "oac-with-edge-signing");
+
         const patternData = parsePattern(pattern);
         const namespace = buildKvNamespace(name);
         createKvRouteData(name, args, self, namespace, {
@@ -51,6 +61,16 @@ export class RouterUrlRoute extends Component {
           origin: {
             protocol: protocol === "https" ? undefined : protocol,
             connectionAttempts: routeArgs?.connectionAttempts,
+            ...(useOac
+              ? {
+                  originAccessControlConfig: {
+                    enabled: true,
+                    signingBehavior: "always",
+                    signingProtocol: "sigv4",
+                    originType: "lambda",
+                  },
+                }
+              : {}),
             timeouts: (() => {
diff --git a/platform/src/components/aws/router.ts b/platform/src/components/aws/router.ts
@@ -429,6 +429,13 @@ export interface RouterUrlRouteArgs extends RouteArgs {
   keepAliveTimeout?: Input<DurationSeconds>;
+  /**
+   * Override the protection mode for this specific route.
+   * When not set, inherits the Router's `protection` setting.
+   */
+  protection?: Input<"none" | "oac" | "oac-with-edge-signing">;
 }

@@ -2537,8 +2559,8 @@ async function handler(event) {
-    all([pattern, args, this.hasInlineRoutes]).apply(
-      ([pattern, args, hasInlineRoutes]) => {
+    all([pattern, args, this.hasInlineRoutes, this._protectionMode]).apply(
+      ([pattern, args, hasInlineRoutes, routerProtection]) => {
         ...
         new RouterUrlRoute(..., {
           ...
+          protection: args?.protection ?? routerProtection.mode,
         });

Happy to open a PR if the direction looks good.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions