跳到正文
DEV Community· Elay osetrov·· 5 小時前AI 評分56

Agent PreShip Guard v1.1 修正誤報並新增破壞性呼叫檢測

Our AI-agent scanner got it backwards. Here's what we fixed in v1.1

AI 導讀

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=True matched, 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, no eval, no shell=True. Just a dict lookup driven by json.loads of 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