Skip to content

fix: enforce path traversal protection for actions, rules, hooks, and databases - #1448

Open
ankita10119 wants to merge 3 commits into
beta/v9.0.0from
DXCDT-2091-1
Open

fix: enforce path traversal protection for actions, rules, hooks, and databases#1448
ankita10119 wants to merge 3 commits into
beta/v9.0.0from
DXCDT-2091-1

Conversation

@ankita10119

Copy link
Copy Markdown
Contributor

🔧 Changes

Background and motivation

This PR addresses two related problems:

1. Security vulnerability - path traversal
Resource configurations (actions, rules, hooks, databases) reference external code files by path. A malicious or misconfigured path such as ../../etc/sensitive-file.js could cause the CLI to load a file from outside the intended config directory. This is a path traversal vulnerability reported by the security team.

2. False errors on legitimate relative paths (issue #1432)
Users on v8.x reported that the CLI was incorrectly emitting warnings/errors for valid relative paths such as ./actions/action-one/code.js. Investigation revealed that the actions and actionModules handlers were using a fragile regex to pre-process paths before passing them to loadFile(), and that loadFile() itself had a silent fallback
(isFile(inSubfolder) ? inSubfolder : inRoot) that behaved unpredictably depending on whether the file existed at the expected location.

These two problems share the same root cause: inconsistent and fragile path resolution logic across handlers.


Root cause

The databases.ts handler already had the correct approach from a previous fix - it used path.resolve + startsWith(configRoot) directly in the handler, calling loadFileAndReplaceKeywords without going through loadFile().

However, actions.ts and actionModules.ts used a different approach:

// Strip leading ./ and drive letters with regex, then delegate
const unixPath = action.code.replace(/[\\/]+/g, '/').replace(/^([a-zA-Z]+:|\.\/)/, '');
action.code = context.loadFile(unixPath, actionFolder);

This regex incorrectly stripped the ./ prefix, causing path resolution in loadFile() to compute the wrong base path and trigger false errors.

rules.ts and hooks.ts passed paths to loadFile() directly:
rule.script = context.loadFile(rule.script, constants.RULES_DIRECTORY);
These relied on loadFile()'s isFile fallback, which silently loaded the wrong file if the expected path did not exist, a security concern in itself.


Edge cases identified and addressed

  1. Windows backslash paths - action.code can contain actions\\code.js on Windows. Fixed by normalizing all backslashes with .replace(/\\/g, '/') before calling path.resolve.
  2. Action code path relative to config root - Action JSON stores the code path relative to the config root (e.g., "./actions/action-one/code.js"). The old regex was stripping ./, causing the path to be misresolved. Fixed by resolving directly from context.filePath using path.resolve.
  3. Rule and hook script paths relative to their subfolder - Rule JSON stores script paths relative to the rules/ subfolder (e.g., "somerule.js"), not the config root. Fixed by resolving from configRoot/rules/ in rules.ts and configRoot/hooks/ in hooks.ts.
  4. Absolute paths passed to loadFile() - On Unix, path.join(base, absolutePath) silently discards the base. The old loadFile() was vulnerable to this. Fixed by using path.resolve() throughout, which handles absolute paths correctly.
  5. Silent isFile fallback - The old loadFile() tried inSubfolder, and if the file didn't exist there, fell back to inRoot with no error or warning. This could silently load the wrong file. Removed entirely, path is now resolved deterministically with no fallback.
  6. Monorepo setups with shared code outside config root - Some teams store shared action code in a directory above the config root. A hard error would break these legitimate setups. Addressed with the AUTH0_ALLOW_EXTERNAL_CODE_PATHS escape hatch (see below).

What changed

  • actions.ts / actionModules.ts: Replaced regex pre-processing + loadFile() delegation with an explicit path.resolve + startsWith check, directly calling loadFileAndReplaceKeywords. Now consistent with databases.ts.
  • rules.ts / hooks.ts: Replaced loadFile() delegation with the same inline path.resolve + startsWith check.
  • directory/index.ts (loadFile()): Removed the isFile fallback. Throws a hard error when path resolves outside config root.
  • yaml/index.ts (loadFile()): Same fix - throws hard error when path resolves outside config root.
  • databases.ts: Detection logic was already correct; updated to support AUTH0_ALLOW_EXTERNAL_CODE_PATHS consistently with other handlers.
  • types.ts: Added AUTH0_ALLOW_EXTERNAL_CODE_PATHS?: boolean to the Config type.
  • docs/configuring-the-deploy-cli.md: Added documentation for AUTH0_ALLOW_EXTERNAL_CODE_PATHS including the monorepo use case, directory structure example, and security notice.

AUTH0_ALLOW_EXTERNAL_CODE_PATHS escape hatch

Introduces an explicit opt-in config flag for monorepo setups where code files legitimately reside outside the config root directory. When set to true, the path traversal check is bypassed and a debug log is emitted instead of an error.

⚠️ Reviewer attention required: Please specifically evaluate the AUTH0_ALLOW_EXTERNAL_CODE_PATHS flag.
This flag was introduced solely to support the monorepo use case described above. However, it bypasses the path traversal protection that this PR introduces. The security team should confirm whether this escape hatch is acceptable and under what conditions it should be permitted. Full context is documented in docs/configuring-the-deploy-cli.md

📚 References

🔬 Testing

Unit tests have been added or updated for all affected handlers:

  • test/context/directory/actions.test.js: Added tests for path traversal rejection, valid relative paths, escape hatch (AUTH0_ALLOW_EXTERNAL_CODE_PATHS), and Windows-style backslash paths.
  • test/context/directory/rules.test.js: Added test for path traversal rejection via rejectedWith.
  • test/context/directory/hooks.test.js: Added test for path traversal rejection via rejectedWith.

All 27 tests in the above files pass. Test assertions were updated from warnSpy checks to rejectedWith(Error, 'is outside the config directory') to reflect the hard error behavior on this beta branch.

Manual testing:

  • Point an action JSON at a path using ../../ traversal - verify the deploy fails with a clear error message.
  • Point an action JSON at a valid relative path (./actions/action-one/code.js) - verify it deploys without error.
  • Set AUTH0_ALLOW_EXTERNAL_CODE_PATHS: true with an external path - verify it loads successfully with a debug log.

📝 Checklist

  • All new/changed/fixed functionality is covered by tests (or N/A)
  • I have added documentation for all new/changed functionality (or N/A)

@ankita10119
ankita10119 requested a review from a team as a code owner August 4, 2026 14:27
@codecov-commenter

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 63.63636% with 20 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (beta/v9.0.0@5ef9b32). Learn more about missing BASE report.

Files with missing lines Patch % Lines
src/context/directory/index.ts 12.50% 7 Missing ⚠️
src/context/directory/handlers/actionModules.ts 55.55% 3 Missing and 1 partial ⚠️
src/context/directory/handlers/databases.ts 40.00% 3 Missing ⚠️
src/context/directory/handlers/hooks.ts 77.77% 1 Missing and 1 partial ⚠️
src/context/directory/handlers/rules.ts 77.77% 1 Missing and 1 partial ⚠️
src/context/yaml/index.ts 66.66% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@              Coverage Diff               @@
##             beta/v9.0.0    #1448   +/-   ##
==============================================
  Coverage               ?   80.19%           
==============================================
  Files                  ?      156           
  Lines                  ?     7250           
  Branches               ?     1604           
==============================================
  Hits                   ?     5814           
  Misses                 ?      776           
  Partials               ?      660           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

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.

2 participants