- 网络安全
- 教程
【免费下载链接】awesome-appsec
A curated list of resources for learning about application security
本文以 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 的指引,向该列表贡献资源的完整流程分为四步:
- 克隆仓库:将仓库 clone 到本地(仓库地址为
https://gitcode.com/gh_mirrors/aw/awesome-appsec),保证基于最新代码工作; - 创建 JSON 文件:在
data/的合适分类目录下新建 JSON 文件,写入资源信息; - 提交变更:
git commit你的改动; - 发送 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 明确规定以下三个字段是必须的:
| 字段 | 类型 | 含义 | 示例 |
|---|---|---|---|
name | string | 资源名称,将作为 README 中的条目标题(以及目录锚点文本) | "How to Safely Generate a Random Number" |
remark | string | 一句话简介,说明该资源的价值与适用场景,将作为条目正文 | "Advice on cryptographically secure pseudo-random number generators." |
url | string | 资源的原始链接,将渲染为 Markdown 链接 | "http://sockpuppet.org/blog/2014/02/25/safely-generate-random-numbers/" |
3.2 可选字段(Optional)
模板中另外两个字段虽非必填,但对 README 的呈现有直接影响:
date:资源发布日期,格式为YYYY-MM-DD。编译器会用DateTime解析它,在条目标题后追加(年份),并在正文首行输出Released信息。省略该字段的条目(如工具类资源)将不会显示年份与发布日期。free:布尔值,标注资源是否免费。设为false时,编译器会在条目标题前插入[](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。
贡献者在使用目录结构时有两点注意:
- 遵循既有分类习惯:能在现有分类下新增资源,就不要另起炉灶创建含义重叠的目录;
- 保持整洁:原文用 "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()决定,其处理规则与前面字段表一一对应:
free字段:若存在且为false,在标题前追加[](https://link.gitcode.com/i/527ba279d4951c7c374986c1da7b1c7d);url字段:存在时渲染为name链接,否则仅输出纯文本标题;date字段:用new \DateTime($fd['date'])解析,标题后追加(Y)(四位年份),正文首行输出**Released**: F j, Y(如February 25, 2014)格式的发布日期;remark字段:作为条目正文输出,缺失时输出空行。
值得留意的一个细节:编译后 README 中[](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 效果是否符合预期。
七、贡献实践清单
综合文档与源码,一份合规且高质量的贡献可以归纳为以下清单:
- 数据而非排版:只在
data/下新增/修改 JSON,不直接改 README 正文(README 是生成产物,手动修改会被下次编译覆盖); - 三字段齐全:
name、remark、url必填,remark用一句话讲清资源价值,避免空泛; - 善用可选字段:付费资源务必加
"free": false(自动附带 nonfree 图标),能确定发布年份的尽量补"date"(自动生成年份与 Released 信息); - 目录即分类:将文件放到语义正确的分类目录,利用
NN-数字前缀控制排序;若确实需要新分类,保证目录命名清晰、不与其他分类语义重叠; - 保持整洁:遵循现有命名习惯(
NNNN-简短英文名.json),避免制造混乱结构; - 本地预览(可选):运行
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
相关推荐
如何安全发布?beautify-github-readme 的授权边界、不伪造原则与 showcase 贡献工作流
如何安全发布?beautify github readme 的授权边界、不伪造原则与 showcase 贡献工作流 beautify github readme
Rust编译器开发指南:探索Rust编译器的工作原理与贡献方式
Rust编译器开发指南:探索Rust编译器的工作原理与贡献方式 1. 项目基础介绍与主要编程语言 本项目是Rust编译器(rustc)的开发指南,旨在帮助开发者
文档/教程free-programming-books 全解析:多语种免费编程学习资源库的资源分类体系、站点构建与贡献工作流
free programming books 全解析:多语种免费编程学习资源库的资源分类体系、站点构建与贡献工作流 本文基于仓库入口文档 README.md h
文档教程知识库
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考