Skip to content

fix(infra/ec2): validate POST /dbs body name against path traversal#106

Merged
SethPaul merged 1 commit into
mainfrom
worktree-fix+ec2-router-name-validation
May 29, 2026
Merged

fix(infra/ec2): validate POST /dbs body name against path traversal#106
SethPaul merged 1 commit into
mainfrom
worktree-fix+ec2-router-name-validation

Conversation

@SethPaul
Copy link
Copy Markdown
Contributor

What

infra/src/ec2/ec2-router.js validates the :name path parameter on all /dbs/:name* routes via a global router.param('name') allowlist (/^[a-zA-Z0-9_-]+$/). But POST /dbs (create) reads name from req.body, which that middleware never sees. The body name then flows unchecked into:

  • resolve(projects_dir, name)mkdirSync(project_dir) + writeFileSync(resolve(project_dir, 'docker-compose.yml'))
  • resolve(data_dir, name) (mount path)
  • sync_seeds(name)resolve(SEEDS_BASE, name) + mkdirSync

A request like {"name":"../../../../tmp/x","engine":"postgres"} is an arbitrary-directory-create + file-write primitive outside projects_dir/data_dir. The router is mounted with no auth (server.jsapp.use(create_ec2_router(...))), so it relies on network isolation alone; per our security-review guidance, internal-only isn't a reason to skip input validation (SSRF/IDOR targets).

This was surfaced by an automated security review that flagged the GET /dbs/:name line; that specific line is actually already protected by router.param. The real, unprotected sink is the body name in POST /dbs — fixed here.

Fix

Apply the same VALID_NAME check to the body name in POST /dbs, reusing the param middleware's exact error message. One guard, no behavior change for valid names.

No command-injection vector exists: every spawnSync uses array args (no shell).

Verification

  • node --check clean.
  • Confirmed VALID_NAME rejects ../../../../tmp/evil, /etc/passwd, foo/../bar, a/b, .., ., foo bar, foo;rm -rf and accepts mydb, saga-api, test_db, DB123.

Note: the ec2-router has no existing unit-test harness (no test file references it; it'd need an express/supertest scaffold). Happy to add one if desired.

The router.param('name') allowlist guard only fires for the path :name
parameter, so it covers GET/POST/DELETE /dbs/:name* routes but NOT the
create endpoint, which reads name from req.body. That name flows unchecked
into resolve(projects_dir, name), resolve(data_dir, name), sync_seeds ->
resolve(SEEDS_BASE, name), plus mkdirSync/writeFileSync — a path-traversal
write primitive (e.g. {"name":"../../../tmp/x"}) on an unauthenticated,
network-isolated db-host API.

Apply the same VALID_NAME (/^[a-zA-Z0-9_-]+$/) check to the body name in
POST /dbs, reusing the param middleware's error message. No shell injection
path (all spawnSync use array args, no shell).
@SethPaul SethPaul merged commit 9eab3e4 into main May 29, 2026
4 checks passed
@SethPaul SethPaul deleted the worktree-fix+ec2-router-name-validation branch May 29, 2026 17:25
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.

1 participant