Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 31 additions & 1 deletion skills/devsecops/pipeline-security/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ phase: [build, deploy]
frameworks: [SLSA-v1.0, OWASP-CICD-Top-10]
difficulty: intermediate
time_estimate: "30-60min"
version: "1.0.0"
version: "1.0.1"
author: unitoneai
license: MIT
allowed-tools: Read, Grep, Glob
Expand Down Expand Up @@ -266,6 +266,19 @@ on: pull_request_target

**Finding format:** Report any `pull_request_target` usage, direct expression injection in `run:` steps, fork workflow policies, and whether PR code can influence privileged pipelines.

##### `workflow_run` Artifact Handoff Evidence Gate

Do not treat absence of `pull_request_target` as sufficient PPE protection. A lower-trust producer workflow can upload artifacts, caches, coverage reports, build outputs, or generated scripts that a later privileged `workflow_run` consumer downloads and executes with write permissions, secrets, package tokens, signing keys, or deployment credentials.

- `PIPE-HANDOFF-01` - Map every producer/consumer chain: producer workflow name, trigger, event type, head repository, head branch/ref, head SHA, actor, artifact/cache/report identity, consumer workflow, consumer trigger, and consumer permissions.
- `PIPE-HANDOFF-02` - Require trusted source checks before privileged consumption: repository owner, fork status, branch protection, environment, actor/team membership, event type, conclusion, and expected workflow name must be verified before download, execution, signing, publishing, or deployment.
- `PIPE-HANDOFF-03` - Bind artifacts to immutable source and workflow identity using digest verification, signed artifact attestations, SLSA provenance, in-toto links, or rebuild-from-trusted-source before executing generated files or publishing outputs.
- `PIPE-HANDOFF-04` - Treat downloaded PR-controlled scripts, archives, coverage reports, dependency caches, build directories, generated release notes, and package metadata as untrusted until verification proves they came from the trusted commit and workflow.
- `PIPE-HANDOFF-05` - Isolate caches across trust boundaries. Untrusted PR workflows must not share cache keys, restore key prefixes, package manager caches, Docker layer caches, build caches, or tool caches with release, signing, deployment, or package-publish workflows.
- `PIPE-HANDOFF-06` - Verify consumer permissions are least-privilege and gated by environment protections. `write-all`, package publish, release creation, signing, cloud deployment, and secret access require stronger source/provenance checks.
- `PIPE-HANDOFF-07` - Require explicit deny behavior for untrusted, forked, stale, rerun, cancelled, failed, or manually modified producer runs. The consumer should fail closed before artifact download or cache restore.
- `PIPE-HANDOFF-08` - Cap status at Critical when a privileged consumer executes or publishes unverified artifacts from an untrusted producer; cap at High when trust checks or cache isolation are incomplete; mark Not Evaluable when the producer/consumer chain cannot be mapped.

---

#### CICD-SEC-5: Insufficient PBAC (Pipeline-Based Access Controls)
Expand Down Expand Up @@ -480,6 +493,23 @@ Produce the final report using the following structure:
| CICD-SEC-2 | Inadequate IAM | ... | ... | ... |
| ... | ... | ... | ... | ... |

### Privileged Workflow Handoff Evidence

| Chain ID | Producer Workflow / Trigger | Producer Trust Source | Artifact or Cache | Consumer Workflow / Permissions | Verification Before Use | Cache Isolation | Status |
|----------|-----------------------------|-----------------------|------------------|---------------------------------|-------------------------|----------------|--------|
| <chain> | <workflow + event> | <repo/ref/SHA/actor/conclusion> | <name/digest/key> | <workflow + token/secrets> | <digest/provenance/rebuild/trust checks> | <separated/shared/unknown> | <Pass/Fail/NE> |

| Gate | Evidence Required | Result | Finding |
|------|-------------------|--------|---------|
| PIPE-HANDOFF-01 | Complete producer/consumer workflow chain mapping | <Pass/Fail/NE> | <notes> |
| PIPE-HANDOFF-02 | Trusted source checks before privileged artifact/cache consumption | <Pass/Fail/NE> | <notes> |
| PIPE-HANDOFF-03 | Artifact digest, signature, provenance, or trusted rebuild binding | <Pass/Fail/NE> | <notes> |
| PIPE-HANDOFF-04 | PR-controlled generated files/reports/scripts treated as untrusted until verified | <Pass/Fail/NE> | <notes> |
| PIPE-HANDOFF-05 | Cache key and restore-key isolation across trust boundaries | <Pass/Fail/NE> | <notes> |
| PIPE-HANDOFF-06 | Consumer permissions and environment gates scoped to verified source | <Pass/Fail/NE> | <notes> |
| PIPE-HANDOFF-07 | Fail-closed behavior for untrusted, stale, rerun, cancelled, failed, or manually modified producers | <Pass/Fail/NE> | <notes> |
| PIPE-HANDOFF-08 | Severity/status cap applied for unmapped or unverified privileged handoffs | <Pass/Fail/NE> | <notes> |

### Detailed Findings

