
Username.md 这个项目简单说就是想让你拥有一个属于自己的、经过签名认证、并且能被 AI Agent 直接解析的身份页。它不依赖某个社交平台也不把身份数据锁在数据库里而是用一页 Markdown 文件来表达你是谁、你的公钥是哪个、你允许 Agent 怎么验证你并联系你。如果你关注去中心化身份、个人数据主权或者正在做 Agent 相关的信息抓取和信任判断这个方向很值得拆一遍。它和传统个人主页最大的区别是两个关键词signed 和 agent-readable。前者解决“内容有没有被篡改”后者解决“机器能不能低成本地消费这份身份信息”。下面我会按实际落地顺序从页面结构、签名流程、Agent 解析、托管发布到排查边界完整过一遍。其中有些做法是项目定位直接指向的有些是通用实践补充落地时根据你的场景调整即可。1. 先搞懂它解决的“身份页”问题1.1 身份页本身不新鲜新鲜的是信任机制个人主页这个概念已经存在二十年了。访客、留言板、个人介绍、链接收集本质上都是身份页。问题在于传统身份页有几个长期没有解决的痛点内容托管在平台上平台删号或者关站身份信息就消失了。内容无法验证作者任何人都可以复制一份别人的主页改个名字然后冒充。跨平台身份很难关联GitHub 上的你、邮箱背后的你、博客里的你是不是同一个人没有统一答案。Username.md 的定位是把身份信息放回你控制的域名或存储空间用标准的 Markdown 文件承载再用密码学签名给这份文件盖章。这样身份页的“所有权”从平台回到了个人手里而“真实性”从平台审核变成了可验证的签名机制。1.2 signed 解决的是完整性和来源问题签名解决的核心问题不是“你是谁”而是“这份内容有没有被改过”。当你用私钥对 username.md 的内容生成签名后任何人只要拿到你的公钥就能验证这份文件是否完整、是否由持有对应私钥的人发布。这和 HTTPS 的证书机制思路类似但更轻量。Username.md 并不强制你接入某个 CA而是采用“公钥即身份”的模式在我认可你的公钥之前这份签名只能证明内容自洽在我认可公钥之后签名才成为可信的来源标记。这里要提前说清楚一个边界签名能防篡改能防“内容被中间人换掉”但不能单独证明“页面背后一定是个真人”。身份真实性还需要别的信任锚比如你的域名所有权、社交账号互链、或者你在其他场景下公开使用同一把公钥。这一点后面专门展开。1.3 agent-readable为什么机器可读突然变重要了现在的 AI Agent 越来越多地被要求去网上获取信息比如查一个人是谁、联系方式是什么、有没有公开声明。传统网页是为人类阅读设计的充斥着导航、广告、动态脚本和无关内容Agent 抓回来的页面噪声很大解析成本很高。Username.md 想解决的就是这个问题用 Markdown 这种通用纯文本格式承载身份信息配合结构化前部字段让 Agent 可以像读配置一样读取身份页。机器先验签再提取字段然后决定是否把这份身份信息纳入自己的判断依据。所以它并不是要做成一个“更好看的个人主页”而是要做成一个“人和 Agent 都能消费的身份文件”。这个定位直接影响后续所有的格式选择和签名设计。2. 一个 Username.md 身份页应该包含什么2.1 文件命名与位置约定从项目名来看核心文件就是username.md其中 username 是你的身份标识。实际部署时有两种常见方式放在你自己的域名根目录比如example.com/username.md。放在域名下的约定路径比如example.com/.well-known/username.md表示这是一个机器可读取的元数据文件。如果你没有独立域名也可以放在静态托管平台生成的页面上比如 GitHub Pages 项目页。关键是这个 URL 要稳定、要公开、要能长期访问。身份页最怕的就是 URL 经常变Agent 和人都需要一个能收藏的稳定地址。2.2 人机两读的格式设计Markdown 本身是给人读的它天然支持标题、列表、段落阅读体验很好。但机器解析纯 Markdown 并不容易因为它没有强制的结构。所以实践上通常会在 Markdown 文件顶部加一段 YAML frontmatter也就是被---包围的键值对区域。这种格式在 Jekyll、Hugo、Obsidian 等工具里已经很常见。Username.md 采用类似思路就可以复用大量现成解析器Agent 不需要从头写一套 Markdown 结构解析引擎。frontmatter 放机器字段正文放人类可读的介绍这样一份文件两种受众都覆盖。2.3 最小字段集应该有哪些根据“身份页 签名 Agent 可读”这三个目标我建议最少包含以下字段字段作用建议格式name显示名称普通字符串handle用户名标识小写字母数字可包含连字符updated内容更新时间ISO 8601如 2025-01-15T10:30:00Zkeys公钥信息OpenPGP 指纹或 SSH 公钥字符串links关联身份地址多个 URL如主页、GitHub、邮箱claims声明或职位信息简短文本列表signature签名入口签名文件的 URL 或内嵌签名串字段不要贪多。身份页不是简历不要塞几十个键值对。Agent 需要的是核心身份信息你暴露越多字段后续维护和防泄露压力就越大。2.4 一个最小示例下面是一个参考结构实际内容按你的身份信息替换--- name: 张三 handle: zhangsan updated: 2025-01-15T10:30:00Z keys: pgp: ABCDEF1234567890ABCDEF1234567890ABCDEF12 links: homepage: https://example.com github: https://github.com/zhangsan email: mailto:zhangsanexample.com claims: - 独立开发者 - 关注开放身份协议 signature: https://example.com/username.md.sig --- # 张三 你好我是张三一名独立开发者。 这个页面是我的身份页用于向人类访客和 AI Agent 提供经过签名的个人身份信息。 ## 联系我 - 邮箱zhangsanexample.com - GitHubhttps://github.com/zhangsan这里注意一点签名文件放在 frontmatter 里用 URL 引用或者直接把 base64 签名串塞进去两种都有人用。引用方式更干净文件本身不会因为签名变化而改动正文内嵌方式更自包含单文件就能完成验证但签名串会占用不少空间。对 Agent 来说两种都能处理只要解析逻辑一致。3. 签名与验证的具体流程3.1 选哪种签名工具不涉及某个固定实现的话常见选择有 OpenPGPGPG、SSH 密钥、Minisign 等。三者的差异可以参考下表工具上手难度生态普及度适合场景GPG/OpenPGP中等高邮件和软件签名常用完整身份签名、密钥链管理SSH 密钥低高开发者和服务器场景熟悉轻量验证适合技术人员Minisign低中等签名文件简单快速给文件签名和验证如果你是第一次做这件事我建议先用 SSH 密钥体验一遍签名和验证的闭环因为大多数开发者本地已经有 SSH 密钥不需要额外生成。如果后续要做长期身份管理再考虑引入 GPG 的密钥链机制。3.2 用 GPG 给 Markdown 文件签名的流程以 GPG 为例核心步骤是三条命令# 生成密钥如果已经存在可以跳过 gpg --full-generate-key # 对身份页生成独立的 ASCII 签名文件 gpg --detach-sign --armor --output username.md.sig username.md # 验证签名 gpg --verify username.md.sig username.md这里有一个非常重要却容易被忽略的点签名针对的是原始 Markdown 文件不是渲染后的 HTML也不是复制到剪贴板的文本。因为签名的本质是对文件字节做哈希和加密任何形式的格式转换、行尾替换、末尾追加换行都会导致验证失败。3.3 验证方应该按什么顺序校验Agent 或者人类访客拿到一份 username.md 后合理的验证顺序是下载username.md和签名文件username.md.sig。从页面字段中读取声明的公钥指纹或公钥内容。从公钥服务器或用户提供的公钥 URL 获取公钥并确认指纹一致。执行验签命令。验签通过后再信任页面里的字段内容。这个顺序不能乱。如果你先解析内容再验签解析器可能已经被污染如果你不核对公钥指纹就导入就可能用了攻击者提供的假公钥。验签不是“跑一下命令看是否成功”而是“确认公钥来源可信”和“确认签名有效”两件事必须同时成立。3.4 密钥更换与时间戳问题身份页是长期存在的密钥却可能因为泄漏、丢失或加密强度升级而更换。建议做法是页面里保留旧的公钥指纹形成一条“我曾用过哪些钥匙”的记录。新身份页用新私钥签名同时用旧私钥对新公钥做一次交叉签名让连续信任关系成立。在 keys 字段中标注每个公钥的起始和结束时间方便 Agent 判断当前应该信任哪把钥匙。时间戳方面如果环境允许可以考虑接入 RFC 3161 时间戳服务把“签名发生在某个时间点”固化下来。但这一步比较复杂普通身份页可以先不做。至少保证页面里的updated字段能反映最新的修改时间。注意密钥的私钥部分一定要离线保存不要放到云端笔记或代码仓库里。身份页签名私钥一旦泄露攻击者就能伪造你的身份页签名。4. Agent 怎么解析这份身份页4.1 只做到“可访问”远远不够很多身份信息其实是公开可访问的但 Agent 无法低成本使用。比如个人博客首页Agent 需要处理 HTML 标签、导航栏、脚本、样式还要从正文中猜测哪句话是身份声明。Username.md 把机器关注的字段集中到 frontmatter等于给 Agent 划好了重点。要做到这一点页面本身要满足几个条件通过 HTTPS 提供访问避免内容被中间人替换。返回的 Content-Type 至少是 text/plain 或 text/markdown不要是 application/json 包装过的。不要使用动态脚本渲染Agent 不会执行 JavaScript 去等一个异步加载的身份页。文件体积要小避免塞入大量无关内容。4.2 一个简单的 Python 解析示例下面是一段参考用的 Python 伪代码作用是把 frontmatter 解析成字典再根据签名结果决定是否信任。注意这段代码只是演示流程不是某个官方库的用法import hashlib import urllib.request import yaml def fetch_text(url): req urllib.request.Request(url, headers{User-Agent: your-agent}) with urllib.request.urlopen(req, timeout10) as resp: return resp.read().decode(utf-8) def parse_frontmatter(text): if not text.startswith(---): raise ValueError(missing frontmatter) parts text.split(---, 2) if len(parts) 3: raise ValueError(frontmatter not closed) return yaml.safe_load(parts[1]), parts[2] def verify_signature(public_key, content_bytes, sig_bytes): # 这里用你选择的签名库实现核心是 # 1. 用 public_key 校验 sig_bytes 是否与 content_bytes 匹配 # 2. 校验通过返回 True否则 False pass page_url https://example.com/username.md sig_url https://example.com/username.md.sig content fetch_text(page_url) sig fetch_text(sig_url) metadata, body parse_frontmatter(content) # 注意验签应该基于原始 content 字节而不是解析后的 body if verify_signature(metadata[keys][pgp], content.encode(utf-8), sig.encode(utf-8)): print(signature valid, name:, metadata.get(name)) else: print(signature invalid, discard content)这段示例说明了一个关键点验签必须在解析 frontmatter 前后保持一致。最安全的做法是先拿到原始字节立即验签验签通过后再做 YAML 解析。不要把解析后的内容拿来验签因为 YAML 解析可能改变了换行符或字段顺序。4.3 Agent 应该提取哪些字段并如何存证Agent 消费身份页时建议按三层存放结果原始层保存抓取到的 username.md 原文和签名文件确保后续可以重新验证。解析层保存 frontmatter 解析出来的键值对。信任层保存验签结果、抓取时间、公钥指纹、以及你决定信任公钥的依据。信任层是最容易被忽略的。很多 Agent 只保存了“身份信息”没有保存“为什么我信任这份身份信息”。一旦后续发现公钥有问题或者页面被篡改没有证据链就无法追踪当初的判断依据。4.4 Agent 侧常见解析坑YAML 解析器对 Tab 字符和缩进有严格要求前端字段中不要混用 Tab 和空格。公钥指纹如果带空格或冒号建议统一格式比如全大写、无分隔符或者明确标注格式。时间字段建议统一为 ISO 8601避免2025/1/15这种有歧义的写法。Agent 抓取时要设置超时和重试避免某个身份页长期无响应导致整个任务卡死。5. 托管、发布与持续更新5.1 托管方案对比身份页最终要放在一个稳定可达的 URL 上。几种常见托管路线的特点托管方式优势需要注意自有域名 静态服务器完全控制URL 稳定可配置缓存头需要自己维护服务器和 HTTPS 证书GitHub Pages免费支持 HTTPS流程简单域名和路径受平台策略影响URL 形式有平台痕迹Netlify / Vercel支持自定义域名自动 HTTPS免费额度有限页面延迟受平台节点影响IPFS内容寻址天然防篡改解析速度不稳定需要固定服务保持内容在线我不是说哪个一定更好。身份页的核心目标是自己持有所以域名所有权比托管平台更重要。哪怕你用免费平台托管只要域名在自己手里随时可以更换托管商身份页 URL 不变。5.2 发布流程应该自动化手工编辑、手工签名、手工上传短期跑一两次没问题长期一定会出错。建议把流程做成脚本或 CI 任务在本地编辑username.md。对文件重新签名生成新的username.md.sig。检查 frontmatter 中updated字段是否已更新。推送或上传到托管平台。用脚本请求公开 URL做一次验签确认。第五步非常重要。很多人本地验签没问题但上传后因为编码转换、末尾换行、平台自动格式化等原因线上文件的验签失败了。发布后立即做一次“从公开 URL 拉取再验签”的检查能避免把坏页面暴露给访问者。5.3 缓存和 CDN 带来的坑身份页不是热门内容但如果你用了 CDN或者托管平台自动做了缓存就可能会遇到签名文件更新但线上页面还是旧版本的情况。处理方式有几个方向在响应头设置合理的Cache-Control比如no-cache或者短缓存时间。签名文件名不要一直叫username.md.sig可以带版本如username.md.sig.v2并在 frontmatter 中引用新名字。Agent 侧抓取时可以带时间戳或随机参数比如?t当前时间强制绕过部分缓存。但最终还是要看服务器是否支持。记住身份页追求的是“当前版本可信”缓存会让旧版本在某个时间段内继续被消费这不是功能 bug但你的排查思路里要有这个概念。6. 边界与常见误判6.1 验签通过不等于身份真实这是整个方案里最容易被误读的一点。任何人都可以创建一把密钥签一个alice.md页面里写着“我是某公司 CEO”。验签只能确认这个页面确实由这把私钥的作者发布但不能确认页面里的声明是事实。要解决“真实身份”问题需要额外的信任锚域名所有权验证如果alice.md挂在alice.com下而alice.com的 WHOIS 信息或其他渠道确认为本人持有域名本身成为信任锚。社交账户互链同一把公钥在你的 GitHub、Twitter、邮箱等多处出现增加身份一致性证据。第三方背书由熟悉你的人或机构对你的公钥做签名形成信任网络。Agent 在设计信任策略时必须把“签名有效”和“声明可信”分开打分。签名有效是第一步来自公钥的信任得分是第二步域名历史和社会背书是第三步。6.2 Markdown 内容规范化问题签名对字节敏感Markdown 解析对格式敏感两者叠加后最容易出问题的场景是行尾符差异Windows 的 CRLF 和 Unix 的 LF在签名内容时会被视为不同字节。末尾是否有换行很多编辑器会在文件尾部自动加换行如果签名时没有线上验证时可能失败。UTF-8 BOM有些编辑器会写入 BOM导致文件开头多出三个字节。前端字段大小写Agent 解析时如果区分大小写而实际字段用了不同大小写就会读取失败。建议在签名流程里加入一个规范化步骤固定使用 UTF-8 无 BOM、LF 行尾、末尾一个换行。Agent 侧抓取内容后不要做任何额外转换直接用原始字节验签。6.3 多密钥和权限管理身份签名密钥不应该和日常开发密钥混用。我见过有人把身份页签名用的私钥直接放在服务器 SSH 目录里一旦服务器被入侵整个身份信任链都完蛋。比较稳妥的做法是身份密钥单独生成单独管理。私钥保存在离线加密环境中只在签名时临时给到进程。页面中可以同时列出多把公钥一把主身份密钥、一把子密钥或备用密钥。如果某把密钥泄露立即在页面上声明失效并用另一把正常密钥对新内容签名。6.4 跨平台身份关联的预期管理Username.md 是一个很好的起点但它不是万能的。它解决的是“我有一个稳定、可验证的身份页”但不解决“所有平台自动认可这个身份页”。GitHub 不会因为你有 username.md 就自动认证你的账号邮件服务商也不会自动把公钥绑到你的邮箱。要把这个身份页变成多方认可的凭证你还需要在常用平台上公开这个页面的 URL并尽可能让各平台信息指向同一把公钥。这个过程有点像滚雪球身份页是核心公钥是锚点平台账号是信任来源三者互相印证信任网络才慢慢建立起来。7. 踩坑之后我建议的排查顺序7.1 验签失败先查什么验签失败是最常见的身份页问题。不要一上来就怀疑工具坏了按这个顺序排查看原始字节把线上文件下载后和本地文件做一次sha256sum对比确认是否一致。看行尾和编码用工具检查是不是 CRLF、是不是带了 BOM。看签名文件是否过期或下载不完整有的服务器会截断或者给签名文件加错误 headers。看公钥指纹确认你导入的公钥确实是对应私钥生成的公钥而不是一个同样名字但内容不同的钥匙。看时间如果签名引用了时间戳确认系统时间和签名生成时间没有巨大偏差。7.2 Agent 解析不到字段先查什么验签成功但解析不出内容优先级是确认 frontmatter 是否真的被解析很多 Markdown 渲染器会自动忽略 frontmatter但你的 YAML 解析器未必认识。确认缩进YAML 对嵌套字段的缩进敏感字段层级错一位就会解析失败。确认字段名大小写你自定义的字段是 camelCase 还是 snake_caseAgent 侧要统一。确认是否有不可见字符比如零宽空格某些编辑器会自动插入导致字段 key 匹配不上。7.3 一份排查对照表现象常见原因优先检查点验签失败文件字节不一致对比线上和本地的 sha256验签失败行尾符或 BOM 问题检查文件编码和 line ending验签失败公钥指纹不匹配重新核对 keys 字段中的 fingerprint解析不出 namefrontmatter 缩进错误检查嵌套字段的缩进层级解析不出 keys字段名大小写不一致统一字段命名规范身份页 404文件名大小写错误确认 URL 中 username 大小写页面被缓存CDN 缓存未过期设置 Cache-Control 或加版本参数更新后线上旧内容发布流程没有验签增加发布后自动验签步骤这张表本质上是把“先看什么”给固化下来。排查时不要跳步先文件层再解析层再信任层。最后留几句经验我把整个方案在本地环境完整跑了一遍之后最大的感受是Username.md 的价值不在于做一个漂亮的页面而在于让“身份信息”本身具备可验证、可迁移、可被 Agent 消费的属性。个人建议的落地顺序是先用一个简单的 username.md 把自己的名字、公钥和联系方式写进去。用熟悉的签名工具跑通签名和验证的闭环。挂到自己的域名或静态托管上确认公开 URL 可访问。再考虑要不要做一个 Agent 解析小工具定期抓取并验签。最后才是把身份页和社交账号互链逐步建立外部信任。踩过几次坑之后我更确定一件事很多问题不是工具能力不够而是文件规范化、公钥管理和发布流程没有处理好。只要把这三件事理顺Username.md 这套思路就能稳定跑起来。