Maintain container security, add deb-check script and update vulnerable libs

This commit is contained in:
2026-03-09 09:11:58 +01:00
parent ea8aa0c15c
commit 3cc34c79e3
19 changed files with 2692 additions and 2306 deletions
+33
View File
@@ -0,0 +1,33 @@
name: Security Audit
on:
pull_request:
branches: [ "main" ]
schedule:
- cron: "0 3 * * 1"
workflow_dispatch:
jobs:
audit:
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Run container-only dependency audit
env:
SECURITY_FAIL_ON: high
run: ./scripts/ci-security.sh audit
- name: Generate SBOM artifacts
run: SECURITY_GENERATE_SBOM=1 ./scripts/ci-security.sh audit
- name: Upload security artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: security-artifacts
path: artifacts/security/
if-no-files-found: warn
+3
View File
@@ -1,3 +1,6 @@
.pnpm-store
.tools
# Logs
data/logs
logs
+4
View File
@@ -1,6 +1,7 @@
FROM node:22-alpine AS client-builder
WORKDIR /app
RUN apk upgrade --no-cache
COPY vendor/guacamole-client/guacamole-common-js/ ./vendor/guacamole-client/guacamole-common-js/
@@ -17,6 +18,7 @@ FROM node:22-alpine AS server-builder
ARG VERSION
WORKDIR /app
RUN apk upgrade --no-cache
RUN apk add --no-cache \
python3 py3-pip py3-setuptools \
@@ -32,6 +34,7 @@ RUN for i in 1 2 3; do yarn install --production --frozen-lockfile --network-tim
COPY server/ server/
FROM node:22-alpine AS guacd-builder
RUN apk upgrade --no-cache
RUN apk add --no-cache \
cairo-dev jpeg-dev libpng-dev ossp-uuid-dev \
@@ -56,6 +59,7 @@ RUN cd guacamole-server \
&& strip /install/usr/local/sbin/guacd /install/usr/local/lib/*.so.* 2>/dev/null || true
FROM node:22-alpine
RUN apk upgrade --no-cache
RUN apk add --no-cache \
cairo jpeg libpng ossp-uuid \
+15
View File
@@ -0,0 +1,15 @@
SHELL := /bin/bash
.PHONY: security-update security-audit security-all security-sbom
security-update:
./scripts/ci-security.sh update
security-audit:
./scripts/ci-security.sh audit
security-all:
./scripts/ci-security.sh all
security-sbom:
SECURITY_GENERATE_SBOM=1 ./scripts/ci-security.sh audit
+36
View File
@@ -105,6 +105,42 @@ The server listens on port 6989 by default. You can modify this behavior using e
- Docker container isolation
- Oauth 2.0 OpenID Connect SSO
### Container-Only Security Pipeline
You can run dependency updates and vulnerability audits without installing Node.js, Yarn, pnpm, Flutter or Cargo on your host.
Only Docker or Podman is required.
```sh
# Update dependency locks (root/client/landing/connector) in containers
make security-update
# Run vulnerability audits with fail-on threshold (default: high)
make security-audit
# Update + audit in one run
make security-all
# Audit + SBOM generation
make security-sbom
```
Equivalent npm scripts are available:
```sh
yarn security:update
yarn security:audit
yarn security:all
yarn security:sbom
```
Useful environment variables:
- `SECURITY_FAIL_ON` (`none|critical|high|moderate|low|info`, default: `high`)
- `SECURITY_GENERATE_SBOM` (`1` to enable SBOM output under `artifacts/security/`)
- `SECURITY_NODE_IMAGE` (override Node image, default: `node:22-bookworm-slim`)
- `SECURITY_SYFT_IMAGE` (override Syft image, default: `anchore/syft:latest`)
- `SECURITY_DRY_RUN` (`1` to print container commands without executing)
## Contributing
Contributions are welcome! Please feel free to:
@@ -0,0 +1 @@
{"type":"auditSummary","data":{"vulnerabilities":{"info":0,"low":0,"moderate":0,"high":0,"critical":0},"dependencies":419,"devDependencies":0,"optionalDependencies":0,"totalDependencies":419}}
@@ -0,0 +1 @@
{"type":"auditSummary","data":{"vulnerabilities":{"info":0,"low":0,"moderate":0,"high":0,"critical":0},"dependencies":12,"devDependencies":0,"optionalDependencies":0,"totalDependencies":12}}
@@ -0,0 +1,18 @@
{
"actions": [],
"advisories": {},
"muted": [],
"metadata": {
"vulnerabilities": {
"info": 0,
"low": 0,
"moderate": 0,
"high": 0,
"critical": 0
},
"dependencies": 336,
"devDependencies": 0,
"optionalDependencies": 0,
"totalDependencies": 336
}
}
+1
View File
@@ -0,0 +1 @@
{"type":"auditSummary","data":{"vulnerabilities":{"info":0,"low":0,"moderate":0,"high":0,"critical":0},"dependencies":771,"devDependencies":0,"optionalDependencies":0,"totalDependencies":771}}
+6 -3
View File
@@ -35,10 +35,10 @@
"react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1",
"react-dom": "^19.2.4",
"react-i18next": "^16.5.5",
"react-i18next": "^16.5.6",
"react-router-dom": "^7.13.1",
"react-use-websocket": "^4.13.0",
"simple-icons": "^16.10.0",
"simple-icons": "^16.11.0",
"ua-parser-js": "^2.0.9"
},
"devDependencies": {
@@ -46,12 +46,15 @@
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^5.1.4",
"eslint": "^9.39.2",
"eslint": "^10.0.3",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.5.2",
"globals": "^17.4.0",
"sass-embedded": "^1.97.3",
"vite": "^7.3.1"
},
"resolutions": {
"dompurify": "^3.3.2"
}
}
+556 -663
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -10,6 +10,6 @@
"tauri": "tauri"
},
"devDependencies": {
"@tauri-apps/cli": "^2"
"@tauri-apps/cli": "^2.10.1"
}
}
+59 -59
View File
@@ -2,74 +2,74 @@
# yarn lockfile v1
"@tauri-apps/cli-darwin-arm64@2.9.6":
version "2.9.6"
resolved "https://registry.yarnpkg.com/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.9.6.tgz#bb1576a6567db0d331e34d2322dc6aebde6681e8"
integrity sha512-gf5no6N9FCk1qMrti4lfwP77JHP5haASZgVbBgpZG7BUepB3fhiLCXGUK8LvuOjP36HivXewjg72LTnPDScnQQ==
"@tauri-apps/cli-darwin-arm64@2.10.1":
version "2.10.1"
resolved "https://registry.yarnpkg.com/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.10.1.tgz#7abb013926613555559cce1583ab6521e07b997c"
integrity sha512-Z2OjCXiZ+fbYZy7PmP3WRnOpM9+Fy+oonKDEmUE6MwN4IGaYqgceTjwHucc/kEEYZos5GICve35f7ZiizgqEnQ==
"@tauri-apps/cli-darwin-x64@2.9.6":
version "2.9.6"
resolved "https://registry.yarnpkg.com/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.9.6.tgz#7beb6ba8218002d7e160764326ce03407e76305d"
integrity sha512-oWh74WmqbERwwrwcueJyY6HYhgCksUc6NT7WKeXyrlY/FPmNgdyQAgcLuTSkhRFuQ6zh4Np1HZpOqCTpeZBDcw==
"@tauri-apps/cli-darwin-x64@2.10.1":
version "2.10.1"
resolved "https://registry.yarnpkg.com/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.10.1.tgz#cd1abd24782d488d0ee26f15015016b83a5b4bde"
integrity sha512-V/irQVvjPMGOTQqNj55PnQPVuH4VJP8vZCN7ajnj+ZS8Kom1tEM2hR3qbbIRoS3dBKs5mbG8yg1WC+97dq17Pw==
"@tauri-apps/cli-linux-arm-gnueabihf@2.9.6":
version "2.9.6"
resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.9.6.tgz#523ddcd86a99bdda5156bd9de96dfd3e4fa75b7f"
integrity sha512-/zde3bFroFsNXOHN204DC2qUxAcAanUjVXXSdEGmhwMUZeAQalNj5cz2Qli2elsRjKN/hVbZOJj0gQ5zaYUjSg==
"@tauri-apps/cli-linux-arm-gnueabihf@2.10.1":
version "2.10.1"
resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.10.1.tgz#9afdc71d24d44941d76fdc353f4401e82e9ccac5"
integrity sha512-Hyzwsb4VnCWKGfTw+wSt15Z2pLw2f0JdFBfq2vHBOBhvg7oi6uhKiF87hmbXOBXUZaGkyRDkCHsdzJcIfoJC2w==
"@tauri-apps/cli-linux-arm64-gnu@2.9.6":
version "2.9.6"
resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.9.6.tgz#60b4cbd4b8b97c5f5be6cc70fa75455b5a6d6292"
integrity sha512-pvbljdhp9VOo4RnID5ywSxgBs7qiylTPlK56cTk7InR3kYSTJKYMqv/4Q/4rGo/mG8cVppesKIeBMH42fw6wjg==
"@tauri-apps/cli-linux-arm64-gnu@2.10.1":
version "2.10.1"
resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.10.1.tgz#3a0a1e31eda8f01b69db09179094e0dc7ff5bb60"
integrity sha512-OyOYs2t5GkBIvyWjA1+h4CZxTcdz1OZPCWAPz5DYEfB0cnWHERTnQ/SLayQzncrT0kwRoSfSz9KxenkyJoTelA==
"@tauri-apps/cli-linux-arm64-musl@2.9.6":
version "2.9.6"
resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.9.6.tgz#ce5e5396db6c7f22b80154757479b1486163364a"
integrity sha512-02TKUndpodXBCR0oP//6dZWGYcc22Upf2eP27NvC6z0DIqvkBBFziQUcvi2n6SrwTRL0yGgQjkm9K5NIn8s6jw==
"@tauri-apps/cli-linux-arm64-musl@2.10.1":
version "2.10.1"
resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.10.1.tgz#6fc7e74e4a6053dd64b81dfdaf9dbabb6df31971"
integrity sha512-MIj78PDDGjkg3NqGptDOGgfXks7SYJwhiMh8SBoZS+vfdz7yP5jN18bNaLnDhsVIPARcAhE1TlsZe/8Yxo2zqg==
"@tauri-apps/cli-linux-riscv64-gnu@2.9.6":
version "2.9.6"
resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-riscv64-gnu/-/cli-linux-riscv64-gnu-2.9.6.tgz#aa8ec23d62cbb85a75c3e172637e78f485dbfcf8"
integrity sha512-fmp1hnulbqzl1GkXl4aTX9fV+ubHw2LqlLH1PE3BxZ11EQk+l/TmiEongjnxF0ie4kV8DQfDNJ1KGiIdWe1GvQ==
"@tauri-apps/cli-linux-riscv64-gnu@2.10.1":
version "2.10.1"
resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-riscv64-gnu/-/cli-linux-riscv64-gnu-2.10.1.tgz#9a5d3bb8efb17ef65146d0f319c6cf212e2a724a"
integrity sha512-X0lvOVUg8PCVaoEtEAnpxmnkwlE1gcMDTqfhbefICKDnOTJ5Est3qL0SrWxizDackIOKBcvtpejrSiVpuJI1kw==
"@tauri-apps/cli-linux-x64-gnu@2.9.6":
version "2.9.6"
resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.9.6.tgz#036c0463c5eee2298ed6ca8cb2838738816c7290"
integrity sha512-vY0le8ad2KaV1PJr+jCd8fUF9VOjwwQP/uBuTJvhvKTloEwxYA/kAjKK9OpIslGA9m/zcnSo74czI6bBrm2sYA==
"@tauri-apps/cli-linux-x64-gnu@2.10.1":
version "2.10.1"
resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.10.1.tgz#cf54e3754d2a54894323840aef1f9789888e0d57"
integrity sha512-2/12bEzsJS9fAKybxgicCDFxYD1WEI9kO+tlDwX5znWG2GwMBaiWcmhGlZ8fi+DMe9CXlcVarMTYc0L3REIRxw==
"@tauri-apps/cli-linux-x64-musl@2.9.6":
version "2.9.6"
resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.9.6.tgz#62b05db3d28b0f12c150836a387bd572de44f5be"
integrity sha512-TOEuB8YCFZTWVDzsO2yW0+zGcoMiPPwcUgdnW1ODnmgfwccpnihDRoks+ABT1e3fHb1ol8QQWsHSCovb3o2ENQ==
"@tauri-apps/cli-linux-x64-musl@2.10.1":
version "2.10.1"
resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.10.1.tgz#dc4bd17e812b448b3ccec0684f336f19fb47b069"
integrity sha512-Y8J0ZzswPz50UcGOFuXGEMrxbjwKSPgXftx5qnkuMs2rmwQB5ssvLb6tn54wDSYxe7S6vlLob9vt0VKuNOaCIQ==
"@tauri-apps/cli-win32-arm64-msvc@2.9.6":
version "2.9.6"
resolved "https://registry.yarnpkg.com/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.9.6.tgz#01f69ba09a6581e70bdfa206c5801b64b329d28d"
integrity sha512-ujmDGMRc4qRLAnj8nNG26Rlz9klJ0I0jmZs2BPpmNNf0gM/rcVHhqbEkAaHPTBVIrtUdf7bGvQAD2pyIiUrBHQ==
"@tauri-apps/cli-win32-arm64-msvc@2.10.1":
version "2.10.1"
resolved "https://registry.yarnpkg.com/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.10.1.tgz#2f398fb17f4665fefe5b8cb21566cde7062236f5"
integrity sha512-iSt5B86jHYAPJa/IlYw++SXtFPGnWtFJriHn7X0NFBVunF6zu9+/zOn8OgqIWSl8RgzhLGXQEEtGBdR4wzpVgg==
"@tauri-apps/cli-win32-ia32-msvc@2.9.6":
version "2.9.6"
resolved "https://registry.yarnpkg.com/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.9.6.tgz#b36c60db5119d74f126d4c4d8288d1c6ae4b45f0"
integrity sha512-S4pT0yAJgFX8QRCyKA1iKjZ9Q/oPjCZf66A/VlG5Yw54Nnr88J1uBpmenINbXxzyhduWrIXBaUbEY1K80ZbpMg==
"@tauri-apps/cli-win32-ia32-msvc@2.10.1":
version "2.10.1"
resolved "https://registry.yarnpkg.com/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.10.1.tgz#ffcbc2fa6a1d71f0b38672ea3468c07581f5cfaf"
integrity sha512-gXyxgEzsFegmnWywYU5pEBURkcFN/Oo45EAwvZrHMh+zUSEAvO5E8TXsgPADYm31d1u7OQU3O3HsYfVBf2moHw==
"@tauri-apps/cli-win32-x64-msvc@2.9.6":
version "2.9.6"
resolved "https://registry.yarnpkg.com/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.9.6.tgz#d58c9f8af835b7e4fc30e201e979342c70bea426"
integrity sha512-ldWuWSSkWbKOPjQMJoYVj9wLHcOniv7diyI5UAJ4XsBdtaFB0pKHQsqw/ItUma0VXGC7vB4E9fZjivmxur60aw==
"@tauri-apps/cli-win32-x64-msvc@2.10.1":
version "2.10.1"
resolved "https://registry.yarnpkg.com/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.10.1.tgz#75a3842ecab5e13b15ad2eead4c1b9cc3916e78d"
integrity sha512-6Cn7YpPFwzChy0ERz6djKEmUehWrYlM+xTaNzGPgZocw3BD7OfwfWHKVWxXzdjEW2KfKkHddfdxK1XXTYqBRLg==
"@tauri-apps/cli@^2":
version "2.9.6"
resolved "https://registry.yarnpkg.com/@tauri-apps/cli/-/cli-2.9.6.tgz#f15ae8e03bf48308055c15ab25b439bed9906bc9"
integrity sha512-3xDdXL5omQ3sPfBfdC8fCtDKcnyV7OqyzQgfyT5P3+zY6lcPqIYKQBvUasNvppi21RSdfhy44ttvJmftb0PCDw==
"@tauri-apps/cli@^2.10.1":
version "2.10.1"
resolved "https://registry.yarnpkg.com/@tauri-apps/cli/-/cli-2.10.1.tgz#d9b0290f03d569e10eb349a0bb72f448880bf9d7"
integrity sha512-jQNGF/5quwORdZSSLtTluyKQ+o6SMa/AUICfhf4egCGFdMHqWssApVgYSbg+jmrZoc8e1DscNvjTnXtlHLS11g==
optionalDependencies:
"@tauri-apps/cli-darwin-arm64" "2.9.6"
"@tauri-apps/cli-darwin-x64" "2.9.6"
"@tauri-apps/cli-linux-arm-gnueabihf" "2.9.6"
"@tauri-apps/cli-linux-arm64-gnu" "2.9.6"
"@tauri-apps/cli-linux-arm64-musl" "2.9.6"
"@tauri-apps/cli-linux-riscv64-gnu" "2.9.6"
"@tauri-apps/cli-linux-x64-gnu" "2.9.6"
"@tauri-apps/cli-linux-x64-musl" "2.9.6"
"@tauri-apps/cli-win32-arm64-msvc" "2.9.6"
"@tauri-apps/cli-win32-ia32-msvc" "2.9.6"
"@tauri-apps/cli-win32-x64-msvc" "2.9.6"
"@tauri-apps/cli-darwin-arm64" "2.10.1"
"@tauri-apps/cli-darwin-x64" "2.10.1"
"@tauri-apps/cli-linux-arm-gnueabihf" "2.10.1"
"@tauri-apps/cli-linux-arm64-gnu" "2.10.1"
"@tauri-apps/cli-linux-arm64-musl" "2.10.1"
"@tauri-apps/cli-linux-riscv64-gnu" "2.10.1"
"@tauri-apps/cli-linux-x64-gnu" "2.10.1"
"@tauri-apps/cli-linux-x64-musl" "2.10.1"
"@tauri-apps/cli-win32-arm64-msvc" "2.10.1"
"@tauri-apps/cli-win32-ia32-msvc" "2.10.1"
"@tauri-apps/cli-win32-x64-msvc" "2.10.1"
+13
View File
@@ -93,3 +93,16 @@ services:
+ infram-net:
+ enable_ipv6: true
```
## Security Maintenance (Container-Only)
For dependency updates and vulnerability scans, you can run the built-in container pipeline:
```sh
make security-update
make security-audit
make security-all
make security-sbom
```
This only requires Docker or Podman on the host. The required Node/Yarn/pnpm tooling is executed inside ephemeral containers.
+12 -12
View File
@@ -10,23 +10,23 @@
"preview": "vite preview"
},
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^7.1.0",
"@fortawesome/free-brands-svg-icons": "^7.1.0",
"@fortawesome/free-solid-svg-icons": "^7.1.0",
"@fortawesome/react-fontawesome": "^3.1.1",
"react": "^19.2.3",
"react-dom": "^19.2.3",
"react-router-dom": "^7.12.0",
"sass": "^1.97.2"
"@fortawesome/fontawesome-svg-core": "^7.2.0",
"@fortawesome/free-brands-svg-icons": "^7.2.0",
"@fortawesome/free-solid-svg-icons": "^7.2.0",
"@fortawesome/react-fontawesome": "^3.2.0",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"react-router-dom": "^7.13.1",
"sass": "^1.97.3"
},
"devDependencies": {
"@types/react": "^19.2.8",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^5.1.2",
"eslint": "^9.39.2",
"@vitejs/plugin-react": "^5.1.4",
"eslint": "^10.0.3",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.4.26",
"eslint-plugin-react-refresh": "^0.5.2",
"vite": "^7.3.1"
}
}
+592 -659
View File
File diff suppressed because it is too large Load Diff
+25 -7
View File
@@ -6,20 +6,22 @@
"author": "Swissmakers GmbH",
"license": "MIT",
"dependencies": {
"@simplewebauthn/server": "^13.2.2",
"@simplewebauthn/server": "^13.2.3",
"archiver": "^7.0.1",
"axios": "^1.13.5",
"axios": "^1.13.6",
"bcrypt": "^6.0.0",
"decompress": "^4.2.1",
"esbuild": "^0.27.3",
"express": "^5.2.1",
"express-jsdoc-swagger": "^1.8.0",
"express-rate-limit": "^8.2.1",
"express-rate-limit": "^8.3.0",
"express-ws": "^5.0.2",
"glob": "^13.0.6",
"joi": "^18.0.2",
"js-yaml": "^4.1.1",
"ldapts": "^8.1.7",
"openid-client": "^6.8.2",
"sequelize": "^6.37.7",
"sequelize": "^6.37.8",
"sharp": "^0.34.5",
"speakeasy": "^2.0.0",
"sqlite3": "^5.1.7",
@@ -34,7 +36,7 @@
"dotenv": "^17.3.1",
"nodemon": "^3.1.14",
"vitepress": "^1.6.4",
"vitepress-openapi": "^0.1.15"
"vitepress-openapi": "^0.1.17"
},
"scripts": {
"dev:server": "nodemon --ignore docs --ignore vendor --ignore client --ignore mobile --ignore data server/index.js",
@@ -44,6 +46,22 @@
"docs:openapi": "node scripts/generate-openapi.js",
"docs:dev": "yarn docs:openapi && vitepress dev docs",
"docs:build": "yarn docs:openapi && vitepress build docs",
"docs:preview": "vitepress preview docs"
}
"docs:preview": "vitepress preview docs",
"security:update": "./scripts/ci-security.sh update",
"security:audit": "./scripts/ci-security.sh audit",
"security:all": "./scripts/ci-security.sh all",
"security:sbom": "SECURITY_GENERATE_SBOM=1 ./scripts/ci-security.sh audit"
},
"resolutions": {
"tar": "^7.5.10",
"@tootallnate/once": "^3.0.1",
"esbuild": "^0.25.0",
"glob": "^11.1.0",
"minimatch": "^10.2.3",
"qs": "^6.14.2",
"lodash": "^4.17.23",
"dottie": "^2.0.7",
"fast-xml-parser": "^5.3.8"
},
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
}
+302
View File
@@ -0,0 +1,302 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
MODE="${1:-all}"
FAIL_ON="${SECURITY_FAIL_ON:-high}"
NODE_IMAGE="${SECURITY_NODE_IMAGE:-docker.io/library/node:22-bookworm-slim}"
SYFT_IMAGE="${SECURITY_SYFT_IMAGE:-docker.io/anchore/syft:latest}"
ARTIFACT_DIR="${SECURITY_ARTIFACT_DIR:-${PROJECT_ROOT}/artifacts/security}"
GENERATE_SBOM="${SECURITY_GENERATE_SBOM:-0}"
DRY_RUN="${SECURITY_DRY_RUN:-0}"
RUNTIME=""
VOLUME_SUFFIX=""
log() {
echo "[security] $*"
}
fail() {
echo "[security] ERROR: $*" >&2
exit 1
}
usage() {
cat <<'EOF'
Usage: ./scripts/ci-security.sh [update|audit|all]
Environment variables:
SECURITY_FAIL_ON Severity threshold for failing audit (default: high)
Allowed: none|critical|high|moderate|low|info
SECURITY_NODE_IMAGE Node container image (default: node:22-bookworm-slim)
SECURITY_SYFT_IMAGE Syft container image (default: anchore/syft:latest)
SECURITY_ARTIFACT_DIR Output folder for reports (default: artifacts/security)
SECURITY_GENERATE_SBOM Set to 1 to generate SBOM files
SECURITY_DRY_RUN Set to 1 to print commands without executing
EOF
}
detect_runtime() {
if command -v podman >/dev/null 2>&1; then
RUNTIME="podman"
VOLUME_SUFFIX=":Z"
return 0
fi
if command -v docker >/dev/null 2>&1; then
RUNTIME="docker"
VOLUME_SUFFIX=""
return 0
fi
fail "Neither podman nor docker is available on PATH."
}
run_node_container() {
local workdir="$1"
local cmd="$2"
local runtime_args=(run --rm -v "${PROJECT_ROOT}:/workspace${VOLUME_SUFFIX}" -w "${workdir}")
if [[ "${RUNTIME}" == "podman" ]]; then
runtime_args+=(--userns=keep-id)
fi
if [[ "${DRY_RUN}" == "1" ]]; then
log "DRY RUN: ${RUNTIME} ${runtime_args[*]} ${NODE_IMAGE} sh -lc \"${cmd}\""
return 0
fi
"${RUNTIME}" "${runtime_args[@]}" "${NODE_IMAGE}" sh -lc "${cmd}"
}
run_syft_container() {
local output_path="$1"
local target_path="$2"
local runtime_args=(run --rm -v "${PROJECT_ROOT}:/workspace${VOLUME_SUFFIX}" -w /workspace)
if [[ "${RUNTIME}" == "podman" ]]; then
runtime_args+=(--userns=keep-id)
fi
if [[ "${DRY_RUN}" == "1" ]]; then
log "DRY RUN: ${RUNTIME} ${runtime_args[*]} ${SYFT_IMAGE} ${target_path} -o spdx-json=${output_path}"
return 0
fi
"${RUNTIME}" "${runtime_args[@]}" "${SYFT_IMAGE}" "${target_path}" -o "spdx-json=${output_path}"
}
prepare_artifacts() {
mkdir -p "${ARTIFACT_DIR}"
}
to_container_path() {
local host_path="$1"
if [[ "${host_path}" == "${PROJECT_ROOT}"* ]]; then
printf "/workspace%s" "${host_path#${PROJECT_ROOT}}"
return 0
fi
fail "SECURITY_ARTIFACT_DIR must be inside ${PROJECT_ROOT} when using containerized commands."
}
extract_yarn_counts() {
local jsonl_file="$1"
python3 -c '
import json
import sys
file = sys.argv[1]
summary = None
with open(file, "r", encoding="utf-8") as handle:
for line in handle:
line = line.strip()
if not line:
continue
try:
obj = json.loads(line)
except Exception:
continue
if obj.get("type") == "auditSummary":
summary = (obj.get("data") or {}).get("vulnerabilities")
if not summary:
print(f"Missing yarn audit summary in {file}", file=sys.stderr)
sys.exit(2)
values = [
int(summary.get("info", 0)),
int(summary.get("low", 0)),
int(summary.get("moderate", 0)),
int(summary.get("high", 0)),
int(summary.get("critical", 0)),
]
print(" ".join(str(v) for v in values), end="")
' "${jsonl_file}"
}
extract_pnpm_counts() {
local json_file="$1"
python3 -c '
import json
import sys
file = sys.argv[1]
with open(file, "r", encoding="utf-8") as handle:
obj = json.load(handle)
vulns = ((obj.get("metadata") or {}).get("vulnerabilities"))
if not vulns:
print(f"Missing pnpm audit summary in {file}", file=sys.stderr)
sys.exit(2)
values = [
int(vulns.get("info", 0)),
int(vulns.get("low", 0)),
int(vulns.get("moderate", 0)),
int(vulns.get("high", 0)),
int(vulns.get("critical", 0)),
]
print(" ".join(str(v) for v in values), end="")
' "${json_file}"
}
should_fail_by_threshold() {
local info="$1"
local low="$2"
local moderate="$3"
local high="$4"
local critical="$5"
case "${FAIL_ON}" in
none) return 1 ;;
critical) (( critical > 0 )) ;;
high) (( high > 0 || critical > 0 )) ;;
moderate) (( moderate > 0 || high > 0 || critical > 0 )) ;;
low) (( low > 0 || moderate > 0 || high > 0 || critical > 0 )) ;;
info) (( info > 0 || low > 0 || moderate > 0 || high > 0 || critical > 0 )) ;;
*) fail "Invalid SECURITY_FAIL_ON='${FAIL_ON}'. Use none|critical|high|moderate|low|info." ;;
esac
}
run_update() {
log "Running dependency update pipeline in containers..."
run_node_container "/workspace" \
"corepack enable && corepack prepare yarn@1.22.22 --activate && yarn upgrade --latest && yarn install"
run_node_container "/workspace/client" \
"corepack enable && corepack prepare yarn@1.22.22 --activate && yarn upgrade --latest && yarn install"
run_node_container "/workspace/landing" \
"corepack enable && COREPACK_ENABLE_PROJECT_SPEC=0 corepack pnpm up --latest --store-dir /workspace/.pnpm-store"
run_node_container "/workspace/connector" \
"corepack enable && corepack prepare yarn@1.22.22 --activate && yarn upgrade --latest && yarn install"
}
run_audit() {
log "Running vulnerability audits (threshold: ${FAIL_ON})..."
prepare_artifacts
local root_jsonl="${ARTIFACT_DIR}/root-yarn-audit.jsonl"
local client_jsonl="${ARTIFACT_DIR}/client-yarn-audit.jsonl"
local connector_jsonl="${ARTIFACT_DIR}/connector-yarn-audit.jsonl"
local landing_json="${ARTIFACT_DIR}/landing-pnpm-audit.json"
local container_artifact_dir
container_artifact_dir="$(to_container_path "${ARTIFACT_DIR}")"
run_node_container "/workspace" \
"corepack enable && corepack prepare yarn@1.22.22 --activate && yarn install --frozen-lockfile && (yarn audit --level low --json || true) > '${container_artifact_dir}/root-yarn-audit.jsonl'"
run_node_container "/workspace/client" \
"corepack enable && corepack prepare yarn@1.22.22 --activate && yarn install --frozen-lockfile && (yarn audit --level low --json || true) > '${container_artifact_dir}/client-yarn-audit.jsonl'"
run_node_container "/workspace/connector" \
"corepack enable && corepack prepare yarn@1.22.22 --activate && yarn install --frozen-lockfile && (yarn audit --level low --json || true) > '${container_artifact_dir}/connector-yarn-audit.jsonl'"
run_node_container "/workspace/landing" \
"corepack enable && COREPACK_ENABLE_PROJECT_SPEC=0 corepack pnpm install --frozen-lockfile --store-dir /workspace/.pnpm-store && (COREPACK_ENABLE_PROJECT_SPEC=0 corepack pnpm audit --json || true) > '${container_artifact_dir}/landing-pnpm-audit.json'"
if [[ "${DRY_RUN}" == "1" ]]; then
log "Dry-run enabled: skipping audit report parsing and threshold enforcement."
return 0
fi
local root_counts client_counts connector_counts landing_counts
root_counts="$(extract_yarn_counts "${root_jsonl}")"
client_counts="$(extract_yarn_counts "${client_jsonl}")"
connector_counts="$(extract_yarn_counts "${connector_jsonl}")"
landing_counts="$(extract_pnpm_counts "${landing_json}")"
local info low moderate high critical
read -r info low moderate high critical <<<"${root_counts}"
log "root: info=${info} low=${low} moderate=${moderate} high=${high} critical=${critical}"
if should_fail_by_threshold "${info}" "${low}" "${moderate}" "${high}" "${critical}"; then
fail "root audit exceeded threshold '${FAIL_ON}'"
fi
read -r info low moderate high critical <<<"${client_counts}"
log "client: info=${info} low=${low} moderate=${moderate} high=${high} critical=${critical}"
if should_fail_by_threshold "${info}" "${low}" "${moderate}" "${high}" "${critical}"; then
fail "client audit exceeded threshold '${FAIL_ON}'"
fi
read -r info low moderate high critical <<<"${connector_counts}"
log "connector: info=${info} low=${low} moderate=${moderate} high=${high} critical=${critical}"
if should_fail_by_threshold "${info}" "${low}" "${moderate}" "${high}" "${critical}"; then
fail "connector audit exceeded threshold '${FAIL_ON}'"
fi
read -r info low moderate high critical <<<"${landing_counts}"
log "landing: info=${info} low=${low} moderate=${moderate} high=${high} critical=${critical}"
if should_fail_by_threshold "${info}" "${low}" "${moderate}" "${high}" "${critical}"; then
fail "landing audit exceeded threshold '${FAIL_ON}'"
fi
}
generate_sbom() {
if [[ "${GENERATE_SBOM}" != "1" ]]; then
return 0
fi
log "Generating SBOM artifacts..."
prepare_artifacts
run_syft_container "/workspace/artifacts/security/sbom-root.spdx.json" "dir:/workspace"
run_syft_container "/workspace/artifacts/security/sbom-client.spdx.json" "dir:/workspace/client"
run_syft_container "/workspace/artifacts/security/sbom-landing.spdx.json" "dir:/workspace/landing"
run_syft_container "/workspace/artifacts/security/sbom-connector.spdx.json" "dir:/workspace/connector"
}
main() {
detect_runtime
log "Using runtime: ${RUNTIME}"
case "${MODE}" in
update)
run_update
generate_sbom
;;
audit)
run_audit
generate_sbom
;;
all)
run_update
run_audit
generate_sbom
;;
-h|--help|help)
usage
;;
*)
usage
fail "Unknown mode '${MODE}'."
;;
esac
log "Completed successfully."
}
main "$@"
+1014 -902
View File
File diff suppressed because it is too large Load Diff