Conversation
…lows (#186) * feat(security): add cosign keyless image signing to Docker build workflows Add opt-in cosign keyless (OIDC) signing for container images across all Docker build workflows (build.yml, typescript-build.yml, go-release.yml). - Create src/security/cosign-sign composite action using sigstore/cosign-installer - Sign images after push using GitHub Actions OIDC identity (no private keys) - Support multi-registry signing (DockerHub + GHCR) - Default enabled (enable_cosign_sign: true), callers need id-token: write - Fix pr-security-reporter to also fail on scan artifact errors (prevent bypass) * fix(security): use env vars instead of direct interpolation in run blocks Move all ${{ }} expressions from run: blocks to env: blocks to prevent CodeQL code injection findings. Applies to cosign-sign composite, build.yml, typescript-build.yml, go-release.yml, and pr-security-reporter. * fix(security): pin actions/github-script to commit SHA
…nd cursor rules (#187) * docs(security): add GitHub Actions workflow security rules to agent and cursor rules * docs(security): add secrets inherit and review.body injection rules
…188) * fix(release): pin lodash-es to 4.17.23 to avoid 4.18.0 broken import lodash-es@4.18.0 (published 2026-03-31) renamed assignInWith to assignWith in template.js but forgot to import it, breaking semantic-release verify phase with ReferenceError. * fix(release): pin third-party actions by SHA in typescript-release All third-party actions must be pinned by commit SHA per repo rules. Aligns typescript-release.yml with release.yml which was already compliant.
The workspace-level npm install does not affect semantic-release which runs from _actions/cycjimmy/semantic-release-action/*/node_modules/. Override lodash-es directly in the action directory.
… group (#180) * chore(deps): bump mikefarah/yq in the utilities group Bumps the utilities group with 1 update: [mikefarah/yq](https://github.com/mikefarah/yq). Updates `mikefarah/yq` from 4.52.4 to 4.52.5 - [Release notes](https://github.com/mikefarah/yq/releases) - [Changelog](https://github.com/mikefarah/yq/blob/master/release_notes.txt) - [Commits](mikefarah/yq@5a7e72a...0f4fb8d) --- updated-dependencies: - dependency-name: mikefarah/yq dependency-version: 4.52.5 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: utilities ... Signed-off-by: dependabot[bot] <support@github.com> * fix(ci): pin actions by SHA and resolve shellcheck warnings in helm-update-chart --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Lucas Bedatty <lucas.bedatty@lerian.studio>
…update-chart (#191) * fix(security): move expression interpolations to env context in helm-update-chart * fix(security): replace unquoted for loops with while-read to fix SC2086 * fix(security): quote SCRIPTS_PATH expansion to fix SC2086 * fix(security): use jq for Severino Slack payload construction
* feat(security): add docker_build_args input to pr-security-scan workflow * feat(build): add docker_build_args input to build workflow
WalkthroughAdded GitHub Actions security hardening guidance to rule documents, introduced a new cosign keyless image-signing composite action, integrated signing into build/release workflows with optional control via Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~28 minutes Possibly related PRs
Suggested labels
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
🔍 Lint Analysis
|
🛡️ CodeQL Analysis ResultsLanguages analyzed: Found 2 issue(s): 2 Medium
🔍 View full scan logs | 🛡️ Security tab |
There was a problem hiding this comment.
Warning
CodeRabbit couldn't request changes on this pull request because it doesn't have sufficient GitHub permissions.
Please grant CodeRabbit Pull requests: Read and write permission and re-run the review.
Actionable comments posted: 13
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.cursor/rules/reusable-workflows.mdc:
- Around line 420-505: Update the contradictory guidance about
workflow_dispatch: replace any statement that "reusable workflows must support
workflow_dispatch" with the current repo policy that "reusable workflows under
.github/workflows must NOT include a workflow_dispatch trigger", and edit
adjacent examples and bullet points to reflect this (search for occurrences of
"workflow_dispatch" and the phrase "support `workflow_dispatch`" in this
document). Ensure the section that lists accepted triggers for reusable
workflows and any examples are reconciled so they no longer recommend adding
`workflow_dispatch` to reusable workflows and instead explain splitting into
separate workflows if interactive/manual dispatch is required.
In @.github/workflows/build.yml:
- Around line 139-142: Top-level workflow permissions currently grant id-token:
write to all jobs; remove id-token: write from the global permissions block and
instead add a job-scoped permissions entry under the build job
(jobs.build.permissions) that sets id-token: write so only the build job gets
OIDC access; leave global permissions.contents: read and packages: write as
appropriate and ensure other jobs (prepare, notify, dispatch-helm) do not
receive id-token write privileges.
- Around line 314-340: The cosign-refs step ignores a caller-provided ghcr_org
and always uses steps.normalize.outputs.owner_lower; update the GHCR_ORG
environment binding for the cosign-refs job to prefer inputs.ghcr_org and fall
back to steps.normalize.outputs.owner_lower (e.g. set GHCR_ORG: ${{
inputs.ghcr_org || steps.normalize.outputs.owner_lower }}), and ensure the
GHCR_ORG variable is used unchanged when constructing the
ghcr.io/${GHCR_ORG}/${APP_NAME}@${DIGEST} reference in the run block.
- Around line 342-346: The workflow currently references a mutable branch in the
reusable workflow usage (the uses:
LerianStudio/github-actions-shared-workflows/src/security/cosign-sign@feat/cosign-sign
line); change that to a pinned, org-owned release tag (e.g., `@vX.Y.Z`) for the
reusable workflow so external callers run immutable, release-tagged code—update
the uses reference to the appropriate release tag for
github-actions-shared-workflows and confirm it points to the org-owned
repository tag rather than a branch name.
In @.github/workflows/go-release.yml:
- Around line 75-78: The top-level permissions block currently grants id-token:
write for the whole workflow; restrict this to only the docker job by removing
id-token: write from the global permissions and adding a permissions: block
under the docker job that includes id-token: write (keep contents/packages as
needed per-job), so only the docker job performs keyless signing with id-token,
and other jobs like release/homebrew/notify retain least-privilege defaults;
update the docker job's permissions section accordingly and remove id-token from
the workflow-level permissions.
- Around line 195-199: The reusable workflow step "Sign container images with
cosign" currently uses a mutable feature branch ref
(LerianStudio/github-actions-shared-workflows/src/security/cosign-sign@feat/cosign-sign);
replace that with an org-owned immutable ref (a release tag or commit SHA) to
satisfy the reusable-workflows policy—e.g., change the uses value to the
corresponding release tag (or pinned SHA) for
LerianStudio/github-actions-shared-workflows so external callers run a fixed,
immutable revision instead of a feature branch.
In @.github/workflows/pr-security-scan.yml:
- Around line 70-74: Update the docker_build_args input description to
explicitly warn against including secrets and point callers to use BuildKit
secrets instead: modify the description string for the docker_build_args input
(symbol: docker_build_args) to read “Newline-separated Docker build arguments
(e.g., `APP_NAME=spi\nCOMPONENT_NAME=api`). For sensitive values (tokens, keys,
passwords), use BuildKit `secrets:` instead—build arguments are visible in image
history and Dockerfile execution.”; optionally add a defensive validation step
before the workflow step that consumes docker_build_args (near where build args
are forwarded, e.g., before the step around line 159) to reject or error on
common secret-like parameter names (e.g., TOKEN, SECRET, KEY, PASS) if present
in the input; apply the same description change to the identical input in
build.yml as well.
In @.github/workflows/typescript-build.yml:
- Around line 334-338: The workflow step "Sign container images with cosign"
currently uses a mutable feature branch ref
("LerianStudio/github-actions-shared-workflows/src/security/cosign-sign@feat/cosign-sign");
replace that with an org-owned, pinned release tag (for example change the uses
value to
"LerianStudio/github-actions-shared-workflows/src/security/cosign-sign@<release-tag>"
where <release-tag> is a stable semver tag) so external callers do not execute
code from a mutable branch; ensure the new ref is a published tag (not
`@main/`@master or any branch) and update any documentation or inputs (e.g.,
inputs.enable_cosign_sign) if you add or require a specific tag variable.
- Around line 148-151: The workflow currently grants id-token: write at workflow
scope which exposes OIDC tokens to all jobs; move the id-token: write permission
out of the top-level permissions block and add it only to the build job's
permissions (the job named "build" that uses cosign). Update the YAML so
top-level permissions remain minimal (e.g., contents: read, packages: write) and
add id-token: write under the build job's permissions to ensure least-privilege.
In `@docs/build-workflow.md`:
- Around line 226-230: The current cosign verify invocation uses a permissive
--certificate-identity-regexp=".*", which allows any GitHub Actions identity;
update the --certificate-identity-regexp to an exact or tightly-scoped regexp
that matches the specific repository/workflow identity (e.g., a pattern anchored
to the repo and workflow path) and for reusable workflows use the reusable
workflow's own identity pattern instead of the caller's; modify the command
surrounding cosign verify and the --certificate-identity-regexp flag to replace
".*" with the appropriate anchored regexp for your OWNER/REPO and WORKFLOW (or
the reusable workflow path) so the signature is bound to the expected signer.
In `@docs/typescript-build.md`:
- Around line 207-216: The Basic Example's workflow permissions are missing the
required OIDC permission, so with enable_cosign_sign defaulting to true the
example will fail; update the example's permissions block to include "id-token:
write" (alongside contents: read and packages: write) or alternatively add
"enable_cosign_sign: false" in the example configuration to explicitly disable
signing; modify the "permissions" section in the Basic Example accordingly so it
matches the "Caller permissions" guidance.
In `@src/security/cosign-sign/README.md`:
- Around line 46-50: The single-image example in the README uses an unqualified
image ref ("myorg/myapp@...") which contradicts the documented contract; update
the example under the Sign container image step so the image-refs input shows a
fully qualified reference including a registry host (e.g., ghcr.io or docker.io)
instead of just "myorg/myapp", keeping the same `${{
steps.build-push.outputs.digest }}` interpolation and the existing input key
"image-refs" so the example matches the multi-registry example and the
documented expectation.
- Around line 65-70: The cosign example uses an overly permissive
--certificate-identity-regexp=".*"; update the examples to a
repository/workflow/branch-scoped regexp (e.g.
--certificate-identity-regexp="^https://github.com/OWNER/REPO/.github/workflows/WORKFLOW.yml@refs/heads/BRANCH$")
replacing OWNER/REPO/WORKFLOW.yml/BRANCH with real values; change the example in
src/security/cosign-sign/README.md and the matching examples in
docs/typescript-build.md, docs/go-release-workflow.md, and
docs/build-workflow.md to use the scoped --certificate-identity-regexp pattern
instead of ".*" so identity verification is restricted to the intended
repository/workflow/branch.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 2d282d6b-8899-42f6-9bc4-67da1fa76d67
📒 Files selected for processing (15)
.cursor/rules/reusable-workflows.mdc.github/workflows/build.yml.github/workflows/go-release.yml.github/workflows/helm-update-chart.yml.github/workflows/pr-security-scan.yml.github/workflows/release.yml.github/workflows/typescript-build.yml.github/workflows/typescript-release.ymlAGENTS.mddocs/build-workflow.mddocs/go-release-workflow.mddocs/typescript-build.mdsrc/security/cosign-sign/README.mdsrc/security/cosign-sign/action.ymlsrc/security/pr-security-reporter/action.yml
| ### pull_request_target — NEVER checkout fork code | ||
|
|
||
| - NEVER use `actions/checkout` with `ref: ${{ github.event.pull_request.head.ref }}` or `ref: ${{ github.event.pull_request.head.sha }}` in `pull_request_target` workflows | ||
| - If `pull_request_target` is needed (e.g., labeling, commenting), it MUST NOT run any code from the fork (no build, no test, no script execution from the PR branch) | ||
| - Prefer `pull_request` trigger over `pull_request_target` unless write permissions to the base repo are explicitly required | ||
| - NEVER use `secrets: inherit` in workflows triggered by `pull_request_target` — it exposes all repository secrets to fork code | ||
|
|
||
| ### Expression injection — sanitize ALL untrusted inputs | ||
|
|
||
| NEVER use these directly in `run:` steps: | ||
|
|
||
| ```yaml | ||
| # ❌ All of these are injectable — NEVER interpolate directly in run: | ||
| ${{ github.event.pull_request.title }} | ||
| ${{ github.event.pull_request.body }} | ||
| ${{ github.event.issue.title }} | ||
| ${{ github.event.issue.body }} | ||
| ${{ github.event.comment.body }} | ||
| ${{ github.event.head_commit.message }} | ||
| ${{ github.event.head_commit.author.name }} | ||
| ${{ github.event.commits[*].message }} | ||
| ${{ github.event.discussion.title }} | ||
| ${{ github.event.discussion.body }} | ||
| ${{ github.event.review.body }} | ||
| ${{ github.head_ref }} | ||
| ${{ github.event.pages[*].page_name }} | ||
| ``` | ||
|
|
||
| If any of these values are needed in a `run:` step, pass them through an environment variable: | ||
|
|
||
| ```yaml | ||
| # ✅ Safe — shell variable, not template injection | ||
| env: | ||
| PR_TITLE: ${{ github.event.pull_request.title }} | ||
| run: echo "$PR_TITLE" | ||
| ``` | ||
|
|
||
| ### workflow_run — treat artifacts as untrusted | ||
|
|
||
| - Workflows triggered by `workflow_run` MUST NOT trust artifacts from the triggering workflow blindly | ||
| - Validate/sanitize any data extracted from artifacts before use in shell commands or API calls | ||
| - Never extract and execute scripts from artifacts without verification | ||
|
|
||
| ### Permissions — principle of least privilege | ||
|
|
||
| - ALWAYS declare explicit `permissions:` block at workflow level | ||
| - Default to `contents: read` — only escalate per-job when needed | ||
| - NEVER use `permissions: write-all` or leave permissions undeclared (defaults to broad access in public repos) | ||
| - For comment/label-only workflows: `permissions: { pull-requests: write }` — nothing else | ||
|
|
||
| ```yaml | ||
| # ✅ Explicit, minimal permissions | ||
| permissions: | ||
| contents: read | ||
|
|
||
| jobs: | ||
| comment: | ||
| permissions: | ||
| pull-requests: write # escalated only for this job | ||
| ``` | ||
|
|
||
| ### Secrets in fork contexts | ||
|
|
||
| - NEVER pass secrets to steps that run fork code | ||
| - `pull_request` from a fork does NOT have access to secrets by default — do not circumvent this | ||
| - If a workflow needs secrets + fork code, split into two workflows: | ||
| - `pull_request` (no secrets, runs fork code) | ||
| - `workflow_run` (has secrets, runs trusted code only) | ||
|
|
||
| ### Script injection via labels/branches | ||
|
|
||
| - Validate branch names and label names before using in shell commands | ||
| - Branch names can contain shell metacharacters — always quote variables | ||
|
|
||
| ```yaml | ||
| # ✅ Always quote branch/label variables | ||
| run: echo "$BRANCH_NAME" | ||
| env: | ||
| BRANCH_NAME: ${{ github.head_ref }} | ||
| ``` | ||
|
|
||
| ### Self-hosted runners | ||
|
|
||
| - NEVER use self-hosted runners for `pull_request` or `pull_request_target` from public repos — a fork can execute arbitrary code on the runner | ||
| - Self-hosted runners are only safe for `push`, `workflow_dispatch`, `schedule`, and other non-fork triggers | ||
|
|
There was a problem hiding this comment.
Reconcile the workflow_dispatch rule in this document.
This rules refresh lands in a file that still tells agents reusable workflows must support workflow_dispatch. Current repo policy for externally consumed reusable workflows is the opposite, so the document now gives conflicting instructions and will drive incorrect edits.
Based on learnings, "reusable workflows under .github/workflows must NOT include a workflow_dispatch trigger."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.cursor/rules/reusable-workflows.mdc around lines 420 - 505, Update the
contradictory guidance about workflow_dispatch: replace any statement that
"reusable workflows must support workflow_dispatch" with the current repo policy
that "reusable workflows under .github/workflows must NOT include a
workflow_dispatch trigger", and edit adjacent examples and bullet points to
reflect this (search for occurrences of "workflow_dispatch" and the phrase
"support `workflow_dispatch`" in this document). Ensure the section that lists
accepted triggers for reusable workflows and any examples are reconciled so they
no longer recommend adding `workflow_dispatch` to reusable workflows and instead
explain splitting into separate workflows if interactive/manual dispatch is
required.
| permissions: | ||
| contents: read | ||
| packages: write | ||
| id-token: write |
There was a problem hiding this comment.
Restrict id-token: write to the build job.
The new signing path only runs in build, but workflow-scope permissions also hand OIDC access to prepare, notify, and dispatch-helm.
As per coding guidelines, "Always declare explicit least-privilege permissions: (default contents: read, avoid broad write-all)."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.github/workflows/build.yml around lines 139 - 142, Top-level workflow
permissions currently grant id-token: write to all jobs; remove id-token: write
from the global permissions block and instead add a job-scoped permissions entry
under the build job (jobs.build.permissions) that sets id-token: write so only
the build job gets OIDC access; leave global permissions.contents: read and
packages: write as appropriate and ensure other jobs (prepare, notify,
dispatch-helm) do not receive id-token write privileges.
| - name: Build cosign image references | ||
| if: inputs.enable_cosign_sign | ||
| id: cosign-refs | ||
| env: | ||
| DIGEST: ${{ steps.build-push.outputs.digest }} | ||
| ENABLE_DOCKERHUB: ${{ inputs.enable_dockerhub }} | ||
| ENABLE_GHCR: ${{ inputs.enable_ghcr }} | ||
| DOCKERHUB_ORG: ${{ inputs.dockerhub_org }} | ||
| APP_NAME: ${{ matrix.app.name }} | ||
| GHCR_ORG: ${{ steps.normalize.outputs.owner_lower }} | ||
| run: | | ||
| REFS="" | ||
|
|
||
| if [ "$ENABLE_DOCKERHUB" == "true" ]; then | ||
| REFS="${DOCKERHUB_ORG}/${APP_NAME}@${DIGEST}" | ||
| fi | ||
|
|
||
| if [ "$ENABLE_GHCR" == "true" ]; then | ||
| [ -n "$REFS" ] && REFS="${REFS}"$'\n' | ||
| REFS="${REFS}ghcr.io/${GHCR_ORG}/${APP_NAME}@${DIGEST}" | ||
| fi | ||
|
|
||
| { | ||
| echo "refs<<EOF" | ||
| echo "$REFS" | ||
| echo "EOF" | ||
| } >> "$GITHUB_OUTPUT" |
There was a problem hiding this comment.
Custom ghcr_org is ignored when building cosign refs.
Set image names resolves inputs.ghcr_org, but this block always signs ghcr.io/${{ steps.normalize.outputs.owner_lower }}/${APP_NAME}. When callers override ghcr_org, cosign targets a different repository than the image that was pushed.
Suggested fix
- name: Build cosign image references
if: inputs.enable_cosign_sign
id: cosign-refs
env:
DIGEST: ${{ steps.build-push.outputs.digest }}
ENABLE_DOCKERHUB: ${{ inputs.enable_dockerhub }}
ENABLE_GHCR: ${{ inputs.enable_ghcr }}
DOCKERHUB_ORG: ${{ inputs.dockerhub_org }}
APP_NAME: ${{ matrix.app.name }}
- GHCR_ORG: ${{ steps.normalize.outputs.owner_lower }}
+ INPUT_GHCR_ORG: ${{ inputs.ghcr_org }}
+ DEFAULT_GHCR_ORG: ${{ steps.normalize.outputs.owner_lower }}
run: |
REFS=""
+ GHCR_ORG="$INPUT_GHCR_ORG"
+ if [ -z "$GHCR_ORG" ]; then
+ GHCR_ORG="$DEFAULT_GHCR_ORG"
+ else
+ GHCR_ORG=$(echo "$GHCR_ORG" | tr '[:upper:]' '[:lower:]')
+ fi
if [ "$ENABLE_DOCKERHUB" == "true" ]; then
REFS="${DOCKERHUB_ORG}/${APP_NAME}@${DIGEST}"
fi🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.github/workflows/build.yml around lines 314 - 340, The cosign-refs step
ignores a caller-provided ghcr_org and always uses
steps.normalize.outputs.owner_lower; update the GHCR_ORG environment binding for
the cosign-refs job to prefer inputs.ghcr_org and fall back to
steps.normalize.outputs.owner_lower (e.g. set GHCR_ORG: ${{ inputs.ghcr_org ||
steps.normalize.outputs.owner_lower }}), and ensure the GHCR_ORG variable is
used unchanged when constructing the ghcr.io/${GHCR_ORG}/${APP_NAME}@${DIGEST}
reference in the run block.
| - name: Sign container images with cosign | ||
| if: inputs.enable_cosign_sign | ||
| uses: LerianStudio/github-actions-shared-workflows/src/security/cosign-sign@feat/cosign-sign | ||
| with: | ||
| image-refs: ${{ steps.cosign-refs.outputs.refs }} |
There was a problem hiding this comment.
Do not publish the workflow with src/security/cosign-sign@feat/cosign-sign.
A caller using a tagged or mainline revision of this reusable workflow would still execute action code from a mutable feature branch. That breaks release immutability and can fail once the branch moves or is deleted.
As per coding guidelines, ".cursor/rules/reusable-workflows.mdc: External callers must use an org-owned ref pinned to a release tag (no @main/@master)."
🧰 Tools
🪛 GitHub Check: Pinned Actions Check
[warning] 344-344:
Internal action not pinned to a version: uses: LerianStudio/github-actions-shared-workflows/src/security/cosign-sign@feat/cosign-sign
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.github/workflows/build.yml around lines 342 - 346, The workflow currently
references a mutable branch in the reusable workflow usage (the uses:
LerianStudio/github-actions-shared-workflows/src/security/cosign-sign@feat/cosign-sign
line); change that to a pinned, org-owned release tag (e.g., `@vX.Y.Z`) for the
reusable workflow so external callers run immutable, release-tagged code—update
the uses reference to the appropriate release tag for
github-actions-shared-workflows and confirm it points to the org-owned
repository tag rather than a branch name.
| permissions: | ||
| contents: write | ||
| packages: write | ||
| id-token: write |
There was a problem hiding this comment.
Scope OIDC to the docker job.
Only docker performs keyless signing. Keeping id-token: write at workflow scope also grants it to release, homebrew, and notify, which widens the credential surface for no benefit.
As per coding guidelines, "Always declare explicit least-privilege permissions: (default contents: read, avoid broad write-all)."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.github/workflows/go-release.yml around lines 75 - 78, The top-level
permissions block currently grants id-token: write for the whole workflow;
restrict this to only the docker job by removing id-token: write from the global
permissions and adding a permissions: block under the docker job that includes
id-token: write (keep contents/packages as needed per-job), so only the docker
job performs keyless signing with id-token, and other jobs like
release/homebrew/notify retain least-privilege defaults; update the docker job's
permissions section accordingly and remove id-token from the workflow-level
permissions.
| - name: Sign container images with cosign | ||
| if: inputs.enable_cosign_sign && !inputs.dry_run && steps.cosign-refs.outputs.refs != '' | ||
| uses: LerianStudio/github-actions-shared-workflows/src/security/cosign-sign@feat/cosign-sign | ||
| with: | ||
| image-refs: ${{ steps.cosign-refs.outputs.refs }} |
There was a problem hiding this comment.
Do not publish the workflow with src/security/cosign-sign@feat/cosign-sign.
A caller using a tagged or mainline revision of this reusable workflow would still execute action code from a mutable feature branch. That breaks release immutability and can fail once the branch moves or is deleted.
As per coding guidelines, ".cursor/rules/reusable-workflows.mdc: External callers must use an org-owned ref pinned to a release tag (no @main/@master)."
🧰 Tools
🪛 GitHub Check: Pinned Actions Check
[warning] 336-336:
Internal action not pinned to a version: uses: LerianStudio/github-actions-shared-workflows/src/security/cosign-sign@feat/cosign-sign
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.github/workflows/typescript-build.yml around lines 334 - 338, The workflow
step "Sign container images with cosign" currently uses a mutable feature branch
ref
("LerianStudio/github-actions-shared-workflows/src/security/cosign-sign@feat/cosign-sign");
replace that with an org-owned, pinned release tag (for example change the uses
value to
"LerianStudio/github-actions-shared-workflows/src/security/cosign-sign@<release-tag>"
where <release-tag> is a stable semver tag) so external callers do not execute
code from a mutable branch; ensure the new ref is a published tag (not
`@main/`@master or any branch) and update any documentation or inputs (e.g.,
inputs.enable_cosign_sign) if you add or require a specific tag variable.
| ```bash | ||
| cosign verify \ | ||
| --certificate-identity-regexp=".*" \ | ||
| --certificate-oidc-issuer="https://token.actions.githubusercontent.com" \ | ||
| docker.io/lerianstudio/my-app@sha256:abc123... |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
For Sigstore cosign keyless verification in GitHub Actions, should --certificate-identity-regexpbe restricted to the specific repository/workflow instead of.*? What identity format is recommended for reusable workflow signatures?
💡 Result:
Yes, --certificate-identity-regexp should be restricted to the specific repository and workflow (or reusable workflow) instead of .* for security. Using .* allows signatures from any GitHub workflow, undermining the identity verification that keyless signing provides. Official Sigstore documentation and cosign examples consistently recommend exact or regex-constrained matching to the expected workflow URI format: https://github.com/OWNER/REPO/.github/workflows/WORKFLOW.yml@refs/heads/BRANCH. For verification, use --certificate-identity-regexp '^https://github\.com/OWNER/REPO/\.github/workflows/WORKFLOW\.yml@refs/.' (escaping dots) or --certificate-identity for exact match, paired with --certificate-oidc-issuer https://token.actions.githubusercontent.com. For reusable workflows, the certificate identity is the reusable workflow's ref (e.g., https://github.com/REPO/.github/workflows/reusable.yml@refs/heads/main), not the caller. Additional OIDs distinguish reusable (Build Signer URI) vs. regular workflows (Build Config URI). Restrict regexp to the specific reusable workflow repository/workflow: verify against the signer's (reusable) identity, using OIDs or policy for further checks if needed. Using . is explicitly called counterproductive in cosign issues, as it enables overly permissive verification vulnerable to compromise outside the trusted workflow.
Citations:
- 1: Certificate identity for GitHub Actions reusable workflow keyless signing sigstore/cosign#2936
- 2: https://github.com/sigstore/cosign/blob/main/doc/cosign_verify.md
- 3: verify/-blob should provide more guidance in identity assertions sigstore/cosign#2804
- 4: https://github.com/sigstore/cosign
- 5: Sugar for common GitHub Actions (and other CI/CD workflows) sigstore/cosign#2691
- 6: https://docs.sigstore.dev/quickstart/quickstart-ci/
- 7: https://docs.sigstore.dev/quickstart/quickstart-ci
- 8: https://docs.sigstore.dev/certificate_authority/oidc-in-fulcio
- 9: https://blog.sigstore.dev/cosign-verify-bundles
Restrict --certificate-identity-regexp to the specific repository/workflow instead of .*.
The wildcard pattern accepts any GitHub Actions-issued identity and defeats the provenance verification. Use an exact identity match or a pattern constrained to your repository and workflow, such as --certificate-identity-regexp='^https://github\.com/OWNER/REPO/\.github/workflows/WORKFLOW\.yml@refs/.*'. For reusable workflow signatures, restrict the regexp to the reusable workflow's own identity (not the caller), for example '^https://github\.com/REPO/\.github/workflows/reusable\.yml@refs/.*'. This ensures the signature binds to the expected signer, not any GitHub Actions workflow.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@docs/build-workflow.md` around lines 226 - 230, The current cosign verify
invocation uses a permissive --certificate-identity-regexp=".*", which allows
any GitHub Actions identity; update the --certificate-identity-regexp to an
exact or tightly-scoped regexp that matches the specific repository/workflow
identity (e.g., a pattern anchored to the repo and workflow path) and for
reusable workflows use the reusable workflow's own identity pattern instead of
the caller's; modify the command surrounding cosign verify and the
--certificate-identity-regexp flag to replace ".*" with the appropriate anchored
regexp for your OWNER/REPO and WORKFLOW (or the reusable workflow path) so the
signature is bound to the expected signer.
| ### Caller permissions | ||
|
|
||
| Callers **must** grant `id-token: write` for signing to work: | ||
|
|
||
| ```yaml | ||
| permissions: | ||
| contents: read | ||
| packages: write | ||
| id-token: write # required for cosign keyless signing | ||
| ``` |
There was a problem hiding this comment.
The Basic Example above is still missing the required OIDC permission.
This section says callers must grant id-token: write, but the Basic Example on Lines 37-40 defines permissions: without it. With enable_cosign_sign defaulting to true, that example will fail unless it also disables signing.
Suggested doc update
permissions:
contents: read
packages: write
+ id-token: write🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@docs/typescript-build.md` around lines 207 - 216, The Basic Example's
workflow permissions are missing the required OIDC permission, so with
enable_cosign_sign defaulting to true the example will fail; update the
example's permissions block to include "id-token: write" (alongside contents:
read and packages: write) or alternatively add "enable_cosign_sign: false" in
the example configuration to explicitly disable signing; modify the
"permissions" section in the Basic Example accordingly so it matches the "Caller
permissions" guidance.
| - name: Sign container image | ||
| uses: LerianStudio/github-actions-shared-workflows/src/security/cosign-sign@v1.x.x | ||
| with: | ||
| image-refs: myorg/myapp@${{ steps.build-push.outputs.digest }} | ||
| ``` |
There was a problem hiding this comment.
Use a fully qualified ref in the single-image example.
image-refs is documented here as a fully qualified image reference, but this example uses myorg/myapp@... without a registry prefix. That makes the example inconsistent with the contract and with the multi-registry example below.
Suggested doc update
- image-refs: myorg/myapp@${{ steps.build-push.outputs.digest }}
+ image-refs: docker.io/myorg/myapp@${{ steps.build-push.outputs.digest }}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| - name: Sign container image | |
| uses: LerianStudio/github-actions-shared-workflows/src/security/cosign-sign@v1.x.x | |
| with: | |
| image-refs: myorg/myapp@${{ steps.build-push.outputs.digest }} | |
| ``` | |
| - name: Sign container image | |
| uses: LerianStudio/github-actions-shared-workflows/src/security/cosign-sign@v1.x.x | |
| with: | |
| image-refs: docker.io/myorg/myapp@${{ steps.build-push.outputs.digest }} |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/security/cosign-sign/README.md` around lines 46 - 50, The single-image
example in the README uses an unqualified image ref ("myorg/myapp@...") which
contradicts the documented contract; update the example under the Sign container
image step so the image-refs input shows a fully qualified reference including a
registry host (e.g., ghcr.io or docker.io) instead of just "myorg/myapp",
keeping the same `${{ steps.build-push.outputs.digest }}` interpolation and the
existing input key "image-refs" so the example matches the multi-registry
example and the documented expectation.
| ```bash | ||
| cosign verify \ | ||
| --certificate-identity-regexp=".*" \ | ||
| --certificate-oidc-issuer="https://token.actions.githubusercontent.com" \ | ||
| docker.io/myorg/myapp@sha256:abc123... | ||
| ``` |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
For Sigstore cosign keyless verification on GitHub Actions, what --certificate-identityor--certificate-identity-regexpvalue should be used to restrict verification to a specific repository/workflow, and why is--certificate-identity-regexp=".*" too permissive?
💡 Result:
For Sigstore cosign keyless verification on GitHub Actions, use --certificate-identity "https://github.com/OWNER/REPO/.github/workflows/WORKFLOW.yml@refs/heads/BRANCH" (exact match) or --certificate-identity-regexp "^https://github.com/OWNER/REPO/.github/workflows/WORKFLOW.yml@refs/heads/BRANCH$" (regexp match) to restrict to a specific repository and workflow. Replace OWNER/REPO/WORKFLOW.yml/BRANCH with your values. This value matches the certificate's Subject Alternative Name (SAN), which Fulcio sets to https://github.com/{job_workflow_ref} from the GitHub OIDC token's job_workflow_ref claim (e.g., "owner/repo/.github/workflows/workflow.yml@refs/heads/main"). This ensures the signature came from that exact repo/workflow/branch/ref. The --certificate-identity-regexp="." is too permissive because it matches any identity (Go RE . matches everything), allowing signatures from any OIDC subject (any repo/workflow/issuer if combined with permissive issuer regexp), defeating the security purpose of identity restriction in keyless verification.
Citations:
- 1: https://github.com/sigstore/cosign/blob/main/doc/cosign_verify.md
- 2: https://medium.com/ci-cd-devops/keyless-image-signing-with-sigstore-cosign-rekor-gh-action-ghcr-482002a0bd67
- 3: https://edu.chainguard.dev/open-source/sigstore/cosign/how-to-verify-file-signatures-with-cosign/
- 4: https://docs.sigstore.dev/quickstart/quickstart-ci
- 5: https://www.chainguard.dev/unchained/zero-friction-keyless-signing-with-github-actions
- 6: https://docs.sigstore.dev/certificate_authority/oidc-in-fulcio
🏁 Script executed:
#!/bin/bash
# Find the README and docs files mentioned
fd -t f -e md "cosign-sign/README.md|typescript-build.md|go-release-workflow.md"Repository: LerianStudio/github-actions-shared-workflows
Length of output: 140
🏁 Script executed:
#!/bin/bash
# Search for the verification example snippet across the repo
rg "certificate-identity-regexp" --type md -B 3 -A 3Repository: LerianStudio/github-actions-shared-workflows
Length of output: 1628
Fix insecure cosign verification examples in documentation.
The --certificate-identity-regexp=".*" pattern in cosign verification examples is too permissive—it matches any GitHub Actions identity from the trusted issuer, not just your repository or workflow. This defeats the security purpose of identity-scoped verification.
Use --certificate-identity-regexp="^https://github.com/OWNER/REPO/.github/workflows/WORKFLOW.yml@refs/heads/BRANCH$" to restrict verification to your specific repository, workflow, and branch. Replace the placeholders with actual values.
Fix this pattern in:
src/security/cosign-sign/README.md(lines 65–70)docs/typescript-build.mddocs/go-release-workflow.mddocs/build-workflow.md
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/security/cosign-sign/README.md` around lines 65 - 70, The cosign example
uses an overly permissive --certificate-identity-regexp=".*"; update the
examples to a repository/workflow/branch-scoped regexp (e.g.
--certificate-identity-regexp="^https://github.com/OWNER/REPO/.github/workflows/WORKFLOW.yml@refs/heads/BRANCH$")
replacing OWNER/REPO/WORKFLOW.yml/BRANCH with real values; change the example in
src/security/cosign-sign/README.md and the matching examples in
docs/typescript-build.md, docs/go-release-workflow.md, and
docs/build-workflow.md to use the scoped --certificate-identity-regexp pattern
instead of ".*" so identity verification is restricted to the intended
repository/workflow/branch.
GitHub Actions Shared Workflows
Description
Type of Change
feat: New workflow or new input/output/step in an existing workflowfix: Bug fix in a workflow (incorrect behavior, broken step, wrong condition)perf: Performance improvement (e.g. caching, parallelism, reduced steps)refactor: Internal restructuring with no behavior changedocs: Documentation only (README, docs/, inline comments)ci: Changes to self-CI (workflows under.github/workflows/that run on this repo)chore: Dependency bumps, config updates, maintenancetest: Adding or updating testsBREAKING CHANGE: Callers must update their configuration after this PRBreaking Changes
None.
Testing
@developor the beta tagCaller repo / workflow run:
Related Issues
Closes #
Summary by CodeRabbit
New Features
Documentation
Chores