Skip to content

fix(sandboxed-web-e2e): probe explicit port 0, not the scheme default - #1467

Merged
seonghobae merged 3 commits into
mainfrom
fix/sandboxed-web-e2e-port-zero
Aug 31, 2026
Merged

fix(sandboxed-web-e2e): probe explicit port 0, not the scheme default#1467
seonghobae merged 3 commits into
mainfrom
fix/sandboxed-web-e2e-port-zero

Conversation

@seonghobae

@seonghobae seonghobae commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Bug

require_unoccupied_readiness_port() in scripts/ci/sandboxed_web_e2e.py derived the probe port with:

port = parsed.port or (443 if parsed.scheme.lower() == "https" else 80)

urllib.parse's .port returns the int 0 for a URL with an explicit :0 port (e.g. http://127.0.0.1:0/health), and 0 or X evaluates to X in Python — so an explicitly-requested port 0 was silently replaced with the scheme's default port (80/443) instead of actually being probed. This means the pre-start "is this readiness port already occupied?" check would connect to the wrong port entirely whenever a caller (deliberately or accidentally) passed a :0 readiness URL.

This was flagged by an external reviewer (Devin) during review of #1347, but was out of scope for that PR's authorized task, so it was left unfixed.

Fix

port = parsed.port if parsed.port is not None else (443 if parsed.scheme.lower() == "https" else 80)

An explicit is not None check distinguishes "no port in the URL" (None, defaulting as before) from "port 0 was requested" (probed as 0).

I checked the rest of the file (wait_for_url and every other URL-handling helper) for the same X.port or <default> shape — require_unoccupied_readiness_port is the only call site that manually derives a port; wait_for_url hands the full URL straight to urllib.request's opener rather than deriving a port, so it does not have this bug.

Tests

Added test_require_unoccupied_readiness_port_probes_explicit_port_zero to tests/test_sandboxed_web_e2e.py: it monkeypatches socket.create_connection to record the address require_unoccupied_readiness_port actually probes for http://127.0.0.1:0/health, and asserts it is ("127.0.0.1", 0) rather than ("127.0.0.1", 80).

Verified locally:

  • python -m pytest tests/test_sandboxed_web_e2e.py -q → 67 passed
  • coverage run -m pytest tests && coverage report --show-missing → 2091 passed, 1 skipped, 21 subtests passed; scripts/ci at 100% coverage (no missing lines/branches)
  • interrogate → 100% docstring coverage

Scope

Single targeted fix + regression test, no unrelated changes. Not merging this myself — leaving it for the normal OpenCode review → merge-scheduler pipeline per this repo's governance model.


Generated by Claude Code


Devin Review

Bandit operational follow-up

After #1464 made the central Python detector execute reliably, its exact-head hosted Bandit job exposed two B108 findings on this file's literal "/tmp" values. Both values are bubblewrap --tmpfs mount targets created inside a new isolated namespace, not host temporary-file paths.

This owner branch now carries exactly two paired # nosec B108 / # noqa: S108 waivers, each immediately preceded by that rationale. The regression pins the exact waiver text, count, and rationale, so neither Bandit nor Ruff exceptions can broaden to other rules or arbitrary paths.

Exact head: ef75ce93732e5706aa51f73a5f466d65aef2c1ff

Independent exact-tree verification:

  • RED: the Bandit scope regression failed before the B108 waiver; the extended regression failed again before the S108 waiver.
  • Focused: 68 passed.
  • Full: 2,092 passed · 1 skipped · 21 subtests.
  • Coverage: statements 10,455/10,455, branches 4,164/4,164; docstrings 100%.
  • Workflow-equivalent Bandit: 0 findings.
  • Ruff 0.16.2 --select S108: all checks passed.
  • Hosted exact-head checks are still queued/in progress, and hosted Bandit remains skipped until fix(ci): stop find | head -1 | grep -q from silently losing matches under pipefail #1464's detector repair reaches protected main; no predecessor or skipped evidence is counted.

require_unoccupied_readiness_port() derived the probe port with
`parsed.port or (443 if https else 80)`. urllib.parse's .port returns
the int 0 for a URL with an explicit :0 port, and `0 or X` evaluates
to X in Python, so an explicitly-requested port 0 was silently
replaced with the scheme's default port (80/443) instead of actually
being probed. Devin's review on PR #1347 flagged this pattern but it
was out of scope for that PR's authorized task.

Switch to an explicit None check so port 0 is honored, and add a
regression test that monkeypatches socket.create_connection to record
the probed address and assert it names port 0.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KPmJErfkcHer4UVEgrQxUX
@coderabbitai

