Skip to content
Source
Sign inSign up

no-rpc-permission-bypass.ts

90 lines3.0 KB

1import { defineRule } from "@oxlint/plugins";
2import * as Option from "effect/Option";
3import { getPropertyName } from "../utils.ts";
4​
5const readOptions = (options: ReadonlyArray<unknown>) => {
6 const [first] = options;
7 return {
8 allowRawClientAccess:
9 typeof first === "object" &&
10 first !== null &&
11 "allowRawClientAccess" in first &&
12 first.allowRawClientAccess === true,
13 allowGuardInstallation:
14 typeof first === "object" &&
15 first !== null &&
16 "allowGuardInstallation" in first &&
17 first.allowGuardInstallation === true,
18 };
19};
20​
21export default defineRule({
22 meta: {
23 type: "problem",
24 docs: {
25 description:
26 "Keep raw RPC access and permission guard installation inside their shared boundaries.",
27 },
28 schema: [
29 {
30 type: "object",
31 properties: {
32 allowRawClientAccess: {
33 type: "boolean",
34 description: "Permit .client access for non-RPC clients in the configured files.",
35 },
36 allowGuardInstallation: {
37 type: "boolean",
38 description: "Permit installing RpcPermissionGuard in a reviewed command boundary.",
39 },
40 },
41 additionalProperties: false,
42 },
43 ],
44 defaultOptions: [{ allowRawClientAccess: false, allowGuardInstallation: false }],
45 },
46 create(context) {
47 const { allowRawClientAccess, allowGuardInstallation } = readOptions(context.options);
48 const report = (node: NonNullable<Parameters<typeof context.report>[0]["node"]>) =>
49 context.report({
50 node,
51 message:
52 "Use permission-aware environment commands. Raw RPC access and RpcPermissionGuard belong in the shared RPC/command boundary.",
53 });
54 return {
55 ImportDeclaration(node) {
56 if (typeof node.source.value !== "string") return;
57 const source = node.source.value;
58 if (/(?:^|\/)rpc\/protocol(?:\.ts)?$/.test(source) && node.importKind !== "type")
59 report(node);
60 if (
61 !allowGuardInstallation &&
62 /(?:^|\/)rpc(?:\/(?:client|index)(?:\.ts)?)?$/.test(source)
63 ) {
64 for (const specifier of node.specifiers) {
65 if (
66 specifier.type === "ImportSpecifier" &&
67 Option.getOrNull(getPropertyName(specifier.imported)) === "RpcPermissionGuard"
68 )
69 report(specifier);
70 }
71 }
72 },
73 VariableDeclarator(node) {
74 if (allowRawClientAccess || node.id.type !== "ObjectPattern") return;
75 for (const property of node.id.properties) {
76 if (
77 property.type === "Property" &&
78 Option.getOrNull(getPropertyName(property.key)) === "client"
79 )
80 report(property);
81 }
82 },
83 MemberExpression(node) {
84 const property = Option.getOrNull(getPropertyName(node.property));
85 if (!allowGuardInstallation && property === "RpcPermissionGuard") report(node);
86 if (!allowRawClientAccess && property === "client") report(node);
87 },
88 };
89 },
90});