本文是DEV 的夏季漏洞清除:清理阵容的投稿,由Sentry提供支持。
我构建了一个AWS 安全态势代理:五个专家 AI 代理,可以扫描您的 AWS 账户以发现安全配置错误,将发现映射到 CIS 基准,评估风险,并生成可复制的修复命令。
这些代理在 CrewAI 上依次运行,使用 Amazon Bedrock Nova Pro 作为 LLM:
1. ResourceDiscovery → 枚举 EC2、S3、Lambda、IAM、安全组、API GW、DynamoDB
2. SecurityScanner → 发现开放端口、公开存储桶、管理员角色、不安全配置
3. ComplianceChecker → 映射到 CIS AWS 基础基准
4. RiskScorer → 严重性 × 影响范围 × 可利用性
5. RemediationPlanner → 生成 AWS CLI 修复命令
每个代理都有定制的 boto3 工具,这些工具针对一个包含 90 个 IAM 角色、14 个 S3 存储桶、9 个安全组和 7 个 Lambda 函数的实时账户进行真实的 AWS API 调用。不是测试数据。都是真实的发现。
以下是在我的 AWS 账户上运行的完整扫描。扫描部分加速了 4 倍,结果演示以正常速度播放:
我的第一反应是归咎于 Bedrock 的延迟。每次遇到速度慢的问题,你都会怪 LLM,对吗?
SecurityScanner 代理耗时 22.6 秒,而其他代理平均只需 5-10 秒。如果不了解每个代理内部执行的情况,我可能会添加time.time()调用然后瞎猜。
Sentry 的跟踪瀑布图讲述了不同的故事。真正的问题不是 Python 执行时间或网络延迟。而是 LLM 收到了一个它无法在第一次尝试时顺利处理的上下文负载。
根本原因:我的 IAMAnalyzer 工具从账户中获取了所有 90 个 IAM 角色(过滤掉服务相关角色后剩 59 个),将它们序列化为一个 27KB 的 JSON 块,并将整个负载作为工具输出传递给 LLM。上下文窗口被撑爆了。CrewAI 的内部重试逻辑被触发,在第二次尝试时浪费了更多 token,上下文也更多。
一个工具。错误的默认值。整个管道都受到影响。
修复的 PR:
IAMAnalyzer 工具会获取账户中的每一个角色(共 90 个,过滤掉服务相关角色后剩 59 个),并将完整详细信息倾倒到一个 JSON 块中。该块达到了 26,980 个字符。LLM 处理不了,CrewAI 重试了任务,导致 SecurityScanner 代理最终耗时 22.6 秒,而其他代理只用了 5-10 秒。
为每个代理和工具添加了 Sentry span。跟踪瀑布图一目了然:SecurityScanner 的宽度是其他代理的两倍。深入工具 span,看到 iam_analyzer 的 result_length_chars: 26980,而其他工具大约只有 4000。
7 倍的输出差异。这就是问题所在。
在 iam_analyzer.py 中做了三件事:
RoleLastUsed 日期分页并排序角色,仅分析最活跃的前 20 个角色。其余的是几个月没人碰过的陈旧角色。核心更改位于 src/security_posture/tools/iam_analyzer.py。以下是前后对比:
修复前(错误):
# 获取所有角色,无分页限制
roles = iam.list_roles(MaxItems=100)
role_details = []
for role in roles["Roles"]:
role_name = role["RoleName"]
if role.get("Path", "").startswith("/aws-service-role/"):
continue
# 分析每个角色...
attached = iam.list_attached_role_policies(RoleName=role_name)
# ...构建庞大的 JSON 输出
对于一个拥有 90 个角色的账户,这将生成 26,980 个字符的 JSON。LLM 处理不了。
修复后:
# 修复:按相关性分页并排序
all_roles = []
paginator = iam.get_paginator("list_roles")
for page in paginator.paginate():
all_roles.extend(page["Roles"])
# 过滤服务相关角色(31 个,无法修改)
auditable_roles = [
r for r in all_roles
if not r.get("Path", "").startswith("/aws-service-role/")
]
# 按最后使用日期排序(最活跃的在前)
auditable_roles.sort(key=_last_used_sort_key, reverse=True)
# 仅取前 20 个角色
roles_to_analyze = auditable_roles[:max_roles]
最后还有一个 token 预算保护:
# Token 预算保护:如果输出超过阈值则截断
if len(output) > 4000:
result["role_summary"] = [
{"role_name": r["role_name"], "policies": r.get("attached_policies", [])}
for r in role_details
]
result["note"] = "角色详情已截断,以保持在 token 预算内"
output = json.dumps(result, indent=2, default=str)
三个更改。分页、相关性排序和安全阀。SecurityScanner 不再重试。
先看结果:
| 指标 | 修复前 | 修复后 | 改进 |
|---|---|---|---|
| IAM 工具输出 | 26,980 字符 | 15,532 字符 | 缩小 42% |
| SecurityScanner 时间 | 22.6 秒 | 17.8 秒 | 快 21% |
| IAM API 调用 | 59 次 | 20 次 | 减少 66% |
| 总管道时间 | 62.0 秒 | 57.7 秒 | 快 7% |
| 安全发现数量 | 97 | 97 | 无覆盖损失 |
SecurityScanner 21% 的速度提升来自 LLM 一次完成分析,而无需重试。
我是如何发现的:
修复本身很简单。有趣的是 Sentry 如何直接指向了问题。
没有跟踪瀑布图,我只会看到“管道耗时 62 秒”。跟踪图则指明了不同:
gen_ai.invoke_agent span 中gen_ai.execute_tool span 中iam_analyzer 工具 span 显示 result_length_chars 为 26,980security_group_analyzer 为 4,200 字符,s3_config_checker 为 3,800 字符差异就是线索。如果工具输出比它的兄弟工具大 7 倍,那就该减少它。
我使用 Sentry 的 AI 代理监控从零开始对多代理 CrewAI 管道进行检测。这不是一个 Web 应用或 API。这是五个自主 AI 代理进行 LLM 调用并执行自定义工具。标准 APM 在这里帮不上忙。
每次管道运行都会创建一个 Sentry 事务,具有以下 span 层次结构:
Transaction: "Security Posture Scan" (57s)
├── gen_ai.invoke_agent: ResourceDiscovery
│ └── gen_ai.execute_tool: aws_resource_scanner
├── gen_ai.invoke_agent: SecurityScanner
│ ├── gen_ai.execute_tool: security_group_analyzer
│ ├── gen_ai.execute_tool: s3_config_checker
│ ├── gen_ai.execute_tool: iam_analyzer
│ ├── gen_ai.execute_tool: ec2_security_checker
│ └── gen_ai.execute_tool: lambda_security_checker
├── gen_ai.invoke_agent: ComplianceChecker
├── gen_ai.invoke_agent: RiskScorer
└── gen_ai.invoke_agent: RemediationPlanner
一个热爱技术的程序员,喜欢分享前沿AI知识和开发经验。