Skip to content

#657 [security][tools] Bash と検証コマンドの、home の秘密の場所の読み取りを OS の隔離で拒否する(第 2 段) - #670

Merged
Kewton merged 2 commits into
developfrom
feature/issue-657-security-tools-bash-home-os-2
Oct 13, 2026
Merged

Kewton merged 2 commits into
developfrom
feature/issue-657-security-tools-bash-home-os-2

Conversation

@Kewton

@Kewton Kewton commented Oct 13, 2026 •

Copy link
Copy Markdown
Owner

概要

#502 の第 2 段(読み取り隔離)です。Bash と検証コマンドから、home の秘密の場所の読み取りを OS の隔離で拒否します。拒否リスト型で、macOS は Seatbelt の deny file-read*、Linux は拒否リストを landlock の許可リストに展開します。home の npm の設定も拒否します(設計は 2026-10-11 に承認)。

Refs #657

変更(develop 比 11 ファイル、+2001 −114、2 commit)

  • 秘密の場所の一覧(src/tools/os_sandbox/secret_locations.rs、新規の葉モジュール)
    • 一覧は 1 か所に置き、macOS と Linux の backend が同じものを使います。
    • home の場所:~/.ssh・~/.aws・~/.gnupg・~/.netrc・~/.git-credentials・~/.npmrc・~/.pypirc・~/.docker/config.json・~/.kube・~/.config/gh・~/.config/gcloud。
    • 各場所は canonicalize します。symlink の秘密は、指す先を拒否します。
    • macOS のワークスペースの規則は、[security][tools] 通常の Read で、モデルがワークスペースの .env(API キー)を承認なしに読める #501 の sensitive_name_rule と path_rule に揃えています。
      • 大文字小文字によらず当たります。
      • 秘密の名前の dir の配下も拒否します。
      • テンプレート 3 種は読めます。ただし秘密の dir の配下にあるものは拒否します。
  • macOS(seatbelt.rs):home の場所は (deny file-read* (subpath …)) で、ワークスペースの規則は正規表現で拒否します。場所はすべて -D の param で渡し、profile の文字列には入れません。
  • Linux(landlock.rs)
    • handled access を from_all にしました。拒否リストは、各コマンドの起動時に許可リストへ展開します。
      • 祖先の各階層で、兄弟を許可します。
      • symlink の entry は許可しません(crate が O_PATH で辿り、規則が指す先の inode に付くため)。
    • 利用可否の probe は、書き込みだけを handle する ruleset で行います(1b と同じ意味)。
  • 記録(os_sandbox.rs):読み取りの隔離の状態は、bash_sandbox_status の既存の reason で出します。event の名前と field は変えていません(src/lib.rs の diff は空です)。
    • macOS で効いているとき:read isolation applied
    • 効かないとき:read isolation is not effective …。書き込みの場所が秘密と重なるとき、HOME が空のとき、policy が無いときです。
    • 試験では SandboxPolicy::with_home で、HOME を tempdir に向けます。
  • 文書:SECURITY.md と ja・en の cli-reference。
  • 試験:tests/issue502_os_sandbox_read.rs(新規)と corpus tests/corpus/apps/issue502-indirect-read/。偽の鍵だけを使い、HOME は tempdir です。
  • 変えていないもの
    • 字句検査(bash.rs・bash_write_guard.rs・sensitive_path.rs)
    • src/lib.rs、成長上限のファイルと baseline
    • Cargo.toml・Cargo.lock・deny.toml・.github/workflows/ci.yml
    • tests/issue502_os_sandbox_write.rs

