Skip to content

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

Open

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions