The Skilluv team takes security seriously. If you discover a security vulnerability, we appreciate your help in disclosing it responsibly.
Please do NOT open a public GitHub issue for security vulnerabilities.
Send a detailed report to security@skilluv.com (or, if that inbox is not yet active, to the maintainer's email listed on the GitHub profile).
Alternatively, use GitHub's Private Vulnerability Reporting feature on this repository.
- A clear description of the vulnerability
- Steps to reproduce (with a proof-of-concept if possible)
- The affected version(s) or commit SHA
- The potential impact
- Your suggested remediation (optional but appreciated)
- Acknowledgment within 5 business days
- Initial assessment within 10 business days
- Coordinated disclosure timeline discussed with the reporter — target 90 days from acknowledgment to public disclosure
- Credit in the security advisory unless you request anonymity
In scope:
- The code hosted in this repository
- Any deployed instance operated by the Skilluv team (staging, production)
Out of scope:
- Third-party services (Stripe, GitHub, Judge0 upstream, etc.) — please report to the respective vendors
- Denial-of-service attacks against production infrastructure
- Social engineering attacks against Skilluv team members
- Issues in dependencies with existing published CVEs
Reporters of valid vulnerabilities may be listed in a SECURITY-ACKNOWLEDGMENTS.md file (with their consent) and, for significant findings, credited in the CVE advisory when applicable.
Thank you for helping keep Skilluv and its community safe.
Every commit that lands on master goes through :
| Layer | Tool | What it catches |
|---|---|---|
| Dependency CVE | cargo-deny + Trivy FS |
Known-vulnerable crates (two independent DBs) |
| License compliance | cargo-deny |
Non-allowlisted licenses (see deny.toml) |
| Secret leak | gitleaks |
Hardcoded secrets in git history |
| Source SAST | GitHub CodeQL (weekly) | SQL injection, unsafe deserialization, CWE patterns |
| Docker CVE | Trivy image scan | Vulnerable base image + system packages |
| Workflow security | zizmor + actionlint | Malicious PR injection, over-privileged perms |
| SBOM | syft (CycloneDX + SPDX) | Full inventory attached to every image |
| Provenance | SLSA L3 via slsa-framework | Cryptographic proof of build origin |
| Signature | cosign keyless (Sigstore) | Every image signed by our GH Actions workflow |
- Non-root container : image runs as
skilluv:skilluv(UID 10001) - tini as PID 1 for signal handling + zombie reaping
- HEALTHCHECK on
/api/health - RLS (Row-Level Security) enforced when
SKILLUV_RLS_ENFORCED=1(P14) - Argon2id password hashing
- SSO client_secret encrypted at rest via ChaCha20-Poly1305 (
SSO_ENCRYPTION_KEY) - JWT short-lived + rotating refresh tokens with SHA-256 hash in DB
- CORS allowlist explicit (
ALLOWED_ORIGINS), no wildcard - Rate limiting on auth endpoints (Redis-backed sliding window)
- CSRF token double-submit cookie on state-changing routes
- Security headers middleware (HSTS, CSP, X-Frame-Options, etc.)
Before pulling any Skilluv backend image, verify its signature :
scripts/verify-image.sh ghcr.io/skilluv/skilluv-backend:master-<sha>The script checks :
- cosign signature was issued by our GitHub Actions workflow (keyless)
- CycloneDX SBOM attestation is present
- SLSA L3 provenance is present
Non-signed or non-attested images are rejected — the deploy pipeline exits non-zero.
To enforce this before every prod deploy :
- Coolify UI → your
skilluv-backendservice → Configuration → Pre-deploy Command - Paste :
(Coolify exposes
bash scripts/verify-image.sh "$COOLIFY_IMAGE_TAG" || exit 1
$COOLIFY_IMAGE_TAG— the specific tag it's about to pull.) - Save + trigger a deploy. If the tag isn't signed, the deploy aborts and the previous healthy container stays live.
Test the guard once by manually pushing an unsigned image with the same tag and observing the deploy refuses. Then never worry about it again unless the signing key rotates.
For emergency rollback to a known-good signed tag, see §Incident response scenario 4 below.
All in .github/workflows/ :
| Workflow | Trigger | Rôle |
|---|---|---|
ci.yml |
PR + push master | fmt, clippy, machete, deny, gitleaks, tests ×2 shards, doctests |
codeql.yml |
weekly + dispatch | SAST source-level |
coverage.yml |
weekly + dispatch | llvm-cov + Codecov |
docker-scan.yml |
PR path-filtered + weekly | Trivy image scan + SBOM |
trivy.yml |
PR path-filtered + weekly | Trivy FS + IaC |
image-sign.yml |
post-CI master | cosign keyless + SBOM attestation |
slsa-provenance.yml |
post-CI master | SLSA L3 build provenance |
workflow-lint.yml |
PR touching .github/ | actionlint + zizmor |
pr-lint.yml |
PR opened/edited | Conventional-commit title enforcement |
load-test.yml |
dispatch (nightly ready) | k6 smoke perf test |
release-please.yml |
push master | Auto CHANGELOG + version bump |
dependabot-auto-merge.yml |
Dependabot PR | Auto-merge minor/patch after CI |
- Rotate the affected credential immediately :
- JWT_SECRET → generate new + redeploy → all existing sessions invalidated
- SSO_ENCRYPTION_KEY → generate new + re-encrypt DB rows (script needed)
- Stripe/OAuth secrets → rotate in provider dashboard + update
.env
- Revoke suspicious sessions :
UPDATE user_sessions SET revoked_at = NOW() WHERE user_id IN (<suspected>) OR last_ip = '<attacker IP>';
- Snapshot logs : Coolify → download last 24h of container logs before rotation
- Public disclosure : if user data affected, follow RGPD Art. 33 (72h notification to CNIL)
cargo deny checklocally to confirm the finding- If a fix is available :
cargo update -p <crate>+ PR - If no fix : evaluate exploitability → temporary mitigation (feature flag, WAF rule) → open upstream issue
- Document in SECURITY-ACKNOWLEDGMENTS.md
- Check
.github/workflows/*.ymlfor unauthorized modifications (git blame) - Verify branch protection settings weren't tampered with
- Force re-run all security workflows on affected commits
- Rotate GitHub PATs / deploy keys if suspected
- Stop the compromised container immediately (Coolify UI)
cosign verifythe previous known-good image tag- Rollback via Coolify to that tag
- Investigate : GitHub Actions logs, GHCR audit log
- If signature key compromised : cosign supports key revocation via Sigstore transparency log
In scope for our controls :
- OWASP Top 10 web (injection, broken auth, XSS, CSRF, insecure deserialization, etc.)
- Supply-chain attacks (typosquatting, malicious deps, compromised registry)
- CI/CD tampering (workflow injection, secret theft via PR)
- Container escape via vulnerable base image
- Session hijacking via network sniffing (TLS everywhere, secure cookies)
- Credential stuffing (rate limiting + Argon2 + WebAuthn opt-in)
Assumed threats (not fully mitigated) :
- Nation-state actor with physical access to Hetzner/Netcup datacenter
- Compromised developer workstation (own the maintainer's laptop = own the deploys)
- Zero-day in Postgres, Redis, Docker Engine (patched within 24h of upstream fix)
Explicitly out of scope :
- DDoS L7 → offloaded to Cloudflare
- Application-layer runtime monitoring (falco, tetragon) → not warranted at solo VPS scale
- Security reports : security@skilluv.com
- Maintainer : @jeremiezitti on GitHub
- Public discussion : never — use private disclosure channels above