Security Scanning¶
Last reviewed: 2026-06-23
Project NEXUS is a public AGPL repository. Security scanning must distinguish reachable production risk from development-tooling noise.
Reporting a Vulnerability¶
Do not open a public issue for an unpatched vulnerability. Use the private disclosure path documented in SECURITY.md.
Security Sources Of Truth¶
| Document or file | Purpose |
|---|---|
SECURITY.md |
Public vulnerability disclosure policy and safe-research rules. |
.github/workflows/security-scan.yml |
CI security scan workflow (runs nightly + on every push to main). |
.github/workflows/dependency-review.yml |
Lightweight PR gate — runs only when package files change. |
owasp-suppressions.xml |
OWASP Dependency-Check suppressions with documented reasons. |
.trivyignore |
Trivy suppressions with documented reasons. |
.semgrepignore |
Semgrep path exclusions (dead/legacy code). |
composer.lock, package-lock.json, react-frontend/package-lock.json |
Dependency state that scanners evaluate. |
CI Scan Coverage¶
The security-scan.yml workflow runs the following tools in order. The CI definition is the authoritative reference — this table is a summary only.
| Tool | What it covers | Blocking? |
|---|---|---|
composer audit --locked |
PHP CVEs in composer.lock |
Yes |
| Trivy filesystem (table) | Filesystem CVEs at CRITICAL/HIGH | Yes, respects .trivyignore |
| Trivy filesystem (SARIF) | Same — uploads to GitHub Security tab | No (visibility only) |
| Semgrep (SAST) | PHP injection, secret patterns, security anti-patterns | No (SARIF upload only) |
| TruffleHog | Verified secrets in git history | Yes |
| OWASP Dependency-Check | Transitive CVEs across PHP + npm, CVSS ≥ 7 | Yes, respects owasp-suppressions.xml |
npm audit --omit=dev |
Production npm CVEs at high+ | Yes |
| Trivy container scan | OS/library CVEs inside the built Docker image | Yes (push events only) |
Full scan results land in the GitHub Security tab (SARIF uploads) and as workflow artifacts for the OWASP HTML report.
Running Routine Dependency Checks Locally¶
Run these before opening a PR when you have changed any lock file. They are fast and catch the most common class of finding before CI does.
PHP¶
- Checks
composer.lockagainst the PHP Security Advisories database. - A clean run prints nothing and exits 0.
- Any output names a package, a CVE or advisory ID, and a severity. Resolve by upgrading the affected package or, if the path is unreachable, by adding a suppression (see below).
npm (production dependencies only)¶
npm audit --omit=dev --audit-level=high
cd react-frontend && npm audit --omit=dev --audit-level=high
--omit=devrestricts the check to packages that ship in the production bundle. Build tools, dev servers, and test frameworks are excluded; their advisories are real noise against the production risk surface.--audit-level=highexits non-zero only on high or critical findings.- The output groups findings by severity and names the vulnerable package, the advisory, the fix version (if one exists), and the dependency path.
To see all severities (informational):
Quick local Trivy scan (optional)¶
Requires Trivy installed locally (brew install trivy / apt install trivy / trivy.dev/latest/getting-started/installation). This runs the same filesystem scan as CI.
Reading Scanner Output¶
composer audit / npm audit¶
Both tools print a table grouped by severity. The key fields to check:
- Package name and version — confirm you have the affected version.
- Advisory or CVE ID — search the advisory for the vulnerable code path. Many advisories affect features (e.g. XML parsing, OAuth callbacks) that the project may not exercise.
- Fixed in — upgrade to at least this version. Check for lock-file conflicts before upgrading.
OWASP Dependency-Check (HTML report)¶
The HTML artifact produced by CI groups findings by dependency and by CVE. Columns to read:
- Severity / CVSS — the CI gate blocks at CVSS ≥ 7 (HIGH). Lower scores are informational.
- Evidence — shows why OWASP linked this CVE to this package. CPE mismatches (the CVE is for a package with a similar name but a different ecosystem) are the most common false-positive class.
- Related Dependencies — shows which file in the project pulled in the affected package. If the file is a dev tool only, the production exposure is zero.
Trivy¶
Trivy prints a table of findings per file/layer. The table shows the library, the installed version, the fixed version, and the CVE ID. A (unfixed) note on the fixed version means no patch exists yet — container scans run with --ignore-unfixed for this reason.
Suppression Policy¶
Suppressions exist to keep the signal-to-noise ratio high. A suppression that hides a real finding is worse than no suppression.
When a suppression is appropriate¶
- The vulnerable code path is not reachable from the project (wrong CPE match, unused feature, dev-only package).
- No fix is available yet and the exposure is accepted pending an upgrade.
- The finding is a kernel-level OS CVE in a container that does not run as root and has no privilege-escalation path.
A suppression is not appropriate just because a finding is inconvenient or because upgrading is difficult. If upgrading is hard, document why in the suppression and set a short review date.
What every suppression must contain¶
- CVE or advisory ID — the specific identifier being suppressed.
- Reason — a plain-English sentence explaining why the finding is not a real risk in this project.
- Review date — when the suppression should be re-evaluated (recommended: 90 days, or when the dependency next has a release).
Trivy suppression format (.trivyignore)¶
Each entry is a CVE ID on its own line. Inline comments (#) carry the required reason and review date:
# CVE-2099-12345: affects the XML parser feature of libfoo; project does not
# use XML parsing anywhere in the dependency graph. Review: 2026-09-23.
CVE-2099-12345
Group related entries under a shared comment block when multiple CVEs share the same reason.
OWASP Dependency-Check suppression format (owasp-suppressions.xml)¶
<suppressions xmlns="https://jeremylong.github.io/DependencyCheck/dependency-suppression.1.3.xsd">
<suppress until="2026-09-23Z">
<notes>
CVE-2099-12345: affects the XML parser feature of example-lib; project
does not use XML parsing. Suppressed until next release of example-lib.
</notes>
<packageUrl regex="true">^pkg:npm/example-lib@.*$</packageUrl>
<cve>CVE-2099-12345</cve>
</suppress>
</suppressions>
The until date enforces expiry — OWASP Dependency-Check will re-surface the finding after that date even if the suppression file is not updated.
Suppression hygiene rules¶
- Do not include secrets, private contacts, live IP addresses, or credential paths in suppression files.
- Explain why the finding is not reachable, not just that it has been reviewed.
- Review suppressions when a dependency moves from development tooling into production runtime.
- Prefer upgrading security-sensitive packages even when exposure is low.
- When a suppression expires or a fixed version ships, remove the suppression and upgrade.
CI Scan Schedule and Gate Summary¶
| When | What runs | Blocks merge? |
|---|---|---|
Every push to main |
Full security scan + container scan | Yes (composer, Trivy, TruffleHog, OWASP, npm audit) |
| Nightly (02:00 UTC) | Full security scan | Yes |
| PR touching package files | Dependency review (GitHub Dependency Graph) | Informational only (Dependency Graph not yet enabled) |
The full scan does not run on PRs by default. PR-time coverage comes from the dependency-review workflow and from the fact that every merge to main triggers the full scan.
Related Checks¶
Run the workflow-defined security checks in CI for release decisions. Do not run repeated local audits merely to recreate the same known noise classes.