#19: add Trivy scanning and multi-architecture GHCR publishing

CI gains a security job (Trivy fs: vuln, secret and misconfig) and an
image job that builds each of the four services and scans the result. The
secret scanner earns its place here: a DKIM key or Cloudflare token
committed by accident is a full compromise of every domain served.

release.yml publishes on a merge into main and on v* tags. Four services
on two architectures is eight build jobs pushing by digest, then four
publish jobs joining each pair into one manifest list, tagged latest, sha
and semver. github.repository is lowercased first because GHCR rejects an
uppercase path and this repository is lucasrguerra/LambdaMail.

Both scanners failed on first run, so this commit also fixes what they
found rather than shipping a workflow that is red on arrival:

  - All four images ran as root (DS-0002), which section 17 risk R5
    explicitly argues against. They now run unprivileged; protocols keeps
    its eight ports below 1024 through cap_net_bind_service on the binary
    rather than through being root.

  - A HIGH advisory in postcss, reached only through Next. "npm audit fix
    --force" would have resolved it by downgrading Next from 15 to 9, so
    it is pinned with an override instead.

  - The Node images copied the build stage's node_modules wholesale,
    shipping typescript, eslint and vitest into production, and the base
    image's own bundled npm accounted for the rest of the CVEs. Runtime
    dependencies now come from npm ci --omit=dev and npm is removed from
    the final layer.

  - compose-up could never have passed: the proxy network is external
    because Coolify owns it on the deploy host, and nothing creates it in
    CI, so compose refused to start the stack at all.

Verified locally: actionlint clean, all ten action refs confirmed to
exist, Trivy fs and all four image scans exit 0, the full stack reaches
Healthy on every container, and the containers run as uid 10001/1000 with
all eight privileged ports bound and SMTP answering on 25. The arm64
build itself is unverified - this machine has no buildx - and will first
be exercised by release.yml.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Lucas RayanandClaude Opus 5 committed 2026-08-02 07:58:02 -03:00
1 parent 3deb328bbf
commit 46d421c874
8 files changed
+350 -79

No files matched your search

