Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ it shipped into a `## [x.y.z] - date` section of their own; the tag's
- `write_secret(secret)` / `AC_write_secret` (`secret`): type a password or
token as Unicode key events without logging, recording or returning it; an
error never names a character. Refuses on a backend without Unicode typing.
- A package gate in front of `AC_add_package_to_executor` and
`AC_add_package_to_callback_executor`: `executor.allow_packages(*names)` (submodules
included) and `executor.set_allow_arbitrary_packages(enabled)` (also on
`package_manager`). A refused package raises `AutoControlExecuteActionException`
before it is imported.
- `message_format.MessageFormatError` (an `AutoControlException` and a
`ValueError`); locales other than en/fr use Babel's CLDR plural rules when
Babel is installed.
Expand Down Expand Up @@ -104,6 +109,13 @@ it shipped into a `## [x.y.z] - date` section of their own; the tag's
generating values from the schema alone used to produce a plain string and
get a `ValueError` out of `datetime.fromisoformat`.

### Deprecated

- Loading a package that is not on the allowlist while the package gate is unconfigured.
It still works, with a `DeprecationWarning`; a future release will refuse it by default.
Migration: call `executor.allow_packages(...)` for the packages your action lists load,
or `executor.set_allow_arbitrary_packages(True)` to keep loading any package.

### Changed

- `format_message` raises `MessageFormatError` for patterns ICU rejects
Expand Down
10 changes: 10 additions & 0 deletions Progress.md
Original file line number Diff line number Diff line change
Expand Up @@ -509,3 +509,13 @@ be at 2x if on a Retina screen」,`scale_down=True` 只在帶 `bbox` 時生效
## `test_usb_acl_prompt.py` 讓 Python 3.10 的 headless 測試間歇 segfault

`TODO` — `test/unit_test/headless/test_usb_acl_prompt.py::test_bridge_remember_persists_acl_rule` 在 `coverage run -m pytest` 下讓行程 SIGSEGV(exit 139),整個 `pytest-headless` job 因此失敗:2026-09-26 連續三次 AutoControl Code Quality(ubuntu-22.04/3.10),2026-09-30 一次(macos-14/3.10);同一次其他版本都過,之後的 run 又過,所以是間歇的。原因還沒查:先在 3.10 開 `faulthandler` 重跑這一支,看崩在哪個原生呼叫。

---

## 套件閘門的預設改成拒絕

`BLOCKED` — 等含警告的版本出去之後再發兩版

`AC_add_package_to_executor`/`AC_add_package_to_callback_executor` 前面已有套件閘門(工作區 X-12),但沒設定時仍會載入任何套件、只發 `DeprecationWarning`。兩個版本之後,在 `utils/package_manager/package_manager_class.py` 的 `PackageManager.__init__` 把 `allow_arbitrary_packages` 改成 `False`,拿掉 `_check_allowed` 裡的警告分支,並更新三份 README 的「Package gate」段落、`docs/source/{Eng,Zh}/doc/keyword_and_executor/keyword_and_executor_doc.rst` 與 `docs/source/API/utils/package_manager.rst`,`CHANGELOG.md` 記成破壞性變更。

**先決定**:只跑動作檔、沒有 Python 宿主程式的使用者(`je_auto_control` CLI、socket/REST/MCP server、排程器)要怎麼放行套件。
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,8 @@ still goes on to the end), so a CI step fails with it. The legacy

All servers bind to `127.0.0.1` unless you opt in explicitly.

**Package gate.** `AC_add_package_to_executor` and `AC_add_package_to_callback_executor` import a Python package and register its members as commands, so an action list arriving over any of these surfaces could load `os` or `subprocess`. The host program decides what may load: `executor.allow_packages("name", …)` lists the packages (submodules included) and `executor.set_allow_arbitrary_packages(False)` refuses the rest before importing them. Neither is an `AC_*` command, so an action list cannot open its own gate. A refused package fails that action with `AutoControlExecuteActionException`. Until the host calls either switch, any package still loads but raises a `DeprecationWarning`: a future release will refuse unlisted packages by default.

### How the remote-desktop wire protocol works

Worth knowing before you expose a host, and described nowhere else in the
Expand Down
2 changes: 2 additions & 0 deletions README/README_zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,8 @@ je_auto_control version

除非明确指定,所有服务器都绑定在 `127.0.0.1`。

