Skip to content
Open
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
26 changes: 26 additions & 0 deletions .github/workflows/dependency-review.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: Dependency diff review

on:
pull_request:
branches:
- master
- work
Comment on lines +5 to +7

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Run dependency review on main-targeted pull requests

This trigger only matches PRs into master/work, so PRs targeting main will never execute the blocking dependency diff check. Given the repository’s branch-protection guidance and required-check setup are defined for main, this leaves the intended high/critical dependency gate inactive on the branch where merges are expected; include main (or remove the branch filter) so the control actually enforces there.

Useful? React with 👍 / 👎.


# Restrict to the minimum permissions needed for checkout and dependency review.
permissions:
contents: read

jobs:
dependency-review:
name: Dependency diff review
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false

- name: Dependency diff review
uses: actions/dependency-review-action@da24556b548a50705dd671f47852072ea4c105d9 # v4.7.1
with:
fail-on-severity: high
46 changes: 46 additions & 0 deletions .github/workflows/trivy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: Trivy repository scan

on:
push:
branches:
- master
- work
pull_request:
branches:
- master
Comment on lines +6 to +10

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Enable Trivy scan for pushes and PRs on main

The Trivy workflow is scoped to master/work, so it will not run for activity on main; that means no filesystem scan and no SARIF refresh for the primary protected branch described in the repo docs. Add main to both push and pull_request branch filters (or broaden the trigger) to avoid silently losing security coverage on the branch used for compliance checks.

Useful? React with 👍 / 👎.

- work

# Restrict to minimum required permissions.
# security-events: write is required only for SARIF upload to code scanning.
permissions:
contents: read
security-events: write

jobs:
trivy:
name: Trivy filesystem scan
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false

- name: Run Trivy filesystem scan
uses: aquasecurity/trivy-action@6e7b7d1fd3e4fef0c5fa8cce1229c54b2c9bd0d8 # v0.30.0
Comment on lines +15 to +30
with:
scan-type: fs
scan-ref: "."
severity: HIGH,CRITICAL
ignore-unfixed: true
format: sarif
output: trivy-results.sarif

- name: Upload Trivy SARIF to code scanning
# Skip on forked PRs — GitHub does not grant security-events: write to
# untrusted fork tokens, so SARIF upload would fail with a permissions error.
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }}
uses: github/codeql-action/upload-sarif@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18
with:
sarif_file: trivy-results.sarif
category: trivy
34 changes: 34 additions & 0 deletions .github/workflows/zizmor.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: zizmor advisory audit

on:
pull_request:
paths:
- ".github/workflows/**"

# Restrict to minimum required permissions.
permissions:
contents: read

jobs:
zizmor:
name: zizmor workflow audit
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false

- name: Install zizmor
run: pip install zizmor==1.5.0

- name: Run zizmor workflow audit
# Advisory mode — findings are reported but do not fail the job.
# Maintainers should review and address findings before merging workflow changes.
run: |
EXIT_CODE=0
zizmor --format plain .github/workflows/ || EXIT_CODE=$?
if [ $EXIT_CODE -ne 0 ]; then
echo "::warning::zizmor found workflow security findings (advisory). Review the output above before merging."
fi
exit 0
Comment on lines +28 to +34
70 changes: 70 additions & 0 deletions apps/api/src/env.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { readFileSync } from 'node:fs';
import path from 'node:path';

import dotenv from 'dotenv';

let envLoaded = false;

export function loadRuntimeEnv(envPathCandidates?: string[]): void {
if (envLoaded) return;

const candidates =
envPathCandidates ??
[
path.resolve(process.cwd(), '.env.local'),
path.resolve(process.cwd(), '.env'),
path.resolve(process.cwd(), '../../.env.local'),
path.resolve(process.cwd(), '../../.env')
Comment on lines +16 to +17

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep fallback .env discovery inside the project tree

These fallback candidates walk two directories up from the current working directory; when the API is started from the repository root, they resolve to /.env.local and /.env. That allows unrelated host-level env files to be loaded and silently influence runtime configuration (including DB credentials), making environment resolution nondeterministic across runners and developer machines. Limit fallback paths to known project locations instead of traversing above repo root.

Useful? React with 👍 / 👎.

];

for (const envPath of candidates) {
dotenv.config({ path: envPath, override: false });
}

envLoaded = true;
}

export function resolveDatabaseUrl(env: NodeJS.ProcessEnv = process.env): string | null {
const direct = (env.DATABASE_URL || '').trim();
if (direct) return direct;

const candidates = [env.SUPABASE_DB_URL, env.SUPABASE_POOLER_URL, env.SUPABASE_DIRECT_URL];

for (const candidate of candidates) {
const value = (candidate || '').trim();
if (value) {
env.DATABASE_URL = value;
return value;
}
}

const supabasePassword = (env.SUPABASE_DB_PASSWORD || '').trim();
if (supabasePassword) {
const poolerCandidates = [
path.resolve(process.cwd(), 'supabase/.temp/pooler-url'),
path.resolve(process.cwd(), '../../supabase/.temp/pooler-url'),
path.resolve(process.env.HOME || '', 'supabase/.temp/pooler-url')
];

for (const poolerPath of poolerCandidates) {
try {
const rawPoolerUrl = readFileSync(poolerPath, 'utf-8').trim();
if (!rawPoolerUrl) continue;

const parsed = new URL(rawPoolerUrl);
if (!parsed.password) {
parsed.password = encodeURIComponent(supabasePassword);
Comment on lines +55 to +56

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Override placeholder pooler password before using DATABASE_URL

In the SUPABASE_DB_PASSWORD fallback path, this check only injects the provided password when parsed.password is empty, but Supabase pooler URLs can contain placeholder credentials like [YOUR-PASSWORD] that parse as a non-empty password. In that case, DATABASE_URL is set to a URL with invalid credentials and Prisma startup/auth fails even though the real password is present in SUPABASE_DB_PASSWORD. Treat placeholder/stale passwords as missing (or overwrite unconditionally in this fallback branch).

Useful? React with 👍 / 👎.

}
parsed.searchParams.set('sslmode', 'require');

const resolved = parsed.toString();
env.DATABASE_URL = resolved;
return resolved;
} catch {
// Continue searching candidate pooler URLs.
}
}
}

return null;
}
Loading