1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
[]
= "ts-trpc-procedure"
= ["typescript", "javascript"]
= "middleware"
= "high"
= "tRPC authz procedure builder used at a call site (protectedProcedure, adminProcedure, ...)"
# tRPC's idiomatic authz pattern is a procedure builder:
#
# export const fooRouter = createTRPCRouter({
# bar: protectedProcedure.input(z.object({...})).query(...),
# baz: adminProcedure.mutation(...),
# });
#
# Each `protectedProcedure.<method>(...)` chain is a distinct policy
# decision point: the procedure builder injects the authz middleware
# (auth check, role gate) into every endpoint built from it.
#
# Surfaced by the corpus shakedown on Cal.com — this rule wraps every
# endpoint declared via an authed procedure builder. Sister rule to
# `ts-nestjs-use-guards` (same role for NestJS controllers).
#
# Match shape: `(member_expression object: (identifier) @proc_name)`
# fires exactly once per chain (the leftmost member access whose object
# is a bare identifier), so `protectedProcedure.use(mw).input(x).query(y)`
# yields a single finding rather than one per chained method.
#
# Predicate is `[a-z][A-Za-z]*Procedure` so well-known builders
# (protectedProcedure, adminProcedure, authedProcedure, orgProcedure,
# teamProcedure) and project-specific ones all qualify. Anchored to
# avoid `someUnrelatedProcedure` slipping through unintentionally —
# the suffix is distinctive enough that false positives are rare in
# practice.
#
# No rego template: the policy lives in the procedure builder's
# middleware, not at the call site, so a stub generated here would be
# misleading. The finding is the policy-decision-point marker.
= """
(member_expression
object: (identifier) @proc_name
) @match
"""
[]
= "^[a-z][A-Za-z]*Procedure$"
# -- Positive: tRPC authz builders --
[[]]
= """
export const list = protectedProcedure
.input(z.object({ teamId: z.string() }))
.query(({ ctx, input }) => ctx.db.team.findMany());
"""
= true
[[]]
= """
export const remove = adminProcedure.mutation(({ ctx }) => ctx.db.team.delete());
"""
= true
[[]]
= """
export const m = authedProcedure.use(rateLimit).mutation(handler);
"""
= true
# Custom org-scoped procedure
[[]]
= """
export const x = orgProcedure.query(handler);
"""
= true
# -- Negative: not a procedure builder --
[[]]
= """
const result = api.user.findMany();
"""
= false
[[]]
= """
const x = obj.method();
"""
= false