Skip to content

Add project-level security scan rules#744

Open
aidenybai wants to merge 18 commits into
mainfrom
feat/security-posture-scanner
Open

Add project-level security scan rules#744
aidenybai wants to merge 18 commits into
mainfrom
feat/security-posture-scanner

Conversation

@aidenybai

@aidenybai aidenybai commented Jun 8, 2026

Copy link
Copy Markdown
Member

Summary

Adds project-level security scan rules: 36 first-class rules that detect what per-file linting never sees — browser-artifact secret leaks, Firebase/Supabase authorization mistakes, CI/install trust-boundary issues, clickjacking/SVG risks, agent/MCP tool capability risks, injection patterns, and committed secret files.

Architecture

Scan rules follow the same lifecycle as every other rule in the repo:

  • One rule, one file — each lives in oxlint-plugin-react-doctor/src/plugin/rules/security-scan/<rule-id>.ts as defineRule({ id, title, severity, recommendation, scan }) — same definition as AST rules, with scan in place of create. The scan(file) => findings field replaces AST visitors for this kind; per-finding severity/title/help overrides cover the two dynamic rules (public-debug-artifact, active-static-asset).
  • Normal registration — rules flow through the standard codegen registry (bucket → category Security, auto-tag security-scan), so tags, severities, titles, and help text are single-sourced. The registry generator keeps main's one-rule-per-file contract.
  • Executed as an environment check@react-doctor/core's checkSecurityScan (shaped like checkPnpmHardening) runs in runInspect's environment-checks phase: one bounded whole-tree walk, per-rule gating through the real shouldEnableRule (capabilities, ignoredTags, disabledBy), and diagnostics streamed through the per-element pipeline so severity controls, inline disables, and surfaces apply like any other diagnostic. services/linter.ts is untouched vs main.
  • Never shipped as no-ops — scan rules are excluded from generated oxlint configs and from the ESLint flat-config presets (regression-tested in both paths).

Behavior notes

  • Skipped on diff/staged scans (gate is diff mode, not the old includePaths === undefined proxy) — projects configuring ignore.files get the security scan.
  • User rules/categories severity overrides, inline disables, and surface filtering apply to scan-rule diagnostics.
  • ignore.tags: ["security-scan"] (or rules ignore-tag security-scan) silences the whole family.

Verification

  • A 757-line fixture suite (packages/core/tests/check-security-scan.test.ts) over 31 fixture projects; a differential harness compared the original monolith implementation vs the final engine over all fixtures: 0 diagnostic differences.
  • runInspect integration tests (full-scan emission, diff-mode gate, severity restamp), ESLint-preset + oxlint-config exclusion regressions, registry invariants (exactly 36 tagged scan rules; scan nowhere else), runScanRule unit harness with regressions for the AST and dynamic-severity rules.
  • pnpm typecheck, pnpm build, core (713 tests), react-doctor (1737 tests), and plugin suites green (the 6 failing plugin files are pre-existing on main).

Test plan

  • pnpm --filter @react-doctor/core test
  • pnpm --filter oxlint-plugin-react-doctor test
  • pnpm --filter ./packages/react-doctor test
  • pnpm typecheck && pnpm build && pnpm format:check

Note

Medium Risk
New whole-repo security scanning on full inspect increases false-positive surface and scan cost, but diff mode skips it and rules are opt-out via security-scan tag; changes are additive to the diagnostic pipeline rather than altering auth or runtime behavior.

Overview
Adds a project-level security file scan with 36 security-scan rules registered like normal React Doctor rules (defineRule + scan instead of AST visitors) but executed only by @react-doctor/core during inspect’s environment-check phase—not via generated oxlint configs or ESLint presets.

checkSecurityScan walks the repo once with caps (depth, file count, size), classifies paths into priority / artifact / other buckets, runs enabled rules through the same shouldEnableRule gating as lint, and emits Security diagnostics through the standard pipeline (severity overrides, inline disables, surfaces, ignore.tags for security-scan).

Coverage spans shipped bundles and maps, .env/credential files, Firebase rules, Supabase SQL, CI/package metadata, and pattern-based risks (SQL/NoSQL injection, webhooks, postMessage, BaaS authz, etc.). --diff / staged scans skip the scan like other whole-project checks. Docs describe how to author scan rules; Tailwind version parsing in core now uses semver lower bounds for gating.

Reviewed by Cursor Bugbot for commit a056719. Bugbot is set up for automated code reviews on this repo. Configure here.

@pkg-pr-new

pkg-pr-new Bot commented Jun 8, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/eslint-plugin-react-doctor@744
npm i https://pkg.pr.new/oxlint-plugin-react-doctor@744
npm i https://pkg.pr.new/react-doctor@744

commit: a056719

@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

No React Doctor issues found. 🎉

Reviewed by React Doctor for commit a056719.

