Skip to content

validation: reject unsafe slugs in 'socrates init <slug>'#3

Open
CryptoJones wants to merge 1 commit into
mainfrom
validation/init-slug-safety
Open

validation: reject unsafe slugs in 'socrates init <slug>'#3
CryptoJones wants to merge 1 commit into
mainfrom
validation/init-slug-safety

Conversation

@CryptoJones
Copy link
Copy Markdown
Owner

Path(base) / args.project had three foot-guns when args.project
was untrusted:

  1. Absolute slug: Path("base") / "/etc/passwd" returns "/etc/passwd"
    in Python pathlib. socrates init /etc/passwd --base ~/foo would
    try to scaffold under /etc/passwd, not under ~/foo. Even though
    the user owns the CLI invocation, this violates the mental model
    ("init creates a project named UNDER --base").

  2. Path-separator slug: socrates init a/b quietly creates nested
    structure; socrates init ../tmp/bad escapes --base.

  3. Empty slug: socrates init "" resolves to the base dir itself,
    risking damage to existing siblings.

Added a _validate_slug() helper that rejects:

  • empty / whitespace-only
  • containing '/' or '\' (single path component only)
  • absolute paths
  • '..' or any '..' segment
  • '.' or '..' literal
  • NUL bytes (defensive)

Accepts alphanumerics, dash, underscore, dot — covers normal slugs
like quarterly-rebates, v0.8.0, .hidden.

Tests (new tests/test_init_slug_safety.py, 22 tests):

  • 11 parametrized rejection cases
  • 7 parametrized acceptance cases (normal slugs unchanged)
  • 4 end-to-end CLI integration tests proving:
    • absolute slug exits 2, /etc/passwd/docs/ never created
    • traversal slug exits 2, sibling dir never created
    • nested slug exits 2 with helpful message
    • empty slug exits 2 with helpful message

169/169 tests pass; ruff + mypy clean.

Self-review caveat: doesn't reject Windows reserved names (CON, NUL, PRN, AUX, COM1-9, LPT1-9) or trailing dots/spaces (Windows strips silently). Unix-only safety; Windows operators can still trip themselves up.

`Path(base) / args.project` had three foot-guns when args.project
was untrusted:

1. Absolute slug: Path("base") / "/etc/passwd" returns "/etc/passwd"
   in Python pathlib. `socrates init /etc/passwd --base ~/foo` would
   try to scaffold under /etc/passwd, not under ~/foo. Even though
   the user owns the CLI invocation, this violates the mental model
   ("init creates a project named <slug> UNDER --base").

2. Path-separator slug: `socrates init a/b` quietly creates nested
   structure; `socrates init ../tmp/bad` escapes --base.

3. Empty slug: `socrates init ""` resolves to the base dir itself,
   risking damage to existing siblings.

Added a `_validate_slug()` helper that rejects:
- empty / whitespace-only
- containing '/' or '\\' (single path component only)
- absolute paths
- '..' or any '..' segment
- '.' or '..' literal
- NUL bytes (defensive)

Accepts alphanumerics, dash, underscore, dot — covers normal slugs
like `quarterly-rebates`, `v0.8.0`, `.hidden`.

Tests (new tests/test_init_slug_safety.py, 22 tests):
- 11 parametrized rejection cases
- 7 parametrized acceptance cases (normal slugs unchanged)
- 4 end-to-end CLI integration tests proving:
  - absolute slug exits 2, /etc/passwd/docs/ never created
  - traversal slug exits 2, sibling dir never created
  - nested slug exits 2 with helpful message
  - empty slug exits 2 with helpful message

169/169 tests pass; ruff + mypy clean.
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