GitHub 和 GitLab 的令牌权限,表面看是 scope,真正难的是边界
大家好,我是若风。
前段时间我帮朋友排一个 CI 问题,场景特别典型:本地 git push 没事,放到流水线里就开始 403;换了另一个 token 之后,代码能推了,但改 .github/workflows 还是失败;再换成第三个 token,终于通了,结果权限给大了,整个仓库都能写。
这种事说实话太常见了。
很多人第一次接触 GitHub 或 GitLab 的 token,直觉都是“这不就是一串密码吗”。但真到实战里你会发现,令牌问题最难的从来不是生成,而是边界搞错了。表面上看是 scope 没选对,真正关键是你根本没分清楚:这个 token 到底代表“我这个人”,还是“这个仓库里的自动化”,还是“只负责部署的机器人”。
这篇文章我按 2026 年 6 月 4 日重新对照了 GitHub 和 GitLab 官方文档,尽量用最人话的方式,把两家的令牌权限讲清楚。你只要记住 3 件事:
- 先分身份,再谈权限。
- 能用仓库级,就别上账号级。
- 能用临时 token,就别存长期 token。
令牌权限最容易错在“身份”和“动作”混在一起
先别急着背名词,我们先把问题抽象一下。
任何一个 token,本质上都在回答两个问题:
- 你是谁。
- 你能做什么。
比如你想让脚本自动发版,这里面至少有 4 种完全不同的需求:
- 只读代码。
- 推代码到仓库。
- 调平台 API 创建 release。
- 只给 CI 在任务运行时临时使用。
这 4 件事看起来很像,实际上对应的 token 可能完全不同。很多事故就是这么来的:本来只想“拉代码”,结果顺手给了“全仓库写 + 全 API”;本来只是自动部署,却拿了一个绑定个人账号的长期 PAT,最后人一离职,流水线全挂。
所以后面你看 GitHub 和 GitLab 时,不要先看 token 名字,要先看它的身份边界。
GitHub 已经从“大 scope”走向“细颗粒权限”
GitHub 现在最常见的 3 类令牌,可以粗暴理解成三层。
| 类型 | 身份边界 | 典型用途 | 风险特点 |
|---|---|---|---|
Personal access token (classic) | 绑定用户账号,影响你能访问的所有仓库范围 | 老脚本、兼容场景、某些 fine-grained 还不支持的能力 | 权限最宽,最容易“顺手给大” |
Fine-grained personal access token | 绑定单个用户或组织,还能限制到指定仓库 | CLI、脚本、单仓库自动化 | 更安全,但不是所有场景都支持 |
GITHUB_TOKEN | 只属于当前 workflow 所在仓库,且生命周期跟 job 走 | GitHub Actions 内部自动化 | 默认更安全,但边界严格 |
Fine-grained PAT 才是 GitHub 现在更推荐的默认选项
GitHub 官方文档里把 fine-grained PAT 讲得很明确:它可以限制到单个资源所有者,还能继续收缩到指定仓库,再往下才是具体权限,比如 Contents、Issues、Pull requests、Workflows。
这套模型的好处很直接。
如果你只是想让一个本地脚本更新某 1 个仓库的 release note,那它没必要碰你账号下另外 20 个仓库,更没必要有组织级管理权限。fine-grained PAT 就是为这个场景生的。
但 GitHub 也明确写了一个现实问题:fine-grained PAT 还不能覆盖 classic PAT 的所有能力。比如某些历史接口、某些跨组织/协作者场景,classic PAT 还在兜底。所以你会看到不少老工具 README 还在教你配 classic PAT,这不是他们懒,很多时候是兼容性决定的。
我的建议很简单:
- 新脚本、新项目,先试 fine-grained PAT。
- 遇到能力缺口,再退回 classic PAT。
- 一旦退回 classic PAT,就默认把它当高风险凭证管理。
Classic PAT 真正危险的地方,不是名字老,而是边界太大
GitHub 官方对 classic PAT 的提醒其实挺直白:这类 token 很容易访问到你本来就有权限的所有仓库资源。也就是说,它不是按“这个脚本需要什么”来授权,而是按“你这个人本来能碰什么”来授权。
这就是为什么很多团队明明只想做一个小自动化,最后却给了 repo 这种大 scope。短期很爽,长期很揪心。
尤其是下面两个场景,特别容易踩坑:
- 你只是想 push 代码,却给了完整
repo。 - 你要改 workflow 文件,却忘了额外的
workflow相关权限。
如果你推的是普通代码,权限模型相对简单;但只要涉及 .github/workflows/*.yml,要求就会明显更高。GitHub 在 classic PAT 体系下有单独的 workflow scope;在 fine-grained PAT 体系下,对应的是仓库级 Workflows 权限。很多“为什么我明明能 push,唯独工作流文件不行”的问题,根因就在这里。
GITHUB_TOKEN 的本质不是“迷你 PAT”,而是仓库内置临时身份
这个点很多人第一遍都会理解偏。
GITHUB_TOKEN 不是你自己生成的 PAT,它本质上是 GitHub Actions 在 job 启动时给当前仓库发的一张临时票。官方文档写得很清楚:它的权限被限制在包含该 workflow 的仓库里,而且生命周期跟 job 绑定。
这就意味着两件事:
第一,它天然适合“仓库内自动化”。比如:
- 创建 issue
- 创建 release
- 评论 PR
- 读取代码、构建、上传 artifact
第二,它天然不适合被你拿去冒充“长期通用钥匙”。
另外还有一个很关键的细节:GitHub Actions 里的 permissions 一旦你显式写了某些权限,没写到的其他权限会被置成 none。这一点非常好,也非常容易让人翻车。
比如这段配置:
permissions:
contents: read
issues: write
它的含义不是“额外加两个权限”,而是“只保留这两个,其余默认都不要”。如果你后面某一步还想发 deployment、改 PR、写 packages,就要自己再补。
所以在 GitHub 里,我的经验是:
- 在 Actions 里能用
GITHUB_TOKEN就先用它。 - 只给 job 需要的最小
permissions。 - 只有当
GITHUB_TOKEN做不到时,才考虑 PAT 或 GitHub App。
GitLab 的 token 更多,但边界反而更好理解
GitLab 这套设计,第一眼看会觉得选项很多:PAT、project access token、group access token、deploy token、CI_JOB_TOKEN……但说白了,它其实比 GitHub 更强调“谁来干活”。
先看一张总表:
| 类型 | 身份边界 | 能不能调 API | 适合什么 |
|---|---|---|---|
| Personal access token | 用户账号 | 能 | 个人脚本、CLI、临时运维 |
| Project access token | 单个项目 | 能 | 项目级机器人、单仓库自动化 |
| Group access token | 单个组 | 能 | 多项目共享自动化 |
| Deploy token | 项目或组的部署身份 | 不能直接调 GitLab 公共 API | 拉代码、拉推镜像、包仓库 |
CI_JOB_TOKEN | 当前 CI job | 只能访问受限 API | 流水线内短时操作 |
GitLab PAT 最像“真人分身”,所以也最该慎用
GitLab 的 personal access token 很像 GitHub classic PAT,都是直接挂在用户身上。官方文档也提醒得很明确:它继承创建者本人的权限,只是再叠加 scope 限制。
这里最容易被忽略的一点是,GitLab 的 api scope 很大。
如果你只是想读 API,就优先看 read_api;如果你只是想通过 HTTP 拉私有仓库代码,就看 read_repository;如果你只是想通过 Git push,就看 write_repository。GitLab 文档甚至单独写了,write_repository 只管 Git over HTTP 的读写,并不等于你能拿它调用所有 API。
这个边界反而是好事。因为它逼着你别偷懒。
Project access token 是 GitLab 里最好用的“项目机器人”
如果你只服务一个项目,我倒是非常推荐优先考虑 project access token。
GitLab 官方文档里明确说了,它被限制在单个项目范围内,而且创建时会生成项目级 bot user。这类 bot user 不占 license seat,对团队自动化来说非常友好。
它适合什么?
- 某个项目自己的发布脚本
- 某个项目自己的镜像构建
- 单项目的 API 调用
- 只想让机器人碰 1 个项目,不想碰整个组
说白了,project access token 的核心价值不是“它更方便”,而是“它出事时波及面更小”。
Group access token 解决的是多项目协同,不是“更高级的 PAT”
很多团队一上来就喜欢配 group access token,觉得以后省事。这个思路我能理解,但风险也确实更大。
因为它的边界已经不是一个项目,而是一整个 group。你只要想到这一点,就知道它应该用在什么地方了:共享模板仓库、多项目统一发版、跨项目机器人账号。这些都合理。
但如果你的自动化只服务 1 个项目,那上来就给 group access token,其实就是在扩大攻击面。
能不用,就别用。
Deploy token 的价值是“会干活,但不碰管理面”
GitLab deploy token 这个设计我一直挺喜欢,因为边界特别清楚。
它能做的,基本都是“交付动作”:
- 克隆仓库
- 拉取或推送容器镜像
- 读写 package registry
它不能直接用来调 GitLab 公共 API。这一点官方文档写得非常明确。
这意味着 deploy token 特别适合部署机、运行时环境、镜像拉取脚本。你把它放在服务器上,心里会踏实很多,因为哪怕它泄漏了,攻击者也不太容易顺着它去改 issue、改 MR、改成员权限。
如果你只是为了让生产机 docker pull 一下镜像,结果塞了个 PAT 进去,说实话,这就有点拿大炮打蚊子了。
CI_JOB_TOKEN 才是流水线里的第一选择
GitLab 官方在 token overview 里有一句建议,我觉得应该贴墙上:做 CI/CD 时,尽量别用个人 PAT,当 job token 或 project access token 能满足时,优先选它们。
原因很简单,CI_JOB_TOKEN 是临时的,而且跟 job 生命周期绑定。任务结束,票也就过期了。
它当然有限制。比如它不是全能 API token,也不能拿去随便调 GraphQL;跨项目访问时还经常要配 allowlist。但这些限制本身就是安全边界的一部分。
如果你的需求是在流水线里拉另一个私有项目、触发部分 API、做包下载,这种时候先试 CI_JOB_TOKEN,往往是最稳的。
像下面这种用法,就很典型:
git clone https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.example.com/group/project.git
这串命令看起来普通,背后其实是一个很重要的安全策略:让临时身份做临时的事。
两个平台最容易踩的 6 个坑
1. 能 push 代码,不代表能改工作流
GitHub 里这事太常见了。普通代码 push 成功,不代表你有修改 Actions workflow 的权限。涉及 .github/workflows 时,要单独确认 classic PAT 的 workflow scope,或者 fine-grained PAT 的 Workflows 权限。
2. 能拉仓库,不代表能调 API
GitLab 的 write_repository 就是典型例子。它支持 Git over HTTP 的 push/pull,但不等于完整 API 权限。很多人配完以后去打 REST API,看到 401 或 403 就开始怀疑人生。
3. 部署场景错用用户级 PAT
这是我最不推荐的坏习惯之一。部署机、运行时环境、镜像拉取机,优先应该拿 deploy token、project token 或临时 job token,而不是某个同事的个人 PAT。
4. 把“跨项目方便”当成“给 group 级权限的理由”
只要你上了 group access token,风险边界就变大了。除非你的自动化天然服务多个项目,否则项目级 token 往往更合适。
5. scope 够了,但资源边界不对
这个坑在 GitHub fine-grained PAT 上特别典型。你可能权限勾得没问题,但 token 只绑定了某个组织、某几个仓库,目标仓库压根不在授权范围里,那照样不行。
6. token 不过期,最后一定出事
GitLab 现在新建很多 access token 默认就要求过期时间;GitHub 这几年也一直在强化过期和审批策略。平台都在往“短期、可审计、可撤销”走,你如果还习惯“先建一个永久 token 再说”,基本就是在给未来埋雷。
真正有用的不是记住名字,而是学会一套选型顺序
如果你懒得背一堆文档,我给你一套特别实用的判断顺序。
场景一:在平台自带 CI 里跑自动化
- GitHub:先用
GITHUB_TOKEN - GitLab:先用
CI_JOB_TOKEN
这是默认答案。
场景二:只服务单个仓库或项目的机器人
- GitHub:先用 fine-grained PAT,限制到指定仓库
- GitLab:先用 project access token
场景三:只做部署、拉包、拉镜像
- GitHub:优先看平台原生部署凭证或 Actions 内部令牌
- GitLab:优先 deploy token
场景四:必须跨多个项目协作
- GitHub:优先评估 GitHub App,其次再看 PAT
- GitLab:必要时再上 group access token
场景五:只是你自己本地命令行偶尔用一下
- GitHub:fine-grained PAT 优先,classic PAT 兜底
- GitLab:PAT 够用,但 scope 尽量收窄
最后给你一个最小权限检查清单
每次创建 token 前,我都会快速过一遍下面 7 条:
- 这个 token 代表的是人、项目机器人,还是 CI 临时身份?
- 它只服务 1 个仓库,还是一组仓库?
- 它到底需要 Git 权限、API 权限,还是 registry/package 权限?
- 如果只读就够,为什么要给写?
- 如果 job 内临时可解决,为什么要存长期 PAT?
- 这个 token 泄漏后,最坏会影响几个仓库、几个项目、几个环境?
- 我有没有设置过期时间、轮换机制和撤销预案?
这 7 个问题里,前 3 个决定“该选谁”,后 4 个决定“别出事”。
写在最后
很多权限问题,最后看起来像平台坑,像文档难懂,像 403 很玄学。但你真的把 GitHub 和 GitLab 的 token 体系拆开看,会发现它们其实都在往同一个方向走:少给一点,短活短票,按边界发身份。
表面上看,我们是在学习 repo、workflow、read_api、write_repository 这些术语;真正该学会的,是别再把“方便”误当成“合理”。
如果你们团队现在还在用一两个超大权限的长期 token 扛所有自动化,我倒是建议抽 30 分钟做一次盘点。很多时候,权限收一轮,风险能直接降一个数量级。
参考文档
- GitHub: Managing your personal access tokens
- GitHub: GITHUB_TOKEN
- GitHub: Workflow syntax for GitHub Actions
- GitHub: Permissions required for fine-grained personal access tokens
- GitLab: Token overview
- GitLab: Personal access tokens
- GitLab: Project access tokens
- GitLab: Group access tokens
- GitLab: Deploy tokens
- GitLab: CI/CD job token
评论互动