Comment thread packages/core/src/check-security-posture.ts Outdated
Comment thread packages/core/src/check-security-posture.ts Outdated
Comment thread packages/core/src/check-security-posture.ts Outdated
@aidenybai aidenybai force-pushed the feat/security-posture-scanner branch from 51e8816 to cdfe430 Compare June 8, 2026 06:57
Comment thread packages/core/src/check-security-posture.ts Outdated
Comment thread packages/core/src/check-security-posture.ts Outdated
Comment thread packages/core/src/check-security-posture.ts Outdated
@aidenybai aidenybai force-pushed the feat/security-posture-scanner branch from cdfe430 to b1fe41d Compare June 8, 2026 07:14
Comment thread packages/core/src/check-security-posture.ts Outdated
Comment thread packages/core/src/constants.ts Outdated
@aidenybai aidenybai force-pushed the feat/security-posture-scanner branch from b1fe41d to d1f18fc Compare June 8, 2026 07:23
Comment thread packages/core/src/check-security-posture.ts Outdated
Comment thread packages/core/src/check-security-posture.ts Outdated
@aidenybai aidenybai force-pushed the feat/security-posture-scanner branch from d1f18fc to d9adf7e Compare June 8, 2026 07:34
Comment thread packages/core/src/check-security-posture.ts Outdated
@aidenybai aidenybai force-pushed the feat/security-posture-scanner branch from d9adf7e to 4a1840e Compare June 8, 2026 07:41
Comment thread packages/core/src/check-security-posture.ts Outdated
Comment thread packages/core/src/check-security-posture.ts Outdated
Comment thread packages/core/src/check-security-posture.ts Outdated
@aidenybai aidenybai force-pushed the feat/security-posture-scanner branch from 4a1840e to b18753e Compare June 8, 2026 07:47
Comment thread packages/core/src/check-security-posture.ts Outdated
Comment thread packages/core/src/check-security-posture.ts Outdated
@aidenybai aidenybai force-pushed the feat/security-posture-scanner branch from b18753e to a940685 Compare June 8, 2026 08:05
Comment thread packages/core/src/check-security-posture.ts
Comment thread packages/core/src/check-security-posture.ts Outdated
@aidenybai

Copy link
Copy Markdown
Member Author

/rde parity

@react-doctor-evals

react-doctor-evals Bot commented Jun 9, 2026

Copy link
Copy Markdown

❌ Parity failed — trace 1405c291c9033052b769f0c8c3a4326b. Check server logs for that trace ID.

Comment thread packages/core/src/check-security-posture.ts Outdated
Comment thread packages/core/src/check-security-posture.ts Outdated
Comment thread packages/core/src/check-security-posture.ts Outdated
Comment thread packages/core/src/check-security-posture.ts Outdated
Comment thread packages/core/src/services/linter.ts Outdated
aidenybai added 10 commits June 9, 2026 14:17
…rule posture modules

One rule file = one rule, like every other bucket: 36 definePostureRule
modules under rules/security-posture/ replace the 1433-line scanner
monolith and the 364-line no-op stub file. The registry generator's
multi-export hack is reverted to main's single-match contract with
definePostureRule added to the alternation. checkSecurityPosture is now
a thin walk-and-dispatch over registry entries carrying `scan`.
Project-level posture rules now execute through core's environment
check machinery — same walker, same dispatch, same diagnostics —
selected via the shared shouldEnableRule capability/tag gate instead
of a hand-rolled filter. The plugin dispatcher remains only as a
parity reference until the next phase removes it.
The scan now joins reduced-motion, pnpm hardening, and expo/RN checks
in run-inspect's environment phase: skipped in diff mode, streamed
through the per-element pipeline (severity controls, inline disables,
surfaces), and gated per rule by shouldEnableRule. services/linter.ts
returns to main unchanged; posture rules are excluded from generated
oxlint configs and ESLint presets instead of shipping as no-ops.
… surface

The scanner file, the /ast subpath export, and the plugin-side copies
of core fs utils are gone; the posture rules and their bucket utils are
the single home for scan logic, and core owns the walk.
Adds ignoredTags + registry-metadata single-sourcing tests and a
package-metadata-secret fixture to the core suite, a runPostureRule
harness with colocated regressions tests for the AST and
dynamic-severity rules, registry invariants (exactly 36 tagged posture
rules, scan field nowhere else), and posture-rule authoring docs. The
changeset now describes the environment-check architecture and its
intentional behavior changes.
Mutation-verified gaps from review: the ESLint-preset posture filter in
rules.ts could be reverted without any test failing (same bug class as
the defaultEnabled preset leak), and the runInspect dispatch line could
be deleted silently. Adds a registry-derived preset regression and
three runInspect integration tests covering full-scan emission, the
diff-mode gate, and user severity overrides restamping posture
diagnostics.
CodeQL js/polynomial-redos: the unanchored (\d+)\.(\d+) scan over the
package.json version string backtracks quadratically on long digit
runs. Bounded {1,4} quantifiers keep it linear, matching the fix
parse-react-major-minor.ts already carries.
Replaces the hand-rolled digit regex (the CodeQL polynomial-redos
surface) with semver, which core already depends on: minVersion gives
the range's lower bound (>=3.4 <5 → 3.4) and coerce covers prefixed
specs like npm:tailwindcss@^3.4.1.
'Posture' meant nothing to anyone. Project-level rules are now plain
'scan rules': defineScanRule modules in rules/security-scan/, tagged
security-scan, typed ScanFinding/FileScan/ScannedFile, executed by
core's checkSecurityScan environment check. Same light shape as before
— a scan field on Rule and a distinctly-named wrapper — no parallel
type system.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit c3f0d7c. Configure here.

const finding: ScanFinding = {
message: "A browser-reachable debug, log, dump, report, or env artifact is present.",
line: location.line,
column: location.column,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Public debug scan skips inline disables

Medium Severity

public-debug-artifact findings are emitted with line and column of 0 because location comes from getMatchLocation with no pattern. The diagnostic pipeline only evaluates inline react-doctor-disable comments when line is greater than 0, so suppressions on the affected file never apply to this rule despite the PR’s promise that scan diagnostics honor inline disables.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit c3f0d7c. Configure here.

@aidenybai aidenybai changed the title Add security posture scanner Add project-level security scan rules Jun 10, 2026
One definition for both rule kinds: an AST rule provides create, a
scan rule provides scan, and defineRule injects the inert visitor
factory hosts require. defineScanRule is gone and the registry
generator's matcher is back to main's defineRule|defineRetiredRule
form. The catch-all generic overload is constrained to extends Rule so
context-sensitive scan arrows resolve to the scan overload instead of
freezing widened literal types.
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.

2 participants