☰
向 Awesome AppSec 贡献应用安全学习资源:data/ 目录驱动的 JSON 工作流与 README 编译原理
2026/10/15 12:37:36 网站建设 项目流程
  • 网络安全
  • 教程

【免费下载链接】awesome-appsec

A curated list of resources for learning about application security

项目地址:https://gitcode.com/gh_mirrors/aw/awesome-appsec
点击查看免费下载

本文以 CONTRIBUTING.md 为主线,系统讲解 awesome-appsec 这个"应用安全学习资源精选列表"的贡献机制:它不靠人工维护一个巨型 README,而是通过data/目录下的结构化 JSON 文件驱动,由 PHP 编译器自动生成 README。读完本文,你将掌握创建 JSON 资源条目(必填字段与可选字段)、用目录层级定义分类结构、本地运行src/compiler.php预览编译结果,以及提交 Pull Request 的完整实战流程,并深入理解编译器的源码实现细节。

一、项目概览:为什么 README 需要"编译"而非手写

awesome-appsec 是一份面向应用安全学习的精选资源清单,收录图书、网站、博客文章与自测题目,覆盖 PHP、Node.js、Java、Python、Go、C 等主流语言与技术栈。项目采用了一种典型的"数据与展示分离"架构:

  • 数据层:data/目录存放大量 JSON 文件,每个文件描述一条资源(名称、备注、URL 等);
  • 展示层:README.md是最终对外呈现的 Markdown 文档,包含按分类组织的目录(Table of Contents)与各条目的完整描述。

正如 CONTRIBUTING.md 开篇明确指出的:"This README file is dynamically generated from thedata/directory."(本 README 文件由data/目录动态生成)。也就是说,贡献者不需要(也不应该)直接编辑 README 正文,只需维护好 JSON 数据文件,剩下的排版工作全部交给编译器完成。

打开 README.md 可以看到,其开头段落(项目简介、维护方说明、Contributing 指引)来自 data/header.md,而"Application Security Learning Resources"之后的所有内容,包括按 General / AWS Lambda / Android / C / C++ / C Sharp / Clojure / Go / Java / Node.js / PHP / Perl / Python / Ruby 等语言与领域划分的章节,全部由 src/compiler.php 依据data/目录递归生成。

二、快速上手:克隆仓库与总体流程

