Skip to content

fix: allow scoped @collective/name in workflow and definition names#888

Merged
stack72 merged 1 commit intomainfrom
fix/887-scoped-name-validation
Mar 27, 2026
Merged

fix: allow scoped @collective/name in workflow and definition names#888
stack72 merged 1 commit intomainfrom
fix/887-scoped-name-validation

Conversation

@stack72
Copy link
Copy Markdown
Contributor

@stack72 stack72 commented Mar 27, 2026

Summary

Extension workflows using scoped names like @john/pod-inventory were rejected
at load time with a path traversal error because the WorkflowSchema and
DefinitionSchema name validation unconditionally rejected any name containing
/. The / in scoped @collective/name patterns is a namespace separator, not
a path traversal vector.

Root cause: The Zod refinement on the name field in both WorkflowSchema
(src/domain/workflows/workflow.ts:41-44) and DefinitionSchema
(src/domain/definitions/definition.ts:151-154) used a flat blocklist:

!name.includes("..") && !name.includes("/") && !name.includes("\\") && !name.includes("\0")

This blocked all / characters, including legitimate ones in scoped extension
names like @john/pod-inventory or @swamp/aws/ec2.

Fix: Updated the inline validation in both schemas to allow / only when
the full name matches the established SCOPED_NAME_PATTERN
(^@[a-z0-9_-]+\/[a-z0-9_-]+(\/[a-z0-9_-]+)*$), which is the same regex used
in extension_manifest.ts. Unscoped names containing / (e.g. a/b,
/etc/passwd) are still rejected. The data_metadata.ts validation is
intentionally unchanged — data artifact names are internal and should not use
scoped patterns.

Changes:

  • src/domain/workflows/workflow.ts — Updated name validation refinement
  • src/domain/definitions/definition.ts — Updated name validation refinement
  • src/domain/workflows/workflow_test.ts — Added tests for scoped name
    acceptance and malformed scoped name rejection
  • src/domain/definitions/definition_test.ts — Same test additions

Verified end-to-end: compiled binary, swamp repo init, swamp extension pull @john/k8s, swamp workflow search pod — all 13 workflows load without
warnings.

Test Plan

  • Existing path traversal rejection tests pass (..', unscoped /, \, null bytes)
  • New tests: scoped @collective/name accepted (@john/pod-inventory, @swamp/aws/ec2)
  • New tests: malformed scoped names rejected (@/, @scope/, @UPPER/case)
  • Full test suite: 3592 tests pass
  • deno check, deno lint, deno fmt all clean
  • E2E: compiled binary, pulled @john/k8s, all 13 workflows load correctly

Fixes #887

🤖 Generated with Claude Code

The path traversal validation in WorkflowSchema and DefinitionSchema
rejected any name containing '/', which blocked extension workflows
using scoped names like @john/pod-inventory. The '/' in scoped
@collective/name patterns is a namespace separator, not a path
traversal vector.

Updated the inline Zod refinement in both schemas to allow '/' only
when the name matches the established SCOPED_NAME_PATTERN
(^@[a-z0-9_-]+\/[a-z0-9_-]+(\/[a-z0-9_-]+)*$). Unscoped names
with '/' are still rejected. The data_metadata.ts validation is
intentionally unchanged — data artifact names are internal and
should not use scoped patterns.

Fixes #887
@stack72 stack72 force-pushed the fix/887-scoped-name-validation branch from 8bc16bf to 480e265 Compare March 27, 2026 00:51
Copy link
Copy Markdown

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

Blocking Issues

None.

Suggestions

  1. Extract shared regex constant: The scoped name regex ^@[a-z0-9_-]+\/[a-z0-9_-]+(\/[a-z0-9_-]+)*$ is now duplicated in 5 places (extension_manifest.ts, pull.ts, yank.ts, workflow.ts, definition.ts). Consider extracting it to a shared domain value object or constant (e.g., in src/domain/extensions/scoped_name.ts) to keep the pattern in one canonical location. Not blocking since the regex is correct and consistent across all sites.

Notes

  • Regex matches the existing SCOPED_NAME_PATTERN from extension_manifest.ts exactly — good consistency.
  • Validation logic is sound: .., \, and \0 are rejected first, then / is only allowed when the full name matches the scoped pattern. Path traversal vectors like @scope/../etc are caught by the .. check before reaching the / branch.
  • Tests cover acceptance (@john/pod-inventory, @swamp/aws/ec2) and rejection (@/, @scope/, @UPPER/case) — good coverage of edge cases.
  • data_metadata.ts intentionally unchanged — correct bounded context boundary.
  • DDD: Changes are appropriately scoped to aggregate root schema validation in the domain layer.
  • No libswamp import boundary violations.
  • No security concerns — the change strictly tightens the validation by only allowing / in a well-defined scoped pattern.

LGTM ✓

Copy link
Copy Markdown

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adversarial Review

Critical / High

None.

Medium

  1. Inline regex duplicationworkflow.ts:48 and definition.ts:158 both inline the regex /^@[a-z0-9_-]+\/[a-z0-9_-]+(\/[a-z0-9_-]+)*$/ rather than importing SCOPED_NAME_PATTERN from extension_manifest.ts (where it's already defined at line 26, and also duplicated in pull.ts:37 and yank.ts:27). If the pattern evolves (e.g., allowing . in segments), these two new sites could drift. Not a bug today, but a maintenance risk. Consider extracting to a shared constant.

Low

  1. Missing test for .. + / combo — The existing ../../../tmp/evil test covers .. in unscoped names, but there's no test for the combined vector @foo/../bar (scoped-looking name with path traversal). The code correctly rejects it because includes("..") fires before the regex check, but an explicit test would document this defense. Purely a test coverage observation.

  2. Unscoped @ names pass validation — A name like @single (no slash) passes the validation because it contains no /, .., \, or \0. This isn't a security issue (no path traversal), but it means the name field accepts partial scoped patterns. This is likely fine since format enforcement belongs elsewhere.

Verdict

PASS — The fix is correct and well-scoped. The .. check fires before the / check, blocking all path traversal vectors through scoped names. The regex exactly matches the canonical SCOPED_NAME_PATTERN. Edge cases (empty segments, uppercase, dots, null bytes in scoped names) are all properly rejected. Tests cover the key acceptance and rejection cases.

@stack72 stack72 merged commit 86500cc into main Mar 27, 2026
10 checks passed
@stack72 stack72 deleted the fix/887-scoped-name-validation branch March 27, 2026 00:59
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.

Extension workflows with @ in name rejected by path traversal validation

1 participant