AI Agent安全实战:白名单风险分级与人工确认落地指南

2026-06-15阅读 0热度 0
前端 人工智能

为什么AI Agent安全是上线前必须解决的难题

过去半年,聊到AI Agent时,大家最容易兴奋的点是“它终于能自己干活了”。读取仓库、修改代码、调用接口、发送请求——听起来就像把一个实习生直接塞进了终端。

AI Agent 工具调用安全实战:白名单、风险分级和人工确认怎么落地

但当真正把Agent部署到生产环境后,团队最先踩的坑往往不是“它不够聪明”,而是“它太敢动手”。一次未截获的delete操作、一个没隔离的外部请求、一个被注入的工具参数,都可能让本应是提效利器的系统,变成新的风险敞口。

观察下来,不少团队在落地Agent时,都会遇到几个典型问题:

  1. 工具权限泛化:读文件、写文件、执行命令全部开放,Agent一旦判断失误,攻击面急剧扩大。
  2. 风险等级缺失:查询天气和删除数据库,在系统里被当作同一类调用处理。
  3. 人工确认环节空白:高风险操作没有二次确认,出了事只能事后翻日志。
  4. 审计信息碎片化:只知道“某次调用失败了”,但不知道谁发起的、为什么放行、参数是什么。

所以这篇文章不聊大而空的“Agent安全趋势”,只解决一个足够实用的问题:如何在自有Agent系统里,加一层真正能落地的工具安全闸门。

目标很明确:

  • 低风险操作自动放行
  • 中风险操作做参数校验
  • 高风险操作必须人工确认
  • 所有动作都有审计记录

下面用一个简化版TypeScript示例,把这套思路完整走一遍。

核心设计原理:策略引擎先行

先说结论:绝不要让Agent直接触碰工具,必须经过一层策略引擎。

这层策略引擎至少完成四项任务:

1. 工具白名单

Agent只能调用系统显式注册过的工具,不能自由拼接任意命令,也不能在运行时动态发现新工具就直接执行。

这一步解决的是“边界”问题。可以理解为给Agent画活动范围:能去哪、能碰什么、不能碰什么,都提前写清楚。

2. 风险分级

每个工具都必须标注风险等级。一套简单但足够实用的分级方法是:

  • low:只读查询,比如搜索文档、查看状态
  • medium:有限写操作,比如创建草稿、更新缓存
  • high:破坏性操作或对外部影响大的操作,比如删文件、发消息、执行shell

风险等级不是装饰,它直接决定后续流程:是否需要人工审批、是否限制参数、是否进入隔离环境。

3. 参数校验

很多事故不是工具本身危险,而是参数危险。比如write_file本身没问题,但如果目标路径跳出了工作目录,风险就完全变了。

因此必须在工具执行前做参数验证:

  • 是否缺少必填字段
  • 路径是否越界
  • 命令是否命中危险关键字
  • URL是否指向允许域名

4. 人工确认与审计

高风险操作绝不能让Agent一步到位。正确流程是:

  1. Agent先生成计划
  2. 策略层判断风险为high
  3. 系统返回requires_confirmation
  4. 人确认后再执行
  5. 全流程记审计日志

这样做的好处不是“绝对安全”,而是把不可控风险压缩到人能接住的范围内。

实战演示:打造安全闸门

下面做一个简化版安全闸门案例。场景是:我们的Agent可以调用三个工具:

  • searchDocs:查文档
  • writeDraft:写草稿文件
  • runShell:执行命令

其中runShell一律视为高风险,必须人工确认;writeDraft只能写到指定目录;searchDocs直接放行。

第一步:定义工具和策略

type RiskLevel = 'low' | 'medium' | 'high'

type ToolContext = {
  userId: string
  workspace: string
  requireHumanConfirm: (payload: {
    tool: string
    reason: string
    input: unknown
  }) => Promise
  audit: (event: AuditEvent) => Promise
}

type AuditEvent = {
  tool: string
  risk: RiskLevel
  status: 'allowed' | 'blocked' | 'confirmed' | 'rejected'
  reason: string
  input: unknown
  createdAt: string
}

type ToolDefinition = {
  name: string
  risk: RiskLevel
  validate: (input: TInput, ctx: ToolContext) => void
  run: (input: TInput, ctx: ToolContext) => Promise
}

关键点在于:工具不只包含run逻辑,还必须自带validate和risk。

很多团队会把校验写在业务逻辑里,导致规则分散,后续维护越来越乱。更稳妥的方式是把校验当成工具定义的组成部分。

第二步:实现三个工具

import path from 'node:path'
import fs from 'node:fs/promises'
import { execFile } from 'node:child_process'
import { promisify } from 'node:util'

const execFileAsync = promisify(execFile)

