Skip to content

#658 [security][tools] --offline の外向きの通信を OS の隔離で遮断し、runtime の依存の導入を隔離の中で動かす(第 3 段) - #671

Merged
Kewton merged 2 commits into
developfrom
feature/issue-658-security-tools-offline-os-runtime-3
Oct 13, 2026
Merged

Kewton merged 2 commits into
developfrom
feature/issue-658-security-tools-offline-os-runtime-3

Conversation

@Kewton

@Kewton Kewton commented Oct 13, 2026 •

Copy link
Copy Markdown
Owner

概要

#502 の第 3 段です(設計は 2026-10-11 に承認)。次の 2 つを入れます。

  • --offline の run では、外向きの通信を OS の隔離で遮断します。
  • runtime の依存の導入(python の venv と pip install -e .、npm install --ignore-scripts)を、第 1・2 段の隔離の中で動かします。

--offline でない run のネットワークは変えません。字句検査も変えず、隔離より先に働きます。

Refs #658

変更(develop 比 9 ファイル、+1250 −98、2 commit)

  • 方針(src/tools/os_sandbox.rs、葉モジュール)
    • SandboxPolicy に offline と dev_server_ports を足しました。
    • install_with(mode, workspace, state_dir, offline, ports) を足しました。既存の install は今と同じ挙動のまま残しています(ネットワークは変えない)。
    • dev_server_ports(profile, goal) が、許可する port を決めます。goal に書かれた port(planner::signals::requested_port_from_text)、なければ profile の既定(planner::profile::resolve_profile_runtime)です。preflight と同じ決め方です。
    • wrap_dependency_setup は、依存の導入を包む継ぎ目です。
  • macOS(seatbelt.rs):--offline のときだけ、profile にネットワークの規則を足します。
    • (deny network*) の後、loopback の TCP(connect・inbound・bind)と、unix socket(connect・bind)だけを許可します。
    • 規則は定数で、path や address の param を持ちません。
  • Linux(landlock.rs):--offline かつ ABI4(kernel 6.7)以上のときだけ、AccessNet::ConnectTcp を handle します。そのうえで、dev server の port を NetPort で許可します。
    • ABI4 未満では、ネットワークには何もしません。bash_sandbox_status の reason に「network is not confined … relies on lexical checks」と書き、書き込み・読み取りの隔離は今どおり適用します。
  • 依存の導入(src/minimal_loop/dependency_setup.rs、配線だけ):npm と python の 2 つの起動点を wrap_dependency_setup で包みます。stdin・stdout・stderr は wrap の後に設定します。
    • required で隔離が使えないときは、既存の観測の Failed と、隔離の拒否の理由で正直に失敗します。
    • auto で使えないときは、今どおり外で動き、「未適用」と記録します。
    • --offline のときは、今どおり導入の手前で止まります。
  • 配線(src/lib.rs、配線だけ +10 −1):install の呼び出しを install_with(…, config.offline, os_sandbox::dev_server_ports(&config.profile, action_goal(&config.action))) にしました。
  • 文書:SECURITY.md と ja・en の cli-reference です。--offline の説明を、「OS が遮断するもの(隔離が使えるとき)」と「字句検査だけのもの」に分けました。
  • 試験:tests/issue502_os_sandbox_network.rs(新規)。外への接続は、TEST-NET-1(192.0.2.1)と、試験の中で立てる localhost だけです。
    • CI があるときは、landlock が未適用か ABI4 未満なら失敗します。kernel と ABI は job summary に 1 行書きます。
  • 変えていないもの
    • 字句検査(bash.rs・bash_write_guard・offline_policy.rs・sensitive_path.rs)
    • 成長上限のファイルと baseline
    • preflight.rs・runs.rs・Cargo.toml・Cargo.lock・deny.toml・.github/workflows/ci.yml
    • event の名前と field(足したのは reason の節だけ)

