* ci: add security scanning suite and governance
Consolidates the security CI work into one reviewable change. Adds, as
separate workflow files under .github/workflows/:
- secret-scan.yml gitleaks (pinned + checksum-verified), full history
- workflow-security.yml actionlint + zizmor, audits the workflows themselves
- dependency-review.yml PR dependency gate + advisory pip-audit
- container-scan.yml hadolint (blocking) + Trivy image scan (advisory)
- codeql.yml CodeQL for Python and JS, main + weekly
Plus .github/dependabot.yml (pip/npm/actions/docker), .github/CODEOWNERS,
and docs/security-ci.md explaining each check and the one-time settings.
All additive: no existing files are modified. Actions are pinned to commit
SHAs, tokens default-deny (permissions: {}), advisory scans never block,
and SARIF upload is gated to push so fork PRs do not fail on a read-only
token. Composes with the correctness CI in #1015.
* ci(security): isolate Trivy from the Dockerfile lint gate
Address review on #1314 (points 2 and 3).
container-scan.yml now runs only hadolint (the blocking Dockerfile lint)
and keeps the broad pull_request + push:[main] trigger so the required
check always reports and never hangs a PR.
The advisory image scan moves to container-trivy.yml, split by event:
- pull_request / workflow_dispatch: build and scan under contents:read
only, no SARIF upload. The image build runs PR-supplied Dockerfile
instructions, so this path holds no write scope.
- push to main: build, scan, and upload SARIF with security-events:write.
Only this trusted path is granted write.
This stops PR jobs from requesting security-events:write they never use,
and a paths-ignore (matching docker-publish.yml) skips the image rebuild
on docs-only changes.
docs/security-ci.md: correct the trigger description to "every pull
request and every push to main", matching the workflows and the existing
ci.yml convention.
Verified locally: zizmor --offline --min-severity=low and actionlint are
clean on the changed and new workflow files.
---------
Co-authored-by: Alexandre Teixeira <111787685+alteixeira20@users.noreply.github.com>
Pin actions to commit SHAs, set persist-credentials: false on every
checkout, and scope token permissions to the jobs that use them. Suppress
the two findings that are safe by design: the description bot's
pull_request_target trigger (no fork code runs) and an intentional
word-split in the docker manifest step.
Clears actionlint and zizmor against dev so the blocking gate from #1314
can pass once both land.
The Inspect step had `${{ github.ref == ''refs/heads/main'' ... }}` with
doubled single quotes (YAML-scalar escaping) inside a `run: |` block, which
GitHub's expression parser rejects, failing the whole workflow at startup
(no jobs run). Replace with a plain shell conditional on $GITHUB_REF.
* ci: build and publish multi-arch Odysseus image to GHCR
Push to main publishes :latest and :X.Y.Z; push to dev publishes :dev and
an immutable :X.Y.Z-dev.<sha>. Multi-arch (linux/amd64 + linux/arm64) via
per-arch native runners building by digest, merged into one manifest list.
Uses the in-repo GITHUB_TOKEN (packages: write), actions pinned by SHA.
* ci(docker): pin actions to latest major releases
checkout v6.0.3 (matches the PR-checks workflow), setup-buildx v4.1.0,
login v4.2.0, build-push v7.2.0, metadata v6.1.0, upload-artifact v7.0.1,
download-artifact v8.0.1 — all by commit SHA.
#3336 reduced the PR-checks workflow to pull-requests:read on the
assumption that PR labels/comments only need issues:write (the REST path
is /issues/{n}/...). They do not: modifying a pull request's labels or
comments requires the pull-requests scope, so issues:write alone returns
403 and crashed the description check on every PR. Restore
pull-requests:write, and fail soft in swapLabel so a label-permission
error can never mask the description verdict.
* ci(pr-checks): add Conventional Commits PR-title check, pin actions by SHA
Add a check-title job that fails the PR when the title is not Conventional
Commits format (type(scope): summary), via an inline github-script regex.
Pin the workflow's actions to their latest release commit SHAs:
actions/checkout v6.0.3 and actions/github-script v9.0.0.
* ci(pr-checks): flag unmergeable PRs in the PR-checks workflow
Add a check-mergeable job to the (renamed) PR checks workflow: on PR events,
poll the PR's mergeable state and, when it conflicts with the base, remove
'ready for review', add a red 'merge conflict' label (auto-created), and
comment; clear the label once mergeable again. Single-PR, no push trigger.
Add ready_for_review to the trigger types.
* ci(pr-checks): drop the comment from check-mergeable, label swap only
* ci(pr-checks): least-privilege workflow permissions
contents:read for base-ref checkout, pull-requests:read for pulls.get
mergeability, issues:write for label + comment management. Drops the
unused pull-requests:write (labels and PR comments go through the issues
API).
* ci: skip pytest smoke on documentation-only changes
Adding paths-ignore for **.md and docs/** so that PRs that touch only
markdown files do not trigger the full pytest suite. Runner minutes are
spent only when Python or config files change.
Closes#2646.
* ci: detect docs-only changes inside the job instead of paths-ignore
Previously paths-ignore on the pull_request trigger caused the entire
workflow to be skipped, which can leave required checks pending and block
merging. Instead, keep the workflow always-triggered and detect docs-only
changes inside python-tests with a git diff step; if every changed file
is a .md or docs/ path, the step reports success without running pytest.
The syntax jobs (python-syntax, node-syntax) are cheap enough to always run.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
.github/workflows/ci.yml runs on push to main + PRs:
- python-syntax: compileall over app.py + core/routes/src/services/scripts/tests
- node-syntax: node --check on our JS (static/app.js + static/js)
- python-tests: pip install + pytest (continue-on-error for now)
Hardening: least-privilege `permissions: contents: read`, a `concurrency`
group that cancels superseded runs, and actions pinned to commit SHAs
(version in a comment) instead of mutable tags.
* ci: add issue/PR description completeness checks (#1958)
Two github-script workflows that validate description structure on
issue/PR open/edit/reopen, for submissions that bypass the browser
template (API, gh CLI, agent bulk PRs).
- PR check: Summary, Linked Issue, Type of Change, duplicate-search
box, How to Test.
- Issue check: body length + per-label bug/enhancement fields, plus a
bug+enhancement conflict guard.
- Pass deletes any prior bot comment and applies `ready for review`;
fail posts an in-place comment, fails the check, and applies
`needs work` (PRs) / `needs more info` (issues).
- References existing labels only — never creates or recolours repo
labels (checks existence first, warns and skips if absent).
- Safe pull_request_target: checkout pinned to the base ref, sparse
`.github/scripts` only; PR head never checked out.
Closes#1958
Co-authored-by: Povilas Kirna <povilas.kirna@pebble.net>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>