Skip to content

fix(threatcrush-scan): report the pull request, not the whole backlog - #965

Merged
ralyodio merged 1 commit into
masterfrom
fix/threatcrush-pr-report
Aug 16, 2026
Merged

fix(threatcrush-scan): report the pull request, not the whole backlog#965
ralyodio merged 1 commit into
masterfrom
fix/threatcrush-pr-report

Conversation

@ralyodio

Copy link
Copy Markdown
Contributor

What prompted this

qryptchat-web#258 changed two files and received a ThreatCrush comment with 91 findings — 76 LOW, 10 MEDIUM, 5 HIGH — none of them from the diff under review.

I reproduced it exactly and hand-verified every HIGH and MEDIUM against the source. One of the 91 was real (a nested quantifier in a URL validator). The rest were false positives, and they're being fixed separately in profullstack/threatcrush#148.

But even with a perfect scanner, this comment would still have been wrong to post. A repository with a legacy backlog hands every author the whole backlog on every pull request. That is how a scanner teaches a team to scroll past it.

What changes

The scan still covers the whole tree, and the Security tab still receives all of it. Narrowing what gets scanned would be a real loss of coverage.

The comment is a review artifact, and a review is about the change under review. So:

  • Lead with findings in the files the pull request touches. The rest folds into one <details> line carrying its severity counts, listing the twenty most serious.
  • Order by severity. The 50-row cap was previously applied in SARIF order, which is file order — so which findings survived truncation was decided by where they sat in the tree. A high in the last file scanned could be cut while fifty notes from the first printed in full.

Same repository, same scan, rendered through the new builder:

## ThreatCrush Security Scan

**2** finding(s) in the 2 file(s) this pull request changes.

**MEDIUM**: 2

| Severity | Rule | Location |
|---|---|---|
| MEDIUM | `js-unescaped-html-sink` | `src/app/layout.jsx`:92 |
| MEDIUM | `js-unescaped-html-sink` | `src/app/layout.jsx`:96 |

<details>
<summary>89 pre-existing finding(s) elsewhere in the repository — **HIGH/CRITICAL**: 5 | **MEDIUM**: 8 | **LOW**: 76</summary>
...
</details>

How the diff is identified

refs/pull/N/merge has the base branch as its first parent and the pull request head as its second, so HEAD^1..HEAD is exactly the pull request — no API call, no token, no added permission. It needs fetch-depth: 2, which checkout now requests.

A conflicted pull request has no merge ref. Checkout falls back to the head commit, where HEAD^1 means "the previous commit on this branch" — a plausible-looking answer to a different question. The workflow verifies HEAD really has two parents before trusting the range; when it doesn't, it reports everything unscoped and says so in the log. Scoping to the wrong set of files is worse than not scoping.

Verification

  • Rendered the real 91-finding SARIF from qryptchat-web through the extracted report builder, scoped and unscoped.
  • Edge cases exercised: clean scan, scoped-but-nothing-touched, a SARIF result with no location, and the fail-closed NOT RUN path (all still correct).
  • pnpm vitest run packages/actions84 passed, including new assertions for the scoping, the ordering, the unscoped fallback, and that the rendered workflow is valid YAML across three input variants. That last one matters: the report builder is a heredoc'd Python program inside a run: block, where an indentation slip yields a workflow GitHub refuses to load while every content assertion still passes.
  • pnpm --filter @profullstack/sh1pt-action-packs typecheck clean.
  • The repo-wide pnpm vitest run has 662 suites failing to resolve workspace dist/ in a fresh worktree. Confirmed pre-existing by stashing this branch and reproducing the identical failure on the base commit.

Pack version 1.5.11.6.0, so consumers re-sync.

Note for whoever lands this

qryptchat-web installs @profullstack/threatcrush@latest (pack v1.1.0). npm is at 0.11.1, which equals threatcrush HEAD — so merging #148 will not change that PR comment until a release is cut, and re-syncing qryptchat to pack 1.6.0 moves it onto the pinned 0.11.0 spec with an integrity hash.

🤖 Generated with Claude Code

qryptchat-web#258 changed two files and got a comment with 91 findings,
five of them HIGH, none of them from the diff under review. Nothing in
that comment was actionable by its author, and the one finding that
mattered was in the middle of seventy-six fixture matches.

The scan still covers the whole tree and the Security tab still gets all
of it — narrowing what is scanned would be a real loss. What changes is
the comment, which is a review artifact and should be about the change
under review.

- Lead with findings in the files the pull request touches, and fold the
  rest into one <details> line carrying its severity counts.
- Order by severity. The 50-row cap used to be applied in SARIF order,
  which is file order, so which findings survived truncation depended on
  where they sat in the tree: a HIGH in the last file scanned could be
  cut while fifty notes from the first printed in full.

The diff comes from HEAD^1..HEAD. refs/pull/N/merge has the base as its
first parent and the head as its second, so that range is exactly the
pull request — no API call, no token, no added permission. It needs
fetch-depth: 2, which checkout now asks for.

A conflicted pull request has no merge ref; checkout falls back to the
head commit and HEAD^1 quietly starts answering a different question. So
the shape is verified before it is trusted, and a failure reports
everything unscoped and says so, rather than scoping to the wrong files.

