Agent PreShip Guard v1.1 修正誤報並新增破壞性呼叫檢測
Our AI-agent scanner got it backwards. Here's what we fixed in v1.1
Agent PreShip Guard v1.1 修正固定命令被誤判為模型輸出的問題,並新增偵測未經審批、由模型輸出選擇破壞性呼叫的高風險規則。測試中,固定 uptime 命令只留下 shell=True 的中風險警告;模型可選擇 rmtree 的示例則觸發高風險警告。作者指出,逐檔檢查可能因其他模組中的審批檢查而誤報;這只是啟發式提示,不能取代人工審查。
We ship a small free GitHub Action, Agent PreShip Guard, that flags risky AI-agent code before it merges. Before writing about it, we pointed it at two tiny demo agents. It got both wrong.
The two agents
Agent A is a read-only uptime check. The command is a fixed string:
# Agent A: read-only uptime check. Command is a fixed literal.
import subprocess
def tool_uptime():
return subprocess.run("uptime", shell=True, capture_output=True, text=True).stdout
Agent B lets the model pick both the tool and its argument, with no approval step:
# Agent B: model chooses the tool AND the argument; no approval gate.
import shutil, json
TOOLS = {"remove_dir": shutil.rmtree}
def act(plan_json):
step = json.loads(plan_json) # plan comes straight from the LLM
return TOOLS[step["tool"]](step["arg"]) # e.g. {"tool":"remove_dir","arg":"/srv/prod"}
Anyone reading these knows which one should keep you up at night.
What v1.0 said
== Agent A
::error AG001 Model/LLM output passed to shell
::warning AG004 shell=True in tool code
Agent PreShip Guard: 1 high, 1 medium findings.
== Agent B
Agent PreShip Guard: 0 high, 0 medium findings.
The safe agent got a high-severity finding. The agent where the model chooses what to rmtree got nothing.
Why
Two bugs, one root cause: we were matching syntax, not authority.
-
False positive on A. The AG001 rule looked for "output" near a subprocess call.
capture_output=Truematched, so a fixed"uptime"command was reported as "model output passed to shell." -
False negative on B. No rule covered the real risk: model output choosing a destructive call. There's no
subprocess, noeval, noshell=True. Just a dict lookup driven byjson.loadsof a model plan.
What v1.1 says
== Agent A
::warning AG004 shell=True in tool code
Agent PreShip Guard: 0 high, 1 medium findings.
== Agent B
::error AG008 Model output selects a destructive call with no approval gate
Agent PreShip Guard: 1 high, 0 medium findings.
v1.1 fixes the AG001 false positive and adds AG008: model/LLM output (for example json.loads(plan) or tool_calls) selects a destructive call (rmtree, os.remove, subprocess, DROP TABLE, DELETE FROM, .delete(, transfer() and no approve/confirm/human-in-the-loop check appears in the file. The remaining medium on A is fair: shell=True isn't needed for a fixed command.
AG008 still has limits, and the README says so. It works per file, so an approval check in another module won't be seen (possible false positive). A gate with an unusual name won't be recognized either. It's a heuristic tripwire, not a review.
Use it
# .github/workflows/agent-guard.yml
name: agent-guard
on: [pull_request, push]
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: elaysuu/agent-preship-guard@main # pin to a commit SHA in production
with:
path: .
fail-on: high # high | medium | none
Findings show up as inline PR annotations. It's MIT licensed, and the tests run with python3 -m unittest discover -s tests.
The lesson
Ask "who decides?", not "which function is called?" A hardcoded shell command is boring. A model choosing which destructive function runs, with nobody approving it, is the incident.
A scanner only catches the obvious cases. For the full manual pass, I sell a 40-check AI Agent Pre-Ship Security Checklist, and the rest of the kits are in the store (or the 5-kit bundle). If the Action flags something it shouldn't, or misses something, please open an issue. That's how v1.1 happened.
來源:DEV Community · dev.to