Skip to content

fix(dev): 強化 SDKMAN JDK 切換安全性#1

Merged
SamWang32191 merged 6 commits into
mainfrom
fix/sdkman-switch-jdk-safety
Jul 21, 2026
Merged

fix(dev): 強化 SDKMAN JDK 切換安全性#1
SamWang32191 merged 6 commits into
mainfrom
fix/sdkman-switch-jdk-safety

Conversation

@SamWang32191

Copy link
Copy Markdown
Owner

變更摘要

  • 重整 sdkman-switch-jdk 的版本解析與作用範圍決策,明確區分暫時執行、SDKMAN default、專案 .sdkmanrc 與完整 SDKMAN environment。
  • 新增安全安裝與執行 scripts,確保 auto-env、安裝提示、PATH shadow 或失敗回傳都不會意外改變既有 Java default。
  • 補充專案範圍與疑難排解文件,處理重複 java=、候選版本歧義、損壞 candidate 與精確 rollback。
  • 整合最新 main 的 plugin 化結構,將完整 skill 放置於 plugins/dev/skills/sdkman-switch-jdk/,不保留舊 skills/ 路徑。

修改原因

原本流程在非互動 shell、沒有既有 default、SDKMAN 安裝失敗或存在多個相符 JDK 時,可能卡住、任選版本、失去 session 切換效果,或意外建立/改寫 java/current。本次將「default 必須維持原狀」設為可驗證的不變條件,並以同一個 runner 保持初始化、切換、驗證與實際命令的 argv 邊界。

安全性與回復

  • 安裝時明確回答不設為 default,並在成功與失敗路徑比較 java/current
  • 發現 default 漂移時,以同 filesystem 的暫存 symlink 原子還原完整 raw target,保留原始 exit status。
  • candidate 不完整、identifier 為保留名稱或 active Java 路徑不符時 fail closed。
  • 暫時 runner 不修改父 shell、專案檔案或原有 SDKMAN default。

驗證

  • node --check scripts/bump-plugin-versions.mjs
  • node --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 均通過
  • failure-path fixtures:absolute/relative/absent default、status 42、PATH shadow、損壞 candidate 與保留名稱案例均通過
  • SDKMAN 5.20.0 實機 smoke test:使用 17.0.18-tem 執行 payload 成功,執行前後 default 均維持 21.0.9-tem

@SamWang32191 SamWang32191 left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

REQUEST-CHANGES (advisory) — 2 個已確認的 must-fix:永久 default 無法精確 rollback,且新 shell 安全行為缺少回歸測試

Must-fix (2)

  1. 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 readlink target,並提供有狀態防護的同-filesystem 暫存 symlink 加原子替換回復流程;完成後驗證 readlink 與保存值逐字相同,不要把 raw target 傳給 sdk default java
  2. 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 上,CURRENTCurrent 等輸入可繞過大小寫敏感的保留名稱檢查並解析到 java/currentrun-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。)

Comment thread plugins/dev/skills/sdkman-switch-jdk/SKILL.md Outdated

@SamWang32191 SamWang32191 left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

REQUEST-CHANGES (advisory) — 3 個已確認的 must-fix:default 競爭保護、full-environment default 保證與對應回歸測試仍不完整

Must-fix (3)

  1. 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,只能還原可確認由本次呼叫建立的狀態,否則停止且不得覆寫。
  2. Required · Spec conformance — plugins/dev/skills/sdkman-switch-jdk/references/project-scope.md:51
    • 完整 SDKMAN environment 流程只在執行前記錄 candidate default,之後直接執行 sdk env installexec payload,沒有重查、比較或還原,因此無法保證未獲授權的 candidate default 維持原狀。
    • Fix: 提供專用 runner,逐 candidate 保存 raw symlink state;sdk env install 後、payload 前逐一比較,只保留明確獲准的變更,其他狀態以具漂移防護的原子流程還原;比較或還原失敗時不得執行 payload。
  3. 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 兩條自動還原路徑都不得覆寫該狀態。

請先處理項目 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。)

Comment thread plugins/dev/skills/sdkman-switch-jdk/scripts/install-java.sh Outdated
Comment thread plugins/dev/skills/sdkman-switch-jdk/references/project-scope.md Outdated
Comment thread test/sdkman-switch-jdk-contract.sh

@SamWang32191 SamWang32191 left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

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 字串建立 current symlink;實際 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 建立的 absolute current target,因而無法逐 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。)

@SamWang32191
SamWang32191 merged commit 17ecbc9 into main Jul 21, 2026
4 checks passed
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