* fix(only-export-components): skip framework route/special files (#776)
Recognize the route/special files of every file-routing framework
react-doctor covers (Next.js, Expo Router, TanStack Router, Remix /
React Router) via a single `isFrameworkRouteOrSpecialFilename` helper,
so the documented "co-export config/metadata next to the default
component" shape no longer triggers false-positive non-component-export
warnings. Fixes the `alt` / `size` / `contentType` / `revalidate`
exports in `opengraph-image.tsx`.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix: change variable name
Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* fix: update usages as well
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Pin actions/setup-node and actions/github-script (referenced inside the
composite action.yml) to full-length commit SHAs, with a trailing version
comment, so the action is consumable under orgs that enforce GitHub's
"actions pinned to a full-length commit SHA" ruleset. Also updates the
github-action contract test to accept SHA-pinned refs (either a floating
major tag or a SHA-pin), keeping the major-version floor.
Supersedes #751.
Co-authored-by: pauldambra <paul@posthog.com>
* feat(core): add Socket.dev supply-chain score check
New SupplyChain service + checkSupplyChain that score each direct dependency against Socket's free, keyless PURL endpoint (the same lookup sfw's free tier uses) and emit a Security diagnostic for any package below the configured minScore. Implemented with Effect (Effect.forEach bounded concurrency, Effect.tryPromise + Effect.timeout for cancellable fetches, fail-open per package).
Co-authored-by: Nisarg Patel <NisargIO@users.noreply.github.com>
* feat: wire SupplyChain into the inspect pipeline
Run the supply-chain check as a scan phase in runInspect (skipped in diff/staged mode), and provide its layer at every composition site: gated on supplyChain.enabled in the CLI (build-runtime-layers) and api (diagnose), and a no-op layerOf([]) for editor scans and the run-inspect tests.
Co-authored-by: Nisarg Patel <NisargIO@users.noreply.github.com>
* chore: add changeset for Socket supply-chain score check
Co-authored-by: Nisarg Patel <NisargIO@users.noreply.github.com>
* feat: run Socket supply-chain check by default
Flip the default so the supply-chain score gate runs out of the box; users opt out with `supplyChain: { enabled: false }`. Editor scans and --diff/--staged runs remain off regardless.
Co-authored-by: Nisarg Patel <NisargIO@users.noreply.github.com>
* feat(cli): add --sfw demo flag to list Socket scores for all dependencies
New collectSupplyChainScores core helper fetches the Socket score of every direct dependency (reusing the free-tier fetch internals). The CLI's --sfw flag short-circuits before the scan, prints a color-coded worst-first table via renderSupplyChainScores, and exits. Mutually exclusive with --json/--score.
Co-authored-by: Nisarg Patel <NisargIO@users.noreply.github.com>
* fix(core): anchor supply-chain diagnostic to the declared package.json entry
The diagnostic now points at the exact line/column where the dependency is declared in package.json (instead of line 0) and names the declared spec plus the entry to edit, so a finding like next@16.2.4 traces directly to its `"next": "16.2.4"` entry. Records the declared spec + key location while collecting dependencies.
Co-authored-by: Nisarg Patel <NisargIO@users.noreply.github.com>
* fix(cli): drop the free-API subtitle from --sfw header
Co-authored-by: Nisarg Patel <NisargIO@users.noreply.github.com>
* feat(cli): scan every workspace package.json for --sfw in a monorepo
collectSupplyChainScores now aggregates direct dependencies across the workspace root and every workspace package (pnpm/npm/yarn/nx globs via the shared project-info resolvers), deduped by name@version and anchored at the true monorepo root even when invoked from a sub-package. The in-scan check stays per-project (the scan loop already visits each workspace).
Co-authored-by: Nisarg Patel <NisargIO@users.noreply.github.com>
* feat(core): exclude next from the supply-chain check
next's framework-specific risks are already covered by react-doctor's Next.js / server-components rules, so a low Socket score is redundant noise. Add a SUPPLY_CHAIN_IGNORED_PACKAGES set and filter it in the shared dependency collector so both the gate and the --sfw listing skip it.
Co-authored-by: Nisarg Patel <NisargIO@users.noreply.github.com>
* refactor(core): drop unused breakdown field and trim Socket artifact schema
The --sfw renderer only reads name/version/overall, so the per-axis breakdown on DependencyScore was speculative dead code — remove it and its construction. Also trim SocketArtifactSchema to the only field read (score); Schema.Struct already ignores the artifact's other fields.
Co-authored-by: Nisarg Patel <NisargIO@users.noreply.github.com>
* fix(core): anchor supply-chain diagnostic within its dependency section
locateDependencyKey previously returned the first `"name":` match anywhere in package.json, so a same-named entry under overrides / resolutions / pnpm.overrides could win over the real dependencies/devDependencies line. Scope the search to the declaring section via its `"<section>": {` header + brace-depth tracking. Fixes Cursor Bugbot finding.
Co-authored-by: Nisarg Patel <NisargIO@users.noreply.github.com>
* refactor(api): derive supplyChain enablement from config in buildDiagnoseLayer
Drop the standalone supplyChainEnabled parameter; buildDiagnoseLayer now takes the effective config (and optional override directory), builds the config layer from it, and reads supplyChain.enabled from that same config. One config input drives both the Config service and the supply-chain gate.
Co-authored-by: Nisarg Patel <NisargIO@users.noreply.github.com>
* feat(core): emit a per-package OTel span for each Socket score lookup
Wrap fetchSocketScore in a SupplyChain.fetchScore span carrying socket.package / socket.version / socket.purl, and annotate the resolved axis scores (socket.score.overall + one per SCORE_AXES dimension, 0-100) plus socket.scored. Lets a trace backend group by package and query score distributions across a scan. No-op without a tracer.
Co-authored-by: Nisarg Patel <NisargIO@users.noreply.github.com>
* refactor(core): inline buildScoreSpanAttributes into its single call site
Co-authored-by: Nisarg Patel <NisargIO@users.noreply.github.com>
* feat(core): run supply-chain in diff mode when the manifest changed
Add supplyChainManifestChanged to InspectInput/InspectOptions and gate the supply-chain phase on (!isDiffMode || supplyChainManifestChanged), so a diff/PR scan that edits package.json still scores its dependencies instead of skipping the check like the other whole-project passes.
Co-authored-by: Nisarg Patel <NisargIO@users.noreply.github.com>
* feat(cli): scan a workspace whose package.json changed in a PR diff
Detect per-project package.json changes (projectManifestChanged, monorepo-aware via the same prefix logic as the include-path resolver). A manifest-only-changed workspace is no longer skipped from the diff loop: it enters the scan with the manifest as the sole include (stays in diff mode, lints nothing) and supplyChainManifestChanged set, so its dependencies get scored. Threaded the flag through the inspect wrapper + scan-result cache key.
Co-authored-by: Nisarg Patel <NisargIO@users.noreply.github.com>
* refactor(cli): share project-relative-directory boundary logic across diff helpers
projectManifestChanged duplicated resolveProjectDiffIncludePaths's project-relative + ..-boundary handling. Extract resolveProjectRelativeDirectory and repoint both so the monorepo prefix rule lives in one place and can't drift.
Co-authored-by: Nisarg Patel <NisargIO@users.noreply.github.com>
* docs(changeset): reflect diff-mode manifest scoring, monorepo --sfw, and next exclusion
Co-authored-by: Nisarg Patel <NisargIO@users.noreply.github.com>
* fix(cli): make --sfw exclusive with diff/staged and score the base manifest in baseline mode
Bugbot triage: (1) --sfw now errors when combined with --staged/--diff (was silently ignored). (2) Baseline diff scans now forward supplyChainManifestChanged to the base pass and include package.json in the diff include set, so the base manifest is scored and computeDiagnosticDelta filters pre-existing low-score deps instead of reporting them as newly introduced. Verified e2e: pre-existing low dep filtered, newly-added one reported.
Co-authored-by: Nisarg Patel <NisargIO@users.noreply.github.com>
* feat(core): list all axis scores in the supply-chain diagnostic message
Replace the single 'Lowest area' summary with the full per-axis breakdown (supply chain / vulnerability / maintenance / quality / license, 0-100) so the message shows exactly which dimensions dragged the score down. Drops the now-redundant findLowestAxis.
Co-authored-by: Nisarg Patel <NisargIO@users.noreply.github.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Nisarg Patel <NisargIO@users.noreply.github.com>
* feat(core): add concrete-semver parsing and comparison util
Adds parseConcreteSemver/compareConcreteSemver/formatConcreteSemver for
exact major.minor.patch versions, rejecting ambiguous range specs so a
security check never guesses a resolved version. Exposes PackageJson.version
for reading installed manifests under node_modules.
Co-authored-by: Nisarg Patel <NisargIO@users.noreply.github.com>
* feat(core): flag React Server Components security advisories by version
Adds a no-vulnerable-react-server-components environment check that detects
projects running RSC on a version with a known advisory: the critical
unauthenticated RCE (CVE-2025-55182, CVSS 10.0) and the later high-severity
DoS (CVE-2026-23870).
- Cross-framework path (Vite, Parcel, React Router, Waku, RedwoodSDK):
resolves the installed react-server-dom-{webpack,parcel,turbopack} version
and compares against the per-minor patched releases (19.0.6/19.1.7/19.2.6).
- Next.js special plumbing: Next vendors its own RSC runtime, so a Next
project is checked by its next version (15.x->15.5.18, 16.x->16.2.6, 13/14
unsupported) and the fix points at a Next.js bump, not a React bump.
- Stays quiet for pure client-side React apps and never flags off an
ambiguous declared range whose lockfile may resolve to a patched version.
Wired into the run-inspect environment phase alongside checkPnpmHardening.
Co-authored-by: Nisarg Patel <NisargIO@users.noreply.github.com>
* refactor(core): address review on RSC advisory check
- Use the `semver` library instead of a hand-rolled version parser/comparator
(removes packages/core/src/utils/concrete-semver.ts and its test).
- Resolve installed versions across every workspace package's node_modules in
addition to the scan root, so a monorepo whose `next`/`react-server-dom-*`
is installed only under e.g. apps/web is still detected (fixes Bugbot
'Misses workspace node_modules installs').
- Add committed test fixtures under
tests/fixtures/check-react-server-components-advisory and drive the
version-matrix suite off them; keep programmatic tests for node_modules
resolution, the monorepo regression, and canary handling.
- Changeset bumped to patch; drop the redundant PackageJson.version comment.
Co-authored-by: Nisarg Patel <NisargIO@users.noreply.github.com>
* fix(core): dispatch RSC advisory on resolved next, not root framework
A monorepo root classified as Vite (or unknown) with Next.js installed only
in a workspace previously skipped the Next.js advisory check, missing the
vendored RSC runtime. Dispatch now keys on whether `next` resolves in any
candidate directory. Fixes Bugbot 'Non-Next framework skips Next check'.
Co-authored-by: Nisarg Patel <NisargIO@users.noreply.github.com>
* fix(core): check every workspace install for RSC advisories
Replaces single first-match resolution with a per-directory scan so:
- An inert/safe Next at the monorepo root no longer hides a vulnerable Next
in a workspace (Bugbot 'First next wins over workspace').
- A pre-13 Next install no longer suppresses the standalone react-server-dom
check, since pre-13 is not an affected line (Bugbot 'Pre-13 next skips RSC
check').
Diagnostics are deduplicated by message across directories.
Co-authored-by: Nisarg Patel <NisargIO@users.noreply.github.com>
* fix(core): walk to true monorepo root for RSC advisory candidates
When scanning a nested workspace package, deps may be hoisted to the real
monorepo root. Walk up via findMonorepoRoot so that root (and its
node_modules) and every sibling workspace are probed, instead of only the
scanned directory. Fixes Bugbot 'Workspace scan misses monorepo root'.
Fixture tests are copied into an isolated temp dir so the walk-up does not
reach the react-doctor repo root they live under.
Co-authored-by: Nisarg Patel <NisargIO@users.noreply.github.com>
* fix(core): don't let an unparseable catalog spec shadow resolved next version
resolveVersionInDirectory now falls through to the first spec that is a
concrete version, so a `catalog:` (or other unparseable) manifest spec no
longer shadows discovery's already-resolved `project.nextjsVersion` pin.
Fixes Bugbot 'Catalog spec blocks version override'.
Co-authored-by: Nisarg Patel <NisargIO@users.noreply.github.com>
* fix(core): probe all workspaces, not just React-bearing ones
Enumerate every workspace package directory for candidate node_modules
probing instead of reusing the React-filtered listWorkspacePackages, so a
workspace declaring only react-server-dom-* (or next under optionalDependencies)
is still evaluated. Fixes Bugbot 'Skips non-React workspace packages'.
Co-authored-by: Nisarg Patel <NisargIO@users.noreply.github.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Nisarg Patel <NisargIO@users.noreply.github.com>
* feat(core): gate React-specific rules behind a `react` capability
Add a `react` capability (set when React or Preact is detected) and use
it to gate every React-runtime rule family so they stay off on a project
without React. The codegen injects the requirement for the React-runtime
buckets (hooks, JSX, a11y, render performance, React state), and
`shouldEnableRule` treats the `react-jsx-only` tag as implying it. Two
bare-name migration rules that matched class-member names without a React
guard (`no-legacy-class-lifecycles`, `no-legacy-context-api`) now require
it explicitly. Without this, hook/component-name heuristics would
false-fire on ordinary TypeScript once non-React scanning is enabled.
Co-authored-by: Nisarg Patel <NisargIO@users.noreply.github.com>
* feat: scan TypeScript projects that don't depend on React
A project is now analyzable when it has source files, not only when React
or Preact is present, so a plain TypeScript/JavaScript codebase runs the
framework-agnostic rules instead of hard-failing with 'No React project
found'. `discoverProject` tolerates a directory with no package.json (a
monorepo's `packages/` subfolder, a loose tree of files) by synthesizing a
ProjectInfo, inheriting dependency/framework detection from the enclosing
workspace root so a React monorepo subdirectory still gets its React
capabilities. Only a directory with neither source files nor an enclosing
project still reports nothing-to-scan.
Co-authored-by: Nisarg Patel <NisargIO@users.noreply.github.com>
* refactor(core): clarify discoverProject's no-package.json branches
Split the synthesized ProjectInfo into its two real cases — a workspace
subfolder (spread the enclosing root's detection, scope the directory-
specific fields) and a standalone source tree (explicit all-null,
framework-agnostic) — replacing the noisy `inherited?.x ?? null` chain
repeated across every field. Behavior is unchanged.
Co-authored-by: Nisarg Patel <NisargIO@users.noreply.github.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Nisarg Patel <NisargIO@users.noreply.github.com>
* chore(ci): compress comments in generated workflow template
Collapses the multi-line YAML comments in the generated
`.github/workflows/react-doctor.yml` to a single sentence per block
(triggers, permissions, concurrency), so a new user sees what each block
does without scrolling past five-line explanations.
No behavior change: same triggers, same permissions (`contents: read`,
`pull-requests: write`, `issues: write`, `statuses: write`), same
`millionco/react-doctor@v2` ref, same concurrency group, same
configuration-knobs reference under the `with:` block.
Co-authored-by: Cursor <cursoragent@cursor.com>
* chore(ci): drop redundant permissions comment in template
Per PR review (#739) — the four `permissions:` keys (`contents: read`,
`pull-requests: write`, `issues: write`, `statuses: write`) are
self-explanatory, so the leading sentence was just noise. Removes the
comment from the generated workflow and tightens the matching
buildWorkflowContent docstring.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>