Skip to content

chore: infra hardening — CodeQL fix, SLSA L3, multi-arch Docker, action pins, NTC cleanup#8

Draft
navidrast wants to merge 7 commits intochore/public-readiness-p0from
chore/infra-hardening-x-items
Draft

chore: infra hardening — CodeQL fix, SLSA L3, multi-arch Docker, action pins, NTC cleanup#8
navidrast wants to merge 7 commits intochore/public-readiness-p0from
chore/infra-hardening-x-items

Conversation

@navidrast
Copy link
Copy Markdown
Collaborator

Summary

Seven commits that close P0 / P1 items from the public-readiness audit. Every change lands in workflow, Docker, release config, plugin manifests, or PWA metadata — zero business-logic code touched, zero API contracts changed, zero data-format shifts.

Chained on top of PR #6 (chore/public-readiness-p0 as base). Once #6 merges, retarget this PR to main with gh pr edit <N> --base main.

Commits (top-down, each independently reviewable)

  1. fix(ci): unbreak CodeQL by removing Flutter build + pinning to v3.35.2
    CodeQL has failed 5/5 recent runs on main. Two compounding causes: Flutter build step + ambiguous @v3 pin. Replace the Flutter build with a single-line app/build/web/index.html placeholder (so //go:embed all:build/web resolves for go build) and pin the three codeql-action steps to v3.35.2. Note: SARIF upload also requires GitHub Advanced Security to be enabled on the repo settings — that's a parallel admin action.

  2. chore(ci): align Go toolchain version to 1.25.6 everywhere
    go.mod and all GitHub workflows were on 1.25. Dockerfile was on 1.24 and Gitea deploy on 1.22.5. Release binaries were built with a patch-lagged toolchain. Bump to 1.25.

  3. chore(ci): add least-privilege permissions blocks to unscoped workflows
    ci.yml / dco.yml / tag-protection.yml had no explicit permissions: block → GITHUB_TOKEN defaulted to repo-wide write. Add minimal scoped blocks. Expected OSSF Scorecard impact: Token-Permissions moves from fail to pass.

  4. feat(release): SLSA L3 provenance attestation + SHA256SUMS checksums
    Add actions/attest-build-provenance@v4.1.0 after goreleaser → every release archive carries an .intoto.jsonl attestation binding binary ← commit ← workflow run. Also make the goreleaser checksum: section explicit (SHA256SUMS) so install.sh's existing SHA-256 verify path has a guaranteed file name.

  5. feat(docker): multi-arch image (linux/amd64 + linux/arm64) via buildx
    Docker image shipped amd64-only → "exec format error" on Apple Silicon, Raspberry Pi, ARM VPS. Add docker/setup-qemu-action + docker/setup-buildx-action@v4.0.0, set platforms: linux/amd64,linux/arm64, turn on GHA layer cache (~2-3 min saved per tag).

  6. chore(ci): pin third-party actions to tagged patch versions
    Every non-actions/*, non-github/* third-party action pinned to a patch tag. Covers subosito/flutter-action, goreleaser-action, sigstore/cosign-installer, anchore/sbom-action, codecov-action, docker/setup-qemu, docker/login, docker/metadata, docker/build-push, googleapis/release-please, pozil/auto-assign-issue. actions/* left at major per Scorecard guidance (trusted first-party).

  7. fix: rename remaining NTC leftovers + replace Flutter template defaults
    Anomaly sweep found six string-only leftovers — three user-visible bugs, three cosmetic:

    • plugins/builtin/telegram/manifest.json declared envVar: NTC_TELEGRAM_BOT_TOKEN but the Go fallback reads OPENDRAY_TELEGRAM_BOT_TOKEN → silent no-op for users following documented env var. Manifest now matches code.
    • app/web/manifest.json was the pristine Flutter create template (name: "ntc", description: "A new Flutter project.", Flutter-blue theme) → rewritten with product name, tagline, brand palette.
    • app/web/index.html same — HTML <title>, iOS web-app-title, meta description all said "ntc" → now "OpenDray".
    • plugins/builtin/opencode/manifest.json description: "NTC host" → "OpenDray host".
    • gateway/simulator.go iOS screenshot temp path: /tmp/ntc_sim_*.png/tmp/opendray_sim_*.png. Verified single write site + same-function read+delete; zero external dependency.
    • Android android:label: "opendray" (lowercase) → "OpenDray" to match iOS CFBundleDisplayName.
      Intentionally NOT touched: SQL migration comments, Go comment example paths, docs/plugin-platform/M3-RELEASE.md lineage notes — these are archival / historical context with no user visibility. Go module path github.com/opendray/opendray stays lowercase (Go modules case-sensitive; current is correct).

Scope audit (what this PR does NOT touch)

  • ❌ No .go business-logic code
  • ❌ No API routes, request/response shapes, or WebSocket protocols
  • ❌ No database schema or migrations
  • ❌ No config-file format changes (existing config.toml, .env, plugin-config DB entries still work)
  • ❌ No tests added, removed, or changed
  • ❌ No runtime behaviour change on any path an existing deployment depends on

Test plan

  • go build ./... clean
  • go vet ./... clean
  • -race test suite — no regressions (known pre-existing flaky TestRunner_EchoSucceeds was fixed upstream on main; 3 "failing" test suites earlier flagged were environmental, all pass with clean env)
  • All 3 modified JSON files (app/web/manifest.json, plugins/builtin/{telegram,opencode}/manifest.json) re-parse as valid JSON
  • PR diff is 17 files, +86/-35 lines — every line hand-reviewed
  • Admin-side (@navidrast): enable GitHub Advanced Security in Settings → Code Security & Analysis (unlocks CodeQL SARIF upload; free on public plan)
  • Post-merge: next tag push should produce: SHA256SUMS, .intoto.jsonl attestations, multi-arch Docker manifest (verify: docker manifest inspect ghcr.io/opendray/opendray:<tag> shows amd64 + arm64)

Projected OSSF Scorecard impact

Check Before After
Token-Permissions fail pass
SLSA fail pass
SAST fail pass*
Pinned-Dependencies partial near-complete
Signed-Releases pass pass
Total estimate ~7/17 ~11–12/17

*SAST pass conditional on Advanced Security being enabled in repo settings.

Related

Merge strategy

Rebase and merge — each commit has a distinct Conventional-Commit prefix that release-please will parse into individual CHANGELOG entries. Do NOT squash (would flatten 7 meaningful entries into 1).

🤖 Generated with Claude Code

RCC Bot and others added 7 commits April 21, 2026 05:20
CodeQL has failed on every run (5/5 recent) on main. Two compounding
causes: (a) the workflow runs `flutter build web --release` before
autobuild, which occasionally fails or times out; (b) the `app/embed.go`
`//go:embed all:build/web` target is empty without a prior Flutter
build, so `go build` (and therefore CodeQL autobuild) can't compile.

Fix:
  - Drop the Flutter build step — CodeQL only scans Go.
  - Replace it with a one-liner that seeds `app/build/web/index.html`
    as a placeholder so `//go:embed` resolves and `go build` succeeds.
  - Pin the three github/codeql-action steps to v3.35.2 instead of the
    floating `v3` major, eliminating tag-drift risk.

Note: the CodeQL SARIF upload step ALSO requires GitHub Advanced
Security to be enabled on the repo (currently off for private repos
on the free plan; free automatically once the repo is public, or
via the Pro plan for private). Enabling AS is a repo-settings action
tracked separately (G9-05 in the public-readiness plan).

Closes C8-01 from the 8/10 action plan; partial progress on S8-02.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: RCC Bot <rcc@localhost>
go.mod, ci.yml, codeql.yml, and release.yml all pin Go 1.25, but the
Docker image + Gitea deploy workflow were still on older toolchains:

  - Dockerfile:8 was on golang:1.24-bookworm
  - .gitea/workflows/deploy.yml:23 was on go1.22.5

Bumps both to match go.mod. Eliminates the risk of a release binary
being built with a toolchain that's missed patch-level CVE fixes
released between 1.22 → 1.25, and keeps Docker image output
byte-comparable with the binaries produced by the main release
pipeline.

Closes C8-02 / S8-06 / X-02 from the 8/10 action plan.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: RCC Bot <rcc@localhost>
Three workflows (ci.yml, dco.yml, tag-protection.yml) had no explicit
permissions block, which means the GITHUB_TOKEN defaulted to
repo-wide write access. That's substantially broader than what any of
them need, and OSSF Scorecard's Token-Permissions check rightly flags
it.

Minimal scopes per workflow:

  - ci.yml       → contents: read + id-token: write (for Codecov OIDC)
  - dco.yml      → contents: read + pull-requests: read + checks: write
  - tag-protection.yml → contents: read (actor-allowlist check only)

release.yml, release-please.yml, docker.yml, codeql.yml already had
correct permissions blocks — not touched.

No behaviour change. If any step needs elevated scope in the future,
grow the block deliberately rather than relying on the wide-open
default.

Closes C8-03 / S8-01 / X-03 from the 8/10 action plan. Should move
OSSF Scorecard Token-Permissions from fail → pass.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: RCC Bot <rcc@localhost>
Two small additions that move the release pipeline from "signed
checksums" to full SLSA L3 supply-chain posture.

1. actions/attest-build-provenance@v4.1.0 runs after goreleaser, one
   attestation per release archive (dist/opendray_*.tar.gz). Emits an
   in-toto statement that binds binary → commit → workflow run,
   verifiable downstream with `gh attestation verify` or the sigstore
   transparency log. Requires `attestations: write` on the workflow
   token, added to the permissions block.

2. .goreleaser.yml gains an explicit `checksum:` section. Produces
   `dist/SHA256SUMS` which install.sh already expects (and the
   cosign sign step already signs). The file existed implicitly in
   goreleaser defaults, but making it explicit eliminates the risk
   of a future goreleaser version changing the default name.

Closes C8-05 / C8-06 / S8-03 / S8-07 / X-04 from the 8/10 action plan.

Expected Scorecard impact: SLSA check moves from FAIL to PASS on the
next tag; Signed-Releases remains PASS. Total bump projected +2 on
the Scorecard score.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: RCC Bot <rcc@localhost>
Docker image previously shipped amd64-only. Apple Silicon users,
Raspberry Pi, AWS Graviton, and ARM-based VPSes were left with either
"exec format error" or a forced manual rebuild.

Changes:
  - Add docker/setup-qemu-action@v3 + docker/setup-buildx-action@v4.0.0
    as prerequisites for multi-platform builds.
  - Set `platforms: linux/amd64,linux/arm64` on build-push-action. The
    resulting manifest list covers both architectures under the same
    tag; `docker pull` auto-selects per host.
  - Turn on GHA layer caching (`cache-from: type=gha` + `cache-to:
    type=gha,mode=max`) to amortise the Flutter + Go toolchain download
    across release runs. Expected ~2–3 min saved per tag.
  - Also pin `github/codeql-action/upload-sarif` to v3.35.2 for
    consistency with codeql.yml.

Closes C8-04 / X-11 from the 8/10 action plan.

Goreleaser archive platforms already cover linux + darwin × amd64 +
arm64, so this closes the "docker pull" parity gap with direct binary
installs.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: RCC Bot <rcc@localhost>
Supply-chain hardening: every non-actions/*, non-github/* third-party
action is now pinned to a patch tag so upstream can't silently change
behaviour between runs. The existing trivy-action and tim-actions/dco
pins (PR #6) already followed this rule; this extends it everywhere.

Pinned (major → patch):
  - subosito/flutter-action          v2       → v2.23.0
  - goreleaser/goreleaser-action     v6       → v6.4.0
  - sigstore/cosign-installer        v3       → v3.10.1
  - anchore/sbom-action              v0       → v0.24.0
  - codecov/codecov-action           v4       → v4.6.0
  - docker/setup-qemu-action         v3       → v3.7.0
  - docker/login-action              v3       → v3.7.0
  - docker/metadata-action           v5       → v5.10.0
  - docker/build-push-action         v6       → v6.19.2
  - googleapis/release-please-action v4       → v4.4.1
  - pozil/auto-assign-issue          v2       → v2.2.0

Left at major (first-party from trusted maintainers per Scorecard
guidance — these orgs don't rewrite tags):
  - actions/checkout, actions/setup-go, actions/upload-artifact,
    actions/download-artifact, actions/labeler
  - github/codeql-action/* already pinned to v3.35.2 in the CodeQL
    and Docker commits in this PR.

Expected Scorecard impact: Pinned-Dependencies moves from partial
to near-complete. Combined with the other commits in this PR,
projected OSSF Scorecard jump: ~7/17 → ~11/17.

Closes S8-04 / X-05 from the 8/10 action plan.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: RCC Bot <rcc@localhost>
Anomaly sweep uncovered three user-visible bugs and three cosmetic
leftovers from the project's earlier NTC name. All are pure string
changes — no business logic, no API, no config-format shifts.

User-visible bugs (3):

1. plugins/builtin/telegram/manifest.json — configSchema declared
   `envVar: NTC_TELEGRAM_BOT_TOKEN` but the Go fallback at
   gateway/telegram/manager.go:219 reads OPENDRAY_TELEGRAM_BOT_TOKEN.
   Operators following the manifest's documented env var got silent
   no-op with no error signal. Manifest now matches code.

2. app/web/manifest.json — was the pristine Flutter-create template:
   name "ntc", short_name "ntc", description "A new Flutter project.",
   background+theme colour #0175C2 (Flutter blue). Every browser's
   "Install this app" prompt, every installed PWA, every splash screen
   showed "ntc". Rewritten with product name + tagline + brand palette
   (#0B1120 background, #0EA5E9 accent from opendray.dev site).

3. app/web/index.html — HTML title, Apple mobile web-app title, and
   meta description were all still "ntc" / "A new Flutter project.".
   Affects browser tab text and iOS "Add to Home Screen" app name.

Cosmetic (3):

4. plugins/builtin/opencode/manifest.json — config description said
   "Path to the opencode binary on the NTC host." → "OpenDray host".

5. gateway/simulator.go — iOS simulator screenshot temp path was
   /tmp/ntc_sim_%d.png → /tmp/opendray_sim_%d.png. Verified no other
   file reads or references this path; lifetime is within a single
   function call (create → exec xcrun → read → defer os.Remove).

6. app/android/app/src/main/AndroidManifest.xml — `android:label`
   was lowercase "opendray"; iOS CFBundleDisplayName + Flutter
   MaterialApp.title are both "OpenDray". Capitalised for platform
   parity; Android home-screen + app drawer now match iOS display.

Not touched (deliberate):
  - SQL migration comments in kernel/store/migrations/006, 008
    (historical context, no user visibility)
  - gateway/docs/forge.go comment example path
  - docs/plugin-platform/M3-RELEASE.md lineage notes
    (rcc → ntc → opendray — intentional provenance documentation)
  - Go module path github.com/opendray/opendray (modules case-
    sensitive; lowercase is correct)

Verification:
  - go build ./...  — clean
  - go vet ./...    — clean
  - all three JSON files re-parse cleanly
  - diff: 6 files, +13/-13 lines

No test changes required; existing tests pass without modification.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: RCC Bot <rcc@localhost>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant