permdock 0.3.1 → 0.5.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/README.md +37 -35
- package/dist/{openapi-schema--ARjyfQr.js → 2020-C9VN-x8c.js} +5 -132
- package/dist/a2a/index.d.ts +8 -6
- package/dist/a2a/index.js +21 -31
- package/dist/{adapter-context-mlWsVGGg.js → adapter-context-CRhqyzCk.js} +1 -1
- package/dist/adoption-scan-B4R5fnMU.js +107 -0
- package/dist/agent/index.d.ts +3 -0
- package/dist/agent/index.js +1 -0
- package/dist/ai-sdk/index.d.ts +18 -23
- package/dist/ai-sdk/index.js +38 -21
- package/dist/app-data-MuyzVqX8.js +60 -0
- package/dist/approval-header-CJACtHkv.js +23 -0
- package/dist/approval-prompt-Dg5LeAcT.js +62 -0
- package/dist/approval-prompt-Djo_PyZc.d.ts +57 -0
- package/dist/approvals/index.d.ts +5 -3
- package/dist/approvals/index.js +8 -5
- package/dist/approvals-DJ6Yd5z1.js +995 -0
- package/dist/{approvers-DT4AVyIg.js → approvers-CqPOLD-o.js} +1 -1
- package/dist/arazzo-B6lebmiQ.js +36 -0
- package/dist/{arazzo-BAgw9ap6.js → arazzo-BWhNF-k0.js} +24 -17
- package/dist/args-IIolxxKA.js +197 -0
- package/dist/astro/index.d.ts +43 -0
- package/dist/astro/index.js +27 -0
- package/dist/audience-Dg8XsRaO.js +55 -0
- package/dist/auth-DRLEizbL.js +178 -0
- package/dist/authzen/index.d.ts +26 -6
- package/dist/authzen/index.js +228 -75
- package/dist/automation-BMZPIVUM.d.ts +114 -0
- package/dist/automation-DVjOckjV.js +157 -0
- package/dist/better-auth/index.d.ts +8 -6
- package/dist/better-auth/index.js +11 -23
- package/dist/better-supabase/index.d.ts +438 -86
- package/dist/better-supabase/index.js +1027 -184
- package/dist/bind--9EFVYWF.d.ts +38 -0
- package/dist/{bind-CuzGytRf.js → bind-CZzhx-Vj.js} +22 -10
- package/dist/body-CH5of8Ne.js +10 -0
- package/dist/body-text-QcXok_SI.js +52 -0
- package/dist/build-instance-BMnAudHn.js +52 -0
- package/dist/{canonical-json-DemiK4Vp.js → canonical-json-BC8_nMTw.js} +6 -6
- package/dist/{capability-2kYTMWDv.d.ts → capability-BSslw1QV.d.ts} +44 -25
- package/dist/{capability-4qPdO9gk.js → capability-N1K9IpqS.js} +69 -42
- package/dist/casbin-Cr7RLtA3.js +30 -0
- package/dist/casbin-Dyu2gnUD.js +234 -0
- package/dist/casl-64gpT-8x.js +24 -0
- package/dist/casl-DjEDhTCP.js +299 -0
- package/dist/catalog/index.d.ts +74 -2
- package/dist/catalog/index.js +51 -2
- package/dist/catalog-BECMPrwc.js +100 -0
- package/dist/catalog-DjhMYYCq.js +1 -0
- package/dist/catalog-R_A__RsH.js +52 -0
- package/dist/{catalog-doc-DdGm82XT.js → catalog-doc-C9wjsfNY.js} +54 -25
- package/dist/catalog-fingerprint-CCP0-yQS.js +153 -0
- package/dist/chat/index.d.ts +2 -0
- package/dist/chat/index.js +224 -0
- package/dist/chat-sdk/index.d.ts +87 -0
- package/dist/chat-sdk/index.js +197 -0
- package/dist/claims-map-7oQ3ue4X.js +13 -0
- package/dist/claims-map-Ca-QX8C6.d.ts +32 -0
- package/dist/claude-agent/index.d.ts +7 -14
- package/dist/claude-agent/index.js +26 -30
- package/dist/clerk/index.d.ts +9 -8
- package/dist/clerk/index.js +15 -45
- package/dist/cli/bin.js +36 -17
- package/dist/cli/index.d.ts +20 -6
- package/dist/cli/index.js +164 -4
- package/dist/cloud/index.d.ts +102 -63
- package/dist/cloud/index.js +240 -347
- package/dist/cloud-CIoHNevm.js +87 -0
- package/dist/codemod-Bmj9dWMH.js +37 -0
- package/dist/codemod-Cxvvc70T.js +2084 -0
- package/dist/{collect-DSTE3ml9.js → collect-CrZJJnEF.js} +79 -17
- package/dist/collect-DloHbDVa.js +55 -0
- package/dist/collect-FlXJlhus.js +1 -0
- package/dist/command-L8EstFNm.js +6 -0
- package/dist/{compact-y6vfu2_f.js → compact-CAf557ZB.js} +11 -1
- package/dist/compile/index.d.ts +1 -1
- package/dist/compile/index.js +1 -2
- package/dist/{compile-Ba4blROR.js → compile-CMqMx_IN.js} +13 -9
- package/dist/{compile-BkRyBDBw.d.ts → compile-Ckm2HqBZ.d.ts} +5 -3
- package/dist/completion-BqhRBJD8.js +143 -0
- package/dist/config-B46rm9sc.js +26 -0
- package/dist/{config-BJiM-xAm.js → config-bs_kZeYq.js} +15 -10
- package/dist/{config-report-CdCgbRnV.js → config-report-DUgj62Ai.js} +17 -8
- package/dist/{connection-CU8MQxRi.js → connection-DjhIJSs2.js} +8 -6
- package/dist/constants-DcJABbLW.js +20 -0
- package/dist/{context-Bcy85rGi.js → context-bbFHR-0D.js} +16 -5
- package/dist/convex/index.d.ts +11 -10
- package/dist/convex/index.js +18 -18
- package/dist/create-0VZ7Pdiu.d.ts +107 -0
- package/dist/create-BDS7wc3y.d.ts +138 -0
- package/dist/create-BFfm17UA.d.ts +110 -0
- package/dist/create-BWRSXgOF.js +403 -0
- package/dist/create-C3b4B5eF.js +130 -0
- package/dist/create-DnrmLnLW.js +323 -0
- package/dist/{credential-8P81ff4u.js → credential-DM2um3ha.js} +153 -32
- package/dist/credentials-DPyHmYz5.js +74 -0
- package/dist/deciding-columns-Sq12mEA6.js +123 -0
- package/dist/delegation-DaXVdDhM.js +286 -0
- package/dist/delegation-input-FTxMK3ji.js +19 -0
- package/dist/delivery/index.d.ts +5 -0
- package/dist/delivery/index.js +5 -0
- package/dist/delivery-DCAgNyCn.js +499 -0
- package/dist/delivery-event-xX4obB8F.js +40 -0
- package/dist/denied-text-D8jDHgsl.d.ts +19 -0
- package/dist/{describe-DMYiCjJV.d.ts → describe-BZUsixiL.d.ts} +11 -2
- package/dist/{describe-B_gVZEVi.js → describe-BybMRY7A.js} +10 -5
- package/dist/device-BARQ8eYG.js +193 -0
- package/dist/diff-CxZo9pTq.js +37 -0
- package/dist/{diff-2VaeChzX.js → diff-DCePiN6M.js} +61 -55
- package/dist/doctor-CxiwSfqH.js +44 -0
- package/dist/doctor-DFjUxf2f.js +2361 -0
- package/dist/drizzle/index.d.ts +5 -3
- package/dist/drizzle/index.js +6 -14
- package/dist/elysia/index.d.ts +16 -13
- package/dist/elysia/index.js +14 -13
- package/dist/env-DJZrPjRi.js +8 -0
- package/dist/errors-BDUwg2qC.js +27 -0
- package/dist/{errors-Cz5zn0mW.js → errors-BKy0EfhN.js} +6 -10
- package/dist/errors-BWXBMaKK.d.ts +11 -0
- package/dist/{errors-DPdqI50v.d.ts → errors-C2PXIv4c.d.ts} +2 -2
- package/dist/errors-CWTY6uih.js +143 -0
- package/dist/evaluate-_R8QkWaf.js +1732 -0
- package/dist/evaluations-16UqqQ6W.d.ts +32 -0
- package/dist/{evaluations-tz6D9A2G.js → evaluations-DZ2CbV5k.js} +181 -32
- package/dist/eve/index.d.ts +12 -16
- package/dist/eve/index.js +23 -29
- package/dist/event-time-CsegxXxB.js +23 -0
- package/dist/expiry-D4_YKRKD.js +52 -0
- package/dist/express/index.d.ts +13 -10
- package/dist/express/index.js +14 -12
- package/dist/fastify/index.d.ts +26 -13
- package/dist/fastify/index.js +17 -36
- package/dist/fetch-adapter-g0ypLkID.js +120 -0
- package/dist/fetch-adapter-qKJBuRqy.d.ts +31 -0
- package/dist/fields-D9tPVUUS.js +203 -0
- package/dist/files-C5ZI27eV.js +124 -0
- package/dist/{files-DWUN9PzI.js → files-D_JIzUrP.js} +12 -3
- package/dist/{fixtures-ht28Cplr.js → fixtures-B7XrLeY7.js} +12 -12
- package/dist/flags-C3DktBDY.js +86 -0
- package/dist/{from-snapshot-CTLijvdm.js → from-snapshot-D2lZt4Dd.js} +78 -65
- package/dist/{generate-CH7ThJ14.js → generate-CNU1pIOo.js} +1 -3
- package/dist/grantee-Cn1HyUsI.js +254 -0
- package/dist/grantee-conditions-CcXf8hMM.js +271 -0
- package/dist/grantee-match-DHArqY82.js +142 -0
- package/dist/h3/index.d.ts +37 -0
- package/dist/h3/index.js +35 -0
- package/dist/headers-BIjfMaoC.js +8 -0
- package/dist/headers-CIbDTUTK.d.ts +7 -0
- package/dist/{helpers-H_KARDVC.js → helpers-DTcZOzhF.js} +47 -39
- package/dist/hono/index.d.ts +12 -9
- package/dist/hono/index.js +14 -13
- package/dist/{hosted-DJQw46hn.js → hosted-BZIAYvon.js} +64 -15
- package/dist/hosted-DBDX0M-P.d.ts +78 -0
- package/dist/{http-SO7qGttf.js → http-BBoKefeS.js} +35 -2
- package/dist/{http-DPuDaChb.d.ts → http-B_LphE5Y.d.ts} +1 -2
- package/dist/http-DXJ37RDA.js +46 -0
- package/dist/{http-BIhmLfCc.js → http-ujKm0JXE.js} +3 -26
- package/dist/{openapi-import-cmwetbzw.js → import-CNV7I2Dw.js} +30 -26
- package/dist/{rls-import-4lLW_pbb.js → import-DMmOG27O.js} +271 -128
- package/dist/index-BcHcPyIo.d.ts +110 -0
- package/dist/index-CaxrMD1A.d.ts +1 -0
- package/dist/index-Chrs5fQ-.d.ts +210 -0
- package/dist/index-CxGUswIC.d.ts +431 -0
- package/dist/index-D8EovfsP.d.ts +91 -0
- package/dist/index-DOVf7RJv.d.ts +900 -0
- package/dist/{index-C8z4RjQV.d.ts → index-SGdIQyzc.d.ts} +34 -16
- package/dist/index.d.ts +20 -14
- package/dist/index.js +33 -23
- package/dist/init-DeMYsnYJ.js +96 -0
- package/dist/init-GG6Jh8HD.js +95 -0
- package/dist/{instance-options-jARdwfPN.d.ts → instance-options-Omfd793B.d.ts} +2 -2
- package/dist/instance-snapshot-Cxq4ygFc.js +600 -0
- package/dist/instant-BluqDvyS.js +19 -0
- package/dist/internals-Dk8A8fX3.js +17 -0
- package/dist/{json-schema-BsAflUGW.js → json-schema-B-vAwlde.js} +46 -9
- package/dist/json-schema-CN_iLFH2.d.ts +40 -0
- package/dist/jwt/index.d.ts +18 -11
- package/dist/jwt/index.js +166 -145
- package/dist/{kernel-_RRHmnMY.js → kernel-5JatXyND.js} +120 -43
- package/dist/kysely/index.d.ts +5 -3
- package/dist/kysely/index.js +6 -14
- package/dist/{live-options-H2eG8dfJ.js → live-options-BaAFCIiu.js} +16 -3
- package/dist/load-BxEtwlBS.js +1 -0
- package/dist/{load-DhWEB9P1.js → load-DFS_ry0W.js} +31 -7
- package/dist/{load-jose-BL6LH4bL.js → load-jose-DZ4dnjri.js} +1 -4
- package/dist/login-Dfe5RlYk.js +68 -0
- package/dist/login-imhuwvn3.js +254 -0
- package/dist/manifest-C8IzW3Do.js +42 -0
- package/dist/manifest-CtKR3GXL.js +370 -0
- package/dist/manifest-json-CAGzls-C.js +9 -0
- package/dist/{manifest-uDS9y5de.d.ts → manifest-mx5ghSFM.d.ts} +9 -2
- package/dist/manifest-schema-C0kK_8V0.js +399 -0
- package/dist/{map-claims-Z4QZOiFF.js → map-claims-D3oRmTk0.js} +19 -25
- package/dist/{map-error-pgUWNNtG.js → map-error-TCAdw-eW.js} +5 -5
- package/dist/{markers-B0hYLvMD.js → markers-C6joDI3o.js} +8 -3
- package/dist/mcp/index.d.ts +36 -9
- package/dist/mcp/index.js +476 -448
- package/dist/{rls-migrate-CN4TlJTO.js → migrate-BShP7Vf4.js} +119 -20
- package/dist/nest/index.d.ts +12 -9
- package/dist/nest/index.js +20 -20
- package/dist/next/index.d.ts +40 -20
- package/dist/next/index.js +37 -39
- package/dist/next/plugin.d.ts +1 -1
- package/dist/next/plugin.js +2 -2
- package/dist/node/index.d.ts +10 -10
- package/dist/node/index.js +7 -9
- package/dist/ocsf-BFp-PZZA.js +126 -0
- package/dist/openai/index.d.ts +9 -16
- package/dist/openai/index.js +18 -25
- package/dist/openapi/index.d.ts +3 -3
- package/dist/openapi/index.js +1 -1
- package/dist/{openapi-BqRBvAng.js → openapi-B7HZ0f-r.js} +9 -12
- package/dist/{openapi-CqqEbHsL.js → openapi-DTaEmTSW.js} +23 -26
- package/dist/openapi-ahr8rHu3.js +138 -0
- package/dist/operation-findings-DgATDpvh.js +36 -0
- package/dist/operations-CxybZErY.d.ts +59 -0
- package/dist/orpc/index.d.ts +13 -10
- package/dist/orpc/index.js +23 -20
- package/dist/otel/index.d.ts +3 -2
- package/dist/otel/index.js +53 -27
- package/dist/output-Dyw8fyPF.js +88 -0
- package/dist/output-files-B3IZJksC.js +1 -0
- package/dist/output-files-DhRBBE4n.js +138 -0
- package/dist/{rls-fields-CZOMlwHl.js → ownership-BatFgAA4.js} +2393 -2349
- package/dist/{package-root-zGkxO3oN.js → package-root-Dk_ETdkp.js} +4 -3
- package/dist/{parse-BuyjxhZV.js → parse-Cxq0WxrA.js} +3 -3
- package/dist/{parse-local-manifest-BrMozJCo.js → parse-local-manifest-CVSs6J9I.js} +2 -4
- package/dist/{parse-local-manifest-JLnaBH0S.d.ts → parse-local-manifest-DgqtruPn.d.ts} +1 -1
- package/dist/parse-request-D_tUXG6M.js +26 -0
- package/dist/paths-DXSHAMIa.js +42 -0
- package/dist/pdp/index.d.ts +7 -3
- package/dist/pdp/index.js +47 -29
- package/dist/{peer-BOr_kJE1.js → peer-C8swJ2GH.js} +23 -4
- package/dist/peer-CRvC7Hg1.js +1 -0
- package/dist/permdock-B9JxkBwC.js +868 -0
- package/dist/permdock-D7eeLJER.d.ts +502 -0
- package/dist/permission-registry-Br4jSNVi.js +385 -0
- package/dist/permissions-B-wCP5ZC.js +30 -0
- package/dist/{permissions-RJrovw98.js → permissions-CZKiUfFM.js} +4 -3
- package/dist/{permissions-CDgvKG5L.d.ts → permissions-oEWfl-kl.d.ts} +1 -2
- package/dist/policies-CO1oOymN.js +295 -0
- package/dist/{policy-D2W7uv19.d.ts → policy-Czo4n3Hs.d.ts} +2 -2
- package/dist/policy-DLZJ5gAp.js +399 -0
- package/dist/policy-from-catalog-DzhEc3Y9.js +450 -0
- package/dist/policy-module-CTlwMvLI.js +89 -0
- package/dist/{policy-IB6yxpC3.d.ts → policy-types-DzwivBgR.d.ts} +939 -1057
- package/dist/postgrest-CW0IqC-U.js +566 -0
- package/dist/postgrest-NUqZT-RB.d.ts +100 -0
- package/dist/powersync-BHfuehf3.js +62 -0
- package/dist/powersync-CbzkqL_M.js +1 -0
- package/dist/{powersync-Dv1_aWF7.js → powersync-bYUDLOAO.js} +30 -32
- package/dist/preset/index.d.ts +146 -0
- package/dist/preset/index.js +1 -0
- package/dist/preset-DZdU0tne.js +1768 -0
- package/dist/principal-source-pHneukrx.js +103 -0
- package/dist/prisma/index.d.ts +6 -3
- package/dist/prisma/index.js +6 -12
- package/dist/{problem-B43ztKIm.js → problem-CTviyH5L.js} +18 -27
- package/dist/{problem-details-DX075l9V.d.ts → problem-details-DXlt-vks.d.ts} +1 -1
- package/dist/{problem-details-_AMOYz-w.js → problem-details-cn5X30gZ.js} +45 -42
- package/dist/problem-types-CJ2325SZ.js +5 -0
- package/dist/{prompt-CXYhLfsS.js → prompt-DfZLJLIn.js} +2 -2
- package/dist/protected-DEPHc2xV.d.ts +38 -0
- package/dist/{protected-utU-SLuy.js → protected-DfjY5mLA.js} +37 -8
- package/dist/{provider-CSQd7m92.js → provider-CC-u3bYP.js} +23 -10
- package/dist/{provider-DZY281jO.d.ts → provider-DMVt6yYn.d.ts} +1 -1
- package/dist/{provider-store-Cfbvo4DE.js → provider-store-B5Yrkhzr.js} +6 -3
- package/dist/pull-BgIfno6w.js +256 -0
- package/dist/pull-ByOks_Tg.js +1 -0
- package/dist/push-CmFVBOeI.js +116 -0
- package/dist/pushed-policy-BImMTTIM.d.ts +57 -0
- package/dist/pushed-policy-DobP7AHI.js +238 -0
- package/dist/react/index.d.ts +8 -8
- package/dist/react/index.js +6 -6
- package/dist/react/provider-client.d.ts +1 -1
- package/dist/react/provider-client.js +1 -2
- package/dist/react-native/index.d.ts +24 -63
- package/dist/react-native/index.js +45 -27
- package/dist/{refs-KYb4ntaz.js → refs-K6eWUJ0H.js} +1 -1
- package/dist/relation-walk-3OyDBF0S.js +186 -0
- package/dist/relations-BnCWnJGI.js +163 -0
- package/dist/renames-CGj7YwcQ.js +175 -0
- package/dist/{replay-CYyiLiwO.d.ts → replay-D7iKcLFe.d.ts} +1 -1
- package/dist/{replay-BsOyTrdV.js → replay-Du1tJjG7.js} +2 -1
- package/dist/request-context-BTRTG7OT.js +71 -0
- package/dist/request-context-C2_1lchh.d.ts +5 -0
- package/dist/request-schema-CMreVuFM.js +148 -0
- package/dist/resend/index.d.ts +126 -0
- package/dist/resend/index.js +379 -0
- package/dist/resource-relations-D_jw0BFx.js +243 -0
- package/dist/retire-CALjIQ7e.js +240 -0
- package/dist/{revocations-Cy06JNx2.d.ts → revocations-R07lFBnX.d.ts} +2 -0
- package/dist/rls-BA8an5Ap.js +336 -0
- package/dist/{rls-DiinlRML.js → rls-j6t43t7n.js} +1066 -1068
- package/dist/role-builder-CXbis5-X.js +623 -0
- package/dist/{rpc-DL9Sqvb1.js → rpc-aPyNi6tA.js} +18 -8
- package/dist/run-CNsApDpd.js +1 -0
- package/dist/run-DOfMtc9V.js +723 -0
- package/dist/run-ticket-BjhTOi6u.d.ts +124 -0
- package/dist/run-ticket-sxFI6LAk.js +337 -0
- package/dist/{runtime-CB8gQFvS.js → runtime-D3VTAiCA.js} +1 -1
- package/dist/{saas-DhpJjnBA.js → saas-FO-Q8oj0.js} +18 -15
- package/dist/scan-BOI5tJ-6.js +29 -0
- package/dist/{schema-D3MnDMxa.js → schema-B4v0Qrac.js} +106 -10
- package/dist/schema-yLuBWWsh.js +130 -0
- package/dist/scim/index.d.ts +2 -2
- package/dist/scim/index.js +1 -2
- package/dist/{scim-BFGBBAqR.js → scim-ClHtpyV3.js} +213 -87
- package/dist/{scopes-CVdPrWN-.js → scopes-CUPSjo9q.js} +20 -7
- package/dist/server/index.d.ts +13 -134
- package/dist/server/index.js +13 -10
- package/dist/server-DqCT4CgZ.js +453 -0
- package/dist/{create-B1d519XK.d.ts → server-types-BLd4MnBX.d.ts} +89 -70
- package/dist/{sha256-BO0swRXL.js → sha256-D9ihqmvd.js} +8 -1
- package/dist/shared-CD11nBbC.js +304 -0
- package/dist/{shared-DRu8VFVM.js → shared-DvvFzYXe.js} +8 -9
- package/dist/signals-D3NcTKUS.js +32 -0
- package/dist/sink-BEZSlMS_.js +957 -0
- package/dist/sink-Bj_lDUDq.d.ts +225 -0
- package/dist/{skills-Biq5uup3.js → skills-B6lVdD_W.js} +16 -8
- package/dist/skills-CTYwkl44.js +51 -0
- package/dist/snapshot-CXUWfsNy.js +269 -0
- package/dist/{snapshot-cache-D6jLelln.js → snapshot-cache-DZHQ0KF6.js} +11 -5
- package/dist/{snapshot-cache-B-LxUahj.d.ts → snapshot-cache-DqK9eDmM.d.ts} +7 -5
- package/dist/solid/index.d.ts +24 -25
- package/dist/solid/index.js +41 -11
- package/dist/solid-start/index.d.ts +52 -0
- package/dist/solid-start/index.js +49 -0
- package/dist/{sources-BdOYLw9j.d.ts → sources-BV9j0DeZ.d.ts} +11 -13
- package/dist/{sources-4p0-4xQm.js → sources-vs5dGOsS.js} +147 -142
- package/dist/sql-B5T1IL83.js +55 -0
- package/dist/src-DLfUWFAI.js +1851 -0
- package/dist/ssf/index.d.ts +4 -4
- package/dist/ssf/index.js +440 -106
- package/dist/standard-webhooks-6lg8v_is.d.ts +88 -0
- package/dist/standard-webhooks-DgFFD-Ga.js +228 -0
- package/dist/storage-C2bWdvJH.js +132 -0
- package/dist/{store-DMAWwAJV.js → store-4u_hxjWa.js} +42 -55
- package/dist/{store-options-ByhZq1jV.js → store-options-BqEakYRa.js} +38 -24
- package/dist/{stores-BT2pSCub.js → stores-BbsD4fAR.js} +7 -3
- package/dist/stores-ClZUgeoP.d.ts +30 -0
- package/dist/{stream-D1RXubw2.d.ts → stream-7-xmnf3U.d.ts} +2 -2
- package/dist/{stream-DDpvynSB.js → stream-CvdUS9V1.js} +10 -2
- package/dist/style-DiKWnAvn.js +1 -0
- package/dist/{style-CTm1AXDT.js → style-Uao76wO7.js} +3 -2
- package/dist/subject-BGoM5o8B.js +17 -0
- package/dist/{subject-settings-BwBmCdTR.js → subject-settings-CkEwrw3W.js} +23 -5
- package/dist/supabase/index.d.ts +23 -106
- package/dist/supabase/index.js +5 -4
- package/dist/supabase/middleware.d.ts +13 -6
- package/dist/supabase/middleware.js +16 -11
- package/dist/supabase-9pqI57pW.js +92 -0
- package/dist/{supabase-C0qESOa-.js → supabase-Dxlp8A94.js} +124 -247
- package/dist/supabase-hook-CXcGpHqq.js +1 -0
- package/dist/{supabase-hook-ntETYCQu.js → supabase-hook-DdDQhMlw.js} +1659 -819
- package/dist/{supautils-u8jXTGrt.js → supautils-B6HYL9eL.js} +2 -2
- package/dist/support-session-C6xJQYut.js +158 -0
- package/dist/svelte/Protected.svelte +11 -4
- package/dist/svelte/index.d.ts +7 -7
- package/dist/svelte/index.js +26 -12
- package/dist/svelte/runtime.d.ts +10 -3
- package/dist/svelte/runtime.js +4 -3
- package/dist/svelte/source.d.ts +7 -7
- package/dist/svelte/source.js +5 -5
- package/dist/sveltekit/index.d.ts +51 -0
- package/dist/sveltekit/index.js +27 -0
- package/dist/tanstack-start/index.d.ts +51 -0
- package/dist/tanstack-start/index.js +40 -0
- package/dist/target-Bw37TFNA.js +51 -0
- package/dist/{validity-uOp8UpYn.js → tenancy-DN2QAx9z.js} +6 -42
- package/dist/terminal/index.d.ts +11 -9
- package/dist/terminal/index.js +36 -316
- package/dist/testing/index.d.ts +391 -106
- package/dist/testing/index.js +4574 -1286
- package/dist/testing/saas/index.d.ts +9 -8
- package/dist/testing/saas/index.js +2 -2
- package/dist/testing/saas/permissions.d.ts +1 -1
- package/dist/testing/saas/permissions.js +1 -2
- package/dist/text-BMFRfBqc.js +8 -0
- package/dist/{text-diff-BdkkYSbi.js → text-diff-BQcUapOb.js} +1 -1
- package/dist/thenable-yD7SabqG.js +31 -0
- package/dist/time-DkWjdL98.js +71 -0
- package/dist/{token-CIWxJOCh.js → token-BLZcjQgt.js} +15 -6
- package/dist/tool-refusal-CMLN5xhn.js +41 -0
- package/dist/trpc/index.d.ts +12 -9
- package/dist/trpc/index.js +18 -17
- package/dist/{types-DbjRN55U.d.ts → types-5Ut_pbKy.d.ts} +141 -13
- package/dist/types-BNORmDM8.d.ts +172 -0
- package/dist/types-BjV-fqcA.d.ts +57 -0
- package/dist/{types-QCuYJh4q.d.ts → types-BqAKcRNW.d.ts} +2 -1
- package/dist/types-C8-KNsFf.d.ts +107 -0
- package/dist/{types-aqOHTHcp.d.ts → types-CCpc2Dcv.d.ts} +77 -17
- package/dist/{types-Dnemwd77.d.ts → types-CZTnPFzM.d.ts} +50 -11
- package/dist/{types-C97IskBw.d.ts → types-CdlxsYQh.d.ts} +23 -11
- package/dist/{types-DxiCGmmd.d.ts → types-ClZaWzfb.d.ts} +9 -6
- package/dist/types-DX0LaKTb.d.ts +157 -0
- package/dist/types-DiQjupG2.d.ts +332 -0
- package/dist/types-DqzsnlSA.js +67 -0
- package/dist/types-_sHy8BQf.d.ts +65 -0
- package/dist/types-fbZ7JEXr.d.ts +89 -0
- package/dist/types-tIgIeIKJ.d.ts +66 -0
- package/dist/unplugin/index.d.ts +1 -1
- package/dist/unplugin/index.js +2 -2
- package/dist/usage-CHOtwL19.js +1 -0
- package/dist/usage-DHtE8j4E.js +32 -0
- package/dist/{usage-ItS7nLcl.js → usage-T5mXq98v.js} +56 -13
- package/dist/{validation-error-Ba264OaJ.d.ts → validation-error-Bg5Tpluk.d.ts} +1 -1
- package/dist/{validation-error-CnjZ4t92.js → validation-error-ZfUHbKvf.js} +1 -1
- package/dist/validity-CZ7-y0Cj.js +79 -0
- package/dist/{verifier-BxBo6I44.js → verifier-DHuh-okk.js} +68 -21
- package/dist/verify-DbCL9bnB.js +1770 -0
- package/dist/{version-BvacVTZR.js → version-CEbQ5UeM.js} +4 -3
- package/dist/version-DXkYUISx.js +1 -0
- package/dist/{views-Soe7bGSC.js → views-DTIIO-oQ.js} +41 -2
- package/dist/{permissions-DttnqqxC.d.ts → vocabulary-D6lPdb4Z.d.ts} +120 -29
- package/dist/vue/index.d.ts +25 -21
- package/dist/vue/index.js +25 -9
- package/dist/waiter-BGUtGYoU.js +67 -0
- package/dist/waiter-CKy4MQSY.d.ts +36 -0
- package/dist/watch-Cb-NcaUw.js +1 -0
- package/dist/{watch-DVBJ9cBq.js → watch-CszoTJMq.js} +47 -8
- package/dist/webmcp/index.d.ts +2 -2
- package/dist/webmcp/index.js +13 -37
- package/dist/where-scope-Dv-WTP-w.js +306 -0
- package/dist/who-can-D0QoYbEb.js +364 -0
- package/dist/wire-delegation-Cv3W2eaM.js +36 -0
- package/dist/wire-denial-C7PaVYEM.js +27 -0
- package/dist/workflow/index.d.ts +5 -0
- package/dist/workflow/index.js +223 -0
- package/dist/workflow-sdk/index.d.ts +56 -0
- package/dist/workflow-sdk/index.js +78 -0
- package/dist/wrap-Bd1Tejur.d.ts +15 -0
- package/dist/wrap-gX61EtpB.js +27 -0
- package/package.json +140 -41
- package/schemas/access-event-v1.json +118 -0
- package/schemas/approval-request-v1.json +34 -1
- package/schemas/approval-signature-v1.json +94 -0
- package/schemas/automation-event-v1.json +113 -0
- package/schemas/capability-v1.json +84 -0
- package/schemas/catalog-event-v1.json +87 -0
- package/schemas/catalog-v1.json +129 -12
- package/schemas/cli-commands-v1.json +164 -0
- package/schemas/cli-result-v1.json +96 -0
- package/schemas/cloud-api-v1.json +5407 -0
- package/schemas/connector-v1.json +408 -0
- package/schemas/credential-event-v1.json +81 -0
- package/schemas/credential-v1.json +134 -0
- package/schemas/decision-event-v1.json +288 -0
- package/schemas/delivery-event-v1.json +117 -0
- package/schemas/directory-event-v1.json +85 -0
- package/schemas/evidence-export-v1.json +54 -0
- package/schemas/membership-event-v1.json +93 -0
- package/schemas/policy-v1.json +68 -0
- package/schemas/pushed-policy-v1.json +236 -0
- package/schemas/run-v1.json +162 -0
- package/schemas/snapshot-v1.json +138 -0
- package/schemas/supabase-claims-v1.json +4 -4
- package/schemas/supabase-manifest-v1.json +5 -1
- package/schemas/support-session-v1.json +111 -0
- package/skills/permdock/SKILL.md +16 -12
- package/skills/permdock-agents/SKILL.md +40 -23
- package/skills/permdock-agents/references/operations-automations-chat.md +221 -0
- package/skills/permdock-agents/references/runtimes.md +22 -18
- package/skills/permdock-approvals/SKILL.md +29 -13
- package/skills/permdock-approvals/references/delivery-and-waiters.md +172 -0
- package/skills/permdock-approvals/references/stores-and-resume.md +21 -7
- package/skills/permdock-audit/SKILL.md +3 -3
- package/skills/permdock-credentials/SKILL.md +5 -5
- package/skills/permdock-credentials/references/tokens-keys-links.md +19 -10
- package/skills/permdock-data/SKILL.md +6 -6
- package/skills/permdock-data/references/queries-and-rls.md +13 -8
- package/skills/permdock-data/references/relationships.md +3 -3
- package/skills/permdock-tenancy/SKILL.md +20 -7
- package/skills/permdock-tenancy/references/contacts-and-support-sessions.md +122 -0
- package/skills/permdock-tenancy/references/ownership-and-custom-roles.md +6 -4
- package/skills/permdock-wire/SKILL.md +18 -12
- package/skills/permdock-wire/references/adapters.md +146 -50
- package/skills/permdock-wire/references/policy-options.md +15 -6
- package/skills/permdock-wire/references/presets-and-recipes.md +58 -0
- package/dist/approvals-DbYAuNkG.js +0 -224
- package/dist/arazzo-C9chp3ST.js +0 -54
- package/dist/bind-C_ob3LKa.d.ts +0 -25
- package/dist/catalog-CRW5Mx_B.js +0 -2
- package/dist/catalog-CYj-mX2O.js +0 -48
- package/dist/catalog-DWF5cYqB.js +0 -55
- package/dist/cloud-CfwZ39Rl.js +0 -50
- package/dist/cloud-DWLXO2af.js +0 -118
- package/dist/collect-CHB22Sx4.js +0 -83
- package/dist/collect-DSWOk19R.js +0 -2
- package/dist/commands-BeaGNj_o.js +0 -97
- package/dist/config-ir40y8m0.js +0 -36
- package/dist/create-CMwIl-w2.js +0 -253
- package/dist/delegation-D8pUEuce.js +0 -103
- package/dist/diff-C9RDjjhs.js +0 -48
- package/dist/doctor-Djh6JDEk.js +0 -53
- package/dist/doctor-DvH0IQff.js +0 -1194
- package/dist/doctor-collect-CjdO0wQX.js +0 -665
- package/dist/duration-xK2jGFVc.js +0 -25
- package/dist/errors-CWfoNg9N.js +0 -47
- package/dist/errors-DRnGZHzF.js +0 -30
- package/dist/evaluation-items-CfCCejQd.js +0 -40
- package/dist/expiry-BoBJ8Tvc.js +0 -7
- package/dist/fields-BMl1RxoX.js +0 -26
- package/dist/grantee-Cv5Sogfv.js +0 -602
- package/dist/headers-BZSDieNT.js +0 -6
- package/dist/headers-Cf-zWvsO.d.ts +0 -6
- package/dist/ids-DC4V4CFu.js +0 -18
- package/dist/index-0nNqHspG.d.ts +0 -6
- package/dist/index-BNc7Wer_.d.ts +0 -483
- package/dist/index-C7Tw7MaW.d.ts +0 -201
- package/dist/instance-options-Dhhcnm3y.js +0 -22
- package/dist/load-DwJUafYN.js +0 -2
- package/dist/manifest-iaoBXHKc.js +0 -783
- package/dist/map-AcxK9o_D.js +0 -147
- package/dist/may-use-DndQ5tQ-.js +0 -77
- package/dist/openapi-CQTsgKoB.js +0 -164
- package/dist/permdock-CbgvLnuo.js +0 -3479
- package/dist/permissions-DVoF6dMt.js +0 -662
- package/dist/policy-xyWg7QdR.js +0 -1122
- package/dist/powersync-CrBX8hY6.js +0 -2
- package/dist/powersync-DE19b9dT.js +0 -62
- package/dist/protected-BdnIEFkY.d.ts +0 -27
- package/dist/protected-xAr24FEC.d.ts +0 -40
- package/dist/relations-cjOL01hZ.js +0 -505
- package/dist/rls-P-BeBx0n.js +0 -238
- package/dist/rls-graph-CHwx0V8q.js +0 -435
- package/dist/rls-ownership-DcYlZ09B.js +0 -843
- package/dist/rls-shared-Cewd_-zI.js +0 -495
- package/dist/rls-verify-CMv9H3wV.js +0 -746
- package/dist/run-BWc9qqvE.js +0 -174
- package/dist/server-DlZ28Oz5.js +0 -268
- package/dist/sink-B1fZzIMf.d.ts +0 -104
- package/dist/sink-Bd9PSyfq.js +0 -141
- package/dist/skills-C5CbhhpG.js +0 -56
- package/dist/snapshot-Enxd0n-s.js +0 -975
- package/dist/sql-xdg5f4Fi.js +0 -27
- package/dist/src-BDwEZWrZ.js +0 -568
- package/dist/store-jOBYpiXC.d.ts +0 -36
- package/dist/stores-C0IMQrGI.d.ts +0 -52
- package/dist/subject-C6dLxqHU.js +0 -24
- package/dist/supabase-config-Bj3iDH2I.js +0 -472
- package/dist/supabase-hook-gd3s8bxB.js +0 -2
- package/dist/supabase-s4o9s1O1.js +0 -83
- package/dist/thenable-okc2xDsj.js +0 -10
- package/dist/types-BnIEz1sU.d.ts +0 -48
- package/dist/types-Bvc4oWT6.d.ts +0 -9
- package/dist/types-CKNSyIf_.d.ts +0 -52
- package/dist/types-Cq_H8N_X.d.ts +0 -197
- package/dist/usage-B0YuTQRO.js +0 -42
- package/dist/usage-DorAJiU_.js +0 -2
- package/dist/vocabulary-DA8a8zif.d.ts +0 -56
- package/dist/watch-BwxLTYMZ.js +0 -2
package/dist/doctor-DvH0IQff.js
DELETED
|
@@ -1,1194 +0,0 @@
|
|
|
1
|
-
import { _ as listPermissions, a as formerKeys, m as isPrincipalRelation } from "./permissions-DVoF6dMt.js";
|
|
2
|
-
import { a as hasConditionOp } from "./refs-KYb4ntaz.js";
|
|
3
|
-
import { d as scopeList } from "./scopes-CVdPrWN-.js";
|
|
4
|
-
import { s as flattenGrantee } from "./grantee-Cv5Sogfv.js";
|
|
5
|
-
import { i as describeError } from "./errors-CWfoNg9N.js";
|
|
6
|
-
import { r as DOCTOR_REPORT_SCHEMA } from "./version-BvacVTZR.js";
|
|
7
|
-
import { a as sqlFiles, i as rel, n as doctorSrcPath, r as listSourceFiles } from "./files-DWUN9PzI.js";
|
|
8
|
-
import { a as loadModule, n as asPolicy, o as pickNamed } from "./load-DhWEB9P1.js";
|
|
9
|
-
import { r as runCollect } from "./collect-DSTE3ml9.js";
|
|
10
|
-
import { n as supabaseTenantClaim } from "./budget-NbEtbJC_.js";
|
|
11
|
-
import { d as keptKeys, r as PERMDOCK_SCHEMA } from "./sources-4p0-4xQm.js";
|
|
12
|
-
import "./markers-B0hYLvMD.js";
|
|
13
|
-
import { t as createStyle } from "./style-CTm1AXDT.js";
|
|
14
|
-
import { i as runSkillsInstall } from "./skills-Biq5uup3.js";
|
|
15
|
-
import { a as pd006, c as pd022, d as group, h as tableKey, i as pd005, l as pd028, n as supabaseConfig, o as pd009, p as sqlStatements, r as MIGRATION_DIRS, s as pd012, t as pd045, u as pd040 } from "./supabase-config-Bj3iDH2I.js";
|
|
16
|
-
import { m as compileGrants } from "./rls-fields-CZOMlwHl.js";
|
|
17
|
-
import { a as pd010, c as pd014, d as pd038, f as pd041, i as pd008, l as pd015, n as pd001, o as pd011, p as pd057, r as pd007, s as pd013, u as pd036 } from "./usage-ItS7nLcl.js";
|
|
18
|
-
import { C as pd035, E as policyOf, S as pd034, T as pd055, _ as pd029, a as pd004, b as pd032, c as pd018, d as pd021, f as pd023, g as pd027, h as pd026, i as pd003, l as pd019, m as pd025, n as collectedOf, o as pd016, p as pd024, r as pd002, s as pd017, u as pd020, v as pd030, w as pd037, x as pd033, y as pd031 } from "./doctor-collect-CjdO0wQX.js";
|
|
19
|
-
import { a as ownershipRules, i as guardedTables } from "./rls-ownership-DcYlZ09B.js";
|
|
20
|
-
import { a as supabaseHookManifest, c as attrsPlan, h as partPath, n as hookOut, s as pd039 } from "./supabase-hook-ntETYCQu.js";
|
|
21
|
-
import { r as staleFiles, t as powersyncFiles } from "./powersync-Dv1_aWF7.js";
|
|
22
|
-
import { t as supautilsRejections } from "./supautils-u8jXTGrt.js";
|
|
23
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
24
|
-
import { join, resolve } from "node:path";
|
|
25
|
-
//#region src/cli/doctor-assignments.ts
|
|
26
|
-
const TRIGGER = new RegExp(String.raw`^create\s+(?:or\s+replace\s+)?trigger\s+"?permdock_assignment"?\s[\s\S]*?\bon\s+((?:"[^"]+"|\w+)(?:\.(?:"[^"]+"|\w+))?)`, "iu");
|
|
27
|
-
/** The tables a `permdock_assignment` trigger is created on in the migrations and the `rls generate` output. */
|
|
28
|
-
function triggeredTables(input) {
|
|
29
|
-
const out = input.config.rls?.out ?? "rls.sql";
|
|
30
|
-
const files = sqlFiles(input.cwd, [...input.config.doctor?.migrations ?? MIGRATION_DIRS, ...new Set([
|
|
31
|
-
out,
|
|
32
|
-
partPath(out, "helpers"),
|
|
33
|
-
partPath(out, "policies")
|
|
34
|
-
].filter((file) => file.endsWith(".sql")))]);
|
|
35
|
-
const tables = /* @__PURE__ */ new Set();
|
|
36
|
-
for (const file of files) for (const statement of sqlStatements(readFileSync(file, "utf8"))) {
|
|
37
|
-
const match = TRIGGER.exec(statement.text);
|
|
38
|
-
if (match !== null) tables.add(tableKey(group(match, 1)));
|
|
39
|
-
}
|
|
40
|
-
return tables;
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
* PD064: `rls.assignments` guards a table (a membership table, the
|
|
44
|
-
* global-roles table or a listed table) that no `permdock_assignment`
|
|
45
|
-
* trigger is created on in the migrations or the `rls generate` output, so a
|
|
46
|
-
* client may write any role there.
|
|
47
|
-
*/
|
|
48
|
-
async function pd064(input) {
|
|
49
|
-
if (input.config.rls?.assignments === void 0) return [];
|
|
50
|
-
const policy = await policyOf(input);
|
|
51
|
-
if (policy === void 0) return [];
|
|
52
|
-
const scopes = scopeList(policy.scopes);
|
|
53
|
-
if ((ownershipRules(policy, scopes)?.assigns.length ?? 0) === 0) return [];
|
|
54
|
-
const found = triggeredTables(input);
|
|
55
|
-
return guardedTables(input.config, scopes).filter((table) => !found.has(tableKey(table))).map((table) => ({
|
|
56
|
-
code: "PD064",
|
|
57
|
-
severity: "warning",
|
|
58
|
-
message: `rls.assignments guards ${table}, but no migration creates its permdock_assignment trigger, so a client may write any role there`,
|
|
59
|
-
fix: "run permdock rls generate and apply its output in a migration, or set rls.out or doctor.migrations to where it lives"
|
|
60
|
-
}));
|
|
61
|
-
}
|
|
62
|
-
//#endregion
|
|
63
|
-
//#region src/cli/doctor-declarative.ts
|
|
64
|
-
const SCHEMAS = "supabase/schemas";
|
|
65
|
-
const MIGRATIONS = "supabase/migrations";
|
|
66
|
-
const CREATES_HOOK = /\bcreate\s+(?:or\s+replace\s+)?function\s+[\w."]*custom_access_token_hook\b/iu;
|
|
67
|
-
const DROPS_HOOK = /\bdrop\s+function\s+(?:if\s+exists\s+)?[\w."]*custom_access_token_hook\b/iu;
|
|
68
|
-
const GRANTS_HOOK = /\bgrant\s+execute\s+on\s+function\s+[\w."]*custom_access_token_hook\s*\(\s*jsonb\s*\)\s+to\s+supabase_auth_admin\b/iu;
|
|
69
|
-
const DEFINES_HELPER = /\bcreate\s+(?:or\s+replace\s+)?function\s+[\w."]*permdock_has\s*\(/iu;
|
|
70
|
-
const CALLS_HELPER = /\b(?:permdock_has|(?:permitted|member)_[a-z][a-z0-9_]*_ids)\s*\(/iu;
|
|
71
|
-
function uncommented(text) {
|
|
72
|
-
return text.replaceAll(/--[^\n]*/gu, "").replaceAll(/\/\*[\s\S]*?\*\//gu, "");
|
|
73
|
-
}
|
|
74
|
-
/**
|
|
75
|
-
* PD042: a hook declared under `supabase/schemas` reaches the database through
|
|
76
|
-
* `supabase db diff`, which drops its `supabase_auth_admin` grants. pg-delta
|
|
77
|
-
* keeps them, so the check is off under `[experimental.pgdelta]`. A function
|
|
78
|
-
* created (or dropped and created again) without them is executable by
|
|
79
|
-
* `public` and not by the auth server, so a migration at or after the one
|
|
80
|
-
* that creates it must carry the grants.
|
|
81
|
-
*/
|
|
82
|
-
function pd042(cwd, config) {
|
|
83
|
-
if (config.supabase?.hook === void 0 || supabaseConfig(cwd).pgDelta !== void 0) return [];
|
|
84
|
-
if (!sqlFiles(cwd, [SCHEMAS]).some((file) => readFileSync(file, "utf8").startsWith(`-- permdock:hook v1 `))) return [];
|
|
85
|
-
const migrations = sqlFiles(cwd, [MIGRATIONS]).map((file) => ({
|
|
86
|
-
file,
|
|
87
|
-
text: uncommented(readFileSync(file, "utf8"))
|
|
88
|
-
}));
|
|
89
|
-
const created = migrations.findIndex(({ text }) => CREATES_HOOK.test(text));
|
|
90
|
-
const dropped = migrations.findLastIndex(({ text }) => DROPS_HOOK.test(text));
|
|
91
|
-
const needed = Math.max(created, dropped);
|
|
92
|
-
if (needed === -1) return [];
|
|
93
|
-
if (migrations.findLastIndex(({ text }) => GRANTS_HOOK.test(text)) >= needed) return [];
|
|
94
|
-
return [{
|
|
95
|
-
code: "PD042",
|
|
96
|
-
severity: "error",
|
|
97
|
-
message: `${rel(cwd, migrations[needed]?.file ?? MIGRATIONS)} creates custom_access_token_hook from supabase/schemas, and no migration from it on grants it to supabase_auth_admin: supabase db diff drops those grants, so the auth server cannot call the hook and public can`,
|
|
98
|
-
fix: "generate the grants with permdock supabase hook generate --grants-out (or rls generate --split ...,hook --grants-out) into a new file from supabase migration new, after the db diff migration"
|
|
99
|
-
}];
|
|
100
|
-
}
|
|
101
|
-
/** The `[db.migrations] schema_paths` globs of `supabase/config.toml`, or Supabase's default. */
|
|
102
|
-
function schemaPaths(cwd) {
|
|
103
|
-
const paths = supabaseConfig(cwd).schemaPaths ?? [];
|
|
104
|
-
return paths.length === 0 ? ["./schemas/**/*.sql"] : paths;
|
|
105
|
-
}
|
|
106
|
-
/** Schema files in the order `supabase db diff` applies them: each glob in turn, sorted within it. */
|
|
107
|
-
function orderedSchemaFiles(cwd) {
|
|
108
|
-
return sqlFiles(join(cwd, "supabase"), schemaPaths(cwd), { order: "entry" });
|
|
109
|
-
}
|
|
110
|
-
/**
|
|
111
|
-
* PD043: `db diff` applies schema files in `schema_paths` order, so a policy
|
|
112
|
-
* that calls `permdock_has` or `permitted_<scope>_ids` before the file that
|
|
113
|
-
* defines them fails to apply. pg-delta orders by dependencies instead.
|
|
114
|
-
*/
|
|
115
|
-
function pd043(cwd) {
|
|
116
|
-
if (supabaseConfig(cwd).pgDelta !== void 0) return [];
|
|
117
|
-
if (!existsSync(join(cwd, SCHEMAS))) return [];
|
|
118
|
-
const files = orderedSchemaFiles(cwd).map((file) => ({
|
|
119
|
-
file,
|
|
120
|
-
text: uncommented(readFileSync(file, "utf8"))
|
|
121
|
-
}));
|
|
122
|
-
const helpers = files.findIndex(({ text }) => DEFINES_HELPER.test(text));
|
|
123
|
-
const defined = sqlFiles(cwd, [SCHEMAS]).find((file) => DEFINES_HELPER.test(uncommented(readFileSync(file, "utf8"))));
|
|
124
|
-
if (helpers === -1) return defined === void 0 ? [] : [{
|
|
125
|
-
code: "PD043",
|
|
126
|
-
severity: "warning",
|
|
127
|
-
message: `${rel(cwd, defined)} defines the PermDock helpers, but schema_paths in supabase/config.toml does not list it, so supabase db diff never applies it`,
|
|
128
|
-
fix: `add it to [db.migrations] schema_paths before every file that calls permdock_has, permitted_<scope>_ids or member_<scope>_ids`
|
|
129
|
-
}];
|
|
130
|
-
const helperFile = rel(cwd, files[helpers]?.file ?? "");
|
|
131
|
-
return files.slice(0, helpers).filter(({ text }) => CALLS_HELPER.test(text)).map(({ file }) => ({
|
|
132
|
-
code: "PD043",
|
|
133
|
-
severity: "warning",
|
|
134
|
-
message: `${rel(cwd, file)} calls the PermDock helpers, but schema_paths applies it before ${helperFile}, which defines them`,
|
|
135
|
-
fix: `list ${helperFile} earlier in [db.migrations] schema_paths, or give it a lower number, such as 056_permdock_helpers.sql before 060_policies.sql`
|
|
136
|
-
}));
|
|
137
|
-
}
|
|
138
|
-
//#endregion
|
|
139
|
-
//#region src/cli/doctor-next.ts
|
|
140
|
-
const ROUTE_GLOBS = ["**/api/permdock/route.{ts,tsx,js,jsx,mjs}"];
|
|
141
|
-
const ENDPOINT = /\bendpoint\s*(?::|=)\s*\{?\s*['"`]/u;
|
|
142
|
-
const NATIVE_IMPORT = /\bfrom\s*['"]permdock\/react-native['"]/u;
|
|
143
|
-
const CLIENT_READS = /* @__PURE__ */ new Set(["usePermission", "reference"]);
|
|
144
|
-
/** A grant the snapshot marks `portable: false`, so a client check needs the endpoint. */
|
|
145
|
-
function serverOnly(policy, grant) {
|
|
146
|
-
if (!grant.portable || hasConditionOp(grant.where, "related")) return true;
|
|
147
|
-
const relations = policy.resources.get(grant.permission.resource)?.relations;
|
|
148
|
-
return flattenGrantee(grant.to).some((item) => {
|
|
149
|
-
if (item.kind !== "relation") return false;
|
|
150
|
-
const spec = relations?.[item.relation];
|
|
151
|
-
return item.through !== void 0 || isPrincipalRelation(spec) && spec.period !== void 0;
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
function serverOnlyKeys(policy) {
|
|
155
|
-
const keys = /* @__PURE__ */ new Set();
|
|
156
|
-
for (const grant of [...policy.grants, ...policy.roles.flatMap((binding) => binding.grants)]) if (grant.hosted === void 0 && grant.effect === "allow" && serverOnly(policy, grant)) keys.add(grant.permission.key);
|
|
157
|
-
return keys;
|
|
158
|
-
}
|
|
159
|
-
/** An `app/**\/api/permdock/route.ts` that exports `permdockHandler`'s result, or an `endpoint` string in the source. */
|
|
160
|
-
function hasEndpoint(cwd, sources) {
|
|
161
|
-
if (listSourceFiles(cwd, ROUTE_GLOBS).filter((file) => /(?:^|[\\/])app[\\/]/u.test(rel(cwd, file))).some((file) => readFileSync(file, "utf8").includes("permdockHandler"))) return true;
|
|
162
|
-
return sources.some((file) => ENDPOINT.test(readFileSync(file, "utf8")));
|
|
163
|
-
}
|
|
164
|
-
/**
|
|
165
|
-
* PD044: `usePermission` reads a permission whose grant the snapshot cannot
|
|
166
|
-
* answer, and the app has no endpoint to ask, so the client always denies it.
|
|
167
|
-
*/
|
|
168
|
-
async function pd044(input) {
|
|
169
|
-
const policy = await policyOf(input);
|
|
170
|
-
if (policy === void 0) return [];
|
|
171
|
-
const keys = serverOnlyKeys(policy);
|
|
172
|
-
if (keys.size === 0) return [];
|
|
173
|
-
const usages = (await collectedOf(input)).scan?.usages ?? {};
|
|
174
|
-
const hooked = [...keys].toSorted().flatMap((key) => (usages[key] ?? []).filter((usage) => usage.call === "usePermission").map((usage) => ({
|
|
175
|
-
key,
|
|
176
|
-
usage
|
|
177
|
-
})));
|
|
178
|
-
if (hooked.length === 0) return [];
|
|
179
|
-
const sources = listSourceFiles(input.cwd, doctorSrcPath(input.config));
|
|
180
|
-
if (hasEndpoint(input.cwd, sources)) return [];
|
|
181
|
-
return hooked.map(({ key, usage }) => ({
|
|
182
|
-
code: "PD044",
|
|
183
|
-
severity: "warning",
|
|
184
|
-
message: `usePermission reads ${key} at ${usage.file}:${String(usage.line)}, whose grant needs the server (a closure, graph relation or period), and no app/**/api/permdock/route.ts exports permdockHandler and no endpoint is configured: the client always answers denied (server-only)`,
|
|
185
|
-
fix: "add app/api/permdock/route.ts exporting permdockHandler(), pass endpoint to createPermDock, or check it on the server with getPermission"
|
|
186
|
-
}));
|
|
187
|
-
}
|
|
188
|
-
/**
|
|
189
|
-
* PD065: a file that imports `permdock/react-native` reads a permission whose
|
|
190
|
-
* grant the snapshot cannot answer, so the device answers it only online.
|
|
191
|
-
*/
|
|
192
|
-
async function pd065(input) {
|
|
193
|
-
const policy = await policyOf(input);
|
|
194
|
-
if (policy === void 0) return [];
|
|
195
|
-
const keys = serverOnlyKeys(policy);
|
|
196
|
-
if (keys.size === 0) return [];
|
|
197
|
-
const usages = (await collectedOf(input)).scan?.usages ?? {};
|
|
198
|
-
const native = /* @__PURE__ */ new Map();
|
|
199
|
-
const isNative = (file) => {
|
|
200
|
-
let hit = native.get(file);
|
|
201
|
-
if (hit === void 0) {
|
|
202
|
-
try {
|
|
203
|
-
hit = NATIVE_IMPORT.test(readFileSync(join(input.cwd, file), "utf8"));
|
|
204
|
-
} catch {
|
|
205
|
-
hit = false;
|
|
206
|
-
}
|
|
207
|
-
native.set(file, hit);
|
|
208
|
-
}
|
|
209
|
-
return hit;
|
|
210
|
-
};
|
|
211
|
-
return [...keys].toSorted().flatMap((key) => (usages[key] ?? []).filter((usage) => CLIENT_READS.has(usage.call) && isNative(usage.file)).map((usage) => ({
|
|
212
|
-
code: "PD065",
|
|
213
|
-
severity: "warning",
|
|
214
|
-
message: `React Native code reads ${key} at ${usage.file}:${String(usage.line)}, whose grant needs the server (a closure, graph relation or period): offline the device answers denied (server-only)`,
|
|
215
|
-
fix: "express the grant with a portable condition if the screen must work offline, or show the offline state for it"
|
|
216
|
-
})));
|
|
217
|
-
}
|
|
218
|
-
//#endregion
|
|
219
|
-
//#region src/cli/doctor-powersync.ts
|
|
220
|
-
/**
|
|
221
|
-
* PD058: `sync-config.yaml` or the `powersync.manifest` file is not what the
|
|
222
|
-
* policy compiles to, so PowerSync syncs rows, or the device builds
|
|
223
|
-
* snapshots, by an older policy. A policy that does not compile is a finding
|
|
224
|
-
* too.
|
|
225
|
-
*/
|
|
226
|
-
async function pd058(input) {
|
|
227
|
-
const { cwd, config } = input;
|
|
228
|
-
if (config.powersync === void 0 || config.policy === void 0) return [];
|
|
229
|
-
const policy = await policyOf(input);
|
|
230
|
-
if (policy === void 0) return [];
|
|
231
|
-
let compiled;
|
|
232
|
-
try {
|
|
233
|
-
compiled = powersyncFiles(policy, config);
|
|
234
|
-
} catch (cause) {
|
|
235
|
-
return [{
|
|
236
|
-
code: "PD058",
|
|
237
|
-
severity: "warning",
|
|
238
|
-
message: `the policy does not compile to Sync Streams: ${cause instanceof Error ? cause.message : String(cause)}`,
|
|
239
|
-
fix: "fix the powersync and rls config, then run permdock powersync generate"
|
|
240
|
-
}];
|
|
241
|
-
}
|
|
242
|
-
return staleFiles(cwd, compiled.files).map((file) => ({
|
|
243
|
-
code: "PD058",
|
|
244
|
-
severity: "warning",
|
|
245
|
-
message: file.missing ? `${file.path} is missing, so ${file.path === config.powersync?.manifest ? "the device builds no local snapshot" : "PowerSync syncs no stream the policy compiles to"}` : `${file.path} is not what the policy compiles to, so ${file.path === config.powersync?.manifest ? "the device builds snapshots" : "PowerSync syncs rows"} by an older policy`,
|
|
246
|
-
fix: "run permdock powersync generate and deploy the files"
|
|
247
|
-
}));
|
|
248
|
-
}
|
|
249
|
-
//#endregion
|
|
250
|
-
//#region src/cli/doctor-seeds.ts
|
|
251
|
-
const SEED_INSERT = /^insert\s+into\s+((?:"[^"]+"|\w+)(?:\.(?:"[^"]+"|\w+))?)\s*\(\s*role\s*,\s*permission\s*,\s*grant_key\s*,\s*scope\s*,\s*effect\s*\)\s*values\s*([\s\S]*?)\s*on\s+conflict\b/iu;
|
|
252
|
-
const SEED_CLEAR = /^delete\s+from\s+((?:"[^"]+"|\w+)(?:\.(?:"[^"]+"|\w+))?)\s*$/iu;
|
|
253
|
-
const LITERAL = String.raw`'((?:[^']|'')*)'`;
|
|
254
|
-
const SEED_ROW = new RegExp(String.raw`\(\s*${LITERAL}\s*,\s*${LITERAL}\s*,\s*${LITERAL}\s*,\s*${LITERAL}\s*,\s*${LITERAL}\s*\)`, "gu");
|
|
255
|
-
function rowKey(values) {
|
|
256
|
-
return values.join(" ");
|
|
257
|
-
}
|
|
258
|
-
/** The rows of the last statement that seeds or clears `table`, in migration order. */
|
|
259
|
-
function lastSeed(cwd, config, table) {
|
|
260
|
-
let seeded;
|
|
261
|
-
for (const file of sqlFiles(cwd, config.doctor?.migrations ?? MIGRATION_DIRS)) for (const { text } of sqlStatements(readFileSync(file, "utf8"))) {
|
|
262
|
-
const insert = SEED_INSERT.exec(text);
|
|
263
|
-
if (insert !== null && tableKey(group(insert, 1)) === table) {
|
|
264
|
-
const rows = /* @__PURE__ */ new Set();
|
|
265
|
-
for (const row of group(insert, 2).matchAll(SEED_ROW)) rows.add(rowKey(row.slice(1, 6).map((value) => value.replaceAll("''", "'"))));
|
|
266
|
-
seeded = {
|
|
267
|
-
file: rel(cwd, file),
|
|
268
|
-
rows
|
|
269
|
-
};
|
|
270
|
-
continue;
|
|
271
|
-
}
|
|
272
|
-
const clear = SEED_CLEAR.exec(text);
|
|
273
|
-
if (clear !== null && tableKey(group(clear, 1)) === table) seeded = {
|
|
274
|
-
file: rel(cwd, file),
|
|
275
|
-
rows: /* @__PURE__ */ new Set()
|
|
276
|
-
};
|
|
277
|
-
}
|
|
278
|
-
return seeded;
|
|
279
|
-
}
|
|
280
|
-
/** The first three rows, as `(role permission grant_key scope effect)`. */
|
|
281
|
-
function sample(rows) {
|
|
282
|
-
return rows.slice(0, 3).map((row) => `(${row})`).join(", ");
|
|
283
|
-
}
|
|
284
|
-
/**
|
|
285
|
-
* PD054: the `role_permissions` rows the migrations seed last are not the
|
|
286
|
-
* rows the policy compiles to, so the helpers answer from a stale policy.
|
|
287
|
-
*/
|
|
288
|
-
async function pd054(input) {
|
|
289
|
-
const { cwd, config } = input;
|
|
290
|
-
const rls = config.rls;
|
|
291
|
-
if (config.policy === void 0 || rls === void 0 && config.supabase === void 0) return [];
|
|
292
|
-
const schema = rls?.schema ?? rls?.rbac?.schema ?? "permdock";
|
|
293
|
-
const seeded = lastSeed(cwd, config, `${schema}.role_permissions`);
|
|
294
|
-
if (seeded === void 0) return [];
|
|
295
|
-
const policy = await policyOf(input);
|
|
296
|
-
if (policy === void 0) return [];
|
|
297
|
-
const ctx = {
|
|
298
|
-
dialect: rls?.dialect ?? "supabase",
|
|
299
|
-
scopes: scopeList(policy.scopes),
|
|
300
|
-
tenantClaim: rls?.tenantClaim ?? "tenant_id",
|
|
301
|
-
gucPrefix: rls?.gucPrefix ?? "app",
|
|
302
|
-
...rls?.actions === void 0 ? {} : { actions: rls.actions },
|
|
303
|
-
schema,
|
|
304
|
-
...rls?.fields === "views" ? { fields: "views" } : {}
|
|
305
|
-
};
|
|
306
|
-
let expected;
|
|
307
|
-
try {
|
|
308
|
-
expected = new Set(compileGrants(policy, ctx, rls?.tables, [], true).rolePermissions.map((row) => rowKey([
|
|
309
|
-
row.role,
|
|
310
|
-
row.permission,
|
|
311
|
-
row.grantKey,
|
|
312
|
-
row.scope,
|
|
313
|
-
row.effect
|
|
314
|
-
])));
|
|
315
|
-
} catch {
|
|
316
|
-
return [];
|
|
317
|
-
}
|
|
318
|
-
const missing = [...expected].filter((row) => !seeded.rows.has(row));
|
|
319
|
-
const stale = [...seeded.rows].filter((row) => !expected.has(row));
|
|
320
|
-
if (missing.length === 0 && stale.length === 0) return [];
|
|
321
|
-
const parts = [...missing.length === 0 ? [] : [`${String(missing.length)} missing, such as ${sample(missing)}`], ...stale.length === 0 ? [] : [`${String(stale.length)} stale, such as ${sample(stale)}`]];
|
|
322
|
-
return [{
|
|
323
|
-
code: "PD054",
|
|
324
|
-
severity: "warning",
|
|
325
|
-
message: `${seeded.file} seeds ${schema}.role_permissions with rows the policy no longer compiles to: ${parts.join("; ")}`,
|
|
326
|
-
fix: "run permdock rls generate (with --split ...,seeds --seeds-out into a new migration for declarative schemas) and apply it"
|
|
327
|
-
}];
|
|
328
|
-
}
|
|
329
|
-
//#endregion
|
|
330
|
-
//#region src/cli/doctor-sql.ts
|
|
331
|
-
/** Supabase's `[api] schemas` when `config.toml` sets none. */
|
|
332
|
-
const API_SCHEMAS = ["public", "graphql_public"];
|
|
333
|
-
const NAME = String.raw`((?:"[^"]+"|\w+)(?:\.(?:"[^"]+"|\w+))?)`;
|
|
334
|
-
const IDENT = String.raw`("[^"]+"|\w+)`;
|
|
335
|
-
/** A statement without its dollar-quoted bodies: what a function header says. */
|
|
336
|
-
function header(text) {
|
|
337
|
-
return text.replaceAll(/\$([A-Za-z_]*)\$[\s\S]*?\$\1\$/gu, "$$$$");
|
|
338
|
-
}
|
|
339
|
-
function isSupabase(cwd, config) {
|
|
340
|
-
return existsSync(join(cwd, "supabase")) || config.rls?.dialect === "supabase" || config.supabase !== void 0;
|
|
341
|
-
}
|
|
342
|
-
function migrationStatements(cwd, config) {
|
|
343
|
-
return sqlFiles(cwd, config.doctor?.migrations ?? MIGRATION_DIRS).flatMap((file) => sqlStatements(readFileSync(file, "utf8")).map((statement) => Object.assign({ file: rel(cwd, file) }, statement)));
|
|
344
|
-
}
|
|
345
|
-
/** The schema of a `tableKey`, which always names one. */
|
|
346
|
-
function schemaOf(key) {
|
|
347
|
-
return key.slice(0, key.indexOf("."));
|
|
348
|
-
}
|
|
349
|
-
function unquote(name) {
|
|
350
|
-
return name.replaceAll("\"", "").toLowerCase();
|
|
351
|
-
}
|
|
352
|
-
/**
|
|
353
|
-
* PD046: the schema of PermDock's `security definer` helpers is one the Data
|
|
354
|
-
* API exposes, so PostgREST serves them as RPCs.
|
|
355
|
-
*/
|
|
356
|
-
function pd046(cwd, config) {
|
|
357
|
-
if (!existsSync(join(cwd, "supabase/config.toml"))) return [];
|
|
358
|
-
const schema = config.rls?.schema ?? config.rls?.rbac?.schema ?? "permdock";
|
|
359
|
-
if (!(supabaseConfig(cwd).apiSchemas ?? API_SCHEMAS).includes(schema)) return [];
|
|
360
|
-
return [{
|
|
361
|
-
code: "PD046",
|
|
362
|
-
severity: "warning",
|
|
363
|
-
message: `the PermDock helper schema ${schema} is in [api] schemas in supabase/config.toml, so the Data API serves its security definer helpers as RPCs`,
|
|
364
|
-
fix: schema === "public" ? `drop rls.schema: 'public' so the helpers move to the private ${PERMDOCK_SCHEMA} schema, regenerate with permdock rls generate, and keep ${PERMDOCK_SCHEMA} out of [api] schemas` : `remove ${schema} from [api] schemas; policies reach the helpers through grant usage, not the Data API`
|
|
365
|
-
}];
|
|
366
|
-
}
|
|
367
|
-
const USER_METADATA = /\b(raw_user_meta_data|user_metadata)\b/giu;
|
|
368
|
-
/** PD047: SQL that reads `user_metadata`, which every user can edit for themselves. */
|
|
369
|
-
function pd047(cwd, config) {
|
|
370
|
-
if (!isSupabase(cwd, config)) return [];
|
|
371
|
-
return migrationStatements(cwd, config).flatMap((statement) => [...statement.text.matchAll(USER_METADATA)].map((match) => ({
|
|
372
|
-
code: "PD047",
|
|
373
|
-
severity: "warning",
|
|
374
|
-
message: `${statement.file}:${String(statement.line + statement.text.slice(0, match.index).split("\n").length - 1)} reads ${match[0]}, which a user can set for themselves`,
|
|
375
|
-
fix: "decide access from app_metadata (raw_app_meta_data), a server-owned table or a hook claim; user_metadata is for display only"
|
|
376
|
-
})));
|
|
377
|
-
}
|
|
378
|
-
const CREATE_FUNCTION = new RegExp(String.raw`^create\s+(?:or\s+replace\s+)?function\s+${NAME}\s*\(`, "iu");
|
|
379
|
-
const ALTER_SEARCH_PATH = new RegExp(String.raw`^alter\s+function\s+${NAME}\s*(?:\([^)]*\))?\s+set\s+search_path\b`, "iu");
|
|
380
|
-
const REVOKE_EXECUTE = new RegExp(String.raw`^revoke\s+(?:all(?:\s+privileges)?|execute)\s+on\s+(?:function|routine)\s+${NAME}\s*(?:\([^)]*\))?\s+from\s+([\s\S]+)$`, "iu");
|
|
381
|
-
const REVOKE_ALL_FUNCTIONS = new RegExp(String.raw`^(?:alter\s+default\s+privileges\s+(?:for\s+role\s+\S+\s+)?in\s+schema\s+${IDENT}\s+)?revoke\s+(?:all(?:\s+privileges)?|execute)\s+on\s+(?:all\s+)?(?:functions|routines)(?:\s+in\s+schema\s+${IDENT})?\s+from\s+([\s\S]+)$`, "iu");
|
|
382
|
-
function createdFunctions(statements) {
|
|
383
|
-
return statements.flatMap((statement) => {
|
|
384
|
-
const match = CREATE_FUNCTION.exec(statement.text);
|
|
385
|
-
return match === null ? [] : [{
|
|
386
|
-
...statement,
|
|
387
|
-
key: tableKey(group(match, 1)),
|
|
388
|
-
head: header(statement.text)
|
|
389
|
-
}];
|
|
390
|
-
});
|
|
391
|
-
}
|
|
392
|
-
/** PD048: a function without `set search_path`, so a caller's objects can shadow the ones it names. */
|
|
393
|
-
function pd048(cwd, config) {
|
|
394
|
-
if (!isSupabase(cwd, config)) return [];
|
|
395
|
-
const statements = migrationStatements(cwd, config);
|
|
396
|
-
const altered = new Set(statements.flatMap((statement) => {
|
|
397
|
-
const match = ALTER_SEARCH_PATH.exec(statement.text);
|
|
398
|
-
return match === null ? [] : [tableKey(group(match, 1))];
|
|
399
|
-
}));
|
|
400
|
-
return createdFunctions(statements).filter((fn) => !/\bset\s+search_path\b/iu.test(fn.head) && !altered.has(fn.key) && !/\blanguage\s+(?:c|internal)\b/iu.test(fn.head)).map((fn) => {
|
|
401
|
-
const definer = /\bsecurity\s+definer\b/iu.test(fn.head);
|
|
402
|
-
return {
|
|
403
|
-
code: "PD048",
|
|
404
|
-
severity: "warning",
|
|
405
|
-
message: `${fn.file}:${String(fn.line)} ${definer ? "security definer " : ""}function ${fn.key} sets no search_path${definer ? ", so a caller can shadow what it reads with their own objects" : ""}`,
|
|
406
|
-
fix: `add set search_path = '' and qualify every name in the body (Supabase advisor function_search_path_mutable)`
|
|
407
|
-
};
|
|
408
|
-
});
|
|
409
|
-
}
|
|
410
|
-
/** Roles each `revoke` statement takes `execute` from, per function key or `schema.*`. */
|
|
411
|
-
function revokedExecute(statements) {
|
|
412
|
-
const revoked = /* @__PURE__ */ new Map();
|
|
413
|
-
const add = (key, roles) => {
|
|
414
|
-
const set = revoked.get(key) ?? /* @__PURE__ */ new Set();
|
|
415
|
-
for (const role of roles.split(",")) set.add(unquote(role.trim().replace(/\s+(?:cascade|restrict)$/iu, "")));
|
|
416
|
-
revoked.set(key, set);
|
|
417
|
-
};
|
|
418
|
-
for (const statement of statements) {
|
|
419
|
-
const one = REVOKE_EXECUTE.exec(statement.text);
|
|
420
|
-
if (one !== null) {
|
|
421
|
-
add(tableKey(group(one, 1)), group(one, 2));
|
|
422
|
-
continue;
|
|
423
|
-
}
|
|
424
|
-
const all = REVOKE_ALL_FUNCTIONS.exec(statement.text);
|
|
425
|
-
if (all !== null) {
|
|
426
|
-
const schema = all[1] ?? all[2];
|
|
427
|
-
add(`${schema === void 0 ? "*" : unquote(schema)}.*`, group(all, 3));
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
return revoked;
|
|
431
|
-
}
|
|
432
|
-
/**
|
|
433
|
-
* PD049: a `security definer` function `public` (every role) or, in Supabase's
|
|
434
|
-
* `public` schema, `anon` may still execute: Postgres grants `execute` to
|
|
435
|
-
* `public` on create, and Supabase's default privileges grant it to `anon`.
|
|
436
|
-
*/
|
|
437
|
-
function pd049(cwd, config) {
|
|
438
|
-
if (!isSupabase(cwd, config)) return [];
|
|
439
|
-
const statements = migrationStatements(cwd, config);
|
|
440
|
-
const revoked = revokedExecute(statements);
|
|
441
|
-
const findings = [];
|
|
442
|
-
for (const fn of createdFunctions(statements)) {
|
|
443
|
-
if (!/\bsecurity\s+definer\b/iu.test(fn.head) || /\breturns\s+(?:event_)?trigger\b/iu.test(fn.head)) continue;
|
|
444
|
-
const schema = schemaOf(fn.key);
|
|
445
|
-
const from = /* @__PURE__ */ new Set([
|
|
446
|
-
...revoked.get(fn.key) ?? [],
|
|
447
|
-
...revoked.get(`${schema}.*`) ?? [],
|
|
448
|
-
...revoked.get("*.*") ?? []
|
|
449
|
-
]);
|
|
450
|
-
const missing = ["public", ...schema === "public" ? ["anon"] : []].filter((role) => !from.has(role));
|
|
451
|
-
if (missing.length > 0) findings.push({
|
|
452
|
-
code: "PD049",
|
|
453
|
-
severity: "warning",
|
|
454
|
-
message: `${fn.file}:${String(fn.line)} security definer function ${fn.key} can be executed by ${missing.join(" and ")}`,
|
|
455
|
-
fix: `revoke execute on function ${fn.key}(…) from public, anon; then grant execute to the roles that call it`
|
|
456
|
-
});
|
|
457
|
-
}
|
|
458
|
-
return findings;
|
|
459
|
-
}
|
|
460
|
-
const CREATE_TABLE = new RegExp(String.raw`^create\s+(?:(?:global|local)\s+)?(?:(?:temp|temporary|unlogged)\s+)?table\s+(?:if\s+not\s+exists\s+)?${NAME}\s*\(`, "iu");
|
|
461
|
-
const ENABLE_RLS = new RegExp(String.raw`^alter\s+table\s+(?:if\s+exists\s+)?(?:only\s+)?${NAME}[\s\S]*\benable\s+row\s+level\s+security\b`, "iu");
|
|
462
|
-
const DROP_TABLE = /^drop\s+table\s+(?:if\s+exists\s+)?([\s\S]+?)(?:\s+(?:cascade|restrict))?$/iu;
|
|
463
|
-
/** PD050: a `public` table without row level security, which the Data API serves whole. */
|
|
464
|
-
function pd050(cwd, config) {
|
|
465
|
-
if (!isSupabase(cwd, config)) return [];
|
|
466
|
-
const created = /* @__PURE__ */ new Map();
|
|
467
|
-
const enabled = /* @__PURE__ */ new Set();
|
|
468
|
-
for (const statement of migrationStatements(cwd, config)) {
|
|
469
|
-
const table = CREATE_TABLE.exec(statement.text);
|
|
470
|
-
if (table !== null) {
|
|
471
|
-
created.set(tableKey(group(table, 1)), statement);
|
|
472
|
-
continue;
|
|
473
|
-
}
|
|
474
|
-
const rls = ENABLE_RLS.exec(statement.text);
|
|
475
|
-
if (rls !== null) {
|
|
476
|
-
enabled.add(tableKey(group(rls, 1)));
|
|
477
|
-
continue;
|
|
478
|
-
}
|
|
479
|
-
const dropped = DROP_TABLE.exec(statement.text);
|
|
480
|
-
if (dropped !== null) for (const name of group(dropped, 1).split(",")) created.delete(tableKey(name.trim()));
|
|
481
|
-
}
|
|
482
|
-
return [...created].filter(([key]) => schemaOf(key) === "public" && !enabled.has(key)).map(([key, statement]) => ({
|
|
483
|
-
code: "PD050",
|
|
484
|
-
severity: "error",
|
|
485
|
-
message: `${statement.file}:${String(statement.line)} creates ${key} without row level security, so the Data API serves every row to anon and authenticated`,
|
|
486
|
-
fix: `alter table ${key} enable row level security; permdock rls generate writes its policies`
|
|
487
|
-
}));
|
|
488
|
-
}
|
|
489
|
-
const CREATE_POLICY = new RegExp(String.raw`^create\s+policy\s+${IDENT}\s+on\s+${NAME}([\s\S]*)$`, "iu");
|
|
490
|
-
const UNWRAPPED_AUTH = /(?<!\bselect\s{1,20})\bauth\.(uid|jwt)\s*\(\s*\)/giu;
|
|
491
|
-
/** PD051: a policy that calls `auth.uid()` or `auth.jwt()` bare, so Postgres runs it once per row. */
|
|
492
|
-
function pd051(cwd, config) {
|
|
493
|
-
if (!isSupabase(cwd, config)) return [];
|
|
494
|
-
return migrationStatements(cwd, config).flatMap((statement) => {
|
|
495
|
-
const policy = CREATE_POLICY.exec(statement.text);
|
|
496
|
-
if (policy === null) return [];
|
|
497
|
-
const calls = [...new Set([...group(policy, 3).matchAll(UNWRAPPED_AUTH)].map((match) => `auth.${group(match, 1).toLowerCase()}()`))];
|
|
498
|
-
const [first] = calls;
|
|
499
|
-
return first === void 0 ? [] : [{
|
|
500
|
-
code: "PD051",
|
|
501
|
-
severity: "warning",
|
|
502
|
-
message: `${statement.file}:${String(statement.line)} policy ${unquote(group(policy, 1))} on ${tableKey(group(policy, 2))} calls ${calls.join(" and ")} once per row`,
|
|
503
|
-
fix: `wrap each call as (select ${first}) so Postgres evaluates it once per statement (Supabase advisor auth_rls_initplan)`
|
|
504
|
-
}];
|
|
505
|
-
});
|
|
506
|
-
}
|
|
507
|
-
/** PD052: an update policy without `with check`, so nothing states what a row may become. */
|
|
508
|
-
function pd052(cwd, config) {
|
|
509
|
-
if (!isSupabase(cwd, config)) return [];
|
|
510
|
-
return migrationStatements(cwd, config).flatMap((statement) => {
|
|
511
|
-
const policy = CREATE_POLICY.exec(statement.text);
|
|
512
|
-
const rest = policy?.[3] ?? "";
|
|
513
|
-
if (policy === null || !/\bfor\s+update\b/iu.test(rest) || /\bwith\s+check\b/iu.test(rest)) return [];
|
|
514
|
-
return [{
|
|
515
|
-
code: "PD052",
|
|
516
|
-
severity: "warning",
|
|
517
|
-
message: `${statement.file}:${String(statement.line)} update policy ${unquote(group(policy, 1))} on ${tableKey(group(policy, 2))} has no with check, so Postgres checks the new row against using alone`,
|
|
518
|
-
fix: "add with check (...) stating what the updated row must satisfy, such as the same tenant and owner test, so an update cannot move a row out of what the user may write"
|
|
519
|
-
}];
|
|
520
|
-
});
|
|
521
|
-
}
|
|
522
|
-
/** Top-level comma-separated parts of a parenthesised list. */
|
|
523
|
-
function topLevelParts(body) {
|
|
524
|
-
const parts = [];
|
|
525
|
-
let depth = 0;
|
|
526
|
-
let start = 0;
|
|
527
|
-
for (let index = 0; index < body.length; index += 1) {
|
|
528
|
-
const char = body[index];
|
|
529
|
-
if (char === "(") depth += 1;
|
|
530
|
-
else if (char === ")") depth -= 1;
|
|
531
|
-
else if (char === "," && depth === 0) {
|
|
532
|
-
parts.push(body.slice(start, index).trim());
|
|
533
|
-
start = index + 1;
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
parts.push(body.slice(start).trim());
|
|
537
|
-
return parts.filter((part) => part !== "");
|
|
538
|
-
}
|
|
539
|
-
/**
|
|
540
|
-
* One column definition or table constraint of a `create table` body: its
|
|
541
|
-
* first column, whether it is a foreign key, and whether a primary key or
|
|
542
|
-
* unique constraint indexes that column.
|
|
543
|
-
*/
|
|
544
|
-
function tablePart(raw) {
|
|
545
|
-
const part = raw.replace(/^constraint\s+(?:"[^"]+"|\w+)\s+/iu, "");
|
|
546
|
-
const listed = /^(foreign\s+key|primary\s+key|unique)\s*\(([^)]*)\)/iu.exec(part);
|
|
547
|
-
if (listed !== null) {
|
|
548
|
-
const foreign = /^foreign/iu.test(group(listed, 1));
|
|
549
|
-
return {
|
|
550
|
-
column: firstColumn(group(listed, 2)),
|
|
551
|
-
foreign,
|
|
552
|
-
indexed: !foreign
|
|
553
|
-
};
|
|
554
|
-
}
|
|
555
|
-
return {
|
|
556
|
-
column: unquote(/^("[^"]+"|\w+)/u.exec(part)?.[1] ?? ""),
|
|
557
|
-
foreign: /\breferences\b/iu.test(part),
|
|
558
|
-
indexed: /\b(?:primary\s+key|unique)\b/iu.test(part)
|
|
559
|
-
};
|
|
560
|
-
}
|
|
561
|
-
function firstColumn(list) {
|
|
562
|
-
return unquote(list.replace(/,[\s\S]*$/u, "").trim());
|
|
563
|
-
}
|
|
564
|
-
const ADD_CONSTRAINT = new RegExp(String.raw`^alter\s+table\s+(?:if\s+exists\s+)?(?:only\s+)?${NAME}\s+add\s+(?:constraint\s+${IDENT}\s+)?(foreign\s+key|primary\s+key|unique)\s*\(([^)]*)\)`, "iu");
|
|
565
|
-
const CREATE_INDEX = new RegExp(String.raw`^create\s+(?:unique\s+)?index\s+(?:concurrently\s+)?(?:if\s+not\s+exists\s+)?(?:${IDENT}\s+)?on\s+(?:only\s+)?${NAME}\s*(?:using\s+\w+\s*)?\(\s*${IDENT}`, "iu");
|
|
566
|
-
/** PD053: a foreign key no index starts with, so joins and deletes on the referenced row scan the table. */
|
|
567
|
-
function pd053(cwd, config) {
|
|
568
|
-
if (!isSupabase(cwd, config)) return [];
|
|
569
|
-
const keys = [];
|
|
570
|
-
const indexed = /* @__PURE__ */ new Set();
|
|
571
|
-
for (const statement of migrationStatements(cwd, config)) {
|
|
572
|
-
const table = CREATE_TABLE.exec(statement.text);
|
|
573
|
-
if (table !== null) {
|
|
574
|
-
const key = tableKey(group(table, 1));
|
|
575
|
-
const open = statement.text.indexOf("(", table[0].length - 1);
|
|
576
|
-
const body = statement.text.slice(open + 1, statement.text.lastIndexOf(")"));
|
|
577
|
-
for (const raw of topLevelParts(body)) {
|
|
578
|
-
const found = tablePart(raw);
|
|
579
|
-
if (found.foreign) keys.push({
|
|
580
|
-
...statement,
|
|
581
|
-
table: key,
|
|
582
|
-
column: found.column
|
|
583
|
-
});
|
|
584
|
-
if (found.indexed) indexed.add(`${key}.${found.column}`);
|
|
585
|
-
}
|
|
586
|
-
continue;
|
|
587
|
-
}
|
|
588
|
-
const added = ADD_CONSTRAINT.exec(statement.text);
|
|
589
|
-
if (added !== null) {
|
|
590
|
-
const key = tableKey(group(added, 1));
|
|
591
|
-
const column = firstColumn(group(added, 4));
|
|
592
|
-
if (/^foreign/iu.test(group(added, 3))) keys.push({
|
|
593
|
-
...statement,
|
|
594
|
-
table: key,
|
|
595
|
-
column
|
|
596
|
-
});
|
|
597
|
-
else indexed.add(`${key}.${column}`);
|
|
598
|
-
continue;
|
|
599
|
-
}
|
|
600
|
-
const index = CREATE_INDEX.exec(statement.text);
|
|
601
|
-
if (index !== null) indexed.add(`${tableKey(group(index, 2))}.${unquote(group(index, 3))}`);
|
|
602
|
-
}
|
|
603
|
-
return keys.filter((fk) => !indexed.has(`${fk.table}.${fk.column}`)).map((fk) => ({
|
|
604
|
-
code: "PD053",
|
|
605
|
-
severity: "warning",
|
|
606
|
-
message: `${fk.file}:${String(fk.line)} ${fk.table}.${fk.column} is a foreign key no index starts with, so policies that join on it and deletes of the referenced row scan ${fk.table}`,
|
|
607
|
-
fix: `create index on ${fk.table} (${fk.column}); (Supabase advisor unindexed_foreign_keys)`
|
|
608
|
-
}));
|
|
609
|
-
}
|
|
610
|
-
const LEGACY_CLAIM = /\brequest\.jwt\.claim\.(\w+)/giu;
|
|
611
|
-
function at(statement) {
|
|
612
|
-
return `${statement.file}:${String(statement.line)}`;
|
|
613
|
-
}
|
|
614
|
-
/**
|
|
615
|
-
* PD062: SQL that reads or sets a per-claim `request.jwt.claim.<name>`
|
|
616
|
-
* setting. Current PostgREST, `withPostgresClient`, PermDock's `withSubject`
|
|
617
|
-
* and `rls verify` set only `request.jwt.claims`. A function replaced by a
|
|
618
|
-
* later migration is judged by its last definition.
|
|
619
|
-
*/
|
|
620
|
-
function pd062(cwd, config) {
|
|
621
|
-
if (!isSupabase(cwd, config)) return [];
|
|
622
|
-
const statements = migrationStatements(cwd, config);
|
|
623
|
-
const latest = /* @__PURE__ */ new Map();
|
|
624
|
-
for (const fn of createdFunctions(statements)) latest.set(fn.key, at(fn));
|
|
625
|
-
return statements.flatMap((statement) => {
|
|
626
|
-
const fn = CREATE_FUNCTION.exec(statement.text);
|
|
627
|
-
if (fn !== null && latest.get(tableKey(group(fn, 1))) !== at(statement)) return [];
|
|
628
|
-
return [...new Set([...statement.text.matchAll(LEGACY_CLAIM)].map((match) => group(match, 1).toLowerCase()))].map((name) => ({
|
|
629
|
-
code: "PD062",
|
|
630
|
-
severity: "warning",
|
|
631
|
-
message: `${statement.file}:${String(statement.line)} uses the legacy request.jwt.claim.${name} setting, which PostgREST 12 and PermDock's withSubject no longer set, so it reads null`,
|
|
632
|
-
fix: name === "sub" ? "read the subject with (select permdock.permdock_user_id()) or (select auth.uid()), and set request.jwt.claims in tests and jobs" : `read (select auth.jwt()) ->> '${name}', and set request.jwt.claims in tests and jobs`
|
|
633
|
-
}));
|
|
634
|
-
});
|
|
635
|
-
}
|
|
636
|
-
/**
|
|
637
|
-
* PD063: a migration statement supautils rejects for `postgres`, so
|
|
638
|
-
* `supabase db push` fails on it.
|
|
639
|
-
*/
|
|
640
|
-
function pd063(cwd, config) {
|
|
641
|
-
if (!isSupabase(cwd, config)) return [];
|
|
642
|
-
return sqlFiles(cwd, config.doctor?.migrations ?? MIGRATION_DIRS).flatMap((file) => supautilsRejections(readFileSync(file, "utf8")).map((found) => ({
|
|
643
|
-
code: "PD063",
|
|
644
|
-
severity: "error",
|
|
645
|
-
message: `${rel(cwd, file)}:${String(found.line)} ${found.reason}, which supautils rejects for the postgres role on Supabase`,
|
|
646
|
-
fix: "remove the statement: reserved roles belong to Supabase. Grant your own role to authenticator instead, and change anon, authenticated, authenticator or service_role only with alter role … set"
|
|
647
|
-
})));
|
|
648
|
-
}
|
|
649
|
-
/**
|
|
650
|
-
* PD056: SQL still calls an `rls.migrate` helper by its legacy name, so the
|
|
651
|
-
* shim (or the legacy function) cannot be dropped yet.
|
|
652
|
-
*/
|
|
653
|
-
function pd056(cwd, config) {
|
|
654
|
-
const helpers = Object.keys(config.rls?.migrate?.helpers ?? {}).filter((name) => /^[a-z_][a-z0-9_]*$/u.test(name));
|
|
655
|
-
if (helpers.length === 0) return [];
|
|
656
|
-
const statements = migrationStatements(cwd, config);
|
|
657
|
-
const findings = [];
|
|
658
|
-
for (const name of helpers) {
|
|
659
|
-
const own = new RegExp(String.raw`^\s*(?:create\s+(?:or\s+replace\s+)?function|drop\s+function|revoke|grant|alter\s+function|comment\s+on\s+function)\b[^(]*?(?:^|[\s."])${name}"?\s*\(`, "iu");
|
|
660
|
-
const call = new RegExp(String.raw`(?<![\w$])(?:(?:"[^"]+"|\w+)\s*\.\s*)?"?${name}"?\s*\(`, "giu");
|
|
661
|
-
let count = 0;
|
|
662
|
-
const files = /* @__PURE__ */ new Set();
|
|
663
|
-
for (const statement of statements) {
|
|
664
|
-
const hits = own.test(statement.text) ? 0 : (statement.text.match(call) ?? []).length;
|
|
665
|
-
if (hits > 0) {
|
|
666
|
-
count += hits;
|
|
667
|
-
files.add(statement.file);
|
|
668
|
-
}
|
|
669
|
-
}
|
|
670
|
-
if (count === 0) continue;
|
|
671
|
-
findings.push({
|
|
672
|
-
code: "PD056",
|
|
673
|
-
severity: "warning",
|
|
674
|
-
message: `legacy helper ${name} is still called ${String(count)} time(s), in ${[...files].toSorted().join(", ")}`,
|
|
675
|
-
fix: `run permdock rls migrate --write for policies, rewrite function bodies, views and triggers onto the permdock helpers by hand, then drop function ${name}`
|
|
676
|
-
});
|
|
677
|
-
}
|
|
678
|
-
return findings;
|
|
679
|
-
}
|
|
680
|
-
//#endregion
|
|
681
|
-
//#region src/cli/doctor-suspension.ts
|
|
682
|
-
/**
|
|
683
|
-
* PD061: a suspended scope keeps permissions. Each scope's kept keys are
|
|
684
|
-
* listed for review, since its members hold them while the instance is
|
|
685
|
-
* suspended, and a key the definitions do not declare is named apart.
|
|
686
|
-
*/
|
|
687
|
-
async function pd061(input) {
|
|
688
|
-
const suspension = input.config.rls?.suspension;
|
|
689
|
-
const kept = [...Object.entries(suspension?.scopes ?? {}).map(([scope, row]) => ({
|
|
690
|
-
path: `scopes.${scope}`,
|
|
691
|
-
who: `members of a suspended ${scope}`,
|
|
692
|
-
row
|
|
693
|
-
})), ...suspension?.memberships === void 0 ? [] : [{
|
|
694
|
-
path: "memberships",
|
|
695
|
-
who: "suspended memberships",
|
|
696
|
-
row: suspension.memberships
|
|
697
|
-
}]].flatMap((entry) => {
|
|
698
|
-
try {
|
|
699
|
-
const keys = keptKeys(entry.row);
|
|
700
|
-
return keys.length === 0 ? [] : [{
|
|
701
|
-
path: entry.path,
|
|
702
|
-
who: entry.who,
|
|
703
|
-
keys
|
|
704
|
-
}];
|
|
705
|
-
} catch {
|
|
706
|
-
return [{
|
|
707
|
-
path: entry.path,
|
|
708
|
-
who: entry.who,
|
|
709
|
-
keys: [],
|
|
710
|
-
invalid: true
|
|
711
|
-
}];
|
|
712
|
-
}
|
|
713
|
-
});
|
|
714
|
-
if (kept.length === 0) return [];
|
|
715
|
-
const policy = await policyOf(input);
|
|
716
|
-
const declared = /* @__PURE__ */ new Set();
|
|
717
|
-
const former = /* @__PURE__ */ new Map();
|
|
718
|
-
for (const leaf of policy === void 0 ? [] : listPermissions(policy.permissions)) {
|
|
719
|
-
declared.add(leaf.key);
|
|
720
|
-
for (const old of formerKeys(leaf)) former.set(old, leaf.key);
|
|
721
|
-
}
|
|
722
|
-
return kept.flatMap((item) => {
|
|
723
|
-
if (item.invalid === true) return [{
|
|
724
|
-
code: "PD061",
|
|
725
|
-
severity: "warning",
|
|
726
|
-
message: `rls.suspension.${item.path}.keep has an entry that is neither a permission nor a permission key, so generate refuses it`,
|
|
727
|
-
fix: "list permission references or their keys in keep"
|
|
728
|
-
}];
|
|
729
|
-
const findings = [{
|
|
730
|
-
code: "PD061",
|
|
731
|
-
severity: "warning",
|
|
732
|
-
message: `${item.who} still hold ${item.keys.join(", ")}`,
|
|
733
|
-
fix: item.path === "memberships" ? "keep only what a suspended member must still do, such as leaving or exporting their own data, in rls.suspension.memberships.keep" : `keep only what a suspended ${item.path.slice(7)} must still do, such as restoring it or cancelling its deletion, in rls.suspension.${item.path}.keep`
|
|
734
|
-
}];
|
|
735
|
-
if (policy === void 0) return findings;
|
|
736
|
-
const unknown = item.keys.filter((key) => !declared.has(key));
|
|
737
|
-
if (unknown.length > 0) findings.push({
|
|
738
|
-
code: "PD061",
|
|
739
|
-
severity: "warning",
|
|
740
|
-
message: `rls.suspension.${item.path}.keep names ${unknown.map((key) => former.has(key) ? `${key} (renamed to ${String(former.get(key))})` : key).join(", ")}, which the definitions do not declare, so a suspended ${item.path === "memberships" ? "membership" : item.path.slice(7)} keeps nothing for them`,
|
|
741
|
-
fix: "keep permission references instead of keys, or replace each key with its current name"
|
|
742
|
-
});
|
|
743
|
-
return findings;
|
|
744
|
-
});
|
|
745
|
-
}
|
|
746
|
-
//#endregion
|
|
747
|
-
//#region src/cli/project.ts
|
|
748
|
-
function loadProject(input) {
|
|
749
|
-
const { cwd, config } = input;
|
|
750
|
-
let sources;
|
|
751
|
-
let policyLoad;
|
|
752
|
-
let collected;
|
|
753
|
-
const loadPolicy = () => {
|
|
754
|
-
policyLoad ??= readPolicy(cwd, config.policy);
|
|
755
|
-
return policyLoad;
|
|
756
|
-
};
|
|
757
|
-
const policy = async () => {
|
|
758
|
-
const load = await loadPolicy();
|
|
759
|
-
return load.status === "loaded" ? load.policy : void 0;
|
|
760
|
-
};
|
|
761
|
-
return {
|
|
762
|
-
cwd,
|
|
763
|
-
config,
|
|
764
|
-
now: input.now,
|
|
765
|
-
io: input.io,
|
|
766
|
-
sources: () => {
|
|
767
|
-
sources ??= listSourceFiles(cwd, doctorSrcPath(config)).map((file) => ({
|
|
768
|
-
file: rel(cwd, file),
|
|
769
|
-
text: readFileSync(file, "utf8")
|
|
770
|
-
}));
|
|
771
|
-
return sources;
|
|
772
|
-
},
|
|
773
|
-
policyLoad: loadPolicy,
|
|
774
|
-
policy,
|
|
775
|
-
collected: () => {
|
|
776
|
-
collected ??= runCollect({
|
|
777
|
-
cwd,
|
|
778
|
-
config,
|
|
779
|
-
collect: config.collect ?? {},
|
|
780
|
-
scanPath: doctorSrcPath(config),
|
|
781
|
-
check: true,
|
|
782
|
-
now: input.now,
|
|
783
|
-
io: input.io,
|
|
784
|
-
loadPolicy: policy
|
|
785
|
-
});
|
|
786
|
-
return collected;
|
|
787
|
-
}
|
|
788
|
-
};
|
|
789
|
-
}
|
|
790
|
-
async function readPolicy(cwd, path) {
|
|
791
|
-
if (path === void 0) return { status: "unset" };
|
|
792
|
-
try {
|
|
793
|
-
return {
|
|
794
|
-
status: "loaded",
|
|
795
|
-
policy: asPolicy(pickNamed(await loadModule(resolve(cwd, path)), ["policy"]))
|
|
796
|
-
};
|
|
797
|
-
} catch (error) {
|
|
798
|
-
return {
|
|
799
|
-
status: "failed",
|
|
800
|
-
path,
|
|
801
|
-
message: describeError(error)
|
|
802
|
-
};
|
|
803
|
-
}
|
|
804
|
-
}
|
|
805
|
-
const DOCTOR_CHECKS = [
|
|
806
|
-
{
|
|
807
|
-
code: "PD001",
|
|
808
|
-
groups: ["imports"],
|
|
809
|
-
run: (project) => pd001(project.sources(), new Set(listSourceFiles(project.cwd, project.config.doctor?.clientEntries ?? []).map((file) => rel(project.cwd, file))), project.config.policy === void 0 ? void 0 : {
|
|
810
|
-
cwd: project.cwd,
|
|
811
|
-
path: project.config.policy
|
|
812
|
-
})
|
|
813
|
-
},
|
|
814
|
-
{
|
|
815
|
-
code: "PD059",
|
|
816
|
-
groups: ["project"],
|
|
817
|
-
run: pd059
|
|
818
|
-
},
|
|
819
|
-
{
|
|
820
|
-
code: "PD060",
|
|
821
|
-
groups: ["project"],
|
|
822
|
-
run: pd060
|
|
823
|
-
},
|
|
824
|
-
{
|
|
825
|
-
code: "PD002",
|
|
826
|
-
groups: ["references"],
|
|
827
|
-
run: pd002
|
|
828
|
-
},
|
|
829
|
-
{
|
|
830
|
-
code: "PD003",
|
|
831
|
-
groups: ["ungranted"],
|
|
832
|
-
run: pd003
|
|
833
|
-
},
|
|
834
|
-
{
|
|
835
|
-
code: "PD004",
|
|
836
|
-
groups: ["catalog"],
|
|
837
|
-
run: pd004
|
|
838
|
-
},
|
|
839
|
-
{
|
|
840
|
-
code: "PD005",
|
|
841
|
-
groups: ["skills"],
|
|
842
|
-
run: (project) => pd005(project.cwd)
|
|
843
|
-
},
|
|
844
|
-
{
|
|
845
|
-
code: "PD006",
|
|
846
|
-
groups: ["typescript"],
|
|
847
|
-
run: (project) => pd006(project.cwd)
|
|
848
|
-
},
|
|
849
|
-
{
|
|
850
|
-
code: "PD007",
|
|
851
|
-
groups: ["validation"],
|
|
852
|
-
run: (project) => pd007(project.sources())
|
|
853
|
-
},
|
|
854
|
-
{
|
|
855
|
-
code: "PD008",
|
|
856
|
-
groups: ["naming"],
|
|
857
|
-
run: (project) => pd008(project.sources())
|
|
858
|
-
},
|
|
859
|
-
{
|
|
860
|
-
code: "PD009",
|
|
861
|
-
groups: ["duplicates"],
|
|
862
|
-
run: (project) => pd009(project.cwd)
|
|
863
|
-
},
|
|
864
|
-
{
|
|
865
|
-
code: "PD010",
|
|
866
|
-
groups: ["claims"],
|
|
867
|
-
run: (project) => pd010(project.sources())
|
|
868
|
-
},
|
|
869
|
-
{
|
|
870
|
-
code: "PD011",
|
|
871
|
-
groups: ["tenant"],
|
|
872
|
-
run: (project) => pd011(project.sources())
|
|
873
|
-
},
|
|
874
|
-
{
|
|
875
|
-
code: "PD012",
|
|
876
|
-
groups: ["drafts"],
|
|
877
|
-
run: (project) => pd012(project.cwd, project.config)
|
|
878
|
-
},
|
|
879
|
-
{
|
|
880
|
-
code: "PD013",
|
|
881
|
-
groups: ["algorithms"],
|
|
882
|
-
run: (project) => pd013(project.sources())
|
|
883
|
-
},
|
|
884
|
-
{
|
|
885
|
-
code: "PD014",
|
|
886
|
-
groups: ["discovery"],
|
|
887
|
-
run: (project) => pd014(project.sources())
|
|
888
|
-
},
|
|
889
|
-
{
|
|
890
|
-
code: "PD015",
|
|
891
|
-
groups: ["typ"],
|
|
892
|
-
run: (project) => pd015(project.sources())
|
|
893
|
-
},
|
|
894
|
-
{
|
|
895
|
-
code: "PD016",
|
|
896
|
-
groups: ["rls"],
|
|
897
|
-
run: pd016
|
|
898
|
-
},
|
|
899
|
-
{
|
|
900
|
-
code: "PD017",
|
|
901
|
-
groups: ["sensitive"],
|
|
902
|
-
run: pd017
|
|
903
|
-
},
|
|
904
|
-
{
|
|
905
|
-
code: "PD018",
|
|
906
|
-
groups: ["separation"],
|
|
907
|
-
run: pd018
|
|
908
|
-
},
|
|
909
|
-
{
|
|
910
|
-
code: "PD019",
|
|
911
|
-
groups: ["jwt-roles"],
|
|
912
|
-
run: pd019
|
|
913
|
-
},
|
|
914
|
-
{
|
|
915
|
-
code: "PD020",
|
|
916
|
-
groups: ["hosted"],
|
|
917
|
-
run: pd020
|
|
918
|
-
},
|
|
919
|
-
{
|
|
920
|
-
code: "PD021",
|
|
921
|
-
groups: ["hosted"],
|
|
922
|
-
run: (project) => pd021({
|
|
923
|
-
...project,
|
|
924
|
-
env: project.io.env ?? process.env
|
|
925
|
-
})
|
|
926
|
-
},
|
|
927
|
-
{
|
|
928
|
-
code: "PD022",
|
|
929
|
-
groups: ["views"],
|
|
930
|
-
run: (project) => pd022(project.cwd, project.config)
|
|
931
|
-
},
|
|
932
|
-
{
|
|
933
|
-
code: "PD023",
|
|
934
|
-
groups: ["custom-roles"],
|
|
935
|
-
run: pd023
|
|
936
|
-
},
|
|
937
|
-
{
|
|
938
|
-
code: "PD024",
|
|
939
|
-
groups: ["self-approval"],
|
|
940
|
-
run: pd024
|
|
941
|
-
},
|
|
942
|
-
{
|
|
943
|
-
code: "PD025",
|
|
944
|
-
groups: ["scopes"],
|
|
945
|
-
run: pd025
|
|
946
|
-
},
|
|
947
|
-
{
|
|
948
|
-
code: "PD026",
|
|
949
|
-
groups: ["ownership"],
|
|
950
|
-
run: pd026
|
|
951
|
-
},
|
|
952
|
-
{
|
|
953
|
-
code: "PD027",
|
|
954
|
-
groups: ["rls", "context-refs"],
|
|
955
|
-
run: pd027
|
|
956
|
-
},
|
|
957
|
-
{
|
|
958
|
-
code: "PD028",
|
|
959
|
-
groups: ["attrs", "supabase"],
|
|
960
|
-
run: (project) => pd028(project.cwd, project.config, attrsPlan)
|
|
961
|
-
},
|
|
962
|
-
{
|
|
963
|
-
code: "PD029",
|
|
964
|
-
groups: ["credentials"],
|
|
965
|
-
run: pd029
|
|
966
|
-
},
|
|
967
|
-
{
|
|
968
|
-
code: "PD030",
|
|
969
|
-
groups: ["fields"],
|
|
970
|
-
run: pd030
|
|
971
|
-
},
|
|
972
|
-
{
|
|
973
|
-
code: "PD031",
|
|
974
|
-
groups: ["graph"],
|
|
975
|
-
run: pd031
|
|
976
|
-
},
|
|
977
|
-
{
|
|
978
|
-
code: "PD032",
|
|
979
|
-
groups: ["graph", "rls"],
|
|
980
|
-
run: pd032
|
|
981
|
-
},
|
|
982
|
-
{
|
|
983
|
-
code: "PD033",
|
|
984
|
-
groups: ["activation"],
|
|
985
|
-
run: pd033
|
|
986
|
-
},
|
|
987
|
-
{
|
|
988
|
-
code: "PD034",
|
|
989
|
-
groups: ["break-glass"],
|
|
990
|
-
run: pd034
|
|
991
|
-
},
|
|
992
|
-
{
|
|
993
|
-
code: "PD035",
|
|
994
|
-
groups: ["support"],
|
|
995
|
-
run: pd035
|
|
996
|
-
},
|
|
997
|
-
{
|
|
998
|
-
code: "PD036",
|
|
999
|
-
groups: ["bola"],
|
|
1000
|
-
run: (project) => pd036(project.sources())
|
|
1001
|
-
},
|
|
1002
|
-
{
|
|
1003
|
-
code: "PD037",
|
|
1004
|
-
groups: ["supabase", "row-conditions"],
|
|
1005
|
-
run: pd037
|
|
1006
|
-
},
|
|
1007
|
-
{
|
|
1008
|
-
code: "PD039",
|
|
1009
|
-
groups: ["supabase", "helpers"],
|
|
1010
|
-
when: (config) => config.supabase?.hook !== void 0,
|
|
1011
|
-
run: supabaseSetup
|
|
1012
|
-
},
|
|
1013
|
-
{
|
|
1014
|
-
code: "PD038",
|
|
1015
|
-
groups: ["supabase", "tenant"],
|
|
1016
|
-
when: (config) => config.rls !== void 0 || config.supabase?.hook !== void 0,
|
|
1017
|
-
run: (project) => pd038(project.sources(), project.config.rls?.tenantClaim ?? "tenant_id", supabaseTenantClaim)
|
|
1018
|
-
},
|
|
1019
|
-
{
|
|
1020
|
-
code: "PD057",
|
|
1021
|
-
groups: ["supabase", "anonymous"],
|
|
1022
|
-
when: (config) => config.rls?.anonymousSignIns === "deny",
|
|
1023
|
-
run: (project) => pd057(project.sources())
|
|
1024
|
-
},
|
|
1025
|
-
{
|
|
1026
|
-
code: "PD040",
|
|
1027
|
-
groups: ["supabase", "auth-role"],
|
|
1028
|
-
run: (project) => pd040(project.cwd, project.config)
|
|
1029
|
-
},
|
|
1030
|
-
{
|
|
1031
|
-
code: "PD041",
|
|
1032
|
-
groups: ["supabase", "capabilities"],
|
|
1033
|
-
run: (project) => pd041(project.sources())
|
|
1034
|
-
},
|
|
1035
|
-
{
|
|
1036
|
-
code: "PD042",
|
|
1037
|
-
groups: ["supabase", "declarative"],
|
|
1038
|
-
run: (project) => pd042(project.cwd, project.config)
|
|
1039
|
-
},
|
|
1040
|
-
{
|
|
1041
|
-
code: "PD043",
|
|
1042
|
-
groups: ["supabase", "declarative"],
|
|
1043
|
-
run: (project) => pd043(project.cwd)
|
|
1044
|
-
},
|
|
1045
|
-
{
|
|
1046
|
-
code: "PD045",
|
|
1047
|
-
groups: ["supabase"],
|
|
1048
|
-
run: (project) => pd045(project.cwd)
|
|
1049
|
-
},
|
|
1050
|
-
...[
|
|
1051
|
-
["PD046", pd046],
|
|
1052
|
-
["PD047", pd047],
|
|
1053
|
-
["PD048", pd048],
|
|
1054
|
-
["PD049", pd049],
|
|
1055
|
-
["PD050", pd050],
|
|
1056
|
-
["PD051", pd051],
|
|
1057
|
-
["PD052", pd052],
|
|
1058
|
-
["PD053", pd053],
|
|
1059
|
-
["PD062", pd062],
|
|
1060
|
-
["PD063", pd063]
|
|
1061
|
-
].map(([code, check]) => ({
|
|
1062
|
-
code,
|
|
1063
|
-
groups: ["supabase", "sql"],
|
|
1064
|
-
run: (project) => check(project.cwd, project.config)
|
|
1065
|
-
})),
|
|
1066
|
-
{
|
|
1067
|
-
code: "PD054",
|
|
1068
|
-
groups: ["supabase", "seeds"],
|
|
1069
|
-
run: pd054
|
|
1070
|
-
},
|
|
1071
|
-
{
|
|
1072
|
-
code: "PD055",
|
|
1073
|
-
groups: ["custom-roles", "renamed"],
|
|
1074
|
-
run: pd055
|
|
1075
|
-
},
|
|
1076
|
-
{
|
|
1077
|
-
code: "PD056",
|
|
1078
|
-
groups: ["sql", "shims"],
|
|
1079
|
-
run: (project) => pd056(project.cwd, project.config)
|
|
1080
|
-
},
|
|
1081
|
-
{
|
|
1082
|
-
code: "PD058",
|
|
1083
|
-
groups: ["powersync"],
|
|
1084
|
-
run: pd058
|
|
1085
|
-
},
|
|
1086
|
-
{
|
|
1087
|
-
code: "PD061",
|
|
1088
|
-
groups: ["rls", "suspension"],
|
|
1089
|
-
when: (config) => config.rls?.suspension?.scopes !== void 0,
|
|
1090
|
-
run: pd061
|
|
1091
|
-
},
|
|
1092
|
-
{
|
|
1093
|
-
code: "PD064",
|
|
1094
|
-
groups: ["rls", "assignments"],
|
|
1095
|
-
when: (config) => config.rls?.assignments !== void 0,
|
|
1096
|
-
run: pd064
|
|
1097
|
-
},
|
|
1098
|
-
{
|
|
1099
|
-
code: "PD044",
|
|
1100
|
-
groups: ["next", "endpoint"],
|
|
1101
|
-
run: pd044
|
|
1102
|
-
},
|
|
1103
|
-
{
|
|
1104
|
-
code: "PD065",
|
|
1105
|
-
groups: ["react-native", "endpoint"],
|
|
1106
|
-
run: pd065
|
|
1107
|
-
}
|
|
1108
|
-
];
|
|
1109
|
-
async function runDoctor(input) {
|
|
1110
|
-
if (input.fix) {
|
|
1111
|
-
runSkillsInstall({
|
|
1112
|
-
cwd: input.cwd,
|
|
1113
|
-
agents: []
|
|
1114
|
-
});
|
|
1115
|
-
await runCollect({
|
|
1116
|
-
cwd: input.cwd,
|
|
1117
|
-
config: input.config,
|
|
1118
|
-
collect: input.config.collect ?? {},
|
|
1119
|
-
check: false,
|
|
1120
|
-
now: input.now,
|
|
1121
|
-
io: input.io
|
|
1122
|
-
});
|
|
1123
|
-
}
|
|
1124
|
-
const project = loadProject(input);
|
|
1125
|
-
const wanted = new Set(input.only.map((item) => item.toLowerCase()));
|
|
1126
|
-
const selected = (check) => wanted.size === 0 || wanted.has(check.code.toLowerCase()) || check.groups.some((group) => wanted.has(group));
|
|
1127
|
-
const findings = [];
|
|
1128
|
-
for (const check of DOCTOR_CHECKS) {
|
|
1129
|
-
if (check.when !== void 0 && !check.when(input.config)) continue;
|
|
1130
|
-
if (selected(check)) findings.push(...await check.run(project));
|
|
1131
|
-
}
|
|
1132
|
-
const errors = findings.filter((item) => item.severity === "error").length;
|
|
1133
|
-
const warnings = findings.filter((item) => item.severity === "warning").length;
|
|
1134
|
-
const report = {
|
|
1135
|
-
$schema: DOCTOR_REPORT_SCHEMA,
|
|
1136
|
-
findings,
|
|
1137
|
-
errors,
|
|
1138
|
-
warnings
|
|
1139
|
-
};
|
|
1140
|
-
return {
|
|
1141
|
-
code: errors > 0 || input.strict && warnings > 0 ? 1 : 0,
|
|
1142
|
-
output: input.json ? `${JSON.stringify(report, null, 2)}\n` : formatDoctor(report, input.color)
|
|
1143
|
-
};
|
|
1144
|
-
}
|
|
1145
|
-
/** PD059: the configured policy module throws or exports no policy, so every policy check is skipped. */
|
|
1146
|
-
async function pd059(project) {
|
|
1147
|
-
const load = await project.policyLoad();
|
|
1148
|
-
if (load.status !== "failed") return [];
|
|
1149
|
-
return [{
|
|
1150
|
-
code: "PD059",
|
|
1151
|
-
severity: "error",
|
|
1152
|
-
message: `policy module ${load.path} did not load (${load.message}), so doctor skipped every check that reads the policy`,
|
|
1153
|
-
fix: "fix the module so it exports policy from definePolicy, or point policy in permdock.config.ts at the module that does"
|
|
1154
|
-
}];
|
|
1155
|
-
}
|
|
1156
|
-
/** PD060: a source file under srcPath has a syntax error, so collect and usage miss what it references. */
|
|
1157
|
-
async function pd060(project) {
|
|
1158
|
-
return ((await project.collected()).scan?.unparsed ?? []).map((item) => ({
|
|
1159
|
-
code: "PD060",
|
|
1160
|
-
severity: "warning",
|
|
1161
|
-
message: `${item.file}:${String(item.line)} does not parse (${item.message}), so the catalog and usage report miss the permissions it references`,
|
|
1162
|
-
fix: "fix the syntax error, or exclude the file from collect.srcPath"
|
|
1163
|
-
}));
|
|
1164
|
-
}
|
|
1165
|
-
function formatDoctor(report, color) {
|
|
1166
|
-
const style = createStyle(color);
|
|
1167
|
-
const lines = [style.paint("bold", "permdock doctor"), ""];
|
|
1168
|
-
for (const finding of report.findings) {
|
|
1169
|
-
const mark = finding.severity === "error" ? style.errorMark : style.warnMark;
|
|
1170
|
-
lines.push(` ${mark} ${style.paint("bold", finding.code)} ${finding.message}`);
|
|
1171
|
-
lines.push(` ${style.paint("dim", `fix: ${finding.fix}`)}`);
|
|
1172
|
-
}
|
|
1173
|
-
if (report.findings.length === 0) lines.push(" no findings");
|
|
1174
|
-
lines.push("");
|
|
1175
|
-
lines.push(` ${String(report.errors)} error${report.errors === 1 ? "" : "s"}, ${String(report.warnings)} warning${report.warnings === 1 ? "" : "s"}`);
|
|
1176
|
-
return `${lines.join("\n")}\n`;
|
|
1177
|
-
}
|
|
1178
|
-
async function supabaseSetup(input) {
|
|
1179
|
-
const policy = await input.policy();
|
|
1180
|
-
if (policy === void 0) return [];
|
|
1181
|
-
let manifest;
|
|
1182
|
-
try {
|
|
1183
|
-
manifest = supabaseHookManifest(scopeList(policy.scopes), input.config, { out: hookOut(input.cwd, input.config) }, policy);
|
|
1184
|
-
} catch {
|
|
1185
|
-
return [];
|
|
1186
|
-
}
|
|
1187
|
-
return pd039({
|
|
1188
|
-
cwd: input.cwd,
|
|
1189
|
-
config: input.config,
|
|
1190
|
-
manifest
|
|
1191
|
-
});
|
|
1192
|
-
}
|
|
1193
|
-
//#endregion
|
|
1194
|
-
export { runDoctor };
|