検証

  • 15 ゲート:HEAD dac61997 で commandmate verify が 15/15 passed でした。最終 commit の後に流し、worktree は clean です。
  • cargo deny check:exit 0(advisories・bans・licenses・sources とも ok)。
  • Linux の確認:cargo-zigbuild clippy --all-targets --target x86_64-unknown-linux-gnu は exit 0 です。
  • macOS の適用の試験(入れ子でない環境、--include-ignored):読み取り 10/10、書き込み 18/18、SKIP 0。
  • macOS での記録:隔離の中(required、偽の home)で次を確かめました。
    • 成功:--version(cargo・rustc・node・npm・python3・git)、依存のない crate の cargo build、キャッシュ済みの fixture の npm ci
    • 拒否:偽の鍵、偽の ~/.npmrc、外への touch
  • Linux の実行(Docker、arm64、kernel 7.0.14、landlock ABI 8、既定の seccomp、mount なし)
    • CI=true で、読み取り 12/12、書き込み 17/17 です。
    • 拒否された形:直接、秘密でない名前の symlink、BASH_ENV、python が組み立てた名前、.npmrc
    • 成功した形:toolchain と git の --version、cargo build、npm ci、next build
    • CI があるときは、landlock が未適用か ABI が足りなければ試験が失敗します。kernel と ABI は job summary に 1 行書きます。
  • 字句の差:H-25 の probe(2507 行)を base と HEAD で流しました。R・A・W・P・S が 1→0、V が 0→1 になった行は 0 件、厳しくなった行も 0 件です。
  • 二点測定:新しい試験のファイルは develop では compile error になります。そのため、検出力は mutants で示しました。
  • cargo mutants(--jobs 1、--timeout 300)
    • macOS:caught 22・missed 7・unviable 4
      • 秘密が読める向きの missed は 1 件でした(home_dir を空にする変異)。試験を足し、取り直して caught です。
      • 残りは、macOS では compile されない cfg の中のコードと、許可が減る向きのものです。
    • Linux(Docker):caught 25・missed 1・unviable 3。missed は、Linux では compile されない他の platform 用の関数です。
  • 途中で見つけて直したもの(Linux の実行で検出)
    • landlock の probe が子の exec を止め、landlock が「使えない」と判定されていました(書き込みの隔離の後退)。
    • 展開が symlink の entry を許可し、秘密を指す symlink を通して鍵が読めていました。
    • どちらも単体試験を足して直しました。

レビュー

  • H-28(merge 前レビュー):merge 可、blocker 0。
    • 13 の迂回の形を足して試しましたが、home の秘密は漏れませんでした。形は、大文字小文字・firmlink・..・/proc/self/root・hardlink・symlink の dir・cp・O_PATH の開き直しです。
    • macOS は実機、Linux は Docker で確かめました。
  • 2 つ目の commit(dac61997)は文書だけです。H-28 の推奨に沿って、限界を実際どおりに書きました。

