Skip to content

fix: preserve case in normalizePathPart to prevent API Gateway path collisions - #13506

Open
karthikeyansundaram2 wants to merge 1 commit into
serverless:mainfrom
karthikeyansundaram2:fix/issue-11956-case-sensitive-http-paths
Open

fix: preserve case in normalizePathPart to prevent API Gateway path collisions#13506
karthikeyansundaram2 wants to merge 1 commit into
serverless:mainfrom
karthikeyansundaram2:fix/issue-11956-case-sensitive-http-paths

Conversation

@karthikeyansundaram2

@karthikeyansundaram2 karthikeyansundaram2 commented Apr 15, 2026

Copy link
Copy Markdown

Closes #11956

_.capitalize() lowercases all chars after the first, causing paths like /DevicePaymentService and /devicepaymentservice to generate identical CloudFormation IDs and fail on deploy.

Fix: use rawPath directly instead of _.capitalize(rawPath). _.upperFirst() still ensures a valid uppercase start for CloudFormation resource names.

Before: /DevicePaymentService and /devicepaymentservice both → 'Devicepaymentservice' (collision)
After: → 'DevicePaymentService' and 'Devicepaymentservice' (distinct)

Summary by CodeRabbit

Release Notes

  • Refactor
    • Optimized internal path normalization logic to improve processing consistency.

…ollisions

_.capitalize() lowercased all characters after the first, causing paths
like /DevicePaymentService and /devicepaymentservice to generate identical
CloudFormation logical IDs and fail on deploy.

Replacing with rawPath directly preserves case differences so paths with
different casing generate distinct resource names.

Closes serverless#11956
@github-actions

Copy link
Copy Markdown


Thank you for your submission, we really appreciate it. Like many open-source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution. You can sign the CLA by just posting a Pull Request Comment same as the below format.


I have read the CLA Document and I hereby sign the CLA


You can retrigger this bot by commenting recheck in this Pull Request. Posted by the CLA Assistant Lite bot.

@coderabbitai

coderabbitai Bot commented Apr 15, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

The normalizePathPart() function in the AWS naming plugin was modified to preserve case sensitivity when normalizing API Gateway path names. Previously, an initial capitalization step would normalize paths differing only in casing to identical values. The change removes this step, allowing case-distinct paths to be treated as separate resources.

Changes

Cohort / File(s) Summary
Path Normalization Logic
packages/serverless/lib/plugins/aws/lib/naming.js
Modified normalizePathPart() to replace _.capitalize(rawPath) with _.upperFirst() applied after character replacements, preserving case sensitivity for path segments while maintaining proper resource naming conventions.

Estimated code review effort

🎯 1 (Trivial) | ⏱️ ~3 minutes

Poem

🐰 A path that whispers in lowercase dreams,
Now stands apart from uppercase schemes.
Case-sensitive routes, at last set free,
Two roads diverge where one used to be.
Hop hop, the naming bug's now gone!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: fixing case preservation in normalizePathPart to prevent API Gateway path collisions.
Linked Issues check ✅ Passed The code change directly addresses issue #11956 by preserving case in normalizePathPart, enabling distinct CloudFormation logical IDs for case-sensitive API Gateway paths.
Out of Scope Changes check ✅ Passed The modification is limited to normalizePathPart logic and is directly scoped to fixing the case-sensitive path collision issue identified in #11956.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@Mmarzex

Mmarzex commented Apr 15, 2026

Copy link
Copy Markdown
Contributor

Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues
Licenses 0 0 0 0 0 issues
Code Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/serverless/lib/plugins/aws/lib/naming.js`:
- Around line 36-37: The current normalization collapses leading-case by calling
_.upperFirst on rawPath (so "foo" and "Foo" both become "Foo"); change the logic
in the naming routine that returns _.upperFirst(rawPath...) to preserve the
original first-character case instead of forcing uppercase: stop forcing
_.upperFirst(rawPath) and use the rawPath casing when building the logical ID,
and if you must maintain backward compatibility add an explicit leading-case
disambiguation (e.g., a stable prefix or marker when the original first char is
lowercase) so that "foo" and "Foo" produce distinct IDs; locate the return that
uses _.upperFirst and update it to either return rawPath with the rest of the
normalization or apply the explicit disambiguation strategy.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: bd58f541-668a-4591-8a81-3cb3a58f92e7

📥 Commits

Reviewing files that changed from the base of the PR and between ab05314 and 68ce59f.

📒 Files selected for processing (1)
  • packages/serverless/lib/plugins/aws/lib/naming.js

Comment on lines 36 to +37
return _.upperFirst(
_.capitalize(rawPath)
rawPath

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

First-character case is still collapsed, so /foo and /Foo can still collide.

Because of _.upperFirst(...) on Line [36], both foo and Foo normalize to Foo. This means case-sensitive paths that differ only at the first character are still not uniquely represented in logical IDs.

Possible fix
-    return _.upperFirst(
-      rawPath
+    return rawPath
         .replace(/-/g, 'Dash')
         .replace(/\{(.*)\}/g, '$1Var')
-        .replace(/[^0-9A-Za-z]/g, ''),
-    )
+        .replace(/[^0-9A-Za-z]/g, '')

If backward compatibility requires preserving current leading-uppercase behavior, then add an explicit leading-case disambiguation strategy instead of collapsing it.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/serverless/lib/plugins/aws/lib/naming.js` around lines 36 - 37, The
current normalization collapses leading-case by calling _.upperFirst on rawPath
(so "foo" and "Foo" both become "Foo"); change the logic in the naming routine
that returns _.upperFirst(rawPath...) to preserve the original first-character
case instead of forcing uppercase: stop forcing _.upperFirst(rawPath) and use
the rawPath casing when building the logical ID, and if you must maintain
backward compatibility add an explicit leading-case disambiguation (e.g., a
stable prefix or marker when the original first char is lowercase) so that "foo"
and "Foo" produce distinct IDs; locate the return that uses _.upperFirst and
update it to either return rawPath with the rest of the normalization or apply
the explicit disambiguation strategy.

