Skip to content

Commit 08c8dee

Browse files
authored
Merge pull request #242 from Integration-Automation/feat/egress-guard-batch
Add network egress allowlist guard for the HTTP client
2 parents a442db4 + 585c4e5 commit 08c8dee

16 files changed

Lines changed: 440 additions & 1 deletion

File tree

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
## Table of Contents
1515

16+
- [What's new (2026-06-19) — Network Egress Allowlist Guard](#whats-new-2026-06-19--network-egress-allowlist-guard)
1617
- [What's new (2026-06-19) — Just-In-Time Credential Leases](#whats-new-2026-06-19--just-in-time-credential-leases)
1718
- [What's new (2026-06-19) — Maker-Checker Approval Gate](#whats-new-2026-06-19--maker-checker-approval-gate)
1819
- [What's new (2026-06-19) — Plugin SDK](#whats-new-2026-06-19--plugin-sdk)
@@ -86,6 +87,12 @@
8687

8788
---
8889

90+
## What's new (2026-06-19) — Network Egress Allowlist Guard
91+
92+
Pin which hosts automation may reach. Full reference: [`docs/source/Eng/doc/new_features/v34_features_doc.rst`](docs/source/Eng/doc/new_features/v34_features_doc.rst).
93+
94+
- **`EgressPolicy` / `set_egress_policy`** (`AC_egress_allow` / `AC_egress_check` / `AC_egress_reset`, `ac_*`): an allow list (default-deny) and/or deny list of `fnmatch` host globs (`*.example.com`) consulted by **every** `http_request` (so `AC_http` and all features built on it are covered at once). Blocked hosts raise `EgressBlocked` *before* a socket opens. Starts in allow-all mode — no behavior change until an operator locks egress down. Closes the exfiltration surface for unattended automation.
95+
8996
## What's new (2026-06-19) — Just-In-Time Credential Leases
9097

9198
Zero standing privilege for secrets. Full reference: [`docs/source/Eng/doc/new_features/v33_features_doc.rst`](docs/source/Eng/doc/new_features/v33_features_doc.rst).

README/README_zh-CN.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
## 目录
1414

15+
- [本次更新 (2026-06-19) — 网络出口允许清单守卫](#本次更新-2026-06-19--网络出口允许清单守卫)
1516
- [本次更新 (2026-06-19) — 即时凭证租约](#本次更新-2026-06-19--即时凭证租约)
1617
- [本次更新 (2026-06-19) — Maker-Checker 审批闸门](#本次更新-2026-06-19--maker-checker-审批闸门)
1718
- [本次更新 (2026-06-19) — Plugin SDK](#本次更新-2026-06-19--plugin-sdk)
@@ -85,6 +86,12 @@
8586

8687
---
8788

89+
## 本次更新 (2026-06-19) — 网络出口允许清单守卫
90+
91+
钉选自动化可连线的主机。完整参考:[`docs/source/Zh/doc/new_features/v34_features_doc.rst`](../docs/source/Zh/doc/new_features/v34_features_doc.rst)
92+
93+
- **`EgressPolicy` / `set_egress_policy`**(`AC_egress_allow` / `AC_egress_check` / `AC_egress_reset``ac_*`):允许清单(默认拒绝)与/或拒绝清单,使用 `fnmatch` 主机通配符(`*.example.com`),由**每一次** `http_request` 咨询(因此 `AC_http` 与所有以其为基础的功能一次涵盖)。被封锁的主机会在 socket 打开**之前**抛出 `EgressBlocked`。以 allow-all 模式启动 —— 操作者锁定前不改变任何行为。封闭无人值守自动化的数据外泄面。
94+
8895
## 本次更新 (2026-06-19) — 即时凭证租约
8996

9097
密钥的零常驻权限。完整参考:[`docs/source/Zh/doc/new_features/v33_features_doc.rst`](../docs/source/Zh/doc/new_features/v33_features_doc.rst)

README/README_zh-TW.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
## 目錄
1414

15+
- [本次更新 (2026-06-19) — 網路出口允許清單守衛](#本次更新-2026-06-19--網路出口允許清單守衛)
1516
- [本次更新 (2026-06-19) — 即時憑證租約](#本次更新-2026-06-19--即時憑證租約)
1617
- [本次更新 (2026-06-19) — Maker-Checker 審批閘門](#本次更新-2026-06-19--maker-checker-審批閘門)
1718
- [本次更新 (2026-06-19) — Plugin SDK](#本次更新-2026-06-19--plugin-sdk)
@@ -85,6 +86,12 @@
8586

8687
---
8788

89+
## 本次更新 (2026-06-19) — 網路出口允許清單守衛
90+
91+
釘選自動化可連線的主機。完整參考:[`docs/source/Zh/doc/new_features/v34_features_doc.rst`](../docs/source/Zh/doc/new_features/v34_features_doc.rst)
92+
93+
- **`EgressPolicy` / `set_egress_policy`**(`AC_egress_allow` / `AC_egress_check` / `AC_egress_reset``ac_*`):允許清單(預設拒絕)與/或拒絕清單,使用 `fnmatch` 主機萬用字元(`*.example.com`),由**每一次** `http_request` 諮詢(因此 `AC_http` 與所有以其為基礎的功能一次涵蓋)。被封鎖的主機會在 socket 開啟**之前**拋出 `EgressBlocked`。以 allow-all 模式啟動 —— 操作者鎖定前不改變任何行為。封閉無人值守自動化的資料外洩面。
94+
8895
## 本次更新 (2026-06-19) — 即時憑證租約
8996

9097
密鑰的零常駐權限。完整參考:[`docs/source/Zh/doc/new_features/v33_features_doc.rst`](../docs/source/Zh/doc/new_features/v33_features_doc.rst)
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
Network Egress Allowlist Guard
2+
==============================
3+
4+
Unattended automation that can reach arbitrary hosts is an exfiltration risk.
5+
``EgressPolicy`` lets an operator pin which hosts the headless HTTP client may
6+
talk to. It is consulted by every
7+
:func:`~je_auto_control.utils.http_client.http_client.http_request` call (and
8+
therefore by ``AC_http`` and every feature built on it), so locking egress down
9+
covers the whole framework at once.
10+
11+
The policy supports an **allow** list (default-deny — only matching hosts pass)
12+
and/or a **deny** list (block these even when otherwise allowed). Patterns are
13+
case-insensitive :mod:`fnmatch` globs over the URL hostname, e.g.
14+
``*.example.com`` or ``localhost``. The module-level policy starts in
15+
*allow-all* mode, so there is **no behavior change** until an operator locks it
16+
down. Pure standard library; imports no ``PySide6``.
17+
18+
Headless API
19+
------------
20+
21+
.. code-block:: python
22+
23+
from je_auto_control import set_egress_policy, EgressBlocked, http_request
24+
25+
set_egress_policy(allow=["*.internal.corp", "api.example.com"])
26+
27+
http_request("https://api.example.com/v1") # ok
28+
try:
29+
http_request("https://evil.test/") # raises before connecting
30+
except EgressBlocked:
31+
...
32+
33+
set_egress_policy(None, None) # back to allow-all
34+
35+
Modes: ``allow=None`` is allow-all; ``allow=[]`` denies everything;
36+
``deny=[...]`` alone blocks just those hosts. ``allow`` / ``deny`` each accept a
37+
list or a single comma-separated string. ``get_egress_policy().is_allowed(url)``
38+
checks a URL without raising; ``EgressPolicy(allow=..., deny=...)`` builds an
39+
independent policy object.
40+
41+
Executor commands
42+
-----------------
43+
44+
================================ ===================================================
45+
Command Effect
46+
================================ ===================================================
47+
``AC_egress_allow`` Lock the HTTP client to ``allow`` / ``deny`` lists.
48+
``AC_egress_check`` Report ``{allowed}`` for a URL (does not raise).
49+
``AC_egress_reset`` Clear the policy back to allow-all.
50+
================================ ===================================================
51+
52+
The same operations are exposed as MCP tools (``ac_egress_allow`` /
53+
``ac_egress_check`` / ``ac_egress_reset``) and as Script Builder commands under
54+
**Tools**.

docs/source/Eng/eng_index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ Comprehensive guides for all AutoControl features.
5656
doc/new_features/v31_features_doc
5757
doc/new_features/v32_features_doc
5858
doc/new_features/v33_features_doc
59+
doc/new_features/v34_features_doc
5960
doc/ocr_backends/ocr_backends_doc
6061
doc/observability/observability_doc
6162
doc/operations_layer/operations_layer_doc
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
網路出口允許清單守衛
2+
====================
3+
4+
能連到任意主機的無人值守自動化是一種資料外洩風險。``EgressPolicy`` 讓操作者釘選無頭
5+
HTTP 用戶端可連線的主機。它會被每一次
6+
:func:`~je_auto_control.utils.http_client.http_client.http_request` 呼叫(因而也包含
7+
``AC_http`` 與所有以其為基礎的功能)所諮詢,因此鎖定出口即可一次涵蓋整個框架。
8+
9+
此策略支援**允許(allow)**清單(預設拒絕 —— 僅符合的主機可通過)與/或**拒絕
10+
(deny)**清單(即使其他情況允許也封鎖)。樣式為對 URL 主機名稱進行不分大小寫的
11+
:mod:`fnmatch` 萬用比對,例如 ``*.example.com`` 或 ``localhost``。模組層級的策略以
12+
*allow-all* 模式啟動,因此在操作者鎖定前**不會改變任何行為**。純標準函式庫,不匯入
13+
``PySide6``。
14+
15+
無頭 API
16+
--------
17+
18+
.. code-block:: python
19+
20+
from je_auto_control import set_egress_policy, EgressBlocked, http_request
21+
22+
set_egress_policy(allow=["*.internal.corp", "api.example.com"])
23+
24+
http_request("https://api.example.com/v1") # 通過
25+
try:
26+
http_request("https://evil.test/") # 連線前即拋出
27+
except EgressBlocked:
28+
...
29+
30+
set_egress_policy(None, None) # 回到 allow-all
31+
32+
模式:``allow=None`` 為 allow-all;``allow=[]`` 拒絕一切;單獨給 ``deny=[...]`` 僅封鎖
33+
那些主機。``allow`` / ``deny`` 皆可接受清單或單一逗號分隔字串。
34+
``get_egress_policy().is_allowed(url)`` 可在不拋出例外的情況下檢查 URL;
35+
``EgressPolicy(allow=..., deny=...)`` 則建立獨立的策略物件。
36+
37+
執行器指令
38+
----------
39+
40+
================================ ===================================================
41+
指令 效果
42+
================================ ===================================================
43+
``AC_egress_allow`` 將 HTTP 用戶端鎖定到 ``allow`` / ``deny`` 清單。
44+
``AC_egress_check`` 回報 URL 的 ``{allowed}``(不拋出例外)。
45+
``AC_egress_reset`` 將策略清回 allow-all。
46+
================================ ===================================================
47+
48+
相同操作亦提供為 MCP 工具(``ac_egress_allow`` / ``ac_egress_check`` /
49+
``ac_egress_reset``),以及 Script Builder 中 **Tools** 分類下的指令。

docs/source/Zh/zh_index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ AutoControl 所有功能的完整使用指南。
5656
doc/new_features/v31_features_doc
5757
doc/new_features/v32_features_doc
5858
doc/new_features/v33_features_doc
59+
doc/new_features/v34_features_doc
5960
doc/ocr_backends/ocr_backends_doc
6061
doc/observability/observability_doc
6162
doc/operations_layer/operations_layer_doc

je_auto_control/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,10 @@
208208
ApprovalGate, CredentialBroker, CredentialBrokerError, default_broker,
209209
set_secret_resolver,
210210
)
211+
# Network egress allowlist guard for the headless HTTP client
212+
from je_auto_control.utils.egress import (
213+
EgressBlocked, EgressPolicy, get_egress_policy, set_egress_policy,
214+
)
211215
# Background popup/interrupt watchdog (unattended automation)
212216
from je_auto_control.utils.watchdog import (
213217
PopupWatchdog, WatchdogRule, default_popup_watchdog,
@@ -645,6 +649,7 @@ def start_autocontrol_gui(*args, **kwargs):
645649
"COMMANDS_GROUP", "discover_plugins", "load_plugins",
646650
"ApprovalGate", "CredentialBroker", "CredentialBrokerError",
647651
"default_broker", "set_secret_resolver",
652+
"EgressBlocked", "EgressPolicy", "get_egress_policy", "set_egress_policy",
648653
# MCP server
649654
"AuditLogger", "HttpMCPServer", "MCPContent", "MCPPrompt",
650655
"MCPPromptArgument", "MCPResource", "MCPServer", "MCPTool",

je_auto_control/gui/script_builder/command_schema.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -751,6 +751,27 @@ def _add_misc_specs(specs: List[CommandSpec]) -> None:
751751
fields=(),
752752
description="List active leases (token, name, ttl_remaining).",
753753
))
754+
specs.append(CommandSpec(
755+
"AC_egress_allow", "Tools", "Egress: Set Allowlist",
756+
fields=(
757+
FieldSpec("allow", FieldType.STRING, optional=True,
758+
placeholder="*.example.com, api.foo.com"),
759+
FieldSpec("deny", FieldType.STRING, optional=True,
760+
placeholder="bad.example.com"),
761+
),
762+
description="Lock the HTTP client to an egress allow/deny policy.",
763+
))
764+
specs.append(CommandSpec(
765+
"AC_egress_check", "Tools", "Egress: Check URL",
766+
fields=(FieldSpec("url", FieldType.STRING,
767+
placeholder="https://api.example.com"),),
768+
description="Report whether a URL is permitted by the egress policy.",
769+
))
770+
specs.append(CommandSpec(
771+
"AC_egress_reset", "Tools", "Egress: Reset (allow-all)",
772+
fields=(),
773+
description="Clear the egress policy back to allow-all.",
774+
))
754775
specs.append(CommandSpec(
755776
"AC_generate_sop", "Report", "Generate SOP Document",
756777
fields=(
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
"""Network egress allowlist guard for the headless HTTP client."""
2+
from je_auto_control.utils.egress.egress_policy import (
3+
EgressBlocked, EgressPolicy, get_egress_policy, set_egress_policy,
4+
)
5+
6+
__all__ = [
7+
"EgressBlocked", "EgressPolicy", "get_egress_policy", "set_egress_policy",
8+
]

0 commit comments

Comments
 (0)