検証

  • 15 ゲート:HEAD c938a227 で commandmate verify が 15/15 passed でした。最終 commit の後に流し、worktree は clean です。
  • cargo deny check:exit 0。
  • Linux の確認:cargo-zigbuild clippy --all-targets --target x86_64-unknown-linux-gnu は exit 0 です。
  • macOS の適用の試験(入れ子でない環境、--include-ignored)
    • network 9/9(ほかに、stdin の試験で起動し直す子のプロセスで 1/1)
    • read 10/10
    • write 18/18
  • 二点測定:新しい試験のファイルは、develop では compile error になります。
    • L-44 の 2 本の試験は、直したファイルを 1 本ずつ戻すと失敗します。stdin の試験は fifo=True で失敗し、unix socket の試験は bind が EPERM で失敗しました。
  • Linux の実行(Docker、arm64、kernel 7.0.14、landlock ABI 8、既定の seccomp、mount なし、CI=true)
    • 試験:network 7/7、read 12/12、write 17/17。
    • leader の probe(required、隔離の中)
      • --offline:192.0.2.1:443 は EACCES(python・node の fetch とも)。許可した port の localhost は通り、ほかの localhost の port は EACCES でした。
      • 文書に書いた限界のとおり、同じ port の外部ホストへの接続と UDP は拒否されません。
      • --offline でない run:外への接続は timeout で、OS は拒否しません。今と同じです。
      • 依存の導入:python は成功し(setup.py から外への書き込みは PermissionError、外の fixture は不変)、キャッシュ済みの npm も成功しました。
      • cargo build・next build・Playwright(--offline で dev server のページを開く形を含む)は成功し、外への touch は拒否されました。
  • macOS の記録(入れ子でない環境、偽の home、required)
    • --offline:外への接続は EPERM、localhost は通りました。
    • --offline でない run:今と同じでした。
    • 依存の導入:python(setup.py から外への書き込みは拒否)と npm は成功しました。
  • 字句の差:H-25 の probe(2507 行)を base と HEAD で流しました。緩んだ行・厳しくなった行とも 0 件 です(L-42)。
  • cargo mutants(L-42、--jobs 1、--timeout 300)
    • Linux(Docker):caught 18・missed 1・unviable 8。
      • missed は network_limitation_note → None です。ABI 8 では本物も None を返すので、この kernel では区別できません(純粋関数の側は caught)。
    • macOS:missed は、読み取りの試験を含めると caught になりました。残りは Linux 専用のコードで、Docker では caught です。
    • 外へ繋がる・外へ書ける向きの missed は 0 です。

レビュー

  • H-29(merge 前レビュー):merge 可、blocker 0。
    • macOS で、外向きの TCP・UDP(IPv4・IPv6・v4-mapped)が拒否され、loopback と unix socket への connect は通ることを実測しました。
    • Linux では、tcp4・tcp6・v4-mapped と、許可していない loopback の port が EACCES になることを実測しました。
  • merge 前に直した 4 点(2 つ目の commit c938a227)
    1. macOS で依存の導入の stdin が null でなくなる退行を直しました(stdin を wrap の後に設定)。
    2. macOS の --offline で、unix socket の bind を許可しました(外向きの拒否は不変)。
    3. landlock.rs のコメントを、実装どおりに直しました。
    4. SECURITY.md に、port が取れないときは localhost を含めて TCP connect をすべて拒否する(fail-closed)ことを書きました。
  • H-30(修正だけの再確認):merge 可、blocker 0。

後続の論点

