Skip to content

feat(security): add injection character validation to all purl type validators#16

Merged
jdalton merged 6 commits intomainfrom
feat/injection-validation-all-types
Mar 30, 2026
Merged

feat(security): add injection character validation to all purl type validators#16
jdalton merged 6 commits intomainfrom
feat/injection-validation-all-types

Conversation

@jdalton
Copy link
Copy Markdown
Collaborator

@jdalton jdalton commented Mar 30, 2026

Summary

  • Add containsInjectionCharacters() checks to all 28 per-type validators, rejecting shell/URL metacharacters (|, &, ;, `, $, <, >, (, ), {, }, #, \, space, tab, newline, CR) in name and namespace components
  • Previously only vscode-extension had these checks — now every ecosystem type validates against injection characters
  • Checks are per-type (not in base validator) to respect the purl spec which allows special characters in the generic type via percent-encoding
  • Version strings intentionally NOT checked (Python epoch !, Maven space/& are legitimate)
  • 47 new tests covering injection rejection across all types

Changes

  • Enhanced 19 existing validators with injection checks (cargo, gem, nuget, maven, golang, cocoapods, conda, swift, bazel, cran, conan, cpan, swid, oci, opam, otp, julia, mlflow, yocto)
  • Added new validate functions to 6 types that previously only had normalize (docker, github, gitlab, bitbucket, hex, pypi)
  • Registered all new validators in purl-type.ts
  • Cocoapods: replaced \s regex with containsInjectionCharacters (subsumes whitespace + adds shell metachar detection)
  • npm/pub: already covered by URL-encoding and [a-z0-9_] checks respectively

Test plan

  • All 1574 existing + new tests pass
  • pnpm run check --all passes
  • pnpm run fix --all passes
  • Build succeeds
  • Purl spec tests (contrib-tests.json with special chars in generic type) still pass
  • Maven 1.0.0 Final version with space still works
  • npm legacy names with ~'!()* still accepted

🤖 Generated with Claude Code

jdalton added 6 commits March 30, 2026 13:26
The conda and docker purlExists tests make real HTTP requests to external
registries. The conda test was timing out on CI at the default 10s limit.
Increase timeout to 30s for both network-calling tests.
…alidators

Add containsInjectionCharacters() checks to all 28 per-type validators,
rejecting shell/URL metacharacters (|, &, ;, `, $, <, >, (, ), {, }, #,
\, space, tab, newline, CR) in name and namespace components.

Previously only vscode-extension had these checks. Now every ecosystem
type validates against injection characters while respecting the purl
spec (which allows special characters in the generic type via
percent-encoding — so checks are per-type, not in the base validator).

- Enhanced 19 existing validators with injection checks
- Added new validate functions to 6 types (docker, github, gitlab,
  bitbucket, hex, pypi) that previously only had normalize
- Registered all new validators in purl-type.ts
- Cocoapods: replaced \s regex with containsInjectionCharacters
  (subsumes whitespace + adds shell metachar detection)
- npm/pub: already covered by URL-encoding and [a-z0-9_] checks
- Version strings intentionally NOT checked (Python epoch ! and
  Maven space/& are legitimate)
- 47 new tests covering all types
…add PurlInjectionError

Architecture improvements:
- Default validator in purl-type.ts now runs injection checks for all
  registered types — security is opt-out, not opt-in. Unregistered types
  (used by purl spec tests) bypass the default, preserving spec compliance.
- New shared validateNoInjectionByType() helper in validate.ts eliminates
  6-line injection check boilerplate from 26 per-type validators.
- Per-type validators now only contain ecosystem-specific rules.

Hardened scanner (containsInjectionCharacters):
- Added single quote (') and double quote (") detection — prevents
  quote-breaking attacks in shell, SQL, and URL contexts
- Added full C0 control character range (0x00-0x1f) — catches ESC
  (terminal escape sequences), BEL, vertical tab, form feed, and all
  other control chars used for log/terminal injection
- Added DEL (0x7f) detection — control character used in terminal attacks
- Extracted isInjectionCharCode() for the core detection logic
- Added findInjectionCharCode() returning the offending char code
- Added formatInjectionChar() for human-readable error labels

New PurlInjectionError class:
- Subclass of PurlError — catchable as either type for flexible handling
- Exposes charCode, component, and purlType properties for programmatic
  inspection by security tooling
- Error messages include the specific character found, e.g.:
  'maven "namespace" component contains injection character ";" (0x3b)'
- Control characters formatted as hex: '0x1b' (not the raw ESC byte)
…ests

- Freeze PurlInjectionError instances (ObjectFreeze in constructor) and
  prototype — prevents property tampering on caught error objects
- Replace raw String.fromCharCode, Number.prototype.toString, and
  String.prototype.padStart with captured primordials
  (StringFromCharCode, NumberPrototypeToString, StringPrototypePadStart)
- Add unit tests verifying frozen instance, frozen prototype, and
  rejection of property writes/additions on error objects
- validate.ts: Array.isArray → ArrayIsArray, Object.keys → ObjectKeys
- normalize.ts: Object.entries → ObjectEntries
- vscode-extension.ts: JSON.stringify → JSONStringify
- gem.ts: Array.isArray → ArrayIsArray
- npm.ts: new Set() → new SetCtor() (2 instances)
- primordials.ts: add NumberPrototypeToString, StringFromCharCode,
  StringPrototypePadStart exports
@jdalton jdalton merged commit 64331f0 into main Mar 30, 2026
9 checks passed
@jdalton jdalton deleted the feat/injection-validation-all-types branch March 30, 2026 22:13
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