mcpField
D
deep-app-audit

deep-app-audit

github.com/frappe安全159247 次安装2 天前更新
代码仓库

关于 deep-app-audit

deep-app-audit 用于对 Frappe 应用代码库开展安全性、正确性和定制化审计。它调度 Agent CLI 任务、验证发现并生成报告,还会记录运行状态以便续跑。

主要功能

  1. 01安全、正确性和定制化检查
  2. 02并行调度 Agent CLI 任务
  3. 03扫描与验证任务分开执行
  4. 04可选测试站点和 Semgrep 准备
  5. 05记录状态并支持续跑
  6. 06汇总任务结果生成报告

适用场景

  1. 01按选定轨道审计 Frappe 应用代码库
  2. 02从有效任务结果恢复中断的审计
  3. 03不创建测试站点,仅检查源代码

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/frappe/skills --skill deep-app-audit

安装后在 Claude Code 中输入/deep-app-audit确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

agents.example.json
prompts/
check.md
context.md
report.md
scan.md
verify.md
quality/
_conventions.md
quality/A-customization/
A01-no-monkey-patching.md
A02-override-class-calls-super.md
A03-extend-instead-of-override.md
A04-regional-override-first.md
A05-override-whitelisted-method-contract.md
A06-hook-the-transaction-not-the-ledger.md
A07-reuse-the-existing-doctype.md
A08-custom-fields-in-code.md
A09-fixtures-overwrite-the-site.md
A10-remove-customizations-on-uninstall.md
A11-declare-required-apps.md
A12-declare-versions-and-branches.md
A13-clean-bench-install.md
A14-dotted-paths-must-resolve.md
A15-scheduler-method-path-is-an-api.md
A16-prefix-custom-fieldnames.md
A17-keep-standard-field-constraints.md
A18-check-every-writer.md
A19-bound-the-custom-field-count.md
A20-server-side-rules-not-in-client-scripts.md
A22-app-logic-not-in-server-scripts.md
A24-hooks-py-is-static-data.md
A25-handler-signature-matches-call-site.md

详细介绍

深度应用审计

此技能从三个方面审计一个 Frappe 应用检出目录,并生成一份报告:

方面提示词ID 前缀
安全性security/ 的各区域目录中的范围S-A 到 S-L
安全性security/checks/ 中的检查项S-P
正确性quality/B-correctness/ 中的规则Q-B
定制化quality/A-customization/ 中的规则Q-A
定制化quality/checks/ 中的检查项Q-K

提示词的 ID 由其方面前缀和文件 ID 组成:security/A-authorization/A02-*.md 的 ID 是 S-A02。quality/mechanisms/ 是质量规则的背景资料,不是一组提示词。

两个方面的工作方式相同。security/_conventions.md 和 quality/_conventions.md 包含相同的章节,每个文件都是其所属方面的约定:方法、问题认定标准、严重性等级、验证步骤和问题格式。prompts/ 中的任务提示词对两个方面相同,并会引导每个 Agent 参照其所属方面的约定。

运行方式

一次运行分为两部分:

部分执行者步骤
设置:参数、目标、清单、semgrep、测试站点你,协调者1 到 4
任务:扫描、检查、验证、报告scripts/run_audit.py5

设置需要判断:每台机器上的 bench 都不一样,而且每个应用都有自己的参与者。因此由你来完成。任务的调度不需要判断,因此由脚本来完成。脚本会将每项任务作为一个 Agent CLI 的新进程启动,因此每项任务都有全新的上下文。看过扫描结果的验证器并不独立,而且单个上下文无法容纳 160 项扫描。

该脚本不依赖某一种模型或运行框架。agents.json 会将每种任务类型映射到一个命令;该命令可以是任何能够接收提示词、读取文件、运行命令并写入文件的 CLI。扫描任务和验证任务可以使用不同的模型。

脚本会:

  • 并行运行任务,并为每个工作进程分配一组测试用户
  • 在扫描文件生成后,将其验证任务加入队列;在数量限制内,按严重程度从高到低处理
  • 检查每个结果文件的格式;文件缺失或格式错误时,重新运行相应任务
  • 在每项任务后运行站点重置命令,并在站点停止响应时停止运行
  • 写入 run.json(每项任务的状态)和 summary.json(计数),并在任务完成后启动报告

每项任务都会将结果写入运行目录中的一个文件。结果文件有效的任务即视为完成,因此脚本再次启动时,会从已停止的位置继续运行。

不要自行审计应用,也不要自行启动任务 Agent。

1. 读取参数

参数是要审计的应用检出目录,后面可以用普通文字指定选项。未提供路径时,使用当前目录。

