diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index df0076a..bafd73e 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -25,13 +25,13 @@ jobs: github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'push' steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: ref: ${{ github.event.workflow_run.head_sha }} - name: Docker metadata id: meta - uses: docker/metadata-action@v6 + uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6 with: images: ghcr.io/${{ github.repository }} tags: | @@ -39,17 +39,17 @@ jobs: type=raw,value=${{ github.event.workflow_run.head_branch }},enable=${{ startsWith(github.event.workflow_run.head_branch, 'v') }},suffix=-${{ matrix.arch }} - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v4 + uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4 - name: Login to GHCR - uses: docker/login-action@v4 + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Build and push - uses: docker/build-push-action@v7 + uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7 with: context: . file: docker/Dockerfile @@ -69,7 +69,7 @@ jobs: steps: - name: Docker metadata id: meta - uses: docker/metadata-action@v6 + uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6 with: images: ghcr.io/${{ github.repository }} tags: | @@ -77,10 +77,10 @@ jobs: type=raw,value=${{ github.event.workflow_run.head_branch }},enable=${{ startsWith(github.event.workflow_run.head_branch, 'v') }} - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v4 + uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4 - name: Login to GHCR - uses: docker/login-action@v4 + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4 with: registry: ghcr.io username: ${{ github.actor }} diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 2e395e7..5dd260d 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -14,10 +14,10 @@ jobs: name: golangci-lint runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Set up Go - uses: actions/setup-go@v6 + uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6 with: go-version: "1.26" @@ -25,7 +25,7 @@ jobs: run: sudo apt-get install -y libsqlite3-dev - name: Run golangci-lint - uses: golangci/golangci-lint-action@v8 + uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 # v8 with: version: latest args: --timeout=5m diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml index 9391388..3fb5d3f 100644 --- a/.github/workflows/release-drafter.yml +++ b/.github/workflows/release-drafter.yml @@ -16,7 +16,7 @@ jobs: pull-requests: write runs-on: ubuntu-latest steps: - - uses: release-drafter/release-drafter@v6 + - uses: release-drafter/release-drafter@67e173cadb2fbd3de94f4a861e0c48c913b462ae # v6 with: config-name: release-drafter.yml env: diff --git a/.github/workflows/smithy-sync.yml b/.github/workflows/smithy-sync.yml index b372256..cff900f 100644 --- a/.github/workflows/smithy-sync.yml +++ b/.github/workflows/smithy-sync.yml @@ -13,10 +13,10 @@ jobs: sync: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Set up Go - uses: actions/setup-go@v6 + uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6 with: go-version: "1.26" @@ -52,7 +52,7 @@ jobs: - name: Create Pull Request if: steps.changes.outputs.changed == 'true' - uses: peter-evans/create-pull-request@v8 + uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8 with: commit-message: "chore: sync Smithy models and regenerate code" title: "chore: weekly Smithy model sync" diff --git a/docker/Dockerfile b/docker/Dockerfile index 2457088..18c1056 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -20,7 +20,8 @@ RUN go build -o /codegen ./cmd/codegen # Stage 3: Runtime FROM alpine:3.20 -RUN apk add --no-cache sqlite-libs ca-certificates +RUN apk add --no-cache sqlite-libs ca-certificates su-exec +RUN adduser -D -H -h /app appuser WORKDIR /app COPY --from=go-builder /devcloud /app/devcloud COPY --from=go-builder /codegen /app/codegen @@ -28,8 +29,11 @@ COPY --from=web-builder /app/web/out /app/web/out COPY devcloud.yaml /app/devcloud.yaml COPY smithy-models/ /app/smithy-models/ COPY internal/codegen/templates/ /app/templates/ +COPY docker/entrypoint.sh /app/entrypoint.sh +RUN chown -R appuser:appuser /app EXPOSE 4747 VOLUME /app/data +ENTRYPOINT ["/app/entrypoint.sh"] CMD ["/app/devcloud", "-config", "/app/devcloud.yaml"] diff --git a/docker/Dockerfile.dev b/docker/Dockerfile.dev index 76a34e1..ba5322c 100644 --- a/docker/Dockerfile.dev +++ b/docker/Dockerfile.dev @@ -1,9 +1,12 @@ FROM golang:1.26-alpine RUN apk add --no-cache gcc musl-dev sqlite-dev +RUN adduser -D -H -h /app appuser WORKDIR /app COPY go.mod go.sum ./ RUN go mod download COPY . . +RUN chown -R appuser:appuser /app +USER appuser ENV CGO_ENABLED=1 RUN go build -o /app/devcloud ./cmd/devcloud EXPOSE 4747 diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh new file mode 100755 index 0000000..a0ecfb5 --- /dev/null +++ b/docker/entrypoint.sh @@ -0,0 +1,11 @@ +#!/bin/sh +set -e + +# Ensure the data directory exists and is writable by appuser. +mkdir -p /app/data +owner=$(stat -c %U /app/data 2>/dev/null || echo "") +if [ "$owner" != "appuser" ]; then + chown appuser:appuser /app/data +fi + +exec su-exec appuser "$@" diff --git a/internal/gateway/router.go b/internal/gateway/router.go index 8d250b3..4175882 100644 --- a/internal/gateway/router.go +++ b/internal/gateway/router.go @@ -5,6 +5,7 @@ package gateway import ( "encoding/json" "encoding/xml" + "mime" "net/http" "strings" @@ -45,9 +46,36 @@ func (sr *ServiceRouter) ServeHTTP(w http.ResponseWriter, r *http.Request) { for k, v := range resp.Headers { w.Header().Set(k, v) } - if resp.ContentType != "" { - w.Header().Set("Content-Type", resp.ContentType) + ct := resp.ContentType + if ct == "" { + ct = w.Header().Get("Content-Type") } + if ct == "" { + ct = "application/octet-stream" + } + // Prevent XSS: this gateway serves AWS API responses only (JSON/XML), + // never user-facing HTML. Sanitize any attempt to serve HTML-like content. + ct = strings.TrimSpace(ct) + htmlLike := false + for _, p := range strings.Split(ct, ",") { + p = strings.TrimSpace(p) + if p == "" { + continue + } + mediaType, _, parseErr := mime.ParseMediaType(p) + if parseErr != nil { + continue + } + mtLower := strings.ToLower(mediaType) + if mtLower == "text/html" || mtLower == "application/xhtml+xml" || strings.HasSuffix(mtLower, "+html") { + htmlLike = true + break + } + } + if htmlLike { + ct = "text/plain; charset=utf-8" + } + w.Header().Set("Content-Type", ct) w.WriteHeader(resp.StatusCode) _, _ = w.Write(resp.Body) } diff --git a/internal/shared/store.go b/internal/shared/store.go index b82078d..3692fb7 100644 --- a/internal/shared/store.go +++ b/internal/shared/store.go @@ -3,6 +3,9 @@ package shared import ( + "fmt" + "strings" + "github.com/skyoo2003/devcloud/internal/storage/sqlite" ) @@ -18,8 +21,44 @@ type ResourceStore[T any] struct { scanner func(Scanner) (T, error) } -func NewResourceStore[T any](db *sqlite.Store, table, idCol, cols string, scanner func(Scanner) (T, error)) *ResourceStore[T] { - return &ResourceStore[T]{db: db, table: table, idCol: idCol, cols: cols, scanner: scanner} +func NewResourceStore[T any](db *sqlite.Store, table, idCol, cols string, scanner func(Scanner) (T, error)) (*ResourceStore[T], error) { + table = strings.TrimSpace(table) + idCol = strings.TrimSpace(idCol) + if err := validateIdentifier(table, "table"); err != nil { + return nil, err + } + if err := validateIdentifier(idCol, "idCol"); err != nil { + return nil, err + } + var validCols []string + for _, c := range strings.Split(cols, ",") { + c = strings.TrimSpace(c) + if c == "" { + continue + } + if err := validateIdentifier(c, "col"); err != nil { + return nil, err + } + validCols = append(validCols, c) + } + normalizedCols := strings.Join(validCols, ", ") + return &ResourceStore[T]{db: db, table: table, idCol: idCol, cols: normalizedCols, scanner: scanner}, nil +} + +func validateIdentifier(s, kind string) error { + if len(s) == 0 { + return fmt.Errorf("shared: empty %s identifier", kind) + } + for _, r := range s { + isLower := r >= 'a' && r <= 'z' + isUpper := r >= 'A' && r <= 'Z' + isDigit := r >= '0' && r <= '9' + isUnderscore := r == '_' + if !isLower && !isUpper && !isDigit && !isUnderscore { + return fmt.Errorf("shared: invalid %s identifier: %q", kind, s) + } + } + return nil } func (s *ResourceStore[T]) DB() *sqlite.Store { return s.db } diff --git a/internal/shared/store_test.go b/internal/shared/store_test.go index 61a8235..31b9d78 100644 --- a/internal/shared/store_test.go +++ b/internal/shared/store_test.go @@ -38,7 +38,9 @@ func newTestResourceStore(t *testing.T) *ResourceStore[testItem] { db, err := sqlite.Open(dbPath, testMigrations) require.NoError(t, err) t.Cleanup(func() { _ = db.Close() }) - return NewResourceStore[testItem](db, "items", "id", "id, name", testScanner) + rs, err := NewResourceStore[testItem](db, "items", "id", "id, name", testScanner) + require.NoError(t, err) + return rs } func TestResourceStore_GetNotFound(t *testing.T) { @@ -110,3 +112,93 @@ func TestResourceStore_Count(t *testing.T) { require.NoError(t, err) assert.Equal(t, 1, n) } + +func newTestDB(t *testing.T) *sqlite.Store { + t.Helper() + dbPath := t.TempDir() + "/test.db" + db, err := sqlite.Open(dbPath, testMigrations) + require.NoError(t, err) + t.Cleanup(func() { _ = db.Close() }) + return db +} + +func TestNewResourceStore_InvalidTable(t *testing.T) { + db := newTestDB(t) + _, err := NewResourceStore[testItem](db, "DROP TABLE items; --", "id", "id", testScanner) + assert.ErrorContains(t, err, "invalid table identifier") +} + +func TestNewResourceStore_InvalidIdCol(t *testing.T) { + db := newTestDB(t) + _, err := NewResourceStore[testItem](db, "items", "id; --", "id", testScanner) + assert.ErrorContains(t, err, "invalid idCol identifier") +} + +func TestNewResourceStore_InvalidCol(t *testing.T) { + db := newTestDB(t) + _, err := NewResourceStore[testItem](db, "items", "id", "id, name; --", testScanner) + assert.ErrorContains(t, err, "invalid col identifier") +} + +func TestNewResourceStore_EmptyTable(t *testing.T) { + db := newTestDB(t) + _, err := NewResourceStore[testItem](db, "", "id", "id", testScanner) + assert.ErrorContains(t, err, "empty table identifier") +} + +func TestNewResourceStore_TrailingComma(t *testing.T) { + db := newTestDB(t) + rs, err := NewResourceStore[testItem](db, "items", "id", "id, name,", testScanner) + require.NoError(t, err) + require.NotNil(t, rs) +} + +func TestNewResourceStore_ValidIdentifiers(t *testing.T) { + db := newTestDB(t) + + tests := []struct { + name string + tableName string + primary string + cols string + }{ + { + name: "underscores", + tableName: "items", + primary: "id", + cols: "id, item_name, created_at", + }, + { + name: "digits_in_identifiers", + tableName: "items", + primary: "id", + cols: "id, name2, col3_v1", + }, + { + name: "trailing_comma", + tableName: "items", + primary: "id", + cols: "id, name,", + }, + { + name: "whitespace_in_identifiers", + tableName: " items ", + primary: " id ", + cols: " id, name ", + }, + { + name: "leading_comma_in_cols", + tableName: "items", + primary: "id", + cols: ", id, name", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + rs, err := NewResourceStore[testItem](db, tc.tableName, tc.primary, tc.cols, testScanner) + require.NoError(t, err) + require.NotNil(t, rs) + }) + } +} diff --git a/web/package-lock.json b/web/package-lock.json index 6cfefca..6d5729a 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -13,7 +13,7 @@ "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "lucide-react": "^1.7.0", - "next": "16.2.3", + "next": "^16.2.4", "react": "19.2.4", "react-dom": "19.2.4", "shadcn": "^4.1.2", @@ -1637,9 +1637,9 @@ } }, "node_modules/@next/env": { - "version": "16.2.3", - "resolved": "https://registry.npmjs.org/@next/env/-/env-16.2.3.tgz", - "integrity": "sha512-ZWXyj4uNu4GCWQw9cjRxWlbD+33mcDszIo9iQxFnBX3Wmgq9ulaSJcl6VhuWx5pCWqqD+9W6Wfz7N0lM5lYPMA==", + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/@next/env/-/env-16.2.4.tgz", + "integrity": "sha512-dKkkOzOSwFYe5RX6y26fZgkSpVAlIOJKQHIiydQcrWH6y/97+RceSOAdjZ14Qa3zLduVUy0TXcn+EiM6t4rPgw==", "license": "MIT" }, "node_modules/@next/eslint-plugin-next": { @@ -1653,9 +1653,9 @@ } }, "node_modules/@next/swc-darwin-arm64": { - "version": "16.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.2.3.tgz", - "integrity": "sha512-u37KDKTKQ+OQLvY+z7SNXixwo4Q2/IAJFDzU1fYe66IbCE51aDSAzkNDkWmLN0yjTUh4BKBd+hb69jYn6qqqSg==", + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.2.4.tgz", + "integrity": "sha512-OXTFFox5EKN1Ym08vfrz+OXxmCcEjT4SFMbNRsWZE99dMqt2Kcusl5MqPXcW232RYkMLQTy0hqgAMEsfEd/l2A==", "cpu": [ "arm64" ], @@ -1669,9 +1669,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "16.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.2.3.tgz", - "integrity": "sha512-gHjL/qy6Q6CG3176FWbAKyKh9IfntKZTB3RY/YOJdDFpHGsUDXVH38U4mMNpHVGXmeYW4wj22dMp1lTfmu/bTQ==", + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.2.4.tgz", + "integrity": "sha512-XhpVnUfmYWvD3YrXu55XdcAkQtOnvaI6wtQa8fuF5fGoKoxIUZ0kWPtcOfqJEWngFF/lOS9l3+O9CcownhiQxQ==", "cpu": [ "x64" ], @@ -1685,12 +1685,15 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "16.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.2.3.tgz", - "integrity": "sha512-U6vtblPtU/P14Y/b/n9ZY0GOxbbIhTFuaFR7F4/uMBidCi2nSdaOFhA0Go81L61Zd6527+yvuX44T4ksnf8T+Q==", + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.2.4.tgz", + "integrity": "sha512-Mx/tjlNA3G8kg14QvuGAJ4xBwPk1tUHq56JxZ8CXnZwz1Etz714soCEzGQQzVMz4bEnGPowzkV6Xrp6wAkEWOQ==", "cpu": [ "arm64" ], + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1701,12 +1704,15 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "16.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.2.3.tgz", - "integrity": "sha512-/YV0LgjHUmfhQpn9bVoGc4x4nan64pkhWR5wyEV8yCOfwwrH630KpvRg86olQHTwHIn1z59uh6JwKvHq1h4QEw==", + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.2.4.tgz", + "integrity": "sha512-iVMMp14514u7Nup2umQS03nT/bN9HurK8ufylC3FZNykrwjtx7V1A7+4kvhbDSCeonTVqV3Txnv0Lu+m2oDXNg==", "cpu": [ "arm64" ], + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -1717,12 +1723,15 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "16.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.2.3.tgz", - "integrity": "sha512-/HiWEcp+WMZ7VajuiMEFGZ6cg0+aYZPqCJD3YJEfpVWQsKYSjXQG06vJP6F1rdA03COD9Fef4aODs3YxKx+RDQ==", + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.2.4.tgz", + "integrity": "sha512-EZOvm1aQWgnI/N/xcWOlnS3RQBk0VtVav5Zo7n4p0A7UKyTDx047k8opDbXgBpHl4CulRqRfbw3QrX2w5UOXMQ==", "cpu": [ "x64" ], + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1733,12 +1742,15 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "16.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.2.3.tgz", - "integrity": "sha512-Kt44hGJfZSefebhk/7nIdivoDr3Ugp5+oNz9VvF3GUtfxutucUIHfIO0ZYO8QlOPDQloUVQn4NVC/9JvHRk9hw==", + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.2.4.tgz", + "integrity": "sha512-h9FxsngCm9cTBf71AR4fGznDEDx1hS7+kSEiIRjq5kO1oXWm07DxVGZjCvk0SGx7TSjlUqhI8oOyz7NfwAdPoA==", "cpu": [ "x64" ], + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -1749,9 +1761,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "16.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.2.3.tgz", - "integrity": "sha512-O2NZ9ie3Tq6xj5Z5CSwBT3+aWAMW2PIZ4egUi9MaWLkwaehgtB7YZjPm+UpcNpKOme0IQuqDcor7BsW6QBiQBw==", + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.2.4.tgz", + "integrity": "sha512-3NdJV5OXMSOeJYijX+bjaLge3mJBlh4ybydbT4GFoB/2hAojWHtMhl3CYlYoMrjPuodp0nzFVi4Tj2+WaMg+Ow==", "cpu": [ "arm64" ], @@ -1765,9 +1777,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "16.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.2.3.tgz", - "integrity": "sha512-Ibm29/GgB/ab5n7XKqlStkm54qqZE8v2FnijUPBgrd67FWrac45o/RsNlaOWjme/B5UqeWt/8KM4aWBwA1D2Kw==", + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.2.4.tgz", + "integrity": "sha512-kMVGgsqhO5YTYODD9IPGGhA6iprWidQckK3LmPeW08PIFENRmgfb4MjXHO+p//d+ts2rpjvK5gXWzXSMrPl9cw==", "cpu": [ "x64" ], @@ -7021,12 +7033,12 @@ } }, "node_modules/next": { - "version": "16.2.3", - "resolved": "https://registry.npmjs.org/next/-/next-16.2.3.tgz", - "integrity": "sha512-9V3zV4oZFza3PVev5/poB9g0dEafVcgNyQ8eTRop8GvxZjV2G15FC5ARuG1eFD42QgeYkzJBJzHghNP8Ad9xtA==", + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/next/-/next-16.2.4.tgz", + "integrity": "sha512-kPvz56wF5frc+FxlHI5qnklCzbq53HTwORaWBGdT0vNoKh1Aya9XC8aPauH4NJxqtzbWsS5mAbctm4cr+EkQ2Q==", "license": "MIT", "dependencies": { - "@next/env": "16.2.3", + "@next/env": "16.2.4", "@swc/helpers": "0.5.15", "baseline-browser-mapping": "^2.9.19", "caniuse-lite": "^1.0.30001579", @@ -7040,14 +7052,14 @@ "node": ">=20.9.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "16.2.3", - "@next/swc-darwin-x64": "16.2.3", - "@next/swc-linux-arm64-gnu": "16.2.3", - "@next/swc-linux-arm64-musl": "16.2.3", - "@next/swc-linux-x64-gnu": "16.2.3", - "@next/swc-linux-x64-musl": "16.2.3", - "@next/swc-win32-arm64-msvc": "16.2.3", - "@next/swc-win32-x64-msvc": "16.2.3", + "@next/swc-darwin-arm64": "16.2.4", + "@next/swc-darwin-x64": "16.2.4", + "@next/swc-linux-arm64-gnu": "16.2.4", + "@next/swc-linux-arm64-musl": "16.2.4", + "@next/swc-linux-x64-gnu": "16.2.4", + "@next/swc-linux-x64-musl": "16.2.4", + "@next/swc-win32-arm64-msvc": "16.2.4", + "@next/swc-win32-x64-msvc": "16.2.4", "sharp": "^0.34.5" }, "peerDependencies": { diff --git a/web/package.json b/web/package.json index a7eb7d1..f7c6d00 100644 --- a/web/package.json +++ b/web/package.json @@ -14,7 +14,7 @@ "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "lucide-react": "^1.7.0", - "next": "16.2.3", + "next": "16.2.4", "react": "19.2.4", "react-dom": "19.2.4", "shadcn": "^4.1.2",