Skip to content

fix(release): merge develop into main#195

Merged
bedatty merged 10 commits intomainfrom
develop
Apr 1, 2026
Merged

fix(release): merge develop into main#195
bedatty merged 10 commits intomainfrom
develop

Conversation

@bedatty
Copy link
Copy Markdown
Contributor

@bedatty bedatty commented Apr 1, 2026

Lerian

GitHub Actions Shared Workflows


Description

Type of Change

  • feat: New workflow or new input/output/step in an existing workflow
  • fix: 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 change
  • docs: 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, maintenance
  • test: Adding or updating tests
  • BREAKING CHANGE: Callers must update their configuration after this PR

Breaking Changes

None.

Testing

  • YAML syntax validated locally
  • Triggered a real workflow run on a caller repository using @develop or the beta tag
  • Verified all existing inputs still work with default values
  • Confirmed no secrets or tokens are printed in logs
  • Checked that unrelated workflows are not affected

Caller repo / workflow run:

Related Issues

Closes #

Summary by CodeRabbit

  • New Features

    • Container image signing with Sigstore cosign keyless (OIDC) signing now available across build workflows
    • Added configurable Docker build arguments input for customizable container builds
  • Documentation

    • Enhanced security guidance for GitHub Actions workflows with improved best practices
    • Added comprehensive documentation for image signing verification and configuration options
  • Chores

    • Updated GitHub Actions dependencies to pinned commit SHAs for improved supply chain security
    • Improved shell script safety and stability across workflows

bedatty and others added 10 commits March 30, 2026 15:57
…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
@bedatty bedatty requested a review from a team as a code owner April 1, 2026 14:23
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 1, 2026

Walkthrough

Added 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 enable_cosign_sign input, pinned action dependencies to commit SHAs, improved shell safety patterns in helm workflow, and documented the cosign feature across workflow documentation.

Changes

Cohort / File(s) Summary
Security Documentation
.cursor/rules/reusable-workflows.mdc, AGENTS.md
Added GitHub Actions security hardening rules: prohibitions for pull_request_target (fork code checkout/execution, secrets: inherit), expression injection prevention via env-var indirection, workflow_run artifact validation, explicit least-privilege permissions declarations, fork-context secrets handling, branch/label-derived variable validation, and self-hosted runner restrictions.
Cosign Signing Action
src/security/cosign-sign/action.yml, src/security/cosign-sign/README.md
New GitHub composite action for keyless OIDC-based cosign image signing with inputs for image references (digest-required format), cosign version, and dry-run mode; output of successfully signed references; validation and fast-fail on missing digests.
Build Workflow Cosign Integration
.github/workflows/build.yml, .github/workflows/go-release.yml, .github/workflows/typescript-build.yml
Added enable_cosign_sign input (default true), elevated permissions with id-token: write, captured build digest via step id, constructed per-registry image references, and conditionally invoked cosign signing workflow with digest-based references.
PR Security Scan Workflow
.github/workflows/pr-security-scan.yml
Added docker_build_args input and wired to Docker build action's build-args field.
Release Workflow Updates
.github/workflows/typescript-release.yml, .github/workflows/release.yml
Pinned GitHub Actions dependencies (actions/create-github-app-token, actions/checkout, actions/setup-node, crazy-max/ghaction-import-gpg, cycjimmy/semantic-release-action, and release-tag-snapshot action) to commit SHAs; updated tag snapshot action version reference.
Helm Workflow Hardening
.github/workflows/helm-update-chart.yml
Pinned GitHub Action dependencies to commit SHAs; refactored shell steps to use env: variables for better quoting; replaced for ... in $(...) loops with while IFS= read -r and process substitution to prevent word-splitting; consolidated Slack output via grouped summary block; rewrote Slack notification to pass tokens via env: and use jq for JSON construction.
PR Security Reporter Action
src/security/pr-security-reporter/action.yml
Pinned actions/github-script to commit SHA; refactored to use env: variables (HAS_ERRORS, HAS_FINDINGS) instead of inline expressions; changed parse-artifact failure from warning to error; added explicit exit-code 1 on error/finding conditions.
Workflow Documentation
docs/build-workflow.md, docs/go-release-workflow.md, docs/typescript-build.md
Added documentation for new enable_cosign_sign input, keyless OIDC-based cosign signing behavior, required caller permissions (id-token: write), disabling instructions, and signature verification examples via cosign verify.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~28 minutes

Possibly related PRs

Suggested labels