选项示例默认值
only:要运行的 ID 前缀S-A、Q-B05全部
skip:要跳过的 ID 前缀S-L、S-P05无
no checks:不运行全范围检查运行检查
no site:仅读取源代码创建站点
site:重用现有的临时站点myapp-audit.localhost创建一个站点
db root password:用于 bench new-site无
bench:bench 目录在检出目录上方查找
output:运行目录<target>/../<app>-deep-audit/
semgrep rules:frappe/semgrep-rules 的本地克隆~/src/semgrep-rules克隆到运行目录
no semgrep:不运行 semgrep 规则运行 semgrep
drop site:结束时删除所创建的站点保留以供排查
agents:Agent 命令文件~/audit-agents.json<run dir>/agents.json
jobs:同时运行的 Agent 数量416
max candidates:每次扫描的验证上限1515
max verifications:本次运行的验证上限600600
timeout:运行停止前的小时数128

“仅检查安全性”对应 only: S-。“仅检查正确性”对应 only: Q-B。“仅检查质量”对应 only: Q-。

2. 确认目标

确认该路径下包含一个 Frappe 应用:其中一个包内含有 hooks.py。如果没有,停止并告知用户。

用两三行告诉用户将运行哪些内容:应用、检查类别、是否创建测试站点、Agent CLI 和模型,以及 Agent 的大致数量。完整运行大约需要 160 个扫描和检查 Agent,外加每个候选项对应的一个 Agent。Agent 总数可能达到数百个。用户已提供路径和选项时,不要请求确认。执行命令即表示同意。

测试站点需要对 bench 具有写入权限。如果检出目录不在 bench 中,或者 bench 看起来是生产环境,请使用 no site 并说明原因。

3. 准备运行环境

请自行完成。不要更改应用检出目录中的任何文件。

  1. 将检出目录解析为绝对路径。记录应用包名称(包含 hooks.py 的目录)和短提交哈希,使用 git -C <target> rev-parse --short HEAD 获取。

  2. 以只读方式查找 bench。bench 目录包含 sites/、apps/ 和 Procfile。已安装的应用通常位于 <bench>/apps/<app>。从 apps/frappe/frappe/__init__.py 记录 Frappe 版本,并记录目标应用要求的应用(hooks.py、pyproject.toml 中的 required_apps)或导入的应用,以及它们在 bench 上各自的路径。

  3. 创建运行目录。如果其中已有 setup.json,则这是一次恢复运行:读取该文件,并跳到第一个尚未完成的步骤。

  4. 构建入口点清单:

    python <skill dir>/scripts/build_inventory.py <run dir>/inventory.json --root <target>
    

    此脚本为只读脚本。它不需要 bench、site 或数据库。如果 frappe 和目标应用所需的应用位于检出目录旁的 <bench>/apps 中,脚本还会加载它们的 DocType。对于位于其他目录中的每个此类应用,添加 --doctypes-from <path>。如果执行失败,记录错误。审计将继续,不使用该清单。

  5. 运行 Frappe semgrep 规则,除非用户指定了 no semgrep。这些规则是质量规则中 semgrep 字段的来源,因此在这里运行一次,不要在每次扫描中重复运行。

    1. 获取 semgrep。如果 PATH 中有 semgrep,则使用它。否则,如果 PATH 中有 uv,运行 uv tool install semgrep,并以 uv tool run semgrep 的形式调用 semgrep,因为 uv 工具目录不一定在 PATH 中。不要以其他方式安装 uv 或 pip 包。

    2. 获取规则。如果指定了 semgrep rules 选项,则按原样使用该克隆。否则,如果 <run dir>/semgrep-rules 不存在,则克隆: git clone --depth 1 https://github.com/frappe/semgrep-rules <run dir>/semgrep-rules。 将克隆放在运行目录中,可确保一次运行中的规则固定,即使运行恢复时也如此。记录克隆的短提交哈希。

    3. 扫描检出目录,并为每个匹配项提供裸规则 ID。Semgrep 会在规则 ID 前加上规则目录的路径,因此请移除最后一个 . 之前的所有内容:

      <semgrep> scan --config <rules>/rules --json --metrics=off --quiet --output <run dir>/semgrep-raw.json <target>
      jq --arg t "<target>/" '[.results[] | {rule: (.check_id | split(".") | last),
        file: (.path | ltrimstr($t)), line: .start.line, message: .extra.message}]' \
        <run dir>/semgrep-raw.json > <run dir>/semgrep.json
      

    如果上述任一步骤失败,记录错误。审计将继续,不使用 semgrep。

  6. 列出提示词。包含上表所列目录中的每个 *.md 文件。排除文件名以 _ 开头的文件。对 ID 应用 only 和 skip,并排除带有 no checks 的检查项。

  7. 写入 <run dir>/setup.json。notes 为可选项:记录所有 Agent 都需要了解的应用事实,例如与 DocType 名称不同的产品名称。

    {
      "target": "/abs/path/to/app", "app": "myapp", "commit": "abc1234",
      "bench": "/abs/path/to/bench", "frameworkVersion": "16.0.0-dev",
      "dependencies": [{"app": "erpnext", "path": "/abs/path/to/bench/apps/erpnext"}],
      "inventory": "/abs/run/dir/inventory.json", "inventoryError": null,
      "semgrep": "/abs/run/dir/semgrep.json", "semgrepRules": "/abs/run/dir/semgrep-rules",
      "semgrepRulesCommit": "def5678", "semgrepError": null,
      "scans": ["S-A01", "Q-B05"], "checks": ["S-P01", "Q-K01"],
      "notes": "In the UI, the DocType Sales Invoice is called Bill."
    }
    
  8. 获取 Agent 命令。如果指定了 agents 选项,则使用该文件。否则,如果 <run dir>/agents.json 不存在,将 <skill dir>/agents.example.json 复制到该路径;如果文件中有适用于你所用工具框架的配置,则将每个角色设置为你自己的工具框架 CLI。如果文件中没有适用于你所用工具框架的配置,或者你不知道自己运行的是哪个 CLI,请询问用户。配置允许使用的工具就是每个任务 Agent 可使用的工具,因此不要扩大权限。

