@sarj/eslint-plugin 19.12.0 → 19.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/catalog.js +302 -9
- package/dist/{chunk-7SYRLUE7.js → chunk-SIHWYU24.js} +353 -42
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/dist/catalog.js
CHANGED
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
publicDocumentation,
|
|
3
3
|
ruleSpec,
|
|
4
4
|
src_default
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-SIHWYU24.js";
|
|
6
6
|
|
|
7
7
|
// src/docs/directive-disable-enable-pair.ts
|
|
8
8
|
var DIRECTIVE_DISABLE_ENABLE_PAIR_DOCUMENTATION = {
|
|
@@ -2523,8 +2523,13 @@ var NO_RAW_ENV_DOCUMENTATION = {
|
|
|
2523
2523
|
remediation: "Define a Zod settings schema, call SettingsSchema.parse(process.env) once at startup, and export or inject that single parsed settings object. Use Pydantic/Pydantic Settings for the equivalent Python boundary. Required values must fail before remote effects; preserve independent security policy and existing optional defaults.",
|
|
2524
2524
|
category: "correctness",
|
|
2525
2525
|
limitations: [
|
|
2526
|
-
"Host markers, assignment targets, tests, ordinary scripts and build config retain their policy exemptions. Boundary-named modules, including env.mjs and settings.ts, are checked.
|
|
2527
|
-
"
|
|
2526
|
+
"Host markers, assignment targets, tests, ordinary scripts and build config retain their policy exemptions. Boundary-named modules, including env.mjs and settings.ts, are checked. Native named process env imports and simple const { env } = process aliases follow the same read policy; foreign, type-only, shadowed and mutable sources are not inferred.",
|
|
2527
|
+
"Each raw value must reach a parse/safeParse argument or supported environment loader, directly or through stable nonexported const aliases whose every use reaches validation. Directly awaited parseAsync/safeParseAsync calls also count; unawaited promises or later await-through-alias flows do not. Conditional tests and logical-and conditions are control inputs, not the parsed result; value arms and existing nullish/logical-or defaults remain supported.",
|
|
2528
|
+
"Simple nonexported const object patterns may select static keys into plain identifier bindings when every ordinary read of each selected field reaches validation. Type-only references are ignored. Native whole-environment host marker keys retain their exemptions, including through stable aliases; an arbitrary raw field with a marker-shaped property does not. Unused, unparsed, escaping or later-written ordinary fields fail the proof. Opaque computed keys, defaults, rest and nested patterns remain unsupported.",
|
|
2529
|
+
"Renamed value imports of createEnv from @t3-oss/env-core or @t3-oss/env-nextjs retain loader ownership. Existing unresolved project parse/safeParse and legacy createEnv protocols remain accepted; this is not a proof of their implementation. A narrowly proven private identity adapter only forwards its input: that input must still reach an actual parser.",
|
|
2530
|
+
"Identity-adapter detection is limited to an explicit module, no namespace or runtime eval reference, one stable nonexported binding and a synchronous function whose sole plain identifier parameter is returned unchanged. An object may contain only one or two unique own parse/safeParse identity methods, with every ordinary receiver reference a direct method call. Exported/mutable/escaped receivers, spreads, getters, duplicate/opaque keys, extra methods, imported loaders, real Zod wrappers and optional/awaited adapter-result ambiguity retain the existing protocol. The proof stops after 128 binding references, 512 module references or 32 enclosing AST parents; it adds no whole-program library-provenance requirement.",
|
|
2531
|
+
"Only unshadowed Number/String/Boolean/BigInt calls and one-argument native JSON decoding are traversed as preprocessing. JSON.parse alone does not validate settings: its decoded result must itself reach the parser. JSON revivers and arbitrary calls cannot consume raw settings under this preprocessing exemption.",
|
|
2532
|
+
"This is bounded same-file syntax analysis (24 flow steps, 128 selected fields per pattern, 128 reads per binding and 512 total expression/field visits per raw read; whole-environment host-marker provenance follows at most 16 aliases); flows exceeding these budgets are unreported. It does not prove a parser's implementation, cross-file schemas, schema completeness, safeParse failure handling or a project-wide singleton. Handle safeParse failure before exposing result.data; prefer throwing parse at startup. Nested component settings and separate server/client runtime boundaries remain valid.",
|
|
2528
2533
|
"Assignment-target exemptions also include compound writes that read the previous value. Whole-environment spreads outside boundary modules retain the existing forwarding exemption; a spread inside a boundary must also reach its parser. No automatic rewrite."
|
|
2529
2534
|
],
|
|
2530
2535
|
examples: [
|
|
@@ -2559,6 +2564,190 @@ var NO_RAW_ENV_DOCUMENTATION = {
|
|
|
2559
2564
|
focusPath: "src/env.ts",
|
|
2560
2565
|
expectedCount: 1,
|
|
2561
2566
|
public: true
|
|
2567
|
+
},
|
|
2568
|
+
{
|
|
2569
|
+
id: "native-env-import-read",
|
|
2570
|
+
scenarioId: "native-env-import",
|
|
2571
|
+
title: "Native env import aliases still need validation",
|
|
2572
|
+
outcome: "match",
|
|
2573
|
+
files: [
|
|
2574
|
+
{
|
|
2575
|
+
path: "src/database.ts",
|
|
2576
|
+
source: 'import { env as raw } from "node:process";\nexport const databaseUrl = raw.DATABASE_URL;'
|
|
2577
|
+
}
|
|
2578
|
+
],
|
|
2579
|
+
focusPath: "src/database.ts",
|
|
2580
|
+
expectedCount: 1,
|
|
2581
|
+
public: true
|
|
2582
|
+
},
|
|
2583
|
+
{
|
|
2584
|
+
id: "native-env-import-validated",
|
|
2585
|
+
scenarioId: "native-env-import",
|
|
2586
|
+
title: "Parse the imported environment once and share settings",
|
|
2587
|
+
outcome: "no-match",
|
|
2588
|
+
files: [
|
|
2589
|
+
{
|
|
2590
|
+
path: "src/database.ts",
|
|
2591
|
+
source: 'import { settings } from "./env.js";\nexport const databaseUrl = settings.DATABASE_URL;'
|
|
2592
|
+
},
|
|
2593
|
+
{
|
|
2594
|
+
path: "src/env.ts",
|
|
2595
|
+
source: 'import { env as raw } from "node:process";\nimport { z } from "zod";\nconst SettingsSchema = z.object({ DATABASE_URL: z.url() });\nexport const settings = SettingsSchema.parse(raw);'
|
|
2596
|
+
}
|
|
2597
|
+
],
|
|
2598
|
+
focusPath: "src/env.ts",
|
|
2599
|
+
expectedCount: 0,
|
|
2600
|
+
public: true
|
|
2601
|
+
},
|
|
2602
|
+
{
|
|
2603
|
+
id: "json-decoding-without-validation",
|
|
2604
|
+
scenarioId: "json-settings",
|
|
2605
|
+
title: "JSON decoding does not validate the settings contract",
|
|
2606
|
+
outcome: "match",
|
|
2607
|
+
files: [
|
|
2608
|
+
{
|
|
2609
|
+
path: "src/env.ts",
|
|
2610
|
+
source: "export const settings = JSON.parse(process.env.SETTINGS_JSON);"
|
|
2611
|
+
}
|
|
2612
|
+
],
|
|
2613
|
+
focusPath: "src/env.ts",
|
|
2614
|
+
expectedCount: 1,
|
|
2615
|
+
public: true
|
|
2616
|
+
},
|
|
2617
|
+
{
|
|
2618
|
+
id: "json-decoding-then-validation",
|
|
2619
|
+
scenarioId: "json-settings",
|
|
2620
|
+
title: "Validate decoded environment JSON with the actual schema",
|
|
2621
|
+
outcome: "no-match",
|
|
2622
|
+
files: [
|
|
2623
|
+
{
|
|
2624
|
+
path: "src/env.ts",
|
|
2625
|
+
source: 'import { z } from "zod";\nconst SettingsSchema = z.object({ API_ORIGIN: z.url() });\nexport const settings = SettingsSchema.parse(JSON.parse(process.env.SETTINGS_JSON));'
|
|
2626
|
+
}
|
|
2627
|
+
],
|
|
2628
|
+
focusPath: "src/env.ts",
|
|
2629
|
+
expectedCount: 0,
|
|
2630
|
+
public: true
|
|
2631
|
+
},
|
|
2632
|
+
{
|
|
2633
|
+
id: "raw-input-controls-effects",
|
|
2634
|
+
scenarioId: "conditional-settings",
|
|
2635
|
+
title: "A raw conditional input is not the value reaching the parser",
|
|
2636
|
+
outcome: "match",
|
|
2637
|
+
files: [
|
|
2638
|
+
{
|
|
2639
|
+
path: "src/env.ts",
|
|
2640
|
+
source: 'import { z } from "zod";\nconst SettingsSchema = z.object({ API_ORIGIN: z.url() });\nexport const settings = SettingsSchema.parse(process.env.LOAD_REMOTE ? fetch("https://example.test/config") : {});'
|
|
2641
|
+
}
|
|
2642
|
+
],
|
|
2643
|
+
focusPath: "src/env.ts",
|
|
2644
|
+
expectedCount: 1,
|
|
2645
|
+
public: true
|
|
2646
|
+
},
|
|
2647
|
+
{
|
|
2648
|
+
id: "validated-input-controls-effects",
|
|
2649
|
+
scenarioId: "conditional-settings",
|
|
2650
|
+
title: "Parse the control setting before remote effects",
|
|
2651
|
+
outcome: "no-match",
|
|
2652
|
+
files: [
|
|
2653
|
+
{
|
|
2654
|
+
path: "src/env.ts",
|
|
2655
|
+
source: 'import { z } from "zod";\nconst SettingsSchema = z.object({ LOAD_REMOTE: z.enum(["0", "1"]).transform(value => value === "1") });\nexport const settings = SettingsSchema.parse(process.env);\nif (settings.LOAD_REMOTE) await fetch("https://example.test/config");'
|
|
2656
|
+
}
|
|
2657
|
+
],
|
|
2658
|
+
focusPath: "src/env.ts",
|
|
2659
|
+
expectedCount: 0,
|
|
2660
|
+
public: true
|
|
2661
|
+
},
|
|
2662
|
+
{
|
|
2663
|
+
id: "async-settings-promise",
|
|
2664
|
+
scenarioId: "async-settings",
|
|
2665
|
+
title: "An unfinished parser promise is not startup settings",
|
|
2666
|
+
outcome: "match",
|
|
2667
|
+
files: [
|
|
2668
|
+
{
|
|
2669
|
+
path: "src/env.ts",
|
|
2670
|
+
source: 'import { z } from "zod";\nconst SettingsSchema = z.object({ API_KEY: z.string().min(1) });\nexport const settings = SettingsSchema.parseAsync(process.env);'
|
|
2671
|
+
}
|
|
2672
|
+
],
|
|
2673
|
+
focusPath: "src/env.ts",
|
|
2674
|
+
expectedCount: 1,
|
|
2675
|
+
public: true
|
|
2676
|
+
},
|
|
2677
|
+
{
|
|
2678
|
+
id: "async-settings-awaited",
|
|
2679
|
+
scenarioId: "async-settings",
|
|
2680
|
+
title: "Await parsing before exporting the shared settings object",
|
|
2681
|
+
outcome: "no-match",
|
|
2682
|
+
files: [
|
|
2683
|
+
{
|
|
2684
|
+
path: "src/env.ts",
|
|
2685
|
+
source: 'import { z } from "zod";\nconst SettingsSchema = z.object({ API_KEY: z.string().min(1) });\nexport const settings = await SettingsSchema.parseAsync(process.env);'
|
|
2686
|
+
}
|
|
2687
|
+
],
|
|
2688
|
+
focusPath: "src/env.ts",
|
|
2689
|
+
expectedCount: 0,
|
|
2690
|
+
public: true
|
|
2691
|
+
},
|
|
2692
|
+
{
|
|
2693
|
+
id: "selected-fields-raw-escape",
|
|
2694
|
+
scenarioId: "selected-settings-fields",
|
|
2695
|
+
title: "Parsing one selected field does not validate another raw export",
|
|
2696
|
+
outcome: "match",
|
|
2697
|
+
files: [
|
|
2698
|
+
{
|
|
2699
|
+
path: "src/env.ts",
|
|
2700
|
+
source: 'import { env as raw } from "node:process";\nimport { z } from "zod";\nconst SettingsSchema = z.object({ API_KEY: z.string().min(1) });\nconst { API_KEY, API_ORIGIN } = raw;\nexport const settings = SettingsSchema.parse({ API_KEY });\nexport const unparsedOrigin = API_ORIGIN;'
|
|
2701
|
+
}
|
|
2702
|
+
],
|
|
2703
|
+
focusPath: "src/env.ts",
|
|
2704
|
+
expectedCount: 1,
|
|
2705
|
+
public: true
|
|
2706
|
+
},
|
|
2707
|
+
{
|
|
2708
|
+
id: "selected-fields-validated",
|
|
2709
|
+
scenarioId: "selected-settings-fields",
|
|
2710
|
+
title: "Validate every selected ordinary field and reuse the parsed result",
|
|
2711
|
+
outcome: "no-match",
|
|
2712
|
+
files: [
|
|
2713
|
+
{
|
|
2714
|
+
path: "src/env.ts",
|
|
2715
|
+
source: 'import { env as raw } from "node:process";\nimport { z } from "zod";\nconst SettingsSchema = z.object({ API_KEY: z.string().min(1), API_ORIGIN: z.url() });\nconst { API_KEY, API_ORIGIN } = raw;\nexport const settings = SettingsSchema.parse({ API_KEY, API_ORIGIN });'
|
|
2716
|
+
}
|
|
2717
|
+
],
|
|
2718
|
+
focusPath: "src/env.ts",
|
|
2719
|
+
expectedCount: 0,
|
|
2720
|
+
public: true
|
|
2721
|
+
},
|
|
2722
|
+
{
|
|
2723
|
+
id: "identity-loader-without-parser",
|
|
2724
|
+
scenarioId: "closed-identity-adapter",
|
|
2725
|
+
title: "A local identity adapter does not validate environment input",
|
|
2726
|
+
outcome: "match",
|
|
2727
|
+
files: [
|
|
2728
|
+
{
|
|
2729
|
+
path: "src/env.ts",
|
|
2730
|
+
source: "const createEnv = input => input;\nexport const settings = createEnv(process.env);"
|
|
2731
|
+
}
|
|
2732
|
+
],
|
|
2733
|
+
focusPath: "src/env.ts",
|
|
2734
|
+
expectedCount: 1,
|
|
2735
|
+
public: true
|
|
2736
|
+
},
|
|
2737
|
+
{
|
|
2738
|
+
id: "identity-loader-before-real-parser",
|
|
2739
|
+
scenarioId: "closed-identity-adapter",
|
|
2740
|
+
title: "An identity adapter can forward into the actual Zod parser",
|
|
2741
|
+
outcome: "no-match",
|
|
2742
|
+
files: [
|
|
2743
|
+
{
|
|
2744
|
+
path: "src/env.ts",
|
|
2745
|
+
source: 'import { z } from "zod";\nconst SettingsSchema = z.object({ API_KEY: z.string().min(1) });\nconst createEnv = input => input;\nexport const settings = SettingsSchema.parse(createEnv(process.env));'
|
|
2746
|
+
}
|
|
2747
|
+
],
|
|
2748
|
+
focusPath: "src/env.ts",
|
|
2749
|
+
expectedCount: 0,
|
|
2750
|
+
public: true
|
|
2562
2751
|
}
|
|
2563
2752
|
]
|
|
2564
2753
|
};
|
|
@@ -3205,12 +3394,13 @@ var NO_SENTINEL_RETURN_ON_CATCH_DOCUMENTATION = {
|
|
|
3205
3394
|
|
|
3206
3395
|
// src/docs/no-server-env-in-client-component.ts
|
|
3207
3396
|
var NO_SERVER_ENV_IN_CLIENT_COMPONENT_DOCUMENTATION = {
|
|
3208
|
-
summary: "server-only environment settings imported by a client
|
|
3209
|
-
rationale: "Next.js client modules run in the browser, where server-only environment values are unavailable; importing a server settings module can produce undefined configuration or bundle a secret-bearing module into the client graph.",
|
|
3210
|
-
remediation: "Pass an explicitly public value from a Server Component, or import it from a separately validated client-settings module backed only by NEXT_PUBLIC_* values.",
|
|
3397
|
+
summary: "server-only environment settings imported or re-exported by a client module",
|
|
3398
|
+
rationale: "Next.js client modules run in the browser, where server-only environment values are unavailable; importing or re-exporting a server settings module can produce undefined configuration or bundle a secret-bearing module into the client graph.",
|
|
3399
|
+
remediation: "Pass an explicitly public value from a Server Component, or import/re-export it from a separately validated client-settings module backed only by NEXT_PUBLIC_* values.",
|
|
3211
3400
|
category: "correctness",
|
|
3212
3401
|
limitations: [
|
|
3213
|
-
"Only static
|
|
3402
|
+
"Only direct static runtime imports and re-exports in files with a top-level 'use client' directive targeting the existing server-env/server-settings module protocol are checked. Type-only declarations/specifiers, local exports without a source and ordinary server modules are excluded.",
|
|
3403
|
+
"Dynamic imports, require calls and transitive barrel graphs are not followed. Public client-settings modules and unrelated module paths are not classified as server-only."
|
|
3214
3404
|
],
|
|
3215
3405
|
examples: [
|
|
3216
3406
|
{
|
|
@@ -3240,6 +3430,44 @@ var NO_SERVER_ENV_IN_CLIENT_COMPONENT_DOCUMENTATION = {
|
|
|
3240
3430
|
focusPath: "status-card.tsx",
|
|
3241
3431
|
expectedCount: 1,
|
|
3242
3432
|
public: true
|
|
3433
|
+
},
|
|
3434
|
+
{
|
|
3435
|
+
id: "public-client-settings-re-export",
|
|
3436
|
+
scenarioId: "client-re-export",
|
|
3437
|
+
title: "Forward public settings from the client boundary",
|
|
3438
|
+
outcome: "no-match",
|
|
3439
|
+
files: [
|
|
3440
|
+
{
|
|
3441
|
+
path: "client-barrel.tsx",
|
|
3442
|
+
source: "'use client';\nexport { CLIENT_SETTINGS } from './client-settings.js';\n"
|
|
3443
|
+
},
|
|
3444
|
+
{
|
|
3445
|
+
path: "client-settings.ts",
|
|
3446
|
+
source: "import { z } from 'zod';\nconst SettingsSchema = z.object({ apiOrigin: z.url() });\nexport const CLIENT_SETTINGS = SettingsSchema.parse({ apiOrigin: process.env.NEXT_PUBLIC_API_ORIGIN });\n"
|
|
3447
|
+
}
|
|
3448
|
+
],
|
|
3449
|
+
focusPath: "client-barrel.tsx",
|
|
3450
|
+
expectedCount: 0,
|
|
3451
|
+
public: true
|
|
3452
|
+
},
|
|
3453
|
+
{
|
|
3454
|
+
id: "server-settings-client-re-export",
|
|
3455
|
+
scenarioId: "client-re-export",
|
|
3456
|
+
title: "A client barrel cannot forward server settings",
|
|
3457
|
+
outcome: "match",
|
|
3458
|
+
files: [
|
|
3459
|
+
{
|
|
3460
|
+
path: "client-barrel.tsx",
|
|
3461
|
+
source: "'use client';\nexport { SERVER_SETTINGS } from './server-settings.js';\n"
|
|
3462
|
+
},
|
|
3463
|
+
{
|
|
3464
|
+
path: "server-settings.ts",
|
|
3465
|
+
source: "import { z } from 'zod';\nconst SettingsSchema = z.object({ apiOrigin: z.url() });\nexport const SERVER_SETTINGS = SettingsSchema.parse({ apiOrigin: process.env.API_ORIGIN });\n"
|
|
3466
|
+
}
|
|
3467
|
+
],
|
|
3468
|
+
focusPath: "client-barrel.tsx",
|
|
3469
|
+
expectedCount: 1,
|
|
3470
|
+
public: true
|
|
3243
3471
|
}
|
|
3244
3472
|
]
|
|
3245
3473
|
};
|
|
@@ -4165,7 +4393,7 @@ var PREFER_CODED_ERROR_RESULT_DOCUMENTATION = {
|
|
|
4165
4393
|
"Broad string and unknown error channels, unshadowed native Error-family types and open string/unknown code fields receive review. Explicit Any stays with the existing no-explicit-any rule. Finite literal codes need no invented payload. Imported domain errors, validation-library errors, generic error parameters, ordinary formatters, legitimate absence and redacted failure states remain valid.",
|
|
4166
4394
|
"Local aliases, unmerged interfaces without inheritance and generic substitutions are bounded to 16 resolution levels, 64 union members and 256 traversal steps. Transparent aliases retain the proven neverthrow Result/ResultAsync error position, including renamed/namespace imports, generic defaults and unshadowed native Promise/PromiseLike wrappers. Imported local declarations, conditional/intersection/mapped types and unresolved contracts abstain. Optional discriminants, independent success/error payloads and partial-data failures are excluded.",
|
|
4167
4395
|
"Required code/kind/type/tag fields of unresolved or imported type defer to the type checker; a finite code does not prove that its cases or payloads are useful. Generic error parameters are retained rather than guessed. Ordinary string formatters, absence, validation-library errors and redacted failure states without a string payload are not flagged.",
|
|
4168
|
-
"A directly returned failure object, neverthrow err/errAsync call or new Err with an interpolated template or proven string concatenation receives review. Without a code, the diagnostic asks for a domain contract; with a literal code, it asks for consumer formatting. Literal-only compositions, including earlier stable local constants, remain valid codes. Nested plain error objects, expression-bodied arrows and
|
|
4396
|
+
"A directly returned failure object, neverthrow err/errAsync call or new Err with an interpolated template or proven string concatenation receives review. Without a code, the diagnostic asks for a domain contract; with a literal code, it asks for consumer formatting. Literal-only compositions, including earlier stable local constants, remain valid codes. Nested plain error objects, expression-bodied arrows and awaited returns are supported. Returned conditional branches are followed without visiting their conditions, call arguments or containers; logical/sequence expressions and arbitrary aliases abstain. The entire return expression abstains beyond 16 levels, 64 leaves or 256 traversal steps. Import identity and value-space shadowing are checked; spreads, accessors, computed keys, helper formatting calls and nonliteral codes abstain. This proves formatting ownership, not which context the domain must expose; presentation adapters remain a reviewed exception.",
|
|
4169
4397
|
"This warning requests domain review: an infrastructure adapter may intentionally retain unknown/Error, and a final presentation boundary may accept every failure and render one message. Neither proves a recoverable domain rejection. No fix invents error codes, fields, handlers or changes serialization, exception unwinding, transactions, retries or logging."
|
|
4170
4398
|
],
|
|
4171
4399
|
references: [
|
|
@@ -4173,6 +4401,36 @@ var PREFER_CODED_ERROR_RESULT_DOCUMENTATION = {
|
|
|
4173
4401
|
"https://github.com/supermacro/neverthrow"
|
|
4174
4402
|
],
|
|
4175
4403
|
examples: [
|
|
4404
|
+
{
|
|
4405
|
+
id: "conditional-message-failure",
|
|
4406
|
+
scenarioId: "conditional-result-producer",
|
|
4407
|
+
title: "A conditional return still needs a useful error contract",
|
|
4408
|
+
outcome: "match",
|
|
4409
|
+
files: [
|
|
4410
|
+
{
|
|
4411
|
+
path: "app/result.ts",
|
|
4412
|
+
source: "import { err, ok } from 'neverthrow'; export const save = (name: string, allowed: boolean) => allowed ? ok(name) : err(`Denied ${name}`);"
|
|
4413
|
+
}
|
|
4414
|
+
],
|
|
4415
|
+
focusPath: "app/result.ts",
|
|
4416
|
+
expectedCount: 1,
|
|
4417
|
+
public: true
|
|
4418
|
+
},
|
|
4419
|
+
{
|
|
4420
|
+
id: "conditional-coded-failure",
|
|
4421
|
+
scenarioId: "conditional-result-producer",
|
|
4422
|
+
title: "Preserve the code and context and let the consumer format",
|
|
4423
|
+
outcome: "no-match",
|
|
4424
|
+
files: [
|
|
4425
|
+
{
|
|
4426
|
+
path: "app/result.ts",
|
|
4427
|
+
source: "import { err, ok, type Result } from 'neverthrow'; type Denied = { code: 'denied'; name: string }; export function save(name: string, allowed: boolean): Result<string, Denied> { return allowed ? ok(name) : err({ code: 'denied', name }); } export function format(error: Denied): string { return `Denied ${error.name}`; }"
|
|
4428
|
+
}
|
|
4429
|
+
],
|
|
4430
|
+
focusPath: "app/result.ts",
|
|
4431
|
+
expectedCount: 0,
|
|
4432
|
+
public: true
|
|
4433
|
+
},
|
|
4176
4434
|
{
|
|
4177
4435
|
id: "aliased-library-message",
|
|
4178
4436
|
scenarioId: "aliased-library-error",
|
|
@@ -4515,7 +4773,8 @@ var PREFER_CONFIGURED_CLOUD_SCOPE_DOCUMENTATION = {
|
|
|
4515
4773
|
requiresTypeInformation: false,
|
|
4516
4774
|
limitations: [
|
|
4517
4775
|
"Initial coverage is canonical HTTPS Cloudflare v4 account-management URLs with a 32-character hexadecimal account segment sent to an unshadowed fetch, stable const alias, or fetch-default parameter. Other providers, SDKs, arbitrary endpoints, Worker names and resource IDs require separate evidence.",
|
|
4518
|
-
"Follows bounded same-file const aliases, concatenations and template strings
|
|
4776
|
+
"Follows bounded same-file const aliases, concatenations and template strings. One-argument native URL wrappers are supported only for fully known absolute account URLs whose native normalization leaves the text unchanged. Local/imported constructors, dynamic constructor arguments, relative-base URLs, Request objects, unknown accounts, fallback expressions, function returns and cross-file values remain unreported.",
|
|
4777
|
+
"Native URL constructor/prototype mutation or capability capture outside new/typeof vetoes wrapper folding. Escaping/dynamic global receiver access and ambient eval/Function also leave URL semantics unknown. A stored URL must be nonexported, have at most 128 references, and every runtime read must be a direct native fetch argument; mutable instances, aliases, injected transports and other escapes remain unknown. These URL limits do not hide independent direct string fetch findings. Direct writes to ambient fetch or its global receivers retain their existing veto; indirect runtime monkeypatches are not modeled.",
|
|
4519
4778
|
"Analysis stops at 16 nested expressions or fetch aliases, 256 URL-expression visits per request, 128 string fragments or 8,192 UTF-16 code units of assembled URL text. These limits deliberately leave more complex expressions unreported.",
|
|
4520
4779
|
"Scripts remain covered. Test/fixture and generated files are excluded. Comparisons, schemas, allowlists and unused URL strings do not produce findings. Each originating account literal is reported at most once per file.",
|
|
4521
4780
|
"This is a warning without an automatic fix: the deployment configuration boundary and independent authorization policy require review. Application code should import validated configuration as required by no-raw-env; browser-exposed variables must never contain secrets."
|
|
@@ -4556,6 +4815,40 @@ var PREFER_CONFIGURED_CLOUD_SCOPE_DOCUMENTATION = {
|
|
|
4556
4815
|
focusPath: ".github/scripts/publish.mjs",
|
|
4557
4816
|
expectedCount: 0,
|
|
4558
4817
|
public: true
|
|
4818
|
+
},
|
|
4819
|
+
{
|
|
4820
|
+
id: "native-url-literal-account",
|
|
4821
|
+
scenarioId: "native-url-account",
|
|
4822
|
+
title: "A native URL wrapper still fixes the outgoing account in source",
|
|
4823
|
+
outcome: "match",
|
|
4824
|
+
files: [
|
|
4825
|
+
{
|
|
4826
|
+
path: "src/cloud-client.ts",
|
|
4827
|
+
source: 'export function list() { return fetch(new URL("https://api.cloudflare.com/client/v4/accounts/0123456789abcdef0123456789abcdef/workers/scripts")); }'
|
|
4828
|
+
}
|
|
4829
|
+
],
|
|
4830
|
+
focusPath: "src/cloud-client.ts",
|
|
4831
|
+
expectedCount: 1,
|
|
4832
|
+
public: true
|
|
4833
|
+
},
|
|
4834
|
+
{
|
|
4835
|
+
id: "native-url-configured-account",
|
|
4836
|
+
scenarioId: "native-url-account",
|
|
4837
|
+
title: "Supply the account from shared Zod-validated settings",
|
|
4838
|
+
outcome: "no-match",
|
|
4839
|
+
files: [
|
|
4840
|
+
{
|
|
4841
|
+
path: "src/cloud-client.ts",
|
|
4842
|
+
source: 'import { settings } from "./env.js";\nexport function list() { return fetch(new URL(`https://api.cloudflare.com/client/v4/accounts/${settings.CLOUDFLARE_ACCOUNT_ID}/workers/scripts`)); }'
|
|
4843
|
+
},
|
|
4844
|
+
{
|
|
4845
|
+
path: "src/env.ts",
|
|
4846
|
+
source: 'import { z } from "zod";\nconst SettingsSchema = z.object({ CLOUDFLARE_ACCOUNT_ID: z.string().length(32).regex(/^[a-fA-F0-9]+$/u) });\nexport const settings = SettingsSchema.parse(process.env);'
|
|
4847
|
+
}
|
|
4848
|
+
],
|
|
4849
|
+
focusPath: "src/cloud-client.ts",
|
|
4850
|
+
expectedCount: 0,
|
|
4851
|
+
public: true
|
|
4559
4852
|
}
|
|
4560
4853
|
]
|
|
4561
4854
|
};
|
|
@@ -15576,9 +15576,9 @@ var PLATFORM_MARKERS = /* @__PURE__ */ new Set([
|
|
|
15576
15576
|
"VERCEL_ENV",
|
|
15577
15577
|
"CI"
|
|
15578
15578
|
]);
|
|
15579
|
-
function
|
|
15580
|
-
const receiver = unwrapExpression(node
|
|
15581
|
-
if (receiver.type !== "Identifier"
|
|
15579
|
+
function isNativeProcess(node, sourceCode2) {
|
|
15580
|
+
const receiver = unwrapExpression(node);
|
|
15581
|
+
if (receiver.type !== "Identifier") return false;
|
|
15582
15582
|
const binding3 = findVariable(sourceCode2.getScope(receiver), receiver.name);
|
|
15583
15583
|
if (binding3 === null || binding3.defs.length === 0) return receiver.name === "process";
|
|
15584
15584
|
return binding3.defs.every((definition) => {
|
|
@@ -15586,6 +15586,9 @@ function isProcessEnv(node, sourceCode2) {
|
|
|
15586
15586
|
return definition.type === "ImportBinding" && parent?.type === "ImportDeclaration" && parent.importKind !== "type" && PROCESS_MODULES.includes(parent.source.value) && PROCESS_IMPORT_SPECIFIERS.includes(definition.node.type);
|
|
15587
15587
|
});
|
|
15588
15588
|
}
|
|
15589
|
+
function isProcessEnv(node, sourceCode2) {
|
|
15590
|
+
return getPropertyName(node) === "env" && isNativeProcess(node.object, sourceCode2);
|
|
15591
|
+
}
|
|
15589
15592
|
function isImportMetaEnv(node) {
|
|
15590
15593
|
return getPropertyName(node) === "env" && node.object.type === "MetaProperty" && node.object.meta.name === "import" && node.object.property.name === "meta";
|
|
15591
15594
|
}
|
|
@@ -15611,37 +15614,170 @@ function isWholeEnvSpread(node) {
|
|
|
15611
15614
|
const parent = parentOf(node);
|
|
15612
15615
|
return parent?.type === "SpreadElement" && parent.argument === node;
|
|
15613
15616
|
}
|
|
15614
|
-
function
|
|
15615
|
-
|
|
15617
|
+
function createValidationBudget() {
|
|
15618
|
+
let remaining = 512;
|
|
15619
|
+
return () => remaining--;
|
|
15620
|
+
}
|
|
15621
|
+
function reachesValidation(node, sourceCode2, seen = /* @__PURE__ */ new Set(), depth = 0, budget = createValidationBudget()) {
|
|
15622
|
+
if (depth >= 24 || budget() <= 0) return true;
|
|
15616
15623
|
const parent = parentOf(node);
|
|
15617
15624
|
if (parent === null) return false;
|
|
15618
15625
|
switch (parent.type) {
|
|
15619
|
-
case "CallExpression":
|
|
15620
|
-
|
|
15626
|
+
case "CallExpression": {
|
|
15627
|
+
const identity2 = isClosedIdentityAdapter(parent, sourceCode2);
|
|
15628
|
+
return parent.arguments.some((argument) => argument === node) && (!identity2 && isValidationCall(parent, sourceCode2) || (identity2 || isScalarCoercion(parent, sourceCode2) || isNativeJsonParse(parent, sourceCode2) && parent.arguments.length === 1) && reachesValidation(parent, sourceCode2, seen, depth + 1, budget));
|
|
15629
|
+
}
|
|
15621
15630
|
case "VariableDeclarator":
|
|
15622
15631
|
return parent.init === node && validatedAlias(parent, sourceCode2, seen, depth, budget);
|
|
15623
15632
|
case "MemberExpression":
|
|
15624
15633
|
return parent.object === node && reachesValidation(parent, sourceCode2, seen, depth + 1, budget);
|
|
15625
15634
|
case "Property":
|
|
15626
15635
|
return parent.value === node && parent.kind === "init" && !parent.method && reachesValidation(parent, sourceCode2, seen, depth + 1, budget);
|
|
15636
|
+
case "ConditionalExpression":
|
|
15637
|
+
return parent.test !== node && reachesValidation(parent, sourceCode2, seen, depth + 1, budget);
|
|
15638
|
+
case "LogicalExpression":
|
|
15639
|
+
return !(parent.operator === "&&" && parent.left === node) && reachesValidation(parent, sourceCode2, seen, depth + 1, budget);
|
|
15627
15640
|
case "ObjectExpression":
|
|
15628
15641
|
case "ArrayExpression":
|
|
15629
15642
|
case "SpreadElement":
|
|
15630
|
-
case "LogicalExpression":
|
|
15631
15643
|
case "BinaryExpression":
|
|
15632
|
-
case "ConditionalExpression":
|
|
15633
15644
|
case "TemplateLiteral":
|
|
15634
15645
|
case "TSAsExpression":
|
|
15635
15646
|
case "TSNonNullExpression":
|
|
15636
15647
|
case "TSSatisfiesExpression":
|
|
15648
|
+
case "TSTypeAssertion":
|
|
15637
15649
|
return reachesValidation(parent, sourceCode2, seen, depth + 1, budget);
|
|
15638
15650
|
default:
|
|
15639
15651
|
return false;
|
|
15640
15652
|
}
|
|
15641
15653
|
}
|
|
15642
|
-
function
|
|
15654
|
+
function identityFunction(node) {
|
|
15655
|
+
const value = unwrapExpression(node);
|
|
15656
|
+
if (value.type !== "ArrowFunctionExpression" && value.type !== "FunctionExpression" && value.type !== "FunctionDeclaration")
|
|
15657
|
+
return false;
|
|
15658
|
+
if (value.async || value.type !== "ArrowFunctionExpression" && value.generator || value.params.length !== 1)
|
|
15659
|
+
return false;
|
|
15660
|
+
const [parameter] = value.params;
|
|
15661
|
+
if (parameter?.type !== "Identifier" || value.body === null) return false;
|
|
15662
|
+
let returned2 = value.body;
|
|
15663
|
+
if (returned2.type === "BlockStatement") {
|
|
15664
|
+
const [statement] = returned2.body;
|
|
15665
|
+
if (returned2.body.length !== 1 || statement?.type !== "ReturnStatement" || statement.argument === null)
|
|
15666
|
+
return false;
|
|
15667
|
+
returned2 = statement.argument;
|
|
15668
|
+
}
|
|
15669
|
+
const result = unwrapExpression(returned2);
|
|
15670
|
+
return result.type === "Identifier" && result.name === parameter.name;
|
|
15671
|
+
}
|
|
15672
|
+
function isClosedIdentityAdapter(node, sourceCode2) {
|
|
15673
|
+
const callee = unwrapExpression(node.callee);
|
|
15674
|
+
const member3 = callee.type === "MemberExpression" ? callee : null;
|
|
15675
|
+
const receiver = member3 === null ? callee : unwrapExpression(member3.object);
|
|
15676
|
+
const method = member3 === null ? null : getPropertyName(member3);
|
|
15677
|
+
const output = parentOf(outerExpression(node));
|
|
15678
|
+
if (node.arguments.length !== 1 || node.arguments[0]?.type === "SpreadElement" || node.optional || member3?.optional || output?.type === "AwaitExpression" || output?.type === "ChainExpression")
|
|
15679
|
+
return false;
|
|
15680
|
+
if (receiver.type !== "Identifier" || (member3 === null ? receiver.name !== "createEnv" : method !== "parse" && method !== "safeParse"))
|
|
15681
|
+
return false;
|
|
15682
|
+
const variable = findVariable(sourceCode2.getScope(receiver), receiver.name);
|
|
15683
|
+
if (variable?.defs.length !== 1 || variable.references.length > 128 || !stableValueBinding(variable))
|
|
15684
|
+
return false;
|
|
15685
|
+
const value = privateIdentityValue(variable);
|
|
15686
|
+
if (value === null) return false;
|
|
15687
|
+
const methods = member3 === null ? null : identityMethods(value);
|
|
15688
|
+
if (member3 !== null && (methods === null || !methods.has(method ?? ""))) return false;
|
|
15689
|
+
if (member3 === null && !identityFunction(value)) return false;
|
|
15690
|
+
return identityReferences(variable, methods) && closedIdentityModule(sourceCode2);
|
|
15691
|
+
}
|
|
15692
|
+
function privateIdentityValue(variable) {
|
|
15693
|
+
const definition = variable.defs[0]?.node;
|
|
15694
|
+
if (definition === void 0) return null;
|
|
15695
|
+
let value = definition;
|
|
15696
|
+
let declaration = parentOf(definition);
|
|
15697
|
+
if (definition.type === "VariableDeclarator") {
|
|
15698
|
+
if (definition.id.type !== "Identifier" || definition.init === null || declaration?.type !== "VariableDeclaration" || declaration.kind !== "const")
|
|
15699
|
+
return null;
|
|
15700
|
+
value = unwrapExpression(definition.init);
|
|
15701
|
+
declaration = parentOf(declaration);
|
|
15702
|
+
} else if (definition.type !== "FunctionDeclaration") return null;
|
|
15703
|
+
if (declaration?.type === "ExportNamedDeclaration" || declaration?.type === "ExportDefaultDeclaration")
|
|
15704
|
+
return null;
|
|
15705
|
+
let ancestor = declaration;
|
|
15706
|
+
let ancestors2 = 32;
|
|
15707
|
+
while (ancestor !== null && ancestor.type !== "Program") {
|
|
15708
|
+
if (ancestors2-- <= 0 || ancestor.type === "TSModuleBlock" || ancestor.type === "TSModuleDeclaration")
|
|
15709
|
+
return null;
|
|
15710
|
+
ancestor = parentOf(ancestor);
|
|
15711
|
+
}
|
|
15712
|
+
return value;
|
|
15713
|
+
}
|
|
15714
|
+
function closedIdentityModule(sourceCode2) {
|
|
15715
|
+
if (!sourceCode2.ast.body.some(
|
|
15716
|
+
(statement) => statement.type === "ImportDeclaration" || statement.type === "ExportNamedDeclaration" || statement.type === "ExportDefaultDeclaration" || statement.type === "ExportAllDeclaration"
|
|
15717
|
+
))
|
|
15718
|
+
return false;
|
|
15719
|
+
let remaining = 512;
|
|
15720
|
+
for (const scope of sourceCode2.scopeManager.scopes) {
|
|
15721
|
+
for (const reference of scope.references) {
|
|
15722
|
+
if (remaining-- <= 0) return false;
|
|
15723
|
+
if (reference.identifier.name === "eval" && !typeOnlyReference(tsReference(reference)))
|
|
15724
|
+
return false;
|
|
15725
|
+
}
|
|
15726
|
+
}
|
|
15727
|
+
return true;
|
|
15728
|
+
}
|
|
15729
|
+
function identityMethods(value) {
|
|
15730
|
+
if (value.type !== "ObjectExpression" || value.properties.length === 0 || value.properties.length > 2)
|
|
15731
|
+
return null;
|
|
15732
|
+
const methods = /* @__PURE__ */ new Set();
|
|
15733
|
+
for (const property of value.properties) {
|
|
15734
|
+
if (property.type !== "Property" || property.kind !== "init" || !identityFunction(property.value))
|
|
15735
|
+
return null;
|
|
15736
|
+
const name = getPropertyName(property);
|
|
15737
|
+
if (name !== "parse" && name !== "safeParse" || methods.has(name)) return null;
|
|
15738
|
+
methods.add(name);
|
|
15739
|
+
}
|
|
15740
|
+
return methods;
|
|
15741
|
+
}
|
|
15742
|
+
function identityReferences(variable, methods) {
|
|
15743
|
+
return variable.references.every((reference) => {
|
|
15744
|
+
if (reference.init === true || typeOnlyReference(reference)) return true;
|
|
15745
|
+
let target = outerExpression(reference.identifier);
|
|
15746
|
+
if (methods !== null) {
|
|
15747
|
+
const property = parentOf(target);
|
|
15748
|
+
if (property?.type !== "MemberExpression" || property.object !== target || property.optional || !methods.has(getPropertyName(property) ?? ""))
|
|
15749
|
+
return false;
|
|
15750
|
+
target = outerExpression(property);
|
|
15751
|
+
}
|
|
15752
|
+
const use = parentOf(target);
|
|
15753
|
+
return use?.type === "CallExpression" && use.callee === target && !use.optional && parentOf(use)?.type !== "ChainExpression";
|
|
15754
|
+
});
|
|
15755
|
+
}
|
|
15756
|
+
function isValidationCall(node, sourceCode2) {
|
|
15757
|
+
const callee = unwrapExpression(node.callee);
|
|
15758
|
+
if (callee.type === "Identifier") {
|
|
15759
|
+
if (callee.name === "createEnv") return true;
|
|
15760
|
+
const binding3 = findVariable(sourceCode2.getScope(callee), callee.name);
|
|
15761
|
+
const definition = binding3?.defs.length === 1 ? binding3.defs[0] : void 0;
|
|
15762
|
+
const declaration = definition?.parent;
|
|
15763
|
+
const specifier = definition?.node;
|
|
15764
|
+
return definition?.type === "ImportBinding" && declaration?.type === "ImportDeclaration" && declaration.importKind !== "type" && ["@t3-oss/env-core", "@t3-oss/env-nextjs"].includes(declaration.source.value) && specifier?.type === "ImportSpecifier" && specifier.importKind !== "type" && importSpecifierName(specifier) === "createEnv" && binding3 !== null && stableValueBinding(binding3);
|
|
15765
|
+
}
|
|
15766
|
+
if (callee.type !== "MemberExpression" || isNativeJsonParse(node, sourceCode2)) return false;
|
|
15767
|
+
const method = getPropertyName(callee);
|
|
15768
|
+
if (method === "parse" || method === "safeParse") return true;
|
|
15769
|
+
if (method !== "parseAsync" && method !== "safeParseAsync") return false;
|
|
15770
|
+
const value = outerExpression(node);
|
|
15771
|
+
const parent = parentOf(value);
|
|
15772
|
+
return parent?.type === "AwaitExpression" && parent.argument === value;
|
|
15773
|
+
}
|
|
15774
|
+
function isNativeJsonParse(node, sourceCode2) {
|
|
15643
15775
|
const callee = unwrapExpression(node.callee);
|
|
15644
|
-
|
|
15776
|
+
if (callee.type !== "MemberExpression" || getPropertyName(callee) !== "parse") return false;
|
|
15777
|
+
const receiver = unwrapExpression(callee.object);
|
|
15778
|
+
if (receiver.type !== "Identifier" || receiver.name !== "JSON") return false;
|
|
15779
|
+
const binding3 = findVariable(sourceCode2.getScope(receiver), receiver.name);
|
|
15780
|
+
return binding3 === null || binding3.defs.length === 0;
|
|
15645
15781
|
}
|
|
15646
15782
|
function isScalarCoercion(node, sourceCode2) {
|
|
15647
15783
|
const callee = unwrapExpression(node.callee);
|
|
@@ -15650,19 +15786,73 @@ function isScalarCoercion(node, sourceCode2) {
|
|
|
15650
15786
|
const binding3 = findVariable(sourceCode2.getScope(callee), callee.name);
|
|
15651
15787
|
return binding3 === null || binding3.defs.length === 0;
|
|
15652
15788
|
}
|
|
15789
|
+
function isWholeEnvironment(node, sourceCode2, seen = /* @__PURE__ */ new Set()) {
|
|
15790
|
+
const value = unwrapExpression(node);
|
|
15791
|
+
if (value.type === "MemberExpression")
|
|
15792
|
+
return isProcessEnv(value, sourceCode2) || isImportMetaEnv(value);
|
|
15793
|
+
if (value.type !== "Identifier" || seen.size >= 16) return false;
|
|
15794
|
+
const variable = stableAliasBinding(value, sourceCode2, seen);
|
|
15795
|
+
const definition = variable?.defs[0];
|
|
15796
|
+
if (variable === null || definition === void 0) return false;
|
|
15797
|
+
const declaration = definition.parent;
|
|
15798
|
+
const binding3 = definition.node;
|
|
15799
|
+
if (definition.type === "ImportBinding") {
|
|
15800
|
+
return declaration?.type === "ImportDeclaration" && declaration.importKind !== "type" && PROCESS_MODULES.includes(declaration.source.value) && binding3.type === "ImportSpecifier" && binding3.importKind !== "type" && importSpecifierName(binding3) === "env";
|
|
15801
|
+
}
|
|
15802
|
+
if (binding3.type !== "VariableDeclarator" || binding3.init === null || declaration?.type !== "VariableDeclaration" || declaration.kind !== "const")
|
|
15803
|
+
return false;
|
|
15804
|
+
if (binding3.id.type === "Identifier")
|
|
15805
|
+
return isWholeEnvironment(binding3.init, sourceCode2, /* @__PURE__ */ new Set([...seen, variable]));
|
|
15806
|
+
return binding3.id.type === "ObjectPattern" && isNativeProcess(binding3.init, sourceCode2) && binding3.id.properties.some(
|
|
15807
|
+
(property) => property.type === "Property" && getPropertyName(property) === "env" && property.value.type === "Identifier" && property.value.name === value.name
|
|
15808
|
+
);
|
|
15809
|
+
}
|
|
15810
|
+
function stableAliasBinding(node, sourceCode2, seen) {
|
|
15811
|
+
const variable = findVariable(sourceCode2.getScope(node), node.name);
|
|
15812
|
+
return variable !== null && variable.defs.length === 1 && !seen.has(variable) && stableValueBinding(variable) ? variable : null;
|
|
15813
|
+
}
|
|
15653
15814
|
function validatedAlias(node, sourceCode2, seen, depth, budget) {
|
|
15654
15815
|
const declaration = parentOf(node);
|
|
15655
|
-
if (
|
|
15816
|
+
if (declaration?.type !== "VariableDeclaration" || declaration.kind !== "const" || parentOf(declaration)?.type === "ExportNamedDeclaration")
|
|
15656
15817
|
return false;
|
|
15657
|
-
if (
|
|
15658
|
-
|
|
15659
|
-
|
|
15818
|
+
if (node.id.type === "Identifier") {
|
|
15819
|
+
const variable = stableAliasBinding(node.id, sourceCode2, seen);
|
|
15820
|
+
return variable !== null && validatedBinding(variable, sourceCode2, seen, depth, budget);
|
|
15821
|
+
}
|
|
15822
|
+
if (node.id.type !== "ObjectPattern" || node.id.properties.length === 0 || node.init === null)
|
|
15660
15823
|
return false;
|
|
15661
|
-
|
|
15824
|
+
if (node.id.properties.length > 128) return true;
|
|
15825
|
+
return validatedFields(node.id, node.init, sourceCode2, seen, depth, budget);
|
|
15826
|
+
}
|
|
15827
|
+
function validatedFields(pattern, input, sourceCode2, seen, depth, budget) {
|
|
15828
|
+
const wholeEnvironment = isWholeEnvironment(input, sourceCode2);
|
|
15829
|
+
for (const property of pattern.properties) {
|
|
15830
|
+
if (budget() <= 0) return true;
|
|
15831
|
+
if (property.type !== "Property" || property.value.type !== "Identifier") return false;
|
|
15832
|
+
const name = getPropertyName(property);
|
|
15833
|
+
if (name === null) return false;
|
|
15834
|
+
const variable = stableAliasBinding(property.value, sourceCode2, seen);
|
|
15835
|
+
if (variable === null) return false;
|
|
15836
|
+
if (wholeEnvironment && (BUILD_TIME_CONSTANTS.has(name) || PLATFORM_MARKERS.has(name)))
|
|
15837
|
+
continue;
|
|
15838
|
+
if (!validatedBinding(variable, sourceCode2, seen, depth, budget)) return false;
|
|
15839
|
+
}
|
|
15840
|
+
return true;
|
|
15841
|
+
}
|
|
15842
|
+
function validatedBinding(variable, sourceCode2, seen, depth, budget) {
|
|
15843
|
+
const reads = variable.references.filter((ref) => ref.isRead() && !typeOnlyReference(ref));
|
|
15662
15844
|
const next = /* @__PURE__ */ new Set([...seen, variable]);
|
|
15663
15845
|
if (reads.length > 128) return true;
|
|
15664
15846
|
return reads.length > 0 && reads.every((ref) => reachesValidation(ref.identifier, sourceCode2, next, depth + 1, budget));
|
|
15665
15847
|
}
|
|
15848
|
+
function isPlatformExtraction(node, sourceCode2) {
|
|
15849
|
+
const declaration = parentOf(node);
|
|
15850
|
+
return declaration?.type === "VariableDeclarator" && declaration.init === node && declaration.id.type === "ObjectPattern" && declaration.id.properties.length > 0 && declaration.id.properties.length <= 128 && declaration.id.properties.every((property) => {
|
|
15851
|
+
if (property.type !== "Property" || property.value.type !== "Identifier") return false;
|
|
15852
|
+
const name = getPropertyName(property);
|
|
15853
|
+
return name !== null && (BUILD_TIME_CONSTANTS.has(name) || PLATFORM_MARKERS.has(name));
|
|
15854
|
+
}) && isWholeEnvironment(node, sourceCode2) && validatedAlias(declaration, sourceCode2, /* @__PURE__ */ new Set(), 0, createValidationBudget());
|
|
15855
|
+
}
|
|
15666
15856
|
var no_raw_env_default = defineRule({
|
|
15667
15857
|
meta: {
|
|
15668
15858
|
type: "problem",
|
|
@@ -15679,6 +15869,17 @@ var no_raw_env_default = defineRule({
|
|
|
15679
15869
|
let sourceCode2;
|
|
15680
15870
|
let reads = [];
|
|
15681
15871
|
let boundaryFile = false;
|
|
15872
|
+
function recordRead(node) {
|
|
15873
|
+
if (!isExemptVariableAccess(node) && !isWriteTarget(node) && !isPlatformExtraction(node, sourceCode2) && !(isWholeEnvSpread(node) && !boundaryFile))
|
|
15874
|
+
reads.push(node);
|
|
15875
|
+
}
|
|
15876
|
+
function recordAlias(node) {
|
|
15877
|
+
const variable = findVariable(sourceCode2.getScope(node), node.name);
|
|
15878
|
+
if (variable?.defs.length !== 1 || !stableValueBinding(variable)) return;
|
|
15879
|
+
for (const reference of variable.references) {
|
|
15880
|
+
if (reference.isRead() && !typeOnlyReference(reference)) recordRead(reference.identifier);
|
|
15881
|
+
}
|
|
15882
|
+
}
|
|
15682
15883
|
return {
|
|
15683
15884
|
before() {
|
|
15684
15885
|
const filename = context.filename;
|
|
@@ -15691,11 +15892,25 @@ var no_raw_env_default = defineRule({
|
|
|
15691
15892
|
reads = [];
|
|
15692
15893
|
return void 0;
|
|
15693
15894
|
},
|
|
15694
|
-
|
|
15695
|
-
if (
|
|
15696
|
-
|
|
15895
|
+
ImportDeclaration(node) {
|
|
15896
|
+
if (node.importKind === "type" || !PROCESS_MODULES.includes(node.source.value)) return;
|
|
15897
|
+
for (const specifier of node.specifiers) {
|
|
15898
|
+
if (specifier.type === "ImportSpecifier" && specifier.importKind !== "type" && importSpecifierName(specifier) === "env")
|
|
15899
|
+
recordAlias(specifier.local);
|
|
15697
15900
|
}
|
|
15698
15901
|
},
|
|
15902
|
+
VariableDeclarator(node) {
|
|
15903
|
+
const declaration = parentOf(node);
|
|
15904
|
+
if (node.id.type !== "ObjectPattern" || node.init === null || declaration?.type !== "VariableDeclaration" || declaration.kind !== "const" || !isNativeProcess(node.init, sourceCode2))
|
|
15905
|
+
return;
|
|
15906
|
+
for (const property of node.id.properties) {
|
|
15907
|
+
if (property.type === "Property" && getPropertyName(property) === "env" && property.value.type === "Identifier")
|
|
15908
|
+
recordAlias(property.value);
|
|
15909
|
+
}
|
|
15910
|
+
},
|
|
15911
|
+
MemberExpression(node) {
|
|
15912
|
+
if (isProcessEnv(node, sourceCode2) || isImportMetaEnv(node)) recordRead(node);
|
|
15913
|
+
},
|
|
15699
15914
|
"Program:exit"() {
|
|
15700
15915
|
for (const node of reads) {
|
|
15701
15916
|
if (!boundaryFile || !reachesValidation(node, sourceCode2))
|
|
@@ -18559,29 +18774,37 @@ function isTypeOnlyImport(node) {
|
|
|
18559
18774
|
(specifier) => specifier.type === "ImportSpecifier" && specifier.importKind === "type"
|
|
18560
18775
|
);
|
|
18561
18776
|
}
|
|
18777
|
+
function isTypeOnlyExport(node) {
|
|
18778
|
+
return node.exportKind === "type" || node.type === "ExportNamedDeclaration" && node.specifiers.length > 0 && node.specifiers.every((specifier) => specifier.exportKind === "type");
|
|
18779
|
+
}
|
|
18562
18780
|
var no_server_env_in_client_component_default = defineRule({
|
|
18563
18781
|
meta: {
|
|
18564
18782
|
type: "problem",
|
|
18565
18783
|
docs: {
|
|
18566
|
-
description: "server-only environment settings imported by a client
|
|
18784
|
+
description: "server-only environment settings imported or re-exported by a client module",
|
|
18567
18785
|
url: docsUrl("no-server-env-in-client-component")
|
|
18568
18786
|
},
|
|
18569
18787
|
schema: [],
|
|
18570
18788
|
messages: {
|
|
18571
|
-
noServerEnvInClientComponent: "
|
|
18789
|
+
noServerEnvInClientComponent: "Do not import or re-export server-only settings from a 'use client' module; use validated client settings."
|
|
18572
18790
|
}
|
|
18573
18791
|
},
|
|
18574
18792
|
createOnce(context) {
|
|
18575
18793
|
let clientModule = false;
|
|
18794
|
+
function checkDependency(node) {
|
|
18795
|
+
if (!clientModule || node.source === null) return;
|
|
18796
|
+
const typeOnly = node.type === "ImportDeclaration" ? isTypeOnlyImport(node) : isTypeOnlyExport(node);
|
|
18797
|
+
if (!typeOnly && SERVER_ENV_MODULE_RE.test(node.source.value)) {
|
|
18798
|
+
context.report({ node, messageId: "noServerEnvInClientComponent" });
|
|
18799
|
+
}
|
|
18800
|
+
}
|
|
18576
18801
|
return {
|
|
18577
18802
|
Program(node) {
|
|
18578
18803
|
clientModule = isClientModule(node);
|
|
18579
18804
|
},
|
|
18580
|
-
ImportDeclaration
|
|
18581
|
-
|
|
18582
|
-
|
|
18583
|
-
}
|
|
18584
|
-
}
|
|
18805
|
+
ImportDeclaration: checkDependency,
|
|
18806
|
+
ExportNamedDeclaration: checkDependency,
|
|
18807
|
+
ExportAllDeclaration: checkDependency
|
|
18585
18808
|
};
|
|
18586
18809
|
}
|
|
18587
18810
|
});
|
|
@@ -21911,6 +22134,19 @@ function libraryReference(node) {
|
|
|
21911
22134
|
return { root: node.object, member: node.property.name };
|
|
21912
22135
|
return null;
|
|
21913
22136
|
}
|
|
22137
|
+
function returnedValues(node, depth = 0, budget = { remaining: MAX_TYPE_STEPS }) {
|
|
22138
|
+
if (depth >= MAX_DEPTH || --budget.remaining < 0) return null;
|
|
22139
|
+
const value = unwrapExpression(node);
|
|
22140
|
+
if (value.type === "AwaitExpression")
|
|
22141
|
+
return returnedValues(value.argument, depth + 1, budget);
|
|
22142
|
+
if (value.type !== "ConditionalExpression") return [value];
|
|
22143
|
+
const consequent = returnedValues(value.consequent, depth + 1, budget);
|
|
22144
|
+
if (consequent === null) return null;
|
|
22145
|
+
const alternate = returnedValues(value.alternate, depth + 1, budget);
|
|
22146
|
+
if (alternate === null || consequent.length + alternate.length > MAX_VARIANTS)
|
|
22147
|
+
return null;
|
|
22148
|
+
return [...consequent, ...alternate];
|
|
22149
|
+
}
|
|
21914
22150
|
function renderedFailure(node, source) {
|
|
21915
22151
|
let value = unwrapExpression(node);
|
|
21916
22152
|
if (value.type === "AwaitExpression")
|
|
@@ -22043,13 +22279,19 @@ var prefer_coded_error_result_default = defineRule({
|
|
|
22043
22279
|
}
|
|
22044
22280
|
function checkProducer(owner, value) {
|
|
22045
22281
|
if (owner === null) return;
|
|
22046
|
-
const
|
|
22047
|
-
|
|
22282
|
+
const issues = (returnedValues(value) ?? []).flatMap((leaf) => {
|
|
22283
|
+
const issue = renderedFailure(leaf, context.sourceCode);
|
|
22284
|
+
return issue === null ? [] : [issue];
|
|
22285
|
+
});
|
|
22286
|
+
if (issues.length === 0) return;
|
|
22048
22287
|
const contract = owner.returnType?.typeAnnotation;
|
|
22049
22288
|
if (contract !== void 0 && outcomeFailures(view(contract), context.sourceCode).length !== 0)
|
|
22050
22289
|
return;
|
|
22051
|
-
|
|
22052
|
-
|
|
22290
|
+
for (const issue of issues) {
|
|
22291
|
+
if (reported.has(issue.node)) continue;
|
|
22292
|
+
reported.add(issue.node);
|
|
22293
|
+
context.report(issue);
|
|
22294
|
+
}
|
|
22053
22295
|
}
|
|
22054
22296
|
return {
|
|
22055
22297
|
before() {
|
|
@@ -22075,6 +22317,7 @@ var prefer_coded_error_result_default = defineRule({
|
|
|
22075
22317
|
});
|
|
22076
22318
|
|
|
22077
22319
|
// src/rules/prefer-configured-cloud-scope.ts
|
|
22320
|
+
import { URL as NativeURL } from "url";
|
|
22078
22321
|
var NAME92 = "prefer-configured-cloud-scope";
|
|
22079
22322
|
var ACCOUNT_PREFIX = "https://api.cloudflare.com/client/v4/accounts/";
|
|
22080
22323
|
var ACCOUNT_URL = /^https:\/\/api\.cloudflare\.com\/client\/v4\/accounts\/[a-fA-F\d]{32}(?=\/|[?#]|$)/u;
|
|
@@ -22110,6 +22353,7 @@ var prefer_configured_cloud_scope_default = defineRule({
|
|
|
22110
22353
|
const requests = [];
|
|
22111
22354
|
let fetchOverridden = false;
|
|
22112
22355
|
let nodesRemaining = 0;
|
|
22356
|
+
let nativeUrlSafe;
|
|
22113
22357
|
function bindingOf(node) {
|
|
22114
22358
|
return findVariable(sourceCode2.getScope(node), node.name);
|
|
22115
22359
|
}
|
|
@@ -22118,7 +22362,8 @@ var prefer_configured_cloud_scope_default = defineRule({
|
|
|
22118
22362
|
return binding3 === null || binding3.defs.length === 0;
|
|
22119
22363
|
}
|
|
22120
22364
|
function globalReceiver2(node) {
|
|
22121
|
-
|
|
22365
|
+
const value = unwrapExpression(node);
|
|
22366
|
+
return value.type === "Identifier" && GLOBAL_RECEIVERS5.has(value.name) && unshadowed(value);
|
|
22122
22367
|
}
|
|
22123
22368
|
function outgoingFetch(node, seen = /* @__PURE__ */ new Set()) {
|
|
22124
22369
|
const callee = unwrapExpression(node);
|
|
@@ -22141,6 +22386,11 @@ var prefer_configured_cloud_scope_default = defineRule({
|
|
|
22141
22386
|
const declarator = definition.node;
|
|
22142
22387
|
return declarator.id.type === "Identifier" && declarator.init !== null && outgoingFetch(declarator.init, next);
|
|
22143
22388
|
}
|
|
22389
|
+
function nativeUrlReference(node) {
|
|
22390
|
+
const value = unwrapExpression(node);
|
|
22391
|
+
if (value.type === "Identifier") return value.name === "URL" && unshadowed(value);
|
|
22392
|
+
return value.type === "MemberExpression" && globalReceiver2(value.object) && staticPropertyName(value, sourceCode2) === "URL";
|
|
22393
|
+
}
|
|
22144
22394
|
function checkWrite(target, depth = 0) {
|
|
22145
22395
|
if (depth >= 16) {
|
|
22146
22396
|
fetchOverridden = true;
|
|
@@ -22182,17 +22432,7 @@ var prefer_configured_cloud_scope_default = defineRule({
|
|
|
22182
22432
|
if (value.type === "Literal" && typeof value.value === "string") {
|
|
22183
22433
|
return boundedParts([{ text: value.value, source: value }]);
|
|
22184
22434
|
}
|
|
22185
|
-
if (value.type === "Identifier")
|
|
22186
|
-
const binding3 = bindingOf(value);
|
|
22187
|
-
if (binding3 === null || seen.has(binding3) || binding3.defs.length !== 1 || binding3.references.some((ref) => ref.isWrite() && ref.init !== true))
|
|
22188
|
-
return UNKNOWN;
|
|
22189
|
-
const [definition] = binding3.defs;
|
|
22190
|
-
if (definition?.type !== "Variable" || definition.parent?.type !== "VariableDeclaration" || definition.parent.kind !== "const")
|
|
22191
|
-
return UNKNOWN;
|
|
22192
|
-
const declarator = definition.node;
|
|
22193
|
-
if (declarator.id.type !== "Identifier" || declarator.init === null) return UNKNOWN;
|
|
22194
|
-
return urlParts(declarator.init, /* @__PURE__ */ new Set([...seen, binding3]), depth + 1);
|
|
22195
|
-
}
|
|
22435
|
+
if (value.type === "Identifier") return identifierUrlParts(value, seen, depth);
|
|
22196
22436
|
if (value.type === "BinaryExpression" && value.operator === "+") {
|
|
22197
22437
|
return boundedParts([
|
|
22198
22438
|
...urlParts(value.left, seen, depth + 1),
|
|
@@ -22200,8 +22440,78 @@ var prefer_configured_cloud_scope_default = defineRule({
|
|
|
22200
22440
|
]);
|
|
22201
22441
|
}
|
|
22202
22442
|
if (value.type === "TemplateLiteral") return templateParts(value, seen, depth);
|
|
22443
|
+
if (value.type === "NewExpression") return nativeUrlParts(value, seen, depth);
|
|
22203
22444
|
return UNKNOWN;
|
|
22204
22445
|
}
|
|
22446
|
+
function identifierUrlParts(identifier, seen, depth) {
|
|
22447
|
+
const binding3 = bindingOf(identifier);
|
|
22448
|
+
if (binding3 === null || seen.has(binding3) || binding3.defs.length !== 1 || binding3.references.some((ref) => ref.isWrite() && ref.init !== true))
|
|
22449
|
+
return UNKNOWN;
|
|
22450
|
+
const [definition] = binding3.defs;
|
|
22451
|
+
if (definition?.type !== "Variable" || definition.parent?.type !== "VariableDeclaration" || definition.parent.kind !== "const")
|
|
22452
|
+
return UNKNOWN;
|
|
22453
|
+
const declarator = definition.node;
|
|
22454
|
+
if (declarator.id.type !== "Identifier" || declarator.init === null) return UNKNOWN;
|
|
22455
|
+
const initializer = unwrapExpression(declarator.init);
|
|
22456
|
+
if (initializer.type === "NewExpression" && nativeUrlReference(initializer.callee) && !onlyUrlRequests(binding3, declarator))
|
|
22457
|
+
return UNKNOWN;
|
|
22458
|
+
return urlParts(declarator.init, /* @__PURE__ */ new Set([...seen, binding3]), depth + 1);
|
|
22459
|
+
}
|
|
22460
|
+
function onlyUrlRequests(variable, declarator) {
|
|
22461
|
+
if (variable.references.length > 128) return false;
|
|
22462
|
+
const declaration = parentOf(declarator);
|
|
22463
|
+
if (declaration !== null && parentOf(declaration)?.type === "ExportNamedDeclaration")
|
|
22464
|
+
return false;
|
|
22465
|
+
return variable.references.every((reference) => {
|
|
22466
|
+
if (reference.init === true || typeOnlyReference(reference)) return true;
|
|
22467
|
+
const value = outerExpression(reference.identifier);
|
|
22468
|
+
const use = parentOf(value);
|
|
22469
|
+
if (use?.type !== "CallExpression" || use.arguments[0] !== value) return false;
|
|
22470
|
+
const callee = unwrapExpression(use.callee);
|
|
22471
|
+
return callee.type === "Identifier" && callee.name === "fetch" && unshadowed(callee) || callee.type === "MemberExpression" && globalReceiver2(callee.object) && staticPropertyName(callee, sourceCode2) === "fetch";
|
|
22472
|
+
});
|
|
22473
|
+
}
|
|
22474
|
+
function nativeUrlParts(value, seen, depth) {
|
|
22475
|
+
if (value.arguments.length !== 1 || !nativeUrlReference(value.callee) || !nativeUrlUnchanged())
|
|
22476
|
+
return UNKNOWN;
|
|
22477
|
+
const [argument] = value.arguments;
|
|
22478
|
+
if (argument === void 0 || argument.type === "SpreadElement") return UNKNOWN;
|
|
22479
|
+
const parts = urlParts(argument, seen, depth + 1);
|
|
22480
|
+
if (parts.some((part) => part.source === null)) return UNKNOWN;
|
|
22481
|
+
const text = parts.map((part) => part.text).join("");
|
|
22482
|
+
if (!ACCOUNT_URL.test(text)) return UNKNOWN;
|
|
22483
|
+
try {
|
|
22484
|
+
return new NativeURL(text).href === text ? parts : UNKNOWN;
|
|
22485
|
+
} catch {
|
|
22486
|
+
return UNKNOWN;
|
|
22487
|
+
}
|
|
22488
|
+
}
|
|
22489
|
+
function nativeUrlUnchanged() {
|
|
22490
|
+
if (nativeUrlSafe !== void 0) return nativeUrlSafe;
|
|
22491
|
+
nativeUrlSafe = sourceCode2.scopeManager.scopes.every(
|
|
22492
|
+
(scope) => scope.references.every((reference) => {
|
|
22493
|
+
if (typeOnlyReference(tsReference(reference))) return true;
|
|
22494
|
+
const identifier = reference.identifier;
|
|
22495
|
+
if (["eval", "Function"].includes(identifier.name) && unshadowed(identifier))
|
|
22496
|
+
return false;
|
|
22497
|
+
let value = outerExpression(identifier);
|
|
22498
|
+
if (!nativeUrlReference(value)) {
|
|
22499
|
+
const member3 = parentOf(value);
|
|
22500
|
+
if (globalReceiver2(value)) {
|
|
22501
|
+
if (member3?.type !== "MemberExpression" || member3.object !== value) return false;
|
|
22502
|
+
const key = staticPropertyName(member3, sourceCode2);
|
|
22503
|
+
if (key === null || GLOBAL_RECEIVERS5.has(key)) return false;
|
|
22504
|
+
}
|
|
22505
|
+
if (member3?.type !== "MemberExpression" || member3.object !== value || !nativeUrlReference(member3))
|
|
22506
|
+
return true;
|
|
22507
|
+
value = outerExpression(member3);
|
|
22508
|
+
}
|
|
22509
|
+
const use = parentOf(value);
|
|
22510
|
+
return use?.type === "NewExpression" && use.callee === value || use?.type === "UnaryExpression" && use.operator === "typeof";
|
|
22511
|
+
})
|
|
22512
|
+
);
|
|
22513
|
+
return nativeUrlSafe;
|
|
22514
|
+
}
|
|
22205
22515
|
function templateParts(value, seen, depth) {
|
|
22206
22516
|
const parts = [];
|
|
22207
22517
|
for (let index = 0; index < value.quasis.length; index++) {
|
|
@@ -22220,6 +22530,7 @@ var prefer_configured_cloud_scope_default = defineRule({
|
|
|
22220
22530
|
reported.clear();
|
|
22221
22531
|
requests.length = 0;
|
|
22222
22532
|
fetchOverridden = false;
|
|
22533
|
+
nativeUrlSafe = void 0;
|
|
22223
22534
|
const file = { filename: context.filename, cwd: context.cwd };
|
|
22224
22535
|
return !isTestFile(file) && !isGeneratedFile(file, sourceCode2.text);
|
|
22225
22536
|
},
|
package/dist/index.js
CHANGED