You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Handle windows-style bare specifiers in exports #30169
The above will load y.js at least on Windows even if x uses exports and didn't whitelist the path. This happens because the specifier isn't recognized as an exports specifier.
There's three possible ways out:
We detect this case and iff x uses exports treat it as "not mapped", no matter what the subpath is.
We accept this as a loophole.
We normalize the specifier, turning backslashes into slashes.
There are other potential loopholes like the following:
@scope/../any/file.js: Doesn't match the RegExp, allows access to any/file.js.
TBD
It's not quite clear if we can plug all holes in CJS but there's likely some way of doing it.
Yes-ish. The problem is that we're crossing streams. It's the same configuration for both ESM and CJS. ESM operates on file URLs so "normalizing" means converting those to platform file system paths. CJS operates on file system paths so "normalizing" means first mapping relative path segments to URL segments, then mapping, and then mapping the result back to file system paths. The question is less "should we normalize" and more "what do we consider our input/output formats to be" so we can normalize correctly.
is the general approach wrt some things like case insensitive filesystems using require(). Seems fine.
As long as it is documented, I don't have a preference on solution chosen.
If we do go with 3. however I would suggest the following types and coercions:
exports key is a relative URL string with leading . or .., or absolute URL string
exports value is a relative URL string string with leading . or .., or absolute URL string
Conversion happens lazily like most things. People can use auditing like linters to do ahead of time checks.
The URLs are resolved against the location of the package.json file using the regular URL parser:
// will ensure trailing / and remove fragment and query// don't need this if you aren't checking location after resolvepkgRoot=newURL('.',fileURLOfPackageJSON);resolvedExportValue=newURL(value,pkgRoot);// could do a pathname check to see if it stayed in the pkgRoot?// idk, seems more of an auditing problem than a need to prevent this behavior// only used by CJS// this strips query/fragment but CJS doesn't support those to begin withfilename=fileURLToPath(resolvedExportValue);
fileURLToPath handles some minor oddities when converting between things and new URL handles \\ normalization in a standardized way as seen with new URL('.\\x\\y', 'file:////app/').
My current thinking is that we should officially narrow the scope of exports to what it is right now: specifiers that are a package name or a package name followed by a slash.
This will mean that code can "exploit" backslash on Windows (under require) and code can "exploit" case-differences on case-insensitive file systems. But portable code will be using exports. Since we can't close the more general escape hatch (case-insensitive file systems), it feels weird to add complexity to close a very platform specific one. I don't think I've seen backslash widely used in imports or requires.
Yep. Or to be more precise: when it's a "package name specifier" which is a subset of all bare specifiers. E.g. \0%$! is a bare specifier but not something that would trigger exports (a "package name").
This issue has been marked as stale due to 210 days of inactivity.
It will be automatically closed in 30 days if no further activity occurs. If this is still relevant, please leave a comment or update it to keep it open.
This issue has been automatically closed after 30 days of inactivity following its stale status (no activity for a total of 120 days).
If this is still relevant, feel free to reopen it or leave a comment with additional details so we can continue the discussion.
Activity
ljharb commentedon Jul 23, 2019
1 or 3 sounds good to me.
zackschuster commentedon Jul 23, 2019
i think normalizing the specifier to the platform would be in the realm of "expected behavior"?
hybrist commentedon Jul 23, 2019
Yes-ish. The problem is that we're crossing streams. It's the same configuration for both ESM and CJS. ESM operates on file URLs so "normalizing" means converting those to platform file system paths. CJS operates on file system paths so "normalizing" means first mapping relative path segments to URL segments, then mapping, and then mapping the result back to file system paths. The question is less "should we normalize" and more "what do we consider our input/output formats to be" so we can normalize correctly.
bmeck commentedon Jul 23, 2019
require(). Seems fine.As long as it is documented, I don't have a preference on solution chosen.
If we do go with 3. however I would suggest the following types and coercions:
exportskey is a relative URL string with leading.or.., or absolute URL stringexportsvalue is a relative URL string string with leading.or.., or absolute URL stringConversion happens lazily like most things. People can use auditing like linters to do ahead of time checks.
The URLs are resolved against the location of the
package.jsonfile using the regular URL parser:fileURLToPathhandles some minor oddities when converting between things andnew URLhandles\\normalization in a standardized way as seen withnew URL('.\\x\\y', 'file:////app/').hybrist commentedon Oct 29, 2019
My current thinking is that we should officially narrow the scope of
exportsto what it is right now: specifiers that are a package name or a package name followed by a slash.This will mean that code can "exploit" backslash on Windows (under
require) and code can "exploit" case-differences on case-insensitive file systems. But portable code will be usingexports. Since we can't close the more general escape hatch (case-insensitive file systems), it feels weird to add complexity to close a very platform specific one. I don't think I've seen backslash widely used in imports or requires.EDIT: Fixed some grammar.
ljharb commentedon Oct 29, 2019
By “specifiers that are a package name” you mean, exports only “kicks in” when the resolved specifier started with a bare specifier?
hybrist commentedon Oct 29, 2019
Yep. Or to be more precise: when it's a "package name specifier" which is a subset of all bare specifiers. E.g.
\0%$!is a bare specifier but not something that would trigger exports (a "package name").MylesBorins commentedon Oct 29, 2019
docmaster2 commentedon May 11, 2020
одно спотыкание и мы<clang-format.json>
targos commentedon Dec 26, 2020
Is this still relevant?
targos commentedon Feb 20, 2021
I confirm the behavior is still there. Option 3 makes sense in my opinion.
github-actions commentedon Jun 27, 2026
This issue has been marked as stale due to 210 days of inactivity.
It will be automatically closed in 30 days if no further activity occurs. If this is still relevant, please leave a comment or update it to keep it open.
github-actions commentedon Jul 28, 2026
This issue has been automatically closed after 30 days of inactivity following its stale status (no activity for a total of 120 days).
If this is still relevant, feel free to reopen it or leave a comment with additional details so we can continue the discussion.