**包闸门。** `AC_add_package_to_executor` 与 `AC_add_package_to_callback_executor` 会导入 Python 包并把成员注册成命令,所以从上面任何一个入口送来的动作列表都可能加载 `os` 或 `subprocess`。哪些包可以加载,由宿主程序决定:`executor.allow_packages("name", …)` 列出可以加载的包(含子模块),`executor.set_allow_arbitrary_packages(False)` 会在导入前拒绝其他包。这两个都不是 `AC_*` 命令,所以动作列表不能自己打开闸门。被拒绝的包会让该动作以 `AutoControlExecuteActionException` 失败。宿主程序调用任一个开关之前,任何包仍会加载,但会发出 `DeprecationWarning`:之后的版本会默认拒绝清单以外的包。

### 远程桌面的线路协议

把主机开放出去之前值得先了解,而且这一段在其他文档里都没有写。默认传输是**裸
Expand Down
2 changes: 2 additions & 0 deletions README/README_zh-TW.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,8 @@ je_auto_control version

除非明確指定,所有伺服器都綁在 `127.0.0.1`。

**套件閘門。** `AC_add_package_to_executor` 與 `AC_add_package_to_callback_executor` 會匯入 Python 套件並把成員註冊成命令,所以從上面任何一個入口送來的動作清單都可能載入 `os` 或 `subprocess`。哪些套件可以載入,由宿主程式決定:`executor.allow_packages("name", …)` 列出可以載入的套件(含子模組),`executor.set_allow_arbitrary_packages(False)` 會在匯入前拒絕其他套件。這兩個都不是 `AC_*` 命令,所以動作清單不能自己打開閘門。被拒絕的套件會讓該動作以 `AutoControlExecuteActionException` 失敗。宿主程式呼叫任一個開關之前,任何套件仍會載入,但會發出 `DeprecationWarning`:之後的版本會預設拒絕清單以外的套件。

### 遠端桌面的線路協定

