oss-harden
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseHarden the security posture of an open source repository
加固开源仓库的安全态势
This skill audits and repairs rather than generating from scratch. Every workflow, pipeline, or setting it touches already exists; the job is to read what is there before changing anything, then close the gap between that and the SEC rules below. Read every existing workflow or pipeline file, the existing dependency update configuration, and the current branch or protected-branch settings before proposing a single edit. Do not write a fresh workflow from a template; edit the one the project already has.
The concrete syntax for reading and fixing each control differs by forge, so it lives in one reference file per forge: references/github.md for GitHub Actions and repository settings, and references/gitlab.md for GitLab CI/CD and project settings. Read the matching file before writing any configuration or giving any settings instruction; do not carry a command, an API path, or a YAML key from one forge to the other by analogy, because GitLab has no direct equivalent for several GitHub controls and the reverse is also true.
此 skill 负责审计和修复,而不是从零生成。它触及的每个 workflow、pipeline 或 setting 都已存在;工作方式是先读取现状,再进行任何更改,然后弥补现状与下文 SEC rules 之间的差距。在提出任何一项编辑之前,读取所有现有 workflow 或 pipeline 文件、现有 dependency update 配置,以及当前 branch 或 protected-branch settings。不要基于 template 新建 workflow;应直接编辑项目已有的 workflow。
读取和修复每项 control 的具体 syntax 因 forge 而异,因此每种 forge 都有一个 reference file:references/github.md 用于 GitHub Actions 和 repository settings,references/gitlab.md 用于 GitLab CI/CD 和 project settings。在编写任何配置或给出任何 settings 指令之前,先读取对应的 reference file;不要把一个 forge 的 command、API path 或 YAML key 类推到另一个 forge,因为 GitLab 对若干 GitHub controls 没有直接等价项,反之亦然。
Scope
范围
The SEC rules belong here: R-SEC-01 pinned references, R-SEC-02 least-privilege permissions, R-SEC-03 dependency updates, R-SEC-04 branch protection, R-SEC-05 signed tags, R-SEC-06 GitLab pipeline inputs, R-SEC-07 untrusted input, R-SEC-08 committed lockfiles, R-SEC-09 static analysis, R-SEC-10 secret detection, R-SEC-11 vulnerability watching, R-SEC-12 required review, R-SEC-13 immutable release tags, R-SEC-14 update cooldown, and R-SEC-15 install scripts.
When exists at the repository root, read the group addressed to this skill and work from that. Each failing rule there carries the audit's evidence and that rule's text verbatim, so reading as well adds nothing. Where the file is absent, work from the request as usual.
oss-audit-report.mdCheck:STANDARD.mdThis skill owns the security posture of the workflow and pipeline files two other skills also write into, and the boundary is the rule area, not a description of files. decides what runs and when. writes the publish job. Treat every mutable external reference and every overly broad or implicit token permission in those files as work for this skill. writes CODEOWNERS but does not enforce it; enforcing code owner review is a branch or merge request protection setting, which belongs here. Do not decide what a job runs, add a product feature, or choose a registry authentication flow while working from this skill; note that the project needs it and hand the work to or .
oss-cioss-publishoss-communityoss-cioss-publishoss-writing本 skill 涵盖以下 SEC rules:R-SEC-01 固定引用、R-SEC-02 最小权限、R-SEC-03 依赖更新、R-SEC-04 branch protection、R-SEC-05 签名 tags、R-SEC-06 GitLab pipeline inputs、R-SEC-07 不可信输入、R-SEC-08 已提交 lockfiles、R-SEC-09 静态分析、R-SEC-10 secret detection、R-SEC-11 漏洞监控、R-SEC-12 强制审核、R-SEC-13 不可变 release tags、R-SEC-14 更新冷却期,以及 R-SEC-15 安装脚本。
当仓库根目录存在 时,读取其中分配给此 skill 的分组,并据此开展工作。其中每个 failing rule 都逐字包含审计证据和该 rule 的 文本,因此再读取 不会增加任何信息。如果该文件不存在,则照常根据请求开展工作。
oss-audit-report.mdCheck:STANDARD.md此 skill 负责 workflow 和 pipeline 文件的安全态势,而另外两个 skills 也会写入这些文件。边界由 rule area 决定,而不是由文件类型决定。 决定运行什么以及何时运行; 编写 publish job。将这些文件中的每一个可变外部引用,以及每一个过宽或隐式授予的 token 权限,都视为此 skill 的工作。若 已编写 CODEOWNERS 但未强制执行,则强制 code owner review 属于 branch 或 merge request protection settings,同样归入此 skill。处理此 skill 的工作时,不要决定某个 job 应运行什么、添加产品功能,或选择 registry authentication flow;应记录项目需要这些内容,并将相应工作移交给 或 。
oss-cioss-publishoss-communityoss-cioss-publishoss-writingPrinciples
原则
Verify every command, API path, and setting name against the current documentation for the platform in question before writing it down or telling the user to click it; a wrong path either does nothing or breaks the workflow. Where a control depends on the forge's plan tier, such as required merge request approvals on GitLab, say which tier it needs rather than assuming the free tier has it, and give the strongest control the project's actual tier supports.
Half of what this skill fixes is repository files, which it edits directly. The other half is settings on the forge that only a repository owner or maintainer can change: branch protection, merge request approval rules, the job token allowlist, the actions permissions policy. For that half, resolve the real owner, repository, and branch name from the repository's own data (the git remote, the manifest's repository field) and give the user a direct settings URL built from those, never a placeholder like "go to your repository settings." Present one settings block, wait for the user to confirm it is done, then verify what can be verified from the command line before moving to the next block, the same pattern uses for trusted publisher setup.
oss-publish在写下或告知用户点击任何 command、API path 或 setting name 之前,先针对相应平台的当前 documentation 进行验证;错误的 path 可能完全不起作用,也可能破坏 workflow。对于依赖 forge plan tier 的 control,例如 GitLab 上 required merge request approvals,应明确说明需要哪个 tier,不要假定免费 tier 已具备该功能,并提供项目实际 tier 所支持的最高强度 control。
此 skill 修复的内容中,一半是 repository files,可直接编辑。另一半是 forge settings,只有 repository owner 或 maintainer 才能更改,包括 branch protection、merge request approval rules、job token allowlist 和 actions permissions policy。对于后一半,应从仓库自身的数据(git remote、manifest 的 repository field)解析出真实的 owner、repository 和 branch name,并向用户提供由这些信息构建的直接 settings URL,绝不能只给出“前往你的 repository settings”之类的占位说明。每次呈现一个 settings block,等待用户确认完成后,再通过 command line 验证可验证的内容,然后才进入下一个 block;这与 配置 trusted publisher 时采用的模式相同。
oss-publishProcess
Process
Step 1: Detect the forge
Step 1:检测 forge
Look for or , check the git remote for a github.com or gitlab host, or ask directly if neither signal is present. If the user states the forge explicitly, trust that over any signal found in the repository. This decides which reference file governs the rest of this process.
.github/workflows/.gitlab-ci.yml查找 或 ,检查 git remote 中是否存在 github.com 或 gitlab host;如果两者都没有信号,则直接询问用户。如果用户明确说明了 forge,则优先相信用户陈述,而不是仓库中找到的信号。该结果决定后续流程应使用哪个 reference file。
.github/workflows/.gitlab-ci.ymlStep 2: Read the current state
Step 2:读取当前状态
Settle the credential before the first call rather than discovering the gap on a failed one. Every read and write from here on goes through the forge's API.
On GitHub, the commands assume is installed and authenticated; prints the active account per host. The settings reads and writes need admin on the repository, which is also why is absent rather than for a caller without it.
ghgh auth statussecurity_and_analysisdisabledOn GitLab, an audit-only run and a repair run need different tokens, and the difference is worth keeping. The read set clears with a token carrying the scope, which GitLab documents as read access to the API. The write set needs , which GitLab documents as complete read and write access. Provision the read-only token for a sweep that only reports, so a run auditing the settings cannot rewrite them.
read_apiapiScope is not the whole answer, because several project-settings reads carry a role floor of their own. GitLab documents the job-token scope reads as needing the Maintainer or Owner role, and the security-settings read as needing Security Manager, Developer, Maintainer, or Owner. Both writes, the job-token allowlist and the security settings, need Maintainer or Owner. So a read-only sweep still needs a Maintainer token; do not assume the reads clear at a lower role than the writes.
Read a refusal carefully. GitLab documents 403 as a request that is not allowed and 404 as a resource that could not be accessed, which its own wording extends to a user who is not authorized to reach it. A read denied for role reasons can therefore answer 404, which reads like a control that is off and is not. Report it as unknown.
Supply the token from an environment variable read from the operator's own secret store, and never inline it. A token pasted into a command lands in shell history and in whatever transcript the run produces, which turns one audit into a leaked credential.
With that settled, read what already exists before changing anything, using the commands the matching reference file names for each of the following:
Every workflow or pipeline file, so the lines, and entries, and blocks that need fixing are known before any edit is proposed.
uses:image:include:permissions:Every or the repository ships, so composite action entries are also known.
action.ymlaction.yamluses:Every package manifest and lockfile the repository holds, including the ones under a documentation, example, or tooling directory and the ones nothing published depends on. The axis here is what is present, not what the project ships: a vulnerability arrives through any dependency the repository resolves, whether or not that dependency reaches a released artifact, and a repository that publishes nothing at all still resolves its own. Steps 5, 10, 11, and 12 all consume this list; establishing it once here is what stops each of them deriving its own. A repository with no registry dependencies has an empty list, which is an answer rather than a gap.
Read the file for each ecosystem on that list before configuring anything for it. Each one carries the updater's ecosystem key and whether security updates exist for it, the lockfile and the command that installs from it frozen, the analyzer the language has, and what watches it for known vulnerabilities:
- references/ecosystems/npm.md
- references/ecosystems/pypi.md
- references/ecosystems/rubygems.md
- references/ecosystems/crates.md
- references/ecosystems/go-modules.md
- references/ecosystems/packagist.md
- references/ecosystems/nuget.md
- references/ecosystems/maven-central.md
- references/ecosystems/hex.md
- references/ecosystems/pubdev.md
- references/ecosystems/containers.md
Container images are on that list because a pushed image carries dependencies of its own, and they are detected by a workflow pushing to a registry or an image already on the forge's registry, never by the presence of a Dockerfile. An ecosystem the repository holds with no file here is one this skill has not researched; say so rather than generalizing from a neighbouring ecosystem, because the four answers above differ per ecosystem in ways that do not follow from the language.
The existing dependency update configuration, if any: or a Renovate configuration on GitHub, a Renovate configuration on GitLab. Note which ecosystems it already covers and which it does not, rather than assuming none exists.
.github/dependabot.ymlThe current branch or merge request protection settings for the default branch, read from the forge's own API rather than assumed from the repository files, since a protected branch leaves no trace in the repository itself.
Whether the newest release tag is signed, if a release has shipped yet; a repository with no tags yet has nothing to check here, so say that plainly instead of treating it as a gap.
Verified 2026-08-07 against GitHub repository REST endpoints, GitLab token scopes, GitLab job token scope API, GitLab project security settings API, and the current sources named in .
references/ecosystems/在第一次调用之前先确定 credential,而不是等到调用失败后才 discovering gap。从这里开始,每次读取和写入都通过 forge 的 API 进行。
在 GitHub 上,相关 commands 假设已安装并认证 ; 会打印各 host 的当前账户。读取和写入 settings 需要 repository admin 权限;这也是调用者没有该权限时, 会表现为缺失而不是 的原因。
ghgh auth statussecurity_and_analysisdisabled在 GitLab 上,audit-only run 和 repair run 需要不同的 token,应保留这一区别。读取集合可通过携带 scope 的 token 完成,GitLab 将其定义为 API 读取权限。写入集合需要 scope,GitLab 将其定义为完整的 API 读取和写入权限。对于仅报告结果的 sweep,应配置 read-only token,使一次 audit 无法改写 settings。
read_apiapi仅凭 scope 并不能解决全部问题,因为若干 project-settings 读取操作还要求特定最低 role。GitLab 文档说明,读取 job-token scopes 需要 Maintainer 或 Owner role,读取 security settings 则需要 Security Manager、Developer、Maintainer 或 Owner。job-token allowlist 和 security settings 两项写入操作都需要 Maintainer 或 Owner。因此,即使只读 sweep 仍需要 Maintainer token;不要假定读取操作所需 role 低于写入操作。
仔细阅读拒绝响应。GitLab 文档将 403 定义为请求不被允许,将 404 定义为无法访问资源;其措辞同样涵盖无权访问的用户。因此,因 role 原因而被拒绝的读取可能返回 404,看起来像是某项 control 已关闭,但事实并非如此。必须将其报告为 unknown。
通过从操作人员自己的 secret store 读取的 environment variable 提供 token,绝不能将其内联到 command 中。粘贴进 command 的 token 会进入 shell history 和运行产生的 transcript,从而把一次 audit 变成 credential 泄露。
确定 credential 后,在更改任何内容之前,使用对应 reference file 为以下各项指定的 commands 读取已有配置:
每个 workflow 或 pipeline file,以便在提出任何编辑前了解需要修复的 lines、 和 entries,以及 blocks。
uses:image:include:permissions:仓库发布的每个 或 ,确保同时掌握 composite action 中的 entries。
action.ymlaction.yamluses:仓库持有的每个 package manifest 和 lockfile,包括 documentation、example 或 tooling 目录下的文件,以及不依赖任何已发布产物的文件。此处的判断依据是仓库中实际存在什么,而不是项目发布了什么:漏洞可能通过仓库解析的任何依赖进入,即使该依赖不会进入 released artifact,也完全不会发布任何内容的仓库仍会解析自身依赖。Steps 5、10、11 和 12 都会使用此列表;在这里一次性建立它,可以避免每一步各自推导。没有任何 registry dependencies 的仓库对应一个空列表,这是完整答案,而不是缺口。
在为此列表中的任何 ecosystem 配置任何内容之前,先读取该 ecosystem 对应的文件。每个文件都提供 updater 的 ecosystem key、是否存在 security updates、lockfile、从冻结状态安装所使用的 command、该语言对应的 analyzer,以及监控已知漏洞的机制:
- references/ecosystems/npm.md
- references/ecosystems/pypi.md
- references/ecosystems/rubygems.md
- references/ecosystems/crates.md
- references/ecosystems/go-modules.md
- references/ecosystems/packagist.md
- references/ecosystems/nuget.md
- references/ecosystems/maven-central.md
- references/ecosystems/hex.md
- references/ecosystems/pubdev.md
- references/ecosystems/containers.md
容器镜像也属于该列表,因为 pushed image 自身携带依赖。检测容器镜像的依据是存在向 registry 推送 image 的 workflow,或 forge registry 中已有 image,而不是仓库中是否包含 Dockerfile。若仓库持有某种 ecosystem,但没有对应的 reference file,说明此 skill 尚未研究该 ecosystem;应明确说明,而不能从相邻 ecosystem 泛化推导,因为上述四项答案会因 ecosystem 而异,不能直接类推。
现有 dependency update 配置,如 、GitHub 上的 Renovate 配置或 GitLab 上的 Renovate 配置。记录它已经覆盖哪些 ecosystems,以及未覆盖哪些,不要假定其不存在。
.github/dependabot.yml通过 forge 自身的 API 读取默认 branch 或 merge request 的当前 protection settings,不要根据 repository files 推测,因为 protected branch 不会在仓库自身留下痕迹。
如果已经发布过 release,检查最新 release tag 是否已签名。尚未发布任何 tag 的仓库没有需要检查的内容,应明确说明,而不要将其视为缺口。
已根据 GitHub repository REST endpoints、GitLab token scopes、GitLab job token scope API、GitLab project security settings API 以及 中指定的当前来源,验证于 2026-08-07。
references/ecosystems/Step 3: Pin third-party references to immutable content
Step 3:将第三方引用固定到不可变内容
On GitHub, every external line in every workflow and shipped composite action should resolve to a full 40-character commit SHA, including GitHub-owned actions and reusable workflows outside the current repository. Preserve the selected version in a trailing comment so a human and the updater can still read it. On GitLab, there is no action-shaped step; the same mutable-reference problem arrives through , , and external entries, so pin those instead, per R-SEC-06. Resolve the version the configuration already selects unless the user separately authorizes an upgrade. Check the newest compatible and newest major releases and report lag, but do not combine a security pin with a potentially breaking upgrade. The reference file for the detected forge gives the exact resolution process and the caveat that an annotated tag needs one extra step most naive lookups miss.
uses:uses:image:services:include:scripts/resolve-pin.mjssh
node skills/oss-harden/scripts/resolve-pin.mjs actions/checkout@v7 ruby/setup-ruby@v1
cat .github/workflows/ci.yml | node skills/oss-harden/scripts/resolve-pin.mjs -It dereferences an annotated tag to the commit it points at, which is the extra step, and it says when a ref is a branch rather than a tag, which is a finding in its own right: and are both branches, so a workflow naming either runs whatever was last pushed to it. Where the script is not installed, because a single skill was installed on its own, do the same work by hand, and dereference the annotated tag.
ruby/setup-ruby@v1rubygems/release-gem@v1A workflow or pipeline can also invoke a package installer or a tool installer directly, such as a or a of a package or a git source. R-SEC-01 and R-SEC-06 check only , , , and ; they do not extend to an installer command run inside a step, and no rule in reaches it, so do not cite a rule ID for what this paragraph produces.
pip installuv tool installuses:image:services:include:STANDARD.mdWhere such a command installs from a registry, R-SEC-08 already governs it through the lockfile, so read Step 10 rather than treating it here. Where it installs from a git URL or a piped script, check that the upstream project actually publishes what the command fetches: read the project's own repository or documentation and confirm the install path came from there, rather than working backward from a registry page, whose author, homepage, and repository fields are text the publisher typed. Report a command whose upstream cannot be established as a supply-chain observation in the summary, separate from the rule findings, and say plainly that it should not be installed until a maintainer decides. Do not resolve it by pinning it to a commit SHA. Pinning code whose origin is unestablished gives the same code at a known revision, and a maintainer reading a pinned line will reasonably assume someone already vouched for it.
在 GitHub 上,每个 workflow 和已发布 composite action 中的外部 line 都应解析为完整的 40-character commit SHA,包括 GitHub 自身的 actions 和当前仓库之外的可复用 workflows。在 trailing comment 中保留所选 version,以便开发者和 updater 仍能读取。
uses:在 GitLab 上,没有 action 形式的 step;同类可变引用问题会通过 、 和外部 entries 出现,因此应改为固定这些引用,遵循 R-SEC-06。除非用户另行授权升级,否则应解析并保留配置当前选择的 version。检查最新兼容 release 和最新 major release,并报告滞后情况,但不要将安全固定与可能造成 breaking changes 的升级合并执行。对应 forge 的 reference file 给出了准确的解析流程,以及大多数简单查询会遗漏的一步:annotated tag 还需要额外处理。
uses:image:services:include:scripts/resolve-pin.mjssh
node skills/oss-harden/scripts/resolve-pin.mjs actions/checkout@v7 ruby/setup-ruby@v1
cat .github/workflows/ci.yml | node skills/oss-harden/scripts/resolve-pin.mjs -它会将 annotated tag 解引用到其指向的 commit,这正是额外的一步;同时会指出某个 ref 是 branch 而不是 tag,而后者本身就是一项 finding: 和 都是 branches,因此 workflow 引用其中任何一个时,都会运行其最近一次 push 的内容。如果只单独安装了某一个 skill,导致该 script 未安装,则手动完成相同工作,并解引用 annotated tag。
ruby/setup-ruby@v1rubygems/release-gem@v1workflow 或 pipeline 还可能直接调用 package installer 或 tool installer,例如从 package 或 git source 执行 或 。R-SEC-01 和 R-SEC-06 只检查 、、 和 ,不涵盖 step 内运行的 installer command, 中也没有 rule 覆盖这种情况,因此不要为本段发现引用 rule ID。
pip installuv tool installuses:image:services:include:STANDARD.md如果此类 command 从 registry 安装内容,R-SEC-08 已通过 lockfile 对其进行治理,因此应读取 Step 10,而不要在这里处理。如果它从 git URL 或 piped script 安装内容,应检查上游 project 是否实际发布了该 command 获取的内容:读取项目自身的 repository 或 documentation,并确认安装路径来自这些来源,而不是根据 registry 页面反推,因为 registry 页面中的 author、homepage 和 repository fields 都只是发布者填写的文本。如果无法确定某个 command 的上游来源,应将其作为 supply-chain observation 单独写入 summary,不与 rule findings 混在一起,并明确说明在 maintainer 作出决定前不应安装。不要通过固定到 commit SHA 来解决来源不明的问题;固定来源未知的代码只会得到已知 revision 的同一份代码,而 maintainer 看到固定后的 line 时,会合理地认为已经有人为其来源作过担保。
Step 4: Set least-privilege permissions
Step 4:设置最小权限
On GitHub, every workflow needs a top-level block granting no more than , with any job that needs more declaring the extra scope at the job level instead of widening the top-level block, per R-SEC-02. This has no GitLab equivalent as a rule; GitLab's analogous control is the job token scope in Step 7. Read the reference file for the default a workflow gets when is absent entirely, since that default is not the same as writing explicitly and a workflow already at the safe default because a wider default was never in play still benefits from writing the block down.
permissions:contents: readpermissions:contents: read在 GitHub 上,每个 workflow 都需要顶层 block,最多授予 ;确实需要更多权限的 job 应在 job level 声明额外 scope,而不是扩大顶层 block,遵循 R-SEC-02。GitLab 没有与该 rule 等价的机制;GitLab 上类似的 control 是 Step 7 中的 job token scope。应读取 reference file,了解完全省略 时 workflow 获得的默认权限,因为该默认值与显式编写 不同。即使某个 workflow 当前已处于安全默认值——因为此前从未启用过更宽的默认值——显式写出该 block 仍然有益。
permissions:contents: readpermissions:contents: readStep 5: Configure automated dependency updates
Step 5:配置自动化依赖更新
Pinning freezes a reference until something unfreezes it; R-SEC-03 requires an updater covering both the project's application dependencies and its CI dependencies, on both forges. On GitHub, Dependabot's ecosystem is what proposes the SHA and comment update for every workflow this skill just pinned, so a this skill writes or edits needs that ecosystem alongside one entry for every ecosystem the manifests read in Step 2 declare; a that updates the package manager but leaves out re-freezes the actions the moment they are pinned. On GitLab, there is no equivalent of Dependabot shipped by the platform; the reference file names the documented alternative and what running it costs. Where a Renovate configuration already exists on either forge, extend its or presets rather than adding a competing Dependabot config; ask before replacing one updater with the other.
github-actionsdependabot.ymldependabot.ymlgithub-actionspackageRulesThe same configuration answers R-SEC-14, which asks the updater to wait before proposing a version that was published minutes ago. Add a block with to , or to a Renovate configuration, and set it in days rather than hours: the window this closes is the one between a compromised publish and somebody noticing, which the incidents it exists for have taken hours to close. The two updaters start from different places, so say which one the user is on. Renovate waits zero days until the key is set, and that repository has the window open today. Dependabot already delays a version update by three days on its own, so writing the number pins a value the platform chose and can change again, and gives the next reader a decision to audit. Write at least that three days there, because a smaller number shortens the delay. Then check what the setting reaches: a Dependabot or list and a Renovate override both leave a configuration that reads as set and covers little. The reference files carry the resolution rules for each. Neither setting delays a fix for a known vulnerability, so say that when the user asks what it costs them. Where the repository has no updater at all, R-SEC-03 is the finding and this rule is not a second one.
cooldowndefault-daysdependabot.ymlminimumReleaseAgeincludeexcludepackageRulesR-SEC-15 belongs to the same step because it is the other half of what an updater's pull request does to a runner. Read every CI step that installs registry dependencies, and check that it either declines dependency-supplied install code or that the repository commits the allowlist its package manager reads. The ecosystem reference gives the flag or the field for the manager in use, and names the ecosystems where no such control exists, which is most of them; where the rule falls outside, say so with the ecosystem named rather than reporting a gap the manager gives nobody a way to close. Where it does apply and the manager now blocks that code by default, write the flag or the allowlist anyway. A default that depends on which version of the manager the runner happens to ship is not evidence in a workflow file, which is the same reason R-SEC-08 asks for the frozen-install flag Yarn and pnpm already default to in CI.
Verified 2026-08-07 against GitHub Actions metadata syntax, GitHub secure use reference, Dependabot options, Dependabot cooldown guidance, Renovate configuration options, and the current sources named in . The two GitHub Dependabot pages conflict on the unset cooldown default.
references/ecosystems/固定引用会一直冻结,直到某项操作解除冻结;R-SEC-03 要求 updater 同时覆盖项目的 application dependencies 和 CI dependencies,并且两个 forge 都需要。
在 GitHub 上,Dependabot 的 ecosystem 会为此 skill 固定的每个 workflow 提出 SHA 和 comment 更新,因此此 skill 编写或编辑的 除了包含 Step 2 中 manifests 声明的每个 ecosystem,还必须包含 。如果 只更新 package manager,却遗漏 ,就会立即重新将 actions 固定住。
github-actionsdependabot.ymlgithub-actionsdependabot.ymlgithub-actions在 GitLab 上,平台没有自带与 Dependabot 等价的功能;reference file 会说明有文档依据的替代方案及其运行成本。如果任一 forge 上已经存在 Renovate 配置,应扩展其 或 presets,而不是添加相互竞争的 Dependabot 配置;在用一个 updater 替换另一个之前,应先征得用户同意。
packageRules同一配置也负责 R-SEC-14,该 rule 要求 updater 在提出刚刚发布的版本前等待一段时间。在 中添加带有 的 block,或在 Renovate 配置中添加 ,并以天而不是小时为单位。该机制要弥合的是恶意发布与有人发现恶意发布之间的时间窗口,而其针对的事件往往需要数小时才能关闭。两个 updater 的起始行为不同,因此应明确说明用户使用的是哪一个。
dependabot.ymldefault-dayscooldownminimumReleaseAge在设置该 key 之前,Renovate 的等待时间为零天,意味着当前仓库仍存在这个窗口。Dependabot 自身会延迟三天再进行 version update,因此写入该数字只是把平台选择的值固定下来;该值之后仍可能变化,同时也给下一位读者留下一个可审计的决策。Dependabot 至少应写入三天,因为更小的数字会缩短延迟时间。
随后检查该设置实际覆盖的范围。Dependabot 的 或 list,以及 Renovate 的 override,都可能让配置看起来已经设置,但实际覆盖范围很小;对应的 resolution rules 位于 reference files 中。
includeexcludepackageRules这两个设置都不会延迟已知漏洞的修复;如果用户询问其成本,应明确说明这一点。如果仓库完全没有 updater,R-SEC-03 本身就是 finding,此时不应再把该 rule 作为第二项 finding 报告。
R-SEC-15 也属于同一步,因为它是 updater 的 pull request 对 runner 产生影响的另一半。读取每个会安装 registry dependencies 的 CI step,并检查它是否拒绝执行 dependency 提供的安装代码,或者仓库是否提交了 package manager 会读取的 allowlist。
Ecosystem reference 会给出当前 package manager 使用的 flag 或 field,并指出哪些 ecosystems 没有这种 control,大多数 ecosystems 都属于后者。如果 rule 超出该 package manager 的能力范围,应明确说明并指出具体 ecosystem,而不是报告一个 package manager 无法解决的缺口。如果 rule 适用,并且该 manager 当前默认阻止这类代码,也仍应显式写入 flag 或 allowlist。依赖 runner 恰好安装的 manager 版本所决定的默认行为,不能作为 workflow file 中的证据;这与 R-SEC-08 要求 Yarn 和 pnpm 在 CI 中使用 frozen-install flag 的原因相同。
已根据 GitHub Actions metadata syntax、GitHub secure use reference、Dependabot options、Dependabot cooldown guidance、Renovate configuration options 以及 中指定的当前来源,验证于 2026-08-07。两个 GitHub Dependabot 页面对于未设置 cooldown 时的默认值存在冲突。
references/ecosystems/Step 6: Branch protection and required review
Step 6:Branch protection 与强制审核
R-SEC-04 requires the default branch to take changes only through a change request, require at least one CI status check to pass, and reject force pushes and deletion, on both forges. This is a forge setting, not a file, so give the user the resolved settings URL for the repository or project found in Step 1, wait for confirmation, then verify with the read command from Step 2. On GitHub, set this up as a repository ruleset; classic branch protection is the older form, still enforced where it already exists and worth reading, but not what to create today. Every control in this paragraph works without a second person, so set all of them whatever the repository's size.
scripts/ruleset.mjssh
node skills/oss-harden/scripts/ruleset.mjs get <owner>/<repo> main
node skills/oss-harden/scripts/ruleset.mjs put <owner>/<repo> main patch.json
node skills/oss-harden/scripts/ruleset.mjs actor <login>put422 Invalid property /rules/N: data matches no possible inputcode_coveragemax_coverage_drop: nullactor{"actor_type": "User", "actor_id": N}RepositoryRoleR-SEC-12 adds the approving review, and reaches only a repository where two or more principals hold push, maintain, or admin access. Establish that first by reading the access list the rule's check names, and report the rule as outside the repository when one principal holds every merge path. Do not report a violation there, and do not set the requirement anyway and then add a bypass entry to undo it; that trades a control the repository had for a status that blocks nobody. Where the rule does reach, set the review requirement alongside the controls above, and where has already written a CODEOWNERS file, turn on the code-owner-approval setting too; a CODEOWNERS file with nothing enforcing it is a suggestion, not a control. Where the platform's free tier does not offer enforced review, as GitLab's does not, say so plainly and give the strongest control the tier actually has; take that fallback only because the tier leaves no other option, the same spirit states for the fallbacks it marks below the bar, and do not claim a paid capability is active on a free plan.
oss-communitySTANDARD.mdR-SEC-04 要求在两个 forge 上,默认 branch 都只能通过 change request 接收更改,至少要求一个 CI status check 通过,并拒绝 force push 和删除。这是 forge setting,而不是 file,因此应向用户提供 Step 1 中找到的 repository 或 project 对应的 settings URL,等待确认,然后使用 Step 2 中的 read command 进行验证。
在 GitHub 上,应将这项配置建立为 repository ruleset;classic branch protection 是较旧的形式,在已经启用的位置仍会生效,也值得读取,但不应作为今天新建的配置。本段中的每项 control 都不需要第二个人,因此无论仓库大小,都应全部启用。
scripts/ruleset.mjssh
node skills/oss-harden/scripts/ruleset.mjs get <owner>/<repo> main
node skills/oss-harden/scripts/ruleset.mjs put <owner>/<repo> main patch.json
node skills/oss-harden/scripts/ruleset.mjs actor <login>put422 Invalid property /rules/N: data matches no possible inputcode_coveragemax_coverage_drop: nullactor{"actor_type": "User", "actor_id": N}RepositoryRole如果未安装该 script,则手动完成相同的三项操作:按 id 而不是通过 list endpoint 读取 ruleset,因为 list endpoint 只返回省略 rules 的摘要;写入前移除每一个 null;然后重新读取结果,而不是根据 200 状态推断结果。
R-SEC-12 还增加了 approving review,并且只适用于有两个或更多 principals 拥有 push、maintain 或 admin access 的 repository。首先读取该 rule 的 所指定的 access list,以确认这一点。如果只有一个 principal 拥有全部 merge paths,应将该 rule 报告为仓库范围之外。不要报告违规,也不要在设置该要求后再添加 bypass entry 来撤销它;这种做法等于用一个无法阻止任何人的状态,替换掉仓库原本具有的 control。
Check:如果该 rule 确实适用,应与上述 controls 一并设置审核要求;如果 已经写入 CODEOWNERS file,还应启用 code-owner-approval setting。没有任何强制执行机制的 CODEOWNERS file 只是一个建议,而不是 control。
oss-community如果平台免费 tier 不提供强制审核,例如 GitLab,应明确说明,并提供该 tier 实际支持的最高强度 control。只有在 tier 不提供其他选择时才采用这种 fallback,这也符合 为低于标准所标记 fallbacks 所体现的原则。不要声称免费 plan 已启用付费能力。
STANDARD.mdStep 7: GitLab job token scope
Step 7:GitLab job token scope
R-SEC-06 also covers which other projects' job tokens may access this GitLab project. The allowlist is an inbound control on the target project, not a list of projects this pipeline's token may call. Read the current inbound scope and allowlist with the commands in ; where the scope is open, enable it and retain only source projects that need access. Where fine-grained job-token permissions are enabled, grant each allowlisted source only the endpoints it needs.
references/gitlab.mdR-SEC-06 还涵盖哪些其他 projects 的 job tokens 可以访问此 GitLab project。Allowlist 是目标 project 上的 inbound control,而不是当前 pipeline 的 token 可以调用的 project 列表。使用 中的 commands 读取当前 inbound scope 和 allowlist;如果 scope 处于开放状态,应启用它,并且只保留确实需要访问的 source projects。启用 fine-grained job-token permissions 时,仅为每个 allowlisted source 授予其所需 endpoints。
references/gitlab.mdStep 8: Signed and protected tags
Step 8:签名与受保护的 tags
R-SEC-05 applies the same way on both forges, because the evidence comes from git itself, not from a forge API. Fetch the tags, then establish what the tag is before reaching for any key: must print , and one read returns the tagger's address and the signature format together. Only then resolve which account publishes the key and fetch it; the repository owner is that account only when the owner is a user, and neither the release publisher nor the tagged commit's author is a substitute, because either can differ from the tagger. Support OpenPGP, SSH, and X.509 signatures instead of assuming every maintainer uses GPG. Fetching a key by account proves publication and not control, so neither that fetch nor either forge's UI verification badge replaces confirming the maintainer through a channel they control. Distinguish a failed verification from an unconfigured verifier: with SSH signatures and no allowed-signers file, git exits nonzero with a configuration error, which is the check failing to run and scores unknown rather than failed. The per-forge command sequences are in and . A repository with no release tag yet has nothing to check; say that rather than reporting a gap, and revisit at the first release.
git cat-file -t <tag>taggit for-each-refreferences/github.mdreferences/gitlab.mdR-SEC-13 is the other half of the same surface, and unlike R-SEC-05 it is a forge setting rather than evidence git can produce: a signature says who cut the tag, and nothing in git stops the same person or anyone else with push access repointing it afterwards. Both forges express it as a separate object from the branch protection Step 6 set, so read it separately and do not report a guarded default branch as covering tags. On GitHub it is a ruleset targeting tags; on GitLab it is protected tags. Take the settings-block pattern from the Principles section for whichever is missing, and read the result back with the command in the matching reference file. Where releases are cut by a workflow rather than a person, the principal permitted to create a tag is that workflow's identity, so establish who or what publishes before naming anyone. A repository with no release tag yet still takes this control, because setting it before the first release is what makes the first release immutable.
Verified 2026-08-07 against GitHub rules REST endpoints, GitHub ruleset rules, Git tag, Git configuration, GitLab merge request approvals, and GitLab protected tags.
R-SEC-05 在两个 forge 上的适用方式相同,因为证据来自 git 本身,而不是 forge API。首先获取 tags,然后在尝试使用任何 key 之前确定 tag 的类型: 必须输出 ,并且一次 读取应同时返回 tagger 的地址和 signature format。只有完成这些步骤后,才能确定哪个 account 发布该 key 并获取它。
git cat-file -t <tag>taggit for-each-ref只有当 repository owner 是用户账户时,它才对应 tagger;release publisher 或 tagged commit 的 author 都不能作为替代,因为二者可能与 tagger 不同。应支持 OpenPGP、SSH 和 X.509 signatures,不要假定每位 maintainer 都使用 GPG。
通过 account 获取 key 只能证明它已发布,不能证明 key 受到控制,因此获取 key 或 forge UI 中的 verification badge 都不能替代通过 maintainer 掌控的 channel 对其进行确认。必须区分验证失败和 verifier 未配置:使用 SSH signature 且没有 allowed-signers file 时,git 会以 configuration error 退出非零状态,这表示检查无法运行,应评为 unknown,而不是 failed。
每个 forge 的 command sequences 位于 和 。尚未发布任何 release tag 的仓库没有需要检查的内容;应明确说明,而不是报告缺口,并在首次发布时重新检查。
references/github.mdreferences/gitlab.mdR-SEC-13 是同一安全面的另一半,而且与 R-SEC-05 不同,它属于 forge setting,而不是 git 能够产生的证据。Signature 说明是谁创建了 tag,但 git 本身无法阻止同一个人或任何其他拥有 push access 的人事后将 tag 重新指向其他对象。
两个 forge 都将其表示为独立于 Step 6 所设 branch protection 的对象,因此应单独读取,不能把受保护的默认 branch 视为已经覆盖 tags。在 GitHub 上,它是指向 tags 的 ruleset;在 GitLab 上,它是 protected tags。针对缺失的配置,采用 Principles 部分中的 settings-block 模式,并使用对应 reference file 中的 command 回读结果。
如果 releases 由 workflow 而非个人创建,允许创建 tag 的 principal 就是该 workflow 的 identity,因此应先确定由谁或什么负责发布,再指定人员。尚未发布任何 release tag 的仓库仍然需要这项 control,因为在首次发布前完成设置,才能让首个 release 具备不可变性。
Step 9: Keep untrusted input out of privileged contexts
Step 9:将不可信输入排除在特权上下文之外
R-SEC-07 applies to both forges through different mechanisms, so read the matching reference file rather than carrying the syntax across. On GitHub the risk is textual: a expression is substituted into the script before any shell sees it, so inside a block executes whatever its author wrote. Move user-controlled values into an block and quote the environment variable in the shell. Separately, a or workflow runs with the base repository's privileges, so it must not execute or check out contributor-controlled code; where one does, that is the highest-severity finding this skill produces and it belongs at the top of the summary. On GitLab, quote every user-controlled predefined variable in and pass values as arguments rather than evaluating generated shell. Store secrets as masked and hidden variables, protect them when only protected refs need them, and keep variable-reference expansion disabled.
${{ }}${{ github.event.issue.title }}run:env:pull_request_targetworkflow_runscript:Verified 2026-08-07 against GitHub script injections, GitHub security, and GitLab CI/CD variables.
pull_request_targetR-SEC-07 通过不同机制同时适用于两个 forge,因此应读取对应的 reference file,而不要跨平台照搬 syntax。
在 GitHub 上,风险以文本形式出现: expression 会在 shell 看到 command 之前被替换,因此 block 中的 会执行其作者编写的任何内容。应将用户可控的值移入 block,并在 shell 中引用该 environment variable。
${{ }}run:${{ github.event.issue.title }}env:此外, 或 workflow 会以 base repository 的权限运行,因此不得执行或 checkout contributor-controlled code。若存在此类行为,这是此 skill 会发现的最严重 finding,应置于 summary 顶部。
pull_request_targetworkflow_run在 GitLab 上,应在 中引用每个用户可控的 predefined variable,并将值作为 arguments 传入,而不是对生成的 shell 进行求值。Secrets 应存储为 masked 和 hidden variables;当只有 protected refs 需要它们时应加以保护,并保持 variable-reference expansion 处于禁用状态。
script:已根据 GitHub script injections、GitHub security 和 GitLab CI/CD variables,验证于 2026-08-07。
pull_request_targetStep 10: Lockfile and frozen installs
Step 10:Lockfile 与 frozen installs
R-SEC-08 requires the package manager's lockfile to be committed and CI to install from it in a mode that fails rather than re-resolves. Working from the manifests Step 2 read, confirm each lockfile is committed rather than gitignored, and verify the current frozen-install command in that package manager's official documentation before changing CI. Examples include , , , , Bundler with local and configuration, and only when every requirement, including transitive dependencies, carries a hash. Where the project pins versions in a manifest but commits no lockfile, say that a version pin still trusts the registry to serve the same bytes while a recorded hash does not. A repository with no registry dependencies has nothing to lock; report that rather than a gap.
npm ciuv sync --lockedcargo build --lockedmix deps.get --check-lockeddeploymentfrozenpip install --require-hashesCommitting the file is the easy half, and the failures land in CI afterward rather than on the machine that generated it. Before opening the change, ask three questions of each ecosystem and answer them from that ecosystem's reference file and its package manager's own documentation.
Does the resolved set vary by runtime version? Where it does, generate the lock on the lowest runtime the matrix tests, or every job below that one fails at install rather than at test.
Does the repository hold locks the updater will not maintain? An updater finds lockfiles by exact filename inside the directories its configuration lists, so a lock outside either is invisible to it and drifts from the manifest the updater does bump. Give the project one command that regenerates every lock, and name it where a contributor merging an update will read it.
Does the lock have to cover a platform nobody develops on? A lock recording only the maintainer's platform leaves a Linux runner with no resolution to install.
Every reference file answers all three, under a heading naming what breaks after the file is committed, and several answer no with the documentation that settles it. Read that heading rather than reasoning from another ecosystem, because the answers differ: a Go module and a Cargo workspace are immune to the platform question by design, while npm and Bundler fail on it routinely.
R-SEC-08 要求提交 package manager 的 lockfile,并让 CI 以无法重新解析的 mode 从中安装。应根据 Step 2 读取的 manifests,确认每个 lockfile 都已提交,而不是被 gitignore,并核对当前 package manager 官方 documentation 中的 frozen-install command,再修改 CI。
示例包括 、、、、配置了 local 和 的 Bundler,以及仅当包括 transitive dependencies 在内的每个 requirement 都带有 hash 时才使用的 。
npm ciuv sync --lockedcargo build --lockedmix deps.get --check-lockeddeploymentfrozenpip install --require-hashes如果项目在 manifest 中固定了 versions,却没有提交 lockfile,应说明 version pin 仍然信任 registry 提供相同 bytes,而记录的 hash 不会。没有 registry dependencies 的仓库没有需要 lock 的内容;应如实报告,而不是将其视为缺口。
提交文件只是简单的一半,真正的失败会出现在之后的 CI 中,而不是生成 lockfile 的机器上。在发起更改之前,针对每个 ecosystem 提出以下三个问题,并根据该 ecosystem 的 reference file 和 package manager 自身 documentation 给出答案。
解析后的依赖集合是否会因 runtime version 而异?如果会,则应在 test matrix 覆盖的最低 runtime 上生成 lockfile;否则,更低版本上的所有 job 都会在 install 阶段失败,而不是在 test 阶段失败。
仓库是否包含 updater 不会维护的 locks?Updater 会在其配置列出的 directories 内按准确 filename 查找 lockfiles,因此位于这些位置之外的 lock 对 updater 不可见,并会与 updater 确实会提升版本的 manifest 发生漂移。应为项目提供一个用于重新生成所有 locks 的 command,并在合并更新的 contributor 能够看到的位置注明它。
Lock 是否需要覆盖一个无人开发但 CI 会使用的 platform?只记录 maintainer platform 的 lock 会使 Linux runner 没有可供安装的 resolution。
每个 reference file 都会在标题为“提交文件后会破坏什么”的章节下回答以上三个问题,其中多个会依据 documentation 给出否定答案。必须阅读相应标题,而不能从另一个 ecosystem 推导,因为答案各不相同:Go module 和 Cargo workspace 在设计上不受 platform 问题影响,而 npm 和 Bundler 经常受到影响。
Step 11: Static analysis on pull requests
Step 11:在 pull request 上运行静态分析
R-SEC-09 applies only where the repository holds source in a language a static analyzer supports, so establish that first and say plainly that the rule does not reach the repository when it does not, rather than reporting a violation. Count the workflow files when you establish that set. A CI configuration is itself source in an analyzed language, so a repository whose application code no analyzer supports is still inside this rule once it has workflows, and reporting it outside on the strength of its application language is a false clean over the surface an attacker reaches first. The reference files name what analyzes those files on each forge. Where it does apply, check that an analysis workflow runs on pull requests to the default branch and that its result is a required check, since an analyzer whose failure does not block merge is advisory. On GitHub, use CodeQL advanced setup whenever its result becomes a required check. Default setup skips fork pull requests and, in a measured run, Dependabot's, and a required check that never reports blocks the pull request. Default setup fits only a repository that gets neither. Turning off an existing default setup weakens a security control, so present it as a settings block the user confirms rather than an API write. Give the resolved settings URL the same way Step 6 does; the reference file names the endpoint that reports whether it is already enabled. Where Step 6 set up a ruleset, add its code scanning rule too, which blocks a merge on what the analysis found rather than only on whether it reported, and so also catches the case where the analysis configuration is deleted and the required check simply stops appearing.
R-SEC-09 仅适用于仓库包含受支持的静态 analyzer 所能分析语言源码的情况,因此应先确认这一点;如果不适用,应明确说明该 rule 不适用于此仓库,而不是报告违规。建立该集合时,要计算 workflow files 的数量。
CI configuration 本身就是受分析语言中的源码,因此,即使 application code 不受任何 analyzer 支持,只要仓库包含 workflows,仍属于该 rule 的适用范围。仅根据 application language 将其报告为范围之外,会对攻击者最先接触到的表面给出虚假的 clean 结论。对应 reference files 指出了每个 forge 上用于分析这些 files 的工具。
如果该 rule 适用,应检查 analysis workflow 是否会在面向默认 branch 的 pull requests 上运行,并确认其结果是 required check,因为 analyzer 失败时若不能阻止 merge,就只是 advisory。
在 GitHub 上,只要 CodeQL 结果会成为 required check,就应使用 CodeQL advanced setup。Default setup 会跳过 fork pull requests,并在一次实际测量中跳过 Dependabot 的 pull requests,因此一个从不报告结果的 required check 会阻塞 pull request。只有当仓库既没有 fork pull requests,也没有 Dependabot pull requests 时,default setup 才适用。
关闭现有 default setup 会削弱安全 control,因此应将其作为需要用户确认的 settings block 呈现,而不是直接通过 API 写入。应像 Step 6 一样提供解析后的 settings URL;reference file 会指出用于报告其是否已启用的 endpoint。
如果 Step 6 已建立 ruleset,还应加入其 code scanning rule。这样不仅会在 analysis 找到问题时阻止 merge,还能处理 analysis configuration 被删除、required check 只是不再出现的情况。
Step 12: Enable the detection controls this repository can actually use
Step 12:启用此仓库实际可用的 detection controls
R-SEC-10 and R-SEC-11 ask whether the repository receives signal: that a committed secret is caught before it reaches the default branch, and that every ecosystem the project ships is watched for known vulnerabilities. Every other rule in this area checks something the project controls. These two check whether anything is looking, which is what the pinning and locking in Steps 3, 5, and 10 assume when they freeze a dependency until somebody says it is now known-bad.
Do not work from a fixed list of switches. Derive the applicable set from three things already known: the forge from Step 1, the plan tier that forge reports for this repository or project, and the ecosystems the manifests and lockfiles read in Step 2 declare. A control the tier withholds is not a failing rule, and a control for an ecosystem the project does not ship is not a gap. Name both out loud rather than dropping them, so the reader can tell a control that does not reach this repository from one the sweep never considered.
For each control in that set, establish whether the forge exposes an API for it. Where it does, write the value and then read it back with a separate call. A write that returns success is not evidence: GitHub's repository answers 200 and silently discards a field the repository has no entitlement for, so a control can appear set and still be off. Where the forge exposes no API, which is true of several switches on the same settings page, resolve the settings URL for this repository and present it the way Step 6 does, then read back whatever the API can read and say plainly which part of the claim rests on the user's confirmation rather than on a reading.
PATCHA read can also fail to answer. On GitHub the object is absent rather than for a caller without admin, so a missing value means unknown, and reporting it as off would be wrong. Say which of the two readings you got.
security_and_analysisdisabledFor R-SEC-11, switching the forge's alerting on does not finish the check. Compare the package set the forge reports watching against what the project actually resolves: a forge that cannot parse the project's lockfile still parses its manifests, so the security overview looks the same whether it covers ten packages or five hundred. Where the two sets differ, that residual is the finding, and the fix is a scanner that reads the lockfile the forge cannot. Scope what blocks a merge to what the change introduces, and let the repository-wide scan report instead. An advisory published overnight against a dependency with no fix available is not something a contributor can act on, and a check that fails for that reason teaches the maintainer to dismiss alerts.
Verified 2026-08-07 against the current package-manager sources named in , GitHub code scanning setup types, GitHub repository REST endpoints, GitHub dependency graph ecosystems, GitLab SAST, GitLab secret detection, and GitLab dependency scanning, and the workflow-file paragraph on 2026-08-11 against CodeQL Actions built-in queries.
references/ecosystems/R-SEC-10 和 R-SEC-11 关注仓库能否收到信号:在已提交的 secret 到达默认 branch 之前捕获它,以及监控项目发布的每个 ecosystem 是否存在已知漏洞。该区域中的其他 rules 检查的是项目能够控制的 things,而这两条 rules 检查的是是否有机制在观察;Steps 3、5 和 10 中的固定与锁定都依赖这种信号,因为它们会冻结依赖,直到有人指出该依赖已知存在问题。
不要依赖固定的 switch 列表。应根据三项已知信息推导适用集合:Step 1 的 forge、forge 为此 repository 或 project 报告的 plan tier,以及 Step 2 中 manifests 和 lockfiles 声明的 ecosystems。
Tier 不提供的 control 不是 failing rule,project 未发布的 ecosystem 所需 control 也不是 gap。必须明确说出这两类情况,而不是直接忽略,以便读者能够区分 control 确实不适用于此仓库,还是 sweep 根本没有考虑它。
对于该集合中的每项 control,先确定 forge 是否提供对应 API。如果提供,则写入该值,然后通过单独一次调用回读。一次成功返回的写入不能作为证据:GitHub repository 的 会返回 200,并静默丢弃 repository 无权设置的 field,因此 control 可能显示为已设置,实际却处于关闭状态。
PATCH如果 forge 没有提供 API——同一 settings page 上的若干 switches 确实如此——则为当前 repository 解析 settings URL,并像 Step 6 一样呈现它。随后回读 API 能够读取的内容,并明确说明哪些结论依赖用户确认,而不是依赖读取结果。
读取也可能无法给出答案。在 GitHub 上,对于没有 admin 权限的调用者, object 会缺失而不是 ;缺失值意味着 unknown,将其报告为 off 则是错误的。必须说明获得了哪一种读取结果。
security_and_analysisdisabled对于 R-SEC-11,仅开启 forge 的 alerting 并不能完成检查。应将 forge 报告为正在监控的 package set 与项目实际解析的 package set 进行比较:无法解析项目 lockfile 的 forge 仍会解析其 manifests,因此无论 security overview 覆盖十个还是五百个 packages,看起来都完全相同。
当两者存在差异时,剩余部分就是 finding,修复方式是使用能读取 forge 无法解析之 lockfile 的 scanner。应将阻止 merge 的范围限定为当前更改所引入的问题,让 repository-wide scan 负责整体报告。
如果某个 advisory 在一夜之间发布,而对应 dependency 没有可用修复,contributor 无法采取行动;让 check 因此失败只会教会 maintainer 忽略 alerts。
已根据 中指定的当前 package-manager 来源、GitHub code scanning setup types、GitHub repository REST endpoints、GitHub dependency graph ecosystems、GitLab SAST、GitLab secret detection、GitLab dependency scanning,以及 CodeQL Actions built-in queries,验证于 2026-08-07;其中 workflow-file 段落验证于 2026-08-11。
references/ecosystems/Step 13: Read OpenSSF Scorecard results
Step 13:读取 OpenSSF Scorecard 结果
Use Scorecard as supplementary evidence after the direct checks above, never as a substitute for them. A missing or stale result is normal and must not change a directly verified rule status. Do not offer to install or run another scanner unless the user asks for that expansion.
The Scorecard section of the reference file Step 1 chose carries the rest: for what the API returns, which checks map to which rule, and how scores a repository that R-SEC-12 does not reach; for what the public dataset covers on that forge.
references/github.mdBranch-Protectionreferences/gitlab.mdScorecard 只能作为上述直接检查之后的补充证据,绝不能替代直接检查。结果缺失或过期属于正常情况,不应改变任何直接验证过的 rule status。除非用户要求扩大扫描范围,否则不要主动安装或运行其他 scanner。
Step 1 选定的 reference file 中包含其余相关细节: 说明 API 返回内容、哪些 checks 对应哪些 rules,以及 如何为 R-SEC-12 不适用的 repository 评分; 说明该 forge 上的 public dataset 覆盖范围。
references/github.mdBranch-Protectionreferences/gitlab.mdStep 14: Present the result
Step 14:呈现结果
Before presenting, read each R-SEC rule's line in against the repository and its settings as they now stand, and fix what fails. Start the list again after each fix, since one setting can move another rule's evidence, and do not report done while any cited rule still fails, unless the maintainer declined its fix.
Check:STANDARD.mdGive every rule one of five statuses, and use no others: fixed, pending confirmation, outside the repository, unknown, or declined. Fixed means the file is written or the setting is read back. Pending confirmation means the settings block is presented and the user has not confirmed it. Outside the repository means the rule's own precondition does not reach this repository, such as a forge it does not apply to or a tier the project does not have. Unknown means the read could not answer, which Step 2 says a 404 can produce, and it is neither a pass nor a fail. Declined means the maintainer saw the fix and its cost and chose to leave the rule failing; record their reason next to it.
Show what Steps 2 through 13 found and fixed, grouped by rule ID: which files this skill edited directly, which settings still need the user's confirmation with the resolved URL for each, and the supply-chain observation from Step 3 if one applies. Where a Scorecard result was read in Step 13, include its dated findings as supplementary evidence alongside this skill's direct findings. Do not mark a rule fixed until the file is written or the setting is confirmed and verified; a settings block the user has not yet confirmed stays listed as pending.
Then close with what the maintainer could still turn on that this skill could not reach itself. Give each item what it does, why the skill could not set it, and what enabling it would take. Three kinds belong here: a control the forge exposes no API for, which only a click reaches; a control the forge's tier withholds, named with the tier it needs; and a control that strengthens the repository beyond what any rule in requires. Keep this list separate from the rule findings and label it as optional. A maintainer reading an unmet rule and an available extra in one list cannot tell which of the two the standard actually asks for, and the one that reads as less urgent is the one that gets skipped.
STANDARD.mdVerified 2026-08-07 against OpenSSF Scorecard checks and GitHub Scorecard guidance.
呈现结果之前,针对每条 R-SEC rule,根据 repository 及其当前 settings 读取 中该 rule 的 line,并修复所有失败项。每次修复后重新开始检查,因为一个 setting 可能改变另一条 rule 的证据。除非 maintainer 拒绝修复,否则在任何已引用 rule 仍然失败时,不得报告任务已经完成。
STANDARD.mdCheck:为每条 rule 使用以下五种状态之一,不得使用其他状态:fixed、pending confirmation、outside the repository、unknown、declined。
- fixed:文件已写入,或 setting 已回读验证。
- pending confirmation:settings block 已呈现,但用户尚未确认。
- outside the repository:该 rule 自身的前提条件不适用于此 repository,例如 forge 不支持该 rule,或 project 不具备所需 tier。
- unknown:读取无法给出答案;Step 2 已说明 404 可能产生这种结果,它既不是 pass,也不是 fail。
- declined:maintainer 已了解修复及其成本,但选择让该 rule 继续失败;应在其旁边记录原因。
展示 Steps 2 到 13 发现和修复的内容,并按 rule ID 分组:列出此 skill 直接编辑了哪些 files、哪些 settings 仍需用户确认并提供各自的解析后 URL,以及 Step 3 中存在 supply-chain observation 时给出该 observation。如果 Step 13 读取了 Scorecard 结果,应将其带日期的 findings 作为补充证据,与此 skill 的直接 findings 一并展示。
在文件尚未写入,或 setting 尚未得到确认和验证之前,不得将 rule 标记为 fixed;尚未得到用户确认的 settings block 必须保持 pending 状态。
最后,列出 maintainer 仍可启用、但此 skill 无法自行完成的 controls。每项都应说明其作用、此 skill 无法设置它的原因,以及启用它需要采取什么措施。
以下三类内容应归入该列表:forge 没有提供 API、只能通过点击才能完成的 control;forge 的 tier 明确不提供、需要指明所需 tier 的 control;以及能够进一步强化 repository、但 中任何 rule 都不要求的 control。
STANDARD.md应将该列表与 rule findings 分开,并将其标记为 optional。Maintainer 如果在一个列表中同时看到未满足的 rule 和可选的额外 control,就无法分辨标准实际要求的是哪一个;其中看起来较不紧急的一项往往会被跳过。
已根据 OpenSSF Scorecard checks 和 GitHub Scorecard guidance,验证于 2026-08-07。