fix(dev): 強化 SDKMAN JDK 切換安全性#1
Merged
Merged
Conversation
SamWang32191
commented
Jul 20, 2026
SamWang32191
left a comment
Owner
Author
There was a problem hiding this comment.
REQUEST-CHANGES (advisory) — 2 個已確認的 must-fix:永久 default 無法精確 rollback,且新 shell 安全行為缺少回歸測試
Must-fix (2)
- Required · Spec conformance —
plugins/dev/skills/sdkman-switch-jdk/SKILL.md:158- 當既有
java/current是絕對或非標準相對 symlink target 時,把保存值當成<previous-identifier>傳回sdk default java只會建立標準 candidate link,無法逐字還原原始 target,違反本 PR 承諾的精確 rollback。 - Fix: 在變更永久 default 前保存 raw
readlinktarget,並提供有狀態防護的同-filesystem 暫存 symlink 加原子替換回復流程;完成後驗證readlink與保存值逐字相同,不要把 raw target 傳給sdk default java。
- 當既有
- Required · Test coverage
- 兩支合計 389 行、會安裝 JDK 並原子改寫或移除 default symlink 的新 shell 程式沒有任何已提交回歸測試;default 漂移、BSD/GNU 平台分支或 exit-status 回歸不會被目前只跑版本管理與 lockstep 的 CI 攔截。
- Fix: 提交可在暫存 SDKMAN 目錄執行的 shell contract tests,以 fake sdk/init 與 candidate fixtures 覆蓋安裝成功/失敗、absolute/relative/absent default、還原失敗、原始 status 傳遞、PATH shadow、損壞 candidate、保留名稱,以及 payload argv/exit status,並納入 CI。
請先處理項目 1 與 2。
Non-blocking (2)
- Nit · Correctness —
plugins/dev/skills/sdkman-switch-jdk/scripts/install-java.sh:19— 在預設大小寫不敏感的 macOS filesystem 上,CURRENT、Current等輸入可繞過大小寫敏感的保留名稱檢查並解析到java/current;run-java.sh:21有相同 guard。 - Optional · Architecture —
plugins/dev/skills/sdkman-switch-jdk/scripts/install-java.sh:48— default state、原子 symlink 置換與還原邏輯在兩支 script 幾乎逐行重複,後續 portability 修補容易只更新其中一條路徑。
Coverage & checks
| Axis | Status |
|---|---|
| Correctness | 1 finding — 檢查參數、SDKMAN 初始化、candidate 完整性、default 狀態、symlink 還原、失敗傳遞、PATH shadow 與 argv 邊界 |
| Spec | 1 finding — 將 intent 的暫時/default/project/full-environment、安裝提示與 rollback 要求逐項對照 6 個變更檔 |
| Scope | clean — 6 個檔案都可直接追溯到 stated intent,無 linked issue 或明顯無關變更 |
| Convention | clean — 依 base-ref AGENTS.md 核對 plugin、version、文件語言與必要驗證規則 |
| Security | clean — identifier allowlist、quoted expansion、SDKMAN init、exec argv、symlink 還原均無 injection、secret 或 authz 缺口 |
| Readability | clean — 文件與兩支 script 的分段、命名、錯誤訊息及控制流可追蹤,無 dead branch 或 eval |
| Architecture | 1 finding — 安裝與 runner 職責分離合理,但核心 default 保護 primitive 重複 |
| Performance | clean — 僅常數次 filesystem/default 檢查及單層 candidate 列舉,無 N+1、遞迴或無界重試 |
| Test coverage | 1 finding — 6 個 changed files 無 test/fixture,新的安全與平台分支未被可重跑測試 exercise |
- Verified — GitHub CI 全部成功:Verify on Node.js 22、Verify on Node.js 24、GitGuardian Security Checks;僅讀取
gh pr checks,未在本機執行 PR 程式。 - Out-of-band — 以 base SHA 讀取 AGENTS.md,並以 head SHA 唯讀核對兩支 script 全文及 repository tree。
🤖 Reviewed by gpt-5.6-sol
(self-review — 同一帳號;GitHub 會以 event=COMMENT 記錄且不顯示 badge,因此上方 verdict 僅供 advisory。)
SamWang32191
commented
Jul 20, 2026
SamWang32191
left a comment
Owner
Author
There was a problem hiding this comment.
REQUEST-CHANGES (advisory) — 3 個已確認的 must-fix:default 競爭保護、full-environment default 保證與對應回歸測試仍不完整
Must-fix (3)
- Required · Correctness —
plugins/dev/skills/sdkman-switch-jdk/scripts/install-java.sh:192- 安裝期間若另一個程序合法變更 Java default,目前程式會把任何 snapshot 前後差異都視為本次安裝造成,並在未確認目前狀態是否仍屬於本次操作產物時覆寫回舊值;
run-java.sh也有相同競爭路徑。 - Fix: 讓 default snapshot、SDKMAN 操作與還原使用一致的跨程序協調機制;在 rename/unlink 前執行 compare-and-swap,只能還原可確認由本次呼叫建立的狀態,否則停止且不得覆寫。
- 安裝期間若另一個程序合法變更 Java default,目前程式會把任何 snapshot 前後差異都視為本次安裝造成,並在未確認目前狀態是否仍屬於本次操作產物時覆寫回舊值;
- Required · Spec conformance —
plugins/dev/skills/sdkman-switch-jdk/references/project-scope.md:51- 完整 SDKMAN environment 流程只在執行前記錄 candidate default,之後直接執行
sdk env install並execpayload,沒有重查、比較或還原,因此無法保證未獲授權的 candidate default 維持原狀。 - Fix: 提供專用 runner,逐 candidate 保存 raw symlink state;
sdk env install後、payload 前逐一比較,只保留明確獲准的變更,其他狀態以具漂移防護的原子流程還原;比較或還原失敗時不得執行 payload。
- 完整 SDKMAN environment 流程只在執行前記錄 candidate default,之後直接執行
- Required · Test coverage —
test/sdkman-switch-jdk-contract.sh:779- 現有 20 個 scenario 沒有任何案例在 snapshot 與還原寫入之間由另一程序更新
java/current,因此「不得覆寫後續 default 變更」的核心並行安全契約沒有回歸保護。 - Fix: 新增可確定重現的 contract scenarios,讓 fake SDK 在 snapshot 後、restore 前協調另一 writer 將
current更新為第三個 default,並斷言 install 與 run 兩條自動還原路徑都不得覆寫該狀態。
- 現有 20 個 scenario 沒有任何案例在 snapshot 與還原寫入之間由另一程序更新
請先處理項目 1 與 2。
Coverage & checks
| Axis | Status |
|---|---|
| Correctness | 1 finding — 逐行追蹤 default snapshot、SDKMAN 呼叫、狀態比較、symlink 還原與失敗狀態傳遞 |
| Spec | 1 finding — 將 intent 的版本解析、default 保持、argv、PATH shadow 與 full-environment 分支逐項對照文件及 scripts |
| Scope | clean — 8 個 changed files 均可直接追溯到 stated intent,無 linked issue 或明顯無關變更 |
| Convention | clean — 依 base-ref AGENTS.md 核對文件語言、plugin/version 邊界及必要驗證規則 |
| Security | clean — identifier、quoted path、raw symlink target、exec "$@" 與原子 rename 無 injection、secret 或 authz 缺口 |
| Readability | clean — skill、references 與 scripts 的分段、命名、錯誤訊息及狀態控制流可追蹤 |
| Architecture | clean — plugin、scripts、contract test 與 workflow 邊界內聚,CI 使用公開測試入口 |
| Performance | clean — 路徑編解碼、單層 candidate 掃描與固定 20 個 fixture 無 N+1、無界迴圈或顯著配置問題 |
| Test coverage | 1 finding — contract suite 涵蓋 default 型態、失敗狀態、GNU/BSD、PATH、argv 等,但未覆蓋並行 writer |
- Verified — GitHub CI 全部成功:SDKMAN JDK script contracts、Verify on Node.js 22、Verify on Node.js 24、GitGuardian Security Checks;僅讀取
gh pr checks,未在本機執行 PR 程式。 - Out-of-band — 以 base SHA 讀取 AGENTS.md,並以 head SHA 唯讀核對兩支 scripts 全文與 repository tree。
🤖 Reviewed by gpt-5.6-sol
(self-review — 同一帳號;GitHub 會以 event=COMMENT 記錄且不顯示 badge,因此上方 verdict 僅供 advisory。)
SamWang32191
commented
Jul 21, 2026
SamWang32191
left a comment
Owner
Author
There was a problem hiding this comment.
APPROVE (advisory) — 9 軸已完整審查,沒有 confirmed must-fix
Verification: killed / demoted / unverifiable (3)
- Required → refuted · Correctness —
plugins/dev/skills/sdkman-switch-jdk/scripts/install-java.sh:79— 原 claim 認為 SDKMAN 可能將current寫成 candidate 的絕對路徑,導致 runner 誤判 drift 並拒絕還原。 Counter-evidence: SDKMAN 5.20.0 的sdkman-install.sh:47使用共用 link helper,而sdkman-path-helpers.sh:88明確以 version 字串建立currentsymlink;實際 operation-owned target 與此處記錄的 identifier 相同。 - Required → refuted · Spec conformance —
plugins/dev/skills/sdkman-switch-jdk/scripts/run-sdkman-env.sh:209— 原 claim 認為 full-environment 流程可能遇到sdk use建立的 absolutecurrenttarget,因而無法逐 byte 還原。 Counter-evidence: SDKMAN 5.20.0 的sdkman-use.sh:52-54呼叫相同 link helper,sdkman-path-helpers.sh:88寫入的是 version 字串;此處 operation-owned state 與實際 SDKMAN target 一致。 - Required → Optional · Test coverage —
test/sdkman-switch-jdk-contract.sh:417— 測試替身具備target_abs行為但目前沒有 scenario 使用。 Reason: SDKMAN 5.20.0 的 link helper 不會產生 absolute target,因此這是可選的防禦性測試擴充,不是合併前必要條件。
Coverage & checks
| Axis | Status |
|---|---|
| Correctness | 1 finding — 追蹤三個 runner 與共用 helper 的 default state、operation-owned 比對、還原及 signal cleanup;finding 經驗證 refuted |
| Spec | 1 finding — 將 absolute/relative/absent default 與成功/失敗不變條件逐項對照三個 runner;finding 經驗證 refuted |
| Scope | clean — workflow、skill 文件、references、runners、共用 helper 與 tests 均直接支援 PR stated intent |
| Convention | clean — 依 base-ref AGENTS.md 核對全部 10 個 changed files,未見 version、marketplace 或文件語言違規 |
| Security | clean — 檢查 identifier、.sdkmanrc ownership、argv、PATH、lock metadata 與 symlink drift guards,未見可確認的安全缺口 |
| Readability | clean — 三個入口、共用 helper 與文件的命名及控制流可追蹤,複雜度對應既有安全契約 |
| Architecture | clean — SDKMAN 初始化留在入口,default-state、lock、restore 與 signal cleanup 集中在共用 helper |
| Performance | clean — candidate 搜尋、reconciliation 與 lock retry 均有界,沒有 hot-path、N+1 或無界重試問題 |
| Test coverage | 1 finding — contract suite 涵蓋 relative、既有 absolute、absent、signal、lock、TOCTOU、GNU/BSD 與 full-env;target_abs 擴充經驗證降為 Optional |
- Verified — GitHub CI 全部成功:SDKMAN JDK script contracts、Verify on Node.js 22、Verify on Node.js 24、GitGuardian Security Checks;僅讀取
gh pr checks,未在本機執行 PR 程式。 - Delivery — rung 1 staged-prompt file reference;初始 dispatch 卡在
pending_init,依使用者指示重派後完成,method 與所有 payload receipts 匹配。
🤖 Reviewed by gpt-5.6-sol
(self-review — 同一帳號;GitHub 會以 event=COMMENT 記錄且不顯示 badge,因此上方 verdict 僅供 advisory。)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
變更摘要
sdkman-switch-jdk的版本解析與作用範圍決策,明確區分暫時執行、SDKMAN default、專案.sdkmanrc與完整 SDKMAN environment。java=、候選版本歧義、損壞 candidate 與精確 rollback。main的 plugin 化結構,將完整 skill 放置於plugins/dev/skills/sdkman-switch-jdk/,不保留舊skills/路徑。修改原因
原本流程在非互動 shell、沒有既有 default、SDKMAN 安裝失敗或存在多個相符 JDK 時,可能卡住、任選版本、失去 session 切換效果,或意外建立/改寫
java/current。本次將「default 必須維持原狀」設為可驗證的不變條件,並以同一個 runner 保持初始化、切換、驗證與實際命令的 argv 邊界。安全性與回復
java/current。驗證
node --check scripts/bump-plugin-versions.mjsnode --test test/bump-plugin-versions.test.mjs:13/13 通過node scripts/bump-plugin-versions.mjs --check:兩個 plugin manifests 與VERSION 0.1.2一致quick_validate.py plugins/dev/skills/sdkman-switch-jdk:通過bash -n:兩支新增 script 均通過17.0.18-tem執行 payload 成功,執行前後 default 均維持21.0.9-tem