Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
124 changes: 124 additions & 0 deletions .github/workflows/docker-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
# Copyright 2026 ResQ Software
# SPDX-License-Identifier: Apache-2.0
#
# Reusable container build + publish + provenance workflow.
# Builds via buildx, pushes to ghcr.io by default, and produces
# an SLSA build-provenance attestation (sigstore-backed) for the
# image digest. Callable from any resq-software/* repo.
#
# SHA-pinned actions with trailing `# <tag>` for Dependabot.

name: docker-publish

on:
workflow_call:
inputs:
image:
description: Image name (without registry), e.g. "resq-software/mcp".
type: string
required: true
registry:
type: string
required: false
default: "ghcr.io"
context:
description: Docker build context path.
type: string
required: false
default: "."
dockerfile:
description: Path to Dockerfile relative to repo root.
type: string
required: false
default: "Dockerfile"
platforms:
description: Comma-separated target platforms.
type: string
required: false
default: "linux/amd64,linux/arm64"
tags:
description: |
Newline-separated image tags to push (full refs). Example:
ghcr.io/resq-software/mcp:latest
ghcr.io/resq-software/mcp:${{ version }}
type: string
required: true
build-args:
description: Newline-separated docker build args (KEY=VALUE).
type: string
required: false
default: ""
push:
type: boolean
required: false
default: true
attest:
description: Emit SLSA build-provenance attestation.
type: boolean
required: false
default: true
ref:
description: Git ref to checkout (defaults to workflow ref).
type: string
required: false
default: ""
outputs:
image-digest:
description: sha256 digest of the pushed image manifest.
value: ${{ jobs.publish.outputs.digest }}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

permissions:
contents: read

jobs:
publish:
name: Build, push, attest
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: read
packages: write # push to GHCR
id-token: write # OIDC for keyless sigstore signing
attestations: write # actions/attest-build-provenance
outputs:
digest: ${{ steps.build.outputs.digest }}
steps:
- name: Harden Runner
uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 # v2
with:
egress-policy: audit

- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ inputs.ref || github.ref }}

- uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3

Check warning

Code scanning / CodeQL

Checkout of untrusted code in a non-privileged context Medium

Potential unsafe checkout of untrusted pull request on non-privileged workflow.
Comment thread
WomB0ComB0 marked this conversation as resolved.
Dismissed
- uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3

