Skip to content

fix: merge latest dev updates into main - #4

Merged
node9ai merged 3 commits into
mainfrom
dev
Apr 4, 2026
Merged

fix: merge latest dev updates into main#4
node9ai merged 3 commits into
mainfrom
dev

Conversation

@node9ai

@node9ai node9ai commented Apr 4, 2026

Copy link
Copy Markdown
Contributor

Auto-generated PR

Merge latest dev changes into main to trigger a release.

⚠️ Important: When you click Squash and Merge, ensure the commit message starts with:

  • fix: to publish a Patch release (0.0.X)
  • feat: to publish a Minor release (0.X.0)
    If it starts with chore:, no PyPI package will be published!

@node9ai

node9ai commented Apr 4, 2026

Copy link
Copy Markdown
Contributor Author

🤖 Claude Code Review

Code Review: Node9 Python SDK — Cloud Routing PR

Security Issues (High Priority)

1. cwd leaks sensitive path information
os.getcwd() is included in every cloud request payload. In CI environments this often contains repo names, org names, or secrets embedded in paths. This is exfiltrated to an external SaaS endpoint with no opt-out mechanism. Consider omitting or making it opt-in.

2. hostname included unconditionally
platform.node() can reveal internal infrastructure naming conventions. Same concern as cwd — no redaction or opt-out.

3. NODE9_API_KEY check is inconsistent
In evaluate(), the guard is if os.environ.get("NODE9_API_KEY"): — truthy check. In _evaluate_cloud(), an empty string raises RuntimeError. But these are the same code path, so the empty string case in _evaluate_cloud is unreachable. The truthy check in evaluate() already gates it. This is dead code that creates a false sense of validation. The empty-string check should live in evaluate() before dispatch, or the logic should be consolidated.

4. HTTPS-only check is bypassable with https://evil.com
The check api_url.startswith("https://") does nothing meaningful for security. A developer could set NODE9_API_URL=https://attacker.com and all credentials + tool call data get forwarded there. There's no certificate pinning or domain allowlist. For a security library, this warrants at minimum a warning in docs, and ideally a NODE9_API_URL override should be locked to known domains unless an explicit "dev mode" flag is set.

5. ci_context value types are not validated
Keys are allowlisted, but values are passed through unconstrained. A github_head_ref containing a 9MB string or a nested object would still pass. Add per-value type/length validation.

6. _read_ci_context size cap has an off-by-one ambiguity
Reading _CI_CONTEXT_MAX_BYTES + 1 bytes to detect overflow is correct, but then json.loads runs on the full oversized buffer before the length check. The check if len(raw_bytes) > _CI_CONTEXT_MAX_BYTES happens after parsing — this is fine since read() happens before json.loads, but the order (read → length check → json.loads) would be clearer and avoids parsing attacker-controlled JSON unnecessarily.


Correctness & Edge Cases

7. Polling interval is fixed at 1 second with no backoff
For a 600-second default timeout, this is 600 HTTP requests. Under network issues the continue on URLError means all 600 fire regardless. Add exponential backoff with a cap (e.g., max 10s).

8. _CHECK_TIMEOUT = 5 used for initial POST to SaaS
5 seconds is very tight for a network call that may trigger async approval workflows. The initial POST timeout should be distinct from the daemon check timeout, or at least documented. A slow SaaS response will raise URLError and surface a confusing error.

9. int(os.environ.get("NODE9_CLOUD_TIMEOUT", "600")) can raise ValueError
If someone sets NODE9_CLOUD_TIMEOUT=abc, this crashes with an unhandled ValueError inside evaluate(). Wrap in a try/except with a fallback or clear error message.

10. status_url construction
f"{api_url}/status/{request_id}"api_url already has .rstrip("/") applied, and request_id is regex-validated, so this is fine. But if NODE9_API_URL is overridden to a non-standard base path, the status URL construction could be wrong. Minor, but worth noting.


API Design / Usability

11. Removed quickstart docstring from __init__.py
The removal of the usage example is a regression for developer experience. Inline examples in __init__.py are often the first thing developers see. Move them to a docstring on protect if you want to reduce __init__.py noise, but don't drop them entirely.

12. print() used for approval waiting message
print(f"🛡️ Node9: waiting for approval...") is inappropriate for a library. Use logging.getLogger("node9").info(...). Library code should never write to stdout unconditionally — it breaks structured logging pipelines and CI log parsers.


Test Coverage Gaps

  • No tests for NODE9_CLOUD_TIMEOUT invalid value (ValueError path)
  • No tests for _read_ci_context with oversized file, non-dict JSON, or disallowed keys
  • No tests for polling loop: APPROVED, DENIED, timeout expiry, transient network errors
  • No tests verifying HTTPS enforcement and that HTTP URLs are rejected
  • No test for the unreachable empty-API-key branch in _evaluate_cloud

Workflow Change

The paths-ignore addition for ai-review.yml and scripts/ai-review.mjs is reasonable to prevent infinite loops. No issues.


Automated review by Claude Sonnet

@node9ai
node9ai merged commit b09c223 into main Apr 4, 2026
20 checks passed
@node9ai
node9ai deleted the dev branch April 4, 2026 00:28
@node9ai
node9ai restored the dev branch April 4, 2026 09:09
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