按照 CONTRIBUTING.md 的指引,向该列表贡献资源的完整流程分为四步:

  1. 克隆仓库:将仓库 clone 到本地(仓库地址为https://gitcode.com/gh_mirrors/aw/awesome-appsec),保证基于最新代码工作;
  2. 创建 JSON 文件:在data/的合适分类目录下新建 JSON 文件,写入资源信息;
  3. 提交变更:git commit你的改动;
  4. 发送 Pull Request:向仓库提交 PR,等待维护者合入。

提交后你无需担心 README 的生成问题——维护者会替你从data/目录下的所有 JSON 文件重新编译 README。当然,如果你想在提交前预览改动效果,也可以在自己的机器上运行 src/compiler.php,不过这一步并非必须:"It only takes a second of our time to build a new README."(维护者重建一份 README 只需一秒钟)。

三、创建 JSON 资源条目:模板与字段详解

创建新条目的唯一硬性要求是"新建一个包含所需信息的 JSON 文件",官方建议参考 template/full.json。该模板是仓库中最完整的字段样例,内容如下:

{ "date": "1970-01-31", "free": true, "name": "", "remark": "Lorem ipsum", "url": "https://example.com" }

3.1 必填字段(Required)

CONTRIBUTING.md 明确规定以下三个字段是必须的:

字段类型含义示例
namestring资源名称,将作为 README 中的条目标题(以及目录锚点文本)"How to Safely Generate a Random Number"
remarkstring一句话简介,说明该资源的价值与适用场景,将作为条目正文"Advice on cryptographically secure pseudo-random number generators."
urlstring资源的原始链接,将渲染为 Markdown 链接"http://sockpuppet.org/blog/2014/02/25/safely-generate-random-numbers/"

3.2 可选字段(Optional)

模板中另外两个字段虽非必填,但对 README 的呈现有直接影响:

  • date:资源发布日期,格式为YYYY-MM-DD。编译器会用DateTime解析它,在条目标题后追加(年份),并在正文首行输出Released信息。省略该字段的条目(如工具类资源)将不会显示年份与发布日期。
  • free:布尔值,标注资源是否免费。设为false时,编译器会在条目标题前插入[![nonfree](https://link.gitcode.com/i/86260b75c2182fa1f4ca3ff77cc1bf27)](https://link.gitcode.com/i/527ba279d4951c7c374986c1da7b1c7d)图标(对应 img/nonfree.png),标记为付费/非自由资源;设为true或省略则不显示任何标记。

3.3 真实数据样例对比

仓库中的数据文件恰好覆盖了上述三种形态,可作为编写 JSON 时的直接参考:

带日期、免费的资源——data/00-general/articles/0001-safely-random-number.json:

{ "date": "2014-02-25", "name": "How to Safely Generate a Random Number", "remark": "Advice on cryptographically secure pseudo-random number generators.", "url": "http://sockpuppet.org/blog/2014/02/25/safely-generate-random-numbers/" }

标记为付费的资源——data/00-general/books/0001-wahh.json:

{ "date": "2011-09-27", "free": false, "name": "Web Application Hacker's Handbook", "remark": "Great introduction to Web Application Security; though slightly dated.", "url": "http://mdsec.net/wahh" }

无日期、免费的工具资源——data/00-general/websites/02-tools/0001-ssl-labs.json:

{ "name": "Qualys SSL Labs", "remark": "The infamous suite of SSL and TLS tools.", "url": "https://www.ssllabs.com/" }

四、用目录层级定义分类结构

CONTRIBUTING.md 特别强调:"Any directories you create will be used to define the structure of your new links."(你创建的任何目录都将用于定义新链接的结构)。这意味着分类体系完全由data/下的目录树决定,而非硬编码在编译器里。

以仓库现有结构为例,data/顶层按语言/领域划分(00-general、AWS-Lambda、Android、C、Go、Java、Node.js、PHP、Perl、Python、Ruby等),再逐层细分。比如 PHP 分类下的完整层级:

data/PHP/ ├── articles/ → 文章 ├── books-and-ebooks/ → 图书与电子书 ├── useful-libraries/ → 实用库 ├── training/ → 培训 └── websites/ ├── blogs/ → 博客 ├── mailing-lists/ → 邮件列表 └── 0001-websec.json

目录名中形如0001-、00-的数字前缀是排序手段——目录遍历时按文件名顺序排列,数字前缀可让同目录下的子目录与文件按预期次序呈现。从 src/Util.php 的bodyTitle()方法可以看到,编译器在生成章节标题时会先剥离NN-前缀、再把连字符-替换为空格,例如目录00-general会渲染为章节标题General,AWS-Lambda渲染为AWS Lambda。

贡献者在使用目录结构时有两点注意:

  1. 遵循既有分类习惯:能在现有分类下新增资源,就不要另起炉灶创建含义重叠的目录;
  2. 保持整洁:原文用 "Please try not to make things too messy."(尽量不要把事情搞得太乱)明确提醒,维护者会不定期清理列表,结构混乱的贡献可能被整理或驳回。

五、本地编译 README:compiler.php 的源码级原理

如果你希望提交前就在本地看到 README 的完整效果,可以直接运行编译器。编译命令非常简单:

php src/compiler.php

运行后它会在仓库根目录重新生成README.md。由于该命令依赖 PHP 环境,请先确认机器上已安装 PHP(任何支持DateTime与json_decode的 PHP 5.2+ 版本均可)。

5.1 编译器主流程

src/compiler.php 的入口逻辑只有短短几行:

$compiled = \file_get_contents(DATADIR.'/header.md')."\n\n"; $compiled .= \implode("\n", \ParagonIE\AwesomeAppsec\Util::compile(DATADIR)); \file_put_contents(dirname(__DIR__).'/README.md', $compiled);
  • 第 1 行:读取 data/header.md 作为 README 的固定头部(项目简介、维护方信息、Contributing 指引);
  • 第 2 行:调用Util::compile()递归遍历data/目录,收集目录(Table of Contents)与正文两部分;
  • 第 3 行:将两者拼接后写回根目录的README.md。

从源码结构看,该脚本被设计为幂等且可重复执行:每次运行都会用data/的最新状态整体覆盖 README,因此无论执行多少次,结果都与数据目录完全一致。

5.2 递归遍历:目录即章节、JSON 即条目

核心逻辑位于 src/Util.php 的Util::compile()。它使用glob($base.'/*')列出当前目录的所有子项,逐一判断:

  • 若是目录:生成目录标题(写入 TOC)与章节标题(写入正文),然后以depth + 1递归进入;
  • 若是JSON 文件(通过#/([^/]+)\.json$#正则识别):生成目录条目与正文条目;
  • 若是其他文件(如header.md、.gitkeep等):直接跳过。

depth参数决定标题的 Markdown 级别:第一层目录生成#级标题,第二层生成##,依此类推。这正好解释了 README 中# General、## Articles、### [某篇文章]的层级来源。

5.3 条目渲染:jsonMarker() 的字段处理逻辑

每条 JSON 数据如何变成 README 中的一段内容,由 src/Util.php 的jsonMarker()决定,其处理规则与前面字段表一一对应:

  1. free字段:若存在且为false,在标题前追加[![nonfree](https://link.gitcode.com/i/86260b75c2182fa1f4ca3ff77cc1bf27)](https://link.gitcode.com/i/527ba279d4951c7c374986c1da7b1c7d);
  2. url字段:存在时渲染为name链接,否则仅输出纯文本标题;
  3. date字段:用new \DateTime($fd['date'])解析,标题后追加(Y)(四位年份),正文首行输出**Released**: F j, Y(如February 25, 2014)格式的发布日期;
  4. remark字段:作为条目正文输出,缺失时输出空行。

值得留意的一个细节:编译后 README 中[![nonfree](https://link.gitcode.com/i/86260b75c2182fa1f4ca3ff77cc1bf27)](https://link.gitcode.com/i/527ba279d4951c7c374986c1da7b1c7d)图标使用的是仓库相对路径img/nonfree.png,因为编译出的 README 位于仓库根目录,该相对路径恰好指向 img/nonfree.png 图标文件。

5.4 目录(TOC)生成与 slug 去重

README 顶部的完整目录由tocDirTitle()与tocFileTitle()两个方法协作生成(src/Util.php):

  • 目录标题与文件标题都按depth用两个空格缩进,形成嵌套列表;
  • 每个锚点由makeSlug()生成:先将标题转为小写、URL 编码,再经slugEscape()把标点符号替换为连字符-,最后压缩连续连字符并去除首尾的-(src/Util.php);
  • 由于同一资源名可能跨语言分类重复(例如多个分类下都有 SEI CERT 编码标准),makeSlug()内部用静态数组$slugs记录已生成的 slug,遇到重复时自动追加-1、-2等序号后缀,保证每个锚点唯一。

这也是为什么 README 中会出现#c-1、#c-2、#books-and-ebooks-6这类锚点——它们正是 slug 去重机制的产物,确保目录链接不会指向错误位置。

六、提交与 Pull Request:注意事项

完成 JSON 创建并本地验证后,按常规 Git 流程提交即可:

git add data/你新增的JSON文件 git commit -m "Add <资源名称> to <分类>" git push origin 你的分支

随后在仓库页面发起 Pull Request。关于提交环节,CONTRIBUTING.md 的原始指引是"Commit your changes, send a pull request.",除此之外没有强制要求(例如不要求附上本地编译产物、不要求通过 CI 检查)。不过为了让维护者快速确认,建议在 PR 描述中说明:新增的资源属于哪个分类、为什么值得收录,以及(如果本地编译过)编译后的 README 效果是否符合预期。

七、贡献实践清单

综合文档与源码,一份合规且高质量的贡献可以归纳为以下清单:

  1. 数据而非排版:只在data/下新增/修改 JSON,不直接改 README 正文(README 是生成产物,手动修改会被下次编译覆盖);
  2. 三字段齐全:name、remark、url必填,remark用一句话讲清资源价值,避免空泛;
  3. 善用可选字段:付费资源务必加"free": false(自动附带 nonfree 图标),能确定发布年份的尽量补"date"(自动生成年份与 Released 信息);
  4. 目录即分类:将文件放到语义正确的分类目录,利用NN-数字前缀控制排序;若确实需要新分类,保证目录命名清晰、不与其他分类语义重叠;
  5. 保持整洁:遵循现有命名习惯(NNNN-简短英文名.json),避免制造混乱结构;
  6. 本地预览(可选):运行php src/compiler.php检查 README 渲染结果,确认标题、锚点、图标符合预期后再提交 PR。

八、总结:一条数据驱动的贡献闭环

awesome-appsec 的贡献体系是一个完整闭环:贡献者只负责在data/写入结构化 JSON →Util::compile()递归遍历目录与文件 →jsonMarker()按字段规则渲染条目 → 拼接header.md后写回README.md。理解这条链路后,贡献者既能写出格式正确的数据文件,也能在遇到渲染异常时(如锚点冲突、日期格式错误、nonfree 图标缺失)直接定位到 src/Util.php 中的对应处理逻辑。这套"数据 + 编译器"的模式也正是维护者能以极低成本持续维护大型精选列表的原因——每次合入 PR 后,重新编译一遍即可让整个 README 保持同步。

  • 网络安全
  • 教程

【免费下载链接】awesome-appsec

A curated list of resources for learning about application security

项目地址:https://gitcode.com/gh_mirrors/aw/awesome-appsec
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询