- name: Log in to registry
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
with:
registry: ${{ inputs.registry }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
Comment on lines +182 to +188

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

Login credentials are hardcoded to the GHCR flow but registry is a configurable input.

registry defaults to ghcr.io but is a caller-controlled input, while the login step always uses github.actor + secrets.GITHUB_TOKEN. Those credentials only authenticate against GHCR, so a caller that sets registry: docker.io (or an ECR/ACR URL) will fail at this step with no clear signal that this workflow is GHCR-only.

Either (a) document this workflow as GHCR-only and validate inputs.registry ends with ghcr.io, or (b) expose caller secrets via on.workflow_call.secrets (e.g., registry-username, registry-password) and fall back to the GHCR defaults when unset.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/docker-publish.yml around lines 132 - 138, The login step
hardcodes GHCR credentials (github.actor + secrets.GITHUB_TOKEN) while registry
is configurable, causing failures for non-ghcr registries; update the workflow
to accept caller-provided registry credentials (e.g., define
on.workflow_call.secrets like registry-username and registry-password or inputs
for them) and in the docker/login-action step use those secrets when present,
falling back to github.actor and secrets.GITHUB_TOKEN only if
registry-username/registry-password are unset; alternatively add validation for
the inputs.registry value to ensure it endsWith "ghcr.io" and fail early if
not—look for the "registry" input, the docker/login-action step, and uses of
"github.actor" / "secrets.GITHUB_TOKEN" to implement this conditional credential
selection or validation.


- name: Build and push
id: build
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
with:
context: ${{ inputs.context }}

Check warning

Code scanning / CodeQL

Code injection Medium

Potential code injection in
${ inputs.context }
, which may be controlled by an external user.
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
file: ${{ inputs.dockerfile }}
platforms: ${{ inputs.platforms }}
tags: ${{ inputs.tags }}
build-args: ${{ inputs.build-args }}
push: ${{ inputs.push }}
provenance: mode=max
sbom: true

- if: ${{ inputs.attest && inputs.push }}
name: Attest build provenance
uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0
with:
subject-name: ${{ inputs.registry }}/${{ inputs.image }}
subject-digest: ${{ steps.build.outputs.digest }}
push-to-registry: true
122 changes: 122 additions & 0 deletions .github/workflows/node-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
# Copyright 2026 ResQ Software
# SPDX-License-Identifier: Apache-2.0
#
# Reusable Node/TypeScript CI — supports bun (default), npm, pnpm, yarn.
# Generic; doesn't enforce a specific test framework or linter. Pairs
# with security-scan.yml. SHA-pinned actions with trailing `# <tag>`.

name: node-ci

on:
workflow_call:
inputs:
package-manager:
description: bun | npm | pnpm | yarn.
type: string
required: false
default: "bun"
node-version:
type: string
required: false
default: "22"
bun-version:
type: string
required: false
default: "latest"
working-directory:
type: string
required: false
default: "."
install-cmd:
description: Override install command. Empty = pm default (e.g., `bun install`).
type: string
required: false
default: ""
lint-cmd:
description: Lint script (empty to skip).
type: string
required: false
default: ""
typecheck-cmd:
description: Typecheck script (empty to skip).
type: string
required: false
default: ""
test-cmd:
description: Test script (empty to skip).
type: string
required: false
default: ""
build-cmd:
description: Build script (empty to skip).
type: string
required: false
default: ""
timeout-minutes:
type: number
required: false
default: 15

permissions:
contents: read

jobs:
ci:
name: Node CI
runs-on: ubuntu-latest
timeout-minutes: ${{ inputs.timeout-minutes }}
defaults:
run:
working-directory: ${{ inputs.working-directory }}
steps:
- name: Harden Runner
uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 # v2
with:
egress-policy: audit

- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- if: ${{ inputs.package-manager == 'bun' }}
uses: oven-sh/setup-bun@735343b667d3e6f658f44d0eca948eb6282f2b76 # v2
with:
bun-version: ${{ inputs.bun-version }}

- if: ${{ inputs.package-manager != 'bun' }}
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: ${{ inputs.node-version }}
cache: ${{ inputs.package-manager == 'yarn' && 'yarn' || inputs.package-manager == 'pnpm' && 'pnpm' || 'npm' }}

- if: ${{ inputs.package-manager == 'pnpm' }}
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4
with:
run_install: false
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

- name: Install
run: |
if [ -n "${{ inputs.install-cmd }}" ]; then

Check warning

Code scanning / CodeQL

Code injection Medium

Potential code injection in
${ inputs.install-cmd }
, which may be controlled by an external user.
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
${{ inputs.install-cmd }}

Check warning

Code scanning / CodeQL

Code injection Medium

Potential code injection in
${ inputs.install-cmd }
, which may be controlled by an external user.
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
else
case "${{ inputs.package-manager }}" in

Check warning

Code scanning / CodeQL

Code injection Medium

Potential code injection in
${ inputs.package-manager }
, which may be controlled by an external user.
Potential code injection in
${ inputs.package-manager }
, which may be controlled by an external user.
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
bun) bun install --frozen-lockfile ;;
npm) npm ci ;;
pnpm) pnpm install --frozen-lockfile ;;
yarn) yarn install --immutable ;;
esac
fi

- if: ${{ inputs.lint-cmd != '' }}
name: Lint
run: ${{ inputs.lint-cmd }}

Check warning

Code scanning / CodeQL

Code injection Medium

Potential code injection in
${ inputs.lint-cmd }
, which may be controlled by an external user.
Potential code injection in
${ inputs.lint-cmd }
, which may be controlled by an external user.
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed

- if: ${{ inputs.typecheck-cmd != '' }}
name: Typecheck
run: ${{ inputs.typecheck-cmd }}

Check warning

Code scanning / CodeQL

Code injection Medium

Potential code injection in
${ inputs.typecheck-cmd }
, which may be controlled by an external user.
Potential code injection in
${ inputs.typecheck-cmd }
, which may be controlled by an external user.
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed

- if: ${{ inputs.build-cmd != '' }}
name: Build
run: ${{ inputs.build-cmd }}

Check warning

Code scanning / CodeQL

Code injection Medium

Potential code injection in
${ inputs.build-cmd }
, which may be controlled by an external user.
Potential code injection in
${ inputs.build-cmd }
, which may be controlled by an external user.
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed

- if: ${{ inputs.test-cmd != '' }}
name: Test
run: ${{ inputs.test-cmd }}

Check warning

Code scanning / CodeQL

Code injection Medium

Potential code injection in
${ inputs.test-cmd }
, which may be controlled by an external user.
Potential code injection in
${ inputs.test-cmd }
, which may be controlled by an external user.
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
Loading
Loading