+72 -1
View File
@@ -1,18 +1,28 @@
name: CI name: CI
# Runs on every branch and pull request. Publishing lives in release.yml and
# only runs once this workflow has passed.
on: on:
push: push:
branches: [main] branches: ['**']
pull_request: pull_request:
branches: [main]
workflow_call:
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
jobs: jobs:
lang-lint: lang-lint:
name: Lint de idioma (R9)
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- run: node scripts/lang-lint.mjs - run: node scripts/lang-lint.mjs
go-protocols: go-protocols:
name: Go (protocols)
runs-on: ubuntu-latest runs-on: ubuntu-latest
services: services:
postgres: postgres:
@@ -49,6 +59,7 @@ jobs:
TEST_DATABASE_URL: postgres://lambdamail_test:lambdamail_test@localhost:5432/lambdamail_test?sslmode=disable TEST_DATABASE_URL: postgres://lambdamail_test:lambdamail_test@localhost:5432/lambdamail_test?sslmode=disable
ts-auth: ts-auth:
name: TypeScript (auth)
runs-on: ubuntu-latest runs-on: ubuntu-latest
defaults: defaults:
run: run:
@@ -62,6 +73,7 @@ jobs:
- run: npm test - run: npm test
ts-storage: ts-storage:
name: TypeScript (storage)
runs-on: ubuntu-latest runs-on: ubuntu-latest
defaults: defaults:
run: run:
@@ -75,6 +87,7 @@ jobs:
- run: npm test - run: npm test
webmail-build: webmail-build:
name: Build (webmail)
runs-on: ubuntu-latest runs-on: ubuntu-latest
defaults: defaults:
run: run:
@@ -86,12 +99,70 @@ jobs:
- run: npm install - run: npm install
- run: npm run build - run: npm run build
security:
name: Trivy (dependências, segredos e Dockerfiles)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# A mail server holds credentials for every mailbox it serves, so the
# secret scanner matters as much as the vulnerability one here: a DKIM
# key or a Cloudflare token committed by accident is a full compromise.
- name: Repositório
uses: aquasecurity/trivy-action@v0.36.0
with:
scan-type: fs
scanners: vuln,secret,misconfig
severity: HIGH,CRITICAL
exit-code: '1'
skip-dirs: services/auth/node_modules,services/storage/node_modules,services/webmail/node_modules,services/webmail/.next
image:
name: Imagem (${{ matrix.service }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
service: [protocols, auth, storage, webmail]
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
# Built but not pushed: this proves each Dockerfile still works on every
# branch, while publishing stays in release.yml.
- name: Build
uses: docker/build-push-action@v6
with:
context: services/${{ matrix.service }}
push: false
load: true
tags: lambdamail-${{ matrix.service }}:ci
cache-from: type=gha,scope=${{ matrix.service }}
cache-to: type=gha,mode=max,scope=${{ matrix.service }}
- name: Trivy na imagem
uses: aquasecurity/trivy-action@v0.36.0
with:
image-ref: lambdamail-${{ matrix.service }}:ci
severity: HIGH,CRITICAL
exit-code: '1'
compose-up: compose-up:
name: Stack completa
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: [go-protocols, ts-auth, ts-storage, webmail-build] needs: [go-protocols, ts-auth, ts-storage, webmail-build]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- run: cp .env.example .env - run: cp .env.example .env
# The proxy network is declared external because Coolify owns it on the
# deploy host. Nothing creates it here, and without it compose refuses to
# start the stack at all.
- name: Cria a rede do proxy
run: docker network create "${COOLIFY_PROXY_NETWORK:-coolify}"
# --build makes compose build each service locally and tag it under the
# same name the published image uses, so the stack comes up without ever
# reaching the registry.
- run: docker compose --env-file .env up --build --wait - run: docker compose --env-file .env up --build --wait
- run: docker compose ps - run: docker compose ps
- run: docker compose down -v - run: docker compose down -v
+179
View File
@@ -0,0 +1,179 @@
name: Release
# Publishes the four service images to GHCR after a merge into main, and again
# under semver tags when a v* tag is pushed. The deploy host only pulls, so
# nothing has to be built on the server.
on:
push:
branches: [main]
tags: ['v*']
workflow_dispatch:
concurrency:
group: release
cancel-in-progress: false
env:
REGISTRY: ghcr.io
jobs:
# Nothing is published unless the full suite passes first.
ci:
uses: ./.github/workflows/ci.yml
# GHCR rejects an uppercase path, and this repository is "lucasrguerra/LambdaMail".
# Lowercasing github.repository here keeps the workflow fork-safe instead of
# hardcoding the owner.
prepare:
name: Namespace das imagens
runs-on: ubuntu-latest
outputs:
namespace: ${{ steps.namespace.outputs.value }}
steps:
- id: namespace
run: echo "value=${GITHUB_REPOSITORY,,}" >> "$GITHUB_OUTPUT"
# Each architecture is built on a runner of its own kind. QEMU emulation
# would also work, but it makes the Go and Next builds roughly an order of
# magnitude slower.
build:
name: Build ${{ matrix.service }} (${{ matrix.platform }})
needs: [ci, prepare]
runs-on: ${{ matrix.runner }}
permissions:
contents: read
packages: write
strategy:
fail-fast: false
matrix:
# Both are axes, so this is a cross product: four services on two
# architectures is eight jobs. Declaring platform only under `include`
# would not multiply anything - include can add keys to existing
# combinations, but it cannot create the combinations themselves.
service: [protocols, auth, storage, webmail]
platform: [linux/amd64, linux/arm64]
include:
- platform: linux/amd64
runner: ubuntu-latest
arch: amd64
- platform: linux/arm64
runner: ubuntu-24.04-arm
arch: arm64
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- name: Autentica no GHCR
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Metadados
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ needs.prepare.outputs.namespace }}/${{ matrix.service }}
# Pushed by digest only: the tags are attached later, once both
# architectures exist and can be joined into a single manifest list.
- name: Build e push por digest
id: build
uses: docker/build-push-action@v6
with:
context: services/${{ matrix.service }}
platforms: ${{ matrix.platform }}
labels: ${{ steps.meta.outputs.labels }}
outputs: type=image,name=${{ env.REGISTRY }}/${{ needs.prepare.outputs.namespace }}/${{ matrix.service }},push-by-digest=true,name-canonical=true,push=true
cache-from: type=gha,scope=${{ matrix.service }}-${{ matrix.arch }}
cache-to: type=gha,mode=max,scope=${{ matrix.service }}-${{ matrix.arch }}
- name: Exporta o digest
run: |
mkdir -p /tmp/digests
# The output already carries the `sha256:` prefix; the file name must
# not, since the merge step prepends it again.
digest='${{ steps.build.outputs.digest }}'
touch "/tmp/digests/${digest#sha256:}"
- name: Guarda o digest
uses: actions/upload-artifact@v4
with:
# Slashes are not allowed in artifact names, hence the arch alias.
name: digest-${{ matrix.service }}-${{ matrix.arch }}
path: /tmp/digests/*
retention-days: 1
if-no-files-found: error
publish:
name: Publica ${{ matrix.service }}
needs: [build, prepare]
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
strategy:
fail-fast: false
matrix:
service: [protocols, auth, storage, webmail]
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- name: Autentica no GHCR
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Recupera os digests
uses: actions/download-artifact@v4
with:
pattern: digest-${{ matrix.service }}-*
merge-multiple: true
path: /tmp/digests
# `latest` is what compose pulls. The semver tags only materialise on a
# v* tag push and make it possible to pin or roll back to an exact
# release; the sha tag does the same for any main build.
- name: Metadados
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ needs.prepare.outputs.namespace }}/${{ matrix.service }}
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=raw,value=latest
type=sha,format=long
# Joins both architectures under one manifest list, so `docker pull`
# resolves to the right one automatically.
- name: Cria o manifesto multi-arquitetura
working-directory: /tmp/digests
run: |
docker buildx imagetools create \
$(jq -cr '.tags | map("-t " + .) | join(" ")' <<< '${{ steps.meta.outputs.json }}') \
$(printf '${{ env.REGISTRY }}/${{ needs.prepare.outputs.namespace }}/${{ matrix.service }}@sha256:%s ' *)
- name: Confere o resultado
run: |
docker buildx imagetools inspect \
"${{ env.REGISTRY }}/${{ needs.prepare.outputs.namespace }}/${{ matrix.service }}:latest" \
--format '{{json .Manifest}}' \
| jq -r '.manifests[] | select(.platform.os == "linux") | "\(.platform.os)/\(.platform.architecture)"'
- name: Resumo
run: |
{
echo "### ${{ matrix.service }} publicado (linux/amd64 e linux/arm64)"
echo
echo '```'
echo "${{ steps.meta.outputs.tags }}"
echo '```'
} >> "$GITHUB_STEP_SUMMARY"
+25 -3
View File
@@ -1,14 +1,36 @@
FROM node:20-alpine AS build FROM node:20-alpine AS build
WORKDIR /app WORKDIR /app
COPY package.json package-lock.json* ./ COPY package.json package-lock.json* ./
RUN npm install # npm ci, not npm install: the image must be the lockfile's content, not
# whatever resolves the day it is built.
RUN npm ci
COPY . . COPY . .
RUN npm run build RUN npm run build
FROM node:20-alpine # Production dependencies only. The previous image copied the build stage's
# node_modules wholesale, shipping typescript, eslint and vitest into
# production - most of the CVEs the image scan reported came from that
# tooling, which never runs in the container.
FROM node:20-alpine AS prod-deps
WORKDIR /app WORKDIR /app
COPY --from=build /app/node_modules ./node_modules COPY package.json package-lock.json* ./
RUN npm ci --omit=dev
FROM node:20-alpine
# The node:20-alpine tag lags its Alpine base, so pending security patches
# (openssl, busybox) land only with an explicit upgrade.
RUN apk upgrade --no-cache \
# The runtime only ever runs "node"; npm is not invoked and its own
# bundled dependency tree (sigstore, cross-spawn, glob, ...) is what
# every remaining image CVE came from. Removing it drops the whole
# class instead of chasing each advisory.
&& rm -rf /usr/local/lib/node_modules/npm /usr/local/bin/npm /usr/local/bin/npx
WORKDIR /app
COPY --from=prod-deps /app/node_modules ./node_modules
COPY --from=build /app/dist ./dist COPY --from=build /app/dist ./dist
COPY package.json ./ COPY package.json ./
# The node images ship an unprivileged "node" user (uid 1000).
USER node
EXPOSE 3001 EXPOSE 3001
CMD ["node", "dist/server.js"] CMD ["node", "dist/server.js"]
+20 -1
View File
@@ -6,7 +6,26 @@ COPY . .
RUN CGO_ENABLED=0 go build -o /out/lambdamail-protocols ./cmd/lambdamail-protocols RUN CGO_ENABLED=0 go build -o /out/lambdamail-protocols ./cmd/lambdamail-protocols
FROM alpine:3.20 FROM alpine:3.20
RUN apk add --no-cache ca-certificates # libcap provides setcap and is dropped again once the capability is set.
RUN apk add --no-cache ca-certificates libcap
COPY --from=build /out/lambdamail-protocols /app/lambdamail-protocols COPY --from=build /out/lambdamail-protocols /app/lambdamail-protocols
# This process terminates SMTP from the open internet, so it must not run as
# root (PLAN.md section 17, risk R5). Every mail port it binds is below 1024,
# which normally requires root - the file capability grants exactly that one
# privilege to this one binary instead.
RUN setcap 'cap_net_bind_service=+ep' /app/lambdamail-protocols \
&& apk del libcap
RUN addgroup -g 10001 -S lambdamail \
&& adduser -u 10001 -S -G lambdamail lambdamail \
# Created here so a fresh named volume mounted at this path inherits the
# ownership: Docker seeds an empty volume from the image directory.
&& mkdir -p /var/lambdamail/spool \
&& chown -R lambdamail:lambdamail /var/lambdamail
USER lambdamail:lambdamail
EXPOSE 8080 EXPOSE 8080
ENTRYPOINT ["/app/lambdamail-protocols"] ENTRYPOINT ["/app/lambdamail-protocols"]
+29 -3
View File
@@ -1,14 +1,40 @@
FROM node:20-alpine AS build FROM node:20-alpine AS build
WORKDIR /app WORKDIR /app
COPY package.json package-lock.json* ./ COPY package.json package-lock.json* ./
RUN npm install # npm ci, not npm install: the image must be the lockfile's content, not
# whatever resolves the day it is built.
RUN npm ci
COPY . . COPY . .
RUN npm run build RUN npm run build
FROM node:20-alpine # Production dependencies only. The previous image copied the build stage's
# node_modules wholesale, shipping typescript, eslint and vitest into
# production - most of the CVEs the image scan reported came from that
# tooling, which never runs in the container.
FROM node:20-alpine AS prod-deps
WORKDIR /app WORKDIR /app
COPY --from=build /app/node_modules ./node_modules COPY package.json package-lock.json* ./
RUN npm ci --omit=dev
FROM node:20-alpine
# The node:20-alpine tag lags its Alpine base, so pending security patches
# (openssl, busybox) land only with an explicit upgrade.
RUN apk upgrade --no-cache \
# The runtime only ever runs "node"; npm is not invoked and its own
# bundled dependency tree (sigstore, cross-spawn, glob, ...) is what
# every remaining image CVE came from. Removing it drops the whole
# class instead of chasing each advisory.
&& rm -rf /usr/local/lib/node_modules/npm /usr/local/bin/npm /usr/local/bin/npx
WORKDIR /app
COPY --from=prod-deps /app/node_modules ./node_modules
COPY --from=build /app/dist ./dist COPY --from=build /app/dist ./dist
COPY package.json ./ COPY package.json ./
# Created before dropping privileges so a fresh named volume mounted here
# inherits the ownership: Docker seeds an empty volume from the image.
RUN mkdir -p /var/lambdamail/mails && chown -R node:node /var/lambdamail
# The node images ship an unprivileged "node" user (uid 1000).
USER node
EXPOSE 3002 EXPOSE 3002
CMD ["node", "dist/server.js"] CMD ["node", "dist/server.js"]
+17 -4
View File
@@ -1,15 +1,28 @@
FROM node:20-alpine AS build FROM node:20-alpine AS build
WORKDIR /app WORKDIR /app
COPY package.json package-lock.json* ./ COPY package.json package-lock.json* ./
RUN npm install RUN npm ci
COPY . . COPY . .
RUN npm run build RUN npm run build
FROM node:20-alpine FROM node:20-alpine
# The node:20-alpine tag lags its Alpine base, so pending security patches
# (openssl, busybox) land only with an explicit upgrade.
RUN apk upgrade --no-cache \
# The runtime only ever runs "node"; npm is not invoked and its own
# bundled dependency tree (sigstore, cross-spawn, glob, ...) is what
# every remaining image CVE came from. Removing it drops the whole
# class instead of chasing each advisory.
&& rm -rf /usr/local/lib/node_modules/npm /usr/local/bin/npm /usr/local/bin/npx
WORKDIR /app WORKDIR /app
COPY --from=build /app/.next/standalone ./ # --chown matters here: Next's standalone server writes its cache under
COPY --from=build /app/.next/static ./.next/static # .next at runtime, which it cannot do if the tree belongs to root.
COPY --from=build /app/public ./public COPY --from=build --chown=node:node /app/.next/standalone ./
COPY --from=build --chown=node:node /app/.next/static ./.next/static
COPY --from=build --chown=node:node /app/public ./public
# The node images ship an unprivileged "node" user (uid 1000).
USER node
EXPOSE 3000 EXPOSE 3000
# Docker sets HOSTNAME to the container ID by default; Next's standalone # Docker sets HOSTNAME to the container ID by default; Next's standalone
# server.js honors that env var and binds only to it, refusing localhost # server.js honors that env var and binds only to it, refusing localhost
+6 -66
View File
@@ -140,9 +140,6 @@
"cpu": [ "cpu": [
"arm" "arm"
], ],
"libc": [
"glibc"
],
"license": "LGPL-3.0-or-later", "license": "LGPL-3.0-or-later",
"optional": true, "optional": true,
"os": [ "os": [
@@ -159,9 +156,6 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"libc": [
"glibc"
],
"license": "LGPL-3.0-or-later", "license": "LGPL-3.0-or-later",
"optional": true, "optional": true,
"os": [ "os": [
@@ -178,9 +172,6 @@
"cpu": [ "cpu": [
"ppc64" "ppc64"
], ],
"libc": [
"glibc"
],
"license": "LGPL-3.0-or-later", "license": "LGPL-3.0-or-later",
"optional": true, "optional": true,
"os": [ "os": [
@@ -197,9 +188,6 @@
"cpu": [ "cpu": [
"riscv64" "riscv64"
], ],
"libc": [
"glibc"
],
"license": "LGPL-3.0-or-later", "license": "LGPL-3.0-or-later",
"optional": true, "optional": true,
"os": [ "os": [
@@ -216,9 +204,6 @@
"cpu": [ "cpu": [
"s390x" "s390x"
], ],
"libc": [
"glibc"
],
"license": "LGPL-3.0-or-later", "license": "LGPL-3.0-or-later",
"optional": true, "optional": true,
"os": [ "os": [
@@ -235,9 +220,6 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"libc": [
"glibc"
],
"license": "LGPL-3.0-or-later", "license": "LGPL-3.0-or-later",
"optional": true, "optional": true,
"os": [ "os": [
@@ -254,9 +236,6 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"libc": [
"musl"
],
"license": "LGPL-3.0-or-later", "license": "LGPL-3.0-or-later",
"optional": true, "optional": true,
"os": [ "os": [
@@ -273,9 +252,6 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"libc": [
"musl"
],
"license": "LGPL-3.0-or-later", "license": "LGPL-3.0-or-later",
"optional": true, "optional": true,
"os": [ "os": [
@@ -292,9 +268,6 @@
"cpu": [ "cpu": [
"arm" "arm"
], ],
"libc": [
"glibc"
],
"license": "Apache-2.0", "license": "Apache-2.0",
"optional": true, "optional": true,
"os": [ "os": [
@@ -317,9 +290,6 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"libc": [
"glibc"
],
"license": "Apache-2.0", "license": "Apache-2.0",
"optional": true, "optional": true,
"os": [ "os": [
@@ -342,9 +312,6 @@
"cpu": [ "cpu": [
"ppc64" "ppc64"
], ],
"libc": [
"glibc"
],
"license": "Apache-2.0", "license": "Apache-2.0",
"optional": true, "optional": true,
"os": [ "os": [
@@ -367,9 +334,6 @@
"cpu": [ "cpu": [
"riscv64" "riscv64"
], ],
"libc": [
"glibc"
],
"license": "Apache-2.0", "license": "Apache-2.0",
"optional": true, "optional": true,
"os": [ "os": [
@@ -392,9 +356,6 @@
"cpu": [ "cpu": [
"s390x" "s390x"
], ],
"libc": [
"glibc"
],
"license": "Apache-2.0", "license": "Apache-2.0",
"optional": true, "optional": true,
"os": [ "os": [
@@ -417,9 +378,6 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"libc": [
"glibc"
],
"license": "Apache-2.0", "license": "Apache-2.0",
"optional": true, "optional": true,
"os": [ "os": [
@@ -442,9 +400,6 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"libc": [
"musl"
],
"license": "Apache-2.0", "license": "Apache-2.0",
"optional": true, "optional": true,
"os": [ "os": [
@@ -467,9 +422,6 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"libc": [
"musl"
],
"license": "Apache-2.0", "license": "Apache-2.0",
"optional": true, "optional": true,
"os": [ "os": [
@@ -622,9 +574,6 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"libc": [
"glibc"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -641,9 +590,6 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"libc": [
"musl"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -660,9 +606,6 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"libc": [
"glibc"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -679,9 +622,6 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"libc": [
"musl"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -898,9 +838,9 @@
"license": "ISC" "license": "ISC"
}, },
"node_modules/postcss": { "node_modules/postcss": {
"version": "8.4.31", "version": "8.5.25",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.25.tgz",
"integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", "integrity": "sha512-DTPx3RWSSnWyzLxQnlH0rJP+EW5ekl16ZU4/psbIhA0e53kJfdgaN5vKM+xP7yJtXVu+nfdVFmlgFDEKAe4Pyw==",
"funding": [ "funding": [
{ {
"type": "opencollective", "type": "opencollective",
@@ -917,9 +857,9 @@
], ],
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"nanoid": "^3.3.6", "nanoid": "^3.3.16",
"picocolors": "^1.0.0", "picocolors": "^1.1.1",
"source-map-js": "^1.0.2" "source-map-js": "^1.2.1"
}, },
"engines": { "engines": {
"node": "^10 || ^12 || >=14" "node": "^10 || ^12 || >=14"
+2 -1
View File
@@ -18,6 +18,7 @@
"@types/react": "^18.3.3" "@types/react": "^18.3.3"
}, },
"overrides": { "overrides": {
"sharp": "^0.35.0" "sharp": "^0.35.0",
"postcss": "^8.5.25"
} }
} }