4. 启动测试 site

使用 no site 跳过此步骤。请自行完成,或将以下步骤交给一个 Agent。site 可让验证者发送真实请求。没有 site 时,审计只会读取源代码。

  1. 在 bench 目录中运行 bench --version,确认 bench 是否可用。如果没有 bench,则继续,但不使用站点。不要安装 bench 或数据库服务器,也不要更改机器的配置。

  2. 获取一个站点。使用 site 选项时,重用该站点。先确认它不是生产站点:如果其中存有真实数据,则停止,并继续但不使用站点。否则,站点名称为 <app>-audit.localhost。如果存在同名站点,则重用它:该名称由本次审计专用。否则创建该站点:

    bench new-site <site> --admin-password audit-admin-pw [--db-root-password <pw>]
    

    命令绝不能停下来等待输入。如果命令失败,记录错误并继续,但不使用站点。绝不要创建、更改、删除或迁移其他站点。

  3. 安装目标应用所需的应用,然后安装目标应用: bench --site <site> install-app <app>。

  4. 创建测试用户。每个运行工作进程都有自己的一组用户,这样任务就能更改自己的用户,且任务后的重置不会影响其他任务。创建 jobs 组,编号从 0 开始。第 k 组包含以下用户,每个用户的密码均为 audit-user-pw,enabled = 1,且不需要完成引导流程或重置密码:

    • milkshake-s<k>[email protected]:Website User,不赋予其他角色
    • milkshake-s<k>[email protected]:普通 System User,不赋予其他角色。如果框架根据角色推导用户类型,则没有 desk 角色的用户属于 Website User。然后创建一个名为 milkshake Desk User 的角色,赋予 desk 访问权限但不授予任何权限,并将该角色分配给此用户。
    • milkshake-s<k>[email protected]:System User 和 System Manager,用于对比管理员已有的权限
    • 对于应用定义的每个角色(即应用 DocType、页面和报表权限中出现的、框架未定义的角色):创建两个用户, milkshake-s<k>-<role slug>[email protected] 和 [email protected],且每个用户仅有该角色。同一角色的两个用户可让验证者测试其访问另一用户记录的权限。

    记录每个用户保存后的角色。应用逻辑可能会添加角色。

  5. 编写重置脚本 <run dir>/reset_site.py,并记录运行该脚本的命令。每个任务完成后,运行程序都会调用此命令,并将 {slot} 替换为组编号。脚本会将该组的每个用户恢复到记录的状态:角色(精确一致)、enabled = 1、用户类型和密码。脚本还会再次将 Administrator 的密码设为 audit-admin-pw。脚本不做其他任何更改,且在几秒内完成,不使用 bench console。例如,可使用 <bench>/sites 中的 bench Python 运行脚本,并针对站点调用 frappe.init 和 frappe.connect。对每组各运行一次该命令,并确认退出码为 0。

  6. 在后台提供站点服务:在 bench 目录中运行 bench serve --port 8199。如果站点需要 Host 标头,则记录该标头。

  7. 记录会影响控件行为的配置。读取 sites/common_site_config.json 和站点的 site_config.json。至少记录 ignore_csrf、developer_mode、allow_tests、server_script_enabled、disable_website_cache、maintenance_mode,以及每个 *_disabled 或 allow_* 键和值。对于会禁用或放宽控件的键,将其标记为 weakens: true,并用一行说明其影响。设置了 ignore_csrf: 1 的 bench 会接受生产环境会拒绝的跨站请求,因此该环境返回 200 不能证明生产环境也会接受。

  8. 证明站点正常运行:

    • curl -sS -o /dev/null -w '%{http_code}' <base url>/api/method/ping 返回 200
    • 使用 [email protected] 通过 /api/method/login 登录,返回 200 并设置 cookie
    • bench --site <site> list-apps 显示该应用 检查失败时,不要使用站点。一个勉强可用的站点比没有站点更糟,因为验证者会把它的错误视为证据。
  9. 写入 <run dir>/site.json。slots 按集合顺序,为每个集合保存一个用户列表。 notes 保存每个 Agent 都必须了解的站点信息:例如,站点初始时没有记录。

    {
      "ready": true, "site": "myapp-audit.localhost", "baseUrl": "http://localhost:8199",
      "hostHeader": "myapp-audit.localhost",
      "bench": "/abs/path/to/bench", "created": true, "apps": ["frappe", "myapp"],
      "adminPassword": "audit-admin-pw",
      "slots": [[{"email": "[email protected]", "password": "audit-user-pw", "actor": "plain System User", "roles": ["milkshake Desk User"]}]],
      "resetCommand": "cd /abs/path/to/bench/sites && ../env/bin/python /abs/run/dir/reset_site.py {slot}",
      "notes": "The site starts with no records.",
      "config": [{"key": "ignore_csrf", "value": "1", "weakens": true, "effect": "CSRF is not checked"}]
    }
    

    如果没有站点,写入 {"ready": false, "reason": "..."}。