Tests assert the scoping, the ordering, the unscoped fallback, and that
the rendered workflow is still parseable YAML across three input
variants — the report builder is a heredoc'd Python program inside a
run: block, where an indentation slip produces a file GitHub refuses to
load while every content assertion still passes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

vu1nz Security Review

0 finding(s) in PR #?

No security issues found.

@github-actions

Copy link
Copy Markdown

ThreatCrush Security Scan

311 finding(s)

HIGH/CRITICAL: 24 | MEDIUM: 51 | LOW: 236

Severity Rule Location
HIGH secret-generic-api-key packages/affiliates/sovrn/src/index.ts:28
HIGH js-nosql-injection packages/ai/amazon-bedrock/src/index.test.ts:121
HIGH secret-generic-credential packages/ai/amazon-bedrock/src/index.ts:9
HIGH secret-generic-credential packages/ai/amazon-bedrock/src/index.ts:10
HIGH secret-generic-credential packages/ai/amazon-bedrock/src/index.ts:11
HIGH js-host-header-trust packages/bots/wechat/src/index.ts:405
HIGH secret-generic-credential packages/bridges/matrix/src/index.ts:58
HIGH secret-generic-credential packages/bridges/matrix/src/index.ts:59
HIGH secret-generic-credential packages/captcha/captchasolver/src/index.ts:34
HIGH secret-generic-credential packages/cli/src/commands/secrets.ts:176
HIGH secret-generic-credential packages/cloud/linode/src/index.ts:15
HIGH secret-generic-credential packages/observability/sentry/src/index.ts:15
HIGH secret-generic-credential packages/outreach/producthunt/src/index.ts:103
HIGH secret-generic-credential packages/promo/posthog/src/index.ts:23
HIGH secret-generic-credential packages/security/snyk/src/index.ts:26
HIGH secret-generic-credential packages/social/hashnode/src/index.ts:4
HIGH secret-generic-credential packages/social/linkedin/src/index.ts:3
HIGH secret-generic-credential packages/social/linkedin/src/index.ts:4
HIGH secret-generic-credential packages/social/medium/src/index.ts:4
HIGH secret-generic-credential packages/social/snapchat/src/index.ts:5
HIGH secret-generic-credential packages/social/tiktok/src/index.ts:5
HIGH secret-generic-credential packages/targets/registry-ans/src/index.ts:49
HIGH secret-generic-credential sites/sh1pt.com/supabase/config.toml:303
HIGH secret-generic-credential sites/sh1pt.com/supabase/config.toml:335
MEDIUM redos-nested-quantifier packages/actions-fleet-core/src/action-pack/schema.ts:3
MEDIUM insecure-temp-file packages/agent-providers/opencode/src/__tests__/opencode.test.ts:19
MEDIUM insecure-temp-file packages/agent-providers/opencode/src/__tests__/opencode.test.ts:42
MEDIUM insecure-temp-file packages/agent-providers/opencode/src/__tests__/opencode.test.ts:45
MEDIUM insecure-temp-file packages/bridges/signal/src/index.test.ts:92
MEDIUM insecure-temp-file packages/bridges/signal/src/index.test.ts:118
MEDIUM insecure-temp-file packages/cli/src/input.test.ts:84
MEDIUM redos-nested-quantifier packages/core/src/setup-helpers.ts:583
MEDIUM insecure-temp-file packages/core/src/testing/harness.ts:15
MEDIUM insecure-temp-file packages/core/src/testing/harness.ts:16
MEDIUM insecure-temp-file packages/core/src/testing/harness.ts:30
MEDIUM insecure-temp-file packages/core/src/testing/harness.ts:42
MEDIUM insecure-temp-file packages/merch/printful/src/index.test.ts:9
MEDIUM insecure-temp-file packages/merch/printify/src/index.test.ts:11
MEDIUM insecure-temp-file packages/policy/src/linter.test.ts:8
MEDIUM redos-nested-quantifier packages/policy/src/rules/bundle-id.ts:3
MEDIUM insecure-temp-file packages/secrets/env-updater/src/index.test.ts:106
MEDIUM insecure-temp-file packages/social/facebook/src/index.test.ts:95
MEDIUM insecure-temp-file packages/social/instagram/src/index.test.ts:177
MEDIUM insecure-temp-file packages/social/pinterest/src/index.test.ts:91
MEDIUM insecure-temp-file packages/social/pinterest/src/index.test.ts:146
MEDIUM insecure-temp-file packages/social/threads/src/index.test.ts:108
MEDIUM insecure-temp-file packages/social/vimeo/src/index.test.ts:137
MEDIUM insecure-temp-file packages/social/x/src/index.test.ts:72
MEDIUM insecure-temp-file packages/social/x/src/index.test.ts:116
MEDIUM insecure-temp-file packages/targets/browser-safari/src/index.test.ts:22

…and 261 more. Full results in the Security tab.

Snippets are redacted; ThreatCrush never prints matched credential material.

@ralyodio
ralyodio merged commit 42439eb into master Aug 16, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant