Skip to content

chore(deps): bump @radix-ui/react-slot from 1.2.4 to 1.2.5 in /web #462

chore(deps): bump @radix-ui/react-slot from 1.2.4 to 1.2.5 in /web

chore(deps): bump @radix-ui/react-slot from 1.2.4 to 1.2.5 in /web #462

Workflow file for this run

name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
permissions:
contents: read
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
jobs:
# detect-changes emits per-area output flags so downstream jobs can
# decide whether they actually need to run. Only Go-touching PRs
# need the slow race+coverage gate; npm/docs PRs (incl. dependabot
# patch bumps) skip it entirely.
detect-changes:
name: Detect changes
runs-on: ubuntu-latest
outputs:
go: ${{ steps.filter.outputs.go }}
steps:
- uses: actions/checkout@v6
# Pinned to a commit SHA per the SonarCloud hotspot guidance —
# third-party actions can have their tags rewritten to point at
# a malicious commit, so consume the action by immutable hash.
# Tag at the time of pinning: v3 (commit d1c1ffe).
- uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1
id: filter
with:
filters: |
go:
- '**/*.go'
- 'go.mod'
- 'go.sum'
- '.github/workflows/ci.yml'
# PR fast gate: no -race, no coverage. Runs on every pull request so
# docs/CI/web-only PRs still get a Go-build sanity check (~45-60s).
# Skipped on push events because main pushes are post-merge — the
# PR-side run already proved the same tree, and go-test-race below
# re-validates with race+coverage.
go-test:
name: Go Tests
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
with:
go-version: "1.26"
# //go:embed cannot reference paths outside the package, so the
# canonical deploy/install-agent.sh is mirrored into the server
# package via `go generate`. Mirror Makefile's gen-install-script
# so every Go-touching CI step sees the embedded file.
- name: Mirror install-agent.sh for //go:embed
run: go generate ./internal/controlplane/server/...
- run: go test -count=1 ./...
# Loadtest correctness gate: the Test* variants of the realistic
# concurrency scenarios (agent burst, login storm, job fan-out)
# assert no errors / no audit drops / no missed lockouts under
# contention. Cheap (<30s) and runs on every PR. The full
# throughput sweep is separate (see load-bench job).
- name: Loadtest correctness assertions
run: go test -count=1 ./internal/loadtest/...
# Agent binaries ship for arm64 (RPi / ARM VPS fleets); until now no
# test ever executed on that architecture. Smoke-run the agent
# packages natively on GitHub's arm64 runners (audit F4).
go-test-arm64:
name: Go Tests (agent, arm64)
runs-on: ubuntu-24.04-arm
timeout-minutes: 10
steps:
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
with:
go-version: "1.26"
- name: Mirror install-agent.sh for //go:embed
run: go generate ./internal/controlplane/server/...
- name: Agent test suite (native arm64)
run: go test -count=1 ./internal/agent/... ./cmd/agent/...
# Slow gate: race detector + coverage. Runs on:
# - PRs that touch Go files / go.mod / go.sum (real merge gate
# under branch protection — required check evaluates here)
# - pushes to main (covers the merge commit and uploads coverage)
# Skipped on PRs that do not touch Go (dependabot npm bumps, doc
# tweaks) so the long pole moves off the dependabot critical path.
go-test-race:
name: Go Tests (race + coverage)
needs: detect-changes
if: |
(github.event_name == 'pull_request' && needs.detect-changes.outputs.go == 'true') ||
(github.event_name == 'push' && github.ref == 'refs/heads/main')
runs-on: ubuntu-latest
# Safety net: -race over the whole tree historically lands around
# 6–8 min; loadtest no-race adds ~30 s. A runaway leaks memory and
# the runner thrashes for an hour before GitHub's default 6 h kill —
# cap explicitly so a regression fails fast.
timeout-minutes: 20
steps:
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
with:
go-version: "1.26"
- name: Mirror install-agent.sh for //go:embed
run: go generate ./internal/controlplane/server/...
# The internal/loadtest scenarios spawn ~100 concurrent Argon2id
# verifies (~96 MiB each) and ~200-agent enrolls; under -race the
# working set climbs past 20 GB RSS and the runner spends 25+ min
# thrashing the page cache. Mirror the local pre-push hook split:
# everything else gets -race + coverage; loadtest gets a separate
# no-race pass below. The fast-PR gate (go-test) already covers
# loadtest correctness on every PR.
- name: Go race + coverage (loadtest excluded)
run: |
PKGS=$(go list ./... | grep -v '^github.com/lost-coder/panvex/internal/loadtest$')
go test -race -coverprofile=coverage.out -covermode=atomic $PKGS
- name: Go tests (loadtest, no -race)
run: go test -count=1 ./internal/loadtest/...
# Coverage floor: total backend coverage must stay above the
# baseline. Fail loud here instead of relying on Codecov's
# asynchronous diff-coverage (which is informational, not a gate).
# The threshold is intentionally below the current measured
# number so a small regression flags rather than every PR — bump
# in lockstep with new test waves.
# Raised 45 -> 48 (audit F4). Measured total at the time was ~50.7%
# (the audit's "~61%" estimate was stale); 48 leaves ~2.7pp headroom
# for fluctuation while catching real regressions. Bump toward 55 as
# coverage grows with new test waves.
- name: Enforce coverage floor (>=48%)
run: |
set -eu
total=$(go tool cover -func=coverage.out | awk '/^total:/ {print $3}' | tr -d '%')
echo "total coverage: ${total}%"
awk -v t="$total" 'BEGIN { if (t+0 < 48.0) exit 1 }'
- name: Upload coverage artifact
if: always()
uses: actions/upload-artifact@v7
with:
name: go-coverage
path: coverage.out
- name: Upload coverage to Codecov
if: always()
uses: codecov/codecov-action@v7
with:
files: coverage.out
flags: backend
fail_ci_if_error: false
# Canary build against the next Go version. Non-blocking — surfaces
# toolchain regressions before we lock the next version into the
# required matrix but does not gate merges if upstream has a
# transient bug.
go-test-next:
name: Go Tests (next version, canary)
needs: detect-changes
# Non-blocking canary (continue-on-error): surfaces toolchain
# regressions on the latest Go without gating merges. We track
# `stable` + check-latest rather than pinning a specific RC — an
# explicit RC tag (e.g. 1.27.0-rc.1) is not reliably resolvable by
# actions/setup-go and left the job permanently red. `stable` always
# resolves and automatically rolls onto the next Go minor the day it
# GAs, so this keeps giving signal without manual version bumps.
runs-on: ubuntu-latest
continue-on-error: true
steps:
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
with:
go-version: "stable"
check-latest: true
- name: Mirror install-agent.sh for //go:embed
run: go generate ./internal/controlplane/server/...
- run: go test -count=1 ./...
schema-sync:
# C9 + A6: verify the PostgreSQL and SQLite migration bundles stay in
# sync AND run the full storage tree (contract suites, EXPLAIN plan
# gates, prune tests) against a live Postgres. Without the DSN the
# postgres-bound tests skip silently — this job is the only place
# the postgres backend is actually executed.
name: Schema Sync (PG vs SQLite)
runs-on: ubuntu-latest
timeout-minutes: 15
services:
postgres:
image: postgres:16-alpine
env:
POSTGRES_USER: panvex
POSTGRES_PASSWORD: panvex
POSTGRES_DB: panvex_test
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
env:
PANVEX_POSTGRES_TEST_DSN: postgres://panvex:panvex@127.0.0.1:5432/panvex_test?sslmode=disable
PANVEX_ALLOW_INSECURE_DB: "1"
steps:
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
with:
go-version: "1.26"
- name: Run storage tree against Postgres (contract + schema-sync + EXPLAIN)
# -p 1: the migrate schema-sync test DROPs/recreates the public
# schema while the postgres contract tests TRUNCATE the same
# database — package-level parallelism would race on the shared
# DSN. Expected duration ~3-5 min.
run: go test -p 1 -count=1 ./internal/controlplane/storage/...
panel-build:
name: Build Binaries
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
with:
go-version: "1.26"
- name: Mirror install-agent.sh for //go:embed
run: go generate ./internal/controlplane/server/...
- run: go build -o /dev/null ./cmd/control-plane
- run: go build -o /dev/null ./cmd/agent
go-lint:
name: Go Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
with:
go-version: "1.26"
- name: Mirror install-agent.sh for //go:embed
run: go generate ./internal/controlplane/server/...
- uses: golangci/golangci-lint-action@v9
with:
# Pin to a known-good golangci-lint version. Bumping is a deliberate
# operator action so a silent upstream regression cannot break CI.
version: v2.11.4
go-vuln:
name: Go Vulnerability Scan
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
with:
go-version: "1.26"
- name: Install govulncheck
# Pinned: a govulncheck regression should not silently break CI.
run: go install golang.org/x/vuln/cmd/govulncheck@v1.1.4
- name: Run govulncheck
run: govulncheck ./...
# Drift guard for the OpenAPI codegen pipeline (Wave 3.3): ensure
# openapi/panvex.gen.go and web/src/shared/api/openapi.gen.ts match
# the canonical openapi/panvex.yaml. Mirrors the gen-settings drift
# guard pattern.
openapi-drift:
name: OpenAPI Codegen Drift
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
with:
go-version: "1.26"
- uses: actions/setup-node@v6
with:
node-version: "22"
cache: 'npm'
cache-dependency-path: web/package-lock.json
- name: Install oapi-codegen
run: go install github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen@v2.7.0
- name: Verify generated files in sync with spec
run: make verify-openapi
# Phase 4 inlined @lost-coder/panvex-ui into web/src/ui/, so the
# frontend jobs no longer need to clone + build a side-repo UI-kit
# or symlink ../ui. They just npm ci + run the target script.
frontend-build:
name: Frontend Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: "22"
cache: 'npm'
cache-dependency-path: web/package-lock.json
- name: Install and build frontend
working-directory: web
run: npm ci && npm run build
# Phase-3 §3.3: size-limit gates the bundle so a careless
# `npm install` of a heavy dep, or a forgotten manualChunks
# override, fails CI instead of silently bloating first-paint.
# Budgets live in web/package.json under "size-limit".
- name: Check bundle size budgets
working-directory: web
run: npm run size
frontend-test:
name: Frontend Tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: "22"
cache: 'npm'
cache-dependency-path: web/package-lock.json
# P2-TEST-01: vitest suite. npm run test = vitest run which exits
# non-zero on any failure; coverage is gated by thresholds in
# vitest.config.ts so a regression under 40% also fails CI.
- name: Run frontend tests
working-directory: web
run: npm ci && npm run test
- name: Run coverage with thresholds
working-directory: web
run: npm run test:coverage
- name: Run node:test state suites
working-directory: web
run: npm run test:state
frontend-lint:
name: Frontend Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: "22"
cache: 'npm'
cache-dependency-path: web/package-lock.json
- name: Lint frontend
working-directory: web
run: npm ci && npm run lint
- name: i18n parity + hardcoded-Cyrillic guard
working-directory: web
run: npm run check:i18n
frontend-e2e:
name: Frontend E2E (Playwright)
runs-on: ubuntu-latest
# The smoke suite stubs the API per-test via page.route(), so it
# boots Vite and a hermetic browser without touching a backend.
# Running on every PR keeps regressions in the navigation /
# auth-shell flows from sneaking past type checks.
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: "22"
cache: 'npm'
cache-dependency-path: web/package-lock.json
- name: Install npm deps
working-directory: web
run: npm ci
- name: Install Playwright browsers
working-directory: web
run: npx playwright install --with-deps chromium
- name: Run Playwright smoke suite
working-directory: web
run: npx playwright test --project=chromium
- name: Upload Playwright report
if: failure()
uses: actions/upload-artifact@v7
with:
name: playwright-report
path: web/playwright-report/
retention-days: 7
frontend-e2e-integration:
name: Frontend E2E (integration, real backend)
runs-on: ubuntu-latest
timeout-minutes: 20
# Unlike frontend-e2e (hermetic, page.route() stubs), this job runs
# the suite in web/tests/e2e/integration/ against a REAL
# control-plane binary with the embedded UI and a SQLite store —
# the auth middleware, session store, and embedded-asset serving
# are exercised end-to-end (audit F2).
env:
PANVEX_ENCRYPTION_KEY: ci-e2e-integration-throwaway-key
steps:
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
with:
go-version: "1.26"
- uses: actions/setup-node@v6
with:
node-version: "22"
cache: 'npm'
cache-dependency-path: web/package-lock.json
- name: Mirror install-agent.sh for //go:embed
run: go generate ./internal/controlplane/server/...
- name: Install npm deps
working-directory: web
run: npm ci
- name: Build embedded UI bundle
working-directory: web
run: npm run build:embed
- name: Build control-plane (embedded UI)
run: |
mkdir -p .tmp/e2e
go build -tags embeddedui -o .tmp/e2e/panvex-control-plane ./cmd/control-plane
- name: Bootstrap admin account (admin/e2e-secret per login.int.spec.ts)
run: |
PANVEX_BOOTSTRAP_ALLOW_INSECURE_FLAG=1 ./.tmp/e2e/panvex-control-plane bootstrap-admin \
-username admin \
-password e2e-secret \
-storage-driver sqlite \
-storage-dsn .tmp/e2e/panvex.db
- name: Start control-plane on :18080
run: |
PANVEX_HTTP_ADDR=:18080 \
PANVEX_GRPC_ADDR=:18443 \
PANVEX_STORAGE_DRIVER=sqlite \
PANVEX_STORAGE_DSN=.tmp/e2e/panvex.db \
./.tmp/e2e/panvex-control-plane > .tmp/e2e/panel.log 2>&1 &
for i in $(seq 1 60); do
if curl -fsS http://127.0.0.1:18080/readyz >/dev/null 2>&1; then
echo "control-plane ready after ${i}s"
exit 0
fi
sleep 1
done
echo "::error::control-plane never became ready on :18080"
cat .tmp/e2e/panel.log
exit 1
- name: Install Playwright browsers
working-directory: web
run: npx playwright install --with-deps chromium
- name: Run integration suite
working-directory: web
run: npx playwright test -c playwright.integration.config.ts
- name: Show panel log on failure
if: failure()
run: tail -n 200 .tmp/e2e/panel.log
- name: Upload traces and report
if: failure()
uses: actions/upload-artifact@v7
with:
name: playwright-integration-report
path: |
web/playwright-report/
web/test-results/
retention-days: 7
frontend-audit:
name: Frontend npm audit
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: "22"
cache: 'npm'
cache-dependency-path: web/package-lock.json
- name: Audit web dependencies
working-directory: web
# `--audit-level=moderate` matches the bar the security audit
# asks us to enforce. Anything moderate or worse fails CI; lower
# severities are still listed by `npm audit` but do not gate.
run: npm audit --audit-level=moderate
buf-lint:
name: Protobuf Lint
runs-on: ubuntu-latest
# bufbuild/buf-action posts a status comment on the PR by default.
# Without pull-requests: write the post fails with 'Resource not
# accessible by integration' even when the lint itself passed —
# which used to surface as a red CI on every PR. Disable the
# comment instead of broadening token scope; GitHub annotations
# already surface buf errors inline on the diff.
steps:
- uses: actions/checkout@v6
- uses: bufbuild/buf-action@v1
with:
lint: true
pr_comment: false
sqlc-check:
name: SQLc Verify
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: sqlc-dev/setup-sqlc@v5
with:
sqlc-version: '1.30.0'
- run: sqlc diff
load-bench:
name: Load harness (smoke)
runs-on: ubuntu-latest
# The login-storm bench fans out 100 concurrent Argon2id verifies at
# 96 MiB each (~10 GiB peak). On a runner that's also under memory
# pressure this thrashes the page cache for tens of minutes before
# the GitHub default 6 h kill. Cap explicitly — the documented
# wall-clock budget is <30 s, so anything past 10 min is a runaway.
timeout-minutes: 10
steps:
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
with:
go-version: "1.26"
# Phase-3 §3.6: smoke-run the perf benchmarks at -benchtime=1x
# so a regression that broke compilation, panicked under load,
# or blew up O(N²) shows up in CI without paying for a full
# statistical run. Operators wanting throughput numbers run
# `go test -bench . -benchtime=10s ./internal/loadtest/...`
# locally or in a nightly job.
- name: Run load harness benches (1x)
run: go test -run '^$' -bench . -benchtime=1x ./internal/loadtest/...
trivy-fs:
name: Trivy filesystem scan
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Trivy scan (fs)
# Catches vulnerable Go modules (go.sum), npm packages
# (web/package-lock.json), and known-bad config patterns. Severity
# gate matches the npm-audit bar so behaviour is consistent across
# ecosystems.
uses: aquasecurity/trivy-action@v0.36.0
with:
scan-type: fs
scan-ref: .
severity: HIGH,CRITICAL
ignore-unfixed: true
exit-code: '1'
format: table
trivy-dockerfile:
name: Trivy Dockerfile scan
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Trivy config scan (Dockerfile)
uses: aquasecurity/trivy-action@v0.36.0
with:
scan-type: config
scan-ref: Dockerfile
severity: HIGH,CRITICAL
exit-code: '1'
format: table
# Scans the *built* image — trivy-fs and trivy-dockerfile cover source
# deps and Dockerfile misconfig, but neither catches a vulnerable OS
# package baked into the final runtime layer (distroless/alpine CVEs,
# transitively pulled libs). Build the image locally (no push) and run
# Trivy in `image` mode with the same HIGH,CRITICAL + ignore-unfixed
# gate the other Trivy jobs use, so behaviour stays consistent.
trivy-image:
name: Trivy image scan
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Build image (load into local docker)
uses: docker/build-push-action@v7
with:
context: .
push: false
load: true
tags: panvex:ci
- name: Trivy scan (image)
uses: aquasecurity/trivy-action@v0.36.0
with:
scan-type: image
image-ref: panvex:ci
severity: HIGH,CRITICAL
ignore-unfixed: true
exit-code: '1'
format: table
# Plan 1 (S-21 security bootstrap) regression gate. Pure grep checks —
# fast (no compile, no deps) — that fail CI if a future refactor undoes
# any of the security fixes landed in S-1..S-9 / T-4 / T-5. Runs in
# parallel with the slower test/lint jobs so it surfaces a regression
# within seconds of the PR opening.
security-gate:
name: Security regression gate (Plan 1)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Plan 1 regression checks
run: |
set -eu
# S-2: SPKI pin must remain wired at the bootstrap dialer.
if ! grep -q 'newBootstrapTLSConfig\|VerifyPeerCertificate' cmd/control-plane/serve.go cmd/control-plane/bootstrap_tls.go; then
echo "S-2 regression: SPKI pin gone from bootstrap dialer" >&2
exit 1
fi
# S-1: bootstrap-admin CLI must validate the password source.
if ! grep -q 'validatePasswordSource' cmd/control-plane/bootstrap_admin.go; then
echo "S-1 regression: validatePasswordSource gone from bootstrap-admin" >&2
exit 1
fi
# T-5: install-agent.sh must self-check its own hash before exec.
# Canonical script lives at deploy/install-agent.sh after ba6aadc;
# the server-package mirror is .gitignored and only materialised
# by `go generate`, so grep the source-of-truth path here.
if ! grep -q 'panvex_self_check_hash' deploy/install-agent.sh; then
echo "T-5 regression: install-agent.sh self-check gone" >&2
exit 1
fi
# T-4: install command issuance must bind the script hash.
if ! grep -q 'ScriptHash\b' internal/controlplane/bootstrap/install.go; then
echo "T-4 regression: ScriptHash binding gone from install command" >&2
exit 1
fi
# S-8: deploy/install.sh must not reintroduce unconditional `curl -k`.
# The CURL_INSECURE_FLAG path is the sanctioned (loopback-only) escape.
if grep -nE 'curl[[:space:]]+[^|]*-k\b' deploy/install.sh | grep -v 'CURL_INSECURE_FLAG'; then
echo "S-8 regression: unconditional curl -k reintroduced in deploy/install.sh" >&2
exit 1
fi
# S-7: agent listener must keep TLS 1.3 floor.
if ! grep -q 'tls.VersionTLS13' internal/agent/transport/listen.go; then
echo "S-7 regression: TLS 1.3 minimum gone from agent listener" >&2
exit 1
fi
# S-9: install-command TTL must stay minute-scale; reject hour-scale.
if grep -nE 'installCommandTTL[[:space:]]*=[[:space:]]*[0-9]+[[:space:]]*\*[[:space:]]*time\.Hour' internal/controlplane/bootstrap/install.go; then
echo "S-9 regression: installCommandTTL bumped to hour-scale" >&2
exit 1
fi
echo "All Plan 1 security gates pass."
shellcheck:
name: ShellCheck
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
# CodeQL covers Go + JS but not shell. install-agent.sh, deploy/
# install.sh and the dev fleet scripts each touch host state on
# operator machines, so a shell-injection bug there is high blast
# radius. ludeeus/action-shellcheck enumerates *.sh by default
# and respects shellcheckdirective comments inline.
- uses: ludeeus/action-shellcheck@2.0.0
env:
SHELLCHECK_OPTS: -e SC1091
with:
severity: warning
ignore_paths: >-
web/node_modules
node_modules
# Per-PR supply-chain check: refuses any new dependency with a known
# vuln (>= moderate) or a license outside the allow-list. CodeQL +
# govulncheck + npm audit + Trivy already gate post-merge / on-tip,
# but they don't surface a new transitive coming in via package-lock
# diff during review. dependency-review-action does exactly that.
dependency-review:
name: Dependency Review
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v6
- uses: actions/dependency-review-action@v5
with:
fail-on-severity: moderate
comment-summary-in-pr: on-failure
gitleaks:
name: gitleaks (secret scan)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
# Full history so gitleaks scans every commit on the branch,
# not just the tip — leaks often slip in mid-branch and get
# "fixed" by a follow-up commit.
fetch-depth: 0
- uses: gitleaks/gitleaks-action@v3
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITLEAKS_LICENSE: ${{ secrets.GITLEAKS_LICENSE }}
# Tokenless reporting: action still fails the job on findings
# but does not post back to the gitleaks dashboard.
GITLEAKS_NOTIFY_USER_LIST: ''