@karthikeyansundaram2

Copy link
Copy Markdown
Author

Thanks for the review @coderabbitai!

Regarding the suggestion to remove _.upperFirst() as well — I'd like to keep it intentionally.

CloudFormation logical resource IDs must start with an uppercase letter. _.upperFirst() ensures that constraint is met regardless of how the path is cased (e.g. /devicesDevices...). Removing it would break deployments where paths start with a lowercase letter.

The core fix here targets _.capitalize(), which was the actual culprit: it lowercases all characters after the first, collapsing /DevicePaymentService and /devicepaymentservice into identical IDs. Replacing it with rawPath preserves internal case while _.upperFirst() still guarantees a valid CloudFormation ID start.

So the remaining edge case is: two paths that differ only in the first character's case (e.g. /foo and /Foo) would still collide. But that's an extreme edge case and a separate concern — fixing it would require a different naming scheme entirely. This PR addresses the reported issue as described.

@coderabbitai

coderabbitai Bot commented Apr 15, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

@karthikeyansundaram2, thank you for the thorough explanation — that reasoning is sound and well-justified.

CloudFormation logical IDs must be alphanumeric, and the CDK's own validation enforces the pattern /^[A-Za-z][A-Za-z0-9]{1,254}$/, meaning the ID must start with a letter. AWS Partner Solutions guidance further recommends using Pascal case and beginning logical IDs with an uppercase letter. So keeping _.upperFirst() is not only safe, it's aligned with both the technical constraint and AWS conventions.

The distinction you've drawn is correct:

  • _.capitalize() was the culprit — it normalised away internal casing, causing collisions.
  • _.upperFirst() only touches the first character, preserving all internal casing while meeting the CloudFormation ID naming requirement.
  • The residual edge case (/foo vs /Foo) is acknowledged and reasonably deferred as a separate concern.

The fix is well-scoped. Happy to approve this or continue the review.


✏️ Learnings added
Learnt from: karthikeyansundaram2
URL: https://github.com/serverless/serverless/pull/13506

Timestamp: 2026-04-15T07:11:03.228Z
Learning: In `normalizePathPart()` in `packages/serverless/lib/plugins/aws/lib/naming.js`, `_.upperFirst()` is intentionally kept to ensure CloudFormation logical resource IDs start with an uppercase letter (CloudFormation requires alphanumeric IDs starting with a letter). The root cause of path ID collisions was `_.capitalize()`, which lowercased all characters after the first — it was replaced with `rawPath` directly. A known residual edge case remains: paths differing only in the first character's casing (e.g. `/foo` vs `/Foo`) will still produce colliding logical IDs, but this is considered out of scope for this fix.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

@karthikeyansundaram2

Copy link
Copy Markdown
Author

Thanks for the review! I'll look into the suggestions:

    • Around line 36-37: The current normalization collapses leading-case by calling
    • packages/serverless/lib/plugins/aws/lib/naming.js

Will push an update if the suggestions improve correctness or edge case handling.

@czubocha czubocha left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for working on this — the bug is real and blocks users who need case-differentiated API Gateway paths.

However, this change is breaking for existing deployments. normalizePathPart generates CloudFormation logical IDs, and changing its output means existing stacks with mixed-case paths (e.g. /DevicePaymentService, /API, /my-Service) will see their logical IDs change on the next deploy. CloudFormation will interpret this as "delete old resource + create new one," which can fail or cause downtime.

Path segment Before After Breaks existing?
users Users Users No
DevicePaymentService Devicepaymentservice DevicePaymentService Yes
API Api API Yes
my-Service MyDashservice MyDashService Yes

All-lowercase paths (the common case) are safe, but any mixed-case path changes its logical ID.

This concern was also raised in the original issue:

Prepare a PR, which puts a new naming scheme behind the option. [...] Switching for existing services will be potentially breaking — most likely, it'll require a teardown and redeployment.

There's a non-breaking alternative — the codebase already has a collision detection + disambiguation pattern that was used to fix the same class of problem for Kafka EventSourceMappings (#13112). See naming.js:531-591 and kafka.js:226-242. The same approach works here:

  1. Keep _.capitalize() — all paths produce the same logical IDs they do today
  2. After computing all path logical IDs, detect collisions (two different raw paths mapping to the same ID)
  3. Only for colliding paths, append a short hash of the raw path as a suffix
  4. The first path to claim an ID keeps the legacy name — zero impact on existing stacks

This way, users without collisions see no change at all, and users who currently can't deploy (because of collisions) get distinct IDs that work.

@karthikeyansundaram2

Copy link
Copy Markdown
Author

Thanks for the review! I'll look into the suggestions:

    • Around line 36-37: The current normalization collapses leading-case by calling
    • packages/serverless/lib/plugins/aws/lib/naming.js

Will push an update if the suggestions improve correctness or edge case handling.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

deploy http triggers to api gateway fails to consider case-sensitive differences in paths

3 participants