workflow, security, documentation, dependencies, size/M

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning The PR title 'fix(release): merge develop into main' does not accurately reflect the substantial content changes; it focuses only on the merge operation rather than the primary changes (cosign signing implementation, security hardening, workflow refactoring). Update the title to reflect the main technical change, such as 'feat: add cosign keyless image signing with security hardening' or split into multiple PRs by concern.
Description check ⚠️ Warning The PR description is largely incomplete: the main 'Description' section under '' is empty, providing no summary of changes, affected workflows, or behavior modifications. Fill in the Description section with a clear summary of the major changes (cosign signing implementation, security rules, workflow refactoring), list affected workflows (.github/workflows/.yml, docs/, security rules), and explain behavior changes for callers.
✅ Passed checks (1 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch develop

Comment @coderabbitai help to get the list of available commands and usage tips.

@lerian-studio lerian-studio added size/L PR changes 500–999 lines documentation Improvements or additions to documentation workflow Changes to one or more reusable workflow files typescript Changes to TypeScript or Frontend workflows security Changes to security workflows or vulnerability reporting policy golang Changes to Go-related workflows labels Apr 1, 2026
@lerian-studio
Copy link
Copy Markdown

🔍 Lint Analysis

Check Files Scanned Status
YAML Lint 9 file(s) ✅ success
Action Lint 7 file(s) ✅ success
Pinned Actions 9 file(s) ✅ success
Markdown Link Check 5 file(s) ✅ success
Spelling Check 15 file(s) ✅ success
Shell Check 9 file(s) ✅ success
README Check 9 file(s) ✅ success
Composite Schema 2 file(s) ✅ success
⚠️ Warnings (4)

Pinned Actions

.github

  • .github (line 90) — Found 3 internal action(s) not pinned to a version. Consider pinning to vX.Y.Z.

.github/workflows/typescript-build.yml

  • .github/workflows/typescript-build.yml (line 336) — Internal action not pinned to a version: uses: LerianStudio/github-actions-shared-workflows/src/security/cosign-sign@feat/cosign-sign

.github/workflows/go-release.yml

  • .github/workflows/go-release.yml (line 197) — Internal action not pinned to a version: uses: LerianStudio/github-actions-shared-workflows/src/security/cosign-sign@feat/cosign-sign

.github/workflows/build.yml

  • .github/workflows/build.yml (line 344) — Internal action not pinned to a version: uses: LerianStudio/github-actions-shared-workflows/src/security/cosign-sign@feat/cosign-sign

🔍 View full scan logs

@lerian-studio
Copy link
Copy Markdown

🛡️ CodeQL Analysis Results

Languages analyzed: actions

Found 2 issue(s): 2 Medium

Severity Rule File Message
🟡 Medium actions/untrusted-checkout/medium .github/workflows/helm-update-chart.yml:155 Potential unsafe checkout of untrusted pull request on privileged workflow.
🟡 Medium actions/untrusted-checkout/medium .github/workflows/go-release.yml:128 Potential unsafe checkout of untrusted pull request on privileged workflow.

🔍 View full scan logs | 🛡️ Security tab

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

👉 Steps to fix this

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

📥 Commits

Reviewing files that changed from the base of the PR and between bbb826b and a3fb701.

📒 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.yml
  • AGENTS.md
  • docs/build-workflow.md
  • docs/go-release-workflow.md
  • docs/typescript-build.md
  • src/security/cosign-sign/README.md
  • src/security/cosign-sign/action.yml
  • src/security/pr-security-reporter/action.yml

Comment on lines +420 to +505
### 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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

Comment on lines 139 to +142
permissions:
contents: read
packages: write
id-token: write
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

Comment on lines +314 to +340
- 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"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

Comment on lines +342 to +346
- 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 }}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

Comment on lines 75 to +78
permissions:
contents: write
packages: write
id-token: write
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

Comment on lines +334 to +338
- 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 }}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

Comment on lines +226 to +230
```bash
cosign verify \
--certificate-identity-regexp=".*" \
--certificate-oidc-issuer="https://token.actions.githubusercontent.com" \
docker.io/lerianstudio/my-app@sha256:abc123...
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 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:


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.

Comment on lines +207 to +216
### 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
```
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

Comment on lines +46 to +50
- 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 }}
```
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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.

Suggested change
- 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.

Comment on lines +65 to +70
```bash
cosign verify \
--certificate-identity-regexp=".*" \
--certificate-oidc-issuer="https://token.actions.githubusercontent.com" \
docker.io/myorg/myapp@sha256:abc123...
```
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 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:


🏁 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 3

Repository: 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.md
  • docs/go-release-workflow.md
  • docs/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.

@bedatty bedatty merged commit 34df442 into main Apr 1, 2026
20 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation golang Changes to Go-related workflows security Changes to security workflows or vulnerability reporting policy size/L PR changes 500–999 lines typescript Changes to TypeScript or Frontend workflows workflow Changes to one or more reusable workflow files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants