From 4886dff45f0634072b713fc6deda87f4494406ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BC=98=E9=9B=85=E3=81=AE=E5=92=B8=E9=B1=BC?= <3129538298@qq.com> Date: Wed, 26 Aug 2026 16:30:17 +0800 Subject: [PATCH 1/7] chore: bump wholocks to 0.3.1 --- src/wholocks/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wholocks/__init__.py b/src/wholocks/__init__.py index a6b8cb7..f3ca65d 100644 --- a/src/wholocks/__init__.py +++ b/src/wholocks/__init__.py @@ -17,7 +17,7 @@ find_holders, ) -__version__ = "0.3.0" +__version__ = "0.3.1" __all__ = [ "find_holders", From 56e0c2bd377e4124886a04d60ef5bc838cc3cf6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BC=98=E9=9B=85=E3=81=AE=E5=92=B8=E9=B1=BC?= <3129538298@qq.com> Date: Wed, 26 Aug 2026 16:35:50 +0800 Subject: [PATCH 2/7] chore: ignore .ruff_cache --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 386c8ce..8b8a8ca 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ __pycache__/ build/ dist/ .pytest_cache/ +.ruff_cache/ .coverage htmlcov/ .venv/ From 367eb539f678cba56c02cb0c6baa569d51ffa563 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BC=98=E9=9B=85=E3=81=AE=E5=92=B8=E9=B1=BC?= <3129538298@qq.com> Date: Wed, 26 Aug 2026 16:35:51 +0800 Subject: [PATCH 3/7] build: add ruff config for 0.3.1 --- pyproject.toml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index e244ddd..f6fb209 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,7 +52,15 @@ Changelog = "https://github.com/hc-ui/wholocks/blob/main/CHANGELOG.md" wholocks = "wholocks.cli:main" [project.optional-dependencies] -dev = ["pytest>=7"] +dev = ["pytest>=7", "ruff>=0.4"] + +[tool.ruff] +line-length = 100 +target-version = "py39" + +[tool.ruff.lint] +select = ["E", "F", "W"] +ignore = ["E501"] [tool.setuptools.dynamic] version = { attr = "wholocks.__version__" } From 109184c62ea53ef3fd4e8d6a30761a640378d567 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BC=98=E9=9B=85=E3=81=AE=E5=92=B8=E9=B1=BC?= <3129538298@qq.com> Date: Wed, 26 Aug 2026 16:38:13 +0800 Subject: [PATCH 4/7] docs: changelog for 0.3.1 parent-shell guard --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 592bd34..2de9b85 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 0.3.1 - 2026-08-26 + +- Refuse to kill the parent shell (the usual holder of `.` via cwd), matching + the existing "don't kill yourself" guard. +- CI now runs ruff. + ## 0.3.0 - 2026-08-13 - **Linux: hardlink detection.** When a process holds the same file under a From 9e908a047856f47ba3a150d674385fdcdfb94116 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BC=98=E9=9B=85=E3=81=AE=E5=92=B8=E9=B1=BC?= <3129538298@qq.com> Date: Wed, 26 Aug 2026 16:40:34 +0800 Subject: [PATCH 5/7] test: refuse killing the parent shell unless force --- tests/test_core.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/test_core.py b/tests/test_core.py index f6e064d..0aa042c 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -116,6 +116,11 @@ def test_refuses_self(self): h = Holder(pid=os.getpid(), name="python") assert kill_block_reason(h) is not None + def test_refuses_parent_shell(self): + h = Holder(pid=os.getppid(), name="bash") + assert kill_block_reason(h) is not None + assert kill_block_reason(h, force=True) is None + def test_service_requires_force(self): h = Holder(pid=5555, name="svc.exe", app_type="service") assert kill_block_reason(h, force=False) is not None From 840023841cf90e3b18a112202fdc9374a6796a21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BC=98=E9=9B=85=E3=81=AE=E5=92=B8=E9=B1=BC?= <3129538298@qq.com> Date: Wed, 26 Aug 2026 16:49:33 +0800 Subject: [PATCH 6/7] fix: refuse killing the parent shell unless --force --- src/wholocks/core.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/wholocks/core.py b/src/wholocks/core.py index c590219..8864d8b 100644 --- a/src/wholocks/core.py +++ b/src/wholocks/core.py @@ -270,6 +270,8 @@ def kill_block_reason(holder: Holder, force: bool = False) -> Optional[str]: return "PID 1 (init/systemd)" if holder.pid == os.getpid(): return "this is wholocks itself" + if holder.pid == os.getppid() and not force: + return "that's the shell wholocks is running in" if holder.app_type == "service" and not force: svc = holder.description or holder.name return ( From 1ba829253933ebd4b0052263fc063be9d9a11e2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BC=98=E9=9B=85=E3=81=AE=E5=92=B8=E9=B1=BC?= <3129538298@qq.com> Date: Wed, 26 Aug 2026 16:50:50 +0800 Subject: [PATCH 7/7] docs: parent shell is refused unless --force --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f6250ee..119602e 100644 --- a/README.md +++ b/README.md @@ -138,7 +138,7 @@ No drivers, no elevated service, no kernel tricks. Killing uses `TerminateProces ## Safety - Killing always shows *what* will die and asks first (`--yes` to skip). -- Refuses to touch critical system processes, PID 0–4 / PID 1, and itself. +- Refuses to touch critical system processes, PID 0–4 / PID 1, itself, and its parent shell. - Windows services are refused unless `--force` (stop them properly with `net stop`). - Databases get a "stop the service instead" warning in the tip line. - `--kill` verifies afterwards: success is reported only if the path is actually free. @@ -231,7 +231,7 @@ wholocks -r /mnt/usb ## 安全设计 - 结束进程前必先列出并确认(脚本用 `--yes` 跳过) -- 拒绝结束系统关键进程、PID 0–4 / PID 1、以及 wholocks 自己 +- 拒绝结束系统关键进程、PID 0–4 / PID 1、wholocks 自己、以及启动它的父 shell - Windows 服务需要 `--force` 才会动手(并提示用 `net stop` 更妥当) - 数据库进程会警告「请正常停止服务,强杀可能损坏数据」 - `--kill` 结束后会重新扫描验证,文件真正释放了才报成功