GitHub 和 GitLab 的令牌权限,表面看是 scope,真正难的是边界

发布于 2026年06月04日 18:27 #DevOps#安全

GitHub 和 GitLab 的令牌权限,表面看是 scope,真正难的是边界 封面图

大家好,我是若风。

前段时间我帮朋友排一个 CI 问题,场景特别典型:本地 git push 没事,放到流水线里就开始 403;换了另一个 token 之后,代码能推了,但改 .github/workflows 还是失败;再换成第三个 token,终于通了,结果权限给大了,整个仓库都能写。

这种事说实话太常见了。

很多人第一次接触 GitHub 或 GitLab 的 token,直觉都是“这不就是一串密码吗”。但真到实战里你会发现,令牌问题最难的从来不是生成,而是边界搞错了。表面上看是 scope 没选对,真正关键是你根本没分清楚:这个 token 到底代表“我这个人”,还是“这个仓库里的自动化”,还是“只负责部署的机器人”。

这篇文章我按 2026 年 6 月 4 日重新对照了 GitHub 和 GitLab 官方文档,尽量用最人话的方式,把两家的令牌权限讲清楚。你只要记住 3 件事:

  1. 先分身份,再谈权限。
  2. 能用仓库级,就别上账号级。
  3. 能用临时 token,就别存长期 token。

令牌权限最容易错在“身份”和“动作”混在一起

先别急着背名词,我们先把问题抽象一下。

任何一个 token,本质上都在回答两个问题:

  1. 你是谁。
  2. 你能做什么。

比如你想让脚本自动发版,这里面至少有 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 讲得很明确:它可以限制到单个资源所有者,还能继续收缩到指定仓库,再往下才是具体权限,比如 ContentsIssuesPull requestsWorkflows

这套模型的好处很直接。

如果你只是想让一个本地脚本更新某 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。短期很爽,长期很揪心。

尤其是下面两个场景,特别容易踩坑:

  1. 你只是想 push 代码,却给了完整 repo
  2. 你要改 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,看到 401403 就开始怀疑人生。

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 条:

  1. 这个 token 代表的是人、项目机器人,还是 CI 临时身份?
  2. 它只服务 1 个仓库,还是一组仓库?
  3. 它到底需要 Git 权限、API 权限,还是 registry/package 权限?
  4. 如果只读就够,为什么要给写?
  5. 如果 job 内临时可解决,为什么要存长期 PAT?
  6. 这个 token 泄漏后,最坏会影响几个仓库、几个项目、几个环境?
  7. 我有没有设置过期时间、轮换机制和撤销预案?

这 7 个问题里,前 3 个决定“该选谁”,后 4 个决定“别出事”。

写在最后

很多权限问题,最后看起来像平台坑,像文档难懂,像 403 很玄学。但你真的把 GitHub 和 GitLab 的 token 体系拆开看,会发现它们其实都在往同一个方向走:少给一点,短活短票,按边界发身份。

表面上看,我们是在学习 repoworkflowread_apiwrite_repository 这些术语;真正该学会的,是别再把“方便”误当成“合理”。

如果你们团队现在还在用一两个超大权限的长期 token 扛所有自动化,我倒是建议抽 30 分钟做一次盘点。很多时候,权限收一轮,风险能直接降一个数量级。

参考文档

评论互动

© 2026 王若风的技术博客 · Powered by Astro