From 54b9f80a98ec223cb7d8141ee6d262c7595c68b4 Mon Sep 17 00:00:00 2001 From: Phillip Cloud <417981+cpcloud@users.noreply.github.com> Date: Thu, 22 Oct 2026 17:37:51 -0400 Subject: [PATCH 1/3] fix(deps): build with Go 1.27.3 to reject distrusted eMudhra intermediates Go 1.27.3 adds a denylist to crypto/x509 (GO-2026-6809, CVE-2026-111943). Certificate verification now rejects any chain that contains one of a small set of distrusted intermediate certificates, including the eMudhra/emSign set. Some system trust stores still accept these certificates, so a binary built with Go 1.27.2 can trust a server that presents one. Both micasa and the relay make outbound HTTPS calls (LLM providers, sync, Stripe), so this check is on their normal code path. govulncheck and osv-scanner fail on every branch until the toolchain moves. The go directive alone does not fix shipped artifacts. Nix builds use nixpkgs' go_1_27, and the relay image uses the golang:1.27-alpine build stage. Both pinned Go 1.27.2, so the flake input and the image digest move together with go.mod. The other image digests move as well, to pick up current CA bundles. This supersedes the open Renovate docker-images update, whose golang digest is already stale. The newest caddy:2-alpine image is still built with Go 1.26.8, which does not have the denylist. Caddy only makes outbound TLS calls to its ACME provider, and the fix must come from an upstream Caddy release. Remove the govulncheck excludes for GO-2026-4599 to GO-2026-4602. Go 1.26.1 fixed them, so they no longer suppress anything. One of them also claimed that micasa does not verify x509 certificates, which is false. Generated with Claude Code (claude-opus-5-5) Co-Authored-By: Claude Opus 5.5 --- .claude/codebase/structure.md | 2 +- .github/copilot-instructions.md | 2 +- .govulncheck-exclude | 11 ----------- deploy/docker-compose.caddy.yml | 2 +- deploy/docker-compose.yml | 2 +- deploy/relay/Dockerfile | 4 ++-- flake.lock | 18 +++++++++--------- go.mod | 2 +- 8 files changed, 16 insertions(+), 27 deletions(-) diff --git a/.claude/codebase/structure.md b/.claude/codebase/structure.md index 8ee54138..4171fd12 100644 --- a/.claude/codebase/structure.md +++ b/.claude/codebase/structure.md @@ -181,7 +181,7 @@ internal/ - `.golangci.yml` - Linter config: `default: all` with errorlint strict (errorf + errorf-multi + comparison + asserts), goconst/gomodguard disabled -- `go.mod` - Go 1.27.2; deps include bubbletea v2 / lipgloss v2 / huh v2, +- `go.mod` - Go 1.27.3; deps include bubbletea v2 / lipgloss v2 / huh v2, gorm + modernc sqlite, any-llm-go, modelcontextprotocol/go-sdk - `docs/` - Hugo site (guides, reference, blog) - `plans/` - Design documents (committed to repo) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index c11aa65b..af278ec5 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -9,7 +9,7 @@ maintenance schedules, appliances, vendors, incidents, and expenses. It uses a single SQLite database file with no cloud dependencies. -- **Language**: Go 1.27.2 +- **Language**: Go 1.27.3 - **TUI framework**: Charmbracelet Bubble Tea + lipgloss + huh forms - **ORM**: GORM with pure-Go SQLite (`modernc.org/sqlite`) - **LLM**: Optional integration via `any-llm-go` (Ollama, OpenAI-compatible) diff --git a/.govulncheck-exclude b/.govulncheck-exclude index f0c9bf7d..128f24b1 100644 --- a/.govulncheck-exclude +++ b/.govulncheck-exclude @@ -10,14 +10,3 @@ GO-2025-3689 GO-2025-3695 GO-2025-3824 GO-2025-4251 - -# Go stdlib vulns with no fix in Go 1.25.x yet (fixed in 1.25.8 / 1.26.1). -# Re-evaluate after nixpkgs picks up Go 1.25.8+. -# os.FileInfo Root escape; micasa never uses os.Root -GO-2026-4602 -# net/url IPv6 parsing; micasa only connects to localhost ollama -GO-2026-4601 -# crypto/x509 malformed cert panic; micasa does not verify x509 certs -GO-2026-4600 -# crypto/x509 email constraint enforcement; same as above -GO-2026-4599 diff --git a/deploy/docker-compose.caddy.yml b/deploy/docker-compose.caddy.yml index 42e04877..f99dcbde 100644 --- a/deploy/docker-compose.caddy.yml +++ b/deploy/docker-compose.caddy.yml @@ -3,7 +3,7 @@ services: caddy: - image: caddy@sha256:844f60b64e4724a5aa8245e019dace0d3f199f7433ce6c57676cb30a920dbad9 # 2-alpine + image: caddy@sha256:eb4d35b7452d1ab6171fbdbe8d9275b2301dad6dad92115d91e19fb9b24d510f # 2-alpine environment: DOMAIN: ${DOMAIN} ports: diff --git a/deploy/docker-compose.yml b/deploy/docker-compose.yml index 3cfffa4d..82d48d29 100644 --- a/deploy/docker-compose.yml +++ b/deploy/docker-compose.yml @@ -3,7 +3,7 @@ services: postgres: - image: postgres@sha256:a02db8cac496f15b094798a38254f14d6e00741f709360e5e00bb6668ea31636 # 17-alpine + image: postgres@sha256:eee6c6e1b0894fa4fe0075eb054af9fd1cf39a35321c2d4110626cfe506411fb # 17-alpine environment: POSTGRES_USER: micasa POSTGRES_DB: micasa diff --git a/deploy/relay/Dockerfile b/deploy/relay/Dockerfile index 8098e8f8..8403f5c9 100644 --- a/deploy/relay/Dockerfile +++ b/deploy/relay/Dockerfile @@ -2,7 +2,7 @@ # Licensed under the Apache License, Version 2.0 # golang 1.27-alpine -FROM golang@sha256:f92b6ef800e499660581efdabdf25d9d817a9d124eaf900924f0504e7e27e12d AS build +FROM golang@sha256:b8c9b82affbbb82a020bbcf998dad2ba9516944ac7e883524642a481a9505e9c AS build ARG BUILD_TAGS WORKDIR /src COPY go.mod go.sum ./ @@ -11,7 +11,7 @@ COPY . . RUN CGO_ENABLED=0 go build -trimpath ${BUILD_TAGS:+-tags "$BUILD_TAGS"} -o /relay ./cmd/relay # distroless static-debian12 -FROM gcr.io/distroless/static-debian12@sha256:a9fcaedd4c9b59e12dd65d954f0b5044f19b0647a8a3712e77205df9e7b102cd +FROM gcr.io/distroless/static-debian12@sha256:d75cdd72874d4790092fcb1b058493ecf6bb5bf2b2b897045b00ff01d91843f2 COPY --from=build /relay /relay EXPOSE 8080 ENTRYPOINT ["/relay"] diff --git a/flake.lock b/flake.lock index a710f1df..0ed43f9b 100644 --- a/flake.lock +++ b/flake.lock @@ -21,11 +21,11 @@ "systems": "systems" }, "locked": { - "lastModified": 1731533236, - "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "lastModified": 1791914995, + "narHash": "sha256-Zf/xfQEAasnwnmEfHqW/4hjDE/s44yn2hD4RuVIZRjA=", "owner": "numtide", "repo": "flake-utils", - "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "rev": "b7a79d3fea1e214f6d668d521cecec0c01e26544", "type": "github" }, "original": { @@ -42,11 +42,11 @@ ] }, "locked": { - "lastModified": 1790500375, - "narHash": "sha256-XN3sDtn8TU9hAc9xqZ+SqRB/HHm2wjxM6aSWYLJU+oo=", + "lastModified": 1791909798, + "narHash": "sha256-VAhpCOmrun/KXimYfR8NM88bciKCui7CqHHhThSzgag=", "owner": "cachix", "repo": "git-hooks.nix", - "rev": "a0e4241b51206fbcbf52fd322eb5f0cd80f153c4", + "rev": "1ba1dbfe28738c9a71c266be6efa5b89e75491a9", "type": "github" }, "original": { @@ -77,11 +77,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1791832894, - "narHash": "sha256-OWH1UMHyyujUBebnpMKqD2S8ESIJBqXl/s0mIUvX2tk=", + "lastModified": 1792617723, + "narHash": "sha256-IyFA94SfEEqeru/RZyRRbmk8gtBejDTiojSReKzUOc8=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "6aa7ad7f840d5a5bf17020b231d1c8d87a71995b", + "rev": "dce1caf58d1a943af38b376e824a0d036ec3eea7", "type": "github" }, "original": { diff --git a/go.mod b/go.mod index 91e04e44..32f7f3b0 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/micasa-dev/micasa -go 1.27.2 +go 1.27.3 require ( charm.land/bubbles/v2 v2.2.1 From e711ce189a52cbd2f3b25be802decbe142991227 Mon Sep 17 00:00:00 2001 From: Phillip Cloud <417981+cpcloud@users.noreply.github.com> Date: Thu, 22 Oct 2026 17:38:00 -0400 Subject: [PATCH 2/3] docs(website): state the real minimum Go version The install and contributor pages said Go 1.26.5 was enough. go.mod already required a Go 1.27 release, and now requires 1.27.3. A user with an older local toolchain gets a toolchain download or an error that the docs did not prepare them for. Generated with Claude Code (claude-opus-5-5) Co-Authored-By: Claude Opus 5.5 --- docs/content/docs/development/building.md | 2 +- docs/content/docs/development/contributing.md | 2 +- docs/content/docs/getting-started/installation.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/content/docs/development/building.md b/docs/content/docs/development/building.md index fc0b3ba0..1ce24938 100644 --- a/docs/content/docs/development/building.md +++ b/docs/content/docs/development/building.md @@ -7,7 +7,7 @@ linkTitle = "Building" ## Prerequisites -- **Go 1.26.5+** (the only hard requirement) +- **Go 1.27.3+** (the only hard requirement) - **Nix** (optional, but provides the full dev environment) ## Quick build diff --git a/docs/content/docs/development/contributing.md b/docs/content/docs/development/contributing.md index 2c162fcd..f4efbf37 100644 --- a/docs/content/docs/development/contributing.md +++ b/docs/content/docs/development/contributing.md @@ -51,7 +51,7 @@ for use as a dependency will be closed. ## Setup 1. Fork and clone the repo -2. Enter the dev shell: `nix develop` (or install Go 1.26.5+ manually) +2. Enter the dev shell: `nix develop` (or install Go 1.27.3+ manually) 3. The dev shell auto-installs pre-commit hooks on first entry ## Pre-commit hooks diff --git a/docs/content/docs/getting-started/installation.md b/docs/content/docs/getting-started/installation.md index 27e872b8..62345d9e 100644 --- a/docs/content/docs/getting-started/installation.md +++ b/docs/content/docs/getting-started/installation.md @@ -18,7 +18,7 @@ brew install micasa ## Go install -Requires Go 1.26.5+: +Requires Go 1.27.3+: ```sh go install github.com/micasa-dev/micasa/cmd/micasa@latest From c6e03a2293e645c6cea7b7edfd973ff33d39842e Mon Sep 17 00:00:00 2001 From: Phillip Cloud <417981+cpcloud@users.noreply.github.com> Date: Fri, 23 Oct 2026 06:17:16 -0400 Subject: [PATCH 3/3] chore(lint): move exhaustive ignore reasons off the directive line golangci-lint v2.15.0 bundles exhaustive v0.15.0, which rejects any text after an `//exhaustive:` directive. A trailing `// reason` now counts as an argument, so every `//exhaustive:ignore // reason` line fails with "error parsing comment directives: args not allowed". CI installs the latest golangci-lint, and the updated nixpkgs ships the same version, so lint fails both in CI and locally. The reason now sits on the comment line above the directive. Both lines are in the same comment group, so the directive still applies to the switch below it. golangci-lint shows at most 3 identical issues by default, so CI reported only 3 of the 11 broken directives. Generated with Claude Code (claude-opus-5-5) Co-Authored-By: Claude Opus 5.5 --- internal/app/extraction_test.go | 3 ++- internal/app/form_validators_test.go | 3 ++- internal/app/model_update.go | 3 ++- internal/app/mouse.go | 3 ++- internal/config/config.go | 9 ++++++--- internal/config/show.go | 6 ++++-- internal/config/validate.go | 3 ++- internal/data/defaults.go | 3 ++- 8 files changed, 22 insertions(+), 11 deletions(-) diff --git a/internal/app/extraction_test.go b/internal/app/extraction_test.go index b985616a..0b24ba3c 100644 --- a/internal/app/extraction_test.go +++ b/internal/app/extraction_test.go @@ -39,7 +39,8 @@ func newExtractionModel(t *testing.T, steps map[extractionStep]stepStatus) *Mode } for si, status := range steps { ex.Steps[si] = extractionStepInfo{Status: status} - //exhaustive:ignore // test helper only sets known steps + // test helper only sets known steps + //exhaustive:ignore switch si { case stepText: ex.hasText = true diff --git a/internal/app/form_validators_test.go b/internal/app/form_validators_test.go index 80f979eb..a38a9ddd 100644 --- a/internal/app/form_validators_test.go +++ b/internal/app/form_validators_test.go @@ -403,7 +403,8 @@ func TestFormDataStructsHaveNoReferenceFields(t *testing.T) { rt := reflect.TypeOf(s) t.Run(rt.Name(), func(t *testing.T) { for f := range rt.Fields() { - //exhaustive:ignore // only reference kinds matter here + // only reference kinds matter here + //exhaustive:ignore switch f.Type.Kind() { case reflect.Pointer, reflect.Slice, reflect.Map, reflect.Chan, reflect.Func, reflect.Interface: diff --git a/internal/app/model_update.go b/internal/app/model_update.go index 9e853385..18757b70 100644 --- a/internal/app/model_update.go +++ b/internal/app/model_update.go @@ -369,7 +369,8 @@ func (m *Model) updateForm(msg tea.Msg) (tea.Model, tea.Cmd) { } } } - //exhaustive:ignore // third-party enum (charmbracelet/huh) + // third-party enum (charmbracelet/huh) + //exhaustive:ignore switch m.fs.form.State { case huh.StateCompleted: return m, m.saveForm() diff --git a/internal/app/mouse.go b/internal/app/mouse.go index 015822d1..e95016ef 100644 --- a/internal/app/mouse.go +++ b/internal/app/mouse.go @@ -52,7 +52,8 @@ func (m *Model) handleMouseClick(msg tea.MouseClickMsg) (tea.Model, tea.Cmd) { // handleMouseWheel dispatches wheel events to scroll handlers. func (m *Model) handleMouseWheel(msg tea.MouseWheelMsg) (tea.Model, tea.Cmd) { - //exhaustive:ignore // third-party enum (charmbracelet/x/ansi); only vertical scroll is handled + // third-party enum (charmbracelet/x/ansi); only vertical scroll is handled + //exhaustive:ignore switch msg.Button { case tea.MouseWheelUp: return m.handleScroll(-1) diff --git a/internal/config/config.go b/internal/config/config.go index 44568b3e..a83e6142 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -412,7 +412,8 @@ func walkEnvFields(v reflect.Value, prefix string, extra map[string]string) erro // setFieldFromEnv assigns a string value from an environment variable to a // reflected config field, converting types as needed. func setFieldFromEnv(fv reflect.Value, envVar, val string) error { - //exhaustive:ignore // only config-relevant kinds + // only config-relevant kinds + //exhaustive:ignore switch fv.Kind() { case reflect.String: fv.SetString(val) @@ -435,7 +436,8 @@ func setFieldFromEnv(fv reflect.Value, envVar, val string) error { } func setFieldFromEnvPtr(fv reflect.Value, envVar, val string) error { - //exhaustive:ignore // only config-relevant kinds + // only config-relevant kinds + //exhaustive:ignore switch fv.Type().Elem().Kind() { case reflect.Bool: b, err := strconv.ParseBool(val) @@ -592,7 +594,8 @@ func formatValue(v reflect.Value) (string, error) { return val.String(), nil } - //exhaustive:ignore // only config-relevant kinds + // only config-relevant kinds + //exhaustive:ignore switch v.Kind() { case reflect.String: return v.String(), nil diff --git a/internal/config/show.go b/internal/config/show.go index 1ba6e0d0..9fc413eb 100644 --- a/internal/config/show.go +++ b/internal/config/show.go @@ -283,7 +283,8 @@ func formatTOMLValue(v reflect.Value) (string, bool) { return strconv.Quote(string(text)), true } - //exhaustive:ignore // only config-relevant kinds + // only config-relevant kinds + //exhaustive:ignore switch v.Kind() { case reflect.String: return strconv.Quote(v.String()), true @@ -326,7 +327,8 @@ func isEmptyValue(v reflect.Value) bool { return v.IsNil() } - //exhaustive:ignore // only config-relevant kinds + // only config-relevant kinds + //exhaustive:ignore switch v.Kind() { case reflect.String: return v.String() == "" diff --git a/internal/config/validate.go b/internal/config/validate.go index 85d86395..c75bbdf4 100644 --- a/internal/config/validate.go +++ b/internal/config/validate.go @@ -55,7 +55,8 @@ func newConfigValidator() *validator.Validate { mustRegister(v, "nonneg_duration", func(fl validator.FieldLevel) bool { field := fl.Field() - //exhaustive:ignore // only numeric kinds relevant + // only numeric kinds relevant + //exhaustive:ignore switch field.Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return field.Int() >= 0 diff --git a/internal/data/defaults.go b/internal/data/defaults.go index cab26e7d..50223f63 100644 --- a/internal/data/defaults.go +++ b/internal/data/defaults.go @@ -94,7 +94,8 @@ func StructDefault[T any](fieldName string) string { } func setFieldDefault(field reflect.Value, tag string) { - //exhaustive:ignore // only handle types used in model/config defaults + // only handle types used in model/config defaults + //exhaustive:ignore switch field.Kind() { case reflect.String: if tag == defaultTagToday {