Skip to content

feat(threatcrush-scan): omit write scopes and their steps rather than disabling them - #970

Merged
ralyodio merged 1 commit into
masterfrom
threatcrush-scan-minimal
Aug 17, 2026
Merged

feat(threatcrush-scan): omit write scopes and their steps rather than disabling them#970
ralyodio merged 1 commit into
masterfrom
threatcrush-scan-minimal

Conversation

@ralyodio

Copy link
Copy Markdown
Contributor

Addresses a maintainer review on mac-developer-bridge#7. Both findings were the pack's, not theirs.

Least privilege was a claim about a condition, not a description of the file. uploadSarif: false / commentOnPr: false still rendered the Security-tab upload and the PR-comment step, guarded by an if:. A disabled upload still asks a maintainer to read an upload and trust the guard. The template gained a whole-line {{#if var}} … {{/if}} block and those steps are now removed from the render. Each scope is emitted by the output that needs it, so permissions: cannot drift from what the workflow does — retiring the hand-assembled extraPermissions input.

Size. Read-only render is 291 lines, one file, against the 382-line workflow + 235-line converter that was offered. The report builder is the same behaviour in about half the lines.

{{#if}} is the only control construct, not nestable, and still takes a bare variable name — the renderer's "only {{varName}}, no expressions" guarantee is unchanged. Anything but the literal 'true' drops the block, because pack inputs are strings and a stray value read as truthy would be a granted write scope. Blocks resolve before substitution, so variables inside a dropped block need not be supplied.

Nothing was dropped from the report: fail-closed NOT RUN, severity-first ordering, the scoped/backlog fold and the truncation notice all still render. Verified against a real 6-finding SARIF across five states (scoped, scoped-with-nothing-touched, unscoped, scan-skipped, SARIF-missing).

One bug found while testing: the "could not read SARIF" warning went to stdout, which is now the report file itself. Moved to stderr.

94 tests pass across packages/actions and packages/actions-fleet-core; actions-fleet-core typechecks clean.

…st disable them

mac-developer-bridge declined the offered workflow on two points that were
the pack's rather than the repository's.

The two write scopes were requested unconditionally even though the workflow
is described as report-only, and `uploadSarif: false` / `commentOnPr: false`
still rendered the steps that use them, guarded by a condition. That makes
"least privilege" a claim about a condition rather than a description of the
file. A shipped-but-disabled Security-tab upload still asks a maintainer to
read an upload and take on trust that the guard is right.

So the template gained a whole-line `{{#if var}} … {{/if}}` block, and the two
optional steps are removed from the render rather than switched off. Each
scope is now emitted by the output that needs it — `pull-requests: write` from
commentOnPr, `security-events: write` from uploadSarif — so the permissions
block cannot drift out of step with what the workflow does. That retires the
hand-assembled `extraPermissions` input, which made least privilege something
a caller had to remember.

`{{#if}}` is the only control construct and it is not nestable. It still takes
a bare variable name, so the renderer's "only {{varName}}, no expressions"
guarantee is unchanged: there is nothing to evaluate, only a value compared
against the literal 'true'. Anything else drops the block, because pack inputs
are strings and treating a stray value as truthy would turn a typo into a
granted write scope. Blocks resolve before substitution, so a variable that
only makes sense inside a kept block need not be supplied when it is dropped.

The second point was size. With both outputs off the render is 291 lines and
one file, against the 382-line workflow plus 235-line converter that was
offered. The report builder is the same behaviour in ~half the lines; the
install step's commentary is trimmed. Nothing was dropped from it: fail-closed
NOT RUN, severity-first ordering, the scoped/backlog fold and the truncation
notice all still render, verified against a real 6-finding SARIF across five
states (scoped, scoped-with-no-touched-findings, unscoped, scan-skipped,
SARIF-missing).

One fix found while testing: the report's "could not read SARIF" warning went
to stdout, which is now the report file itself. It goes to stderr.

Also read-only outputs are the ones that keep working on fork pull requests,
where GitHub downgrades GITHUB_TOKEN — so the richest reporting was least
reliable exactly where an external scan is most useful. That is now in the
README rather than discovered by a maintainer.
@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

84 finding(s)

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

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 34 more. Full results in the Security tab.

Snippets are redacted; ThreatCrush never prints matched credential material.

@ralyodio
ralyodio merged commit a660656 into master Aug 17, 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