Sets up a full security stack on your company's projects hosted on GitLab.com (non-PrestaShop: Laravel, Astro, TanStack, etc.). Use ONLY when the project is a GitLab.com Free tier project. Triggers when the user asks to add dependency scanning, vulnerability alerts, security setup, Trivy, pnpm supply chain protection, or wants email reports of vulnerabilities. Do NOT use for GitHub-hosted projects, personal projects, or PrestaShop projects — use ps-security-audit skill instead for any PrestaShop project.
Sets up a full security stack on your company's projects hosted on GitLab.com (non-PrestaShop: Laravel, Astro, TanStack, etc.). Use ONLY when the project is a GitLab.com Free tier project. Triggers when the user asks to add dependency scanning, vulnerability alerts, security setup, Trivy, pnpm supply chain protection, or wants email reports of vulnerabilities. Do NOT use for GitHub-hosted projects, personal projects, or PrestaShop projects — use ps-security-audit skill instead for any PrestaShop project.
version
0.2.1
metadata
{"author":"Eduardo Calvo"}
GitLab Security Setup
Full security stack for your company's GitLab.com projects on the Free tier.
Covers: pnpm 11 supply chain, Trivy weekly scan, HTML email reports via Gmail.
Placeholder:{report_recipients} is a comma-separated list of email
addresses that receive the vulnerability reports (e.g. you@example.com, teammate@example.com).
Replace it everywhere it appears below with your own recipient address(es) before running.
What gets set up
pnpm 11 with supply chain protection (minimumReleaseAge, overrides)
Trivy vulnerability + secret scanner via GitLab CI
Weekly scheduled pipeline (Monday 8am Madrid) with HTML email report
Composer audit for PHP/Laravel projects
Gmail SMTP delivery via GitLab CI/CD variables
Step 1 — pnpm 11 Supply Chain
pnpm-workspace.yaml (create or update)
# WARNING: single-package repos do NOT need a `packages:` block on pnpm 11.# BUT on pnpm 9 (Vercel default for older projects) the mere presence of this# file REQUIRES a non-empty `packages:` or install dies with# "packages field missing or empty". If targeting pnpm 9, add `packages: ['.']`.minimumReleaseAge:4320# packages must be 72h old before install (minutes)# Block transitive deps from git repos / raw tarball URLs (needs pnpm 10.26+,# silently inert below). See supply-chain-security skill for the full checklist.blockExoticSubdeps:true# Allowlist for postinstall/build scripts. pnpm 10+ blocks ALL by default.# List ONLY packages that genuinely need to compile. pnpm will prompt you to# add new entries when you install a dep with a blocked build script.allowBuilds:esbuild:truesharp:true# lightningcss-cli: true # uncomment if using lightningcssoverrides:form-data:">=4.0.6"axios:">=1.15.2"lodash:">=4.18.0"picomatch:">=4.0.4"qs:">=6.14.2"shell-quote:">=1.8.4"
Rules:
minimumReleaseAge is in minutes (4320 = 72h). Blocks supply chain attacks via typosquatting/fast-publish.
allowBuilds — pnpm 10+ blocks all postinstall scripts by default. Add ONLY packages that need to compile. pnpm 11 will tell you during install if a new dep needs adding here.
overrides pins known vulnerable transitive deps. Add new entries as CVEs appear.
Do NOT put minimumReleaseAge in .npmrc — pnpm 11 reads it from pnpm-workspace.yaml only.
Real test is CI: pnpm install --frozen-lockfile on a clean machine enforces all policies; a warm local cache skips them.
package.json additions
{"packageManager":"pnpm@11.1.2","private":true}
Pin the exact version (not 11.x.x) so CI/Vercel use the version you tested.
Remove any overrides or pnpm.overrides blocks from package.json — they belong in pnpm-workspace.yaml for pnpm 11.
entrypoint: [""] — mandatory; Trivy Docker image has no shell otherwise (exit code 127)
--exit-code 0 — never fail the pipeline; email even when clean
--scanners vuln,secret — covers both dependency CVEs and leaked secrets
rules: schedule — only runs on scheduled pipelines, not every push
Vulnerabilities grouped by (package, installed_version) — one row per package, showing highest severity and best fix version
py3-packaging via apk — not pip (pip is blocked in Alpine CI)
Recipients are parametrized via the REPORT_RECIPIENTS CI/CD variable (comma-separated) in BOTH the Python To: header and the curl --mail-rcpt loop — never hardcode addresses in the job.
"DepSheriff" branded email — modern Untitled-UI HTML (Inter font, rounded severity pills, orange accent, logo). Trivy's contrib/html.tpl artifact still ships too.
CVE → NVD links via cve_links() (first 5 linked, rest collapsed to +N more).
Scan date/timestamp from CI_PIPELINE_CREATED_AT, shown in the header and the subject ([proj] Security Scan · 2C 5H · YYYY-MM-DD).
"Paste to Agent" block — a markdown table of findings ready to drop into any AI agent for fix recommendations.
The logo (ik.imagekit.io/.../security-logo.png) and footer credit are the DepSheriff brand — keep or swap for your own.
0 7 * * 1 = Monday 07:00 UTC = 08:00/09:00 Madrid (winter/summer)
ref = default branch (main or develop)
PROJECT_ID = GitLab project → Settings → General
Or via UI: CI/CD → Schedules → New schedule
Trigger manually to test:
curl --request POST \
--header "PRIVATE-TOKEN: <token>" \
"https://gitlab.com/api/v4/projects/<PROJECT_ID>/pipeline_schedules/<SCHEDULE_ID>/play"
Step 5 — PHP/Composer Projects (Laravel)
The Trivy fs scan above already reads composer.lock and reports PHP CVEs in
the email — so PHP deps are covered out of the box. composer audit adds the
Packagist Security Advisories feed on top (some advisories land there before a
CVE is assigned).
Do NOT add composer audit inline to the dependency-scan job — the aquasec/trivy
Alpine image has no PHP/Composer binary, so it would silently no-op. Add a separate
job with a composer image instead:
# Packagist advisory audit — complements the Trivy composer.lock scan.# Output goes to the job log (not the email). Non-blocking.composer-audit:image:composer:2script:-composeraudit--no-interaction--format=plain||truerules:-if:$CI_PIPELINE_SOURCE=="schedule"allow_failure:true
For fixing PHP vulnerabilities locally:
# Update specific packages
composer update "symfony/*" --with-all-dependencies
# Update all PHP deps (careful — test after)
composer update
Common PHP transitive dep CVEs — update these when flagged:
symfony/* — update to latest patch on your major (e.g. 7.4.x)
'>{s.capitalize()} {counts[s]}</span>"
for s in ["CRITICAL", "HIGH", "MEDIUM", "LOW"] if counts[s] > 0
])
table_section = "" if not vulns else f"""
<table border='