Skip to content

Commit a442db4

Browse files
authored
Merge pull request #241 from Integration-Automation/feat/credential-lease-batch
Add just-in-time credential leases (zero standing privilege)
2 parents d2c50b8 + b2cee5f commit a442db4

15 files changed

Lines changed: 468 additions & 5 deletions

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) — Just-In-Time Credential Leases](#whats-new-2026-06-19--just-in-time-credential-leases)
1617
- [What's new (2026-06-19) — Maker-Checker Approval Gate](#whats-new-2026-06-19--maker-checker-approval-gate)
1718
- [What's new (2026-06-19) — Plugin SDK](#whats-new-2026-06-19--plugin-sdk)
1819
- [What's new (2026-06-19) — MCP Structured Output](#whats-new-2026-06-19--mcp-structured-output)
@@ -85,6 +86,12 @@
8586

8687
---
8788

89+
## What's new (2026-06-19) — Just-In-Time Credential Leases
90+
91+
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).
92+
93+
- **`CredentialBroker`** (`AC_lease_secret` / `AC_lease_valid` / `AC_revoke_lease` / `AC_lease_active`, `ac_*`): a consumer takes a short-lived *lease* (token bound to a secret name + expiry); the real value is fetched only at `redeem` time, only while valid, through a pluggable resolver (an unlocked `SecretManager`, env, vault). Secret values never enter executor/MCP records — the executor/MCP/Builder surfaces manage the lease lifecycle only; `redeem` is a deliberate Python-API-only escape hatch. Clock and resolver injectable.
94+
8895
## What's new (2026-06-19) — Maker-Checker Approval Gate
8996

9097
Segregation of duties for high-risk steps. Full reference: [`docs/source/Eng/doc/new_features/v32_features_doc.rst`](docs/source/Eng/doc/new_features/v32_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) — Maker-Checker 审批闸门](#本次更新-2026-06-19--maker-checker-审批闸门)
1617
- [本次更新 (2026-06-19) — Plugin SDK](#本次更新-2026-06-19--plugin-sdk)
1718
- [本次更新 (2026-06-19) — MCP 结构化输出](#本次更新-2026-06-19--mcp-结构化输出)
@@ -84,6 +85,12 @@
8485

8586
---
8687

88+
## 本次更新 (2026-06-19) — 即时凭证租约
89+
90+
密钥的零常驻权限。完整参考:[`docs/source/Zh/doc/new_features/v33_features_doc.rst`](../docs/source/Zh/doc/new_features/v33_features_doc.rst)
91+
92+
- **`CredentialBroker`**(`AC_lease_secret` / `AC_lease_valid` / `AC_revoke_lease` / `AC_lease_active``ac_*`):使用者取得短效*租约*(绑定密钥名称 + 到期时间的 token);真正的值仅在 `redeem` 时、且仅在有效期间,通过可插拔解析器(已解锁的 `SecretManager`、环境变量、vault)取得。密钥值永不进入 executor/MCP 记录 —— executor/MCP/Builder 接口仅管理租约生命周期;`redeem` 是刻意设计的仅限 Python API 逃生门。时钟与解析器皆可注入。
93+
8794
## 本次更新 (2026-06-19) — Maker-Checker 审批闸门
8895

8996
高风险步骤的职责分离。完整参考:[`docs/source/Zh/doc/new_features/v32_features_doc.rst`](../docs/source/Zh/doc/new_features/v32_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) — Maker-Checker 審批閘門](#本次更新-2026-06-19--maker-checker-審批閘門)
1617
- [本次更新 (2026-06-19) — Plugin SDK](#本次更新-2026-06-19--plugin-sdk)
1718
- [本次更新 (2026-06-19) — MCP 結構化輸出](#本次更新-2026-06-19--mcp-結構化輸出)
@@ -84,6 +85,12 @@
8485

8586
---
8687

88+
## 本次更新 (2026-06-19) — 即時憑證租約
89+
90+
密鑰的零常駐權限。完整參考:[`docs/source/Zh/doc/new_features/v33_features_doc.rst`](../docs/source/Zh/doc/new_features/v33_features_doc.rst)
91+
92+
- **`CredentialBroker`**(`AC_lease_secret` / `AC_lease_valid` / `AC_revoke_lease` / `AC_lease_active``ac_*`):使用者取得短效*租約*(綁定密鑰名稱 + 到期時間的 token);真正的值僅在 `redeem` 時、且僅在有效期間,透過可插拔解析器(已解鎖的 `SecretManager`、環境變數、vault)取得。密鑰值永不進入 executor/MCP 紀錄 —— executor/MCP/Builder 介面僅管理租約生命週期;`redeem` 是刻意設計的僅限 Python API 逃生門。時鐘與解析器皆可注入。
93+
8794
## 本次更新 (2026-06-19) — Maker-Checker 審批閘門
8895

8996
高風險步驟的職責分離。完整參考:[`docs/source/Zh/doc/new_features/v32_features_doc.rst`](../docs/source/Zh/doc/new_features/v32_features_doc.rst)
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
Just-In-Time Credential Leases
2+
==============================
3+
4+
Long-lived secrets handed to automation are a standing liability. ``CredentialBroker``
5+
applies **zero standing privilege**: a consumer takes a short-lived *lease* — a
6+
token bound to a secret name with an expiry — and the real value is fetched only
7+
at :meth:`redeem` time, only while the lease is valid, through a pluggable
8+
*resolver* (an unlocked ``SecretManager``'s ``get``, an environment lookup, a
9+
vault client). Expired or revoked leases yield nothing.
10+
11+
Secret values never enter executor/MCP records: the executor and MCP surfaces
12+
manage the lease *lifecycle* only. :meth:`redeem`, which returns the real value,
13+
is a deliberate **Python-API-only** escape hatch for code that must handle the
14+
secret. The module is pure standard library and imports no ``PySide6``; the
15+
clock and resolver are injectable, so expiry is deterministically testable.
16+
17+
Headless API
18+
------------
19+
20+
.. code-block:: python
21+
22+
from je_auto_control import CredentialBroker
23+
24+
broker = CredentialBroker(resolver=secret_manager.get) # resolver(name)->value
25+
token = broker.lease("db_password", ttl=120) # token, not the value
26+
27+
if broker.is_valid(token):
28+
password = broker.redeem(token) # fetched just in time, Python-only
29+
connect(password)
30+
31+
broker.revoke(token) # or let it expire after ttl seconds
32+
33+
``active()`` lists non-expired leases as ``{token, name, ttl_remaining}`` with no
34+
values. A module-level :data:`default_broker` backs the executor/MCP commands;
35+
configure its resolver once with ``set_secret_resolver(fn)``.
36+
37+
Executor commands
38+
-----------------
39+
40+
================================ ===================================================
41+
Command Effect
42+
================================ ===================================================
43+
``AC_lease_secret`` Issue a lease for ``name`` (``ttl`` s); ``{token, ttl}``.
44+
``AC_lease_valid`` Report ``{valid}`` for a lease token.
45+
``AC_revoke_lease`` Revoke a lease token; ``{revoked}``.
46+
``AC_lease_active`` List active leases (no secret values).
47+
================================ ===================================================
48+
49+
There is intentionally **no** redeem command on the executor, MCP, or Script
50+
Builder surfaces — exposing the value there would leak it into run records.
51+
Redeeming is Python-only. The same lifecycle operations are exposed as MCP tools
52+
(``ac_lease_secret`` / ``ac_lease_valid`` / ``ac_revoke_lease`` /
53+
``ac_lease_active``) and as Script Builder commands under **Tools**.

docs/source/Eng/eng_index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ Comprehensive guides for all AutoControl features.
5555
doc/new_features/v30_features_doc
5656
doc/new_features/v31_features_doc
5757
doc/new_features/v32_features_doc
58+
doc/new_features/v33_features_doc
5859
doc/ocr_backends/ocr_backends_doc
5960
doc/observability/observability_doc
6061
doc/operations_layer/operations_layer_doc
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
即時(Just-In-Time)憑證租約
2+
============================
3+
4+
交給自動化的長效密鑰是一種長期負債。``CredentialBroker`` 實踐**零常駐權限(zero
5+
standing privilege)**:使用者取得短效的*租約(lease)*—— 一個綁定密鑰名稱並帶有
6+
到期時間的 token —— 真正的值僅在 :meth:`redeem` 時、且僅在租約有效期間,透過可插拔
7+
的*解析器(resolver)*取得(已解鎖的 ``SecretManager`` 的 ``get``、環境變數查詢、
8+
vault 用戶端)。已過期或已撤銷的租約取不到任何值。
9+
10+
密鑰值永遠不會進入 executor/MCP 紀錄:executor 與 MCP 介面僅管理租約*生命週期*。
11+
回傳真正值的 :meth:`redeem` 是刻意設計的**僅限 Python API**逃生門,供必須處理密鑰
12+
的程式使用。本模組為純標準函式庫,不匯入 ``PySide6``;時鐘與解析器皆可注入,因此到
13+
期行為可被確定性地測試。
14+
15+
無頭 API
16+
--------
17+
18+
.. code-block:: python
19+
20+
from je_auto_control import CredentialBroker
21+
22+
broker = CredentialBroker(resolver=secret_manager.get) # resolver(name)->value
23+
token = broker.lease("db_password", ttl=120) # 取得 token,而非值
24+
25+
if broker.is_valid(token):
26+
password = broker.redeem(token) # 即時取得,僅限 Python
27+
connect(password)
28+
29+
broker.revoke(token) # 或讓它在 ttl 秒後自然過期
30+
31+
``active()`` 以 ``{token, name, ttl_remaining}`` 列出未過期的租約,不含任何值。模組
32+
層級的 :data:`default_broker` 支撐 executor/MCP 指令;以 ``set_secret_resolver(fn)``
33+
設定一次其解析器即可。
34+
35+
執行器指令
36+
----------
37+
38+
================================ ===================================================
39+
指令 效果
40+
================================ ===================================================
41+
``AC_lease_secret`` 為 ``name`` 發出租約(``ttl`` 秒);``{token, ttl}``。
42+
``AC_lease_valid`` 回報租約 token 的 ``{valid}``。
43+
``AC_revoke_lease`` 撤銷租約 token;``{revoked}``。
44+
``AC_lease_active`` 列出有效租約(不含密鑰值)。
45+
================================ ===================================================
46+
47+
executor、MCP 與 Script Builder 介面刻意**沒有** redeem 指令 —— 在那些介面暴露值會
48+
使其洩漏進執行紀錄。Redeem 僅限 Python。相同的生命週期操作亦提供為 MCP 工具
49+
(``ac_lease_secret`` / ``ac_lease_valid`` / ``ac_revoke_lease`` /
50+
``ac_lease_active``),以及 Script Builder 中 **Tools** 分類下的指令。

docs/source/Zh/zh_index.rst

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

je_auto_control/__init__.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -203,8 +203,11 @@
203203
from je_auto_control.utils.plugin_sdk import (
204204
COMMANDS_GROUP, discover_plugins, load_plugins,
205205
)
206-
# Maker-checker approval gate (segregation of duties for high-risk actions)
207-
from je_auto_control.utils.governance import ApprovalGate
206+
# Maker-checker approval gate + just-in-time credential leases (PAM/governance)
207+
from je_auto_control.utils.governance import (
208+
ApprovalGate, CredentialBroker, CredentialBrokerError, default_broker,
209+
set_secret_resolver,
210+
)
208211
# Background popup/interrupt watchdog (unattended automation)
209212
from je_auto_control.utils.watchdog import (
210213
PopupWatchdog, WatchdogRule, default_popup_watchdog,
@@ -640,7 +643,8 @@ def start_autocontrol_gui(*args, **kwargs):
640643
"describe_step", "generate_sop", "write_sop",
641644
"easing_names", "tween_drag", "tween_points",
642645
"COMMANDS_GROUP", "discover_plugins", "load_plugins",
643-
"ApprovalGate",
646+
"ApprovalGate", "CredentialBroker", "CredentialBrokerError",
647+
"default_broker", "set_secret_resolver",
644648
# MCP server
645649
"AuditLogger", "HttpMCPServer", "MCPContent", "MCPPrompt",
646650
"MCPPromptArgument", "MCPResource", "MCPServer", "MCPTool",

je_auto_control/gui/script_builder/command_schema.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -728,6 +728,29 @@ def _add_misc_specs(specs: List[CommandSpec]) -> None:
728728
),
729729
description="Report a request's status and approved flag.",
730730
))
731+
specs.append(CommandSpec(
732+
"AC_lease_secret", "Tools", "Lease: Issue",
733+
fields=(
734+
FieldSpec("name", FieldType.STRING),
735+
FieldSpec("ttl", FieldType.FLOAT, optional=True, default=300.0),
736+
),
737+
description="Issue a short-lived JIT lease for a secret (no value).",
738+
))
739+
specs.append(CommandSpec(
740+
"AC_lease_valid", "Tools", "Lease: Valid?",
741+
fields=(FieldSpec("token", FieldType.STRING),),
742+
description="Report whether a lease token is still valid.",
743+
))
744+
specs.append(CommandSpec(
745+
"AC_revoke_lease", "Tools", "Lease: Revoke",
746+
fields=(FieldSpec("token", FieldType.STRING),),
747+
description="Revoke a lease token immediately.",
748+
))
749+
specs.append(CommandSpec(
750+
"AC_lease_active", "Tools", "Lease: List Active",
751+
fields=(),
752+
description="List active leases (token, name, ttl_remaining).",
753+
))
731754
specs.append(CommandSpec(
732755
"AC_generate_sop", "Report", "Generate SOP Document",
733756
fields=(

je_auto_control/utils/executor/action_executor.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2922,6 +2922,30 @@ def _approval_status(token: str, db: Optional[str] = None) -> Dict[str, Any]:
29222922
return {"status": gate.status(token), "approved": gate.is_approved(token)}
29232923

29242924

2925+
def _lease_secret(name: str, ttl: float = 300.0) -> Dict[str, Any]:
2926+
"""Adapter: issue a JIT lease for a secret name (no value returned)."""
2927+
from je_auto_control.utils.governance import default_broker
2928+
return {"token": default_broker.lease(name, ttl), "ttl": float(ttl)}
2929+
2930+
2931+
def _lease_valid(token: str) -> Dict[str, Any]:
2932+
"""Adapter: report whether a lease token is still valid."""
2933+
from je_auto_control.utils.governance import default_broker
2934+
return {"valid": default_broker.is_valid(token)}
2935+
2936+
2937+
def _revoke_lease(token: str) -> Dict[str, Any]:
2938+
"""Adapter: revoke a lease token immediately."""
2939+
from je_auto_control.utils.governance import default_broker
2940+
return {"revoked": default_broker.revoke(token)}
2941+
2942+
2943+
def _lease_active() -> Dict[str, Any]:
2944+
"""Adapter: list active (non-expired) leases without any secret values."""
2945+
from je_auto_control.utils.governance import default_broker
2946+
return {"leases": default_broker.active()}
2947+
2948+
29252949
class Executor:
29262950
"""
29272951
Executor
@@ -3157,6 +3181,10 @@ def __init__(self):
31573181
"AC_approval_approve": _approval_approve,
31583182
"AC_approval_reject": _approval_reject,
31593183
"AC_approval_status": _approval_status,
3184+
"AC_lease_secret": _lease_secret,
3185+
"AC_lease_valid": _lease_valid,
3186+
"AC_revoke_lease": _revoke_lease,
3187+
"AC_lease_active": _lease_active,
31603188
"AC_a11y_record_start": _a11y_record_start,
31613189
"AC_a11y_record_stop": _a11y_record_stop,
31623190
"AC_a11y_record_events": _a11y_record_events,

0 commit comments

Comments
 (0)