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
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

## Table of Contents

- [What's new (2026-06-21) — JSON Web Tokens (JWT)](#whats-new-2026-06-21--json-web-tokens-jwt)
- [What's new (2026-06-21) — License Policy Gate](#whats-new-2026-06-21--license-policy-gate)
- [What's new (2026-06-21) — OpenVEX Vulnerability Triage](#whats-new-2026-06-21--openvex-vulnerability-triage)
- [What's new (2026-06-21) — Dependency Vulnerability Scanning (OSV)](#whats-new-2026-06-21--dependency-vulnerability-scanning-osv)
Expand Down Expand Up @@ -113,6 +114,12 @@

---

## What's new (2026-06-21) — JSON Web Tokens (JWT)

Mint and verify bearer tokens for the APIs you automate. Full reference: [`docs/source/Eng/doc/new_features/v61_features_doc.rst`](docs/source/Eng/doc/new_features/v61_features_doc.rst).

- **`encode_jwt` / `decode_jwt` / `ClaimsPolicy`** (`AC_jwt_encode`, `AC_jwt_decode`): the framework had HMAC *file* signing and an ACME-bound RS256 JWS, but nothing to mint/verify a compact bearer JWT. This adds a pure-stdlib HS256/384/512 codec with full claim validation (`exp`/`nbf`/`aud`/`iss`, injectable clock) that drops straight into `http_request`'s bearer auth. Safe by default: rejects `alg:none`, enforces an algorithm allowlist (anti-confusion), and compares signatures with `hmac.compare_digest`. `AC_jwt_decode` returns `{ok, claims}` so flows can branch without raising.

## What's new (2026-06-21) — License Policy Gate

Flag disallowed dependency licenses. Full reference: [`docs/source/Eng/doc/new_features/v60_features_doc.rst`](docs/source/Eng/doc/new_features/v60_features_doc.rst).
Expand Down
7 changes: 7 additions & 0 deletions README/README_zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

## 目录

- [本次更新 (2026-06-21) — JSON Web Token(JWT)](#本次更新-2026-06-21--json-web-tokenjwt)
- [本次更新 (2026-06-21) — 许可证策略闸门](#本次更新-2026-06-21--许可证策略闸门)
- [本次更新 (2026-06-21) — OpenVEX 漏洞分级](#本次更新-2026-06-21--openvex-漏洞分级)
- [本次更新 (2026-06-21) — 依赖项漏洞扫描(OSV)](#本次更新-2026-06-21--依赖项漏洞扫描osv)
Expand Down Expand Up @@ -112,6 +113,12 @@

---

## 本次更新 (2026-06-21) — JSON Web Token(JWT)

为你自动化的 API 签发与验证 bearer token。完整参考:[`docs/source/Zh/doc/new_features/v61_features_doc.rst`](../docs/source/Zh/doc/new_features/v61_features_doc.rst)。

- **`encode_jwt` / `decode_jwt` / `ClaimsPolicy`**(`AC_jwt_encode`、`AC_jwt_decode`):框架过去有 HMAC *文件*签名与绑定 ACME 的 RS256 JWS,却没有可签发/验证精简 bearer JWT 的工具。本功能补上纯标准库的 HS256/384/512 编解码器,含完整声明验证(`exp`/`nbf`/`aud`/`iss`、可注入时钟),可直接接上 `http_request` 的 bearer 验证。默认即安全:拒绝 `alg:none`、强制算法允许列表(防混淆),并以 `hmac.compare_digest` 比对签名。`AC_jwt_decode` 返回 `{ok, claims}`,让流程不必抛异常即可分支。

## 本次更新 (2026-06-21) — 许可证策略闸门

标记不被允许的依赖项许可证。完整参考:[`docs/source/Zh/doc/new_features/v60_features_doc.rst`](../docs/source/Zh/doc/new_features/v60_features_doc.rst)。
Expand Down
7 changes: 7 additions & 0 deletions README/README_zh-TW.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

## 目錄

- [本次更新 (2026-06-21) — JSON Web Token(JWT)](#本次更新-2026-06-21--json-web-tokenjwt)
- [本次更新 (2026-06-21) — 授權政策閘門](#本次更新-2026-06-21--授權政策閘門)
- [本次更新 (2026-06-21) — OpenVEX 漏洞分級](#本次更新-2026-06-21--openvex-漏洞分級)
- [本次更新 (2026-06-21) — 相依套件漏洞掃描(OSV)](#本次更新-2026-06-21--相依套件漏洞掃描osv)
Expand Down Expand Up @@ -112,6 +113,12 @@

---

## 本次更新 (2026-06-21) — JSON Web Token(JWT)

為你自動化的 API 簽發與驗證 bearer token。完整參考:[`docs/source/Zh/doc/new_features/v61_features_doc.rst`](../docs/source/Zh/doc/new_features/v61_features_doc.rst)。

- **`encode_jwt` / `decode_jwt` / `ClaimsPolicy`**(`AC_jwt_encode`、`AC_jwt_decode`):框架過去有 HMAC *檔案*簽章與綁定 ACME 的 RS256 JWS,卻沒有可簽發/驗證精簡 bearer JWT 的工具。本功能補上純標準函式庫的 HS256/384/512 編解碼器,含完整宣告驗證(`exp`/`nbf`/`aud`/`iss`、可注入時鐘),可直接接上 `http_request` 的 bearer 驗證。預設即安全:拒絕 `alg:none`、強制演算法允許清單(防混淆),並以 `hmac.compare_digest` 比對簽章。`AC_jwt_decode` 回傳 `{ok, claims}`,讓流程不必拋例外即可分支。

## 本次更新 (2026-06-21) — 授權政策閘門

標記不被允許的相依套件授權。完整參考:[`docs/source/Zh/doc/new_features/v60_features_doc.rst`](../docs/source/Zh/doc/new_features/v60_features_doc.rst)。
Expand Down
66 changes: 66 additions & 0 deletions docs/source/Eng/doc/new_features/v61_features_doc.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
JSON Web Tokens (JWT)
=====================

RPA flows constantly need to mint or verify bearer tokens for the APIs they
drive, but the framework only had HMAC *file* signing (``action_signing``) and
an ACME-bound RS256 JWS (``acme_v2``) — neither produces or validates a compact
bearer JWT. This adds a focused, pure-stdlib JWT codec for the HMAC family with
full claim validation, designed to feed straight into ``http_request``'s bearer
auth.

Pure standard library (``hmac`` + ``hashlib`` + ``base64`` + ``json``); the
clock is injectable so ``exp`` / ``nbf`` checks are deterministic. Imports no
``PySide6``.

Security
--------

The decoder is safe by default:

* it **rejects ``alg: "none"``** and any algorithm the caller did not
explicitly allow-list, defeating the classic algorithm-confusion / downgrade
attack;
* it compares signatures with ``hmac.compare_digest`` (constant time);
* RSA/EC algorithms (RS256/ES256) are intentionally **out of scope** — they
require a third-party crypto library.

Headless API
------------

.. code-block:: python

from je_auto_control import encode_jwt, decode_jwt, ClaimsPolicy

token = encode_jwt({"sub": "user1", "aud": "api", "exp": 1893456000}, secret)
# -> "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...."

# default policy: HS256 only, verify exp/nbf, no audience/issuer check
claims = decode_jwt(token, secret)

# tighten the policy for audience / issuer / leeway / algorithms
policy = ClaimsPolicy(algorithms=("HS256",), audience="api",
issuer="my-service", leeway=30)
claims = decode_jwt(token, secret, policy)

``encode_jwt`` signs a compact ``header.payload.signature`` token with
``HS256`` / ``HS384`` / ``HS512``. ``decode_jwt`` verifies the signature, then
validates the standard claims against a :class:`ClaimsPolicy` (``exp`` / ``nbf``
with ``leeway``, ``aud`` membership, ``iss`` match) using an injectable ``now``;
it raises ``ExpiredTokenError`` / ``InvalidSignatureError`` / ``JwtError`` on
failure. The minted token drops straight into the HTTP client:

.. code-block:: python

from je_auto_control import http_request
http_request("https://api.example.com/me",
auth={"type": "bearer", "token": token})

Executor commands
-----------------

``AC_jwt_encode`` takes ``claims`` (a dict or JSON string), ``key`` and an
optional ``alg``; it returns ``{token}``. ``AC_jwt_decode`` takes ``token``,
``key`` and optional ``algorithms`` / ``audience`` / ``leeway``; it returns
``{ok, claims}`` (or ``{ok: false, error}`` so a flow can branch without
raising). Both are exposed as the MCP tools ``ac_jwt_encode`` / ``ac_jwt_decode``
and as Script Builder commands under **Security**.
1 change: 1 addition & 0 deletions docs/source/Eng/eng_index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ Comprehensive guides for all AutoControl features.
doc/new_features/v58_features_doc
doc/new_features/v59_features_doc
doc/new_features/v60_features_doc
doc/new_features/v61_features_doc
doc/ocr_backends/ocr_backends_doc
doc/observability/observability_doc
doc/operations_layer/operations_layer_doc
Expand Down
59 changes: 59 additions & 0 deletions docs/source/Zh/doc/new_features/v61_features_doc.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
JSON Web Token(JWT)
===================

RPA 流程經常需要為其驅動的 API 簽發或驗證 bearer token,但框架過去只有 HMAC *檔案*簽章
(``action_signing``)以及綁定 ACME 的 RS256 JWS(``acme_v2``)—— 兩者都不會產生或驗證精簡的
bearer JWT。本功能補上一個聚焦、純標準函式庫的 JWT 編解碼器(HMAC 家族)並含完整的宣告驗證,
設計上可直接餵入 ``http_request`` 的 bearer 驗證。

純標準函式庫(``hmac`` + ``hashlib`` + ``base64`` + ``json``);時鐘可注入,因此 ``exp`` /
``nbf`` 檢查具決定性。不匯入 ``PySide6``。

安全性
------

解碼器預設即安全:

* **拒絕 ``alg: "none"``** 以及任何呼叫端未明確列入允許清單的演算法,藉此擊敗經典的演算法
混淆 / 降級攻擊;
* 以 ``hmac.compare_digest``(常數時間)比較簽章;
* RSA/EC 演算法(RS256/ES256)刻意**不在範圍內** —— 它們需要第三方加密函式庫。

無頭 API
--------

.. code-block:: python

from je_auto_control import encode_jwt, decode_jwt, ClaimsPolicy

token = encode_jwt({"sub": "user1", "aud": "api", "exp": 1893456000}, secret)
# -> "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...."

# 預設政策:僅 HS256、驗證 exp/nbf、不檢查 audience/issuer
claims = decode_jwt(token, secret)

# 以 ClaimsPolicy 收緊 audience / issuer / leeway / algorithms
policy = ClaimsPolicy(algorithms=("HS256",), audience="api",
issuer="my-service", leeway=30)
claims = decode_jwt(token, secret, policy)

``encode_jwt`` 以 ``HS256`` / ``HS384`` / ``HS512`` 簽出精簡的
``header.payload.signature`` token。``decode_jwt`` 先驗證簽章,再以一份 :class:`ClaimsPolicy`
(含 ``leeway`` 的 ``exp`` / ``nbf``、``aud`` 成員資格、``iss`` 比對)使用可注入的 ``now`` 驗證
標準宣告;失敗時拋出 ``ExpiredTokenError`` / ``InvalidSignatureError`` / ``JwtError``。簽出的
token 可直接接上 HTTP 用戶端:

.. code-block:: python

from je_auto_control import http_request
http_request("https://api.example.com/me",
auth={"type": "bearer", "token": token})

執行器命令
----------

``AC_jwt_encode`` 接受 ``claims``(dict 或 JSON 字串)、``key`` 與選用的 ``alg``,回傳
``{token}``。``AC_jwt_decode`` 接受 ``token``、``key`` 與選用的 ``algorithms`` /
``audience`` / ``leeway``,回傳 ``{ok, claims}``(或 ``{ok: false, error}``,讓流程可在不拋出
例外的情況下分支)。兩者皆以 MCP 工具 ``ac_jwt_encode`` / ``ac_jwt_decode`` 以及 Script Builder
中 **Security** 分類下的命令提供。
1 change: 1 addition & 0 deletions docs/source/Zh/zh_index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ AutoControl 所有功能的完整使用指南。
doc/new_features/v58_features_doc
doc/new_features/v59_features_doc
doc/new_features/v60_features_doc
doc/new_features/v61_features_doc
doc/ocr_backends/ocr_backends_doc
doc/observability/observability_doc
doc/operations_layer/operations_layer_doc
Expand Down
7 changes: 7 additions & 0 deletions je_auto_control/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,11 @@
DEFAULT_COPYLEFT, evaluate_license, evaluate_sbom,
license_findings_to_sarif, normalize_spdx,
)
# JSON Web Token (HMAC family) encode/decode + claim validation
from je_auto_control.utils.jwt import (
ClaimsPolicy, ExpiredTokenError, InvalidSignatureError, JwtError,
decode_jwt, encode_jwt,
)
# Background popup/interrupt watchdog (unattended automation)
from je_auto_control.utils.watchdog import (
PopupWatchdog, WatchdogRule, default_popup_watchdog,
Expand Down Expand Up @@ -791,6 +796,8 @@ def start_autocontrol_gui(*args, **kwargs):
"vex_statement",
"DEFAULT_COPYLEFT", "evaluate_license", "evaluate_sbom",
"license_findings_to_sarif", "normalize_spdx",
"ClaimsPolicy", "ExpiredTokenError", "InvalidSignatureError", "JwtError",
"decode_jwt", "encode_jwt",
# MCP server
"AuditLogger", "HttpMCPServer", "MCPContent", "MCPPrompt",
"MCPPromptArgument", "MCPResource", "MCPServer", "MCPTool",
Expand Down
23 changes: 23 additions & 0 deletions je_auto_control/gui/script_builder/command_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -1192,6 +1192,29 @@ def _add_misc_specs(specs: List[CommandSpec]) -> None:
),
description="Evaluate SBOM licenses against allow/deny SPDX lists.",
))
specs.append(CommandSpec(
"AC_jwt_encode", "Security", "JWT: Sign Token",
fields=(
FieldSpec("claims", FieldType.STRING,
placeholder='{"sub": "user1", "exp": 1893456000}'),
FieldSpec("key", FieldType.STRING, placeholder="shared secret"),
FieldSpec("alg", FieldType.STRING, optional=True,
placeholder="HS256",
choices=("HS256", "HS384", "HS512")),
),
description="Sign a compact JWT (HMAC) from claims; returns {token}.",
))
specs.append(CommandSpec(
"AC_jwt_decode", "Security", "JWT: Verify Token",
fields=(
FieldSpec("token", FieldType.STRING, placeholder="eyJhbGci..."),
FieldSpec("key", FieldType.STRING, placeholder="shared secret"),
FieldSpec("algorithms", FieldType.STRING, optional=True,
placeholder='["HS256"]'),
FieldSpec("audience", FieldType.STRING, optional=True),
),
description="Verify a JWT (alg allowlist + exp/nbf/aud); returns {ok, claims}.",
))
specs.append(CommandSpec(
"AC_run_saga", "Flow", "Run Saga (Compensating Rollback)",
fields=(
Expand Down
28 changes: 28 additions & 0 deletions je_auto_control/utils/executor/action_executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -2914,6 +2914,32 @@ def _check_licenses(components: Any, allow: Any = None,
return {"violations": violations, "count": len(violations)}


def _jwt_encode(claims: Any, key: str, alg: str = "HS256") -> Dict[str, Any]:
"""Adapter: sign a compact JWT from claims (a dict or JSON string)."""
import json
from je_auto_control.utils.jwt import encode_jwt
if isinstance(claims, str):
claims = json.loads(claims)
return {"token": encode_jwt(claims, key, alg=alg)}


def _jwt_decode(token: str, key: str, algorithms: Any = None,
audience: Optional[str] = None,
leeway: float = 0.0) -> Dict[str, Any]:
"""Adapter: verify a JWT and return {ok, claims} or {ok: False, error}."""
import json
from je_auto_control.utils.jwt import ClaimsPolicy, JwtError, decode_jwt
if isinstance(algorithms, str):
algorithms = json.loads(algorithms)
policy = ClaimsPolicy(algorithms=tuple(algorithms) if algorithms
else ("HS256",), audience=audience, leeway=leeway)
try:
claims = decode_jwt(token, key, policy)
except JwtError as exc:
return {"ok": False, "error": str(exc)}
return {"ok": True, "claims": claims}


def _generate_sop(actions: List[Any], title: str = "Automation Procedure",
path: Optional[str] = None) -> Dict[str, Any]:
"""Adapter: build (or write) a step-by-step SOP from an action list."""
Expand Down Expand Up @@ -3696,6 +3722,8 @@ def __init__(self):
"AC_scan_vulns": _scan_vulns,
"AC_apply_vex": _apply_vex,
"AC_check_licenses": _check_licenses,
"AC_jwt_encode": _jwt_encode,
"AC_jwt_decode": _jwt_decode,
"AC_generate_sop": _generate_sop,
"AC_tween_drag": _tween_drag,
"AC_list_plugins": _list_plugins,
Expand Down
10 changes: 10 additions & 0 deletions je_auto_control/utils/jwt/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
"""JSON Web Token (HMAC family) encoding, decoding and claim validation."""
from je_auto_control.utils.jwt.jwt_codec import (
ClaimsPolicy, ExpiredTokenError, InvalidSignatureError, JwtError,
decode_jwt, encode_jwt,
)

__all__ = [
"ClaimsPolicy", "ExpiredTokenError", "InvalidSignatureError", "JwtError",
"decode_jwt", "encode_jwt",
]
Loading
Loading