文書に明記した限界

  • 拒否は名前(path)で判定します。macOS では、書き込めるワークスペースの中で資格情報を rename・copy して別名にすると、その名前では読めます。実行時に組み立てた名前を閉じるのは、組み立てた名前が一覧の名前そのものの場合に限ります。
  • 隔離の外で作られた bind mount や hardlink を通り、一覧の外の path から届く秘密は、どちらの OS でも拒否しません。隔離の中で hardlink を作る形は拒否します。
  • Linux では、ワークスペースの資格情報を秘密でない名前の symlink で読む形は閉じません(字句検査が担当します)。
  • Linux の展開は各コマンドの起動時に行います。そのため、許可していない場所の一覧(ls /・ls $HOME・echo $HOME/*)はできません。
  • private registry の ~/.npmrc を使う npm は、Bash の中で失敗します。

後続の論点

  • Linux で効いているとき reason が None:macOS は read isolation applied、未適用・効かないときは両 OS とも理由の文です。明示の文に揃えるには tests/issue502_os_sandbox_write.rs の期待を変えるので、後続の小さな Issue を推奨します。
  • 展開の規則の数:規則の数は home の兄弟の数に比例します。上限を超えたら「not effective」と記録する防御を、後続で検討します。
  • private registry の npm:required の run での実績を見て、トークンを Bash の外から渡す方法を検討します。
  • macOS の rename:ワークスペースの資格情報に書き込みの拒否も足して rename・unlink を止めるかは、別の Issue で扱います。

CI の runner の kernel と ABI

  • この PR の CI(run 38384229322、dac61997)では、issue502_os_sandbox_read は 10 passed・2 ignored でした(ignored は heavy の 2 本で、CI では明示しないと走りません)。
    • CI があるので、landlock が未適用か ABI が足りなければ、この試験は失敗します。通ったので、runner で読み取りの隔離は適用されていました。
    • 書き込みの試験も pass です。
  • kernel と ABI の行(issue502/657 landlock read: kernel=… abi=…)は job summary にだけ書かれます。job summary は gh と API では取れないため、ここには記録できていません(ログには出ません。cargo test が通った試験の出力を捕まえるため)。
  • 参考:#656 [security][tools] Bash と検証コマンドの書き込みを Linux の landlock で閉じ込める(1b) #666 の同じ runner の種類(ubuntu-24.04)の記録は kernel=6.17.0-1022-azure abi=7 です。

🤖 Generated with Claude Code

Kewton and others added 2 commits October 13, 2026 17:31
Issue #657 (stage 2 of #502) adds an OS read-confinement layer to the
#655/#656 write backend:

- A shared leaf (src/tools/os_sandbox/secret_locations.rs) holds the home
  secret list, the #501 workspace credential basenames, and the three
  #501 path sequences; both backends import it.
- macOS Seatbelt denies the home secret locations (subpath params) and
  the workspace credentials (case-folded regex with a `(/.*)?$` tail so a
  credential-named directory's contents are denied); a later rule
  re-allows the strict dotenv templates and a final rule re-denies a
  template below a credential directory. The regex root is canonicalized.
- Linux Landlock handles the read rights (`Execute | ReadFile | ReadDir`,
  from ABI1) and rewrites the deny list into a read-allow list built once
  at run start. It canonicalizes each secret location (so a symlinked
  `~/.ssh` denies its target) and the writable locations (so a symlinked
  workspace is compared by its real path), and reports in
  `bash_sandbox_status` when a writable location is the home, an ancestor
  of a secret, or inside a secret (or when HOME is empty), instead of
  claiming the read isolation applied.
- The read-allow expansion skips symlink entries: `path_beneath_rules`
  opens a path with `O_PATH`, following the link, so a rule for a symlink
  entry would attach to its target. A sibling symlink to a secret file,
  or a secret location that is itself a link, would otherwise open the
  secret; the real path decides those instead.
- The availability probe builds its own write-only ruleset (no allow
  rules), so its child can still `exec` even though the production
  ruleset now handles the read set.
- The read isolation is reported through the existing
  `bash_sandbox_status` reason (no new event field, no name/field change).
- Docs: SECURITY.md and both cli-reference files describe the read layer,
  the Linux ReadDir consequence (`ls /`, `ls $HOME`, `echo $HOME/*`), and
  the home-npm-config consequence.
- Tests (added for cargo-mutants gaps): a unit test pins
  `SandboxPolicy::new(..).home` to the process `HOME` (so emptying
  `home_dir` is caught), and the workspace-regex test now checks the rule
  is anchored at the canonical workspace root via the `regex` crate (so
  emptying `escape_regex`, which would deny the name everywhere, is
  caught).

読み替え: 本文の受入条件は bash_sandbox_status へ field を足す形を示唆しますが、
tests/issue502_os_sandbox_write.rs が applied + ABI3+ で reason==None、unavailable で
reason==プローブ理由、を厳密に固定しており(そのまま通すことが制約)、新しい field を
足すと書き込み隔離の証跡を書き換えることになります。指示に従い、field ではなく既存の
reason で読み取りの隔離の適用・未適用を報告しています。
判断: 読み取りは書き込みと同じ backend 可用性で適用されるため、Linux の ABI3+ かつ
完全に閉じている場合は reason を None(=完全に隔離)に保ち、未適用(writable が secret を
覆う場合・secret の中の場合・HOME が空の場合)と ABI<3 の場合に reason を出します。
macOS は write limitation が無いため reason に "read isolation applied" を出します。

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
Match the Read confinement item to the actual behavior: denial is
decided by name and path, so on macOS a credential renamed or copied to
another name inside the writable workspace is not denied, and a name an
interpreter assembles at run time is closed only when the assembled name
is itself a denied name. Note that a secret reached from an off-list path
through a bind mount, or through a hardlink created outside the
isolation, is not denied on either OS, while creating such a hardlink
inside the isolation is. Also state that the Linux read-allow list is
rebuilt at each command's start (not once at run start), so a location
created after a command starts is not readable inside that command. Fix
the same sentences in the en and ja cli-reference files.

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
@Kewton
Kewton merged commit e79191f into develop Oct 13, 2026
5 checks passed
@Kewton
Kewton deleted the feature/issue-657-security-tools-bash-home-os-2 branch October 13, 2026 10:51
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