Describe the bug
Description
There is a path traversal vulnerability in functions:create. The interactive
prompt correctly validates function names with /^[\w.-]+$/i, but this validation
is completely bypassed when the name is supplied via the --name flag or as a
positional argument.
Affected File
src/commands/functions/functions-create.ts
Reproduction Steps
Run either of the following:
netlify functions:create --name "../../evil"
netlify functions:create "../../evil"
This causes files to be written outside the intended functions directory.
Root Cause
In getNameFromArgs, validation only runs on the interactive prompt:
const { name } = await inquirer.prompt([
{
validate: (val) => Boolean(val) && /^[\w.-]+$/i.test(val), // ✅ validated
},
])
But when --name or a positional arg is passed, the raw unvalidated value goes
directly into:
const functionPath = path.join(functionsDir, name) // ❌ no validation
Impact
An attacker or a malicious script can write files to arbitrary locations on the
filesystem outside the functions directory.
Proposed Fix
Add a containment check in ensureFunctionPathIsOk:
const ensureFunctionPathIsOk = function (functionsDir, name) {
const functionPath = path.join(functionsDir, name)
if (!functionPath.startsWith(path.resolve(functionsDir) + path.sep)) {
log(Invalid function name: "${name}" resolves outside the functions directory.)
process.exit(1)
}
if (fs.existsSync(functionPath)) {
log(Function ${functionPath} already exists, cancelling...)
process.exit(1)
}
return functionPath
}
Additional Note
A second related issue exists in downloadFromURL where filenames from the
GitHub API response are not sanitized before being passed to path.join.
Fix: wrap name with path.basename(name) before use.
Steps to reproduce
Steps to Reproduce
Prerequisites:
Node.js v20.12.2 or above installed
netlify-cli installed globally: npm install netlify-cli -g
A project directory initialized
Step 1 — Create a test project directory
mkdir test-project
cd test-project
netlify login
Step 2 — Create a safe functions directory
mkdir functions
ls
# Expected: only 'functions' folder exists
Step 3 — Trigger the vulnerability using --name flag
netlify functions:create --name "../../evil"
Step 4 — Observe the result
ls ../..
# You will see an 'evil' folder created TWO levels above your project directory
# Files were written OUTSIDE the intended functions directory
Step 5 — Confirm with positional argument (second vector)
netlify functions:create "../../evil2"
ls ../..
# Same result — 'evil2' folder created outside project
Expected Behavior:
Error: Invalid function name. Name must only contain letters, numbers,
hyphens, underscores, or dots.
Actual Behavior:
Files are written to ../../evil/ — completely outside the
intended functions directory with no warning or error.
Configuration
No response
Environment
OS: [your OS]
Node version: run node -v
netlify-cli version: run netlify --version
Describe the bug
Description
There is a path traversal vulnerability in
functions:create. The interactiveprompt correctly validates function names with
/^[\w.-]+$/i, but this validationis completely bypassed when the name is supplied via the
--nameflag or as apositional argument.
Affected File
src/commands/functions/functions-create.tsReproduction Steps
Run either of the following:
netlify functions:create --name "../../evil"
netlify functions:create "../../evil"
This causes files to be written outside the intended functions directory.
Root Cause
In
getNameFromArgs, validation only runs on the interactive prompt:const { name } = await inquirer.prompt([
{
validate: (val) => Boolean(val) && /^[\w.-]+$/i.test(val), // ✅ validated
},
])
But when --name or a positional arg is passed, the raw unvalidated value goes
directly into:
const functionPath = path.join(functionsDir, name) // ❌ no validation
Impact
An attacker or a malicious script can write files to arbitrary locations on the
filesystem outside the functions directory.
Proposed Fix
Add a containment check in
ensureFunctionPathIsOk:const ensureFunctionPathIsOk = function (functionsDir, name) {
const functionPath = path.join(functionsDir, name)
if (!functionPath.startsWith(path.resolve(functionsDir) + path.sep)) {
log(
Invalid function name: "${name}" resolves outside the functions directory.)process.exit(1)
}
if (fs.existsSync(functionPath)) {
log(
Function ${functionPath} already exists, cancelling...)process.exit(1)
}
return functionPath
}
Additional Note
A second related issue exists in
downloadFromURLwhere filenames from theGitHub API response are not sanitized before being passed to
path.join.Fix: wrap
namewithpath.basename(name)before use.Steps to reproduce
Steps to Reproduce
Prerequisites:
Step 1 — Create a test project directory
mkdir test-project cd test-project netlify loginStep 2 — Create a safe functions directory
mkdir functions ls # Expected: only 'functions' folder existsStep 3 — Trigger the vulnerability using --name flag
netlify functions:create --name "../../evil"Step 4 — Observe the result
Step 5 — Confirm with positional argument (second vector)
Expected Behavior:
Actual Behavior:
Configuration
No response
Environment
OS: [your OS]
Node version: run
node -vnetlify-cli version: run
netlify --version