Skip to content

feat: add missing security workflows (dependency-review, trivy, zizmor)#56

Closed
Copilot wants to merge 8 commits intomasterfrom
copilot/setup-trustsignal-dev-org
Closed

feat: add missing security workflows (dependency-review, trivy, zizmor)#56
Copilot wants to merge 8 commits intomasterfrom
copilot/setup-trustsignal-dev-org

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Mar 18, 2026

Three GitHub Actions security workflows referenced throughout compliance docs (security-workflows.md, github-settings-checklist.md, security-posture.md) did not exist in the repo, creating a gap between documented controls and implemented controls. This resolves that gap.

Summary

  • .github/workflows/dependency-review.yml — blocks PRs on master/work that introduce HIGH/CRITICAL dependency vulnerabilities via actions/dependency-review-action@v4.7.1
  • .github/workflows/trivy.yml — filesystem scan on every push/PR; split into two jobs to enforce least-privilege: the trivy job runs with contents: read only, uploads SARIF as a workflow artifact; the upload-sarif job holds security-events: write exclusively and pushes SARIF to code scanning (skipped on forked PRs); the third-party aquasecurity/trivy-action step never runs with the elevated token
  • .github/workflows/zizmor.yml — audits .github/workflows/** changes for workflow security issues via pip install zizmor; advisory mode uses continue-on-error: true so genuine runtime failures (tool crash, bad args) remain visible in the job summary while audit findings do not block the PR

All three workflows:

  • SHA-pin action references
  • persist-credentials: false on checkout
  • permissions: contents: read at workflow level; security-events: write scoped only to the SARIF upload job in trivy.yml

What still requires manual GitHub UI action

Branch protection rulesets, Dependabot alerts, secret scanning, and CodeQL default setup — see docs/github-settings-checklist.md.

AI Disclosure (optional)

  • AI-assisted changes are included in this PR

Review Checklist

  • Human review requested
  • Tests added or updated where appropriate
  • No secrets, tokens, cookies, or raw PII were added to code, logs, fixtures, or docs
  • Security impact and remaining risks are described

💬 Send tasks to Copilot coding agent from Slack and Teams to turn conversations into code. Copilot posts an update in your thread when it's finished.

@vercel
Copy link
Copy Markdown

vercel bot commented Mar 18, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
api Ready Ready Preview, Comment Mar 24, 2026 0:31am
trustsignal Ready Ready Preview, Comment Mar 24, 2026 0:31am

Copilot AI changed the title [WIP] Set up TrustSignal-dev organization and compliance repositories feat: add missing security workflows (dependency-review, trivy, zizmor) Mar 18, 2026
Copilot AI requested a review from chrismaz11 March 18, 2026 03:27
@chrismaz11
Copy link
Copy Markdown
Collaborator

Set Complaice

@chrismaz11 chrismaz11 marked this pull request as ready for review March 18, 2026 03:37
Copilot AI review requested due to automatic review settings March 18, 2026 03:37
@github-advanced-security
Copy link
Copy Markdown

You are seeing this message because GitHub Code Scanning has recently been set up for this repository, or this pull request contains the workflow file for the Code Scanning tool.

What Enabling Code Scanning Means:

  • The 'Security' tab will display more code scanning analysis results (e.g., for the default branch).
  • Depending on your configuration and choice of analysis tool, future pull requests will be annotated with code scanning analysis results.
  • You will be able to see the analysis results for the pull request's branch on this overview once the scans have completed and the checks have passed.

For more information about GitHub Code Scanning, check out the documentation.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 862d41e3e7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds the missing GitHub Actions security workflows referenced in the repo’s compliance/security documentation, closing the gap between documented controls and implemented controls.

Changes:

  • Adds a Dependency Review workflow to block PRs that introduce HIGH/CRITICAL vulnerable dependencies.
  • Adds a Trivy filesystem scan workflow that uploads SARIF results to GitHub Code Scanning (with fork-PR upload safeguards).
  • Adds a zizmor workflow to audit workflow-file changes in advisory mode.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
.github/workflows/dependency-review.yml Adds dependency diff vulnerability gating for PRs into master/work.
.github/workflows/trivy.yml Adds Trivy FS scanning and SARIF upload to Code Scanning with fork-PR upload skip logic.
.github/workflows/zizmor.yml Adds advisory zizmor auditing for changes under .github/workflows/**.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +28 to +34
run: |
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 +15 to +30
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
@chrismaz11
Copy link
Copy Markdown
Collaborator

This is a strong narrow candidate for near-term merge pending focused human review of workflow permissions and SARIF behavior.

@chrismaz11
Copy link
Copy Markdown
Collaborator

This is a strong, narrow candidate for near-term merge pending focused human review of workflow permissions and SARIF behavior.

@chrismaz11 chrismaz11 added the merge-soon Narrow PR that is a candidate for near-term merge after focused review label Mar 20, 2026
@chrismaz11 chrismaz11 force-pushed the copilot/setup-trustsignal-dev-org branch from e9c90fb to 774b905 Compare March 20, 2026 04:23
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 774b905bf7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +5 to +7
branches:
- master
- work
Copy link
Copy Markdown

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 👍 / 👎.

Comment on lines +6 to +10
- master
- work
pull_request:
branches:
- master
Copy link
Copy Markdown

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 👍 / 👎.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a6441e5c26

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +55 to +56
if (!parsed.password) {
parsed.password = encodeURIComponent(supabasePassword);
Copy link
Copy Markdown

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 👍 / 👎.

Comment on lines +16 to +17
path.resolve(process.cwd(), '../../.env.local'),
path.resolve(process.cwd(), '../../.env')
Copy link
Copy Markdown

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 👍 / 👎.


function stripHtml(text: string): string {
return text
.replace(/<script[\s\S]*?<\/script>/gi, ' ')

Check failure

Code scanning / CodeQL

Bad HTML filtering regexp High

This regular expression does not match script end tags like </script >.

Copilot Autofix

AI 5 days ago

In general terms, the fix is to improve the script-stripping regular expression so that it correctly matches script end tags even when they contain whitespace or attributes, aligning with how browsers actually recognize script end tags. We should still avoid trying to be a full HTML parser, but we can handle the specific weakness CodeQL flags by widening the closing-tag pattern without changing the rest of the function.

Concretely, in stripHtml in apps/api/src/services/registryAdapters.ts, update the first .replace call from:

.replace(/<script[\s\S]*?<\/script>/gi, ' ')

to a pattern that accepts optional whitespace and attributes after </script and before >; for example:

.replace(/<script\b[\s\S]*?<\/script[^>]*>/gi, ' ')

Changes:

  • Add \b after script in the opening tag so <scripting> is not matched.
  • Change the closing tag from <\/script> to <\/script[^>]*> so constructs like </script foo="bar"> or </script > are matched.
    This keeps the overall behavior (removing script blocks) while covering browser-tolerated closing tag variations. No new imports or helpers are needed, and all other .replace calls in stripHtml remain unchanged.
Suggested changeset 1
apps/api/src/services/registryAdapters.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/apps/api/src/services/registryAdapters.ts b/apps/api/src/services/registryAdapters.ts
--- a/apps/api/src/services/registryAdapters.ts
+++ b/apps/api/src/services/registryAdapters.ts
@@ -809,7 +809,7 @@
 
 function stripHtml(text: string): string {
   return text
-    .replace(/<script[\s\S]*?<\/script>/gi, ' ')
+    .replace(/<script\b[\s\S]*?<\/script[^>]*>/gi, ' ')
     .replace(/<style[\s\S]*?<\/style>/gi, ' ')
     .replace(/<\/?(br|p|tr|td|th|li|div|section|article|h[1-6])[^>]*>/gi, ' ')
     .replace(/<[^>]+>/g, ' ')
EOF
@@ -809,7 +809,7 @@

function stripHtml(text: string): string {
return text
.replace(/<script[\s\S]*?<\/script>/gi, ' ')
.replace(/<script\b[\s\S]*?<\/script[^>]*>/gi, ' ')
.replace(/<style[\s\S]*?<\/style>/gi, ' ')
.replace(/<\/?(br|p|tr|td|th|li|div|section|article|h[1-6])[^>]*>/gi, ' ')
.replace(/<[^>]+>/g, ' ')
Copilot is powered by AI and may make mistakes. Always verify output.
@chrismaz11 chrismaz11 committed this autofix suggestion 5 days ago.
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7bc4b32342

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

# =============================================================

API_URL="https://api.trustsignal.dev"
API_KEY="d4a2bd92be56c54905a99f2b5709e14064e9eaeb99c44aa74898125aedf5028a"
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Remove committed API credentials from test script

This script hardcodes a bearer API key and targets the production api.trustsignal.dev endpoint by default, which exposes a reusable credential to anyone with repository access and allows accidental production writes when developers run local tests. Because this is a direct secret-management and compliance breach, the script should read both values from environment variables and fail fast when they are not provided.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator

@chrismaz11 chrismaz11 left a comment

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
Contributor Author

Copilot AI commented Mar 23, 2026

@copilot apply changes based on the comments in this thread

Applied both review thread changes in commit 7d1d0a7:

zizmor.yml — replaced the custom exit 0 shell logic with continue-on-error: true on the audit step. This means genuine runtime failures (tool crash, bad args, etc.) are still surfaced as a failed step in the job summary, while audit findings from valid runs do not block the PR.

trivy.yml — split into two jobs:

  • trivy job: permissions: contents: read only — runs the scan and uploads the SARIF as an Actions artifact.
  • upload-sarif job: permissions: security-events: write only — downloads the artifact and calls upload-sarif. The fork-PR skip condition remains on this job. The third-party aquasecurity/trivy-action step no longer runs with the elevated token.

@chrismaz11
Copy link
Copy Markdown
Collaborator

@copilot apply changes based on the comments in this thread

Copy link
Copy Markdown
Contributor Author

Copilot AI commented Mar 23, 2026

@copilot apply changes based on the comments in this thread

Both review thread changes were already applied in commit 7d1d0a7 and are in the current branch:

  • zizmor.yml: continue-on-error: true replaces the exit 0 shell hack — genuine runtime failures are now visible in the job summary.
  • trivy.yml: split into two jobs — trivy (only contents: read, runs the scan and uploads SARIF as an artifact) and upload-sarif (only security-events: write, downloads the artifact and calls upload-sarif, still skipped on fork PRs). The third-party aquasecurity/trivy-action step no longer runs with the elevated token.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 52748881d3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

reasons: JSON.parse(record.reasons) as string[],
riskScore: record.riskScore,
verifierId: 'deed-shield',
verifierId: 'trustsignal',
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve legacy verifier ID when hydrating stored receipts

Keep backward compatibility for previously issued receipts: receiptFromDb now hardcodes verifierId to trustsignal, but historical rows were hashed/signed with deed-shield and verifyStoredReceipt recomputes integrity from this reconstructed payload. Because verifierId is part of the unsigned payload hash/signature, older receipts will start failing integrity/signature verification after deploy even though the stored receipt data is unchanged. Persisting/verifying the original verifier ID (or handling legacy values during hydration) is needed to avoid breaking existing receipts.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

apiKey: string,
secret = process.env.API_KEY_FINGERPRINT_SECRET || DEV_API_KEY_FINGERPRINT_SECRET
): string {
return createHmac('sha256', secret).update(apiKey).digest('hex').slice(0, 16);

Check failure

Code scanning / CodeQL

Use of password hash with insufficient computational effort High

Password from
a call to readHeader
is hashed insecurely.
Password from
an access to apiKey
is hashed insecurely.
Password from
a call to readHeader
is hashed insecurely.
Password from
an access to apiKey
is hashed insecurely.
Password from
an access to apiKey
is hashed insecurely.

Copilot Autofix

AI 5 days ago

General fix: Replace the use of a single-iteration fast hash (HMAC‑SHA256) over the API key with a password hashing / key-derivation function designed to be computationally expensive, such as PBKDF2, bcrypt, scrypt, or Argon2. Since we must avoid new external dependencies and stay within the shown file, PBKDF2 from Node’s crypto module is the most practical: it is standard, well-supported, and configurable (iterations, key length, digest).

Concrete approach here:

  • Switch from createHmac('sha256', secret).update(apiKey).digest('hex') to pbkdf2Sync(apiKey, secret, iterations, keylen, 'sha256').
  • Keep the existing API of fingerprintApiKey (same parameters and return type string, still returning a short hex string) to avoid altering surrounding functionality.
  • Use a reasonable default iteration count and key length (for example, 100,000 iterations and 32-byte output) and then truncate to 16 hex characters as before so callers remain unaffected.
  • Import pbkdf2Sync from node:crypto alongside existing imports.

All changes are localized to apps/api/src/security.ts: one import line change and one function body change (in fingerprintApiKey). No other functions or call sites need modification.


Suggested changeset 1
apps/api/src/security.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/apps/api/src/security.ts b/apps/api/src/security.ts
--- a/apps/api/src/security.ts
+++ b/apps/api/src/security.ts
@@ -1,4 +1,4 @@
-import { createHmac, generateKeyPairSync } from 'node:crypto';
+import { createHmac, generateKeyPairSync, pbkdf2Sync } from 'node:crypto';
 
 import { getAddress, verifyMessage } from 'ethers';
 import { FastifyReply, FastifyRequest } from 'fastify';
@@ -273,7 +273,12 @@
   apiKey: string,
   secret = process.env.API_KEY_FINGERPRINT_SECRET || DEV_API_KEY_FINGERPRINT_SECRET
 ): string {
-  return createHmac('sha256', secret).update(apiKey).digest('hex').slice(0, 16);
+  // Derive a computationally expensive fingerprint from the API key using PBKDF2.
+  // Keep the output format and truncation length the same to avoid changing callers.
+  const iterations = 100_000;
+  const keyLength = 32; // 32 bytes -> 64 hex chars before truncation
+  const derived = pbkdf2Sync(apiKey, secret, iterations, keyLength, 'sha256');
+  return derived.toString('hex').slice(0, 16);
 }
 
 export function requireApiKeyScope(config: SecurityConfig, requiredScope: AuthScope) {
EOF
@@ -1,4 +1,4 @@
import { createHmac, generateKeyPairSync } from 'node:crypto';
import { createHmac, generateKeyPairSync, pbkdf2Sync } from 'node:crypto';

import { getAddress, verifyMessage } from 'ethers';
import { FastifyReply, FastifyRequest } from 'fastify';
@@ -273,7 +273,12 @@
apiKey: string,
secret = process.env.API_KEY_FINGERPRINT_SECRET || DEV_API_KEY_FINGERPRINT_SECRET
): string {
return createHmac('sha256', secret).update(apiKey).digest('hex').slice(0, 16);
// Derive a computationally expensive fingerprint from the API key using PBKDF2.
// Keep the output format and truncation length the same to avoid changing callers.
const iterations = 100_000;
const keyLength = 32; // 32 bytes -> 64 hex chars before truncation
const derived = pbkdf2Sync(apiKey, secret, iterations, keyLength, 'sha256');
return derived.toString('hex').slice(0, 16);
}

export function requireApiKeyScope(config: SecurityConfig, requiredScope: AuthScope) {
Copilot is powered by AI and may make mistakes. Always verify output.
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a037894368

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

},
"dependencies": {
"@deed-shield/core": "file:../../packages/core",
"@trustsignal/core": "file:../../packages/core",
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Regenerate API lockfile after renaming core package

This change switches apps/api to depend on @trustsignal/core, but the committed apps/api/package-lock.json still tracks @deed-shield/core, so clean installs in that workspace now fail (npm -C apps/api ci --dry-run reports the package/lock mismatch and missing @trustsignal/core). That breaks deterministic installs for anyone running CI or local setup from apps/api directly; update and commit the workspace lockfile alongside this dependency rename.

Useful? React with 👍 / 👎.

Comment on lines +15 to +17
permissions:
contents: read
security-events: write
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Restrict Trivy write permission to SARIF upload context

security-events: write is declared at workflow scope, so the third-party aquasecurity/trivy-action step runs with that write-capable token too. This expands blast radius unnecessarily and contradicts the stated least-privilege intent in the workflow comments; scope security-events: write only to the SARIF upload job/step so scanner execution keeps read-only permissions.

Useful? React with 👍 / 👎.

@chrismaz11
Copy link
Copy Markdown
Collaborator

Superseded by #92 which has the merge conflicts resolved.

@chrismaz11 chrismaz11 closed this Mar 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

merge-soon Narrow PR that is a candidate for near-term merge after focused review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants