Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/trivy-no-exceptions.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# No accepted image vulnerability exceptions.
# Bare advisory IDs are intentionally absent.
31 changes: 23 additions & 8 deletions .github/workflows/security.yml
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ jobs:
timeout-minutes: 10 # Prevent long-running jobs
steps:
- uses: actions/checkout@v7
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Cache Trivy DB
uses: actions/cache@v6
with:
Expand All @@ -104,31 +106,44 @@ jobs:
trivy-db-
continue-on-error: true
- name: Build image for scanning
run: docker build -t eventrelay:test -f Dockerfile .
run: |
docker build -t eventrelay:test -f Dockerfile .
docker image inspect eventrelay:test > image-identity.json
git rev-parse HEAD > image-source-commit.txt
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
with:
image-ref: 'eventrelay:test'
format: 'sarif'
output: 'trivy-results.sarif'
exit-code: '0'
ignore-unfixed: true
exit-code: '1'
ignore-unfixed: false
vuln-type: 'os,library'
severity: 'CRITICAL,HIGH'
trivyignores: '.trivyignore'
trivyignores: '.github/trivy-no-exceptions.txt'
- name: Upload Trivy results to GitHub Security tab
uses: github/codeql-action/upload-sarif@v4
if: hashFiles('trivy-results.sarif') != ''
if: always() && hashFiles('trivy-results.sarif') != ''
with:
sarif_file: 'trivy-results.sarif'
- name: Generate human-readable report
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
if: success()
if: always()
with:
image-ref: 'eventrelay:test'
format: 'table'
exit-code: '0'
ignore-unfixed: true
ignore-unfixed: false
vuln-type: 'os,library'
severity: 'CRITICAL,HIGH'
trivyignores: '.trivyignore'
trivyignores: '.github/trivy-no-exceptions.txt'
- name: Retain exact image scan evidence
uses: actions/upload-artifact@v7
if: always()
with:
name: trivy-image-evidence
path: |
trivy-results.sarif
image-identity.json
image-source-commit.txt
if-no-files-found: error
15 changes: 12 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
&& rm -rf /var/lib/apt/lists/*

# Copy dependency manifests
COPY pyproject.toml requirements.txt* ./
COPY pyproject.toml requirements.txt* constraints.txt ./
COPY package.json package-lock.json ./
COPY apps/web/package.json ./apps/web/

Expand All @@ -32,8 +32,12 @@ COPY apps/web/package.json ./apps/web/
COPY apps/web/scripts ./apps/web/scripts

# Install Python dependencies
RUN pip install --no-cache-dir --upgrade pip && \
(pip install --no-cache-dir -r requirements.txt || pip install --no-cache-dir -e .)
RUN pip install --no-cache-dir --upgrade \
pip \
"setuptools>=80.10.2" \
"wheel>=0.46.2" && \
(pip install --no-cache-dir -c constraints.txt -r requirements.txt || \
pip install --no-cache-dir -c constraints.txt -e .)

# Install Node.js dependencies for the web app
# Using workspace to ensure proper hoisting and dependency resolution
Expand All @@ -53,13 +57,18 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates \
&& curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \
&& apt-get install -y nodejs \
&& npm install --global npm@12.2.0 \
&& rm -rf /var/lib/apt/lists/*

# Create non-root user
RUN groupadd --gid 1000 appuser && \
useradd --uid 1000 --gid appuser --shell /bin/bash --create-home appuser

# Copy installed Python packages from builder
# The base image ships packaging metadata of its own. Clear it before copying
# the builder environment so stale vulnerable dist-info/vendor records cannot
# survive the directory overlay into the runtime image.
RUN rm -rf /usr/local/lib/python3.11/site-packages/*
COPY --from=builder /usr/local/lib/python3.11/site-packages /usr/local/lib/python3.11/site-packages
COPY --from=builder /usr/local/bin /usr/local/bin

Expand Down
52 changes: 21 additions & 31 deletions MERGE_POLICY.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,27 +113,22 @@ strand a change permanently pending. v1 listed `Security` among its required
six, and dropping every security scan out of the required set would be a
weakening introduced by the rewrite that set out to make this gate precise.

> **What requiring these does and does not buy you.** Four of the six cannot
> fail on findings, by construction, in `.github/workflows/security.yml`:
>
> | Job | Why it always reports `success` |
> | --- | --- |
> | `npm-audit` | `continue-on-error: true` |
> | `python-safety` | `\|\| true` **and** `continue-on-error: true` |
> | `bandit` | `\|\| true` |
> | `trivy` | `exit-code: '0'` on both invocations |
>
> `Security Scan - python` and `Security Scan - javascript` are CodeQL matrix
> jobs; they report that analysis completed and upload alerts to the Security
> tab. Whether an alert blocks a merge is a code-scanning check-failure setting,
> not something the workflow decides.
>
> So gate 2 requires these security scan jobs (check-runs) to **run and complete** —
> which catches a scan that crashed, timed out, or was silently dropped from the
> pipeline. It is not a findings gate, and this policy should not be read as
> claiming vulnerability enforcement. Making findings actually block is a change to
> `security.yml`, with its own diff and its own blast radius; see *What is
> deliberately not here*.
`trivy` is a findings gate: the image scan fails on every reported HIGH or
CRITICAL vulnerability, including findings without a fix. SARIF and exact image
identity are retained even when the scan fails. The human-readable report does
not override the enforcing scan's exit status.

No exceptions are accepted by the current image gate. Existing bare IDs in
`.trivyignore` are not evidence of approved risk acceptance and are not applied.
Any future exception must be introduced in a separately reviewed change with
an exact advisory/package/version/image scope, applicability evidence,
compensating controls, an accountable approver, linked issue, expiry, and an
enforced expiry check. A missing advisory or unproven exposure remains unresolved.

The other security jobs remain completion gates: `npm-audit`, `python-safety`,
and `bandit` still tolerate findings. CodeQL check failure also depends on the
repository's code-scanning settings. Their green conclusions do not establish
absence of vulnerabilities.

> **`trivy` is lowercase.** Two distinct check-runs exist on the same head —
> `trivy` reports `success`, `Trivy` reports `neutral`, confirmed on both heads
Expand Down Expand Up @@ -252,16 +247,11 @@ laundering itself as enforcement.
- **No evidence ledger requirement.** v1 mandated a receipt comment on #898 for
every gate evaluation. Nothing consumed those receipts. Git history and
check runs are the ledger.
- **No change to what the security scans enforce.** Gate 2 requires the six
security checks to run and complete; four of them cannot fail on findings, as
documented there. Turning `npm-audit`, `python-safety`, `bandit`, and `trivy`
into blocking gates means removing their `continue-on-error` / `|| true` /
`exit-code: '0'` escapes in `security.yml`, which will fail pull requests on
the existing backlog of findings the moment it lands. That is a deliberate
decision about tolerance for known vulnerabilities, not a documentation fix,
so it is left to its own pull request. Requiring the checks first is still
worth doing: it makes a scan that crashes or quietly disappears from the
pipeline visible, which is the failure this policy can actually close.
- **Image findings are enforced.** `trivy` must fail on unresolved HIGH and
CRITICAL findings. Reporting success while tolerating findings is confined to
the other explicitly advisory scanners. Do not automatically demote `trivy`
for a high failure rate caused by real unresolved vulnerabilities; the 50%
demotion rule applies to broken execution, not to valid security findings.

## Review clock

Expand Down
2 changes: 1 addition & 1 deletion apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
"flags": "^4.3.1",
"jpeg-js": "^0.4.4",
"lucide-react": "^1.47.0",
"next": "^16.3.3",
"next": "16.3.8",
"next-auth": "^4.24.15",
"openai": "^7.23.0",
"react": "^19",
Expand Down
2 changes: 2 additions & 0 deletions constraints.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Security floors for transitive Python packages copied into the runtime image.
urllib3>=2.8.0
Loading
Loading