fix: restore "empty version means latest" behavior broken by PR #44 hardening#46
Conversation
…n hardening PR #44's command-injection hardening validated the caller-supplied version with 'version !== undefined', but the public API documents version as optional-defaults-to-latest and the pre-hardening code treated any falsy version ('', null) as 'install latest'. Clients sending an empty version field started getting 'Invalid package version: ...' errors from POST /packages/install and PUT /packages/:name/upgrade. Fixes: - Normalize version input in installPackage/upgradePackage: null, empty, and whitespace-only versions are treated as undefined (latest), and surrounding whitespace is trimmed before validation. Malicious version specs are still rejected by the same allowlist. - The npm package-name allowlist was also applied to python-runtime packages in upgradePackage and checkForUpdates before their python branches. PyPI names may legally contain uppercase letters (accepted at install time), so those records could never be upgraded or update-checked. Both paths now validate the persisted name against the allowlist matching the package's runtime. Adds regression tests covering empty/null/whitespace versions on install and upgrade, version trimming, and uppercase python package names in upgradePackage/checkForUpdates. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015MvwXBH8tFAebZWZExXVm3
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015MvwXBH8tFAebZWZExXVm3
|
Automated review 🤖 Summary of Changes This PR fixes two regressions introduced by PR #44's injection-hardening in Key Changes & Positives
Potential Issues & Recommendations
Approval Recommendation Approve with caveats
|
There was a problem hiding this comment.
Pull request overview
Restores the pre-#44 API behavior where an empty/null/whitespace version means “latest”, and fixes runtime-specific persisted-name re-validation so Python packages (including those with uppercase names) can be upgraded and update-checked again without weakening the command-injection hardening.
Changes:
- Normalize
versioninputs (null/empty/whitespace →undefined, trim surrounding whitespace) before npm version allowlist validation. - Re-validate persisted package names using the correct allowlist per runtime (npm vs PyPI) in
upgradePackageandcheckForUpdates. - Add regression tests and bump package version to
1.11.11.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
src/services/packages.ts |
Normalizes version before validation and applies runtime-appropriate name allowlists for upgrade/update-check paths. |
test/packages-injection.spec.ts |
Adds regression tests for empty/null/whitespace versions and uppercase Python package names through upgrade/update-check paths. |
package.json |
Bumps the published version to 1.11.11. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Empty/null versions mean "latest" (see normalizeVersionInput). | ||
| const version = PackageService.normalizeVersionInput(request.version) |
| // Empty/null versions mean "latest" (see normalizeVersionInput). | ||
| version = PackageService.normalizeVersionInput(version) | ||
|
|
Addresses review feedback on PR #46: normalizeVersionInput previously stringified non-string inputs (true -> 'true', 123 -> '123'), which was looser than the typeof guard in isValidNpmVersionSpec on main. Non-string, non-null version values in the request body are now rejected with the standard invalid-version error before normalization, in both installPackage and upgradePackage. Also per review: upgradePackage uses a new normalizedVersion const instead of reassigning the parameter, and the pip index versions mock in the uppercase-python test documents the output format the production parser consumes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015MvwXBH8tFAebZWZExXVm3
|
Automated review 🤖 Summary of Changes This PR fixes two regressions introduced by PR #44's injection-hardening in Key Changes & Positives
Potential Issues & Recommendations
Tests
Approval Recommendation Approve with caveats
|
Per review on PR #46: note at the controller call site that the service's version type/format re-validation is intentional defense-in-depth (the body cast performs no runtime validation), and document that PYTHON_NAME_RE is deliberately more permissive than PEP 508 since its purpose is subprocess-argument safety, not PyPI name canonicalization. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015MvwXBH8tFAebZWZExXVm3
|
Automated review 🤖 Summary of Changes This PR fixes two regressions introduced by PR #44's injection-hardening: (1) empty/null/whitespace Key Changes & Positives
Potential Issues & Recommendations
Tests The Approval Recommendation Approve with caveats
|
…hen pip-path test Per review on PR #46: - PYTHON_NAME_RE now requires names to end (as well as start) with an alphanumeric character, matching PEP 508's boundary rules, so names like 'my-pkg-' get a structured validation error instead of raw pip stderr. Consecutive separators remain accepted (documented) since the allowlist exists for subprocess-argument safety, not PyPI canonicalization. - The uppercase-python upgrade test now asserts the pip code path actually ran (pip install --upgrade MarkupSafe, version read via pip show) so a future refactor cannot pass the test through the node path vacuously. - Added boundary-rejection tests for python install names. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015MvwXBH8tFAebZWZExXVm3
|
Automated review 🤖 Summary of Changes This PR fixes two regressions introduced by PR #44's injection hardening in Key Changes & Positives
Potential Issues & Recommendations
Tests
Approval Recommendation Approve with caveats
|
…obustness Per review on PR #46: - Controller cast widened to 'string | null | undefined' (JSON bodies can carry version: null) and upgradePackage's signature updated to match. - upgradePackage logs a warn with the runtime when a persisted name fails its allowlist, and the checkForUpdates skip log includes the runtime, easing diagnosis of legacy records. - Empty-string upgrade test locates the npm install call with find() instead of indexing [0], consistent with the rest of the suite. - Documented in beforeEach why per-test execFile mockImplementations cannot leak across tests (mockResolvedValue replaces implementations). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015MvwXBH8tFAebZWZExXVm3
|
Automated review 🤖 Summary of Changes This PR fixes two regressions introduced by PR #44's injection hardening in Key Changes & Positives
Potential Issues & Recommendations
Tests
Approval Recommendation Approve with caveats
|
Per review on PR #46: - PYTHON_NAME_RE comment now states unambiguously that consecutive separators are intentionally deferred to pip (shell-inert via execFile argv, non-canonical per PEP 508), rather than framing it as a gap; the boundary tests carry a matching note. - The controller's upgrade log trims the raw version so whitespace-only input no longer logs a misleading ' to version ' suffix. The empty-string upgrade test fixture already pins runtime: 'node' explicitly, so no fixture change was needed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015MvwXBH8tFAebZWZExXVm3
|
Automated review 🤖 Summary of Changes This PR fixes two regressions introduced by PR #44's injection hardening in Key Changes & Positives
Potential Issues & Recommendations
Tests
Approval Recommendation Approve with caveats
|
|
Re the latest automated review — no changes made this round, because both checkbox items rest on incorrect premises: Issue 1 (type-guard Issue 3 (pin Issue 2 was marked "no change required" in the review itself; the redundant Generated by Claude Code |
Problem
After merging #44 and #45, installs and upgrades through the API started failing. Reproduced by booting the API, installing
@missionsquad/mcp-helper-tools, and exercising the package endpoints. The root cause is in #44's injection hardening (#45 checked out clean — its SSRF validation only affects oauth2 servers with aregistrationEndpointand behaves as intended):Empty
versionnow rejected. The API documentsversionas optional, defaults to latest, and pre-NPM Package Version Command Injection Vulnerability in MissionSquad/mcp-api #44 code used falsy checks (version ? \${name}@${version}` : name), soversion: ""/version: nullinstalled latest. #44's guard isversion !== undefined, so those requests now fail withInvalid package version: . Version must be a valid npm semver, range, or tag.onPOST /packages/installandPUT /packages/:name/upgrade` — breaking any client that sends an empty version field.npm name allowlist applied to Python packages. In
upgradePackageandcheckForUpdates, the persisted-name re-validation ran before the python-runtime branch. PyPI names may legally contain uppercase letters (the python install path accepts/^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/), but the npm allowlist is lowercase-only — so such Python packages could never be upgraded or update-checked again.Fix
installPackage/upgradePackagenormalize the version input before validation:null,"", and whitespace-only mean "latest", and surrounding whitespace is trimmed. Genuinely invalid specs are still rejected by the same allowlist.upgradePackage/checkForUpdatesnow uses the allowlist matching the package's runtime (python regex for python records, npm regex otherwise).The security posture of #44 is unchanged: all npm invocations still go through the
execFile-basedrunNpm(no shell), and malicious names/versions ("1.0.0; touch /tmp/pwn",--registry=..., etc.) are still rejected — re-verified live and covered by the existing tests.Verification
""/nullversion succeeds and installs latest; upgrade with""succeeds (helper-tools → 1.1.1);mcp/tool/callagainst the installed server works; malicious version specs still return the validation error.🤖 Generated with Claude Code
https://claude.ai/code/session_015MvwXBH8tFAebZWZExXVm3
Generated by Claude Code