把主機開出去之前值得先知道,而且這段在其他文件裡都沒有寫。預設傳輸是**裸 TCP
Expand Down
4 changes: 4 additions & 0 deletions architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,10 @@ first. `AC_*` command names and the legacy CLI flags are public too (action file
- Flat exception hierarchy: every framework error derives from `AutoControlException`; assertion failures keep
propagating. → CLAUDE.md › Coding Standards › Project-specific rules
- Validate at boundaries and reject unknown command names; servers bind `127.0.0.1` unless explicitly opted in. → same
- `AC_add_package_to_executor` / `AC_add_package_to_callback_executor` pass the package gate in
`utils/package_manager/package_manager_class.py` before importing: `executor.allow_packages(...)` and
`executor.set_allow_arbitrary_packages(...)` are Python-only switches, never `AC_*` commands, so an action list
cannot open its own gate. Unconfigured, any package loads with a `DeprecationWarning` (workspace X-12).
- No `print()` or runtime `assert` in library code; lazy imports for optional and platform deps; release platform
resources in `finally` / `with`; guard shared state with locks or queues; pin dependency versions. → same
- Size limits (cyclomatic ≤ 10, cognitive ≤ 15, function ≤ 75 lines, file ≤ 750 lines, line ≤ 120) are a review
Expand Down
20 changes: 10 additions & 10 deletions architecture_explore.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ iOS(WebDriverAgent)。核心能力是滑鼠/鍵盤控制、影像辨識、
| 指標 | 數值 |
| --- | ---: |
| Python 模組總數(含周邊子專案) | 1,063 |
| 程式碼總行數 | 157,034 |
| 程式碼總行數 | 157,101 |
| `je_auto_control/utils/` 子套件數 | 310 |
| `AC_*` 動作指令數(`known_commands()` 實測) | 776 |
| 套件門面 `__all__` 公開名稱數 | 1,244 |
Expand Down Expand Up @@ -272,7 +272,7 @@ socket server 有 8 MiB 讀取上限與 30 秒 handler timeout。

### 5.4.1 執行引擎與腳本資產

> 24 個套件、約 14,674 行。
> 24 個套件、約 14,689 行。

| 模組 | 行數 | 職責 |
| --- | ---: | --- |
Expand All @@ -283,7 +283,7 @@ socket server 有 8 MiB 讀取上限與 30 秒 handler timeout。
| `utils/dag/` | 536 | 跨主機 DAG 編排器(圖模型 + runner) |
| `utils/decision_table/` | 112 | DMN 風格決策表:規則 + 命中策略,把分支外部化 |
| `utils/deterministic/` | 116 | 決定性執行控制:固定亂數種子 + 凍結時鐘 |
| `utils/executor/` | 9,545 | **核心**。`Executor` 指令分派表(776 個 `AC_*`)、參數插值、乾跑、逐步 callback;`flow_control` 提供 34 個區塊指令(迴圈/分支/try/巨集/變數) |
| `utils/executor/` | 9,560 | **核心**。`Executor` 指令分派表(776 個 `AC_*`)、參數插值、乾跑、逐步 callback;`flow_control` 提供 34 個區塊指令(迴圈/分支/try/巨集/變數) |
| `utils/flow_debugger/` | 166 | action list 的單步除錯器與追蹤器 |
| `utils/input_macro/` | 462 | 定時輸入事件:錄製結果的整形(`timeline`/`InputRecorder`,Windows 與 macOS 共用)、重播與宣告式輸入序列 DSL |
| `utils/json/` | 99 | action JSON 檔讀寫與正規化格式化(`fmt --check` 的後端) |
Expand All @@ -303,7 +303,7 @@ socket server 有 8 MiB 讀取上限與 30 秒 handler timeout。

### 5.4.2 框架基礎設施

> 14 個套件、約 3,053 行。
> 14 個套件、約 3,105 行。

| 模組 | 行數 | 職責 |
| --- | ---: | --- |
Expand All @@ -316,7 +316,7 @@ socket server 有 8 MiB 讀取上限與 30 秒 handler timeout。
| `utils/failure_bundle/` | 229 | 可攜、已遮蔽的失敗診斷 ZIP(截圖 + 診斷 + log 尾段) |
| `utils/file_process/` | 40 | 目錄檔案列舉(`execute_dir` 的後端) |
| `utils/logging/` | 168 | `autocontrol_logger` 單例 + 家目錄共用記錄檔 handler(`JE_AUTOCONTROL_LOG_FILE` 可改) |
| `utils/package_manager/` | 103 | 動態載入套件並把 executor 注入其中 |
| `utils/package_manager/` | 155 | 動態載入套件並把 executor 注入其中;載入前先過套件閘門(允許清單加上只能從 Python 呼叫的開關,工作區 X-12) |
| `utils/path_guard/` | 114 | 命令列傳入路徑的正規化與邊界檢查(防路徑穿越) |
| `utils/platform_id/` | 62 | 作業系統家族的單一判定點。`sys.platform` 原本在一百多處跟字面清單比對,而那些清單都沒有 BSD;`is_x11_unix()` 問的是「這是不是 X11 unix」,這才是守衛一直想問的問題 |
| `utils/shell_process/` | 279 | `ShellManager`:以 argv list 執行外部命令(禁用 `shell=True`) |
Expand Down Expand Up @@ -696,11 +696,11 @@ socket server 有 8 MiB 讀取上限與 30 秒 handler timeout。

上表以子套件為單位;以下把行數最大的幾個子系統展開到檔案層。

#### `utils/executor/`(9,545 行)— 執行核心
#### `utils/executor/`(9,560 行)— 執行核心

| 檔案 | 行數 | 職責 |
| --- | ---: | --- |
| `action_executor.py` | 8,326 | `Executor` 類別與 `event_dict` 分派表(776 個指令),另含數百個把 utils 能力接成指令的 adapter 函式;全域單例 `executor` 與 `add_command_to_executor()` 擴充點。 |
| `action_executor.py` | 8,341 | `Executor` 類別與 `event_dict` 分派表(776 個指令),另含數百個把 utils 能力接成指令的 adapter 函式;全域單例 `executor` 與 `add_command_to_executor()` 擴充點。 |
| `flow_control.py` | 644 | 真正的流程控制:`AC_loop`/`AC_for_each`/`AC_while_*`/`AC_if_*`/`AC_try`/`AC_retry`/`AC_parallel`/`AC_define_macro`/`AC_call_macro`/變數指令(`AC_set_var`/`AC_get_var`/`AC_inc_var`)。`LoopBreak`/`LoopContinue` 以例外實作。34 個區塊指令的分派表 `BLOCK_COMMANDS` 也在這裡,含下一列匯入的資料來源指令。 |
| `flow_data_commands.py` | 272 | `AC_*_to_var` 資料來源與轉換指令:shell、時鐘、亂數、PDF、TOTP、SQL、檔案、HTTP、OCR,加上 `AC_assert_var`/`AC_assert_db`/`AC_assert_duration`/`AC_transform_var`。都不執行巢狀 action list,所以沒有迴圈/分支語意。 |
| `action_schema.py` | 159 | action list 的結構驗證:形狀、參數型別、未知指令拒絕。單一走訪同時支援兩種消費方式:`validate_actions()` 遇到第一個問題就拋、`unknown_command_names()` 收齊全部不認得的名字(REST `/execute` 用它回 400)。 |
Expand Down Expand Up @@ -1073,7 +1073,7 @@ socket 預設綁 `127.0.0.1`;資源一律用 `with`。
| `gui/` | 95 | 27,829 |
| `utils/mcp_server/` | 35 | 18,846 |
| `utils/remote_desktop/` | 56 | 13,014 |
| `utils/executor/` | 8 | 9,545 |
| `utils/executor/` | 8 | 9,560 |
| `utils/usb/` | 17 | 4,572 |
| `je_auto_control/`(頂層 3 檔) | 3 | 2,411 |
| `utils/accessibility/` | 14 | 3,143 |
Expand All @@ -1090,6 +1090,6 @@ socket 預設綁 `127.0.0.1`;資源一律用 `with`。
| `osx/` | 17 | 925 |
| `autocontrol-lsp/` | 8 | 744 |
| `utils/hotkey/` | 7 | 852 |
| 其餘模組(約 286 個 `utils/` 子套件 + `android/`/`ios/`/周邊小工具) | 681 | 56,986 |
| **總計** | **1,057** | **156,969** |
| 其餘模組(約 286 個 `utils/` 子套件 + `android/`/`ios/`/周邊小工具) | 681 | 57,038 |
| **總計** | **1,057** | **157,036** |

12 changes: 12 additions & 0 deletions docs/source/API/utils/package_manager.rst
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,18 @@ PackageManager
:param predicate: Inspection predicate (e.g., ``isfunction``, ``isclass``).
:param target: Target executor whose ``event_dict`` will be updated.

.. method:: allow_packages(*packages)

Adds packages, and their submodules, to the package gate's allowlist.

.. method:: set_allow_arbitrary_packages(enabled)

Allows (``True``) or refuses (``False``) packages outside the allowlist. Until either switch
is called, any package loads with a ``DeprecationWarning``. ``add_package_to_executor`` and
``add_package_to_callback_executor`` check the gate before importing and raise
``AutoControlExecuteActionException`` for a refused package. The ``Executor`` has the same two
static methods; neither is an ``AC_*`` command.

.. method:: add_package_to_target(package, target)

Loads functions, built-ins, and classes from a package into the specified target executor.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,22 @@ Extending the Executor

You can dynamically load external Python packages into the executor:

The package gate decides which packages may load. ``AC_add_package_to_executor`` can import ``os`` or
``subprocess`` for any action list, so the host program lists what it needs:

.. code-block:: python

from je_auto_control import executor

executor.allow_packages("time") # these, and their submodules
executor.set_allow_arbitrary_packages(False) # refuse everything else before importing it

Neither switch is an ``AC_*`` command, so an action list cannot open its own gate; a refused package fails
its action with ``AutoControlExecuteActionException``. Until the host calls either switch, any package still
loads but raises a ``DeprecationWarning``; a future release will refuse packages outside the allowlist by
default.


.. code-block:: python

from je_auto_control import package_manager
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,21 @@ JSON 陣列(關鍵字),由執行者解析並執行。

你可以動態載入外部 Python 套件到執行者中:

哪些套件可以載入由套件閘門決定。``AC_add_package_to_executor`` 能替任何動作清單匯入 ``os`` 或
``subprocess``,所以由宿主程式列出它需要的套件:

.. code-block:: python

from je_auto_control import executor

executor.allow_packages("time") # 這些套件與其子模組
executor.set_allow_arbitrary_packages(False) # 其他套件在匯入前就拒絕

這兩個開關都不是 ``AC_*`` 命令,所以動作清單不能自己打開閘門;被拒絕的套件會讓該動作以
``AutoControlExecuteActionException`` 失敗。宿主程式呼叫任一個開關之前,任何套件仍會載入,但會發出
``DeprecationWarning``;之後的版本會預設拒絕允許清單以外的套件。


.. code-block:: python

from je_auto_control import package_manager
Expand Down
24 changes: 24 additions & 0 deletions docs/updates/2026-10.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,27 @@ WebRunner's native commands (its U-20261001-28 and -29), `WR_ac_fill_native_file
- **Contract**: `test_cross_project_contracts.py` pins `AC_write_secret` (`secret`) among the commands WebRunner sends, and `tab` in the key table (`WR_ac_basic_auth` moves to the password field with it). §6's WebRunner row lists it.
- **Tests**: `test/unit_test/headless/test_write_secret.py` (8): exact typing including a non-BMP character, nothing in log or record, a failure that names no character and chains no cause, refusal on a backend without Unicode typing and on a non-string, registration, and the executor masking both argument forms.
- **Docs**: README and both translations (capability row, command count 776), `docs/source/{Eng,Zh}/doc/keyboard` and `keyword_and_executor`, `CHANGELOG.md`; the stub `je_auto_control/actions.pyi` and the quoted line counts in `architecture_explore.md` regenerated with their generators.

## U-20261001-08 · 2026-10-01 · Package gate in front of AC_add_package_to_executor · #security #X-12

- **What**: workspace X-12. `AC_add_package_to_executor` / `AC_add_package_to_callback_executor` imported any installed package and registered its members as commands. Any action list could therefore load `os` or `subprocess`, whether it came from a JSON file, the socket / REST / MCP servers or the scheduler.
- `PackageManager` now checks the name before importing. This is the same gate WebRunner, APITestka, LoadDensity and MailThunder use:
- `allow_packages(*names)` sets the allowlist (a listed package also allows its submodules);
- `set_allow_arbitrary_packages(bool)` opens the gate (`True`) or refuses everything else (`False`);
- a refusal raises `AutoControlExecuteActionException`.
- The `Executor` has the same two static methods. Neither is an `AC_*` command, and a test checks that no command name contains them.
- Unconfigured, any package still loads with a `DeprecationWarning`. Flipping that default is a `Progress.md` item.
- ID 08 because `feat/jeffrey-rpa-gui-apis` already uses 03–06 and `write_secret` took 07.
- **Docs**:
- the "Package gate" paragraph in the three READMEs (Servers and integrations);
- `docs/source/API/utils/package_manager.rst`;
- the "Extending the Executor" section of `docs/source/{Eng,Zh}/doc/keyword_and_executor/keyword_and_executor_doc.rst`;
- `architecture.md` §7 and the `utils/package_manager/` row in `architecture_explore.md`, with its line counts re-measured by `test_doc_line_counts.py --fix`;
- `CHANGELOG.md` (Added and Deprecated).
- **Result / numbers**: `test/unit_test/headless/test_package_gate.py` adds 8 tests. The full headless suite on this branch: 10559 passed, 46 skipped.
- **Files**:
- `je_auto_control/utils/package_manager/package_manager_class.py`, `je_auto_control/utils/executor/action_executor.py`;
- `test/unit_test/headless/test_package_gate.py`;
- `README.md`, `README/README_zh-TW.md`, `README/README_zh-CN.md`;
- `docs/source/API/utils/package_manager.rst`, `docs/source/{Eng,Zh}/doc/keyword_and_executor/keyword_and_executor_doc.rst`;
- `architecture.md`, `architecture_explore.md`, `CHANGELOG.md`, `Progress.md`.
1 change: 1 addition & 0 deletions docs/updates/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ In the same commit: delete the item from `Progress.md`, add a `#done` entry here

| ID | Date | Title | Tags | Batch |
|---|---|---|---|---|
| U-20261001-08 | 2026-10-01 | Package gate in front of AC_add_package_to_executor | #security #X-12 | [2026-10](2026-10.md) |
| U-20261001-07 | 2026-10-01 | write_secret / AC_write_secret: type a password without logging, recording or returning it | #done #keyboard #security #webrunner | [2026-10](2026-10.md) |
| U-20261001-02 | 2026-10-01 | Pin the AC commands WebRunner's native WR_ac_* send; record the missing secret typing | #contract #webrunner | [2026-10](2026-10.md) |
| U-20261001-01 | 2026-10-01 | Run AC_web_run's documented form, record WebRunner failures, gate through execute_one | #bugfix #webrunner | [2026-10](2026-10.md) |
Expand Down
Loading