
最近 Hacker News 上出现了一个有意思的项目Username.md。它的完整标题是 “Username.md – a signed, agent-readable identity page you own”。这个标题很短但信息量很大三个关键词几乎踩中了 AI Agent 时代数字身份的全部痛点签名signed、Agent 可读agent-readable、自主拥有you own。先说说我为什么觉得这个项目值得关注。过去我们做个人主页默认访客都是“人类”页面要好看、文案要清楚、首屏要在 3 秒内让对方知道你是谁。但这两年情况正在变化越来越多的访问不再是人类发出的而是 AI Agent 代替用户发出的。Agent 会替你读简历、对比产品、筛选候选人、判断某个开源作者靠不靠谱。而大多数个人主页是写给人类眼睛看的 HTMLAgent 解析起来并不轻松。更麻烦的是真伪问题。任何人都可以复制一份“我是某某我有多少年经验”的文案做成一个看起来像模像样的个人主页。Agent 拿到这些页面时很难判断里面的身份声明到底是本人写的还是伪造的。Username.md 这类项目想解决的就是这两件事让身份页面能被 Agent 结构化读取并且通过数字签名让身份声明可验真、防冒充。这篇文章不会只停留在介绍这个项目本身。我会从“Agent 时代为什么需要这样的身份页面”这个更宏观的问题切入拆解 signed、agent-readable、you own 这三个关键词背后的技术含义然后给出你可以直接落地的部署、签名、验证方案。1. 为什么要关注 Username.mdAgent 时代的身份危机如果把时间线拉长数字身份形态经历了三个阶段。第一代是“论坛和邮箱时代”。你的身份是一个用户名一个 email 地址可以随时换别人也很难验证真假。第二代是“平台主页时代”。GitHub、Twitter、LinkedIn 这类平台成了身份载体平台通过实名认证、粉丝数、历史记录来间接证明“这个人可信”。这也是当前的主流。第三代就是正在发生的“Agent 时代”。AI Agent 会成为互联网的新网民。它们替用户访问网站、判断信息、执行任务但在做决定之前它必须先回答一个非常底层的问题这个页面说的“我是谁”到底能不能信问题在于现有身份方案都是给人类设计的。拿 GitHub 主页来说人类看一个账号的星星数、提交记录、公司信息基本能做出判断。但 Agent 要理解这些信息需要有平台 API、需要解析 HTML、需要理解“小绿格子”的含义。LinkedIn 主页同理信息密度高但对程序来说就是一堆 DOM 节点没有结构化语义。再比如 HTTPS 证书。它能证明“你访问的确实是 example.com 这个域名”但域名背后的人是谁、是否同时属于另一个项目团队、相关声明是否真实证书一概回答不了。所以 Agent 时代的身份问题可以归纳成两个核心痛点机器不可读。身份信息没有统一结构Agent 每访问一个站点都要重新理解页面布局。声明不可验真。一段“我是谁”的文本无法被程序化验证伪造成本几乎为零。Username.md 这类项目的价值就是在这两个痛点上给出了一个很朴素的方案用 Markdown 写身份信息把结构化字段放在 YAML frontmatter 中让页面顶层路径成为约定的读取入口再用 OpenPGP 或 SSH 签名来证明文件确实由私钥持有者生成。一句话总结这个趋势Agent 时代身份页面不再只是给人类看的“名片”还必须是给机器读的“声明文件”。谁更早建立这个习惯谁就能在 Agent 生态里占据先机。2. 核心概念拆解signed、agent-readable、you own理解 Username.md关键是拆解它的项目标题。这里面的每个词都不是修饰而是在表达一项具体能力。2.1 Identity page身份页面身份页面不是个人博客也不是作品集而是一个以“验证你是谁”为核心目标的声明文件。它可以包含名字、头像、邮箱、社交链接、所属组织、主要项目、开源 PGP 公钥、SSH 公钥等信息。与普通个人主页的区别在于身份页面的读者不只包括人还包括程序。所以它的信息组织必须“可解析”通常会把结构化字段和人类可读正文分开。YAML frontmatter 是一个很自然的选择因为解析成本低生态成熟。2.2 Agent-readable面向 Agent 的可读性Agent-readable 强调的内容是一个程序能够稳定地从页面中提取出“身份字段”。普通 HTML 页面的字段是隐式的依赖视觉布局和语义标签。Markdown 配合 YAML frontmatter 则提供了近似于“表单”的结构。我个人的判断是Agent-readable 不一定意味着格式上绝对统一而是表达了一种约定让 Agent 知道去哪里读取、读取后得到哪些字段。类似 robots.txt 约定爬虫行为一样Username.md 想成为 Agent 读取身份信息的默认路径。这也是它取 “Username.md” 这个名字的原因——把用户名和 Markdown 组合在一起形成直觉上可发现的固定入口。2.3 Signed数字签名使声明可验真Signed 是整个方案里最关键的一环。没有签名Username.md 就是一个“结构化的自我介绍”伪造太容易。任何人写一段“我是 Alice维护某个知名项目”再放到自己的网站上Agent 拿它没有办法。有签名之后情况就不同了发布者用私钥对整个文件签名验证方用公钥校验签名。只要签名校验通过就能证明文件内容没有被篡改且确实是由持有对应私钥的人发布的。这里要特别强调一个容易被误解的点签名解决的是“文件完整性和作者身份”但它不解决“第一次信任建立”。如果你第一次拿到某个公钥你仍然不知道这个公钥背后的人是不是你想象的那个人。这是整个信任模型里最难的问题后面我会在第 8 章专门展开。2.4 You own数据所有权与自主性You own 是这个项目最坚决的一个立场身份页面放在你自己的域名下你控制文件内容你持有签名私钥你决定让哪些 Agent 读取你哪些信息。对比当下的主流方案你会发现这是很大的不同。GitHub、LinkedIn、Twitter 的身份数据本质上属于平台平台可以改算法、关 API、删账号你的身份会随平台规则而波动。Agent 时代一旦完全依赖平台身份等于把“你是谁”的判断权交给了少数几个大公司。而“自主拥有”意味着只要域名还在、私钥还在、静态文件还能被访问身份就成立。它不一定能取代平台身份但给 Agent 世界提供了一个不依赖平台的可选路径。维度传统个人主页平台主页Username.md 模式读者人类人类为主人类 Agent机器可读性差依赖 HTML 解析依赖平台 API好Markdown YAML防伪能力无依赖平台背书数字签名验真数据控制权自主平台控制自主信息结构自由排版平台模板约定 可扩展字段这张表基本能解释为什么这类项目会出现在 Hacker News 上它正好补上了当前身份方案在 Agent 时代的缺口。3. Username.md 的典型文件形态从项目名的 .md 后缀可以看出身份页面的载体是 Markdown 文件。最简单的情况就是在一个域名根目录下放一个username.md文件通过https://example.com/username.md访问。一个典型的 Username.md 文件大概长这样。需要说明的是下面这个示例是结合这类模式设计的通用形态具体字段以项目文档为准但是思路是一致的。--- username: alice display_name: Alice Zhang role: Backend Engineer organization: ExampleCloud email: aliceexample.com website: https://example.com location: Shanghai, China openpgp: https://example.com/alice.asc ssh: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... created: 2025-01-10T08:00:00Z updated: 2025-04-12T10:30:00Z --- # Alice Zhang Im a backend engineer working on distributed systems. Maintainer of [demo-project](https://github.com/alice/demo-project). - Focus: Go, Kubernetes, cloud-native infra - Email: aliceexample.com - PGP: https://example.com/alice.asc这里最核心的是上方的 YAML frontmatter它的作用是把身份信息结构化让程序可以精确提取。下方的 Markdown 正文则是给人读的可以自由发挥。在字段设计上有几个值得注意的地方。username 字段作为唯一标识建议只在初始配置后固定不变。display_name 是可以展示的中文或本地化名称。organsation 和 role 不必写得太细因为 Agent 世界里的身份信息更新频率其实很低写得太具体反而容易过期。email 建议使用邮箱公开地址防止被爬虫采集后再做营销骚扰。openpgp 和 ssh 字段则是关键它们是验证签名和后续交互的公钥地址。把签名块直接放进这个文件还是单独放一个.asc文件是两种不同的做法。如果希望文件自包含可以使用 OpenPGP 的 clearsign 模式签名块直接嵌入文件尾部。如果希望保持原始 Markdown 干净建议使用分离签名detached signature也就是在服务器上同时发布username.md和username.md.asc两个文件。Agent 验证时同时下载这两个文件。从实际工程角度看分离签名更灵活程序可以先用普通 Markdown 解析器读取文件内容然后单独验证签名块如果签名要更新也不需要改正文文件。下面第 4 章会分别演示这两种方式。4. 生成签名OpenPGP 与 SSH 两种方式要生成签名首先需要一组密钥。对个人开发者来说最稳妥的选择是 OpenPGP 密钥也就是 GPG。它生态成熟、工具链完整、支持子密钥和吊销证书。如果你更偏好极简方案也可以使用 SSH 签名虽然不具备完整的信任网络但胜在轻量而且很多开发者本来就有 SSH 密钥。4.1 用 GPG 生成密钥并签名先生成密钥这个过程会交互式询问姓名、邮箱和密码属于正常步骤。gpg --full-generate-key生成后会得到密钥 ID可以通过下列命令查看gpg --list-secret-keys --keyid-formatlong假设密钥 ID 是3A5B...E9F0接下来可以生成分离签名。gpg --detach-sign --armor -u 3A5B...E9F0 username.md这条命令会生成username.md.asc文件。把username.md和username.md.asc都上传到服务器验证方就能使用公钥来校验。如果你希望签名内嵌在文件中用一个文件完成发布可以使用 clearsign 模式gpg --clearsign -u 3A5B...E9F0 username.md -o username.signed.md此时username.signed.md的内容会包含签名块。缺点是文件不再像原始 Markdown 那样干净但在某些静态托管场景下反而更方便因为用户只需要下载一个文件即可完成验证。4.2 用 SSH 密钥签名如果你不想专门管理 GPG 密钥SSH 签名是一个可用的轻量方案。前提是你的 SSH 私钥是 ed25519 类型且公钥已经发布到页面上。ssh-keygen -Y sign -f ~/.ssh/id_ed25519 -n username.md username.md这个命令会生成username.md.sig签名文件。验证方验证时需要使用你的公钥和对应 namespace。ssh-keygen -Y verify -f allowed_signers -I alice -n username.md \ -s username.md.sig username.md其中allowed_signers文件需要记录你的公钥一行格式类似alice ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI...两种方案的适用场景不同。GPG 更适合独立身份声明因为它本身具备开放信任网络SSH 签名更适合团队内部或设备相互验证的场景配置简单不依赖第三方信任体系。这里要强调一个很容易踩坑的细节签名依赖文件内容的精确字节。如果你在本地 Linux 上签名上传到服务器后因为 Windows 换行符被修改验证就会失败。更稳妥的做法是在服务器端完成签名或者在上传时保持二进制一致并确认.gitattributes没有强制转换行尾。5. 部署身份页面路径、域名与静态托管Username.md 的模式成立有一个前提页面必须放在一个你控制的空间里。最合理的地方就是你的个人域名而且路径要足够固定这样 Agent 才能“按约定找到你”。5.1 路径约定从直觉和工程惯例看根路径https://example.com/username.md是最容易记忆和检查的路径语义上和 robots.txt 非常像。如果你希望接入更多 Agent 生态未来也可以把文件放到/.well-known/目录下例如https://example.com/.well-known/username.md。这个路径沿用了 IANA 的 RFC 8615 well-known URI 约定更便于标准化发现。两种路径可以同时发布内容保持一致。签名的好处恰恰在这里即使两个路径指向同一个文件签名也能保证内容一致防止被篡改或镜像站冒用。5.2 静态托管部署因为 Username.md 是一个纯静态文件部署可以相当轻量。GitHub Pages、Cloudflare Pages、Nginx、对象存储均可以。下面的示例展示如何用 Nginx 在一个静态站点目录中发布文件并加上跨域访问头方便 Agent 在浏览器环境中做 fetch 请求。server { listen 443 ssl; server_name example.com; ssl_certificate /etc/nginx/ssl/example.com.crt; ssl_certificate_key /etc/nginx/ssl/example.com.key; root /var/www/example; index index.html; location /username.md { default_type text/markdown; add_header Access-Control-Allow-Origin *; add_header Cache-Control no-cache; } location /username.md.asc { default_type application/pgp-signature; add_header Access-Control-Allow-Origin *; } }Access-Control-Allow-Origin *是给浏览器端 JS 脚本读取用的。如果你只服务于非浏览器 Agent这个头可以不加。Cache-Control: no-cache建议加上因为身份信息更新频率不高但不希望 Agent 长时间缓存旧版本后产生验证问题。如果你使用 GitHub Pages只需要把username.md和username.md.asc放进仓库根目录或 docs 目录开启 Pages 即可。优点是零运维缺点是域名路径是username.github.io/repo/对 Agent 友好度低一些。因此生产环境更推荐绑定自定义域名。5.3 域名所有权与身份证明在部署层面之外还应该考虑一个信任问题Agent 怎么知道username.md所声明的“Alice”和域名 example.com 的持有者是同一个人一种简单的交叉验证方式是在 DNS 里添加一条 TXT 记录把身份声明和域名所有权绑定起来。例如username-md.example.com. IN TXT alice; openpgp3A5B...E9F0这样 Agent 在验证文件签名之前可以先去 DNS 查询这条记录确认公钥指纹与域名对应。这是 HTTPS 证书做域名验证之外的补充方案成本很低却能在首次信任建立上提供不少帮助。6. Agent 如何消费与验证一个最小消费者实现前几章从发布者视角讲了如何生成和部署 Username.md。这一章从消费端出发演示一个 Agent 是如何读取、解析和验证身份页面的。假设我们要验证https://example.com/username.md这个身份页面。第一步是下载文件主体和分离签名第二步是获取公钥第三步是用公钥校验签名第四步是提取 frontmatter 中的结构化字段。手动验证可以直接使用命令行curl -s https://example.com/username.md -o username.md curl -s https://example.com/username.md.asc -o username.md.asc curl -s https://example.com/alice.asc -o alice.asc gpg --import alice.asc gpg --verify username.md.asc username.mdgpg --verify输出如果包含Good signature说明签名有效。要注意的是首次导入公钥时GPG 会显示“此公钥未被信任”not certified with a trusted signature的警告。这是预期的因为它表示“没有建立信任链”不代表签名无效。为了让消费流程可复用更好的是编写一个小脚本。下面这个 Python 示例演示了一个常规流程下载文件、下载公钥、调用 gpgv 验证、解析 frontmatter 并输出结构化结果。# 文件路径verify_username_md.py import hashlib import subprocess import tempfile from pathlib import Path import requests import yaml BASE_URL https://example.com USERNAME_MD f{BASE_URL}/username.md SIGNATURE f{BASE_URL}/username.md.asc PUBLIC_KEY f{BASE_URL}/alice.asc def download(url: str, dest: Path) - None: resp requests.get(url, timeout10) resp.raise_for_status() dest.write_bytes(resp.content) def verify_signature(signed_file: Path, sig_file: Path, key_file: Path) - bool: with tempfile.TemporaryDirectory() as tmpdir: keyring Path(tmpdir) / keyring.gpg subprocess.run( [gpg, --no-default-keyring, --keyring, str(keyring), --import, str(key_file)], checkTrue, capture_outputTrue, ) proc subprocess.run( [gpgv, --keyring, str(keyring), str(sig_file), str(signed_file)], capture_outputTrue, textTrue, ) return proc.returncode 0 def extract_frontmatter(md_file: Path) - dict: raw md_file.read_text(encodingutf-8) if not raw.startswith(---): return {} _, fm_text, _ raw.split(---, 2) return yaml.safe_load(fm_text) def main() - None: with tempfile.TemporaryDirectory() as tmpdir: md_path Path(tmpdir) / username.md sig_path Path(tmpdir) / username.md.asc key_path Path(tmpdir) / alice.asc download(USERNAME_MD, md_path) download(SIGNATURE, sig_path) download(PUBLIC_KEY, key_path) if not verify_signature(md_path, sig_path, key_path): print([error] signature verification failed) return meta extract_frontmatter(md_path) print([ok] signature verified) print(username:, meta.get(username)) print(display_name:, meta.get(display_name)) print(email:, meta.get(email)) print(openpgp:, meta.get(openpgp)) if __name__ __main__: main()这个脚本的流程是下载username.md和username.md.asc以及页面声明的公钥alice.asc。把公钥导入临时 keyring避免污染用户主密钥环。使用gpgv在临时 keyring 环境下验证分离签名。解析 Markdown 开头的 YAML frontmatter得到结构化身份信息。之所以用gpgv而不是gpg --verify是因为gpgv默认不会访问用户主密钥环中的信任数据库它只会使用临时 keyring 中的公钥来验证签名更干净也有了更小的攻击面。在 Agent 的自动化消费场景里这种隔离方式很重要。运行方式pip install pyyaml requests python verify_username_md.py预期输出如下[ok] signature verified username: alice display_name: Alice Zhang email: aliceexample.com openpgp: https://example.com/alice.asc如果签名验证失败脚本会输出错误信息并中断。注意这个消费脚本只是一个最小实现真实生产环境中还需要考虑公钥指纹白名单、缓存策略、超时重试以及多个公钥的信任路径问题。Agent 拿到这些结构化字段后可以做的事情就很容易扩展了。比如把username作为用户唯一 ID 存储用openpgp公钥去验证该用户发布的其他内容用organization字段判断用户所属团队把ssh公钥用于代码签名或 Git 提交验证。这些能力叠加起来Agent 对一个人的理解就从“一段文字”变成了“一组可验证的声明”。7. 常见问题与排查方法在实际把 Username.md 跑起来的过程中可能会遇到一些典型的坑。这里整理成一张排查表发布者和消费方都能用。问题现象可能原因排查方式解决方案签名验证总是失败文件在传输或编辑过程中换行符被修改对比本地文件和服务器文件的 sha256在服务器端重新签名或检查 .gitattributes 行尾设置下载公钥后 gpg --import 失败公钥文件不是 ASCII-armored 格式查看公钥文件是否为BEGIN PGP PUBLIC KEY BLOCK开头重新导出gpg --armor --export KEYIDAgent 拿不到 frontmatter 字段Markdown 文件开头不是严格的---分隔打开文件确认首行无 BOM 和多余空行使用统一模板生成文件避免手动编辑首部CORS 请求失败静态服务器未返回 Access-Control-Allow-Origin 头curl -I 查看响应头Nginx 增加 add_headerCDN 平台配置同样响应头更新身份信息后签名与文件不匹配修改正文后忘记重新生成签名验证一下 .asc 文件时间戳修改文件后立即重新执行签名命令并发布公钥被泄露后如何撤销没有吊销证书或短时间内无法替换检查是否备份过 revoke.asc立即发布吊销证书并替换新的密钥对gpgv 提示公钥未找到导入公钥时使用了错误 keyring检查临时 keyring 文件内容确保--keyring参数指向导入公钥的文件Agent 在浏览器中无法请求页面缺少 CORS 头浏览器开发者工具查看预检请求补充Access-Control-Allow-Origin必要时处理 OPTIONS 请求第 7 章里最重要的一条经验是永远不要在修改文件后跳过重新签名。签名一旦失效整个身份页面的可信度就会归零Agent 会认为这是被篡改过的文件直接拒绝采用。签名文件的生成和发布最好做成一个小脚本绑定到部署流程里而不是每次手动执行。8. 安全边界与最佳实践Username.md 的模式很有潜力但作为工程实现它踩在安全边界上有几个问题必须在动手之前想清楚。8.1 私钥安全是生死线签名价值完全建立在私钥安全之上。一旦私钥泄露任何人都可以替你的身份页面签名发布虚假信息。这在 Agent 生态里等于身份被完整接管。生产环境中建议做到私钥存放在硬件密钥或加密容器中不放在普通服务器可读路径。签名时使用 GPG 智能卡、YubiKey 或至少使用带密码保护的密钥。不要把私钥提交到 Git 仓库、CI 变量或任何第三方平台。使用子密钥进行日常签名主密钥离线保存这样泄露子密钥时可以单独吊销。定期检查密钥指纹是否出现在公开代理上比如 GitHub 的密钥扫描。8.2 信任起点问题这是整个方案里最棘手的问题。签名的数学原理是无懈可击的它证明“持有私钥的人生成了这份文件”但无法回答“这份公钥对应的人是不是现实中的 Alice”。解决首次信任问题通常需要以下几种方式叠加DNS 记录在域名 TXT 记录中写入公钥指纹利用域名控制的唯一性做绑定。多平台交叉验证将同一公钥指纹发布在 GitHub、个人博客、社交平台等多处Agent 可以交叉比对。已有信任网络如果你的公钥被其他已信任的人签名过比如 Web of Trust 或相近领域开发者之间的相互签名信任关系会自动传播。历史行为验证长期使用同一个公钥发布内容、提交代码Agent 可以通过时间线积累信任。不要期待单靠 Username.md 就能建立完整信任它是一个基础层需要和其他信号配合使用。8.3 最小信息原则与隐私边界身份页面作为一种“机器可读的自我介绍”很容易在设计时信息过度暴露。比较稳妥的边界是只发布 Agent 完成判断所必需的信息例如用户名、显示名、角色、组织、公钥地址。手机号、精确住址、内部公司系统信息不要放进去。还有一个经常被忽略的点更新身份页面后旧版本被 Agent 缓存带来的隐私残留。建议给公开文件加上不缓存或短缓存策略并保留吊销机制。如果未来某一天你不再希望 Agent 读取该身份页面最直接的动作是删除域名下的username.md文件和对应的 DNS TXT 记录然后发布一条签名声明说明该身份作废。8.4 身份声明不等于身份认证必须明确一个边界Username.md 只回答“这个文件由谁签名、内容是否完整”的问题它不回答“请求 Agent 是否真的是 Alice 的 Agent”的问题。如果某个服务要求你登录用 Username.md 里的公钥签一个 challenge它的意义只是证明“请求者持有该公钥对应私钥”这属于 authentication 的范畴和身份声明是两层逻辑。任何把 Username.md 直接当作登录凭证、支付凭据或授权凭证的方案都需要额外的协议层和安全审计不要在生产系统中贪图方便直接使用。8.5 字段设计要面向未来身份信息的字段会随时间增长。建议在最开始就使用语义清晰、可扩展的命名预留字段前缀空间。比如openpgp、ssh、dns_txt、cross_links这些字段未来都有可能被 Agent 社区标准化。一旦形成了事实标准再改字段名就会产生兼容成本所以早期设计比后期修补重要。9. 总结与下一步实践Username.md 这个项目表面上看是一个“把个人主页写成 Markdown 文件”的小工具但从架构角度看它其实在推动一个更底层的转变身份声明从“给人浏览的内容”变成“给机器验证的数据”。如果把 Agent 比作互联网的新一代访客那么username.md在这套模式里扮演的角色非常像当年的robots.txt。它用约定路径 约定格式告诉 Agent从这里读我的身份信息。而 signed 则补上了 robots.txt 时代没有的能力——读取者可以验证这份声明确实来自持有私钥的本人而不是中间人伪造的副本。对开发者来说动手门槛其实很低第一步申请或确认一个自己拥有能力的域名。第二步写一个带 YAML frontmatter 的 Markdown 身份文件同时生成一个分离签名。第三步把文件传到静态服务器加上 DNS TXT 记录作为交叉验证。第四步写一个消费端验证脚本把整个流程跑通。第五步把公钥指纹发布到 GitHub、个人博客等你能控制的其他位置。真正值得深入研究的不是 Markdown 语法而是三个更深的工程问题如何设计一套被 Agent 识别的标准字段库如何解决公钥的首次信任建立如何在身份页面被大规模读取时控制隐私暴露面。这几个问题目前社区还在早期探索阶段但方向已经比较清晰。我的建议是不要等标准定下来再动手。先用一个最小页面跑通“生成签名、上传部署、Agent 验证”的完整闭环感受一下这个模式的成本和收益。等到 Agent 访问量真正起来的那一天你不需要重新思考身份问题只需要在现有基础上扩展字段和信任机制就好。设备、代码和身份最好都在值得信任的地方提前布局。