5. 运行任务

从技能目录启动脚本,使其与运行环境分离,并将输出发送到 <run dir>/run.log:

mkdir -p <run dir>/tmp && [ -e <run dir>/tmp/last-check ] || touch <run dir>/tmp/last-check
setsid nohup python <skill dir>/scripts/run_audit.py <run dir> --agents <agents file> \
  --jobs <jobs> --timeout <timeout> \
  --max-candidates <max candidates> --max-verifications <max verifications> \
  [--only <prefix> ...] [--skip <prefix> ...] >> <run dir>/run.log 2>&1 < /dev/null &

不要将脚本作为运行环境的后台命令运行。运行环境通常会在固定时间后停止后台命令,例如 2 小时,而一次运行所需的时间更长。脚本有自己的时间限制 --timeout。到达该时限后,它会停止 Agent 并退出。

先使用 --dry-run 启动一次。试运行不会启动 Agent。它会显示任务列表、每种任务的命令,以及每种任务的一份完整提示词。阅读其中一份提示词,并确认站点、用户和路径都正确。

如果运行环境无法启动分离进程,或不允许启动 Agent 进程,请把命令提供给用户,然后停止。用户运行命令,并在运行完成后向你询问其余步骤。

脚本运行期间,不要启动 Agent。脚本会为每个已结束的任务打印一行内容。<run dir>/run.json 包含每个任务的状态,而 <run dir>/logs/<task>.log 包含每个 Agent 的输出。如果脚本在结束前停止,请使用相同参数重新启动。它会继续处理没有有效结果的任务。

告知用户进度

一次运行需要数小时。如果没有更新,用户无法判断是运行较慢还是已停止。因此,请每隔 10 到 15 分钟检查一次进度,直到脚本退出。使用计时器或由你的运行环境唤醒。运行环境不会告知你后台脚本何时退出,因此每次唤醒时都必须启动下一次检查。如果运行环境不具备这两种能力,请告诉用户如何自行查看运行情况(tail -f <run dir>/run.log),并等待脚本退出。

每次检查都在运行目录中执行以下命令:

