Skip to content

security: fix SSRF, file size DoS, PermissionError shadowing, and CI/CD supply chain gaps#19

Draft
Copilot wants to merge 3 commits intomainfrom
copilot/fix-ssrf-risk-in-sdks
Draft

security: fix SSRF, file size DoS, PermissionError shadowing, and CI/CD supply chain gaps#19
Copilot wants to merge 3 commits intomainfrom
copilot/fix-ssrf-risk-in-sdks

Conversation

Copy link

Copilot AI commented Mar 1, 2026

Four medium-severity security issues across both SDKs and the release pipeline. Changes add URL validation, file size limits, fix a Python built-in name collision, and harden the release workflow.

SSRF — baseUrl / base_url validation

Both constructors now reject unsafe URLs before any requests are made:

  • Non-HTTPS schemes (http://, etc.)
  • Localhost and loopback (localhost, 127.0.0.1, ::1, 0.0.0.0)
  • Private/link-local IPv4 ranges: 10.x, 172.16–31.x, 192.168.x, 169.254.x (covers AWS metadata endpoint)
# Raises ValidationError: "base_url must use HTTPS protocol"
Capture(token="tok", base_url="http://internal-service/api")

# Raises ValidationError: "base_url must not point to localhost or private network addresses"
Capture(token="tok", base_url="http://169.254.169.254/latest/meta-data/")

File size DoS — configurable maxFileSize / max_file_size

Added to CaptureOptions (default 100 MB). For file paths, stat() is checked before reading; for File/Blob, .size is used; for Buffer/bytes, byte length is checked. Applied in both register() and searchAsset().

const capture = new Capture({ token, maxFileSize: 50 * 1024 * 1024 }) // 50 MB cap

Python PermissionErrorForbiddenError

The SDK's PermissionError shadowed Python's built-in (OSError subclass), causing confusing except PermissionError behavior in user code. Renamed to ForbiddenError; PermissionError = ForbiddenError alias retained for backwards compatibility. ForbiddenError is now exported from __init__.py.

CI/CD supply chain hardening (release.yml)

  • Test gate: new test job runs npm test + pytest before publish; both publish-npm and publish-pypi now depend on [validate, test].
  • Pinned action: softprops/action-gh-release@v1 → pinned to commit SHA de2c0eb89ae2a093876385947365aca7b0e5f844.
  • Artifact checksums: SHA-256 of the npm .tgz and Python dist/* are computed and included in the GitHub release body.
Original prompt

This section details on the original issue you should resolve

<issue_title>[Security][Medium] SSRF risk, file size DoS, PermissionError shadowing, and CI/CD supply chain gaps</issue_title>
<issue_description>## Security Findings — Medium Severity

1. Custom baseUrl Allows SSRF When Used Server-Side

Files:

  • ts/src/client.ts line 152
  • python/numbersprotocol_capture/client.py line 158

Description:
Both SDKs accept a custom baseUrl/base_url parameter without any validation. There is no check that the URL uses HTTPS, belongs to a trusted domain, or is even a valid URL. The SDK will then send authenticated requests (including the API token) to whatever URL is provided.

Impact:
In server-side applications where baseUrl might be influenced by configuration or environment variables, this enables SSRF attacks. An attacker can set baseUrl to an internal network address (e.g., http://169.254.169.254/latest/meta-data/) to exfiltrate cloud credentials, or to an attacker-controlled server to capture the API token.

Suggested fix:
Add validation to ensure baseUrl uses HTTPS protocol. Optionally support a configurable allowlist of trusted domains. At minimum, reject http://, private IP ranges, and localhost.


2. No File Size Limits on Asset Registration (DoS via Memory Exhaustion)

Files:

  • ts/src/client.ts lines 86, 94, 103 (in normalizeFile)
  • python/numbersprotocol_capture/client.py lines 83, 92, 101 (in _normalize_file)

Description:
Both SDKs read entire files into memory without any size limit. The only validation is that the file is non-empty, but there is no upper bound. In TypeScript, the data is copied a second time for ArrayBuffer conversion, so peak memory is ~2x file size.

Impact:
A large file can cause out-of-memory crashes (denial of service). Particularly dangerous in server-side applications processing user-provided files.

Suggested fix:
Add a configurable maxFileSize parameter (default e.g. 100 MB). For file paths, use fs.stat() / path.stat() to check size before reading. For blobs, check .size property.


3. Python PermissionError Shadows Built-in

File: python/numbersprotocol_capture/errors.py line 35

Description:
The SDK defines a custom PermissionError class that shadows Python's built-in PermissionError (a subclass of OSError). Any code using from numbersprotocol_capture import * will shadow the built-in.

Impact:
except PermissionError blocks in user code will catch SDK API 403 errors but not OS-level file permission errors (or vice versa), causing confusing debugging scenarios.

Suggested fix:
Rename to ForbiddenError or AccessDeniedError to avoid name collision. This is a breaking change requiring a minor/major version bump with a deprecation alias.


4. API Token Sent to Multiple Third-Party Cloud Endpoints

Files:

  • ts/src/client.ts lines 374-379, 430-436
  • python/numbersprotocol_capture/client.py lines 449-451, 514-517

Description:
The API token is sent via Authorization header to four distinct third-party endpoints across different providers: the primary API, AWS Lambda, Google Cloud Functions, and Pipedream. Issue #10 covers only the Pipedream endpoint; the AWS Lambda and GCF endpoints also receive the full token.

The AWS Lambda URL uses an auto-generated API Gateway identifier (e23hi68y55), suggesting it may lack production-grade security controls.

Impact:
Broadest possible token exposure surface. If any third-party endpoint is compromised or logs request headers, the token is leaked.

Suggested fix:
Consider using separate, scoped tokens for different endpoints, or route all requests through the primary API. At minimum, document which endpoints receive the token.


5. CI/CD Release Pipeline Missing Integrity Verification

File: .github/workflows/release.yml lines 51-122

Description:

  • No CI test gate before publish: Publish jobs depend only on validate (version check), not on CI tests passing. A tag push can publish even if tests fail.
  • Unpinned GitHub Action: softprops/action-gh-release@v1 uses a mutable tag rather than a pinned SHA.
  • No artifact checksums: No verification of built artifacts before publishing.

Impact:
Compromised CI environment or dependency could inject malicious code into published packages without detection.

Suggested fix:

  1. Add CI test jobs as dependencies of publish jobs.
  2. Pin softprops/action-gh-release to a specific commit SHA.
  3. Add artifact checksum computation and include hashes in release notes.</issue_description>

Comments on the Issue (you are @copilot in this section)


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Copilot AI and others added 2 commits March 1, 2026 00:43
…CD supply chain gaps

Co-authored-by: numbers-official <181934381+numbers-official@users.noreply.github.com>
Co-authored-by: numbers-official <181934381+numbers-official@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix SSRF risk in custom baseUrl handling security: fix SSRF, file size DoS, PermissionError shadowing, and CI/CD supply chain gaps Mar 1, 2026
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.

[Security][Medium] SSRF risk, file size DoS, PermissionError shadowing, and CI/CD supply chain gaps

2 participants