const tools: Record> = {
  searchDocs: {
    name: 'searchDocs',
    risk: 'low',
    validate(input: { keyword: string }) {
      if (!input.keyword || input.keyword.trim().length < 2) {
        throw new Error('keyword 太短,拒绝执行')
      }
    },
    async run(input) {
      return [`找到与 ${input.keyword} 相关的 3 篇文档`]
    }
  },

  writeDraft: {
    name: 'writeDraft',
    risk: 'medium',
    validate(input: { fileName: string; content: string }, ctx) {
      const target = path.resolve(ctx.workspace, 'drafts', input.fileName)
      const allowedRoot = path.resolve(ctx.workspace, 'drafts')

      if (!target.startsWith(allowedRoot)) {
        throw new Error('目标路径越界,拒绝写入')
      }

      if (!input.content || input.content.trim().length === 0) {
        throw new Error('content 不能为空')
      }
    },
    async run(input, ctx) {
      const target = path.resolve(ctx.workspace, 'drafts', input.fileName)
      await fs.mkdir(path.dirname(target), { recursive: true })
      await fs.writeFile(target, input.content, 'utf8')
      return { sa ved: true, path: target }
    }
  },

  runShell: {
    name: 'runShell',
    risk: 'high',
    validate(input: { command: string; args?: string[] }) {
      const blocked = ['rm', 'sudo', 'mkfs', 'shutdown']
      if (blocked.includes(input.command)) {
        throw new Error(`命中高危命令 ${input.command},禁止执行`)
      }
    },
    async run(input) {
      const { stdout, stderr } = await execFileAsync(input.command, input.args ?? [])
      return { stdout, stderr }
    }
  }
}

这里有两个实战细节值得注意。

第一,writeDraft不只是检查文件名,而是把路径resolve成绝对路径后再判断是否仍在允许目录内。很多“目录穿越”漏洞,都是因为只做了字符串包含判断。

第二,runShell这里故意没做“黑名单万能论”。高危命令拦截只是第一层,真正上生产时,最好再叠加容器隔离、只读挂载和网络白名单。别指望一个if判断解决所有安全问题。

第三步:实现统一闸门

async function executeTool(
  toolName: string,
  input: unknown,
  ctx: ToolContext
) {
  const tool = tools[toolName]

  if (!tool) {
    await ctx.audit({
      tool: toolName,
      risk: 'high',
      status: 'blocked',
      reason: 'tool_not_registered',
      input,
      createdAt: new Date().toISOString()
    })
    throw new Error(`未知工具:${toolName}`)
  }

  tool.validate(input, ctx)

  if (tool.risk === 'high') {
    const approved = await ctx.requireHumanConfirm({
      tool: tool.name,
      reason: 'high_risk_operation',
      input
    })

    await ctx.audit({
      tool: tool.name,
      risk: tool.risk,
      status: approved ? 'confirmed' : 'rejected',
      reason: 'manual_confirmation',
      input,
      createdAt: new Date().toISOString()
    })

    if (!approved) {
      throw new Error('人工确认未通过,终止执行')
    }
  }

  const result = await tool.run(input, ctx)

  await ctx.audit({
    tool: tool.name,
    risk: tool.risk,
    status: 'allowed',
    reason: 'policy_passed',
    input,
    createdAt: new Date().toISOString()
  })

  return result
}

至此,核心框架搭建完成。Agent不再直接调用工具,而是统一走executeTool。后续无论接MCP、接内部函数、还是接第三方API,都能复用这一层策略。

第四步:模拟一次实际调用

const ctx: ToolContext = {
  userId: 'u_001',
  workspace: '/srv/project-a',
  async requireHumanConfirm(payload) {
    console.log('待人工确认:', payload)
    return false // 模拟审批拒绝
  },
  async audit(event) {
    console.log('AUDIT =>', JSON.stringify(event))
  }
}

await executeTool('searchDocs', { keyword: 'mcp' }, ctx)
await executeTool('writeDraft', {
  fileName: 'weekly-report.md',
  content: '# 本周进展n- 完成策略层接入'
}, ctx)
await executeTool('runShell', {
  command: 'git',
  args: ['status']
}, ctx)

前两个调用会正常执行,第三个会先进入人工确认流程。这里模拟的是拒绝,所以git status不会实际运行。你也可以把逻辑改成:只有在工单系统里点击通过,才允许继续执行。

第五步:放到真实项目里时,再补三刀

如果计划把这套方案接进线上,建议再叠加三层防护:

1. 会话级配额

为每个用户、每个Agent、每类工具都加上调用额度。目的不是省钱,而是防止异常循环和批量误操作。

2. 环境隔离

高风险工具尽量放进隔离执行环境。即使Agent出错,也别让它直接触碰宿主机关键目录。

3. 审批上下文

人工确认页面不要只给一个“同意/拒绝”按钮,最好同时展示:

  • Agent原始意图
  • 工具名称
  • 完整参数
  • 风险等级
  • 预期影响范围

这样确认动作才有实际意义,否则只是把决策压力甩给人工。

最后一句话

AI Agent真正难的,不是让它多会干活,而是让它在该干的时候干,在不该动手的时候停下来

工程上最容易落地的一套思路,就是今天讲的四步:

  1. 工具必须注册到白名单
  2. 每个工具都要标风险等级
  3. 执行前做参数校验
  4. 高风险操作走人工确认并写审计日志

这套设计不复杂,但特别适合做第一版安全底座。它不能保证系统永远不出事,但能显著降低“Agent一步走错,把事故直接放大”的概率。

如果你们团队最近正准备把Agent接入真实业务,建议很直接:先别急着追求它能调多少工具,先保证它每多拿一项能力,你都知道怎么收回来。

免责声明

本网站新闻资讯均来自公开渠道,力求准确但不保证绝对无误,内容观点仅代表作者本人,与本站无关。若涉及侵权,请联系我们处理。本站保留对声明的修改权,最终解释权归本站所有。

相关阅读

更多
欢迎回来 登录或注册后,可保存提示词和历史记录
登录后可同步收藏、历史记录和常用模板
注册即表示同意服务条款与隐私政策