kill -0 "$(cat run.pid)" 2>/dev/null && echo "script: running" || echo "script: exited"
grep -E '^(ok|FAIL) ' run.log | tail -n 1
grep -vE '^(ok|FAIL) ' run.log | tail -n 5
jq -r '"complete: \([.tasks[] | select(.ok)] | length), failed: \(.failed | length), verifications left in the cap: \(.verificationsLeft), site down: \(.siteDown), timed out: \(.timedOut)"' run.json
jq -r '"now: \(now | strflocaltime("%F %T")), updated: \(.updated), last progress: \(.lastProgress), deadline: \(.deadline)", (.running | to_entries[] | "attempt running since \(.value): \(.key)")' run.json
find verdicts -name '*.json' -newer tmp/last-check -print0 | xargs -0r jq -r \
  'select(.verdict == "confirmed") | "\(.severity) \(input_filename | split("/")[1]): \(.finding.title)"'
touch tmp/last-check

最后两条命令只显示上次检查后确认的发现,因此不会重复告知同一条发现,重启后也一样。

用两到三行告知用户:

  • 任务计数:已完成、排队中、正在运行和失败
  • 每条新确认的发现单独占一行,注明严重程度和 ID。并说明仍需由人员确认。
  • 问题:任务失败、重置失败或网站停止响应

只读取这些输出。不要打开扫描文件和判定文件,也不要评判发现;报告任务会处理这些。

每次检查还必须确认运行仍在取得进展:

  • 脚本已退出。 run.log 的最后一行是 === exit <code>。如果没有这一行,脚本就崩溃或被停止了。告知用户 run.log 的最后几行,并使用相同参数重新启动脚本。
  • 脚本没有响应。 脚本每分钟更新一次 run.json。如果 updated 超过 5 分钟未更新,说明脚本卡住了。使用 kill "$(cat run.pid)" 停止它,这也会停止它的 Agent,然后重新启动。
  • 没有任务结束。 脚本会在 Agent 配置文件中的 timeout 时限到达时停止 Agent;agents.example.json 中的时限为 1 小时。last progress 表示上一次 Agent 尝试结束的时间,或脚本启动的时间。因此,如果 last progress,或 running 中某次尝试的时间,早于该时限加 15 分钟,则说明运行卡住了。按上述方式停止并重新启动。 如果同一次停止发生两次,就停止运行并告知用户。

重启后会继续处理没有有效结果的任务,不会重复执行已完成的任务。

如果测试网站停止响应,脚本就会停止,因为验证器会将已失效网站的错误视为证据。重新启动网站,使用步骤 4.8 确认网站正常,然后重新启动脚本。

所有任务完成时,脚本以 0 退出。以 1 退出时,run.json 会列出失败的任务。报告仍会生成,并会列出这些任务。

当 run.json 中有 "timedOut": true 时,说明运行已达到时限,且报告未运行。告知用户已完成的任务数量,并询问是否继续。要继续,请使用相同参数重新启动脚本。每次启动都会获得完整的 --timeout 时限。

仅当运行创建了该网站时,才使用 drop site:停止 bench serve,并运行 bench drop-site <site> --force。

6. 告知用户

读取 <run dir>/summary.json,以及 <run dir>/report.md 的标题和汇总表。告知用户:

  • 报告和运行目录的路径
  • summary.json 中各赛道和严重程度对应的已确认候选数量,以及已证伪和不确定的数量。报告会按根本原因合并候选,因此其中的发现较少。请读取报告标题中的去重后数量。
  • 报告汇总表中 Critical 和 High 级别发现的标题,每行一个
  • 如果运行保留了测试网站,则告知用户网站名称,以便复现发现
  • 运行中遗漏的任何内容:失败的任务、因数量上限而未验证的候选、标记为 not applicable 的检查、失败的 semgrep 步骤

不要在回复中复制报告内容。不要提出修复建议。

如果报告任务失败,运行目录中仍保留所有结果。告知用户这一点,并提出重新启动脚本;它只会运行报告。

提示词

任务提示词模板位于 prompts/ 中。脚本会使用 setup.json 和 site.json 填充这些模板,并将填充后的提示词分别保存到 <run dir>/prompts/<task>.md。

模板用途
context.md每个提示词的开头部分:路径、基准、清单、semgrep、站点、用户、规则
scan.md一个范围或规则,并写入 scans/<id>.json
verify.md一个候选项,并写入 verdicts/<id>/<n>.json
check.md一项针对整个表面的检查,并写入 checks/<id>.json
report.md报告,并写入 report.md

{{name}} 是一个值。{{#if name}}...{{else}}...{{/if}} 会在值已设置时保留其中一个部分。 如果模板引用了脚本不认识的值,脚本会在开始时停止。更改模板中的结果格式时,也要一并更改 run_audit.py 中 validate() 的检查。

警告

输出由 Agent 生成。任何人采取行动前,必须由人员确认每一项发现。请注意误报和遗漏。报告中未发现问题,并不能证明应用是安全或正确的。

评论

支持 Markdown