mirror of
https://github.com/NCBM/plyngent.git
synced 2026-07-25 08:04:57 +08:00
core/cli: wire fetch private-host policy confirm
This commit is contained in:
+10
-1
@@ -300,9 +300,14 @@ async def _run_chat( # noqa: C901, PLR0912, PLR0915 — chat orchestration
|
||||
# Path denylist and policy confirm live on instance.workspace (no process bag).
|
||||
state.instance_state.workspace.path_denylist = tuple(store.agent_config.path_denylist or ())
|
||||
if interactive:
|
||||
from plyngent.cli.limits import prompt_policy_command_confirm
|
||||
from plyngent.cli.limits import prompt_policy_command_confirm, prompt_policy_fetch_confirm
|
||||
from plyngent.tools.net import set_fetch_policy_confirm_hook
|
||||
|
||||
state.instance_state.workspace.policy_confirm_hook = prompt_policy_command_confirm
|
||||
set_fetch_policy_confirm_hook(
|
||||
prompt_policy_fetch_confirm,
|
||||
instance=state.instance_state,
|
||||
)
|
||||
# Seed cache if we already fetched; else warm in background for Tab.
|
||||
if remote_ids is not None:
|
||||
state.seed_remote_models(remote_ids)
|
||||
@@ -334,6 +339,10 @@ async def _run_chat( # noqa: C901, PLR0912, PLR0915 — chat orchestration
|
||||
if isinstance(state_obj, ReplState):
|
||||
state_obj.instance_state.workspace.policy_confirm_hook = None
|
||||
state_obj.instance_state.workspace.policy_allowed_commands.clear()
|
||||
from plyngent.tools.net import clear_private_grants, set_fetch_policy_confirm_hook
|
||||
|
||||
set_fetch_policy_confirm_hook(None, instance=state_obj.instance_state)
|
||||
clear_private_grants(instance=state_obj.instance_state)
|
||||
await state_obj.instance_state.shutdown()
|
||||
else:
|
||||
# No ReplState: only PTY class cleanup (temps require instance allowlist).
|
||||
|
||||
@@ -232,6 +232,51 @@ def prompt_policy_command_confirm(
|
||||
return False
|
||||
|
||||
|
||||
def prompt_policy_fetch_confirm(
|
||||
host: str,
|
||||
port: int,
|
||||
url: str,
|
||||
timeout_seconds: float,
|
||||
) -> bool:
|
||||
"""Ask whether to allow fetch to a private/loopback host (timed).
|
||||
|
||||
Independent of YOLO: always prompts when interactive. Default is **deny**.
|
||||
Timeout, cancel, or non-interactive → False. On approve, the host:port is
|
||||
granted for the rest of this process (see net grants).
|
||||
"""
|
||||
reason = (
|
||||
f"fetch targets private/loopback host {host!r} port {port}\n"
|
||||
f"url: {url}\n"
|
||||
f"Allow this host:port for the rest of this process?\n"
|
||||
f"(timeout {timeout_seconds:g}s defaults to DENY; not skipped by YOLO)"
|
||||
)
|
||||
try:
|
||||
with pause_task_cancel_for_prompt():
|
||||
backend = get_prompt_backend()
|
||||
if not backend.is_interactive():
|
||||
return False
|
||||
backend.echo()
|
||||
backend.secho(format_tool_confirm_box("policy", reason), fg="yellow")
|
||||
backend.echo()
|
||||
grant = f"{host}:{port}"
|
||||
prompt = f"[policy] allow fetch {grant!r}? [y/N] (timeout {timeout_seconds:g}s): "
|
||||
with contextlib.suppress(OSError):
|
||||
_ = sys.stderr.write(prompt)
|
||||
_ = sys.stderr.flush()
|
||||
raw = _read_yes_no_line_with_timeout(timeout_seconds)
|
||||
if raw is None:
|
||||
with contextlib.suppress(OSError, NonInteractiveError):
|
||||
backend.secho(
|
||||
f"[policy] timed out after {timeout_seconds:g}s — denied",
|
||||
fg="red",
|
||||
err=True,
|
||||
)
|
||||
return False
|
||||
return raw.strip().lower() in {"y", "yes"}
|
||||
except NonInteractiveError, KeyboardInterrupt, EOFError:
|
||||
return False
|
||||
|
||||
|
||||
async def prompt_confirm_tool_async(name: str, args: Mapping[str, object], reason: str) -> bool | str:
|
||||
"""Async confirm: True allow, False deny, str = deny with user comment."""
|
||||
del args
|
||||
|
||||
@@ -29,6 +29,14 @@ raise `max_replaces` or narrow `old_string`.
|
||||
for ordered pipelines (`pipe_out` / `mix_stderr` as needed).
|
||||
- Prefer `vcs_*` for status/diff/log/branch when enough.
|
||||
|
||||
### Network
|
||||
- Prefer `fetch` (GET/POST/PUT/DELETE) for HTTP(S) docs/APIs over curl/wget via shell.
|
||||
- Set `user_agent` (or a User-Agent header) when the remote expects a specific client; \
|
||||
otherwise a small default is used. Never rely on shell to spoof identity.
|
||||
- Private/loopback/LAN hosts need an explicit human policy allow (not skipped by YOLO).
|
||||
- Prefer provider hosted search (e.g. web_search) for open-ended research; use `fetch` for known URLs.
|
||||
- Respect denials, size limits, and HTTPS→HTTP redirect blocks; do not re-fetch the same URL repeatedly.
|
||||
|
||||
### Humans & safety
|
||||
- Prefer `ask_user_line` / `ask_user_choice` / `ask_user_form` over waiting for the next free-form turn.
|
||||
- Overwrites, deletes, shells, and risky ops may require human confirm; hard denylists are not skipped by YOLO.
|
||||
|
||||
Reference in New Issue
Block a user