[DXAA-651] ProdSec updates to onboarding tool#179
Conversation
…kstart guide tool branch
Remove the duplicate hasNonVerifiableCallbacks from quickstart-guide now that the canonical implementation from PR #162 is available, resolving the standing TODO. Drops the redundant test block; coverage lives in onboarding.test.ts.
Replace the hardcoded VALID_FRAMEWORKS list with the shared SUPPORTED_FRAMEWORKS/isFrameworkSupported from onboarding util. This also makes framework validation case-insensitive, consistent with fetchQuickstartSpec which already lowercases the framework.
SUPPORTED_FRAMEWORKS derives order from FRAMEWORK_FILENAMES (react, vue, angular, nextjs); update assertion to match.
A relative project_path resolved against the MCP server's stdio subprocess cwd (the client's launch dir), never the user's project — silently reading the wrong .env. Reject non-absolute paths at the handler boundary in both tools sharing the project_path contract, and tighten the schema description.
The response reported the raw project_path while all file detection used the resolved path, so a normalizable input (e.g. trailing /sub/..) could point somewhere different from where files were actually inspected. Return the normalized resolvedProjectPath so the response matches the work done.
URL updates are strictly additive ([...existing, ...missing]) — nothing is removed or overwritten — so destructiveHint: true overstated the tool's behavior. Flip it to false and, matching the sibling app tools, instruct the LLM to inform the user about auto-applied settings like skip_non_verifiable_callback_uri_confirmation_prompt.
When no environment file is detected, the error now enumerates the filenames detectExistingEnvFile scans (.env.local, .env, .env.development.local, .env.development) plus the spec's snippet file, so the LLM/user knows exactly what to create instead of guessing.
The flag was only nested under configured_urls, but the per-response instructions only tell the LLM to summarize actions_taken — so the auto-applied setting was never reported to the user. Push an explicit line into actions_taken when the flag is set, riding the channel the instructions already cover.
Clarify that an existing environment file was detected (not assumed to hold Auth0 credentials), keeping the instruction to use it as-is and skip .env setup steps in the prompt.
The old name auth0_get_quickstart_guide implied a read-only fetch, but the tool also configures callback/logout/web-origin URLs on the Auth0 app. Rename it so the name reflects that it both configures the application and returns the quickstart guide.
Orchestrates Auth0 project onboarding by validating framework/path, fetching CDN spec, creating an application with correct config, and saving credentials to .env. Returns next_steps for quickstart guide.
When save_credentials fails, include the created client_id in the error so the LLM knows the app exists. Add defensive defaults for saveData fields. Add tests for path resolution and case-insensitive framework.
…the onboarding tool for unsupported frameworks
…x .env file detection when running the get quickstart tool
…e before and after the save credentials tool execution
| return 'project_path does not exist or is not a directory'; | ||
| } | ||
| return null; | ||
| } |
There was a problem hiding this comment.
Here on line 64, path.resolve() turns the path into an absolute path. We then check it to make sure exists and that it's within the current working directory
There was a problem hiding this comment.
In the tool definitions, we are stating that the project path is an absolute path. I am okay doing this and double-checking, however, we do kind lose the preventative behaviour of stopping the user from providing a relative path. I am okay either way, but it's a bit inconsistent with the schema definitions:
- applications.ts:365 — 'Absolute path to the project root directory. The env file is written here.'
- quickstarts.ts:45 — 'Absolute path to the project directory. Used for .env file check and project config port
detection.'
- quickstarts.ts:45 — 'Absolute path to the project directory. Used for .env file check and project config port
There was a problem hiding this comment.
@wdaimee I added back the isAbsolute() check since like what you mentioned and what @kushalshit27 the check is needed
|
|
||
| expect(parseEnvFile(envFilePath)).toEqual({ FOO: 'bar' }); | ||
| }); | ||
| }); |
There was a problem hiding this comment.
These two test cases are due to the change from regex to dotenv library usage. Using dotenv will strip surrounding quotes and trims inline comments from unquoted values. Previously the regex would miss these cases
| }); | ||
|
|
||
| describe('resolveAndWriteCredentials — project path validation', () => { | ||
| it('returns error when project_path is not absolute', async () => { |
There was a problem hiding this comment.
Removed this test case since we are not using isAbsolute() anymore (path.resolve() makes the path absolute within validateProjectPath())
| () => commentOutConflictingKeys(existingContent, incomingKeys), | ||
| ENV_PARSE_TIMEOUT_MS, | ||
| 'Env file processing' | ||
| ); |
There was a problem hiding this comment.
Here we use a timeout to ensure when parsing the env file it does not catch when reading a potentially large file (which should not be realistic anyway for a env file)
| throw new Error( | ||
| `Security error: env file exceeds maximum allowed size of ${MAX_ENV_FILE_SIZE_BYTES / 1024 / 1024} MB. ` + | ||
| 'The file may be corrupted or malicious. Remove or truncate it before retrying.' | ||
| ); |
There was a problem hiding this comment.
This covers https://auth0team.atlassian.net/browse/SEC-32494 to prevent a large .env file causing the MCP to hang
| throw new Error( | ||
| `Security error: expected file mode ${CREDENTIAL_FILE_MODE.toString(8)}, got ${actualMode.toString(8)}` | ||
| ); | ||
| } |
There was a problem hiding this comment.
This covers https://auth0team.atlassian.net/browse/SEC-32495 and prevents access if the file has a non 600 permission
| root => resolvedDir === root || resolvedDir.startsWith(root + path.sep) | ||
| ); | ||
| } | ||
|
|
There was a problem hiding this comment.
This function covers https://auth0team.atlassian.net/browse/SEC-32459 and warns the user to double check their .env file is not made public on the web server. The method is called in src/utils/env-credentials.ts
| ); | ||
| } | ||
| } | ||
|
|
There was a problem hiding this comment.
This method covers https://auth0team.atlassian.net/browse/SEC-32517 and imposes a sliding window rate limiting (checks the last 60 secs for calls made)
There was a problem hiding this comment.
Does the rate limiter apply to dry_run calls at the server layer, before the handler knows the call is non-writing?
There was a problem hiding this comment.
The dry_run would get rate limited, which I don't think we should have since no writes are actually happening. I've gone and removed the rate limited if dry_run is true (line 119)
| calls.set(key, timestamps); | ||
| return { allowed: true, retryAfterMs: 0 }; | ||
| }; | ||
| } |
There was a problem hiding this comment.
createRateLimiter(5, 60_000) is called once at startup and returns a function. Each time you call that function with a key (e.g. 'auth0_save_credentials_to_file'), it either allows or denies the call:
- It keeps a list of timestamps for each key, pruning any older than 60 seconds on every check
- If fewer than 5 timestamps remain after pruning, the call is allowed and the current timestamp is recorded
- If 5 or more remain, the call is denied and retryAfterMs tells the caller how long until the oldest timestamp expires out of the window
…rite guard message
| return WEB_SERVED_SEGMENT_NAMES.has(path.basename(resolvedDir).toLowerCase()); | ||
| } | ||
|
|
||
| function validateProjectPath(projectPath: string): string | null { |
There was a problem hiding this comment.
Looks like the path.isAbsolute() check got dropped here. The new traversal check only catches paths within them. It prevents the resolved directory from changing depending on the server's CWD at call time, which matters in containers or multi-user setups.
There was a problem hiding this comment.
@kushalshit27 thanks for pointing that out, I've gone ahead and added the isAbsolute() check back in, along with some refactoring that comes with path.resolve() not being needed then anymore
Changes
Write guard & audit log (
env-credentials.ts).auth0-mcp-writes.log) recording each write with timestamp, framework, keys written, and target file..gitignore.Dry-run mode (
env-credentials.ts,applications.ts)dry_runparameter returns the computed key names without writing to disk, so callers can confirm what would be written before committing.Path validation hardening (
env-credentials.ts,quickstarts.ts)validateProjectPath(), which enforces that the resolved path stays within the current working directory and contains no..traversal sequences.Atomic writes & better parsing (
credentials-writer.ts)dotenvlibrary for correct handling of quoted values, escapes, and edge cases.ensureGitignorenow returns a boolean and avoids duplicating the # Auth0 credentials comment header.CREDENTIAL_FILE_MODE = 0o600as a non-overridable constant; on POSIX, throws if the post-write stat mode doesn't match (previously chmod failures were silently swallowed)parseEnvFileRate limiting
auth0_save_credentials_to_fileat the server dispatch level; returns a clear retry-after error on excess calls; other tools are unaffectedCDN error handling (
env-credentials.ts)Env file write safeguards (
src/utils/env-credentials.ts)project_pathlooks like a web server document root (e.g. /var/www/..., or a directory named public, dist, build, static, etc.) and appends a warning to the success message advising to block .env file access in the web server config~/.ssh)Schema (
src/tools/applications.ts,src/tools/onboarding.ts,src/tools/quickstarts.ts)additionalProperties: falseto the input schemas for auth0_save_credentials_to_file, auth0_onboarding, and auth0_configure_and_get_quickstart_guide so safe guards from injection attacks and compliant MCP clients will reject calls with undocumented parameters (e.g. permissions: "640")References
https://auth0team.atlassian.net/browse/DXAA-651
https://oktainc.atlassian.net/wiki/spaces/REX/pages/927957893/Security+Review+Report+-+PSREV-2889+Auth0+MCP+Server+auth0_save_credentials_to_file+tool+Updates
Testing
New and expanded tests cover write guard logic, dry-run, atomic writes, path traversal rejection, .gitignore deduplication, parseEnvFile edge cases, and the CDN failure path.
Checklist