2025 年 11 月 19 日,NPM 生态系统经历了一次重大的安全架构变革:所有经典令牌被永久撤销,粒度令牌开始强制执行 90 天最大生命周期和默认 2FA 认证。这一变革并非突发奇想,而是对 2025 年 9 月 Shai-Hulud 攻击的直接回应 —— 那次攻击通过窃取维护者凭证感染了 500 多个包,暴露了传统令牌系统的根本性安全缺陷。
分阶段发布机制的技术实现
NPM 的分阶段发布机制始于 2021 年 12 月,当时 GitHub 开始逐步向所有包发布者推出增强型登录验证。这一机制的核心设计原则是渐进式部署与最小化中断,通过精心设计的阶段划分来平衡安全性与可用性。
阶段划分策略
分阶段发布通常遵循以下技术模式:
-
观察阶段(2021 年 12 月 - 2022 年 1 月):向所有曾有过发布权限的账户推出增强型登录验证,通过邮件 OTP 作为额外认证层。这一阶段的关键指标是采用率与误报率,系统需要监控有多少用户成功完成验证,以及有多少合法发布被错误拦截。
-
强制执行阶段(2022 年 2 月起):首先对依赖数前 100 的包维护者强制执行 2FA,随后扩展到前 500 名。这一阶段的工程挑战在于回滚机制的设计 —— 当某个关键包的发布流程因 2FA 问题中断时,系统需要能够在 15 分钟内恢复服务。
-
令牌迁移阶段(2025 年 11 月):这是最复杂的阶段,涉及从经典令牌到粒度令牌的全面迁移。技术实现上需要处理令牌映射、权限继承和会话保持三个核心问题。
零停机迁移架构
实现零停机迁移的关键在于双轨运行架构。在迁移期间,系统需要同时支持经典令牌和粒度令牌,通过以下技术手段确保平滑过渡:
# 迁移期间的认证路由策略
authentication_routing:
classic_tokens:
enabled: true
expiration: "2025-11-19T23:59:59Z"
fallback_to_granular: true
granular_tokens:
enabled: true
default_2fa: true
max_lifetime_days: 90
trusted_publishing:
preferred: true
oidc_providers: ["github", "gitlab", "azure"]
这种架构允许 CI/CD 流水线在迁移期间继续使用经典令牌,同时逐步切换到粒度令牌或可信发布。监控系统需要实时追踪令牌使用分布,当经典令牌使用率低于 5% 时,可以安全地完全禁用经典令牌路径。
从经典令牌到粒度令牌的安全架构设计
经典令牌的主要安全缺陷在于其无限制权限和无限期有效期。一个被泄露的经典令牌可以访问用户的所有包,并且除非手动撤销,否则永久有效。粒度令牌通过三个核心设计解决了这些问题:
1. 权限最小化原则
粒度令牌遵循最小权限原则,每个令牌只能访问特定的包或组织。技术实现上,这需要权限矩阵的精细设计:
// 粒度令牌权限矩阵示例
const granularPermissions = {
read: ["metadata", "versions", "download"],
write: ["publish", "deprecate", "unpublish"],
settings: ["2fa", "collaborators", "webhooks"],
// 包级权限控制
packageScopes: {
exact: ["@org/package-a", "@org/package-b"],
pattern: ["@org/*"], // 通配符支持
exclude: ["@org/internal-*"] // 排除模式
}
};
2. 生命周期管理
粒度令牌引入了严格的生命周期控制:
- 最大生命周期:90 天(可配置为 7、30、90 天)
- 自动续期:支持自动续期,但需要重新认证
- 使用频率监控:低频使用的令牌会被标记为可疑
工程实现上,这需要令牌状态机的设计:
class TokenStateMachine:
STATES = ["ACTIVE", "EXPIRING_SOON", "EXPIRED", "REVOKED"]
def transition(self, token, event):
if event == "USED":
token.last_used = datetime.now()
if token.days_since_creation > 80:
self.notify_expiring(token)
elif event == "RENEW_REQUEST":
if self.validate_renewal(token):
token.expires_at += timedelta(days=90)
token.state = "ACTIVE"
3. 2FA 集成架构
默认启用 2FA 是粒度令牌的核心安全特性。技术实现需要考虑多种 2FA 方法的兼容性:
- TOTP 支持:向后兼容,但新用户被引导使用 WebAuthn
- WebAuthn 集成:支持硬件安全密钥和生物识别
- CI/CD 特殊处理:提供 "绕过 2FA" 选项,但需要显式启用并记录审计日志
可信发布(OIDC)的工程化迁移方案
可信发布是 NPM 安全架构的终极目标,它完全消除了长期令牌的需求。技术实现基于 OpenID Connect(OIDC)协议,核心优势在于临时凭证和身份验证链。
OIDC 工作流架构
可信发布的工作流涉及三个主要组件:
- 身份提供者(如 GitHub Actions、GitLab CI)
- NPM 注册表(作为依赖方)
- OIDC 令牌服务
工作流程如下:
graph LR
A[CI/CD Pipeline] --> B[请求OIDC令牌]
B --> C[身份提供者验证]
C --> D[签发短期JWT]
D --> E[NPM验证JWT]
E --> F[发布包]
迁移路径设计
从经典令牌迁移到可信发布需要分阶段进行:
阶段 1:评估与准备(1-2 周)
- 审计现有 CI/CD 流水线中的令牌使用情况
- 识别依赖经典令牌的关键发布流程
- 建立监控基线(发布成功率、延迟、错误率)
阶段 2:并行运行(2-4 周)
- 在非关键流水线中启用可信发布
- 保持经典令牌作为回滚路径
- 收集性能和安全指标
阶段 3:全面切换(1 周)
- 逐步将关键流水线迁移到可信发布
- 监控发布成功率,确保不低于 99.9%
- 建立自动回滚机制
阶段 4:清理与加固(持续)
- 撤销所有经典令牌
- 实施令牌使用审计
- 建立异常检测规则
企业级配置示例
对于大型组织,可信发布需要更复杂的配置:
# 企业可信发布配置
trusted_publishing:
oidc_providers:
github:
allowed_repositories:
- "org/production-*"
- "org/libs/*"
required_environment: ["prod", "staging"]
max_token_lifetime: "10m"
gitlab:
allowed_groups: ["@org/engineering"]
branch_protection: true
require_approval: true
security_policies:
package_scope_validation: true
provenance_generation: true
audit_log_retention: "90d"
monitoring:
success_rate_threshold: 99.5%
latency_p95_threshold: "5s"
alert_on_suspicious_patterns: true
监控要点与回滚策略
关键监控指标
成功的迁移需要全面的监控覆盖:
- 发布成功率:按发布类型(经典 / 粒度 / 可信)分类监控
- 认证延迟:P95 和 P99 延迟,特别关注 2FA 验证时间
- 令牌使用模式:识别异常使用模式(如地理位置突变、频率异常)
- 错误分类:详细记录 401、403、429 等错误的原因
自动回滚机制
当监控系统检测到问题时,需要能够自动回滚:
class AutoRollbackSystem:
def __init__(self):
self.metrics = MetricsCollector()
self.rollback_triggers = {
"publish_success_rate < 95%": self.rollback_to_classic,
"auth_latency_p99 > 10s": self.enable_fallback,
"error_rate_4xx > 5%": self.investigate_and_rollback
}
def monitor_and_act(self):
for trigger, action in self.rollback_triggers.items():
if self.evaluate_trigger(trigger):
logger.warning(f"触发回滚条件: {trigger}")
action()
self.notify_team(trigger)
渐进式回滚策略
回滚不应该是一次性的全量操作,而应该是渐进式的:
- 受影响流水线隔离:首先回滚显示问题的特定流水线
- 按组织分批回滚:如果问题广泛,按组织维度分批回滚
- 功能标志控制:通过功能标志动态控制认证方法
- 金丝雀发布反转:将已迁移的流水线逐步切回旧系统
工程实践建议
基于实际迁移经验,我们总结出以下工程实践:
1. 迁移清单
在开始迁移前,确保完成以下检查:
- 所有维护者已启用 WebAuthn 2FA
- CI/CD 流水线支持 OIDC 令牌
- 监控系统已就位
- 回滚计划已文档化并测试
- 团队已进行迁移演练
2. 测试策略
迁移测试需要覆盖多个维度:
- 单元测试:验证令牌验证逻辑
- 集成测试:测试完整的发布流程
- 负载测试:模拟高并发发布场景
- 故障注入测试:测试网络分区、服务降级等情况
3. 文档与培训
成功的迁移离不开充分的准备:
- 编写详细的迁移指南
- 录制操作演示视频
- 建立 FAQ 和故障排除文档
- 提供迁移支持热线
未来展望
NPM 的安全架构演进不会止步于当前的可信发布。未来可能的发展方向包括:
- 基于属性的访问控制(ABAC):更细粒度的权限控制
- 零信任架构:每次访问都需要重新验证
- 机器学习异常检测:自动识别可疑的发布模式
- 去中心化身份:基于区块链的包发布者身份验证
结语
NPM 的分阶段发布与令牌迁移是一次典型的安全架构演进案例。它展示了如何在保持系统可用性的同时,逐步提升安全水位。关键的成功因素包括:精心设计的阶段划分、全面的监控覆盖、自动化的回滚机制,以及充分的团队准备。
对于正在或计划进行类似迁移的团队,最重要的建议是:不要试图一次性完成所有迁移。通过分阶段、可监控、可回滚的方式,逐步推进安全改进,才能在确保系统稳定性的同时,实现安全目标的持续提升。
资料来源:
- GitHub 博客:Our plan for a more secure npm supply chain (2025-09-22)
- BYBOWU 文章:NPM Token Migration: Beat the Nov 19 Deadline (2025-11-06)
- NPM 官方文档:Trusted Publishers and Granular Access Tokens