Skip to content

v3.39.0: fewer false positives, Effect schema option, banned-import allowNames

Choose a tag to compare

@BartWaardenburg BartWaardenburg released this 03 Nov 20:43
· 217 commits to main since this release
Immutable release. Only release title and notes can be modified.
v3.39.0
a6956af

Features

  • banned-import rules accept allowNames. A rule can ban a package and keep some of its exports, for example { "kind": "banned-import", "specifiers": ["three"], "allowNames": ["Vector3", "Quaternion"] }. Each entry is an exact imported name, and default allows a default import. A namespace import, a star re-export and a side-effect import always report. A finding of such a rule has matched set to <specifier>: <binding>, for example three: Mesh. Adding allowNames changes the baseline keys and finding ids of that rule, so re-save the baseline in the same commit. Thanks @Smrtnyk for the report. (#4072)

  • An explicit flag now says when its rule is off in config. Before, fallow dead-code --unused-deps with unused-dependencies at off printed No issues found. Now fallow prints a note on stderr that the flag did not check the rule. JSON gets the new workspace_diagnostics kind requested-rule-off, with rule and flag. SARIF, Markdown, MCP, the GitHub Action and the GitLab template also show the note. The exit code does not change, so set the rule to "error" to fail on a finding. Thanks @Smrtnyk for the report. (#4067)

  • The MCP tool inspect_target returns the cloud section and the compact text. Set cloud: true on a file target to run the same path as fallow inspect --file <path> --cloud. The CLI reads the key from FALLOW_API_KEY in the server environment, then the keychain, then the credentials file. The key is never a tool parameter. A status other than ok does not fail the call. Set format: "compact" to get the plain text of fallow inspect --format compact instead of the JSON bundle. (#4001)

  • fallow security reports more sinks. These shapes are now candidates when the value is not a literal:

    • iframe srcdoc as dangerous-html, through assignment, setAttribute, JSX srcDoc, Vue :srcdoc, Svelte srcdoc and Angular [srcdoc];
    • an assignment to src, text, textContent or innerText on a local bound to document.createElement("script") as code-injection, with the callee HTMLScriptElement.<property>. The same property on another element stays quiet;
    • a non-literal argument to Range.createContextualFragment() as dangerous-html.
  • The review brief reports security sink substitution. A change that removes a security sink and adds a different sink of the same class in the same file gives a security-check-removed signal. The signal appears in weakening[] of fallow audit --brief and fallow review. The classes are code execution, such as new Function and vm.runInNewContext, and HTML, such as innerHTML and dangerouslySetInnerHTML. A sink that only moves in the file, or goes away, gives no signal. Test files do not take part.

  • A graphql-config plugin keeps GraphQL schema and operation files used. The plugin reads the schema and documents globs of graphql.config.*, .graphqlrc, .graphqlrc.* or the graphql key in package.json. It marks the matching .graphql, .gql and .graphqls files as used, and the config file too. A glob is relative to the directory of the config file. A URL, a negated glob, and a glob for source files get no credit.

  • fallow audit shows the reason of an added fallow-ignore comment. An added-suppression finding now has a suppression object with kinds, reason and security_without_reason. Write the reason after --, for example // fallow-ignore-next-line security-sink -- the HTML is a constant template. When the comment hides security-sink or security-client-server-leak without a reason, security_without_reason is true. The signal does not change the severity, the verdict or the exit code. To find existing security suppressions without a reason, run fallow suppressions --format json and select reason_present: false.

  • effect.schemaExportsAsTypes reports unused Effect schemas as unused types. With { "effect": { "schemaExportsAsTypes": true } }, fallow reports an unused schema export under unused-types, not unused-exports. A schema is a class that extends a Schema class factory, or a subclass of such a class. A const whose value is a Schema constructor, member or pipe chain is also a schema. Schema must be an import from effect, effect/Schema or @effect/schema. fallow fix does not change these exports. The option is off by default. Older versions reject the effect key, so pair it with minimumVersion.

Changed

  • Runtime coverage totals are null for a partial capture. A Fallow Cloud partial capture (capturePolicy: positive_only) records only the functions that ran. Now summary.functions_tracked, summary.functions_unhit and summary.coverage_percent are null for such a capture. summary.functions_untracked now also counts the functions without a call. The new summary.capture_policy field is positive_only. The human output no longer prints an unhit count for a partial capture. A full capture gives the same numbers as before. See the upgrade notes for the schema versions.

  • The secret-leak client-server-leak finding now has category: "client-server-leak". Every security finding now names a category, so you can group candidates on category alone. The rule id, the finding_id and the SARIF fingerprint do not change.

  • Production mode leaves out user support entries. In a production run, a file that only a user framework[] entry with an explicit "entryPointRole": "support" matches is now like a test file. It is not an entry point, and its imports credit nothing. An export, a file or a dependency that only a dev tool script uses is now reported. A file that a runtime root also seeds stays a runtime root. (#4068)

Bug fixes

  • fallow dead-code no longer reports generateStaticParams in a Next.js metadata route as an unused export. Fallow now credits it on every metadata route, such as an opengraph-image, twitter-image, icon, apple-icon or sitemap route under a dynamic segment. Fallow also credits the route segment config exports, such as dynamic and revalidate. dynamicParams and unrelated helper exports stay reportable. Thanks @Vladimir-Pirotskiy for the patch. (#4038)

  • fallow mutation keeps the root of the selected Vitest config. Before, the companion replaced the config root with the project root, so a config with its own root could list no test files. The run then stopped with test-files-not-discovered. Now the companion starts Vitest from the project root without the override. A config without root behaves as before. (#4039)

  • fallow mutation keeps the test error of a test that fails in a dry run. Before, baseline_failures[].message had only a fixed reason or no message, so the assertion and the stack were lost. Now the Vitest, Node test and Bun test companions send failure_kind and message for each failed dry-run test. The human footer shows the reason on the Message: line and the error on the Error: line. The run stays partial. (#4030)

  • fallow mutation finds the runtime on PATH and skips files that cannot run. A name without a path separator, such as node in --runtime node, is now a PATH lookup, as in a shell. A path with a separator, such as ./bin/node, stays relative to the working directory. On Unix, a file without an execute bit early on PATH no longer hides a real node or bun later on PATH.

  • fallow dead-code credits alias replacement files in more config shapes. Fallow reported some stub files as unused files, or, with includeEntryExports, reported their exports as unused. Fallow now credits the replacement file in these cases:

    • a tsconfig paths or baseUrl maps the alias key to another existing file;
    • a top-level const or let holds the replacement as path.resolve(...), path.join(...), fileURLToPath(...) or new URL(...).pathname.

    This applies to Vite, Vitest and webpack aliases. Vitest projects entries and vitest.workspace arrays also read these constants. Thanks @Smrtnyk for the report of the tsconfig case. (#4035)

  • fallow dead-code no longer reports the default export of a local Oxlint jsPlugins directory whose main points at build output. Fallow now maps a built file, such as ./dist/src/index.js, back to its source file, such as src/index.ts. The rootDir and outDir options of tsconfig.json apply. Other exports of the source file stay reportable. Thanks @Smrtnyk for the report. (#4034)

  • fallow dead-code credits a member access through a local that holds the result of a method called on this. Code such as const old = this.current(); old?.deactivate?.(); now credits Tool.deactivate. The fix also covers this.current?.(), a #-private method, and a method after the call site. A cast, a call with arguments, and a call in a plain function give no credit. Thanks @Smrtnyk for the report. (#4033)

  • fallow dead-code reports a module that wraps a same-named import as a duplicate export again. A module that imports encode under an alias and declares its own encode is a wrapper. Since 3.35.0 the duplicate-exports rule dropped such a module. An overlay module that also declares export * from the same module stays dropped. To keep a wrapper on purpose, add // fallow-ignore-next-line duplicate-export above its export, or add the name to ignoreExports. (#4024)

  • absent-component-props reports in two more cases. A method signature such as onClose(): void in a props type now counts as a prop, so the component no longer abstains. An overloaded method is optional only when every overload is optional. A DOM query, innerHTML or an unsafe Lit directive in the calling module now makes only Lit elements and imported Svelte components abstain. (#4022, #4023)

  • Fallow Cloud keychain calls have time limits, and two logins no longer lose a key. The keychain read of fallow inspect --cloud now has a time limit of 1 second. At the limit, fallow reads the credentials file and reports no_key when no key is stored. In fallow cloud login and fallow cloud logout, each keychain call has a limit of 60 seconds. Fallow now holds a lock on credentials.lock while it changes the credentials file. Two logins at the same time for two API URLs then keep both keys. When the credentials file does not parse, the command stops with a message that names the file, and fallow does not change the file. (#4003, #4004)

  • Large or deep inputs no longer stall fallow or exhaust memory.

    • Fallow now expands at most 64 directory arguments of node --test, bun test or deno test for each command in a package script. A chain of command wrappers stops after 8 wrappers.
    • Fallow now rejects a CODEOWNERS pattern or a config glob that is longer than 512 bytes, with an error that names the limit.
    • Fallow now treats a collection value for a pnpm override in pnpm-workspace.yaml as a missing value. Fallow reports it as misconfigured, as for npm overrides and bun resolutions.
  • fallow type-aware status names the tsrs backend in its human output. With FALLOW_TYPE_AWARE_BACKEND=tsrs, the line now says tsrs <version>, not TypeScript <version>. The line for the default backend and the JSON output do not change. Thanks @Smrtnyk for the report. (#4066)

  • fallow dead-code credits a package or file that code loads through an inline createRequire(import.meta.url)('pkg') call. Fallow now reads every spelling of a require function based on the file itself. Examples are .resolve on the call, a binding with any name, and an aliased createRequire import. These forms now give the same findings as the named require form. A call with another base, such as createRequire(process.cwd()), credits nothing. Thanks @Smrtnyk for the report. (#4062)

  • fallow baselines prune no longer removes valid entries of a baseline saved in another mode. Prune now stops on such a file with reason_code: "scope-mismatch", exits 2, and prints the command to use. baselines prune now accepts --production and honors --include-entry-exports. Prune also stops on a file with no record of a mode that the run uses. To clear that refusal, review the findings and save the file again in that mode. (#4070)

  • --fail-on-stale-baseline now judges a dead-code baseline only when it is saved in the same mode as the run. A same-mode run with a stale entry can now exit 1 where it exited 0. To clear it, run the prune command that the failure prints and commit the result. A run in another mode now exits 0, and the gate status is skipped. change_scoped and scope_reasons now name what stops the run from judging the file. Thanks @Smrtnyk for the report. (#4070)

  • An overrides rule that cannot change a result now reports a diagnostic. The new override-rule-ineffective entry in workspace_diagnostics[] has rule, files and reason. reason: project-level covers project-level rules, such as dev-dependencies-in-production. reason: base-rule-off covers an override for a rule that is off at top level. Findings and exit codes do not change. The old stderr warnings for duplicate-exports and re-export-cycle moved to this entry. Thanks @Smrtnyk for the report. (#4065)

  • The suppression footer no longer prints 0 stale suppressions, and the status line names every finding type. The suppression footer now names a stale count only when the count is above zero. Thanks @Smrtnyk for the report. (#4063) The status line and the --group-by headers now name every finding type that the run counts, for example ⚠ 1 unused catalog entry (0.02s). Before, some runs printed an empty label. JSON output does not change.

  • banned-call rules and boundaries.calls.forbidden now match new expressions. A Date pattern now reports new Date() too, with matched or callee set to new Date. Findings for the call form keep their id, fingerprint and baseline key. A saved baseline does not cover the new findings. To preview them, run fallow rule-pack test. To accept them, set the rule severity to warn or save the baseline again. banned-effect does not change. Thanks @Smrtnyk for the report. (#4071)

  • Config load now warns about a plugin that can never activate. A framework[] entry or a plugin file with neither detection nor enablers now gives a warning on stderr. The warning tells you to add one of them, then run fallow plugin-check. Fallow prints the warning also with --quiet, --ci or FALLOW_QUIET. Findings, stdout and exit codes do not change. Thanks @Smrtnyk for the report. (#4068)

  • fallow dead-code --help now names every kind that --unused-deps selects. The help now lists unused, dev and optional dependencies, plus type-only, test-only and dev-in-production dependencies. Findings, JSON output and exit codes do not change. Thanks @Smrtnyk for the report. (#4064)

  • Findings in .vue, .svelte, .astro and .mdx files now report the line of the call. Before, banned-call, banned-effect, boundaries.calls.forbidden, unprovided-inject and misplaced-directive findings had a wrong line and column. A fallow-ignore-next-line comment above the call now applies. In these files, taint trace sources now have the correct line, and a next/dynamic import with ssr: false counts as client-only for client-server-leak. Policy baseline keys do not change, but line-based CodeClimate fingerprints can change.

  • ignore-dependencies-entry-redundant no longer tells you to remove an entry that a --production run needs. A normal run no longer reports an ignoreDependencies entry as redundant when the entry hides a type-only-dependencies or unused-dependencies finding of a --production run. Findings and exit codes do not change.

  • fallow security no longer misses sinks behind evasion syntax, on complex objects, or from named vm imports. Examples of new candidates:

    • (0, eval)(code), globalThis.eval(code), const e = eval; e(code), Function(code) and setTimeout("run(" + id + ")");
    • el["inner" + "HTML"] = html, and a JSX spread that includes dangerouslySetInnerHTML;
    • innerHTML on this.node, el!, (el as T), an optional chain or a call result;
    • runInNewContext, runInThisContext and runInContext from a named import of vm or node:vm, or from require("vm");
    • new Script(code) and compileFunction from a named import of vm or from require("vm");
    • new Function(...names, body), and el.setAttribute("src", url) on a <script> element.

    fallow security blind-spots now lists an assignment whose property name joins a string with a dynamic part, such as el[prefix + "HTML"] = html.

  • fallow security reports fewer source-free candidates in library, build and CLI code. On eight public projects, most path-traversal, dynamic-regex and header-injection candidates had no untrusted input near them. RegExp(x), res.setHeader and res.writeHead now need a value that traces to untrusted input. path.join and path.resolve now need untrusted input in the argument, the same module, or every direct importer. req.url, req.originalUrl and req.headers are now untrusted input. process.env, import.meta.env, test files, story files and tooling-config files no longer start an untrusted-source trace.

  • fallow dead-code credits more override members of a class with a package base call. A class such as class LookupError extends Schema.TaggedError<LookupError>()("LookupError", { ...sharedFields }) no longer reports override get message() as unused. The same applies when the call takes a module-scope const with an object literal initializer. A mixin call that takes a project class still gives no credit.

  • A /// <reference types="pkg" /> directive now uses the package pkg. Fallow records the directive as a type-only import, so the rules for import type apply. In production mode, a package in dependencies that only such directives use is reported as a type-only dependency. Fallow ignores a directive after code, a types value that starts with . or /, and types="node".

  • The pnpm plugin activates from more files. The plugin now activates from pnpm-workspace.yaml, pnpm-lock.yaml, a pnpmfile, and a packageManager field that names pnpm. fallow dead-code no longer reports .pnpmfile.cjs or .pnpmfile.mjs as unused. The plugin also marks the files that the pnpmfile setting names as used.

  • A React Native platform file that imports from its base file no longer reports a re-export self-loop. When a platform extension makes a specifier resolve to the importing file, fallow resolves it again without platform extensions. export type { ButtonProps } from "./Button" in Button.ios.tsx then resolves to Button.tsx. A specifier that names the platform, such as ./Button.ios, keeps its target.

  • fallow dead-code credits a class member that a runtime check reads by name. Code such as "displayDetail" in cause or Reflect.has(cause, "displayDetail") now credits the member. The credit applies to classes in the same module and its direct import neighbors. A dynamic key, Object.hasOwn, hasOwnProperty and a check in an unreachable file give no credit.

  • fallow migrate translates more settings. The migration now translates settings in a workspaces map, with the workspace path in front of each pattern. It removes the trailing ! production marker from entry patterns, so the entry is no longer lost. It warns about a section that mixes marked and unmarked patterns, and about a negated entry pattern. It now also translates the root includeEntryExports and ignoreIssues settings.

  • The VS Code extension uses the project fallow-type-aware companion. When the extension runs Fallow from the project node_modules, it uses the fallow-type-aware package of the same install if the two versions are equal. This works with the pnpm store layout too. A FALLOW_TYPE_AWARE_BIN that you set still wins. (#4069)

Security

  • fallow-similar-code no longer ships zip 8.6.0. The sidecar now uses candle-core, candle-nn and candle-transformers 0.11.1, and the lockfile selects zip 9.3.0. This removes RUSTSEC-2026-0429 and the allowlist entries for RUSTSEC-2026-0367, RUSTSEC-2026-0368 and RUSTSEC-2026-0369. The sidecar never opened a ZIP archive. It also moves from tokenizers 0.22.2 to 0.23.2. The embedding semantics stay at v2, so the vector cache stays valid.

Upgrade notes

  • Partial-capture coverage totals are nullable, so some schema versions change. fallow health moves to 13, fallow audit to 14, the bare combined output to 15, fallow audit --brief to 14, and fallow coverage analyze to "3". Update scripts that check these versions or read summary.coverage_percent as a number.

  • Adding allowNames to a banned-import rule changes its baseline keys and finding ids. Re-save the baseline in the same commit. --fail-on-baseline-growth reports the new keys. (#4072)

  • banned-call and boundaries.calls.forbidden can report new new expression findings. A rule with severity error can now fail the gate. Run fallow rule-pack test to preview them, then set the rule to warn or save the baseline again. (#4071)

  • duplicate-exports reports wrapper modules again. This rule is error by default, so CI can show new findings. Add a fallow-ignore-next-line duplicate-export comment or an ignoreExports entry for an intended wrapper. (#4024)

  • fallow baselines prune can exit 2 with reason_code: "scope-mismatch". prune --check can exit 2 when the config sets production mode and the baseline file has no record of that mode. Review the findings and save the baseline again in that mode. (#4070)

  • --fail-on-stale-baseline can change its exit code. A same-mode run with a stale entry can exit 1, and a run in another mode now exits 0 with the gate skipped. Run the prune command that the failure prints and commit the result. (#4070)

  • New workspace_diagnostics kinds appear in JSON. requested-rule-off has rule and flag, and override-rule-ineffective has rule, files and reason. The stderr warnings for duplicate-exports and re-export-cycle moved to override-rule-ineffective. Update scripts that parse these entries or the stderr text. (#4067, #4065)

  • Security findings change. Every security finding now has a category, and client-server-leak uses category: "client-server-leak". fallow security reports new sink candidates and drops many source-free candidates. Expect new code-injection and dangerous-html candidates, and fewer path-traversal, dynamic-regex and header-injection candidates. Code that logs or copies req.url, req.originalUrl or req.headers can show a new secret-pii-log or mass-assignment candidate.

  • Line-based CodeClimate fingerprints can change for findings in .vue, .svelte, .astro and .mdx files. Policy baseline keys do not change.

  • A CODEOWNERS pattern or a config glob longer than 512 bytes is now an error. Fallow rejects such a pattern with an error that names the limit.

  • The first run after the upgrade parses every file again. The extraction cache version changes, and the graph cache rebuilds. No action is needed.

  • To use effect.schemaExportsAsTypes, pair it with minimumVersion. Older versions reject the effect key.

  • A production run can report new dead code. A project that declares tool scripts as user entries with "entryPointRole": "support" can see new unused-export, unused-file and unused-dependency findings with --production. The production health vital signs that come from dead code follow the new result. (#4068)

Full Changelog: v3.38.1...v3.39.0