@mrciphersmith/keryx 0.3.2 → 0.3.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +4745 -2482
- package/dist/core.js +66 -10
- package/package.json +1 -1
- package/src/gdskills/bundled/install-manifest.json +578 -2
- package/src/gdskills/bundled/rules/core/model-selection.mdc +18 -0
- package/src/gdskills/bundled/skills/orchestration/job-orchestrator/SKILL.md +1 -1
- package/src/gdskills/bundled/skills/planning/brainstorm/SKILL.md +1 -1
- package/src/gdskills/bundled/skills/planning/interviewer/SKILL.md +1 -1
- package/src/gdskills/bundled/skills/quality/deploy/SKILL.md +1 -1
- package/src/gdskills/bundled/skills/review/review-jev-contract/SKILL.md +193 -0
- package/src/gdskills/bundled/skills/review/review-orchestrator/SKILL.detail.md +81 -21
- package/src/gdskills/bundled/skills/review/review-orchestrator/SKILL.md +4 -4
- package/src/gdskills/bundled/stacks/c-cpp/agent-refs.json +4 -0
- package/src/gdskills/bundled/stacks/c-cpp/governance/eval.json +1777 -0
- package/src/gdskills/bundled/stacks/c-cpp/governance/scout.json +31 -0
- package/src/gdskills/bundled/stacks/c-cpp/pack.json +42 -0
- package/src/gdskills/bundled/stacks/c-cpp/rules/coding-style.mdc +80 -0
- package/src/gdskills/bundled/stacks/c-cpp/rules/patterns.mdc +87 -0
- package/src/gdskills/bundled/stacks/c-cpp/rules/security.mdc +90 -0
- package/src/gdskills/bundled/stacks/c-cpp/rules/testing.mdc +83 -0
- package/src/gdskills/bundled/stacks/c-cpp/skills/c-cpp-build-fix/SKILL.md +153 -0
- package/src/gdskills/bundled/stacks/c-cpp/skills/c-cpp-build-fix/evals.json +74 -0
- package/src/gdskills/bundled/stacks/c-cpp/skills/c-cpp-code-review/SKILL.md +132 -0
- package/src/gdskills/bundled/stacks/c-cpp/skills/c-cpp-code-review/evals.json +73 -0
- package/src/gdskills/bundled/stacks/c-cpp/skills/c-cpp-implementation/SKILL.md +151 -0
- package/src/gdskills/bundled/stacks/c-cpp/skills/c-cpp-implementation/evals.json +74 -0
- package/src/gdskills/bundled/stacks/c-cpp/skills/c-cpp-testing/SKILL.md +152 -0
- package/src/gdskills/bundled/stacks/c-cpp/skills/c-cpp-testing/evals.json +74 -0
- package/src/gdskills/bundled/stacks/ci-github-gitlab/agent-refs.json +4 -0
- package/src/gdskills/bundled/stacks/ci-github-gitlab/governance/eval.json +1295 -0
- package/src/gdskills/bundled/stacks/ci-github-gitlab/governance/scout.json +26 -0
- package/src/gdskills/bundled/stacks/ci-github-gitlab/pack.json +41 -0
- package/src/gdskills/bundled/stacks/ci-github-gitlab/rules/patterns.mdc +77 -0
- package/src/gdskills/bundled/stacks/ci-github-gitlab/rules/security.mdc +144 -0
- package/src/gdskills/bundled/stacks/ci-github-gitlab/skills/ci-pipeline-build-fix/SKILL.md +121 -0
- package/src/gdskills/bundled/stacks/ci-github-gitlab/skills/ci-pipeline-build-fix/evals.json +73 -0
- package/src/gdskills/bundled/stacks/ci-github-gitlab/skills/ci-pipeline-code-review/SKILL.md +139 -0
- package/src/gdskills/bundled/stacks/ci-github-gitlab/skills/ci-pipeline-code-review/evals.json +73 -0
- package/src/gdskills/bundled/stacks/ci-github-gitlab/skills/ci-pipeline-implementation/SKILL.md +147 -0
- package/src/gdskills/bundled/stacks/ci-github-gitlab/skills/ci-pipeline-implementation/evals.json +74 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/agent-refs.json +4 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/governance/eval.json +1881 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/governance/scout.json +33 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/pack.json +38 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/rules/coding-style.mdc +100 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/rules/patterns.mdc +107 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/rules/security.mdc +86 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/rules/testing.mdc +89 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/skills/dotnet-build-fix/SKILL.md +143 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/skills/dotnet-build-fix/evals.json +77 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/skills/dotnet-code-review/SKILL.md +121 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/skills/dotnet-code-review/evals.json +77 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/skills/dotnet-implementation/SKILL.md +134 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/skills/dotnet-implementation/evals.json +76 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/skills/dotnet-testing/SKILL.md +130 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/skills/dotnet-testing/evals.json +77 -0
- package/src/gdskills/bundled/stacks/docker-k8s-terraform/agent-refs.json +4 -0
- package/src/gdskills/bundled/stacks/docker-k8s-terraform/governance/eval.json +865 -0
- package/src/gdskills/bundled/stacks/docker-k8s-terraform/governance/scout.json +16 -0
- package/src/gdskills/bundled/stacks/docker-k8s-terraform/pack.json +46 -0
- package/src/gdskills/bundled/stacks/docker-k8s-terraform/rules/coding-style.mdc +74 -0
- package/src/gdskills/bundled/stacks/docker-k8s-terraform/rules/patterns.mdc +81 -0
- package/src/gdskills/bundled/stacks/docker-k8s-terraform/rules/security.mdc +146 -0
- package/src/gdskills/bundled/stacks/docker-k8s-terraform/rules/testing.mdc +61 -0
- package/src/gdskills/bundled/stacks/docker-k8s-terraform/skills/docker-k8s-terraform-build-fix/SKILL.md +151 -0
- package/src/gdskills/bundled/stacks/docker-k8s-terraform/skills/docker-k8s-terraform-build-fix/evals.json +74 -0
- package/src/gdskills/bundled/stacks/docker-k8s-terraform/skills/docker-k8s-terraform-review/SKILL.md +135 -0
- package/src/gdskills/bundled/stacks/docker-k8s-terraform/skills/docker-k8s-terraform-review/evals.json +76 -0
- package/src/gdskills/bundled/stacks/flutter-dart/agent-refs.json +4 -0
- package/src/gdskills/bundled/stacks/flutter-dart/governance/eval.json +1849 -0
- package/src/gdskills/bundled/stacks/flutter-dart/governance/scout.json +33 -0
- package/src/gdskills/bundled/stacks/flutter-dart/pack.json +41 -0
- package/src/gdskills/bundled/stacks/flutter-dart/rules/coding-style.mdc +98 -0
- package/src/gdskills/bundled/stacks/flutter-dart/rules/patterns.mdc +88 -0
- package/src/gdskills/bundled/stacks/flutter-dart/rules/security.mdc +91 -0
- package/src/gdskills/bundled/stacks/flutter-dart/rules/testing.mdc +101 -0
- package/src/gdskills/bundled/stacks/flutter-dart/skills/flutter-build-fix/SKILL.md +134 -0
- package/src/gdskills/bundled/stacks/flutter-dart/skills/flutter-build-fix/evals.json +79 -0
- package/src/gdskills/bundled/stacks/flutter-dart/skills/flutter-code-review/SKILL.md +124 -0
- package/src/gdskills/bundled/stacks/flutter-dart/skills/flutter-code-review/evals.json +74 -0
- package/src/gdskills/bundled/stacks/flutter-dart/skills/flutter-implementation/SKILL.md +139 -0
- package/src/gdskills/bundled/stacks/flutter-dart/skills/flutter-implementation/evals.json +77 -0
- package/src/gdskills/bundled/stacks/flutter-dart/skills/flutter-testing/SKILL.md +134 -0
- package/src/gdskills/bundled/stacks/flutter-dart/skills/flutter-testing/evals.json +74 -0
- package/src/gdskills/bundled/stacks/kotlin-android/agent-refs.json +4 -0
- package/src/gdskills/bundled/stacks/kotlin-android/governance/eval.json +1889 -0
- package/src/gdskills/bundled/stacks/kotlin-android/governance/scout.json +34 -0
- package/src/gdskills/bundled/stacks/kotlin-android/pack.json +38 -0
- package/src/gdskills/bundled/stacks/kotlin-android/rules/coding-style.mdc +89 -0
- package/src/gdskills/bundled/stacks/kotlin-android/rules/patterns.mdc +96 -0
- package/src/gdskills/bundled/stacks/kotlin-android/rules/security.mdc +90 -0
- package/src/gdskills/bundled/stacks/kotlin-android/rules/testing.mdc +89 -0
- package/src/gdskills/bundled/stacks/kotlin-android/skills/compose-implementation/SKILL.md +150 -0
- package/src/gdskills/bundled/stacks/kotlin-android/skills/compose-implementation/evals.json +77 -0
- package/src/gdskills/bundled/stacks/kotlin-android/skills/kotlin-android-build-fix/SKILL.md +151 -0
- package/src/gdskills/bundled/stacks/kotlin-android/skills/kotlin-android-build-fix/evals.json +76 -0
- package/src/gdskills/bundled/stacks/kotlin-android/skills/kotlin-android-code-review/SKILL.md +139 -0
- package/src/gdskills/bundled/stacks/kotlin-android/skills/kotlin-android-code-review/evals.json +78 -0
- package/src/gdskills/bundled/stacks/kotlin-android/skills/kotlin-android-testing/SKILL.md +131 -0
- package/src/gdskills/bundled/stacks/kotlin-android/skills/kotlin-android-testing/evals.json +77 -0
- package/src/gdskills/bundled/stacks/php-laravel/agent-refs.json +4 -0
- package/src/gdskills/bundled/stacks/php-laravel/governance/eval.json +1829 -0
- package/src/gdskills/bundled/stacks/php-laravel/governance/scout.json +33 -0
- package/src/gdskills/bundled/stacks/php-laravel/pack.json +41 -0
- package/src/gdskills/bundled/stacks/php-laravel/rules/coding-style.mdc +82 -0
- package/src/gdskills/bundled/stacks/php-laravel/rules/patterns.mdc +80 -0
- package/src/gdskills/bundled/stacks/php-laravel/rules/security.mdc +80 -0
- package/src/gdskills/bundled/stacks/php-laravel/rules/testing.mdc +82 -0
- package/src/gdskills/bundled/stacks/php-laravel/skills/php-laravel-build-fix/SKILL.md +143 -0
- package/src/gdskills/bundled/stacks/php-laravel/skills/php-laravel-build-fix/evals.json +74 -0
- package/src/gdskills/bundled/stacks/php-laravel/skills/php-laravel-code-review/SKILL.md +126 -0
- package/src/gdskills/bundled/stacks/php-laravel/skills/php-laravel-code-review/evals.json +76 -0
- package/src/gdskills/bundled/stacks/php-laravel/skills/php-laravel-implementation/SKILL.md +140 -0
- package/src/gdskills/bundled/stacks/php-laravel/skills/php-laravel-implementation/evals.json +75 -0
- package/src/gdskills/bundled/stacks/php-laravel/skills/php-laravel-testing/SKILL.md +124 -0
- package/src/gdskills/bundled/stacks/php-laravel/skills/php-laravel-testing/evals.json +74 -0
- package/src/gdskills/bundled/stacks/ruby-rails/agent-refs.json +4 -0
- package/src/gdskills/bundled/stacks/ruby-rails/governance/eval.json +1673 -0
- package/src/gdskills/bundled/stacks/ruby-rails/governance/scout.json +33 -0
- package/src/gdskills/bundled/stacks/ruby-rails/pack.json +42 -0
- package/src/gdskills/bundled/stacks/ruby-rails/rules/coding-style.mdc +69 -0
- package/src/gdskills/bundled/stacks/ruby-rails/rules/patterns.mdc +93 -0
- package/src/gdskills/bundled/stacks/ruby-rails/rules/security.mdc +90 -0
- package/src/gdskills/bundled/stacks/ruby-rails/rules/testing.mdc +89 -0
- package/src/gdskills/bundled/stacks/ruby-rails/skills/ruby-rails-build-fix/SKILL.md +143 -0
- package/src/gdskills/bundled/stacks/ruby-rails/skills/ruby-rails-build-fix/evals.json +73 -0
- package/src/gdskills/bundled/stacks/ruby-rails/skills/ruby-rails-code-review/SKILL.md +134 -0
- package/src/gdskills/bundled/stacks/ruby-rails/skills/ruby-rails-code-review/evals.json +71 -0
- package/src/gdskills/bundled/stacks/ruby-rails/skills/ruby-rails-implementation/SKILL.md +141 -0
- package/src/gdskills/bundled/stacks/ruby-rails/skills/ruby-rails-implementation/evals.json +72 -0
- package/src/gdskills/bundled/stacks/ruby-rails/skills/ruby-rails-testing/SKILL.md +125 -0
- package/src/gdskills/bundled/stacks/ruby-rails/skills/ruby-rails-testing/evals.json +72 -0
- package/src/gdskills/bundled/stacks/sql-db/agent-refs.json +4 -0
- package/src/gdskills/bundled/stacks/sql-db/governance/eval.json +1829 -0
- package/src/gdskills/bundled/stacks/sql-db/governance/scout.json +30 -0
- package/src/gdskills/bundled/stacks/sql-db/pack.json +40 -0
- package/src/gdskills/bundled/stacks/sql-db/rules/coding-style.mdc +69 -0
- package/src/gdskills/bundled/stacks/sql-db/rules/patterns.mdc +134 -0
- package/src/gdskills/bundled/stacks/sql-db/rules/security.mdc +74 -0
- package/src/gdskills/bundled/stacks/sql-db/rules/testing.mdc +83 -0
- package/src/gdskills/bundled/stacks/sql-db/skills/sql-db-build-fix/SKILL.md +147 -0
- package/src/gdskills/bundled/stacks/sql-db/skills/sql-db-build-fix/evals.json +72 -0
- package/src/gdskills/bundled/stacks/sql-db/skills/sql-db-code-review/SKILL.md +132 -0
- package/src/gdskills/bundled/stacks/sql-db/skills/sql-db-code-review/evals.json +73 -0
- package/src/gdskills/bundled/stacks/sql-db/skills/sql-db-implementation/SKILL.md +153 -0
- package/src/gdskills/bundled/stacks/sql-db/skills/sql-db-implementation/evals.json +77 -0
- package/src/gdskills/bundled/stacks/sql-db/skills/sql-db-testing/SKILL.md +129 -0
- package/src/gdskills/bundled/stacks/sql-db/skills/sql-db-testing/evals.json +73 -0
- package/src/gdskills/bundled/stacks/swift-ios/agent-refs.json +4 -0
- package/src/gdskills/bundled/stacks/swift-ios/governance/eval.json +1803 -0
- package/src/gdskills/bundled/stacks/swift-ios/governance/scout.json +32 -0
- package/src/gdskills/bundled/stacks/swift-ios/pack.json +38 -0
- package/src/gdskills/bundled/stacks/swift-ios/rules/coding-style.mdc +92 -0
- package/src/gdskills/bundled/stacks/swift-ios/rules/patterns.mdc +112 -0
- package/src/gdskills/bundled/stacks/swift-ios/rules/security.mdc +78 -0
- package/src/gdskills/bundled/stacks/swift-ios/rules/testing.mdc +90 -0
- package/src/gdskills/bundled/stacks/swift-ios/skills/swift-build-fix/SKILL.md +144 -0
- package/src/gdskills/bundled/stacks/swift-ios/skills/swift-build-fix/evals.json +75 -0
- package/src/gdskills/bundled/stacks/swift-ios/skills/swift-code-review/SKILL.md +122 -0
- package/src/gdskills/bundled/stacks/swift-ios/skills/swift-code-review/evals.json +75 -0
- package/src/gdskills/bundled/stacks/swift-ios/skills/swift-testing/SKILL.md +131 -0
- package/src/gdskills/bundled/stacks/swift-ios/skills/swift-testing/evals.json +75 -0
- package/src/gdskills/bundled/stacks/swift-ios/skills/swiftui-implementation/SKILL.md +149 -0
- package/src/gdskills/bundled/stacks/swift-ios/skills/swiftui-implementation/evals.json +76 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"query": "Use when authoring or extending a GitHub Actions workflow (.github/workflows/*.yml) or a GitLab CI pipeline (.gitlab-ci.yml) -- trigger and job design, reusable workflows/templates, caching, least-privilege permissions, and safe handling of untrusted pull-request/merge-request input.",
|
|
4
|
+
"decision": "create",
|
|
5
|
+
"topMatch": "ci-github-gitlab/ci-pipeline-code-review (0.57, raw tool decision: use)",
|
|
6
|
+
"recordedAt": "2026-09-25T15:30:00.000Z",
|
|
7
|
+
"skillName": "ci-pipeline-implementation",
|
|
8
|
+
"justification": "keryx skills scout --candidate <own-dir> scored the raw top match as this pack's own ci-pipeline-code-review (overlap 0.57, above the 0.55 'use' threshold) purely on shared CI/permissions/pull-request vocabulary -- but ci-pipeline-code-review is a read-only review skill (no edits, findings + fix direction only) and cannot substitute for authoring/extending a workflow file, which requires actually writing YAML and applying a fix. No skill outside this new pack scored above the fork threshold (nothing in ts-js-node/go/python/docker-k8s-terraform authors GitHub Actions or GitLab CI YAML specifically), so this is a genuine create, not a substitute for an existing implement-category skill."
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"query": "Use when reviewing a GitHub Actions workflow (.github/workflows/*.yml) or GitLab CI pipeline (.gitlab-ci.yml) change for security and structural risk -- pull_request_target combined with untrusted checkout, tag-pinned third-party actions, missing least-privilege permissions, script injection via unsanitized event/variable interpolation, and unprotected access to deploy secrets. Read-only, no edits.",
|
|
12
|
+
"decision": "create",
|
|
13
|
+
"topMatch": "ci-github-gitlab/ci-pipeline-implementation (0.37, raw tool decision: fork)",
|
|
14
|
+
"recordedAt": "2026-09-25T15:30:05.000Z",
|
|
15
|
+
"skillName": "ci-pipeline-code-review",
|
|
16
|
+
"justification": "keryx skills scout --candidate <own-dir> scored the raw top match as this pack's own ci-pipeline-implementation (overlap 0.37, above the 0.3 fork threshold) on shared CI/permissions/action vocabulary -- but ci-pipeline-implementation authors and edits workflow files, while this skill is strictly read-only (findings + fix direction, never an edit). The next-nearest matches outside this pack, docker-k8s-terraform's own review skill (0.17) and ts-js-node/nodejs-code-review (0.16), review Dockerfiles/Kubernetes manifests and JS/TS source respectively -- neither carries GitHub Actions/GitLab CI-specific vocabulary (pull_request_target, actions/checkout pinning, GITHUB_TOKEN permissions, protected GitLab variables), so this is a genuine create, not an adaptation of an existing review skill's content."
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"query": "Use when a GitHub Actions workflow run or GitLab CI pipeline is failing at the pipeline-configuration level -- YAML syntax errors, an invalid trigger/job key, a missing or misscoped permissions block breaking a step, a job failing because a protected variable is unavailable on its branch, or a broken needs:/rules:/dependency graph -- with the smallest root-cause fix to the config itself, not the application code it builds.",
|
|
20
|
+
"decision": "create",
|
|
21
|
+
"topMatch": "ci-github-gitlab/ci-pipeline-implementation (0.28)",
|
|
22
|
+
"recordedAt": "2026-09-25T15:30:10.000Z",
|
|
23
|
+
"skillName": "ci-pipeline-build-fix",
|
|
24
|
+
"justification": "keryx skills scout --candidate <own-dir> put every match below the 0.3 fork threshold (own-pack implementation/review skills at 0.28/0.24; docker-k8s-terraform-build-fix, python-build-fix, and nodejs-build-fix all around 0.18-0.19 on generic 'root cause'/'build'/'fail' vocabulary only, none of which cover GitHub Actions/GitLab CI syntax errors, needs:/rules: graph breaks, or GitLab protected-variable scoping) -- an unambiguous create, matching the tool's own raw decision."
|
|
25
|
+
}
|
|
26
|
+
]
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "ci-github-gitlab",
|
|
3
|
+
"family": "tool",
|
|
4
|
+
"modules": ["ci-github-gitlab-rules", "ci-github-gitlab-skills"],
|
|
5
|
+
"detectionMarkers": ["github-actions", "gitlab-ci"],
|
|
6
|
+
"provenance": {
|
|
7
|
+
"origin": "authored",
|
|
8
|
+
"sourceRef": "flow 338, Wave 4 batch 6"
|
|
9
|
+
},
|
|
10
|
+
"stability": "experimental",
|
|
11
|
+
"skills": {
|
|
12
|
+
"implement": ["ci-pipeline-implementation"],
|
|
13
|
+
"test": [],
|
|
14
|
+
"review": ["ci-pipeline-code-review"],
|
|
15
|
+
"build-fix": ["ci-pipeline-build-fix"],
|
|
16
|
+
"migrate": []
|
|
17
|
+
},
|
|
18
|
+
"notes": "No separate `test` skill: a CI workflow file has no independent test suite of its own beyond the platform linter (actionlint / GitLab CI Lint), which `ci-pipeline-implementation`'s own Verify step and `ci-pipeline-build-fix` already cover -- a dedicated `test` skill would duplicate one of those two rather than earn a distinct place. No `migrate` skill: migrating GitHub Actions runtime/syntax versions or GitLab CI schema changes did not reach the bar for a distinct skill in this pass (unlike `ts-js-node`/`react`'s ESM/version-upgrade migrations, there is no single recurring migration shape specific enough to write a focused skill and eval suite around yet) -- left for a future batch if a concrete need shows up.",
|
|
19
|
+
"agentProfile": {
|
|
20
|
+
"displayName": "CI (GitHub Actions & GitLab CI)",
|
|
21
|
+
"auditFocus": [
|
|
22
|
+
"a pull_request_target job that checks out github.event.pull_request.head.sha/.ref and then runs a build/test/lint step against it (the pwn-request pattern)",
|
|
23
|
+
"a third-party uses: action pinned to a mutable tag (@v4, @main) instead of a full commit SHA",
|
|
24
|
+
"a workflow-level permissions: write-all, or no permissions: block at all, on a workflow whose jobs do not all need write access",
|
|
25
|
+
"${{ github.event.* }} interpolated directly into a run: shell string instead of passed through env: first, or a GitLab CI script: line that unquotes an untrusted variable, feeds it to eval/sh -c, or reaches it through an unvalidated $[[ inputs.* ]] interpolation",
|
|
26
|
+
"a GitLab CI deploy job consuming a Protected+Masked variable with no rules: restricting it to the protected branch/tag that variable is exposed to",
|
|
27
|
+
"a cache key that never changes, or a job with no timeout-minutes/timeout on a step that can hang"
|
|
28
|
+
],
|
|
29
|
+
"buildCommands": [
|
|
30
|
+
"actionlint (GitHub Actions workflow files, when configured)",
|
|
31
|
+
"the project's own GitLab CI Lint / gitlab-ci-lint run (.gitlab-ci.yml, when configured)",
|
|
32
|
+
"re-trigger the specific failing run to confirm the named error is resolved"
|
|
33
|
+
],
|
|
34
|
+
"fixGuardrails": [
|
|
35
|
+
"never grant a broader permissions: scope than the specific failing step needs, and never at the workflow level to fix one job's failure",
|
|
36
|
+
"never unprotect a GitLab CI/CD variable, or remove a rules:/branch gate protecting it, just to make a job on the wrong branch pass",
|
|
37
|
+
"never disable or loosen a rules:/if: condition to make a job \"just run\" without understanding why it was scoped that way",
|
|
38
|
+
"never pin an action to a tag to work around a SHA-pinning lint failure instead of resolving to the correct commit SHA"
|
|
39
|
+
]
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
---
|
|
2
|
+
extends: common
|
|
3
|
+
paths: [".github/workflows/*.yml", ".github/workflows/*.yaml", ".github/actions/**/action.yml", ".github/actions/**/action.yaml", ".gitlab-ci.yml", ".gitlab/**/*.yml", ".gitlab/**/*.yaml"]
|
|
4
|
+
metadata:
|
|
5
|
+
origin: authored
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# CI workflow patterns (GitHub Actions & GitLab CI)
|
|
9
|
+
|
|
10
|
+
Narrows `core-common-rules`' stack-agnostic design guidance to workflow-YAML
|
|
11
|
+
structure and reuse. Applies only to GitHub Actions workflow files under
|
|
12
|
+
`.github/workflows/`, GitHub composite `action.yml`/`action.yaml` files
|
|
13
|
+
under `.github/actions/`, `.gitlab-ci.yml`, and GitLab CI included config
|
|
14
|
+
under `.gitlab/` — not to Kubernetes manifests,
|
|
15
|
+
Docker Compose files, or any other YAML the `docker-k8s-terraform` pack
|
|
16
|
+
already scopes to.
|
|
17
|
+
|
|
18
|
+
## Trigger and concurrency shape
|
|
19
|
+
|
|
20
|
+
- Scope `on:`/`rules:` to the events a workflow actually needs (a specific
|
|
21
|
+
branch, path filter, or event type) rather than firing on every push —
|
|
22
|
+
a broad trigger wastes runner minutes and widens the surface a
|
|
23
|
+
misconfigured job can affect.
|
|
24
|
+
- Add a `concurrency:` group (GitHub Actions) or a `resource_group` (GitLab
|
|
25
|
+
CI) to any workflow that deploys or mutates shared state, so a second
|
|
26
|
+
push does not race an in-flight run against the same target — a
|
|
27
|
+
`resource_group` serializes/queues jobs targeting the same resource,
|
|
28
|
+
which is the property a deploy job needs. Do not reach for
|
|
29
|
+
`interruptible: true` here: that flag means the OPPOSITE (this job is
|
|
30
|
+
SAFE to auto-cancel when a newer pipeline starts), which is right for a
|
|
31
|
+
build/test job made obsolete by a newer commit, not for a deploy already
|
|
32
|
+
in flight.
|
|
33
|
+
- Keep `pull_request` as the default trigger for anything that only builds
|
|
34
|
+
or tests untrusted contributions; reach for `pull_request_target` only
|
|
35
|
+
when the job genuinely needs base-repo secrets, and see
|
|
36
|
+
`rules/security.mdc` for what that requires.
|
|
37
|
+
|
|
38
|
+
## Job structure and reuse
|
|
39
|
+
|
|
40
|
+
- Give every job an explicit `name:` and a `timeout-minutes` (GitHub
|
|
41
|
+
Actions) or job-level `timeout` (GitLab CI) — an unbounded job can hang
|
|
42
|
+
a runner indefinitely on a stuck process or a flaky network call.
|
|
43
|
+
- Factor a step sequence used by more than one workflow into a reusable
|
|
44
|
+
workflow (`workflow_call`) or composite action (GitHub Actions), or an
|
|
45
|
+
`include:`/`extends:` template (GitLab CI), instead of copy-pasting the
|
|
46
|
+
same steps into every workflow file — the copy drifts the moment one
|
|
47
|
+
copy gets a fix the others do not.
|
|
48
|
+
- Pin a `runs-on`/`image:` to a specific, known runner or image tag rather
|
|
49
|
+
than a rolling `latest`, so a runner image update cannot silently change
|
|
50
|
+
build behavior underneath an unrelated change.
|
|
51
|
+
- Use a job-level `needs:` (GitHub Actions) or `needs:`/stage ordering
|
|
52
|
+
(GitLab CI) to express real dependencies between jobs explicitly, rather
|
|
53
|
+
than relying on default stage ordering to imply an ordering the config
|
|
54
|
+
does not actually state.
|
|
55
|
+
|
|
56
|
+
## Caching and artifacts
|
|
57
|
+
|
|
58
|
+
- Key a cache (`actions/cache`, GitLab's `cache:key`) off the lockfile or
|
|
59
|
+
dependency manifest hash, not a static string — a static key serves a
|
|
60
|
+
stale cache forever and a hash-based key invalidates exactly when the
|
|
61
|
+
dependency set actually changes.
|
|
62
|
+
- Scope `artifacts:`/`actions/upload-artifact` to the files a later job
|
|
63
|
+
actually consumes, with an explicit retention/expiry, rather than
|
|
64
|
+
uploading an entire working directory that balloons storage and
|
|
65
|
+
transfer time.
|
|
66
|
+
|
|
67
|
+
## Anti-patterns to flag
|
|
68
|
+
|
|
69
|
+
- A workflow that duplicates the same 10+ line step sequence across
|
|
70
|
+
multiple jobs or files instead of a reusable workflow, composite
|
|
71
|
+
action, or `include:`/`extends:` template.
|
|
72
|
+
- A job with no `timeout-minutes`/`timeout` on a step that can hang (a
|
|
73
|
+
network call, a wait loop, an interactive prompt the runner cannot
|
|
74
|
+
answer).
|
|
75
|
+
- A cache key that never changes (a literal string with no hash of the
|
|
76
|
+
lockfile), which either never invalidates or never actually caches
|
|
77
|
+
anything useful.
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
---
|
|
2
|
+
extends: common
|
|
3
|
+
paths: [".github/workflows/*.yml", ".github/workflows/*.yaml", ".github/actions/**/action.yml", ".github/actions/**/action.yaml", ".gitlab-ci.yml", ".gitlab/**/*.yml", ".gitlab/**/*.yaml"]
|
|
4
|
+
metadata:
|
|
5
|
+
origin: authored
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# CI workflow security (GitHub Actions & GitLab CI)
|
|
9
|
+
|
|
10
|
+
Narrows `core-common-rules`' stack-agnostic security rules to the risks
|
|
11
|
+
specific to workflow-YAML configuration. Applies only to GitHub Actions
|
|
12
|
+
workflow files under `.github/workflows/`, GitHub composite `action.yml`/
|
|
13
|
+
`action.yaml` files under `.github/actions/`, `.gitlab-ci.yml`, and GitLab
|
|
14
|
+
CI included config under `.gitlab/`.
|
|
15
|
+
|
|
16
|
+
## `pull_request_target` and untrusted fork code
|
|
17
|
+
|
|
18
|
+
- `pull_request_target` runs in the context of the **base** repository —
|
|
19
|
+
every repo secret the workflow references is available to it (secrets
|
|
20
|
+
are consumed, not read/write-scoped, so "access" is simply yes/no here),
|
|
21
|
+
and `GITHUB_TOKEN` gets read/write repository permissions by default
|
|
22
|
+
(unless a `permissions:` block explicitly narrows it) — even though the
|
|
23
|
+
triggering pull request can come from an untrusted fork.
|
|
24
|
+
- Never check out and then execute the pull request's own head ref/SHA
|
|
25
|
+
(`actions/checkout` with `ref: ${{ github.event.pull_request.head.sha }}`)
|
|
26
|
+
inside a `pull_request_target` job and then run a build/test/lint
|
|
27
|
+
command against it — that combination (checkout untrusted code + run it
|
|
28
|
+
with base-repo secrets) is the "pwn request" pattern: it hands an
|
|
29
|
+
attacker-controlled pull request everything it needs to exfiltrate
|
|
30
|
+
secrets or push with the workflow's own write access.
|
|
31
|
+
- If a `pull_request_target` job genuinely needs to read fork content
|
|
32
|
+
(e.g. to comment on a PR), check it out read-only and do not execute
|
|
33
|
+
anything from it — no `run:`, no build step, no test step against the
|
|
34
|
+
fork's own code — or route the job through a privilege-separated
|
|
35
|
+
`workflow_run` split (a build job with no secrets, a separate
|
|
36
|
+
privileged job that only consumes safe artifacts) instead.
|
|
37
|
+
- When a job only needs to build/test a contribution and does not need
|
|
38
|
+
base-repo secrets or write access, use `pull_request`, not
|
|
39
|
+
`pull_request_target` — when a `pull_request` run is triggered from a
|
|
40
|
+
fork, `GITHUB_TOKEN` (still the base repo's own token, not a separate
|
|
41
|
+
"fork token") gets read-only permissions, and no other repo secret is
|
|
42
|
+
passed to the runner at all.
|
|
43
|
+
|
|
44
|
+
## Pin third-party actions to a commit SHA
|
|
45
|
+
|
|
46
|
+
- Reference a third-party action by its full-length commit SHA
|
|
47
|
+
(`uses: actions/checkout@<40-char-sha>`), not a mutable tag like `@v4`
|
|
48
|
+
or a branch name — a tag can be moved by the action's own maintainer
|
|
49
|
+
(or, if the account is compromised, by an attacker) to point at a
|
|
50
|
+
different, malicious commit without the reference in your workflow ever
|
|
51
|
+
changing.
|
|
52
|
+
- A first-party action from `actions/*` maintained by GitHub itself is
|
|
53
|
+
lower risk than an arbitrary third-party action, but the SHA-pinning
|
|
54
|
+
rule still applies to any action outside the repository's own
|
|
55
|
+
`.github/actions/` — do not special-case a well-known org as exempt.
|
|
56
|
+
- When a comment noting the tag the SHA corresponds to helps readability
|
|
57
|
+
(`uses: actions/checkout@<sha> # v4.2.0`), that comment is documentation
|
|
58
|
+
only — it is the SHA, not the comment, that pins the actual code that
|
|
59
|
+
runs.
|
|
60
|
+
|
|
61
|
+
## Least-privilege `permissions:`
|
|
62
|
+
|
|
63
|
+
- Default to `permissions: {}` (or `permissions: read-all` only when read
|
|
64
|
+
access is genuinely needed everywhere) at the workflow level, then grant
|
|
65
|
+
the specific scopes a job needs (`contents: write`, `pull-requests:
|
|
66
|
+
write`, `id-token: write` for OIDC, etc.) at the **job** level, not the
|
|
67
|
+
workflow level — a workflow-level `permissions: write-all` (or no
|
|
68
|
+
`permissions:` key at all, which can default to broad access depending
|
|
69
|
+
on the repository/org setting) hands every job in the file whatever
|
|
70
|
+
scope the most-privileged job needed, even ones that only read.
|
|
71
|
+
- Grant `id-token: write` only to the job that actually performs OIDC
|
|
72
|
+
federation (cloud credential exchange); never at the workflow level
|
|
73
|
+
alongside jobs that do not need it.
|
|
74
|
+
|
|
75
|
+
## Script injection via untrusted expression expansion
|
|
76
|
+
|
|
77
|
+
- Never interpolate an untrusted `${{ github.event.* }}` value (a PR
|
|
78
|
+
title, issue body, commit message, branch name, or any other
|
|
79
|
+
attacker-influenced field) directly into a `run:` shell string — the
|
|
80
|
+
expression is substituted into the generated script **before** the
|
|
81
|
+
shell runs, so a value containing shell metacharacters (`"`, `` ` ``,
|
|
82
|
+
`$(...)`) can break out of the intended string and execute arbitrary
|
|
83
|
+
commands on the runner with whatever permissions that job has.
|
|
84
|
+
- Pass the untrusted value through an intermediate `env:` variable first,
|
|
85
|
+
then reference the environment variable (`"$TITLE"`, quoted) inside the
|
|
86
|
+
script, instead of interpolating `${{ github.event.pull_request.title
|
|
87
|
+
}}` directly into the `run:` string — the environment variable is set
|
|
88
|
+
in memory and never re-enters shell-script generation, which is what
|
|
89
|
+
actually neutralizes the injection.
|
|
90
|
+
- GitLab CI's own script-injection risk is NOT the same mechanism, and the
|
|
91
|
+
`env:`-indirection fix above does not carry over: every GitLab CI/CD
|
|
92
|
+
variable (a predefined one like `CI_MERGE_REQUEST_TITLE`, or one you
|
|
93
|
+
declare under `variables:`) is already an ordinary shell environment
|
|
94
|
+
variable by the time `script:` runs — there is no separate "before the
|
|
95
|
+
shell sees it" substitution step to intercept the way `${{ }}` needs
|
|
96
|
+
routing through `env:` first. Wrapping an already-a-variable in another
|
|
97
|
+
`variables:` entry changes nothing about how the shell expands it.
|
|
98
|
+
The real risks, and their real fixes:
|
|
99
|
+
- **Unquoted expansion**: `script: - rm -rf $CI_MERGE_REQUEST_TITLE` lets
|
|
100
|
+
the shell word-split and glob-expand an untrusted value; always quote
|
|
101
|
+
(`"$CI_MERGE_REQUEST_TITLE"`), and never build a command string by
|
|
102
|
+
concatenating an untrusted variable into one for `eval`/`sh -c`
|
|
103
|
+
(`eval "some-command $CI_MERGE_REQUEST_TITLE"` is exploitable however
|
|
104
|
+
the variable got its value).
|
|
105
|
+
- **Config-time interpolation via CI/CD inputs** (`$[[ inputs.x ]]`,
|
|
106
|
+
used in a component/included config's `rules:`/`script:`): this IS
|
|
107
|
+
substituted into the YAML/expression text before the job is even
|
|
108
|
+
created, the same class of risk `${{ }}` is in GitHub Actions — do not
|
|
109
|
+
feed an untrusted pipeline or trigger input value (a `spec:inputs`
|
|
110
|
+
value the pipeline/component was invoked with, not a predefined
|
|
111
|
+
variable like an MR title) into an `inputs:`-driven `$[[ ]]`
|
|
112
|
+
interpolation that reaches a `script:` line or a `rules:if:`
|
|
113
|
+
expression without validating/escaping it first.
|
|
114
|
+
|
|
115
|
+
## GitLab CI protected variables and branches
|
|
116
|
+
|
|
117
|
+
- A CI/CD variable marked "Protected" is only exposed to pipelines running
|
|
118
|
+
on a protected branch or a protected tag — an unprotected branch's
|
|
119
|
+
pipeline (including one from a fork merge request, when forks are
|
|
120
|
+
allowed) never receives it. Do not rely on a protected variable being
|
|
121
|
+
present in a job whose `rules:`/`only:`/`except:` allow it to run on a
|
|
122
|
+
non-protected ref; the value will simply be empty there, not an error.
|
|
123
|
+
- Mark any variable carrying a credential, deploy token, or signing
|
|
124
|
+
secret both "Protected" and "Masked" and restrict the job that consumes
|
|
125
|
+
it to protected branches/tags via `rules:` — a deploy job reachable from
|
|
126
|
+
an unprotected branch or an open merge request should not have access
|
|
127
|
+
to production credentials at all.
|
|
128
|
+
|
|
129
|
+
## Anti-patterns to flag
|
|
130
|
+
|
|
131
|
+
- A `pull_request_target` job that checks out `github.event.pull_request.head.sha`
|
|
132
|
+
(or `.ref`) and then runs a build/test/lint step against that checkout.
|
|
133
|
+
- Any `uses:` line pinned to a tag (`@v4`, `@main`) instead of a full
|
|
134
|
+
commit SHA for a third-party action.
|
|
135
|
+
- A workflow-level `permissions: write-all`, or no `permissions:` block at
|
|
136
|
+
all, on a workflow with jobs that do not all need write access.
|
|
137
|
+
- `${{ github.event.* }}` interpolated directly inside a `run:` string
|
|
138
|
+
instead of passed through `env:` first.
|
|
139
|
+
- A GitLab CI `script:` unquoted use of a variable holding untrusted input
|
|
140
|
+
(an MR title, a branch name), or that variable concatenated into a
|
|
141
|
+
string handed to `eval`/`sh -c`.
|
|
142
|
+
- An untrusted value reaching a GitLab CI/CD `inputs:`-driven `$[[ ]]`
|
|
143
|
+
interpolation in a `script:` line or a `rules:if:` expression with no
|
|
144
|
+
validation.
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ci-pipeline-build-fix
|
|
3
|
+
description: "Use when a GitHub Actions workflow run or GitLab CI pipeline is failing at the pipeline-configuration level -- YAML syntax errors, an invalid trigger/job key, a missing or misscoped permissions block breaking a step, a job failing because a protected variable is unavailable on its branch, or a broken needs:/rules:/dependency graph -- with the smallest root-cause fix to the config itself. Not for a failure in the application code the pipeline runs (a Python, TypeScript, Go, or other language compile/import/test error -- fix the code, or use that language's own build-fix skill), not for a Dockerfile/Kubernetes/Terraform build or validation failure (use docker-k8s-terraform-build-fix), and not for authoring a new workflow/pipeline from scratch (use ci-pipeline-implementation)."
|
|
4
|
+
triggers:
|
|
5
|
+
- "this GitHub Actions workflow fails to parse, what's wrong with the YAML"
|
|
6
|
+
- "our deploy step lost access to a GitLab variable it needs"
|
|
7
|
+
- "this job says permission denied writing to the PR, fix the workflow"
|
|
8
|
+
- "the pipeline says invalid needs: reference, fix the job graph"
|
|
9
|
+
- "GitLab CI says this job's rules: never match, why doesn't it run"
|
|
10
|
+
- "actions/checkout is failing with an unrecognized input, fix the workflow"
|
|
11
|
+
- "this step references a repo secret that doesn't exist, the job just fails silently"
|
|
12
|
+
metadata:
|
|
13
|
+
origin: authored
|
|
14
|
+
category: build-fix
|
|
15
|
+
version: "1.0.0"
|
|
16
|
+
compatible_harnesses: "claude,codex,cursor,zed,opencode"
|
|
17
|
+
license: "MIT"
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
# CI pipeline build-fix (GitHub Actions & GitLab CI)
|
|
21
|
+
|
|
22
|
+
Fix a GitHub Actions workflow or GitLab CI pipeline that is failing at the
|
|
23
|
+
**pipeline-configuration level**: YAML syntax, an invalid or misspelled
|
|
24
|
+
trigger/job key, a `permissions:` scope too narrow for what a step needs,
|
|
25
|
+
a job that cannot see a variable because of GitLab's protected-branch
|
|
26
|
+
scoping, or a broken `needs:`/`rules:`/dependency graph. Scoped to the
|
|
27
|
+
workflow file itself — a failure in the application code the pipeline
|
|
28
|
+
builds or tests belongs to that stack's own `build-fix`/`test` skill, not
|
|
29
|
+
here; do not widen a permissions grant or disable a `rules:` gate just to
|
|
30
|
+
make a run go green without understanding why it was scoped that way.
|
|
31
|
+
|
|
32
|
+
## Workflow
|
|
33
|
+
|
|
34
|
+
### Step 1: Read the actual failure, not just the job name
|
|
35
|
+
|
|
36
|
+
1. Get the exact error from the run log or the platform's own linter
|
|
37
|
+
(GitHub Actions' "Invalid workflow file" annotation, GitLab's CI Lint
|
|
38
|
+
page/`gitlab-ci-lint` output) — a config-level failure usually names
|
|
39
|
+
the exact line and key it choked on.
|
|
40
|
+
2. Classify the failure: YAML syntax (indentation, an unquoted string
|
|
41
|
+
colliding with a reserved word), an unknown/misspelled key, a
|
|
42
|
+
`permissions:` scope missing what a step needs (e.g. `Resource not
|
|
43
|
+
accessible by integration` writing a PR comment), a variable that is
|
|
44
|
+
`Protected` but the job runs on an unprotected branch, or a `needs:`/
|
|
45
|
+
`rules:`/dependency reference to a job name that does not exist or
|
|
46
|
+
never runs on this ref.
|
|
47
|
+
|
|
48
|
+
### Step 2: Fix the smallest root cause
|
|
49
|
+
|
|
50
|
+
- **YAML syntax**: fix the actual indentation/quoting/key at the named
|
|
51
|
+
line; do not restructure unrelated parts of the file.
|
|
52
|
+
- **Missing permission**: grant the specific scope the failing step
|
|
53
|
+
actually needs at the job level (not a blanket `write-all` at the
|
|
54
|
+
workflow level) — trace the failing API call back to the
|
|
55
|
+
[documented scope it requires](https://docs.github.com/en/actions/reference/authentication-in-a-workflow)
|
|
56
|
+
rather than guessing.
|
|
57
|
+
- **Protected variable unavailable**: either move the job's `rules:` so
|
|
58
|
+
it only runs on the protected branch/tag the variable is scoped to, or
|
|
59
|
+
— if the job genuinely must run elsewhere — use a separate,
|
|
60
|
+
non-protected variable there and keep the protected one for the
|
|
61
|
+
protected-branch job only. Never unprotect the variable just to make an
|
|
62
|
+
unprotected-branch job pass.
|
|
63
|
+
- **Broken `needs:`/`rules:`/dependency graph**: fix the job-name
|
|
64
|
+
reference or the ordering itself; confirm the referenced job actually
|
|
65
|
+
exists and actually runs on the ref this job runs on (a `needs:`
|
|
66
|
+
reference to a job whose own `rules:`/`if:` skips it on this ref is a
|
|
67
|
+
common cause of "job never starts").
|
|
68
|
+
|
|
69
|
+
### Step 3: Verify
|
|
70
|
+
|
|
71
|
+
- Re-run the platform's own linter (`actionlint`, GitLab's CI Lint page,
|
|
72
|
+
or a local `gitlab-ci-lint` run) against the fixed file.
|
|
73
|
+
- Confirm the fix does not widen `permissions:` past what the failing
|
|
74
|
+
step specifically needs, and does not remove a `rules:`/protected-branch
|
|
75
|
+
gate instead of routing the job to the right ref.
|
|
76
|
+
- Trigger (or describe how to trigger) the same failing run once more to
|
|
77
|
+
confirm the specific error is gone.
|
|
78
|
+
|
|
79
|
+
### Step 4: Report
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
Fixed: .github/workflows/deploy.yml
|
|
83
|
+
- permissions.pull-requests was missing "write"; the step posting a PR
|
|
84
|
+
comment needed it. Added at job level only (not workflow level).
|
|
85
|
+
- actionlint passes; re-run confirms the comment step now succeeds.
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Rules
|
|
89
|
+
|
|
90
|
+
- Fix the smallest root cause named by the actual linter/run-log error;
|
|
91
|
+
do not restructure unrelated jobs or steps while fixing one failure.
|
|
92
|
+
- Never grant a broader `permissions:` scope than the specific failing
|
|
93
|
+
step needs, and never grant it at the workflow level to fix a
|
|
94
|
+
single job's failure.
|
|
95
|
+
- Never unprotect a GitLab CI/CD variable, or remove a `rules:`/branch
|
|
96
|
+
gate protecting it, just to make a job on the wrong branch pass —
|
|
97
|
+
route the job to the right branch/ref, or use a separate variable
|
|
98
|
+
there.
|
|
99
|
+
- Never disable or loosen a `rules:`/`if:` condition to make a job "just
|
|
100
|
+
run" without first understanding why it was scoped the way it was.
|
|
101
|
+
|
|
102
|
+
## Red Flags
|
|
103
|
+
|
|
104
|
+
| Rationalization | Why it is wrong |
|
|
105
|
+
|---|---|
|
|
106
|
+
| "I'll set permissions: write-all, it's faster than figuring out which scope this needs" | That fixes this failure by silently widening every other job in the file too; the actual fix (one scope, one job) costs one extra line and leaves the rest of the file at its intended privilege |
|
|
107
|
+
| "This variable is protected and the job can't see it, I'll just uncheck Protected" | That exposes the credential to every unprotected branch's pipeline, including forks if allowed -- move the job to the protected ref instead, or use a separate non-protected variable for the non-protected case |
|
|
108
|
+
| "The rules: condition is why this job won't run, I'll just delete it" | Deleting the gate makes the job run everywhere the rule was written to exclude, which is usually the actual cause of the failure being investigated in the first place, not the fix for it |
|
|
109
|
+
|
|
110
|
+
## Verification
|
|
111
|
+
|
|
112
|
+
Do not report the fix done until all of the following hold:
|
|
113
|
+
|
|
114
|
+
- The platform's own linter (actionlint / GitLab CI Lint) passes against
|
|
115
|
+
the fixed file.
|
|
116
|
+
- The specific error named in the original failure is resolved, not
|
|
117
|
+
worked around by widening scope or disabling a gate.
|
|
118
|
+
- No `permissions:` grant was added beyond what the failing step
|
|
119
|
+
specifically needs.
|
|
120
|
+
- No `rules:`/protected-variable gate was removed or loosened as part of
|
|
121
|
+
the fix.
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
{
|
|
2
|
+
"triggers": {
|
|
3
|
+
"positive": [
|
|
4
|
+
"This workflow won't even parse -- GitHub says there's a YAML syntax error",
|
|
5
|
+
"Our deploy job can't read the API_KEY variable even though it's set in GitLab",
|
|
6
|
+
"The bot says Resource not accessible by integration when posting a PR comment",
|
|
7
|
+
"GitLab keeps saying this job's rules never match so it just gets skipped",
|
|
8
|
+
"actions/checkout is erroring out with an unrecognized input on this job",
|
|
9
|
+
"This job references another job in needs: that GitHub says doesn't exist"
|
|
10
|
+
],
|
|
11
|
+
"negative": [
|
|
12
|
+
"The build fails because a Python import is missing, fix the code",
|
|
13
|
+
"Our TypeScript compile step is failing with a type error, fix it",
|
|
14
|
+
"The Go binary fails go vet, please fix the source",
|
|
15
|
+
"This Dockerfile fails to build because of a missing apt package",
|
|
16
|
+
"Write a new workflow that lints our code on every push",
|
|
17
|
+
"Review this workflow for security issues before we merge it"
|
|
18
|
+
]
|
|
19
|
+
},
|
|
20
|
+
"scenarios": [
|
|
21
|
+
{
|
|
22
|
+
"id": "permission-scope-fix",
|
|
23
|
+
"prompt": "This job fails with 'Resource not accessible by integration' when it tries to comment on the pull request. The workflow currently has no permissions: block. How do I fix it?",
|
|
24
|
+
"strictness": "high",
|
|
25
|
+
"expected_behavior": [
|
|
26
|
+
{
|
|
27
|
+
"grader": "judge",
|
|
28
|
+
"rubric": "A correct answer identifies the missing pull-requests: write scope as the root cause of the 'Resource not accessible by integration' failure, fixes it by adding that specific scope (plus contents: read if the job checks out code) scoped no wider than the failing job needs -- at the job level when the workflow has other jobs that should not get it, or at the workflow level only if this is the workflow's only job -- and confirms the fix does not widen access beyond what the failing comment step needs (no workflow-level write-all, no unscoped grant reaching jobs that do not need it).",
|
|
29
|
+
"pass_criteria": [
|
|
30
|
+
"Identifies the missing permissions: pull-requests: write scope as the root cause of the 'Resource not accessible by integration' failure.",
|
|
31
|
+
"Scopes the grant to only what this job needs (pull-requests: write, plus contents: read if the job also checks out code) rather than a broad or unscoped grant -- placing it at the job level is the clearly correct choice when the workflow has other jobs that do not need it, and is an acceptable choice (not a requirement to fail on) when this is the workflow's only job.",
|
|
32
|
+
"States or confirms that the fix does not widen scope beyond what the failing comment step specifically needs."
|
|
33
|
+
],
|
|
34
|
+
"fail_criteria": [
|
|
35
|
+
"Recommends permissions: write-all (or another broad, unscoped grant, at either the workflow or job level) as the fix instead of the specific pull-requests: write (+ contents: read if needed) scope. Mentioning write-all only to warn against it is not a failure."
|
|
36
|
+
]
|
|
37
|
+
}
|
|
38
|
+
],
|
|
39
|
+
"calibration": {
|
|
40
|
+
"known_right": "The root cause is that the workflow has no permissions: block at all, so the default GITHUB_TOKEN scope doesn't include write access to pull requests, and the comment step fails with 'Resource not accessible by integration'. Fix: add `permissions: { contents: read, pull-requests: write }` directly on this job (not at the workflow level, so the rest of the file stays at its current, narrower scope) -- contents: read is included because setting any explicit permissions: block resets every unlisted scope to none, and this job still needs to check out the repo. That grants exactly the access the comment step needs and nothing more. Re-run the job after adding it to confirm the comment now posts successfully.",
|
|
41
|
+
"known_wrong": "Simplest fix here is to add `permissions: write-all` at the top of the workflow -- that guarantees this job (and anything else added later) has whatever access it needs without having to track down the exact scope each time.",
|
|
42
|
+
"vague": "You'll need to add the right permissions so the comment step can post.",
|
|
43
|
+
"subtle_wrong": "Add `permissions: { contents: write, pull-requests: write, issues: write }` at the workflow level -- that covers the comment step and leaves room for other jobs that might need similar write access later, so nobody has to keep revisiting the permissions block every time a new job needs something."
|
|
44
|
+
},
|
|
45
|
+
"anti_patterns": ["write-all"]
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"id": "protected-variable-fix",
|
|
49
|
+
"prompt": "The deploy job can't see DEPLOY_TOKEN even though it's defined in GitLab CI/CD variables and marked Protected. The pipeline runs on a feature branch, which is not a protected branch. How do I fix this?",
|
|
50
|
+
"strictness": "high",
|
|
51
|
+
"expected_behavior": [
|
|
52
|
+
{
|
|
53
|
+
"grader": "judge",
|
|
54
|
+
"rubric": "A correct answer identifies that DEPLOY_TOKEN being unavailable is because it is marked Protected and the pipeline is running on an unprotected feature branch, and fixes it by restricting the deploy job's rules: to the protected branch/tag (or by introducing a separate non-protected variable for the feature-branch case) rather than by unprotecting DEPLOY_TOKEN.",
|
|
55
|
+
"pass_criteria": [
|
|
56
|
+
"Identifies that DEPLOY_TOKEN is unavailable because it is a Protected variable and the pipeline is running on an unprotected feature branch, not because the variable is misconfigured or missing.",
|
|
57
|
+
"Recommends restricting the deploy job (via rules:) to run only on the protected branch/tag DEPLOY_TOKEN is exposed to, or introducing a separate non-protected variable for the feature-branch case.",
|
|
58
|
+
"Does not recommend removing Protected status from DEPLOY_TOKEN as part of the fix."
|
|
59
|
+
],
|
|
60
|
+
"fail_criteria": [
|
|
61
|
+
"Recommends unprotecting or removing Protected status from DEPLOY_TOKEN so it becomes available on the feature branch. Mentioning that this is what not to do is not a failure."
|
|
62
|
+
]
|
|
63
|
+
}
|
|
64
|
+
],
|
|
65
|
+
"calibration": {
|
|
66
|
+
"known_right": "DEPLOY_TOKEN is marked Protected in GitLab, which means it's only exposed to pipelines running on a protected branch or tag -- a feature branch pipeline simply never receives it, which is why it reads as empty/missing rather than erroring loudly. Fix: restrict this deploy job's rules: so it only runs on the protected branch (or tag) that DEPLOY_TOKEN is actually scoped to, rather than on every feature branch. If the deploy job genuinely needs to run from feature branches too (e.g. for a staging deploy), use a separate, non-protected variable for that case instead of exposing the production DEPLOY_TOKEN there.",
|
|
67
|
+
"known_wrong": "Easiest fix is to open the variable settings in GitLab and uncheck 'Protected' on DEPLOY_TOKEN -- then it'll be available to every branch's pipeline, including this feature branch, with no other changes needed.",
|
|
68
|
+
"vague": "You probably just need to adjust the variable settings so the deploy job can see it.",
|
|
69
|
+
"subtle_wrong": "Since the feature branch needs the token right now for testing, go ahead and unprotect DEPLOY_TOKEN temporarily -- you can always re-protect it later once the feature branch work is done and merged."
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
]
|
|
73
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ci-pipeline-code-review
|
|
3
|
+
description: "Use when reviewing a GitHub Actions workflow (.github/workflows/*.yml) or GitLab CI pipeline (.gitlab-ci.yml) change for security and structural risk -- pull_request_target combined with untrusted checkout, tag-pinned third-party actions, missing least-privilege permissions, script injection via unsanitized event/variable interpolation, and unprotected access to deploy secrets. Read-only, no edits."
|
|
4
|
+
triggers:
|
|
5
|
+
- "review this GitHub Actions workflow diff for security issues"
|
|
6
|
+
- "check this .gitlab-ci.yml change for exposed secrets"
|
|
7
|
+
- "does this workflow have a script injection risk?"
|
|
8
|
+
- "review this pull_request_target job for pwn request risk"
|
|
9
|
+
- "check whether these third-party actions are pinned safely"
|
|
10
|
+
- "review this deploy job for least-privilege permissions"
|
|
11
|
+
metadata:
|
|
12
|
+
origin: authored
|
|
13
|
+
category: review
|
|
14
|
+
version: "1.0.0"
|
|
15
|
+
compatible_harnesses: "claude,codex,cursor,zed,opencode"
|
|
16
|
+
license: "MIT"
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
# CI pipeline security review (GitHub Actions & GitLab CI)
|
|
20
|
+
|
|
21
|
+
Review a GitHub Actions workflow or GitLab CI pipeline change for the
|
|
22
|
+
specific risks `rules/security.mdc` catalogs: `pull_request_target`
|
|
23
|
+
combined with untrusted checkout, mutable-tag action pinning, missing
|
|
24
|
+
least-privilege `permissions:`, script injection via unsanitized
|
|
25
|
+
`${{ github.event.* }}`/CI-variable interpolation, and unprotected access
|
|
26
|
+
to deploy secrets. **This skill is strictly read-only** — it reports
|
|
27
|
+
findings and a fix direction, and never edits, patches, or claims to have
|
|
28
|
+
applied even a partial or proof-of-concept change to the workflow file
|
|
29
|
+
under review; a change belongs to `ci-pipeline-implementation`, not here.
|
|
30
|
+
|
|
31
|
+
## Workflow
|
|
32
|
+
|
|
33
|
+
### Step 1: Scope the diff
|
|
34
|
+
|
|
35
|
+
1. Read the full diff of every changed `.github/workflows/*.yml` or
|
|
36
|
+
`.gitlab-ci.yml` file, not just the added lines — a security-relevant
|
|
37
|
+
line (a `permissions:` block, a `uses:` pin) removed or weakened is as
|
|
38
|
+
much a finding as one added.
|
|
39
|
+
2. Note the trigger(s) each changed/added job runs under
|
|
40
|
+
(`pull_request`, `pull_request_target`, `push`, `workflow_dispatch`,
|
|
41
|
+
GitLab's `rules:`/pipeline source) — the trigger determines which
|
|
42
|
+
findings below even apply.
|
|
43
|
+
|
|
44
|
+
### Step 2: Check each risk category
|
|
45
|
+
|
|
46
|
+
- **`pull_request_target` + untrusted checkout.** Does any
|
|
47
|
+
`pull_request_target`-triggered job check out the contributor's own fork
|
|
48
|
+
ref or head commit SHA (via the pull request event's own `head` field)
|
|
49
|
+
and then run a build/test/lint step against that checkout?
|
|
50
|
+
That combination is the "pwn request" pattern — flag it regardless of
|
|
51
|
+
how innocuous the executed step looks, since the risk is the
|
|
52
|
+
combination, not any one line.
|
|
53
|
+
- **Action pinning.** Does every third-party `uses:` line (anything
|
|
54
|
+
outside the repository's own `.github/actions/`) reference a full
|
|
55
|
+
commit SHA rather than a tag (`@v4`) or branch name? A tag comment next
|
|
56
|
+
to a SHA is fine; a bare tag as the actual pin is not.
|
|
57
|
+
- **`permissions:` scope.** Is there a workflow-level `permissions:
|
|
58
|
+
write-all`, or no `permissions:` key at all on a workflow whose jobs do
|
|
59
|
+
not all need write access? Is `id-token: write` granted only to the job
|
|
60
|
+
that actually performs OIDC federation?
|
|
61
|
+
- **Script injection.** For GitHub Actions: does any `run:` step
|
|
62
|
+
interpolate `${{ github.event.* }}` (a PR title, issue body, branch
|
|
63
|
+
name, commit message) directly into the shell string, instead of
|
|
64
|
+
routing it through an intermediate `env:` entry first? For GitLab CI:
|
|
65
|
+
does a `script:` line use an untrusted variable (an MR title, a branch
|
|
66
|
+
name) unquoted, or concatenate it into a string handed to `eval`/
|
|
67
|
+
`sh -c` — routing it through another `variables:` entry does not fix
|
|
68
|
+
this, since every GitLab CI/CD variable is already a shell environment
|
|
69
|
+
variable by the time `script:` runs; also check any `$[[ inputs.* ]]`
|
|
70
|
+
interpolation for untrusted input, since that IS substituted before the
|
|
71
|
+
job is created.
|
|
72
|
+
- **GitLab protected variables/branches.** Does a deploy job that
|
|
73
|
+
consumes a credential-bearing variable have `rules:` restricting it to
|
|
74
|
+
the protected branch/tag that variable is actually exposed to, and is
|
|
75
|
+
that variable itself marked Protected and Masked?
|
|
76
|
+
|
|
77
|
+
### Step 3: Report findings
|
|
78
|
+
|
|
79
|
+
For each finding: name the exact line/job, state which risk category it
|
|
80
|
+
falls under, explain the concrete consequence (not just "this is
|
|
81
|
+
insecure"), and give a fix direction from `rules/security.mdc` — never
|
|
82
|
+
apply the fix yourself.
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
Findings in .github/workflows/pr-review.yml:
|
|
86
|
+
- Job "test" (line 12): triggered by pull_request_target, this job
|
|
87
|
+
checks out the fork's own head SHA and then runs `make test` against
|
|
88
|
+
it -- pwn-request pattern. Fix direction: switch to `pull_request`, or split
|
|
89
|
+
into an unprivileged build job + a separate privileged job that only
|
|
90
|
+
consumes safe artifacts.
|
|
91
|
+
- Job "test" (line 18): actions/checkout@v4 -- pinned to a mutable tag.
|
|
92
|
+
Fix direction: pin to actions/checkout's full commit SHA.
|
|
93
|
+
No other findings.
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Rules
|
|
97
|
+
|
|
98
|
+
- Read-only: report findings and fix direction, never edit the workflow
|
|
99
|
+
file or claim a fix was applied, even partially or as a proof of
|
|
100
|
+
concept.
|
|
101
|
+
- Flag `pull_request_target` + untrusted-checkout-and-execute as a
|
|
102
|
+
finding every time it appears, regardless of how trivial the executed
|
|
103
|
+
step looks.
|
|
104
|
+
- Flag any third-party action pinned to a tag rather than a full commit
|
|
105
|
+
SHA.
|
|
106
|
+
- Flag a workflow-level `permissions: write-all` (or no `permissions:` at
|
|
107
|
+
all) alongside jobs that do not all need write access.
|
|
108
|
+
- Flag `${{ github.event.* }}` interpolated directly into a `run:` string
|
|
109
|
+
instead of passed through `env:` first (GitHub Actions). Flag a GitLab
|
|
110
|
+
CI `script:` line that uses an untrusted variable unquoted or
|
|
111
|
+
concatenates it into `eval`/`sh -c` (routing it through another
|
|
112
|
+
`variables:` entry does not fix this), or an untrusted pipeline/trigger
|
|
113
|
+
input reaching a `$[[ inputs.* ]]` interpolation unvalidated.
|
|
114
|
+
|
|
115
|
+
## Red Flags
|
|
116
|
+
|
|
117
|
+
| Rationalization | Why it is wrong |
|
|
118
|
+
|---|---|
|
|
119
|
+
| "This is just a docs/CI change, security review doesn't apply" | A CI workflow change is exactly the surface this skill exists for -- a workflow file itself grants access to secrets and write scope, independent of what other code changed in the same PR |
|
|
120
|
+
| "The tag is from the official actions/ org, it's fine to leave unpinned" | The pinning risk is about the tag being movable, not about who currently controls it -- a first-party org account can still be compromised, and the fix costs nothing |
|
|
121
|
+
| "I noticed the fix while reviewing so I went ahead and applied it" | This skill is read-only; report the finding and fix direction, and let the implementation skill or the author apply it |
|
|
122
|
+
|
|
123
|
+
## Verification
|
|
124
|
+
|
|
125
|
+
Do not report the review done until all of the following hold:
|
|
126
|
+
|
|
127
|
+
- Every changed job's trigger was checked against the
|
|
128
|
+
`pull_request_target` + untrusted-checkout pattern.
|
|
129
|
+
- Every third-party `uses:` line in the diff was checked for tag vs.
|
|
130
|
+
commit-SHA pinning.
|
|
131
|
+
- The `permissions:` block (workflow- and job-level) was checked for
|
|
132
|
+
least privilege.
|
|
133
|
+
- Every `run:` step touching `${{ github.event.* }}` was checked for
|
|
134
|
+
direct interpolation vs. intermediate `env:` (GitHub Actions), and every
|
|
135
|
+
GitLab CI `script:` step touching an untrusted variable was checked for
|
|
136
|
+
unquoted use, `eval`/`sh -c` concatenation, or an unvalidated
|
|
137
|
+
`$[[ inputs.* ]]` interpolation.
|
|
138
|
+
- No finding was silently skipped because the surrounding change looked
|
|
139
|
+
unrelated to security.
|