@tiens.nguyen/gonext-local-worker 1.0.226 → 1.0.227
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/gonext-local-worker.mjs +6 -0
- package/gonext_agent_chat.py +52 -18
- package/package.json +1 -1
package/gonext-local-worker.mjs
CHANGED
|
@@ -1842,6 +1842,12 @@ async function runAgentChatJob(job) {
|
|
|
1842
1842
|
// workspaces are registered. Passing a literal 5 here would look user-set and
|
|
1843
1843
|
// block that bump.
|
|
1844
1844
|
maxSteps: payload?.maxSteps ?? 0,
|
|
1845
|
+
// run_command policy (web Settings → Agent). Both optional: empty = allow every
|
|
1846
|
+
// command except the always-blocked sudo/su. runAllowlist (non-empty) = opt-in
|
|
1847
|
+
// lockdown to exactly those runners; runDenylist = extra blocks. Accept whatever
|
|
1848
|
+
// the API sends (list or comma/space string); python re-parses either shape.
|
|
1849
|
+
runAllowlist: payload?.runAllowlist ?? [],
|
|
1850
|
+
runDenylist: payload?.runDenylist ?? [],
|
|
1845
1851
|
// Max web_search + fetch_url calls the agent may make per task (user-configurable
|
|
1846
1852
|
// in web Settings → Agent). Default 10; past it the retrieval tools refuse.
|
|
1847
1853
|
researchBudget: payload?.researchBudget ?? 10,
|
package/gonext_agent_chat.py
CHANGED
|
@@ -2209,17 +2209,23 @@ def _ws_record_change(action: str, path: str, backup) -> None:
|
|
|
2209
2209
|
# the model on productive paths (build/test/deploy), not to contain a hostile one.
|
|
2210
2210
|
# Still excluded on purpose: bare shells, package publishes, sudo, and bare `pip`
|
|
2211
2211
|
# (the model must use `python3 -m pip`, which pip itself recommends).
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2212
|
+
# run_command execution policy — ALLOW-BY-DEFAULT.
|
|
2213
|
+
# We used to gate on a fixed allowlist (npm/python/go/…). It rejected legitimate DevOps
|
|
2214
|
+
# tooling (docker, kubectl, terraform, aws, gh, …) and, because that universe is unbounded
|
|
2215
|
+
# and grows constantly, any list was a hardcode that upset the next user. Crucially, the
|
|
2216
|
+
# gate was never a SANDBOX: `python3`/`node` are runnable and can subprocess anything, so a
|
|
2217
|
+
# restrictive allowlist bought STEERING, not safety — it just added friction. So now: any
|
|
2218
|
+
# command runs, with two guards kept:
|
|
2219
|
+
# 1. Privilege escalation is ALWAYS blocked (`sudo`/`su`/`doas`) — running the model's
|
|
2220
|
+
# commands as root is the one footgun a run-enabled workspace shouldn't hand over.
|
|
2221
|
+
# 2. Commands run via argv (shlex.split + Popen, NO shell), so pipes/redirects/`&&`
|
|
2222
|
+
# /backticks are literal args, never shell operations — the shell-injection guard.
|
|
2223
|
+
# The user can still tighten this from web Settings (passed through cfg, both default off):
|
|
2224
|
+
# • runAllowlist — if non-empty, ONLY these runners are permitted (opt-in lockdown).
|
|
2225
|
+
# • runDenylist — extra runners to block on top of the always-blocked set.
|
|
2226
|
+
_WS_RUN_DENY_ALWAYS = {"sudo", "su", "doas"} # privilege escalation — never runnable
|
|
2227
|
+
_WS_RUN_ALLOWLIST = set() # opt-in lockdown from cfg; empty = allow all
|
|
2228
|
+
_WS_RUN_DENYLIST = set() # extra user blocks from cfg
|
|
2223
2229
|
|
|
2224
2230
|
# ---- background servers (behavior-detected, command-agnostic) ----
|
|
2225
2231
|
# run_command classifies a command by OBSERVED BEHAVIOR, never by its text: if the
|
|
@@ -2597,6 +2603,18 @@ def run_agent_chat(cfg):
|
|
|
2597
2603
|
except Exception: # noqa: BLE001
|
|
2598
2604
|
continue
|
|
2599
2605
|
_WS_AVAILABLE = bool(_WS_ROOTS)
|
|
2606
|
+
# run_command policy overrides from Settings (both optional, default = allow all but
|
|
2607
|
+
# sudo). Accept either a list or a comma/space-separated string; basenames so a user
|
|
2608
|
+
# can write "git" and match "/usr/bin/git".
|
|
2609
|
+
global _WS_RUN_ALLOWLIST, _WS_RUN_DENYLIST
|
|
2610
|
+
|
|
2611
|
+
def _split_cmds(v):
|
|
2612
|
+
import re as _re
|
|
2613
|
+
items = v if isinstance(v, (list, tuple)) else _re.split(r"[,\s]+", str(v or ""))
|
|
2614
|
+
return {str(c).strip() for c in items if str(c).strip()}
|
|
2615
|
+
|
|
2616
|
+
_WS_RUN_ALLOWLIST = _split_cmds(cfg.get("runAllowlist"))
|
|
2617
|
+
_WS_RUN_DENYLIST = _split_cmds(cfg.get("runDenylist"))
|
|
2600
2618
|
# Active terminal workspace: the folder the `gonext` REPL was launched from (sent as
|
|
2601
2619
|
# payload.activeWorkspace = the terminal's cwd). download_file / unzip_file put their
|
|
2602
2620
|
# output HERE instead of the shared ~/.gonext/rag-work cache, so files land where the
|
|
@@ -2960,9 +2978,10 @@ def run_agent_chat(cfg):
|
|
|
2960
2978
|
"left RUNNING (its window opens on the user's Mac); stop_server closes it. An "
|
|
2961
2979
|
"interactive input() CLI can't be driven here (EOFError) — make it non-interactive "
|
|
2962
2980
|
"or give the user the command.\n"
|
|
2963
|
-
|
|
2964
|
-
"
|
|
2965
|
-
"
|
|
2981
|
+
" Most commands run directly — build/test/package tools, git, docker, kubectl, "
|
|
2982
|
+
"terraform, aws/gcloud/az, and so on. Two limits: `sudo`/`su` are blocked (no root); "
|
|
2983
|
+
"and there is NO shell, so pipes `|`, redirects `>`, `&&`, and backticks do NOT work "
|
|
2984
|
+
"— run one command per call, or use python3 for anything that needs composition.\n"
|
|
2966
2985
|
" - deploy_web(local_dir, host, user, remote_path, domain='') — DEPLOY a built "
|
|
2967
2986
|
"static site in ONE call: build first (run_command('npm run build')), then "
|
|
2968
2987
|
"deploy_web('my-app/build', host, user, remote_path, domain). It rsyncs over your "
|
|
@@ -4568,7 +4587,7 @@ def run_agent_chat(cfg):
|
|
|
4568
4587
|
"""Run a command inside a workspace that has run permission (e.g. 'npm test', 'npm run build', 'npm start', 'mvn test', 'python3 app.py'). One-shot commands return their output when they exit. If the command turns out to be a SERVER (keeps running and listens on a port), it is left RUNNING and its URL is reported. If it is a DESKTOP/GUI app (opens a window — tkinter, pygame, PyQt…), it is LAUNCHED and left running: its window appears on the user's Mac; tell them it's open. Both are stoppable via stop_server. Also allows DEPLOY commands (ssh/scp/rsync) — use non-interactive KEY auth (-o BatchMode=yes); never put a password in the command (there is no stdin). Note: an interactive CLI that reads stdin (input()) cannot be driven here — it will fail with EOFError; make it non-interactive or give the user the command to run themselves.
|
|
4569
4588
|
|
|
4570
4589
|
Args:
|
|
4571
|
-
command: the command line
|
|
4590
|
+
command: the command line. Most commands run; no shell (so no pipes/redirects/&&), and sudo/su are blocked.
|
|
4572
4591
|
workdir: directory to run in. Empty = first registered workspace.
|
|
4573
4592
|
timeout_seconds: give up after this many seconds if the command neither exits nor starts a server (max 600).
|
|
4574
4593
|
"""
|
|
@@ -4610,9 +4629,24 @@ def run_agent_chat(cfg):
|
|
|
4610
4629
|
msg = "Error: empty command."
|
|
4611
4630
|
_last_obs["text"] = msg
|
|
4612
4631
|
return msg
|
|
4613
|
-
|
|
4614
|
-
|
|
4615
|
-
|
|
4632
|
+
# Allow-by-default policy (see _WS_RUN_DENY_ALWAYS). Match on the runner's
|
|
4633
|
+
# basename too, so `/usr/bin/sudo` can't slip past a bare-name block.
|
|
4634
|
+
_runner = argv[0]
|
|
4635
|
+
_rbase = os.path.basename(_runner)
|
|
4636
|
+
if _runner in _WS_RUN_DENY_ALWAYS or _rbase in _WS_RUN_DENY_ALWAYS:
|
|
4637
|
+
msg = (f"Error: '{_runner}' is blocked (privilege escalation is never "
|
|
4638
|
+
"run by the agent). Run that step yourself, or tell the user the "
|
|
4639
|
+
"exact command to run.")
|
|
4640
|
+
_last_obs["text"] = msg
|
|
4641
|
+
return msg
|
|
4642
|
+
if _WS_RUN_DENYLIST and (_runner in _WS_RUN_DENYLIST or _rbase in _WS_RUN_DENYLIST):
|
|
4643
|
+
msg = (f"Error: '{_runner}' is blocked by your run denylist "
|
|
4644
|
+
"(change it in web Settings → Agent).")
|
|
4645
|
+
_last_obs["text"] = msg
|
|
4646
|
+
return msg
|
|
4647
|
+
if _WS_RUN_ALLOWLIST and _runner not in _WS_RUN_ALLOWLIST and _rbase not in _WS_RUN_ALLOWLIST:
|
|
4648
|
+
msg = (f"Error: '{_runner}' is not in your run allowlist. Add it in web "
|
|
4649
|
+
"Settings → Agent, or clear the allowlist to allow all commands.")
|
|
4616
4650
|
_last_obs["text"] = msg
|
|
4617
4651
|
return msg
|
|
4618
4652
|
_emit({"type": "step", "text": f"Running → {command[:70]}"})
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tiens.nguyen/gonext-local-worker",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.227",
|
|
4
4
|
"description": "Polls GoNext cloud API for async local LLM jobs and runs them against Ollama/OpenAI-compatible servers on this Mac",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|