#### [CICD-SEC-X] <Risk Name>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
{
"case_id": "pipeline_workflow_run_verified_artifact_handoff",
"description": "A privileged workflow_run consumer only publishes artifacts after mapping the producer chain, verifying trusted source, validating artifact provenance, and isolating caches from PR workflows.",
"repository": "acme/payments-api",
"chains": [
{
"chain_id": "release-build-to-publish",
"producer": {
"workflow_name": "trusted-release-build",
"trigger": "push",
"event_type": "push",
"head_repository": "acme/payments-api",
"head_repository_owner": "acme",
"fork": false,
"head_branch": "main",
"head_sha": "sha-example-trusted-release-20260609",
"actor": "release-bot",
"actor_team": "release-engineering",
"conclusion": "success",
"workflow_file_ref": ".github/workflows/release-build.yml@sha-example-trusted-release-20260609"
},
"produced_material": [
{
"type": "artifact",
"name": "dist-package",
"artifact_id": "artifact-981245",
"digest": "sha256-example-dist-package-20260609",
"provenance": {
"type": "slsa-v1",
"attestation": "attestation-dist-package-981245",
"builder_id": "github-actions-hosted",
"source_sha": "sha-example-trusted-release-20260609",
"workflow_name": "trusted-release-build",
"verified_by_consumer": true
}
},
{
"type": "coverage_report",
"name": "coverage-summary",
"digest": "sha256-example-coverage-summary-20260609",
"executed_by_consumer": false
}
],
"consumer": {
"workflow_name": "publish-release",
"trigger": "workflow_run",
"listens_to": [
"trusted-release-build"
],
"permissions": {
"contents": "write",
"packages": "write",
"id-token": "write",
"actions": "read"
},
"secrets_available": [
"package-publish-token"
],
"environment": {
"name": "production-release",
"required_reviewers": [
"release-engineering"
],
"protected_branch_only": true
},
"pre_download_checks": [
"workflow_run.head_repository.full_name == acme/payments-api",
"workflow_run.head_branch == main",
"workflow_run.head_sha matches protected branch tip",
"workflow_run.actor is release-bot or release-engineering member",
"workflow_run.event == push",
"workflow_run.conclusion == success",
"workflow_run.name == trusted-release-build"
],
"artifact_verification": [
"downloaded artifact digest equals expected digest from attestation",
"SLSA provenance subject digest equals artifact digest",
"provenance source sha equals workflow_run.head_sha",
"rebuild fallback exists for missing attestation"
],
"fail_closed_before_download": true
},
"cache_isolation": {
"producer_cache_key": "release-main-${{ github.sha }}",
"pr_cache_key_prefix": "pr-${{ github.event.pull_request.head.sha }}",
"release_restore_keys": [
"release-main-"
],
"pr_restore_keys": [
"pr-"
],
"shared_with_pull_request": false,
"docker_layer_cache_shared": false,
"package_manager_cache_shared": false
},
"expected_status": "Pass"
},
{
"chain_id": "pr-build-to-analysis",
"producer": {
"workflow_name": "pull-request-build",
"trigger": "pull_request",
"event_type": "pull_request",
"head_repository": "contributor/payments-api",
"head_repository_owner": "contributor",
"fork": true,
"head_branch": "feature",
"head_sha": "sha-example-fork-pr-20260609",
"actor": "external-contributor",
"conclusion": "success"
},
"produced_material": [
{
"type": "artifact",
"name": "test-results",
"digest": "sha256-example-pr-test-results-20260609",
"executed_by_consumer": false,
"published_by_consumer": false
}
],
"consumer": {
"workflow_name": "pr-result-commenter",
"trigger": "workflow_run",
"permissions": {
"contents": "read",
"pull-requests": "write"
},
"secrets_available": [],
"pre_download_checks": [
"workflow_run.name == pull-request-build",
"workflow_run.conclusion == success",
"consumer never executes downloaded files",
"consumer only parses fixed-format JSON after schema validation"
],
"artifact_verification": [
"schema validation before parsing",
"path traversal checks before extraction",
"no shell execution"
],
"fail_closed_before_download": true
},
"cache_isolation": {
"shared_with_release_or_deploy": false,
"restore_keys_overlap_release": false
},
"expected_status": "Pass"
}
],
"expected_gate_results": {
"PIPE-HANDOFF-01": "Pass",
"PIPE-HANDOFF-02": "Pass",
"PIPE-HANDOFF-03": "Pass",
"PIPE-HANDOFF-04": "Pass",
"PIPE-HANDOFF-05": "Pass",
"PIPE-HANDOFF-06": "Pass",
"PIPE-HANDOFF-07": "Pass",
"PIPE-HANDOFF-08": "Pass"
},
"expected_assessment": {
"cicd_sec_4": "Pass",
"cicd_sec_9": "Pass",
"status_cap": "None",
"finding": "Privileged workflow_run consumers are restricted to trusted producers, verified source and artifact identity, isolated caches, least-privilege permissions, and fail-closed behavior."
}
}
Loading