Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/cool-fans-fly.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"react-doctor": patch
"oxlint-plugin-react-doctor": patch
---

Refine security posture scanner false positives while preserving coverage for Firebase compat writes, public scripts, private key material, and unsafe signature comparisons. Share oxlint AST parsing utilities through an explicit plugin subpath.
1 change: 0 additions & 1 deletion packages/core/src/run-inspect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,6 @@ export const runInspect = <HooksR = never>(
Stream.tap((diagnostic) => reporterService.emit(diagnostic)),
);

// ── Phase: environment checks ──────────────────────────────────
const environmentDiagnostics: ReadonlyArray<Diagnostic> = isDiffMode
? []
: [
Expand Down
25 changes: 24 additions & 1 deletion packages/core/src/services/linter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as Effect from "effect/Effect";
import * as Layer from "effect/Layer";
import * as Ref from "effect/Ref";
import * as Stream from "effect/Stream";
import reactDoctorPlugin, { checkSecurityPosture } from "oxlint-plugin-react-doctor";
import type { Diagnostic, ProjectInfo, ReactDoctorConfig } from "../types/index.js";
import { OxlintSpawnFailed, ReactDoctorError } from "../errors.js";
import { OxlintConcurrency, OxlintOutputMaxBytes, OxlintSpawnTimeoutMs } from "../refs.js";
Expand Down Expand Up @@ -53,6 +54,19 @@ const ensureReactDoctorError = (cause: unknown): ReactDoctorError =>
? cause
: new ReactDoctorError({ reason: new OxlintSpawnFailed({ cause }) });

const isSecurityPostureDiagnosticEnabled = (
diagnostic: Diagnostic,
ignoredTags: ReadonlySet<string> | undefined,
): boolean => {
const rule = reactDoctorPlugin.rules[diagnostic.rule];
if (!rule) return true;
if (!rule.tags) return true;
for (const tag of rule.tags) {
if (ignoredTags?.has(tag)) return false;
}
return true;
};

/**
* `Linter` is the cross-backend service for "produce diagnostics for
* an input." Today the only live layer is `layerOxlint` — wrapping
Expand Down Expand Up @@ -107,7 +121,7 @@ export class Linter extends Context.Service<
const outputMaxBytes = yield* OxlintOutputMaxBytes;
const concurrency = yield* OxlintConcurrency;
const collectedFailures: string[] = [];
const diagnostics = yield* Effect.tryPromise({
const oxlintDiagnostics = yield* Effect.tryPromise({
try: () =>
runOxlint({
rootDirectory: input.rootDirectory,
Expand All @@ -130,6 +144,15 @@ export class Linter extends Context.Service<
}),
catch: ensureReactDoctorError,
});
const diagnostics =
input.includePaths === undefined
? [
...oxlintDiagnostics,
...checkSecurityPosture(input.rootDirectory).filter((diagnostic) =>
isSecurityPostureDiagnosticEnabled(diagnostic, input.ignoredTags),
),
]
: oxlintDiagnostics;
Comment thread
cursor[bot] marked this conversation as resolved.
Outdated
if (collectedFailures.length > 0) {
yield* Ref.update(partialFailures, (existing) => [...existing, ...collectedFailures]);
}
Expand Down
Loading
Loading