coderabbitai Bot commented Aug 31, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

require_unoccupied_readiness_port가 명시적 포트 0을 유지하도록 수정되었습니다. 두 bubblewrap tmpfs 대상에는 설명과 # nosec B108 주석이 추가되었습니다. 두 변경을 검증하는 테스트가 추가되었습니다.

Changes

샌드박스 웹 E2E 보정

Layer / File(s) Summary
명시적 readiness 포트 처리
scripts/ci/sandboxed_web_e2e.py, tests/test_sandboxed_web_e2e.py
require_unoccupied_readiness_port가 URL의 명시적 포트 0을 사용합니다. 테스트는 127.0.0.1:0으로 연결을 시도하는지 확인합니다.
tmpfs Bandit 예외 명확화
scripts/ci/sandboxed_web_e2e.py, tests/test_sandboxed_web_e2e.py
두 bubblewrap /tmp 대상에 설명과 # nosec B108 주석을 추가했습니다. 테스트는 해당 예외와 설명이 각각 두 번만 존재하는지 확인합니다.

Estimated code review effort: 2 (Simple) | ~10 minutes

Merge Risk: 🔵 Low · up to d8ccd

The change fixes explicit port-0 readiness probing without altering production interfaces or deployment behavior. Merge readiness is low risk, but the repository’s lint checks may reject the two sandbox mount paths unless they receive narrowly scoped exemptions.

Suggested reviewers: cursoragent

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 87.50% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 8 functions across 2 files.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목은 명시적으로 지정된 포트 0을 기본 스킴 포트 대신 탐색하도록 수정한 핵심 변경을 정확하고 간결하게 설명합니다.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/sandboxed-web-e2e-port-zero

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Note

This report is out of date. Scroll down for Devin Review's latest report on this PR.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Devin Review

Bandit B108 correctly began running after the central file-detection repair and identified the two literal /tmp arguments passed to bubblewrap. These are tmpfs mount targets inside a new isolated namespace, not host temporary-file paths. Add B108-only waivers with adjacent rationale and a regression that pins both the scope and count, preserving every other Bandit rule and host-path finding.
devin-ai-integration[bot]

This comment was marked as resolved.

Copy link
Copy Markdown
Contributor Author

@opencode-agent review

Exact-head request: d8ccd5ef1754a3d3ce7c6356af21abbdfde8525e. Please issue a formal current-head verdict; status-only or predecessor evidence is non-passing.

coderabbitai[bot]

This comment was marked as resolved.

CodeRabbit ran `ruff check --select S108` against this file and found
the two bubblewrap tmpfs `"/tmp"` mount-target arguments still fail
Ruff's own insecure-temp-path rule -- `# nosec B108` only silences
Bandit, not Ruff. Add `# noqa: S108` alongside each existing waiver and
extend the regression test that already pins the Bandit waiver's exact
text/count/rationale to also pin the Ruff waiver, so this scoped
exception can't silently broaden to other paths or rules.

Co-Authored-By: Claude Sonnet 5
Claude-Session: https://claude.ai/code/session_01KPmJErfkcHer4UVEgrQxUX

Copy link
Copy Markdown
Contributor Author

Fixed CodeRabbit's finding: # nosec B108 only silences Bandit, not Ruff's own S108 rule, and CodeRabbit actually ran ruff check --select S108 and confirmed both waived lines still fail it. Pushed ef75ce93 adding # noqa: S108 alongside each existing # nosec B108, and extended the regression test that already pins the Bandit waiver's exact text/count/rationale to also pin the Ruff waiver (so the exception can't silently broaden).

Verified on the exact new head:

  • ruff check --select S108 scripts/ci/sandboxed_web_e2e.py → all checks passed
  • python -m pytest tests/test_sandboxed_web_e2e.py -q → 68 passed
  • coverage run -m pytest tests -q → 2092 passed, 1 skipped, 21 subtests; scripts/ci at 100% statement+branch coverage
  • interrogate → 100% docstring coverage
  • git diff --check → clean

Generated by Claude Code

Copy link
Copy Markdown
Contributor Author

@opencode-agent review

Exact-head request: ef75ce93732e5706aa51f73a5f466d65aef2c1ff. Please issue a formal current-head verdict; status-only, skipped, or predecessor evidence is non-passing.

@seonghobae
seonghobae merged commit 2c69e31 into main Aug 31, 2026
42 of 46 checks passed
@seonghobae
seonghobae deleted the fix/sandboxed-web-e2e-port-zero branch August 31, 2026 02:42
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.

2 participants