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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# SPDX-FileCopyrightText: 2026 soundminds.ai
#
# SPDX-License-Identifier: Apache-2.0

# CodeQL — GitHub's first-party static analysis (SAST). Closes the OSSF
# Scorecard "SAST tool is not run on all commits" finding and, more importantly,
# surfaces real code-level security bugs on every PR. Scans both languages in
# the repo: Python (backend) and JavaScript/TypeScript (ui). Both use
# build-mode `none` — CodeQL extracts interpreted-language source directly, so
# no compile step is needed (and no engine/cluster service containers).
#
# Action refs are pinned to commit SHAs to match the repo's pinned-dependencies
# posture (chore_scorecard_pin_deps_postcss); Dependabot's github-actions
# ecosystem keeps the SHAs fresh.
name: CodeQL

on:
push:
branches: [main]
pull_request:
branches: [main]
schedule:
# Mondays 04:31 UTC — offset from scorecard.yml's 04:27 to spread load.
- cron: "31 4 * * 1"

# Least-privilege default; the analyze job widens to security-events: write.
permissions:
contents: read

concurrency:
group: codeql-${{ github.ref }}
cancel-in-progress: true

jobs:
analyze:
name: Analyze (${{ matrix.language }})
runs-on: ubuntu-latest
timeout-minutes: 20
permissions:
contents: read
security-events: write # upload CodeQL SARIF to the Security tab
strategy:
fail-fast: false
matrix:
include:
- language: python
build-mode: none
- language: javascript-typescript
build-mode: none
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6

- name: Initialize CodeQL
uses: github/codeql-action/init@87557b9c84dde89fdd9b10e88954ac2f4248e463 # v4
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@87557b9c84dde89fdd9b10e88954ac2f4248e463 # v4
with:
category: "/language:${{ matrix.language }}"
15 changes: 8 additions & 7 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,17 @@
# Per docs/01_architecture/deployment.md §"MVP1 deployment shape" + the
# implementation_plan.md Story 4.1 "Decision rationale".

# Single digest-pinned base image (PinnedDependencies / OSSF Scorecard).
# tag + digest in one ARG so an override (`--build-arg BASE_IMAGE=...`) is
# unambiguous — a separate version/digest pair would let the digest silently
# win over a changed tag. Dependabot's docker ecosystem keeps this fresh.
ARG BASE_IMAGE=python:3.14-slim@sha256:c845af9399020c7e562969a13689e929074a10fd057acd1b1fad06a2fb068e97

# ---------------------------------------------------------------------------
# Stage 1 — base: Python + uv + system deps for healthcheck (curl)
# ---------------------------------------------------------------------------
FROM ${BASE_IMAGE} AS base
# python:3.14-slim, digest-pinned (PinnedDependencies / OSSF Scorecard). The
# digest is written literally on the FROM (not via an ARG) because Scorecard's
# static parser only credits a pin it can see inline as `image@sha256:…`; an
# ARG-indirected digest reads as "unpinned". Writing the tag + digest together
# also removes the override footgun of a separate version/digest pair (the
# digest would silently win over a changed tag). Dependabot's docker ecosystem
# bumps the tag + digest together; refresh both when bumping Python.
FROM python:3.14-slim@sha256:c845af9399020c7e562969a13689e929074a10fd057acd1b1fad06a2fb068e97 AS base

ENV PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1 \
Expand Down
23 changes: 12 additions & 11 deletions ui/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -16,27 +16,28 @@
# Node 26 to use the latest engine features without waiting for the
# October LTS transition.

# Single digest-pinned base image (PinnedDependencies / OSSF Scorecard),
# declared once and reused by every stage. Dependabot's docker ecosystem
# keeps the digest fresh.
ARG BASE_IMAGE=node:26-bookworm-slim@sha256:79723b41edbedf595f62e943a9f8b0ba9af5b1e61045c5f8f59c2c02c1212a16

FROM ${BASE_IMAGE} AS deps
# node:26-bookworm-slim, digest-pinned (PinnedDependencies / OSSF Scorecard).
# The digest is written literally on every FROM (not via an ARG) because
# Scorecard's static parser only credits a pin it can see inline as
# `image@sha256:…`; an ARG-indirected digest reads as "unpinned". Dependabot's
# docker ecosystem bumps every occurrence in lockstep, so the repetition is not
# a sync hazard.
FROM node:26-bookworm-slim@sha256:79723b41edbedf595f62e943a9f8b0ba9af5b1e61045c5f8f59c2c02c1212a16 AS deps
WORKDIR /app
RUN npm install -g pnpm@9
COPY pnpm-lock.yaml package.json ./
RUN pnpm install --frozen-lockfile

FROM ${BASE_IMAGE} AS builder
WORKDIR /app
RUN npm install -g pnpm@9
COPY --from=deps /app/node_modules ./node_modules
# Inherit from `deps` — pnpm, WORKDIR, and node_modules are already in place,
# so no redundant install/copy (and one fewer digest occurrence). A stage ref
# to a digest-pinned stage is still credited as pinned by Scorecard.
FROM deps AS builder
COPY . .
ARG NEXT_PUBLIC_API_BASE_URL=http://localhost:8000
ENV NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL
RUN pnpm build

FROM ${BASE_IMAGE} AS runner
FROM node:26-bookworm-slim@sha256:79723b41edbedf595f62e943a9f8b0ba9af5b1e61045c5f8f59c2c02c1212a16 AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV PORT=3000
Expand Down
Loading