CI の runner の kernel と ABI

  • この PR の CI(run 38414178703、c938a227、runner は ubuntu-24.04)では、job のログで次を確かめました。
    • issue502_os_sandbox_network:7 passed(linux_offline_refuses_outbound_and_allows_only_the_dev_server_port と dependency_setup_runs_inside_the_sandbox_and_cannot_write_outside を含む)。
    • issue502_os_sandbox_read:10 passed・2 ignored(ignored は heavy の 2 本)。
    • issue502_os_sandbox_write:16 passed・1 ignored(heavy の 1 本)。
  • GitHub Actions は CI=true を置きます。そのため、landlock が未適用か ABI4 未満なら、network の試験は skip せずに失敗します。通ったので、runner で landlock の ABI4 以上が使え、ネットワークの隔離が適用されていました。
  • kernel と ABI の行(issue502/658 landlock network: kernel=… abi=…)は job summary にだけ書かれます。job summary は gh と API では取れず、ログにも出ないため(cargo test が通った試験の出力を捕まえるため)、ここには記録できていません。参考として、#656 [security][tools] Bash と検証コマンドの書き込みを Linux の landlock で閉じ込める(1b) #666 の同じ種類の runner の記録は kernel=6.17.0-1022-azure abi=7 です。
  • MPTCP の probe は、この CI では流していません(後続の論点)。

🤖 Generated with Claude Code

Kewton and others added 2 commits October 13, 2026 21:35
…pendency setup

Issue #658 (stage 3 of #502) adds the network layer and dependency-setup
confinement. New behavior lives in the os_sandbox leaf module; dependency_setup
and lib.rs carry only wiring.

- SandboxPolicy gains `offline` and `dev_server_ports`. The run installs it
  through os_sandbox::install_with, so config.offline reaches the model Bash,
  verifier, and dependency-setup launches; lib.rs passes config.offline and the
  leaf-derived dev-server ports. os_sandbox::install keeps the non-offline
  default for other callers.
- os_sandbox::dev_server_ports derives the allow list (the goal's requested port
  from planner::signals, else the profile default from planner::profile).
- macOS Seatbelt appends an --offline network profile (deny network*, re-allow
  loopback TCP in/out/bind and unix-socket connects); verified against
  sandbox-exec (external connect gives EPERM, loopback and unix sockets work).
- Linux Landlock handles AccessNet::ConnectTcp from ABI4 and re-allows the run's
  dev-server port with a NetPort rule; below ABI4 it records that the network is
  lexical-only in bash_sandbox_status while the write and read layers still
  apply. The network-rights/ABI handling is documented in the module and
  create_ruleset.
- The Python sh -c and npm install launches call os_sandbox::wrap_dependency_setup
  (the leaf seam); required-unavailable fails the setup with the sandbox reason,
  auto runs it outside and the run status records it as not applied.
- tests/issue502_os_sandbox_network.rs drives the network layer and the
  dependency-setup launch, gates ABI4 under CI (fail, not skip), and records the
  kernel/ABI to GITHUB_STEP_SUMMARY like the read test.
- SECURITY.md and both cli-reference pages split the --offline explanation into
  "OS-blocked when the sandbox is applied" and "lexical-only".

判断: the Linux dev-server allow list is derived at run start in the leaf module from the goal's requested port (else the profile default), matching preflight, so a sandboxed command can reach the run's dev server while every other connect is denied.
読み替え: the English --offline/--sandbox table rows stay pinned to the CLI help text by tests/doc_drift.rs, whose source is out of scope, so the rewrite lives in the section prose.

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
…nd offline

Four review fixes for #658:

- Set the dependency-setup child's stdin after the sandbox wrap, so on macOS
  (where seatbelt::wrap rebuilds the command) it stays null instead of inheriting
  the parent's stdin. A macOS applied test runs the check in a child process with
  a piped stdin, so an inherited stdin is visible.
- Allow `network-bind (local unix-socket)` in the macOS --offline profile, so a
  unix socket can be created inside the isolation (the design allows unix
  sockets); outbound TCP/UDP stay denied. A macOS applied test binds and listens
  on a socket inside the tempdir and still checks the 192.0.2.1 connect is
  refused.
- Correct the landlock module comment: the ABI4 port-scope and UDP limits are
  documented, not recorded in bash_sandbox_status; only a kernel below ABI4 is
  recorded there.
- SECURITY.md: note the Linux fail-closed case -- when no dev-server port can be
  derived, every TCP connect is denied, localhost included.

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
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