shopify 4.9.2 → 4.9.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/analytics-CLYTLX2B.js +1 -0
- package/dist/analytics-K6IFFIPX.js +1 -0
- package/dist/bootstrap.js +1 -1
- package/dist/{chunk-ELJ34WWD.js → chunk-2E2NYRQH.js} +1 -1
- package/dist/{chunk-JUCOIXG5.js → chunk-2GYIQINP.js} +1 -1
- package/dist/{chunk-FNKTF2OF.js → chunk-2NRFFAV4.js} +1 -1
- package/dist/{chunk-4565O5TG.js → chunk-2S7WRX2J.js} +1 -1
- package/dist/{chunk-HKZ75UKL.js → chunk-2WQGEYBB.js} +1 -1
- package/dist/{chunk-N4AR2B6D.js → chunk-32HWTEOM.js} +1 -1
- package/dist/chunk-3B3CJS5I.js +1 -0
- package/dist/chunk-3FCN4NS6.js +6 -0
- package/dist/{chunk-CDIKRQ6M.js → chunk-3FPVTPQO.js} +1 -1
- package/dist/{chunk-JCKA3O5A.js → chunk-3JN6OWIO.js} +1 -1
- package/dist/{chunk-CNAMKKAK.js → chunk-3U2OMG7L.js} +1 -1
- package/dist/{chunk-3ZL4TDTH.js → chunk-3YAD263J.js} +1 -1
- package/dist/{chunk-OXN2DID2.js → chunk-43DSLBSX.js} +1 -1
- package/dist/{chunk-I55L6FOM.js → chunk-46I3BQZH.js} +1 -1
- package/dist/{chunk-5BO5HCN6.js → chunk-4J7HQGWI.js} +1 -1
- package/dist/{chunk-46U6C6C6.js → chunk-4LTQEQ4O.js} +1 -1
- package/dist/{chunk-TWS34WUT.js → chunk-4MUH4E2U.js} +1 -1
- package/dist/{chunk-EZ5F4ZFY.js → chunk-4Q5TSWQL.js} +3 -3
- package/dist/{chunk-IQIUFTR6.js → chunk-4Q7NZI5A.js} +1 -1
- package/dist/{chunk-BVVYKX4D.js → chunk-4RFKEFWW.js} +1 -1
- package/dist/{chunk-X6HLGOIV.js → chunk-52ZHZWF7.js} +1 -1
- package/dist/{chunk-SHBB5GST.js → chunk-543SYIY6.js} +1 -1
- package/dist/chunk-5BGUKF7L.js +1 -0
- package/dist/{chunk-3NRGGEGU.js → chunk-5LONOHNN.js} +1 -1
- package/dist/{chunk-R6IJRVVM.js → chunk-5LZ5L2FF.js} +1 -1
- package/dist/{chunk-AVOSD4NV.js → chunk-5RJ6KQHZ.js} +1 -1
- package/dist/{chunk-WZEKBD7B.js → chunk-66JMKA7C.js} +1 -1
- package/dist/{chunk-J7ZG6SO3.js → chunk-6BVQSX7D.js} +1 -1
- package/dist/{chunk-G627WGIC.js → chunk-6HWDGTSX.js} +2 -2
- package/dist/{chunk-RBPMV3KB.js → chunk-6JOZHD34.js} +1 -1
- package/dist/{chunk-WINVPFT5.js → chunk-6ZSI3C4G.js} +1 -1
- package/dist/{chunk-TEASB2WU.js → chunk-73LX4DOM.js} +1 -1
- package/dist/{chunk-KY36HSIB.js → chunk-7AP5E5CF.js} +1 -1
- package/dist/{chunk-OERMQ2FP.js → chunk-7IYSJGFF.js} +2 -2
- package/dist/{chunk-3ATOVFLZ.js → chunk-7KCHSZ6H.js} +1 -1
- package/dist/{chunk-NDMISEDF.js → chunk-7KVTQDRW.js} +1 -1
- package/dist/{chunk-YKYJOUV7.js → chunk-7NHCYOKI.js} +1 -1
- package/dist/{chunk-Z5SFLTH6.js → chunk-7SIY6GZD.js} +1 -1
- package/dist/{chunk-BLBJT7BM.js → chunk-A36J5FWZ.js} +1 -1
- package/dist/{chunk-UB2LAIXS.js → chunk-A6KNDSNT.js} +1 -1
- package/dist/{chunk-JJHJYBPQ.js → chunk-A6NAQ2Y3.js} +1 -1
- package/dist/{chunk-EMRUM6AA.js → chunk-A75SA57D.js} +1 -1
- package/dist/{chunk-65I35VBS.js → chunk-A76YYGZT.js} +1 -1
- package/dist/{chunk-UOKX3AME.js → chunk-ACGPDJ6Q.js} +1 -1
- package/dist/{chunk-PV5C6Q2P.js → chunk-ADGPOQ5M.js} +1 -1
- package/dist/{chunk-PQMKPXFV.js → chunk-ADL6JSX7.js} +1 -1
- package/dist/{chunk-J66WAKXN.js → chunk-ADVNRGOU.js} +1 -1
- package/dist/{chunk-D2S566HF.js → chunk-AF36J7R7.js} +1 -1
- package/dist/{chunk-ZJ4H3XEL.js → chunk-B32O5KET.js} +1 -1
- package/dist/{chunk-5TQOMAJS.js → chunk-BHOZAIHS.js} +1 -1
- package/dist/{chunk-KPNUSDCW.js → chunk-BWCPDFEY.js} +1 -1
- package/dist/{chunk-SDHDDNKM.js → chunk-BZSCRXYR.js} +3 -3
- package/dist/{chunk-LIQSNIB3.js → chunk-C4G7OKJD.js} +1 -1
- package/dist/{chunk-6H3HVSO5.js → chunk-CB6QT5K5.js} +1 -1
- package/dist/{chunk-7EDXMGBQ.js → chunk-CGCM5DB7.js} +1 -1
- package/dist/{chunk-XE5QH4D7.js → chunk-CNA5YGXA.js} +1 -1
- package/dist/{chunk-UYRXUGLY.js → chunk-CNHO3T4F.js} +1 -1
- package/dist/{chunk-4S2WKMLJ.js → chunk-CPT2VBMR.js} +1 -1
- package/dist/{chunk-GPJJA3FX.js → chunk-CQKZSB7Z.js} +1 -1
- package/dist/{chunk-7ZNR4YL4.js → chunk-CTDETNGR.js} +1 -1
- package/dist/{chunk-MP5NZL7C.js → chunk-CTVOXVXG.js} +1 -1
- package/dist/{chunk-2X3HYG4B.js → chunk-DE5BUQL5.js} +1 -1
- package/dist/{chunk-BVR3GF2L.js → chunk-DHWY73OS.js} +1 -1
- package/dist/chunk-DPGUL2TW.js +1 -0
- package/dist/{chunk-PYILLIGT.js → chunk-DWDR7XMI.js} +1 -1
- package/dist/{chunk-RY6TZDVV.js → chunk-E7PW5SVD.js} +1 -1
- package/dist/{chunk-Q4TZD2M6.js → chunk-EA5LKCRF.js} +1 -1
- package/dist/{chunk-HYC3JXR4.js → chunk-EJ46P6A3.js} +1 -1
- package/dist/{chunk-QN6SITWN.js → chunk-EM6KPZCM.js} +1 -1
- package/dist/{chunk-TUE67I2F.js → chunk-FHOBQUQT.js} +1 -1
- package/dist/{chunk-2R2PKQQV.js → chunk-FKGN6F3F.js} +1 -1
- package/dist/{chunk-KYBZ2MBA.js → chunk-FLPWT4AK.js} +1 -1
- package/dist/{chunk-55QTRPZ2.js → chunk-FMBC7LZB.js} +1 -1
- package/dist/{chunk-OMEWUFUL.js → chunk-GB4WR6DY.js} +1 -1
- package/dist/{chunk-EXKTQF26.js → chunk-GBG6SUK4.js} +1 -1
- package/dist/{chunk-JCO2SDDR.js → chunk-GIGA7EU3.js} +1 -1
- package/dist/{chunk-WXT5AKZV.js → chunk-GOBTF2ZH.js} +1 -1
- package/dist/{chunk-OIQVPTNQ.js → chunk-GPJAWTR7.js} +1 -1
- package/dist/{chunk-7ARGLS6W.js → chunk-GQZ3KCWJ.js} +1 -1
- package/dist/{chunk-C4GEP3CX.js → chunk-H3N5LKJD.js} +1 -1
- package/dist/{chunk-525BMYNR.js → chunk-H4PXFC7T.js} +1 -1
- package/dist/{chunk-ELQ5NPPB.js → chunk-H5ZPH2EC.js} +1 -1
- package/dist/{chunk-RAQP66LR.js → chunk-HHNMV6GX.js} +1 -1
- package/dist/{chunk-PQH4U2XV.js → chunk-HJPAHR2V.js} +1 -1
- package/dist/{chunk-XTKXBQVA.js → chunk-HQ4JWC5M.js} +1 -1
- package/dist/{chunk-UMSO6MOI.js → chunk-IH2CB73H.js} +1 -1
- package/dist/{chunk-IESHPQLC.js → chunk-IJM576XA.js} +1 -1
- package/dist/{chunk-3NUQGW57.js → chunk-IKN6ICJR.js} +1 -1
- package/dist/{chunk-RCYQPOJW.js → chunk-IN634BRF.js} +3 -3
- package/dist/{chunk-Q26ES5T3.js → chunk-IR6OW5R2.js} +1 -1
- package/dist/{chunk-JKWB7WTO.js → chunk-ISU7Q2EB.js} +1 -1
- package/dist/{chunk-EMKLN75K.js → chunk-J6BK6SNB.js} +1 -1
- package/dist/{chunk-NLKO2J7T.js → chunk-JQ3DMPI6.js} +1 -1
- package/dist/{chunk-6NLGZGYE.js → chunk-JTXQPPKQ.js} +1 -1
- package/dist/{chunk-7IRPE3M5.js → chunk-JYWFR6VL.js} +1 -1
- package/dist/chunk-KD5AYVA2.js +1 -0
- package/dist/{chunk-PG6XEOYF.js → chunk-KEI3OJVP.js} +1 -1
- package/dist/{chunk-EP5HIGAL.js → chunk-KFOU4WLJ.js} +1 -1
- package/dist/{chunk-BPVXYM6G.js → chunk-KI3VRWBP.js} +1 -1
- package/dist/{chunk-DTGIMRBX.js → chunk-KRL7FE6M.js} +1 -1
- package/dist/{chunk-BLCR7BXU.js → chunk-KWKRJYTY.js} +1 -1
- package/dist/{chunk-2NMICU24.js → chunk-KXZQKOKF.js} +1 -1
- package/dist/{chunk-LWF4EV22.js → chunk-KY4WH4ZK.js} +1 -1
- package/dist/{chunk-T3JUBPBU.js → chunk-KZ54OSBH.js} +1 -1
- package/dist/{chunk-YXKMPCFH.js → chunk-KZE6JT42.js} +1 -1
- package/dist/{chunk-OCQZ2ULI.js → chunk-KZWYFH7M.js} +1 -1
- package/dist/chunk-L4DFMMOI.js +1 -0
- package/dist/{chunk-5JW2EYSE.js → chunk-LGRZQTQL.js} +1 -1
- package/dist/{chunk-AKP4QAGL.js → chunk-LIE6QSQ2.js} +1 -1
- package/dist/{chunk-VXGXK66W.js → chunk-LOTMFUSV.js} +1 -1
- package/dist/{chunk-CHRFYG6W.js → chunk-MAQ3SRQF.js} +1 -1
- package/dist/{chunk-2P5DUPT7.js → chunk-MBE23GGW.js} +1 -1
- package/dist/{chunk-SZ2JOXTG.js → chunk-MDGF3K45.js} +1 -1
- package/dist/{chunk-MIP2Y34N.js → chunk-ME4EYPKZ.js} +1 -1
- package/dist/{chunk-7MMPQMU7.js → chunk-MMCDBR23.js} +1 -1
- package/dist/{chunk-R43LXE66.js → chunk-MNHZWEXI.js} +1 -1
- package/dist/{chunk-YIV3CWED.js → chunk-MROQ2FRU.js} +1 -1
- package/dist/{chunk-SWNY3RUM.js → chunk-MVFBLQ4J.js} +1 -1
- package/dist/{chunk-FICC6IFE.js → chunk-MWVT5HYW.js} +1 -1
- package/dist/{chunk-NS4FUSSK.js → chunk-MYIN4HYW.js} +1 -1
- package/dist/{chunk-SQIESCOJ.js → chunk-MYP6QEJW.js} +1 -1
- package/dist/{chunk-UVC3IXM3.js → chunk-NC64STMQ.js} +1 -1
- package/dist/{chunk-WNE2MZAZ.js → chunk-NL6DJQJG.js} +1 -1
- package/dist/{chunk-E64OFJHF.js → chunk-NO3QDX7R.js} +1 -1
- package/dist/{chunk-2VDCTMLY.js → chunk-NYVG34OW.js} +1 -1
- package/dist/{chunk-U5L2P36D.js → chunk-O36EB75H.js} +1 -1
- package/dist/{chunk-IJLD5UYC.js → chunk-O7YHDFOO.js} +1 -1
- package/dist/{chunk-3E3MCY5O.js → chunk-OSGXBBUJ.js} +3 -3
- package/dist/{chunk-JWDR66AS.js → chunk-OUO2T3CC.js} +1 -1
- package/dist/{chunk-JAX56OYG.js → chunk-OVTWO3W7.js} +1 -1
- package/dist/{chunk-QA2NOPXG.js → chunk-P4MNJULR.js} +1 -1
- package/dist/{chunk-HZJHGHLN.js → chunk-P7YDAQ53.js} +1 -1
- package/dist/{chunk-7GJYJ6DU.js → chunk-PE6H7U6N.js} +1 -1
- package/dist/{chunk-Q7PF4VKI.js → chunk-PFBESNYV.js} +62 -51
- package/dist/{chunk-SRDKLZ4O.js → chunk-PFPVFPHV.js} +1 -1
- package/dist/{chunk-RW2MONAS.js → chunk-PQEFZTEJ.js} +1 -1
- package/dist/chunk-Q54LU5W4.js +1 -0
- package/dist/{chunk-I2LILWDI.js → chunk-QKJ542VO.js} +1 -1
- package/dist/{chunk-DH5E6NM3.js → chunk-QKSN6ZYW.js} +1 -1
- package/dist/{chunk-IOS4IDZ2.js → chunk-QZGVZMB5.js} +1 -1
- package/dist/{chunk-ER7ZTF26.js → chunk-RJGFFGEN.js} +1 -1
- package/dist/{chunk-BK4VCXDD.js → chunk-S34ZYEZS.js} +1 -1
- package/dist/{chunk-3EEH3A27.js → chunk-SZLNUMWO.js} +1 -1
- package/dist/{chunk-JLGXJHPX.js → chunk-T3LZJIZN.js} +1 -1
- package/dist/{chunk-EGJQEKAO.js → chunk-TFB4JJFQ.js} +1 -1
- package/dist/{chunk-THW6UDZH.js → chunk-TWT5LVEM.js} +1 -1
- package/dist/{chunk-MQZPASSP.js → chunk-UGJN5Q6Y.js} +1 -1
- package/dist/{chunk-TPPBEVNQ.js → chunk-UKINO57E.js} +1 -1
- package/dist/{chunk-3F42A3XV.js → chunk-UYWRJHAJ.js} +3 -3
- package/dist/{chunk-QMJZFKKU.js → chunk-UZL4B7LA.js} +1 -1
- package/dist/{chunk-EJY4LA2S.js → chunk-V4F4LQBQ.js} +1 -1
- package/dist/{chunk-HVU3D3ZU.js → chunk-VCAYMY7D.js} +2 -2
- package/dist/{chunk-ZZ7SOJHH.js → chunk-VDE7IIYQ.js} +1 -1
- package/dist/{chunk-B477XHLZ.js → chunk-VOOYVTYA.js} +1 -1
- package/dist/{chunk-MW3HFOPD.js → chunk-VWDVWHV7.js} +1 -1
- package/dist/{chunk-W56I6HOT.js → chunk-WEB63XPH.js} +1 -1
- package/dist/{chunk-DGOBYP74.js → chunk-WS54VCDC.js} +1 -1
- package/dist/{chunk-FU4UVINZ.js → chunk-WUGFTI5M.js} +1 -1
- package/dist/{chunk-PQCALXSD.js → chunk-X5VBX2QA.js} +1 -1
- package/dist/{chunk-JAY7AGAG.js → chunk-XBCFYD3T.js} +1 -1
- package/dist/{chunk-Z57YWHOE.js → chunk-XGFY6FV4.js} +1 -1
- package/dist/{chunk-PFIQR7XS.js → chunk-XMPLZWXP.js} +1 -1
- package/dist/{chunk-XRJ2GQ24.js → chunk-XSGYQXWO.js} +1 -1
- package/dist/{chunk-Q555FV2X.js → chunk-XVOUKPGD.js} +1 -1
- package/dist/{chunk-PI6FLLDD.js → chunk-XZHUUA6U.js} +1 -1
- package/dist/{chunk-VZLEYZLX.js → chunk-XZOSPVMV.js} +1 -1
- package/dist/{chunk-3U2MYUWG.js → chunk-Y45QFHVA.js} +1 -1
- package/dist/{chunk-SYNCIFJ6.js → chunk-YKK4PGCQ.js} +1 -1
- package/dist/{chunk-6SO5GA6P.js → chunk-YMAVVMN5.js} +1 -1
- package/dist/{chunk-M7QFZQ23.js → chunk-YN3NT2RC.js} +1 -1
- package/dist/{chunk-PGE4HGLA.js → chunk-YPGZANN3.js} +1 -1
- package/dist/{chunk-P6TOIMMF.js → chunk-YS447QGN.js} +1 -1
- package/dist/{chunk-RDO5IWB5.js → chunk-YUM5XGSV.js} +1 -1
- package/dist/{chunk-CGH5Y6XE.js → chunk-YVX2OJ7N.js} +1 -1
- package/dist/{chunk-ANHSOQTZ.js → chunk-YWS2OTSC.js} +1 -1
- package/dist/{chunk-MHZSSRHP.js → chunk-YZXNXWDR.js} +1 -1
- package/dist/{chunk-P2MVR6CG.js → chunk-Z5M2VWQZ.js} +1 -1
- package/dist/{chunk-GS52MX5F.js → chunk-ZBMEU2CE.js} +1 -1
- package/dist/{chunk-4QEALPWD.js → chunk-ZIL2P7FL.js} +1 -1
- package/dist/{chunk-RGZCV56L.js → chunk-ZRW47MJT.js} +1 -1
- package/dist/{chunk-T6TYF72N.js → chunk-ZZWGNETX.js} +1 -1
- package/dist/cli/commands/app/app-logs/sources.js +1 -1
- package/dist/cli/commands/app/build.js +1 -1
- package/dist/cli/commands/app/bulk/cancel.js +1 -1
- package/dist/cli/commands/app/bulk/execute.js +1 -1
- package/dist/cli/commands/app/bulk/status.js +1 -1
- package/dist/cli/commands/app/config/link.js +1 -1
- package/dist/cli/commands/app/config/pull.js +1 -1
- package/dist/cli/commands/app/config/use.js +1 -1
- package/dist/cli/commands/app/config/validate.js +1 -1
- package/dist/cli/commands/app/demo/watcher.js +1 -1
- package/dist/cli/commands/app/deploy.js +1 -1
- package/dist/cli/commands/app/dev/clean.js +1 -1
- package/dist/cli/commands/app/dev.js +1 -1
- package/dist/cli/commands/app/env/pull.js +1 -1
- package/dist/cli/commands/app/env/show.js +1 -1
- package/dist/cli/commands/app/execute.js +1 -1
- package/dist/cli/commands/app/function/build.js +1 -1
- package/dist/cli/commands/app/function/info.js +1 -1
- package/dist/cli/commands/app/function/replay.js +1 -1
- package/dist/cli/commands/app/function/run.js +1 -1
- package/dist/cli/commands/app/function/schema.js +1 -1
- package/dist/cli/commands/app/function/typegen.js +1 -1
- package/dist/cli/commands/app/generate/extension.js +1 -1
- package/dist/cli/commands/app/graphiql.js +1 -1
- package/dist/cli/commands/app/import/channel-config.js +1 -1
- package/dist/cli/commands/app/import/custom-data-definitions.js +1 -1
- package/dist/cli/commands/app/import/dashboard-extensions.js +1 -1
- package/dist/cli/commands/app/import-custom-data-definitions.js +1 -1
- package/dist/cli/commands/app/import-extensions.js +1 -1
- package/dist/cli/commands/app/info.js +1 -1
- package/dist/cli/commands/app/init.js +1 -1
- package/dist/cli/commands/app/logs.js +1 -1
- package/dist/cli/commands/app/release.js +1 -1
- package/dist/cli/commands/app/security/check.js +1 -1
- package/dist/cli/commands/app/security/clean.js +1 -1
- package/dist/cli/commands/app/security/instructions.js +1 -1
- package/dist/cli/commands/app/security/record.js +1 -1
- package/dist/cli/commands/app/security/review.js +1 -1
- package/dist/cli/commands/app/subscription-migrations/cancel.js +1 -1
- package/dist/cli/commands/app/subscription-migrations/list.js +1 -1
- package/dist/cli/commands/app/subscription-migrations/schedule.js +1 -1
- package/dist/cli/commands/app/subscription-migrations/status.js +1 -1
- package/dist/cli/commands/app/subscription-migrations/unschedule.js +1 -1
- package/dist/cli/commands/app/versions/list.js +1 -1
- package/dist/cli/commands/app/webhook/trigger.js +1 -1
- package/dist/cli/commands/auth/login.js +1 -1
- package/dist/cli/commands/auth/logout.js +1 -1
- package/dist/cli/commands/cache/clear.js +1 -1
- package/dist/cli/commands/config/autoupgrade/off.js +1 -1
- package/dist/cli/commands/config/autoupgrade/on.js +1 -1
- package/dist/cli/commands/config/autoupgrade/status.js +1 -1
- package/dist/cli/commands/debug/command-flags.js +1 -1
- package/dist/cli/commands/doc/fetch.js +1 -1
- package/dist/cli/commands/doc/search.js +1 -1
- package/dist/cli/commands/docs/generate.js +1 -1
- package/dist/cli/commands/doctor-release/doctor-release.js +1 -1
- package/dist/cli/commands/doctor-release/theme/index.js +1 -1
- package/dist/cli/commands/help.js +1 -1
- package/dist/cli/commands/kitchen-sink/async.js +1 -1
- package/dist/cli/commands/kitchen-sink/index.js +1 -1
- package/dist/cli/commands/kitchen-sink/prompts.js +1 -1
- package/dist/cli/commands/kitchen-sink/static.js +1 -1
- package/dist/cli/commands/notifications/generate.js +1 -1
- package/dist/cli/commands/notifications/list.js +1 -1
- package/dist/cli/commands/organization/list.js +1 -1
- package/dist/cli/commands/search.js +1 -1
- package/dist/cli/commands/send-analytics.js +1 -1
- package/dist/cli/commands/theme/check.js +1 -1
- package/dist/cli/commands/theme/console.js +1 -1
- package/dist/cli/commands/theme/delete.js +1 -1
- package/dist/cli/commands/theme/dev.js +1 -1
- package/dist/cli/commands/theme/duplicate.js +1 -1
- package/dist/cli/commands/theme/info.js +1 -1
- package/dist/cli/commands/theme/init.js +1 -1
- package/dist/cli/commands/theme/language-server.js +1 -1
- package/dist/cli/commands/theme/list.js +1 -1
- package/dist/cli/commands/theme/metafields/pull.js +1 -1
- package/dist/cli/commands/theme/open.js +1 -1
- package/dist/cli/commands/theme/package.js +1 -1
- package/dist/cli/commands/theme/preview.js +1 -1
- package/dist/cli/commands/theme/profile.js +1 -1
- package/dist/cli/commands/theme/publish.js +1 -1
- package/dist/cli/commands/theme/pull.js +1 -1
- package/dist/cli/commands/theme/push.js +1 -1
- package/dist/cli/commands/theme/rename.js +1 -1
- package/dist/cli/commands/theme/share.js +1 -1
- package/dist/cli/commands/upgrade.js +1 -1
- package/dist/cli/commands/version.js +1 -1
- package/dist/cli/help.js +1 -1
- package/dist/{command-json-schema-HYYJSZAH.js → command-json-schema-AWZEAYEU.js} +1 -1
- package/dist/command-registry.js +1 -1
- package/dist/{conf-store-CDZ5YSQM.js → conf-store-TBERV4YJ.js} +1 -1
- package/dist/{custom-oclif-loader-NVOK22CJ.js → custom-oclif-loader-RHFO4GO3.js} +1 -1
- package/dist/{deprecations-33DG6ZUL.js → deprecations-FNYLBULK.js} +1 -1
- package/dist/dist-HV5W7PYL.js +1 -0
- package/dist/dist-VKKGDJQ2.js +1 -0
- package/dist/environments-XC6OMZY2.js +1 -0
- package/dist/{error-V77H2UYX.js → error-SI2QH7DT.js} +1 -1
- package/dist/error-handler-XOEXJ4TY.js +1 -0
- package/dist/{fs-SKH3XZU7.js → fs-J67RRM6W.js} +1 -1
- package/dist/hooks/did-you-mean.js +1 -1
- package/dist/hooks/hydrogen-init.js +1 -1
- package/dist/hooks/postrun.js +1 -1
- package/dist/hooks/prerun.js +1 -1
- package/dist/hooks/public-metadata.js +1 -1
- package/dist/hooks/sensitive-metadata.js +1 -1
- package/dist/hooks/tunnel-provider.js +1 -1
- package/dist/hooks/tunnel-start.js +1 -1
- package/dist/index.js +1 -1
- package/dist/{is-global-2G4BLF7H.js → is-global-BSY2VR5F.js} +1 -1
- package/dist/{json-error-WSMAL66C.js → json-error-BVX244JK.js} +1 -1
- package/dist/{local-G2Z24BHM.js → local-VCJXKA4Z.js} +1 -1
- package/dist/{metadata-Y3RKC4P2.js → metadata-G7M7BSIE.js} +1 -1
- package/dist/{node-package-manager-A7HTBJKY.js → node-package-manager-PMTBCYAX.js} +1 -1
- package/dist/notifications-system-O2R4VN7A.js +1 -0
- package/dist/{os-7VQXVZWF.js → os-MMNGCSNT.js} +1 -1
- package/dist/{output-ZFFHJGNK.js → output-2Q6CSC33.js} +1 -1
- package/dist/{system-ZLITSFDC.js → system-DQ2SKK6C.js} +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/{ui-YSFALYSL.js → ui-QOSX5V7Z.js} +1 -1
- package/dist/upgrade-7HYLPUVK.js +1 -0
- package/dist/version-BPEZT4N3.js +1 -0
- package/dist/version-VNCEYJRH.js +1 -0
- package/dist/{workerd-RNDPF36Q.js → workerd-2RFRLQVH.js} +1 -1
- package/oclif.manifest.json +4 -4
- package/package.json +7 -7
- package/dist/analytics-H2ANKZ2S.js +0 -1
- package/dist/analytics-Q6HJRPRX.js +0 -1
- package/dist/chunk-246AZCDT.js +0 -1
- package/dist/chunk-DV77BSKW.js +0 -1
- package/dist/chunk-FQKURDZR.js +0 -6
- package/dist/chunk-JUVJNIEA.js +0 -1
- package/dist/chunk-KOYBR5MV.js +0 -1
- package/dist/chunk-QLHPTMIH.js +0 -1
- package/dist/chunk-TQ7MHGY4.js +0 -1
- package/dist/dist-DODCXJ2A.js +0 -1
- package/dist/dist-YXMAKIFS.js +0 -1
- package/dist/environments-KBRQNE4R.js +0 -1
- package/dist/error-handler-JY3ZKE2G.js +0 -1
- package/dist/notifications-system-E64OSVXF.js +0 -1
- package/dist/upgrade-I4JTR4LA.js +0 -1
- package/dist/version-AOVO47AW.js +0 -1
- package/dist/version-LQV54JBC.js +0 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import{a as
|
|
1
|
+
import{a as ve}from"./chunk-LOTMFUSV.js";import{A as Be,B as Qe,C as ln,D as pn,Ea as gn,Ia as yn,da as fn,fa as mn,m as be,n as We,r as on,s as an,t as dn}from"./chunk-KEI3OJVP.js";import{L as Ve,X as cn,ea as un,fa as hn,q as ze}from"./chunk-X5VBX2QA.js";import{a as rn}from"./chunk-4LTQEQ4O.js";import{da as js}from"./chunk-Z5M2VWQZ.js";import{a as Ge}from"./chunk-7IYSJGFF.js";import{a as Wt}from"./chunk-DPGUL2TW.js";import{E as en,P as nn}from"./chunk-IN634BRF.js";import{a as Zt}from"./chunk-YTJ3EPD3.js";import{i as I,l as tn}from"./chunk-VDE7IIYQ.js";import{Ia as je,Ka as Jt,La as Kt,Nb as sn,Xa as Yt,Ya as He,b as p,fb as Xt,qa as Bt,ra as Qt,sa as ce}from"./chunk-UYWRJHAJ.js";import{b as $s}from"./chunk-I3J63RFV.js";import{d as b,e as R,f as T,g as w,h as Vt,i as S,j as k,k as O,o as P,q as L}from"./chunk-3VYOLEQE.js";import{e as zt,g as u}from"./chunk-VKWPEFWQ.js";u();var vn=zt($s(),1);function bn(e){return vn.Flags.boolean({description:"Scan --path as an app with no app configuration file. Config checks are skipped. Requires --client-id.",env:"SHOPIFY_FLAG_WITHOUT_APP_CONFIG",exclusive:["config"],...e})}var Hs={path:ve.path,config:ve.config,"client-id":ve["client-id"],"without-app-config":bn({dependsOn:["client-id"]})},Mc={...Hs,"without-app-config":bn({relationships:[{type:"all",flags:[{name:"client-id",when:async e=>!e.all}]}]})};u();import{lstat as Gs}from"node:fs/promises";var zs={confirmScanWithoutAppConfig:e=>en({message:`No app configuration found at or above ${e}. Scan it without app configuration? Config checks will be skipped.`,confirmationMessage:"Yes, scan without app configuration",cancellationMessage:"No",defaultValue:!1}),pickClientId:async e=>(await mn(gn(e))).apiKey,pickConfigFile:async e=>(await an(e)).valueOrAbort(),lookUpApp:async e=>{await fn({apiKey:e,offerReset:!1})}};function wn(e){return e.kind==="config"?k(e.appConfigFilePath):void 0}function td(e){return e.kind==="no-config"?e.clientId:e.clientIdOverride??e.configClientId}function nd(e){return e.kind==="no-config"?e.clientId:e.clientIdOverride??k(e.appConfigFilePath,".toml")}async function id(e){let t=await we(e.path);if(e.withoutAppConfig)return t;try{return await ce((await Be.load(e.path)).directory)}catch(n){if(!(n instanceof Qe))throw n;Ke(e.path)}}async function sd(e,t=zs){if(e.withoutAppConfig){if(!e.clientId)throw new I("--without-app-config requires --client-id.");let n=await we(e.path);return await Je(e,t),{kind:"no-config",appDirectory:n,clientId:e.clientId,clientIdSource:"flag"}}await we(e.path);try{let n=e.config?void 0:await Ws(e,t),{app:i,project:s,activeConfig:r}=await yn({directory:e.path,userProvidedConfigName:e.config??n?.fileName,skipPrompts:!e.allowPrompts});await Je(e,t);let o=await ce(i.directory);return{kind:"config",appDirectory:o,appConfigFilePath:b(o,k(i.configPath)),appConfigDirectories:await Bs(s,r.file),configClientId:i.configuration.client_id||void 0,clientIdOverride:e.clientId,appConfigFilePicked:n?.picked}}catch(n){if(!(n instanceof Qe))throw n;return Vs(e,t)}}async function Ws(e,t){let{directory:n}=await Be.load(e.path),i=dn(n)?.configFile;if(i&&He(b(n,i)))return;if(He(b(n,ze.app)))return i?{fileName:ze.app,picked:!1}:void 0;let s=(await on(n)).map(r=>k(r));return s.length===1?{fileName:s[0],picked:!1}:((!e.allowPrompts||e.clientId)&&Js(n,s,e.clientId),{fileName:await t.pickConfigFile(n),picked:!0})}async function Vs(e,t){e.allowPrompts||Ke(e.path);let n=await we(e.path);return await Je(e,t),await t.confirmScanWithoutAppConfig(e.path)||Ke(e.path),e.clientId?{kind:"no-config",appDirectory:n,clientId:e.clientId,clientIdSource:"flag"}:{kind:"no-config",appDirectory:n,clientId:await t.pickClientId(n),clientIdSource:"picker"}}async function Je(e,t){e.validateClientIdFlag&&e.clientId!==void 0&&await t.lookUpApp(e.clientId)}async function rd(e){let t=[];for(let n of e){let i=T(L(),n),s=await xn(i);if(s===void 0)throw new I(`--include-dir ${n}: directory doesn't exist.`);if(!await je(s))throw new I(`--include-dir ${n}: not a directory.`);t.push(s)}return t}async function Bs(e,t){let n=[...ln(e,t),...pn(e,t)].map(s=>S(s.path)).filter(s=>!P(e.directory,s)),i=[];for(let s of new Set(n))await Qs(e.directory,s)||i.push(await ce(s));return i.sort()}async function Qs(e,t){let n=w(e,t).split("/"),i=b(e,...n.filter(s=>s===".."));for(let s of n.filter(r=>r!==".."))if(i=b(i,s),(await Gs(i)).isSymbolicLink())return!0;return!1}function od(e,t,n=[]){let i=a=>b(e,w(e,a))!==R(a),s=t.find(i);if(s)throw new I(`--include-dir ${s}: must be on the same drive as the app directory.`);let r=n.find(i);if(r)throw new I(`Extension or web directory ${r}: must be on the same drive as the app directory.`);let o=[{directory:e,origin:"app_directory"},...t.map(a=>({directory:a,origin:"include_dir"})),...n.map(a=>({directory:a,origin:"app_config_directory"}))].filter((a,c,l)=>l.findIndex(({directory:d})=>d===a.directory)===c);return{scanDirectories:o.filter(a=>!o.some(({directory:c})=>c!==a.directory&&P(c,a.directory))),requestedScanDirectories:o.map(({directory:a})=>a)}}function Ke(e){throw new I(`No app configuration found at or above ${e}.`,"Pass `--path` to your app directory, or scan without app configuration with `--without-app-config --client-id <client-id>`.")}function Js(e,t,n){let i=t.map(s=>be(s)??s).join(", ");throw new I(`${t.length} app configurations found in ${e}, and none is selected.`,n?`\`--client-id\` can't be combined with \`--config\`, so first select one with \`shopify app config use <config>\`: ${i}.`:`Pass \`--config\` with one of: ${i}.`)}async function we(e){let t=await xn(e);if(t===void 0||!await je(t))throw new I(`--path ${e}: not a directory.`);return t}async function xn(e){return ce(e).catch(()=>{})}u();import{realpathSync as Ks}from"node:fs";var Ys={include_dirs:[],excludes:[],no_git_ignore:!1};function kn(e){try{return Ks(e)}catch{return T(e)}}function Xs(e){return kn(e)===kn(L())?[]:[{flag:"--path",value:w(L(),e)}]}function Zs(e){if(e.kind==="no-config")return[{flag:"--client-id",value:e.clientId},"--without-app-config"];if(e.clientIdOverride)return[{flag:"--client-id",value:e.clientIdOverride}];let t=wn(e),n=t?be(t):void 0;return n?[{flag:"--config",value:n}]:[]}function er(e){return[...e.include_dirs.map(t=>({flag:"--include-dir",value:t})),...e.excludes.map(t=>({flag:"--exclude",value:t})),...e.no_git_ignore?["--no-git-ignore"]:[]]}function hd(e,t,n=Ys){let i="shopify",s=r=>["app","security",r,...Xs(t),...Zs(e)];return{scan:{command:i,args:[...s("check"),...er(n)]},record:{command:i,args:s("record"),stdinPlaceholder:"<findings.json>"},review:{command:i,args:s("review")},clean:{command:i,args:s("clean")}}}function _n(e=process.platform,t=process.env){return e!=="win32"||tr(t)?"posix":nr(t)}function tr(e){if(e.MSYSTEM||e.CYGWIN)return!0;let t=e.SHELL??"";return/(?:^|[\\/])(bash|zsh|sh|fish|dash)(?:\.exe)?$/i.test(t)}function nr(e){return e.PROMPT?"cmd":e.POWERSHELL_DISTRIBUTION_CHANNEL||e.PSExecutionPolicyPreference||e.PSModulePath?"powershell":"cmd"}function ir(e,t){return t==="cmd"?sr(e):t==="powershell"?`'${e.replaceAll("'","''")}'`:`'${e.replaceAll("'","'\\''")}'`}function sr(e){return e.split("%").map(rr).join("^%")}function rr(e){let t=e.replace(/"/g,'""'),n=/\\+$/.exec(t)?.[0]??"";return`"${t}${n}"`}function fd(e,t=_n()){let n=Sn(e,t);return e.stdinPlaceholder?t==="powershell"?`Get-Content -Raw -Encoding UTF8 ${e.stdinPlaceholder} | ${n}`:`${n} < ${e.stdinPlaceholder}`:n}function md(e,t,n=_n()){let i=Sn(e,n);if(n==="posix")return`${i} <<'EOF'
|
|
2
2
|
${t}
|
|
3
3
|
EOF`;if(n==="powershell")return`@'
|
|
4
4
|
${t}
|
|
5
|
-
'@ | ${i}`}function
|
|
5
|
+
'@ | ${i}`}function Sn(e,t){let n=e.args.map(i=>typeof i=="string"?i:`${i.flag} ${ir(i.value,t)}`);return[e.command,...n].join(" ")}u();var j={high:3,medium:2,low:1},C=1,Ye=1,de=1,D="shopify-app-security";u();u();var Tn=[`---
|
|
6
6
|
id: ACTIVE_UPLOADS_AND_PRIVILEGED_PREVIEWS
|
|
7
7
|
version: 1
|
|
8
8
|
severity: high
|
|
@@ -79,7 +79,7 @@ Do not report:
|
|
|
79
79
|
without a concrete privileged renderer or execution surface.
|
|
80
80
|
`,`---
|
|
81
81
|
id: APP_PROXY_LIQUID_INJECTION
|
|
82
|
-
version:
|
|
82
|
+
version: 3
|
|
83
83
|
severity: high
|
|
84
84
|
precedence: prefer-agent
|
|
85
85
|
---
|
|
@@ -182,9 +182,9 @@ Do not report:
|
|
|
182
182
|
- Handlers that return only static content (no shop-specific data)
|
|
183
183
|
- Protocol-only canonicalization concerns with no demonstrated app-local exploit path
|
|
184
184
|
- Test handlers
|
|
185
|
-
`,"---\nid: COMMITTED_SECRET\nversion:
|
|
185
|
+
`,"---\nid: COMMITTED_SECRET\nversion: 4\nseverity: high\nprecedence: union\n---\n\n# Committed Secret\n\nInspect files skipped by deterministic secret scanning for committed credentials. Never quote or reproduce a secret; cite only the file and redacted credential kind, and recommend rotation.\n\nDo not report placeholders, public client identifiers (`SHOPIFY_API_KEY`, Stripe `pk_`), or files git confirms are untracked and ignored. Template env files (`.env.example`, `.sample`, `.template`, `.dist`) are findings only when they contain a known credential format.\n",`---
|
|
186
186
|
id: CREDENTIAL_BROWSER_LEAKAGE
|
|
187
|
-
version:
|
|
187
|
+
version: 2
|
|
188
188
|
severity: high
|
|
189
189
|
precedence: prefer-agent
|
|
190
190
|
---
|
|
@@ -194,7 +194,7 @@ precedence: prefer-agent
|
|
|
194
194
|
Trace credentials, access tokens, session tokens, and client secrets into loader/HTTP responses, browser globals, DOM values, client bundles, or external requests. Do not report server-only use or safe boolean/redacted/hash-derived values.
|
|
195
195
|
`,`---
|
|
196
196
|
id: CREDENTIAL_LOG_LEAKAGE
|
|
197
|
-
version:
|
|
197
|
+
version: 2
|
|
198
198
|
severity: high
|
|
199
199
|
precedence: prefer-agent
|
|
200
200
|
---
|
|
@@ -501,7 +501,7 @@ Do not report:
|
|
|
501
501
|
- guessed exploitability when the call path or trigger cannot be established from source.
|
|
502
502
|
`,`---
|
|
503
503
|
id: DEPRECATED_SCRIPT_TAG_SCOPE
|
|
504
|
-
version:
|
|
504
|
+
version: 2
|
|
505
505
|
severity: medium
|
|
506
506
|
precedence: prefer-agent
|
|
507
507
|
---
|
|
@@ -511,7 +511,7 @@ precedence: prefer-agent
|
|
|
511
511
|
Inspect parsed app scopes and JavaScript/TypeScript Admin API operations for deprecated ScriptTag capability. Report \`read_script_tags\`, \`write_script_tags\`, or ScriptTag create/update use under this single product ID.
|
|
512
512
|
`,`---
|
|
513
513
|
id: EOL_API_VERSION
|
|
514
|
-
version:
|
|
514
|
+
version: 2
|
|
515
515
|
severity: low
|
|
516
516
|
precedence: prefer-agent
|
|
517
517
|
---
|
|
@@ -519,7 +519,7 @@ precedence: prefer-agent
|
|
|
519
519
|
# Eol Api Version
|
|
520
520
|
|
|
521
521
|
Inspect the selected \`shopify.app*.toml\` plus React Router \`app/shopify.server.*\` declarations. Shopify publishes quarterly versions in January, April, July, and October and supports each stable version for 12 months; the app security check allows a documented 30-day extension grace period before reporting it as end-of-life. Cite the exact declaration. For malformed config, computed \`ApiVersion\` values, or a Shopify-announced exceptional extension, inspect the source and current lifecycle policy rather than inferring from unrelated constants.
|
|
522
|
-
`,"---\nid: EXPIRING_OFFLINE_TOKEN\nversion:
|
|
522
|
+
`,"---\nid: EXPIRING_OFFLINE_TOKEN\nversion: 2\nseverity: medium\nprecedence: prefer-agent\n---\n\n# Expiring Offline Token\n\nFor supported React Router apps, verify `expiringOfflineAccessTokens` is enabled and the selected session storage persists `expires`, `refreshToken`, and `refreshTokenExpires` metadata needed for refresh and rotation. `isOnline: false` selects an offline session; it does not disable token expiry and is not a finding. Report an explicit `expiringOfflineAccessTokens: false`. Treat absent or computed flags, custom storage, and ambiguous Prisma schemas as unresolved investigation: inspect storage adapters, migrations, and serialization before returning a clean result. Config-only and unsupported frameworks are handled by the runtime applicability boundary.\n",`---
|
|
523
523
|
id: INSECURE_WEBHOOK_URL
|
|
524
524
|
version: 2
|
|
525
525
|
severity: high
|
|
@@ -531,16 +531,23 @@ precedence: prefer-agent
|
|
|
531
531
|
Inspect webhook destinations and OAuth redirect URLs in the selected Shopify app configuration. Relative Shopify paths and valid pubsub/eventbridge webhook destinations are allowed. Report HTTP, malformed, credential-bearing, wildcard-host, wildcard-path, or otherwise unsafe configured callback URLs.
|
|
532
532
|
`,"---\nid: LIQUID_UNSAFE_RENDER\nversion: 1\nseverity: medium\nprecedence: union\n---\n\n# Liquid Unsafe Render\n\nInspect only theme-extension Liquid/HTML files the parser could not analyze. Liquid output is not automatically HTML-escaped. Check the destination: use `escape`/`escape_once` for HTML text and ordinary attributes, `json` when embedding a value as JavaScript data, and `metafield_tag` only for supported rich metafield rendering in HTML content. HTML escaping is not sufficient for event handlers, `srcdoc`, or a `<script src>` URL; avoid merchant-controlled output there and validate any dynamic URL. An ordinary non-script `src` attribute is not itself an executable sink. `{% raw %}` suppresses Liquid parsing, so apparent output inside it is literal text. Follow rendered snippets and inspect ambiguous filters or parser failures before reporting.\n",`---
|
|
533
533
|
id: METAFIELD_OFFLINE_TOKEN
|
|
534
|
-
version:
|
|
534
|
+
version: 2
|
|
535
535
|
severity: high
|
|
536
536
|
---
|
|
537
537
|
|
|
538
538
|
# Metafield Offline Token
|
|
539
539
|
|
|
540
|
-
Trace metafield writes and determine
|
|
540
|
+
Trace metafield and metaobject writes and determine their token provenance. Report a write that runs in an offline Admin API context that no verified request for the same shop authorizes: for example \`unauthenticated.admin(shop)\` or a stored offline session reached from an unauthenticated route, an unverified webhook or app proxy request, or a job whose shop or values come from request-controlled input.
|
|
541
|
+
|
|
542
|
+
An offline session returned by \`authenticate.admin(request)\` (the React Router SDK default when \`useOnlineTokens\` isn't set) is a verified, shop-bound context: Shopify verified the session token and the staff member's access to the app. Don't report a write only because its token is offline or because an online token would add user attribution or expiry. Report it when:
|
|
543
|
+
|
|
544
|
+
- the app defines its own per-user permission, for example an app-level role or allowlist check, and this write bypasses it. Name that permission.
|
|
545
|
+
- request-controlled values reach an app-reserved (\`$app\`) metafield or metaobject that the app treats as trusted state and merchants can't edit themselves, for example a definition with \`access.admin = "merchant_read"\` or no merchant access, or billing, entitlement or feature-flag data. Writing to the \`$app\` namespace isn't safe by itself.
|
|
546
|
+
|
|
547
|
+
Confirm token provenance and compensating authorization before reporting.
|
|
541
548
|
`,`---
|
|
542
549
|
id: MISSING_AUTHORIZATION_CHECK
|
|
543
|
-
version:
|
|
550
|
+
version: 3
|
|
544
551
|
severity: high
|
|
545
552
|
---
|
|
546
553
|
|
|
@@ -597,6 +604,16 @@ actions.
|
|
|
597
604
|
changes, direct URLs that survive permission downgrades, and backend actions
|
|
598
605
|
that remain callable after a feature is hidden or disabled in the UI.
|
|
599
606
|
|
|
607
|
+
## Shopify embedded apps (React Router)
|
|
608
|
+
|
|
609
|
+
\`authenticate.admin(request)\` verifies the Shopify session token, so the caller is a staff member of that shop whom Shopify has granted access to this app. With offline tokens (the SDK default), Admin API calls use the app's granted scopes rather than the staff member's own permissions. That is Shopify's standard embedded app model, not a finding by itself. Report a handler only when it skips a concrete authorization boundary inside the app:
|
|
610
|
+
|
|
611
|
+
- The app defines its own roles, owners, or allowlists (for example a check on \`sessionToken.sub\`, \`onlineAccessInfo.associated_user\`, \`account_owner\`, or an app database role), and a handler that performs the same or a more privileged operation doesn't apply it. Compare each route's loader and action, and sibling routes.
|
|
612
|
+
- A request-supplied identifier selects an app-owned record that belongs to another user of the app.
|
|
613
|
+
- The app uses online tokens to respect staff permissions on some paths, but a privileged path falls back to an offline session.
|
|
614
|
+
|
|
615
|
+
If none of these applies, record the check as executed with no finding.
|
|
616
|
+
|
|
600
617
|
## What to report
|
|
601
618
|
|
|
602
619
|
For each action that exposes a concrete unauthorized operation beyond authentication:
|
|
@@ -994,7 +1011,7 @@ Do not report:
|
|
|
994
1011
|
- Test files (under test/ or \\*\\_test.rb)
|
|
995
1012
|
`,`---
|
|
996
1013
|
id: REQUEST_CONTROLLED_ADMIN_CONTEXT
|
|
997
|
-
version:
|
|
1014
|
+
version: 4
|
|
998
1015
|
severity: high
|
|
999
1016
|
precedence: prefer-agent
|
|
1000
1017
|
---
|
|
@@ -1586,17 +1603,7 @@ Do not report:
|
|
|
1586
1603
|
- HTTP calls where the URL is validated against an allowlist
|
|
1587
1604
|
- \`exec()\` with literal strings (no user input)
|
|
1588
1605
|
- Shell calls with properly escaped arguments
|
|
1589
|
-
|
|
1590
|
-
id: STATIC_FRAME_ANCESTORS
|
|
1591
|
-
version: 1
|
|
1592
|
-
severity: high
|
|
1593
|
-
precedence: prefer-agent
|
|
1594
|
-
---
|
|
1595
|
-
|
|
1596
|
-
# Static Frame Ancestors
|
|
1597
|
-
|
|
1598
|
-
Inspect embedded-app Content-Security-Policy construction. Report wildcard or static cross-shop frame-ancestors policies; accept a policy derived safely for the authenticated shop plus Shopify Admin.
|
|
1599
|
-
`,`---
|
|
1606
|
+
`,"---\nid: STATIC_FRAME_ANCESTORS\nversion: 2\nseverity: high\nprecedence: prefer-agent\n---\n\n# Static Frame Ancestors\n\nInspect embedded-app Content-Security-Policy construction, including headers that app code sets after or instead of the SDK's. Report wildcard or static cross-shop frame-ancestors policies, such as `*`, `https:`, `*.myshopify.com`, or a fixed list of shop domains, whether the value is written literally or built from variables. Accept a policy derived safely for the shop plus Shopify Admin.\n\nThe React Router SDK's `addDocumentResponseHeaders` (from `shopifyApp()`, usually called in `entry.server`) is the accepted pattern: it sets `frame-ancestors` to the sanitized request shop plus Shopify Admin origins, including Shopify-operated development admin origins. Don't report it unless app code overrides or extends its header.\n",`---
|
|
1600
1607
|
id: TEXT_SETTING_HTML_SMUGGLING
|
|
1601
1608
|
version: 2
|
|
1602
1609
|
severity: high
|
|
@@ -1788,7 +1795,7 @@ Do not report:
|
|
|
1788
1795
|
- Test files
|
|
1789
1796
|
`,`---
|
|
1790
1797
|
id: UNAUTHENTICATED_ENDPOINT
|
|
1791
|
-
version:
|
|
1798
|
+
version: 3
|
|
1792
1799
|
severity: high
|
|
1793
1800
|
precedence: union
|
|
1794
1801
|
---
|
|
@@ -1924,7 +1931,7 @@ guard, sink, and impact, and record the check execution per the review
|
|
|
1924
1931
|
pack instructions.
|
|
1925
1932
|
`,`---
|
|
1926
1933
|
id: UNSAFE_INNERHTML
|
|
1927
|
-
version:
|
|
1934
|
+
version: 3
|
|
1928
1935
|
severity: high
|
|
1929
1936
|
precedence: union
|
|
1930
1937
|
---
|
|
@@ -2149,7 +2156,7 @@ severity: high
|
|
|
2149
2156
|
# Weak Shop Validation
|
|
2150
2157
|
|
|
2151
2158
|
Inspect shop-domain validation across aliases and helpers. Report validation that accepts attacker-controlled domains because it is unanchored or otherwise fails to enforce the exact permitted Shopify hostname form.
|
|
2152
|
-
`],
|
|
2159
|
+
`],En=`The app security check is Shopify's local security review workflow for app source code. It lives in Shopify CLI, which owns the deterministic rules, detailed semantic check prompts, findings schema, redaction rules, and artifact formats. Your job is to orchestrate the CLI, investigate the agent checks it generates, and record your findings back to it with a command\u2014not to recreate its security checks from memory.
|
|
2153
2160
|
|
|
2154
2161
|
## Scope
|
|
2155
2162
|
|
|
@@ -2195,7 +2202,10 @@ For each check:
|
|
|
2195
2202
|
5. Keep the check \`id\` and \`version\` exactly as they appear in {{AGENT_CHECKS_PATH}}.
|
|
2196
2203
|
6. Include concise evidence citations. Never include a detected secret value or unnecessary personal data.
|
|
2197
2204
|
|
|
2198
|
-
A check with no verified issue must not produce a fabricated finding.
|
|
2205
|
+
A check with no verified issue must not produce a fabricated finding. Choose the status from what you were able to investigate, not from whether you can prove a negative:
|
|
2206
|
+
|
|
2207
|
+
- \`executed\`: you traced the paths the check directs you to and found no concrete issue. This includes code that follows the safe pattern the check or its \`docs_url\` describes. A candidate is ruled out when the code establishes the boundary the check asks about, even if you can't rule out hypothetical policies or requirements that the repository doesn't define.
|
|
2208
|
+
- \`unresolved\`: you couldn't complete the investigation, for example because required code was unreadable, out of scope, or outside the repository and you couldn't inspect it, or you found a specific candidate whose boundary you could neither establish nor show to be missing. Name the candidate's file and line, or the missing input, in the reason.
|
|
2199
2209
|
|
|
2200
2210
|
### 4. Write one findings document
|
|
2201
2211
|
|
|
@@ -2239,7 +2249,7 @@ Write a single JSON document that covers every check you ran:
|
|
|
2239
2249
|
- \`status\` is one of:
|
|
2240
2250
|
- \`executed\`: you investigated the check, whether or not it produced findings.
|
|
2241
2251
|
- \`not_applicable\`: the capability the check covers is absent. It can't have findings.
|
|
2242
|
-
- \`unresolved\`: you couldn't
|
|
2252
|
+
- \`unresolved\`: you couldn't complete the investigation, or you named a specific candidate whose boundary is still unclear. An unresolved check didn't pass; never describe it as passing.
|
|
2243
2253
|
- \`not_applicable\` and \`unresolved\` require a \`reason\` with a short \`code\` and a \`message\`.
|
|
2244
2254
|
- Each finding needs \`file\`, \`line\` (1 or greater), \`message\`, and at least one \`evidence\` item with \`file\`, \`line\`, and \`quote\`.
|
|
2245
2255
|
- Optional finding fields: \`snippet\`, \`confidence\` (\`high\`, \`medium\`, or \`low\`), \`reasoning\`, and \`suppression\` (\`{"justification": "..."}\`).
|
|
@@ -2303,9 +2313,9 @@ When the user explicitly wants a fast local or CI scan without semantic investig
|
|
|
2303
2313
|
For a pass/fail gate, add \`--blocking <severity>\`: the command exits 1 when a check with findings is at or above that severity. Do not describe a deterministic-only scan as the full app security check.
|
|
2304
2314
|
|
|
2305
2315
|
Route authentication retains a template-oriented heuristic. Calls using \`context.shopify.authenticate.admin(...)\` are deferred to the \`UNAUTHENTICATED_ENDPOINT\` agent review, with unresolved coverage rather than a missing-auth finding or a pass. The heuristic does not establish binding provenance, control-flow safety, or tenant/object authorization.
|
|
2306
|
-
`;u();u();var
|
|
2307
|
-
`);for(let[d,c]of a.entries())for(let p of _n)p.regex.test(c)&&i.push({id:"COMMITTED_SECRET",pattern_id:p.name,severity:"high",points:-50,title:`Hardcoded ${p.name} detected`,message:`A ${p.name} pattern was found in repository text. Never hardcode secrets \u2014 use environment variables.`,location:{file:o.path,line:d+1},snippet:An(c,p),fix:{automated:!1,description:"Move the secret to an environment variable and rotate it immediately"}})}return i}async function Pn(e,t){let n=await z(e,t);return{exitCode:n?.exitCode,out:n?.stdout.trim()??""}}async function er(e){let t=async m=>Pn(I(e.absolutePath),m),n=T(e.absolutePath),i=await t(["rev-parse","--is-inside-work-tree"]);if(i.exitCode!==0||i.out!=="true")return{tracked:void 0,ignored:void 0,reason:"not a git repository, or git unavailable",evidence:["git rev-parse --is-inside-work-tree \u2192 not a work tree"]};let s=[],r=await t(["ls-files","--error-unmatch","--",n]),o;r.exitCode===0&&r.out.length>0?o=!0:r.exitCode===1&&(o=!1);let a="unknown";o===!0?a="TRACKED":o===!1&&(a="untracked"),s.push(`git ls-files --error-unmatch ${e.path} \u2192 ${a}`);let d=await t(["check-ignore","-q","--",n]),c;d.exitCode===0?c=!0:d.exitCode===1&&(c=!1);let p="unknown";return c===!0?p="ignored":c===!1&&(p="not ignored"),s.push(`git check-ignore -q ${e.path} \u2192 ${p}`),{tracked:o,ignored:c,...o===void 0||c===void 0?{reason:"git status command failed"}:{},evidence:s}}u();var y="https://shopify.dev/docs/apps/build/security/app-security-checks",W=[{id:"DEPRECATED_SCRIPT_TAG_SCOPE",title:"Deprecated ScriptTag capability",severity:"medium",points:-10,description:"Detects the deprecated write_script_tags OAuth scope in shopify.app.toml.",fix:"Remove write_script_tags from scopes and migrate to app embeds.",guide:"https://shopify.dev/docs/apps/online-store/app-embeds",docsUrl:`${y}/deprecated-script-tag-scope`},{id:"SCOPE_OVER_REQUEST",title:"OAuth scope may be over-requested",severity:"high",points:-12,description:"Detects declared OAuth scopes that don't appear to be used by the app code.",fix:"Remove unused scopes or add the code path that needs them.",guide:"https://shopify.dev/docs/api/usage/access-scopes",docsUrl:`${y}/scope-over-request`},{id:"UNSAFE_INNERHTML",title:"Unsafe HTML assignment",severity:"high",points:-25,description:"Detects innerHTML, outerHTML, and insertAdjacentHTML receiving non-literal values.",fix:"Use textContent where possible or sanitize with DOMPurify before rendering.",guide:"https://shopify.dev/docs/apps/online-store/security#xss-prevention",requires:"theme_app_extension",docsUrl:`${y}/unsafe-innerhtml`},{id:"LIQUID_UNSAFE_RENDER",title:"Unsafe Liquid metafield or setting output",severity:"medium",points:-10,description:"Detects metafield and merchant-setting output without context-appropriate escaping or serialization.",fix:"Use escape for HTML text/attributes, json for JavaScript data, or metafield_tag for supported rich content.",requires:"theme_app_extension",docsUrl:`${y}/liquid-unsafe-render`},{id:"INSECURE_WEBHOOK_URL",title:"Configured callback URL is not HTTPS",severity:"high",points:-12,description:"Detects webhook subscriptions and OAuth redirect URLs configured with unsafe absolute URLs.",fix:"Use HTTPS or a supported Shopify-relative path for every configured callback URL.",guide:"https://shopify.dev/docs/apps/webhooks",docsUrl:`${y}/insecure-webhook-url`},{id:"COMMITTED_SECRET",title:"Committed secret or environment file",severity:"high",points:-50,description:"Detects environment files and common API key/token patterns in source files.",fix:"Remove committed secrets, rotate exposed values, and store them in environment variables.",docsUrl:`${y}/committed-secret`},{id:"CREDENTIAL_LOG_LEAKAGE",title:"Credential reaches a log sink",severity:"high",points:-20,description:"Detects direct credential flows to console and logger sinks.",fix:"Remove credentials from logs; emit only safe redacted, hashed, or boolean-derived values.",docsUrl:`${y}/credential-log-leakage`},{id:"CREDENTIAL_BROWSER_LEAKAGE",title:"Credential reaches the client browser",severity:"high",points:-20,description:"Detects direct credential flows into responses, loaders, globals, DOM, and external requests.",fix:"Keep credentials server-side and return only non-sensitive derived data.",docsUrl:`${y}/credential-browser-leakage`},{id:"EXPIRING_OFFLINE_TOKEN",title:"Expiring offline access tokens explicitly disabled",severity:"medium",points:-10,description:"Verifies React Router expiring offline-token configuration and refresh-metadata storage compatibility.",fix:"Enable expiringOfflineAccessTokens and ensure session storage persists expiry and refresh metadata.",guide:"https://shopify.dev/docs/apps/build/authentication-authorization/access-token-types/online-access-tokens",docsUrl:`${y}/expiring-offline-token`},{id:"METAFIELD_OFFLINE_TOKEN",title:"Metafield write using offline token context",severity:"high",points:-15,description:"Detects metafield writes (metafieldsSet) in offline-token contexts. Online tokens provide user attribution and expiry.",fix:"Use authenticate.admin(request) instead of unauthenticated.admin() for metafield writes.",guide:"https://shopify.dev/docs/apps/build/authentication-authorization/access-token-types/online-access-tokens",docsUrl:`${y}/metafield-offline-token`},{id:"MISSING_EMBEDDED_CSP",title:"Embedded app missing frame-ancestors CSP directive",severity:"medium",points:-10,description:"Detects embedded apps that set document response headers but omit frame-ancestors.",fix:"Add frame-ancestors to CSP, restricted to the authenticated shop domain and admin.shopify.com.",guide:"https://shopify.dev/docs/apps/build/security/set-up-iframe-protection",docsUrl:`${y}/missing-embedded-csp`},{id:"MISSING_COMPLIANCE_WEBHOOKS",title:"Missing mandatory GDPR compliance webhooks",severity:"medium",points:-10,description:"Detects apps missing mandatory compliance webhooks (shop/redact, customers/data_request, customers/redact).",fix:"Add the missing webhook subscriptions to shopify.app.toml.",guide:"https://shopify.dev/docs/apps/webhooks/configuration/mandatory-webhooks",docsUrl:`${y}/missing-compliance-webhooks`},{id:"MISSING_DEPENDENCY_SECURITY_AUTOMATION",title:"Repository-level dependency management configuration not detected",severity:"low",points:-5,description:"Looks for a local Dependabot or Renovate configuration file at the repository root without validating its contents.",fix:"Add a Dependabot or Renovate configuration file at the repository root, or verify existing coverage.",docsUrl:`${y}/missing-dependency-security-automation`},{id:"EOL_API_VERSION",title:"End-of-life API version",severity:"low",points:-5,description:"Detects parsed config and React Router server API versions past the 12-month support window and 30-day grace period.",fix:"Update api_version in shopify.app.toml and the ApiVersion enum in shopify.server.ts.",guide:"https://shopify.dev/docs/api/usage/versioning",docsUrl:`${y}/eol-api-version`},{id:"WEAK_SHOP_VALIDATION",title:"Shop domain validation regex is not anchored",severity:"high",points:-15,description:"Detects unanchored myshopify.com regexes that allow domain spoofing and token theft.",fix:"Anchor the regex with ^ and $ to prevent prefix/suffix attacks.",guide:"https://shopify.dev/docs/apps/build/authentication-authorization/get-access-tokens/oauth",docsUrl:`${y}/weak-shop-validation`},{id:"APP_PROXY_LIQUID_INJECTION",title:"App proxy Liquid injection risk",severity:"high",points:-20,description:"Detects app proxy endpoints that interpolate request parameters into active Liquid or HTML responses.",fix:"Don't interpolate user input into application/liquid responses. Use application/json or escape all input.",guide:"https://shopify.dev/docs/apps/online-store/app-proxies",docsUrl:`${y}/app-proxy-liquid-injection`},{id:"MISSING_TENANT_ISOLATION",title:"Database query may not be scoped by shop",severity:"high",points:-20,description:"Detects database queries in authenticated routes that don't reference the shop domain or session.",fix:"Add the authenticated shop to the query's where clause.",guide:"https://shopify.dev/docs/apps/build/authentication-authorization/session-tokens",docsUrl:`${y}/missing-tenant-isolation`},{id:"OPEN_REDIRECT",title:"Open redirect in auth callback",severity:"medium",points:-12,description:"Detects redirect targets taken from request parameters without validation.",fix:"Validate the redirect URL against an allowlist of permitted domains.",guide:"https://shopify.dev/docs/apps/build/authentication-authorization",docsUrl:`${y}/open-redirect`},{id:"UNAUTHENTICATED_ENDPOINT",title:"Route handler lacks recognized auth verification",severity:"high",points:-15,description:"Uses the template route-auth heuristic, with agent review for context.shopify.authenticate.admin calls instead of treating them as missing authentication.",fix:"Complete the verification appropriate to the entry point before protected operations, and stop those operations when verification fails.",guide:"https://shopify.dev/docs/apps/auth",requires:"has_backend",docsUrl:`${y}/unauthenticated-endpoint`},{id:"REQUEST_CONTROLLED_ADMIN_CONTEXT",title:"Request input selects Admin API shop context",severity:"high",points:-20,description:"Detects request-derived shop values passed into unauthenticated.admin(...), allowing callers to select another shop's Admin API context.",fix:"Use the Admin API context returned by authenticate.admin(request). Never pass form, JSON, query, or route input into unauthenticated.admin(...).",guide:"https://shopify.dev/docs/apps/build/authentication-authorization/access-tokens/online-access-tokens",docsUrl:`${y}/request-controlled-admin-context`},{id:"APP_PROXY_UNVERIFIED_SIGNATURE",title:"App proxy request trusted without signature verification",severity:"high",points:-15,description:"Detects app proxy parameters read without visible signature verification.",fix:"Verify app proxy signatures before trusting shop, customer, or path parameters.",guide:"https://shopify.dev/docs/apps/build/online-store/app-proxies/authenticate-app-proxies",requires:"app_proxy",docsUrl:`${y}/app-proxy-unverified-signature`},{id:"REQUEST_DERIVED_SHOP_SCOPE",title:"Query scoped by shop value taken from request input",severity:"high",points:-15,description:"Detects queries whose shop scope comes from request params rather than the authenticated session. Presence of a shop key is not proof of scoping when its value is attacker-controlled.",fix:"Scope the query with the shop from the authenticated session, or verify the supplied shop against a signed session or HMAC first.",guide:"https://shopify.dev/docs/apps/build/authentication-authorization/session-tokens",docsUrl:`${y}/request-derived-shop-scope`},{id:"UNSCOPED_SHOP_CONFIG_WRITE",title:"Config write trusts shop from request input",severity:"high",points:-15,description:"Detects settings or metafield writes scoped by request-controlled shop input without visible auth verification.",fix:"Derive the target shop from an authenticated session or verified HMAC.",guide:"https://shopify.dev/docs/apps/build/authentication-authorization/session-tokens",docsUrl:`${y}/unscoped-shop-config-write`},{id:"STATIC_FRAME_ANCESTORS",title:"Embedded app frame-ancestors uses a wildcard",severity:"high",points:-12,description:"Detects literal wildcard or clearly permissive frame-ancestors policies in embedded app code.",fix:"Restrict frame-ancestors to Shopify Admin and the authenticated shop origin.",guide:"https://shopify.dev/docs/apps/build/security/set-up-iframe-protection",requires:"embedded_app",docsUrl:`${y}/static-frame-ancestors`},{id:"ACTIVE_UPLOADS_AND_PRIVILEGED_PREVIEWS",title:"Active upload or privileged preview may execute untrusted content",severity:"high",points:-15,description:"Investigates uploaded or imported active content that may execute in storefront, embedded admin, customer-account, or operator preview surfaces.",fix:"Sanitize or re-encode active content and isolate previews from privileged origins.",docsUrl:`${y}/active-uploads-and-privileged-previews`},{id:"DEPENDENCY_REACHABILITY",title:"Vulnerable dependency may be reachable in app code",severity:"medium",points:-10,description:"Investigates whether a vulnerable dependency version is actually used through the affected API, helper, or configuration.",fix:"Upgrade the dependency or remove the reachable vulnerable code path.",docsUrl:`${y}/dependency-reachability`},{id:"SESSION_LIFECYCLE_AND_REPLAY",title:"Session lifecycle or replay control may be missing",severity:"high",points:-15,description:"Investigates stale sessions, replayable signed links, and missing invalidation or idempotency across lifecycle transitions.",fix:"Invalidate stale authority and enforce replay protection or re-authorization before sensitive actions.",docsUrl:`${y}/session-lifecycle-and-replay`},{id:"OVERBROAD_DATA_ACCESS",title:"Data access may exceed the current request",severity:"medium",points:-10,description:"Investigates reads that may expose data outside the authenticated shop or user boundary.",fix:"Scope data access to the authenticated shop and minimum necessary records.",docsUrl:`${y}/overbroad-data-access`},{id:"CROSS_SITE_SCRIPTING",title:"Cross-site scripting risk",severity:"high",points:-15,description:"Investigates lower-trust data rendered as executable browser content in app-owned pages.",fix:"Use context-appropriate escaping, safe serialization, URL validation, or HTML sanitization at the final sink.",guide:"https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html",docsUrl:`${y}/cross-site-scripting`},{id:"SQL_INJECTION",title:"SQL injection risk",severity:"high",points:-15,description:"Investigates lower-trust input that changes SQL syntax in a reachable database operation.",fix:"Bind query values and use trusted mappings or safe identifier quoting for dynamic SQL structure.",guide:"https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html",docsUrl:`${y}/sql-injection`},{id:"SSRF_REQUEST_FORGERY",title:"Server-side request forgery risk",severity:"high",points:-15,description:"Investigates server-side requests whose destinations may be controlled by request input.",fix:"Allowlist destinations and reject private, local, and unsafe network targets.",docsUrl:`${y}/ssrf-request-forgery`},{id:"THEME_EXTENSION_XSS",title:"Theme extension cross-site scripting risk",severity:"high",points:-15,description:"Investigates untrusted values rendered without safe escaping in theme extensions.",fix:"Escape untrusted values and avoid raw HTML rendering.",requires:"theme_app_extension",docsUrl:`${y}/theme-extension-xss`},{id:"CSRF_MISSING_PROTECTION",title:"Cross-site request forgery protection may be missing",severity:"medium",points:-10,description:"Investigates state-changing endpoints for missing request authenticity protections.",fix:"Require an authenticated session and CSRF protection on state-changing requests.",docsUrl:`${y}/csrf-missing-protection`},{id:"MISSING_AUTHORIZATION_CHECK",title:"Authorization check may be missing",severity:"high",points:-15,description:"Investigates authenticated handlers that may not authorize access to the requested resource.",fix:"Authorize the actor and resource after authentication.",docsUrl:`${y}/missing-authorization-check`},{id:"SCRIPT_TAG_URL_INJECTION",title:"ScriptTag URL may be request controlled",severity:"high",points:-15,description:"Investigates ScriptTag writes whose source URL may be controlled by untrusted input.",fix:"Remove ScriptTag usage or restrict sources to trusted versioned assets.",docsUrl:`${y}/script-tag-url-injection`},{id:"TEXT_SETTING_HTML_SMUGGLING",title:"Text setting may be rendered as unsafe HTML",severity:"high",points:-15,description:"Investigates merchant-configurable text rendered as HTML without sanitization.",fix:"Render settings as text or sanitize with a strict allowlist.",docsUrl:`${y}/text-setting-html-smuggling`}];function tr(e){return W.find(t=>t.id===e)?.docsUrl}var nr=e=>e==="high"||e==="medium"||e==="low",ir=e=>e==="union"||e==="prefer-agent",sr=(e,t)=>{if(t===void 0)return"union";if(!ir(t))throw new Error(`Invalid precedence for agent check ${e}: ${t} (expected union or prefer-agent)`);return t},rr=e=>{let t=/^---\n([\s\S]*?)\n---\n([\s\S]*)$/.exec(e);if(!t)return{meta:{},body:e};let[,n="",i=""]=t,s={};for(let r of n.split(`
|
|
2308
|
-
`)){let o=r.indexOf(":");o!==-1&&(s[r.slice(0,o).trim()]=r.slice(o+1).trim())}return{meta:s,body:i.trim()}},
|
|
2316
|
+
`;u();u();var or={fileSystemMonitor:{args:["-c","core.fsmonitor="]},bareRepository:{args:["-c","safe.bareRepository=explicit"]},lazyFetch:{env:{GIT_NO_LAZY_FETCH:"1"}},transports:{env:{GIT_ALLOW_PROTOCOL:""}}},In=Object.values(or),ar=In.flatMap(e=>e.args??[]);function cr(){return Object.fromEntries(In.flatMap(e=>Object.entries(e.env??{})))}async function H(e,t){try{let n=await nn("git",[...ar,...t],{cwd:e,env:cr()});return{exitCode:n.exitCode,stdout:n.stdout}}catch{return}}var Ln={regex:/shp(?:(?:at|ca|pa|ss)_[a-fA-F0-9]{16,}|(?:rt|sb|tka|ua)_[a-zA-Z0-9]{16,})/,name:"Shopify token",wholeMatch:!0},An=[Ln],dr=[Ln,{regex:/(?:sk|rk)_(?:live|test)_[a-zA-Z0-9]{20,}/,name:"Stripe API key",wholeMatch:!0},{regex:/AKIA[0-9A-Z]{16}/,name:"AWS access key",wholeMatch:!0},{regex:/aws[_-]?secret[_-]?access[_-]?key\s*[:=]\s*['"]([A-Za-z0-9/+=]{40})['"]/i,name:"AWS secret access key"},{regex:/gh[pousr]_[A-Za-z0-9]{36,}/,name:"GitHub token",wholeMatch:!0},{regex:/AIza[0-9A-Za-z_-]{35}/,name:"Google API key",wholeMatch:!0},{regex:/xox[baprs]-[0-9A-Za-z-]{10,}/,name:"Slack token",wholeMatch:!0},{regex:/-----BEGIN ((?:(?:RSA|EC|OPENSSH) )?PRIVATE KEY|PGP PRIVATE KEY BLOCK)-----[\s\S]*?-----END \1-----/,name:"private key block",wholeMatch:!0},{regex:/-----BEGIN (?:(?:RSA|EC|OPENSSH) )?PRIVATE KEY-----|-----BEGIN PGP PRIVATE KEY BLOCK-----/,name:"private key",wholeMatch:!0,redactEntireInput:!0}];function Dn(e,t){let n=t.regex.exec(e);if(!n)return"[REDACTED]";if(t.redactEntireInput)return"[REDACTED LINE]";let i=t.wholeMatch?n[0]:n[1];if(!i)return"[REDACTED]";let s=Math.min(4,Math.floor(i.length/4)),r=s>0?i.slice(0,s):"",o=e.split(i).join(`${r}[REDACTED:${i.length}]`);return o.includes(i)?"[REDACTED LINE]":o}function y(e){let t=e;for(let n of dr){for(let i=0;i<100&&n.regex.exec(t);i++){let r=Dn(t,n);if(r===t)return"[REDACTED TEXT]";t=r}if(n.regex.test(t))return"[REDACTED TEXT]"}return t}function xe(e){return{include_dirs:e.include_dirs.map(y),excludes:e.excludes.map(y),no_git_ignore:e.no_git_ignore}}var Rn=/(^|\/)(?:\.env\.(?:secrets|keys)|(?:secrets|credentials)\.json)$/;function lr(e){let t=e.slice(e.lastIndexOf("/")+1);if(t===".env"||t.startsWith(".env."))return t}function Cn(e){return lr(e)!==void 0}async function Nn(e){let t=await On(e,["rev-parse","--show-toplevel"]);return t.exitCode===0&&t.out!==""?t.out:void 0}async function pr(e,t,n){if(t==="failed")return{reason:"listing-failed",evidence:[]};if(t==="not-a-repository")return{reason:"unknown",evidence:[]};let i=await Nn(S(e.absolutePath)),s=await n(),r=i!==void 0&&s!==void 0,o=r&&i!==s,a="unknown";return o?a="differs":r&&(a="same"),{reason:o?"nested-repository":"unknown",evidence:[`git rev-parse --show-toplevel \u2192 ${a} for ${e.path} and the app root`]}}function Pn(e,t,n,i){let s=n?"Environment file with secrets":"Secret file",r=t.tracked===!0,o=t.tracked===!1&&t.ignored===!1,a=t.tracked===!1&&t.ignored===!0,c,l,d;return r?(c=`${s} is tracked by git`,l=`${e.path} IS TRACKED BY GIT (confirmed via git ls-files), so its contents are in the repository history. Rotate every exposed secret and purge the file from history.`,d=`git rm --cached ${e.path}, add it to .gitignore, purge it from history, and rotate every exposed secret`):o?(c=`${s} is not ignored by git`,l=`${e.path} is untracked but not ignored. If committed, its contents enter repository history.`,d=`Add ${e.path} to .gitignore, confirm with 'git check-ignore ${e.path}', and rotate any exposed secrets`):i==="nested-repository"?(c=`${s} is inside a nested git repository`,l=`${e.path} belongs to a nested git repository (its top level differs from the app's), so this app's ignore rules don't protect it and it was scanned.`,d=`Ignore ${e.path} in the nested repository and rotate any exposed secrets`):i==="listing-failed"?(c=`${s} is ignored by git but was scanned`,l=`${e.path} is ignored by git, but the app security check could not list the ignored files for this app, so it was scanned.`,d="Confirm the repository is healthy with 'git status' and rotate any exposed secrets"):a?(c=`${s} is ignored by git but was scanned`,l=`${e.path} is ignored by git but was still scanned; the app security check couldn't determine why. Confirm the rule ignoring it belongs to the repository that owns this app before treating this as clean.`,d=`Confirm with 'git check-ignore -v ${e.path}' that this app's repository ignores it, and rotate any exposed secrets`):(c=`${s} could not be confirmed as ignored`,l=`${e.path} could not be confirmed as untracked-and-ignored${t.reason?` (${t.reason})`:""}. Confirm it is gitignored before treating this as clean.`,d=`Add ${e.path} to .gitignore, confirm with 'git ls-files ${e.path}', and rotate any exposed secrets`),{id:"COMMITTED_SECRET",pattern_id:`${n?"environment-file":"secret-file"}:${r?"tracked":"unconfirmed"}`,severity:"high",points:-50,title:c,message:l,location:{file:e.path},detection_evidence:t.evidence,fix:{automated:!1,description:d}}}async function qn(e,t,n){let i=[],s,r=()=>(s??=Nn(t),s);for(let o of e){let a=o.content;if(a===void 0)continue;let c=Cn(o.path),l=Rn.test(o.path);if(!c&&!l)continue;let d=An.some(E=>E.regex.test(a)),f=l&&a.trim()==="";if(!d&&!f)continue;let h=await ur(o);if(!d){f&&h.tracked===!0&&i.push(Pn(o,h,c,"unknown"));continue}let g="unknown",x=h.evidence??[];if(h.tracked===!1&&h.ignored===!0){if(n==="git-ignore-off")continue;let E=await pr(o,n,r);g=E.reason,x=[...x,...E.evidence]}i.push(Pn(o,{...h,evidence:x},c,g))}for(let o of e){if(!o.content||Cn(o.path)||Rn.test(o.path))continue;let a=o.content.split(`
|
|
2317
|
+
`);for(let[c,l]of a.entries())for(let d of An)d.regex.test(l)&&i.push({id:"COMMITTED_SECRET",pattern_id:d.name,severity:"high",points:-50,title:`Hardcoded ${d.name} detected`,message:`A ${d.name} pattern was found in repository text. Never hardcode secrets \u2014 use environment variables.`,location:{file:o.path,line:c+1},snippet:Dn(l,d),fix:{automated:!1,description:"Move the secret to an environment variable and rotate it immediately"}})}return i}async function On(e,t){let n=await H(e,t);return{exitCode:n?.exitCode,out:n?.stdout.trim()??""}}async function ur(e){let t=async f=>On(S(e.absolutePath),f),n=k(e.absolutePath),i=await t(["rev-parse","--is-inside-work-tree"]);if(i.exitCode!==0||i.out!=="true")return{tracked:void 0,ignored:void 0,reason:"not a git repository, or git unavailable",evidence:["git rev-parse --is-inside-work-tree \u2192 not a work tree"]};let s=[],r=await t(["ls-files","--error-unmatch","--",n]),o;r.exitCode===0&&r.out.length>0?o=!0:r.exitCode===1&&(o=!1);let a="unknown";o===!0?a="TRACKED":o===!1&&(a="untracked"),s.push(`git ls-files --error-unmatch ${e.path} \u2192 ${a}`);let c=await t(["check-ignore","-q","--",n]),l;c.exitCode===0?l=!0:c.exitCode===1&&(l=!1);let d="unknown";return l===!0?d="ignored":l===!1&&(d="not ignored"),s.push(`git check-ignore -q ${e.path} \u2192 ${d}`),{tracked:o,ignored:l,...o===void 0||l===void 0?{reason:"git status command failed"}:{},evidence:s}}u();var v="https://shopify.dev/docs/apps/build/security/app-security-checks",G=[{id:"DEPRECATED_SCRIPT_TAG_SCOPE",title:"Deprecated ScriptTag capability",severity:"medium",points:-10,description:"Detects the deprecated write_script_tags OAuth scope in shopify.app.toml.",fix:"Remove write_script_tags from scopes and migrate to app embeds.",guide:"https://shopify.dev/docs/apps/online-store/app-embeds",docsUrl:`${v}/deprecated-script-tag-scope`},{id:"SCOPE_OVER_REQUEST",title:"OAuth scope may be over-requested",severity:"high",points:-12,description:"Detects declared OAuth scopes that don't appear to be used by the app code.",fix:"Remove unused scopes or add the code path that needs them.",guide:"https://shopify.dev/docs/api/usage/access-scopes",docsUrl:`${v}/scope-over-request`},{id:"UNSAFE_INNERHTML",title:"Unsafe HTML assignment",severity:"high",points:-25,description:"Detects innerHTML, outerHTML, and insertAdjacentHTML receiving non-literal values.",fix:"Use textContent where possible or sanitize with DOMPurify before rendering.",guide:"https://shopify.dev/docs/apps/online-store/security#xss-prevention",requires:"theme_app_extension",docsUrl:`${v}/unsafe-innerhtml`},{id:"LIQUID_UNSAFE_RENDER",title:"Unsafe Liquid metafield or setting output",severity:"medium",points:-10,description:"Detects metafield and merchant-setting output without context-appropriate escaping or serialization.",fix:"Use escape for HTML text/attributes, json for JavaScript data, or metafield_tag for supported rich content.",requires:"theme_app_extension",docsUrl:`${v}/liquid-unsafe-render`},{id:"INSECURE_WEBHOOK_URL",title:"Configured callback URL is not HTTPS",severity:"high",points:-12,description:"Detects webhook subscriptions and OAuth redirect URLs configured with unsafe absolute URLs.",fix:"Use HTTPS or a supported Shopify-relative path for every configured callback URL.",guide:"https://shopify.dev/docs/apps/webhooks",docsUrl:`${v}/insecure-webhook-url`},{id:"COMMITTED_SECRET",title:"Committed secret or environment file",severity:"high",points:-50,description:"Detects environment files and common API key/token patterns in source files.",fix:"Remove committed secrets, rotate exposed values, and store them in environment variables.",docsUrl:`${v}/committed-secret`},{id:"CREDENTIAL_LOG_LEAKAGE",title:"Credential reaches a log sink",severity:"high",points:-20,description:"Detects direct credential flows to console and logger sinks.",fix:"Remove credentials from logs; emit only safe redacted, hashed, or boolean-derived values.",docsUrl:`${v}/credential-log-leakage`},{id:"CREDENTIAL_BROWSER_LEAKAGE",title:"Credential reaches the client browser",severity:"high",points:-20,description:"Detects direct credential flows into responses, loaders, globals, DOM, and external requests.",fix:"Keep credentials server-side and return only non-sensitive derived data.",docsUrl:`${v}/credential-browser-leakage`},{id:"EXPIRING_OFFLINE_TOKEN",title:"Expiring offline access tokens explicitly disabled",severity:"medium",points:-10,description:"Verifies React Router expiring offline-token configuration and refresh-metadata storage compatibility.",fix:"Enable expiringOfflineAccessTokens and ensure session storage persists expiry and refresh metadata.",guide:"https://shopify.dev/docs/apps/build/authentication-authorization/access-token-types/online-access-tokens",docsUrl:`${v}/expiring-offline-token`},{id:"METAFIELD_OFFLINE_TOKEN",title:"Metafield write in an unverified offline token context",severity:"high",points:-15,description:"Investigates metafield and metaobject writes made with an offline Admin API context that no verified request for the same shop authorizes.",fix:"Make the write from a request verified with authenticate.admin(request), or derive the shop and values from a verified source.",guide:"https://shopify.dev/docs/apps/build/authentication-authorization/access-token-types/online-access-tokens",docsUrl:`${v}/metafield-offline-token`},{id:"MISSING_EMBEDDED_CSP",title:"Embedded app missing frame-ancestors CSP directive",severity:"medium",points:-10,description:"Detects embedded apps that set document response headers but omit frame-ancestors.",fix:"Add frame-ancestors to CSP, restricted to the authenticated shop domain and admin.shopify.com.",guide:"https://shopify.dev/docs/apps/build/security/set-up-iframe-protection",docsUrl:`${v}/missing-embedded-csp`},{id:"MISSING_COMPLIANCE_WEBHOOKS",title:"Missing mandatory GDPR compliance webhooks",severity:"medium",points:-10,description:"Detects apps missing mandatory compliance webhooks (shop/redact, customers/data_request, customers/redact).",fix:"Add the missing webhook subscriptions to shopify.app.toml.",guide:"https://shopify.dev/docs/apps/webhooks/configuration/mandatory-webhooks",docsUrl:`${v}/missing-compliance-webhooks`},{id:"MISSING_DEPENDENCY_SECURITY_AUTOMATION",title:"Repository-level dependency management configuration not detected",severity:"low",points:-5,description:"Looks for a local Dependabot or Renovate configuration file at the repository root without validating its contents.",fix:"Add a Dependabot or Renovate configuration file at the repository root, or verify existing coverage.",docsUrl:`${v}/missing-dependency-security-automation`},{id:"EOL_API_VERSION",title:"End-of-life API version",severity:"low",points:-5,description:"Detects parsed config and React Router server API versions past the 12-month support window and 30-day grace period.",fix:"Update api_version in shopify.app.toml and the ApiVersion enum in shopify.server.ts.",guide:"https://shopify.dev/docs/api/usage/versioning",docsUrl:`${v}/eol-api-version`},{id:"WEAK_SHOP_VALIDATION",title:"Shop domain validation regex is not anchored",severity:"high",points:-15,description:"Detects unanchored myshopify.com regexes that allow domain spoofing and token theft.",fix:"Anchor the regex with ^ and $ to prevent prefix/suffix attacks.",guide:"https://shopify.dev/docs/apps/build/authentication-authorization/get-access-tokens/oauth",docsUrl:`${v}/weak-shop-validation`},{id:"APP_PROXY_LIQUID_INJECTION",title:"App proxy Liquid injection risk",severity:"high",points:-20,description:"Detects app proxy endpoints that interpolate request parameters into active Liquid or HTML responses.",fix:"Don't interpolate user input into application/liquid responses. Use application/json or escape all input.",guide:"https://shopify.dev/docs/apps/online-store/app-proxies",docsUrl:`${v}/app-proxy-liquid-injection`},{id:"MISSING_TENANT_ISOLATION",title:"Database query may not be scoped by shop",severity:"high",points:-20,description:"Detects database queries in authenticated routes that don't reference the shop domain or session.",fix:"Add the authenticated shop to the query's where clause.",guide:"https://shopify.dev/docs/apps/build/authentication-authorization/session-tokens",docsUrl:`${v}/missing-tenant-isolation`},{id:"OPEN_REDIRECT",title:"Open redirect in auth callback",severity:"medium",points:-12,description:"Detects redirect targets taken from request parameters without validation.",fix:"Validate the redirect URL against an allowlist of permitted domains.",guide:"https://shopify.dev/docs/apps/build/authentication-authorization",docsUrl:`${v}/open-redirect`},{id:"UNAUTHENTICATED_ENDPOINT",title:"Route handler lacks recognized auth verification",severity:"high",points:-15,description:"Uses the template route-auth heuristic, with agent review for context.shopify.authenticate.admin calls instead of treating them as missing authentication.",fix:"Complete the verification appropriate to the entry point before protected operations, and stop those operations when verification fails.",guide:"https://shopify.dev/docs/apps/auth",requires:"has_backend",docsUrl:`${v}/unauthenticated-endpoint`},{id:"REQUEST_CONTROLLED_ADMIN_CONTEXT",title:"Request input selects Admin API shop context",severity:"high",points:-20,description:"Detects request-derived shop values passed into unauthenticated.admin(...), allowing callers to select another shop's Admin API context.",fix:"Use the Admin API context returned by authenticate.admin(request). Never pass form, JSON, query, or route input into unauthenticated.admin(...).",guide:"https://shopify.dev/docs/apps/build/authentication-authorization/access-tokens/online-access-tokens",docsUrl:`${v}/request-controlled-admin-context`},{id:"APP_PROXY_UNVERIFIED_SIGNATURE",title:"App proxy request trusted without signature verification",severity:"high",points:-15,description:"Detects app proxy parameters read without visible signature verification.",fix:"Verify app proxy signatures before trusting shop, customer, or path parameters.",guide:"https://shopify.dev/docs/apps/build/online-store/app-proxies/authenticate-app-proxies",requires:"app_proxy",docsUrl:`${v}/app-proxy-unverified-signature`},{id:"REQUEST_DERIVED_SHOP_SCOPE",title:"Query scoped by shop value taken from request input",severity:"high",points:-15,description:"Detects queries whose shop scope comes from request params rather than the authenticated session. Presence of a shop key is not proof of scoping when its value is attacker-controlled.",fix:"Scope the query with the shop from the authenticated session, or verify the supplied shop against a signed session or HMAC first.",guide:"https://shopify.dev/docs/apps/build/authentication-authorization/session-tokens",docsUrl:`${v}/request-derived-shop-scope`},{id:"UNSCOPED_SHOP_CONFIG_WRITE",title:"Config write trusts shop from request input",severity:"high",points:-15,description:"Detects settings or metafield writes scoped by request-controlled shop input without visible auth verification.",fix:"Derive the target shop from an authenticated session or verified HMAC.",guide:"https://shopify.dev/docs/apps/build/authentication-authorization/session-tokens",docsUrl:`${v}/unscoped-shop-config-write`},{id:"STATIC_FRAME_ANCESTORS",title:"Embedded app frame-ancestors uses a wildcard",severity:"high",points:-12,description:"Detects wildcard or static cross-shop frame-ancestors policies in embedded app code, whether written literally or built from variables.",fix:"Restrict frame-ancestors to Shopify Admin and the authenticated shop origin.",guide:"https://shopify.dev/docs/apps/build/security/set-up-iframe-protection",requires:"embedded_app",docsUrl:`${v}/static-frame-ancestors`},{id:"ACTIVE_UPLOADS_AND_PRIVILEGED_PREVIEWS",title:"Active upload or privileged preview may execute untrusted content",severity:"high",points:-15,description:"Investigates uploaded or imported active content that may execute in storefront, embedded admin, customer-account, or operator preview surfaces.",fix:"Sanitize or re-encode active content and isolate previews from privileged origins.",docsUrl:`${v}/active-uploads-and-privileged-previews`},{id:"DEPENDENCY_REACHABILITY",title:"Vulnerable dependency may be reachable in app code",severity:"medium",points:-10,description:"Investigates whether a vulnerable dependency version is actually used through the affected API, helper, or configuration.",fix:"Upgrade the dependency or remove the reachable vulnerable code path.",docsUrl:`${v}/dependency-reachability`},{id:"SESSION_LIFECYCLE_AND_REPLAY",title:"Session lifecycle or replay control may be missing",severity:"high",points:-15,description:"Investigates stale sessions, replayable signed links, and missing invalidation or idempotency across lifecycle transitions.",fix:"Invalidate stale authority and enforce replay protection or re-authorization before sensitive actions.",docsUrl:`${v}/session-lifecycle-and-replay`},{id:"OVERBROAD_DATA_ACCESS",title:"Data access may exceed the current request",severity:"medium",points:-10,description:"Investigates reads that may expose data outside the authenticated shop or user boundary.",fix:"Scope data access to the authenticated shop and minimum necessary records.",docsUrl:`${v}/overbroad-data-access`},{id:"CROSS_SITE_SCRIPTING",title:"Cross-site scripting risk",severity:"high",points:-15,description:"Investigates lower-trust data rendered as executable browser content in app-owned pages.",fix:"Use context-appropriate escaping, safe serialization, URL validation, or HTML sanitization at the final sink.",guide:"https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html",docsUrl:`${v}/cross-site-scripting`},{id:"SQL_INJECTION",title:"SQL injection risk",severity:"high",points:-15,description:"Investigates lower-trust input that changes SQL syntax in a reachable database operation.",fix:"Bind query values and use trusted mappings or safe identifier quoting for dynamic SQL structure.",guide:"https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html",docsUrl:`${v}/sql-injection`},{id:"SSRF_REQUEST_FORGERY",title:"Server-side request forgery risk",severity:"high",points:-15,description:"Investigates server-side requests whose destinations may be controlled by request input.",fix:"Allowlist destinations and reject private, local, and unsafe network targets.",docsUrl:`${v}/ssrf-request-forgery`},{id:"THEME_EXTENSION_XSS",title:"Theme extension cross-site scripting risk",severity:"high",points:-15,description:"Investigates untrusted values rendered without safe escaping in theme extensions.",fix:"Escape untrusted values and avoid raw HTML rendering.",requires:"theme_app_extension",docsUrl:`${v}/theme-extension-xss`},{id:"CSRF_MISSING_PROTECTION",title:"Cross-site request forgery protection may be missing",severity:"medium",points:-10,description:"Investigates state-changing endpoints for missing request authenticity protections.",fix:"Require an authenticated session and CSRF protection on state-changing requests.",docsUrl:`${v}/csrf-missing-protection`},{id:"MISSING_AUTHORIZATION_CHECK",title:"Authorization check may be missing",severity:"high",points:-15,description:"Investigates authenticated handlers that may not authorize access to the requested resource.",fix:"Authorize the actor and resource after authentication.",docsUrl:`${v}/missing-authorization-check`},{id:"SCRIPT_TAG_URL_INJECTION",title:"ScriptTag URL may be request controlled",severity:"high",points:-15,description:"Investigates ScriptTag writes whose source URL may be controlled by untrusted input.",fix:"Remove ScriptTag usage or restrict sources to trusted versioned assets.",docsUrl:`${v}/script-tag-url-injection`},{id:"TEXT_SETTING_HTML_SMUGGLING",title:"Text setting may be rendered as unsafe HTML",severity:"high",points:-15,description:"Investigates merchant-configurable text rendered as HTML without sanitization.",fix:"Render settings as text or sanitize with a strict allowlist.",docsUrl:`${v}/text-setting-html-smuggling`}];function hr(e){return G.find(t=>t.id===e)?.docsUrl}var fr=e=>e==="high"||e==="medium"||e==="low",mr=e=>e==="union"||e==="prefer-agent",gr=(e,t)=>{if(t===void 0)return"union";if(!mr(t))throw new Error(`Invalid precedence for agent check ${e}: ${t} (expected union or prefer-agent)`);return t},yr=e=>{let t=/^---\n([\s\S]*?)\n---\n([\s\S]*)$/.exec(e);if(!t)return{meta:{},body:e};let[,n="",i=""]=t,s={};for(let r of n.split(`
|
|
2318
|
+
`)){let o=r.indexOf(":");o!==-1&&(s[r.slice(0,o).trim()]=r.slice(o+1).trim())}return{meta:s,body:i.trim()}},Ze=(e=Tn)=>{let t=new Map;for(let n of e){let{meta:i,body:s}=yr(n);if(i.id){if(t.has(i.id))throw new Error(`Duplicate agent stable ID: ${i.id}`);t.set(i.id,{id:i.id,version:Number(i.version??1),tier:"agentic",severity:fr(i.severity)?i.severity:"medium",prompt:s,prompt_hash:`sha256:${Zt(s).toString("hex")}`,precedence:gr(i.id,i.precedence)})}}return t},vr=`Each check below is a prompt for you to run against this
|
|
2309
2319
|
codebase. For each one, explore the repository, find real instances of what
|
|
2310
2320
|
it describes, and report them with evidence. Each check's docs_url is its
|
|
2311
2321
|
page on shopify.dev: link it when you explain a finding to the user.
|
|
@@ -2341,28 +2351,29 @@ Rules:
|
|
|
2341
2351
|
- Record unresolved and not_applicable checks with a reason: { "code": "...", "message": "..." }.
|
|
2342
2352
|
- A not_applicable check can't have findings.
|
|
2343
2353
|
- An unsupported or unresolved check didn't pass. Never describe it as passing or complete.
|
|
2344
|
-
- If you can't prove exploitability or affected authority, record the check as unresolved
|
|
2345
|
-
- Don't report things you couldn't confirm \u2014 uncertainty is not a finding.`,Hn=e=>{let t=W.find(n=>n.id===e);if(!t)throw new Error(`Agent check has no catalog entry: ${e}`);return t},Gn=e=>({schema_version:Ke,engine:{name:N,version:e},generated_at:new Date().toISOString(),checks:[...Xe().values()].map(t=>({id:t.id,version:t.version,severity:t.severity,docs_url:Hn(t.id).docsUrl,prompt:t.prompt})),instructions:or}),ke=4e3,Ln=4e3,Dn=8e3,Nn=4e3,qn=100,On=50,Fn=4e3,xe=1024,Mn=1e3,ar=["executed","not_applicable","unresolved"],q=e=>typeof e=="object"&&e!==null&&!Array.isArray(e),Ye=e=>Number.isInteger(e)&&e>=1,Un=e=>!e||e.startsWith("/")||/^[a-zA-Z]:[\\/]/.test(e)?!1:!e.includes("\0"),cr=e=>{if(!q(e))return"finding must be an object";let t=e;if(typeof t.check_id!="string"||!t.check_id)return"missing or invalid check_id";if(!Ye(t.check_version))return"missing or invalid check_version";if(typeof t.file!="string"||!t.file)return"missing or invalid file";if(!Number.isInteger(t.line)||t.line<1)return`invalid line number: ${t.line}`;if(typeof t.message!="string"||!t.message)return"missing or invalid message";if(!Array.isArray(t.evidence)||t.evidence.length===0)return"finding requires at least one evidence citation";if(t.evidence.length>On)return`finding exceeds ${On} evidence citations`;if(t.file.length>xe)return`file path exceeds ${xe} characters`;if(!Un(t.file))return`unsafe file path (must be relative): ${t.file}`;if(t.message.length>ke)return`message exceeds ${ke} characters`;if(t.snippet!==void 0&&typeof t.snippet!="string")return"snippet must be a string";if(typeof t.snippet=="string"&&t.snippet.length>Ln)return`snippet exceeds ${Ln} characters`;if(t.reasoning!==void 0&&typeof t.reasoning!="string")return"reasoning must be a string";if(typeof t.reasoning=="string"&&t.reasoning.length>Dn)return`reasoning exceeds ${Dn} characters`;if(t.confidence!==void 0&&(typeof t.confidence!="string"||!["high","medium","low"].includes(t.confidence)))return"confidence must be high, medium, or low";if(t.suppression!==void 0){let n=t.suppression;if(!q(n)||typeof n.justification!="string"||!n.justification.trim())return"suppression requires a justification";if(n.justification.length>Nn)return`suppression justification exceeds ${Nn} characters`}for(let n of t.evidence){if(!q(n))return"evidence citation must be an object";if(typeof n.file!="string"||!n.file)return"evidence file must be a non-empty string";if(!Un(n.file))return`unsafe evidence file path: ${n.file}`;if(n.file.length>xe)return`evidence file path exceeds ${xe} characters`;if(n.line!==void 0&&!Ye(n.line))return`invalid evidence line number: ${n.line}`;if(n.quote!==void 0&&typeof n.quote!="string")return"evidence quote must be a string";if(typeof n.quote=="string"&&n.quote.length>Fn)return`evidence quote exceeds ${Fn} characters`}},dr=(e,t,n)=>{if(!Ye(e.check_version))return"missing or invalid check_version";if(typeof t!="string"||!ar.includes(t))return"status must be executed, not_applicable, or unresolved";let i=e.reason;if(i===void 0)return t==="executed"?void 0:`${t} requires a reason`;if(!q(i)||typeof i.code!="string"||!i.code||typeof i.message!="string"||!i.message.trim())return"reason requires a code and message";if(i.code.length>qn)return`reason code exceeds ${qn} characters`;if(i.message.length>ke)return`reason message exceeds ${ke} characters`;if(t==="not_applicable"&&n.has(e.check_id))return"a not_applicable check can't have findings"};function lr(e,t,n=Xe()){let i=[],s=[],r=new Set,o=new Set(t.filter(q).map(a=>a.check_id));return e.length>n.size?(i.push(`checks_executed contains ${e.length} entries, exceeding the limit of ${n.size}`),{reports:s,errors:i}):(e.forEach((a,d)=>{let c=`checks_executed[${d}]`;if(!q(a)){i.push(`${c}: executed check must be an object`);return}if(typeof a.check_id!="string"||!a.check_id){i.push(`${c}: missing or invalid check_id`);return}let p=`${c} (${a.check_id})`;if(!n.has(a.check_id)){i.push(`${p}: unknown check_id`);return}if(r.has(a.check_id)){i.push(`${p}: duplicate check_id in checks_executed`);return}r.add(a.check_id);let m=a.status??"executed",h=dr(a,m,o);if(h){i.push(`${p}: ${h}`);return}let b=a.reason;s.push({check_id:a.check_id,check_version:a.check_version,status:m,...b?{reason:{code:g(b.code),message:g(b.message)}}:{}})}),{reports:s,errors:i})}var $n=e=>g(e.replace(/\\/g,"/")),pr=e=>({location:{file:$n(e.file),line:e.line},message:g(e.message),evidence:e.evidence.map(t=>({location:{file:$n(t.file),...t.line===void 0?{}:{line:t.line}},...t.quote===void 0?{}:{quote:g(t.quote)}})),...e.snippet===void 0?{}:{snippet:g(e.snippet)},...e.confidence===void 0?{}:{confidence:e.confidence},...e.reasoning===void 0?{}:{reasoning:g(e.reasoning)},...e.suppression===void 0?{}:{suppression:{justification:g(e.suppression.justification)}}});function ur(e,t){let n=[],i=new Map;for(let o of t)i.set(o.check_id,[...i.get(o.check_id)??[],o]);let s=new Map(e.map(o=>[o.check_id,o])),r=e.map(o=>({report:o,findings:i.get(o.check_id)??[]}));for(let[o,a]of i){let d=[...new Set(a.map(m=>m.check_version))].sort((m,h)=>m-h),c=s.get(o);if(c){d.some(m=>m!==c.check_version)&&n.push(`${o}: findings claim check_version ${d.join(", ")}, but its checks_executed entry claims ${c.check_version}`);continue}let[p]=d;if(p!==void 0){if(d.length>1){n.push(`${o}: findings claim different check_version values (${d.join(", ")})`);continue}r.push({report:{check_id:o,check_version:p,status:"executed"},findings:a})}}return{groups:r,errors:n}}function hr(e){let t=Hn(e.id);return{title:t.title,severity:e.severity,description:t.description,...t.guide?{guide:t.guide}:{},docs_url:t.docsUrl,current_version:e.version,precedence:e.precedence}}var fr=e=>{if(!q(e))return{error:"scope is required and must be an object"};let{include_dirs:t,excludes:n,no_git_ignore:i}=e,s=r=>Array.isArray(r)&&r.every(o=>typeof o=="string");return s(t)?s(n)?typeof i!="boolean"?{error:"scope.no_git_ignore must be a boolean"}:{scope:we({include_dirs:t,excludes:n,no_git_ignore:i})}:{error:"scope.excludes must be an array of strings"}:{error:"scope.include_dirs must be an array of strings"}},jn=(e,t,n)=>{let i=e[t];return i===void 0?[]:Array.isArray(i)?i:(n.push(`${t} must be an array`),[])};function mr(e,t){if(!q(e))return{ok:!1,errors:["The findings document must be a JSON object."]};let n=[];e.schema_version!==de&&n.push(`schema_version must be ${de}`);let i=fr(e.scope);"error"in i&&n.push(i.error);let s=jn(e,"checks_executed",n),r=jn(e,"findings",n),o=Xe(),a=lr(s,r,o);n.push(...a.errors);let d=[];r.length>Mn?n.push(`findings contains ${r.length} findings, exceeding the limit of ${Mn}`):r.forEach((m,h)=>{let b=q(m)&&typeof m.check_id=="string"?m.check_id:void 0,k=b?`findings[${h}] (${b})`:`findings[${h}]`,_=cr(m)??(o.has(b??"")?void 0:"unknown check_id");if(_){n.push(`${k}: ${_}`);return}d.push(m)});let c=ur(a.reports,d);if(n.push(...c.errors),n.length>0||"error"in i)return{ok:!1,errors:n.map(g)};let p=c.groups.map(({report:m,findings:h})=>({id:m.check_id,version:m.check_version,status:m.status,...m.reason?{reason:m.reason}:{},snapshot:hr(o.get(m.check_id)),findings:h.map(pr)})).sort((m,h)=>m.id.localeCompare(h.id));return{ok:!0,document:{schema_version:L,source:"agent",engine:{name:N,version:t.engineVersion},generated_at:t.generatedAt??new Date().toISOString(),scope:i.scope,checks:p}}}u();function V(){return $t}u();u();u();u();function U(e){return e instanceof Error&&"code"in e&&(e.code==="ENOENT"||e.code==="ENOTDIR")}function O(e,t){let n=t instanceof Error&&"code"in t&&typeof t.code=="string"?t.code:void 0;return n?`Could not inspect ${e} (${n})`:`Could not inspect ${e}`}u();u();import{lstatSync as gr}from"node:fs";function _e(e){let t=e;for(;;){try{let i=gr(v(t,".git"));return i.isDirectory()||i.isFile()?{status:"found",directory:t}:{status:"ambiguous",reason:"Could not determine repository ownership from .git"}}catch(i){if(!U(i))return{status:"ambiguous",reason:O(".git",i)}}let n=I(t);if(n===t)return{status:"none"};t=n}}import{lstatSync as yr,realpathSync as vr}from"node:fs";function zn(e){let t=vr(D());return{excludePatterns:e.excludePatterns.filter(n=>n!=="").map(n=>wr(n,t)),gitFiltering:!e.noGitIgnore,workingDirectory:t}}var br=/[*?[\]{}()!#\\]/;function wr(e,t){let n=e.split("/"),i=n.findIndex(d=>br.test(d)),s=i===-1?n:n.slice(0,i),r=s.join("/")||(e.startsWith("/")?"/":"");if(r==="")return e;let o=w(t,E(t,r)),a=n.slice(s.length).join("/");return a===""?o:o===""?a:`${o}/${a}`}function Ze(e,t){return t.status==="listed"?{directory:e,paths:new Set(t.paths)}:void 0}function Wn(e,t,n){return e.gitFiltering&&(T(n.absolutePath)===".git"||t&&xr(t,n))?!0:et(e,n.absolutePath)}function Vn(e,t,n){let i=n.split("/");return i.some((s,r)=>{let o=v(t,...i.slice(0,r+1));return e.gitFiltering&&s===".git"||et(e,o)})}function Se(e,t){let n=w(e.workingDirectory,t).split("/");return n.some((i,s)=>et(e,v(e.workingDirectory,...n.slice(0,s+1))))}function xr(e,t){let n=w(e.directory,t.absolutePath);return e.paths.has(t.isDirectory?`${n}/`:n)}function et(e,t){if(e.excludePatterns.length===0)return!1;let n=w(e.workingDirectory,t);return e.excludePatterns.some(i=>Bt(n,i))}async function tt(e){let t=await kr(e);return t.status!=="listed"&&Xt(`app security check: git ignore listing skipped (${t.status})`),t}async function kr(e){let t=await z(e,["rev-parse","--is-inside-work-tree"]);if(t===void 0)return{status:"failed"};if(t.exitCode!==0)return _e(e).status==="none"?{status:"not-a-repository"}:{status:"failed"};if(t.stdout.trim()==="false")return{status:"not-a-repository"};if(t.stdout.trim()!=="true")return{status:"failed"};let n=await z(e,["ls-files","-z","--others","--ignored","--exclude-standard","--directory","--","."]);return n===void 0||n.exitCode!==0?{status:"failed"}:{status:"listed",paths:Kn(n.stdout)}}async function Bn(e){let t=I(e);return t===e?!1:(await z(t,["check-ignore","--no-index","-q","--",T(e)]))?.exitCode===0}async function Qn(e){let t=await z(e,["ls-files","-z","--cached","--","."]);if(!(t===void 0||t.exitCode!==0))return Kn(t.stdout)}async function Jn(e){try{let t=yr(v(e,".git"));return t.isDirectory()||t.isFile()?await tt(e):{status:"failed"}}catch(t){return U(t)?void 0:{status:"failed"}}}function Kn(e){return e.split("\0").filter(t=>t!=="")}u();var Yn=[".github/dependabot.yml",".github/dependabot.yaml","renovate.json","renovate.jsonc","renovate.json5",".github/renovate.json",".github/renovate.jsonc",".github/renovate.json5",".gitlab/renovate.json",".gitlab/renovate.jsonc",".gitlab/renovate.json5",".renovaterc",".renovaterc.json",".renovaterc.jsonc",".renovaterc.json5"];function _r(e){return Object.keys(e.dependencies).length>0||Object.keys(e.devDependencies??{}).length>0}function Sr(e){return e.replace(/\\/g,"/")}function Tr(e){let t=e.map(i=>Sr(i.path)),n=t.find(i=>i==="package.json");return n||[...t].sort((i,s)=>{let r=i.split("/").length-s.split("/").length;return r===0?i.localeCompare(s):r})[0]}function Xn(e){let t=e.manifests.filter(_r);if(t.length===0)return{issues:[]};let{files:n,unresolvedReason:i}=e.dependencyAutomation;return i?{issues:[],unresolvedReason:i}:n.length>0?{issues:[]}:{issues:[{id:"MISSING_DEPENDENCY_SECURITY_AUTOMATION",severity:"low",points:-5,title:"Repository-level dependency management configuration not detected",message:"No recognized Dependabot or Renovate configuration file was found at the repository root. Add dependency update automation there, or verify that an existing integration covers this app. This check only looks for local configuration files; it does not validate their contents or inspect hosted integrations, CI workflows, or execution results.",location:{file:Tr(t)},fix:{automated:!1,description:"Add a Dependabot or Renovate configuration file at the repository root, or verify existing coverage."}}]}}import{lstatSync as B,readdirSync as Er,realpathSync as pe}from"node:fs";function ei(e,t){let n=ct(e);if(n===void 0)return null;try{let i=He(n);return Rr(i,e,n,t)}catch{return Z(e,{ok:!1,reason:"unreadable",detail:"TOML could not be parsed"}),null}}var Ir=l.object({api_version:l.string().optional(),privacy_compliance:l.object({customer_deletion_url:l.string().optional(),customer_data_request_url:l.string().optional(),shop_deletion_url:l.string().optional()}).optional(),subscriptions:l.array(l.unknown()).optional()}),Ar=nn.extend({uri:l.preprocess(e=>We(e),l.string({invalid_type_error:"Value must be string"}))});function Rr(e,t,n,i){let s=Cr(e.access_scopes,t,i),r=Pr(e.auth,t,i),o=Lr(e.webhooks,t,i);return{raw:e,path:t,content:n,scopes:s,apiVersion:o.apiVersion,redirectUrls:r,webhooks:o.subscriptions}}function Cr(e,t,n){if(e===void 0)return;let i=an.safeParse(e);if(!i.success){Te(n,t,"access_scopes section could not be parsed");return}let s=i.data.scopes?.split(/[\s,]+/).filter(Boolean)??[],r=[...new Set([...s,...i.data.required_scopes??[]])];return r.length>0?r.join(","):void 0}function Pr(e,t,n){if(e===void 0)return[];let i=cn.safeParse(e);return i.success?i.data.redirect_urls:(Te(n,t,"auth section could not be parsed"),[])}function Lr(e,t,n){if(e===void 0)return{subscriptions:[]};let i=Ir.safeParse(e);if(!i.success)return Te(n,t,"webhooks section could not be parsed"),{subscriptions:[]};let s=(i.data.subscriptions??[]).flatMap(a=>Nr(a,t,n)),r=i.data.privacy_compliance,o=[{topic:"customers/redact",uri:r?.customer_deletion_url},{topic:"customers/data_request",uri:r?.customer_data_request_url},{topic:"shop/redact",uri:r?.shop_deletion_url}].flatMap(({topic:a,uri:d})=>d?[{topics:[a],uri:d}]:[]);return{apiVersion:i.data.api_version,subscriptions:[...s,...o]}}var Dr=l.object({uri:l.preprocess(e=>We(e),l.string({invalid_type_error:"Value must be string"}))});function Nr(e,t,n){let i=Ar.safeParse(e);if(i.success)return[{topics:[...i.data.topics??[],...i.data.compliance_topics??[]],uri:i.data.uri}];Te(n,t,"webhook subscription could not be parsed");let s=Dr.safeParse(e);return s.success?[{topics:[],uri:s.data.uri}]:[]}function Te(e,t,n){e&&Z(t,{ok:!1,reason:"unreadable",detail:n})}function qr(e,t){try{return Er(e,{withFileTypes:!0})}catch(n){Z(e,{ok:!1,reason:"unreadable",detail:O(t?"scan directory":Ie(e),n)});return}}async function ti({appDirectory:e,scanDirectories:t,requestedScanDirectories:n,selectedAppConfigFilePath:i,rules:s}){let r=[];if(s.gitFiltering)for(let c of n)Se(s,c)||await Bn(c)&&r.push(c);let o=[];for(let c of t)o.push(await Fr(c,s,r.includes(c)));let a=[...o.flatMap(c=>c.absolutePaths),...i?[i]:[]],d=[...new Set(a.map(c=>P(w(e,c))))].sort();return{paths:d,ignoredScanDirectories:r,otherAppDirectories:Or(e,d),listingStatus:o[0]?.listingStatus??(s.gitFiltering?"tracked-only":"git-ignore-off")}}function Or(e,t){let n=t.filter(i=>ze(T(i))).map(i=>I(i)).filter(i=>i!==".");return[...new Set(n)].map(i=>v(e,i))}async function Fr(e,t,n){if(!t.gitFiltering)return{absolutePaths:await Zn(e,t,void 0),listingStatus:"git-ignore-off"};if(n){if(Se(t,e))return{absolutePaths:[],listingStatus:"tracked-only"};let s=await Qn(e);if(s===void 0)throw new A(`Couldn't list the files Git tracks in ${w(D(),e)||"."}.`);return{absolutePaths:s.filter(r=>!Vn(t,e,r)).map(r=>v(e,r)).filter(Mr),listingStatus:"tracked-only"}}let i=await tt(e);return{absolutePaths:await Zn(e,t,Ze(e,i)),listingStatus:i.status}}function Mr(e){try{return!B(e).isDirectory()}catch{return!1}}async function Zn(e,t,n){if(Se(t,e))return[];let i=[],s=[{directory:e,repository:n}];for(let{directory:r,repository:o}of s){let a=qr(r,r===e);if(a!==void 0)for(let d of a){let c=v(r,d.name),p=d.isDirectory();if(Wn(t,o,{absolutePath:c,isDirectory:p}))continue;if(!p){i.push(c);continue}let m=t.gitFiltering?await Jn(c):void 0;s.push({directory:c,repository:m?Ze(c,m):o})}}return i}function Ur(e,t){let n=new Map([...t].map(i=>[i,[]]));for(let i of e){if(!si(i))continue;let s=I(i);for(;s!==".";)n.get(s)?.push(i),s=I(s);n.get(".")?.push(i)}return n}function ni(e,t){let n=t.filter(s=>T(s)==="shopify.extension.toml");if(n.length===0)return[];let i=Ur(t,new Set(n.map(s=>I(s))));return n.flatMap(s=>{let r=v(e,s),o=ct(r);if(o===void 0)return[];try{let d=He(o).type,c=dt(e,i.get(I(s))??[]);return[{path:s,type:d,content:o,files:c}]}catch{return Z(r,{ok:!1,reason:"unreadable",detail:"TOML could not be parsed"}),[]}})}var $r=5e5,rt=[],nt=new Map,it;function ot(e){it=e,rt=[],nt.clear()}function Ee(){if(!it)throw new Kt("The repository reader was used before it was configured.");return it}function at(){return[...rt]}function Z(e,t){let n=w(Ee().appDirectory,e).replace(/\\/g,"/");rt.push({path:n.length>0?n:e,reason:t.reason,...t.sizeBytes===void 0?{}:{size_bytes:t.sizeBytes},...t.detail?{detail:t.detail}:{}})}function jr(e){try{let t=Wt(e);return t>$r?{ok:!1,reason:"too_large",sizeBytes:t}:{ok:!0,content:Gt(e)}}catch(t){return{ok:!1,reason:"unreadable",detail:t instanceof Error?t.message:String(t)}}}function le(e){return{ok:!1,reason:"unreadable",detail:e}}function Ie(e){let t=P(w(Ee().appDirectory,e));return t.length===0||t==="."||jt(t)?"path":t}function Hr(e,t){let n=E(e),i=E(n,t),s=Ie(i);if(!M(n,i))return{status:"unresolved",reason:`${s} escapes the scan directory`};let r;try{r=pe(n)}catch(a){return{status:"unresolved",reason:O("scan directory",a)}}let o=P(w(n,i)).split("/").filter(a=>a.length>0&&a!==".");if(o.includes(".."))return{status:"unresolved",reason:`${s} escapes the scan directory`};if(o.length===0)return{status:"unresolved",reason:`${s} is not a file`};try{let a=pe(i);return M(r,a)?B(a).isFile()?{status:"file",path:a}:{status:"unresolved",reason:`${s} is not a file`}:{status:"unresolved",reason:`${s} resolves outside the scan directory`}}catch(a){return U(a)?Gr(r,o,s):{status:"unresolved",reason:O(s,a)}}}function Gr(e,t,n){let i=e;for(let[s,r]of t.entries()){i=v(i,r);try{B(i);let o=pe(i);if(!M(e,o))return{status:"unresolved",reason:`${n} resolves outside the scan directory`};let a=B(o);if(s===t.length-1){if(!a.isFile())return{status:"unresolved",reason:`${n} is not a file`}}else if(!a.isDirectory())return{status:"unresolved",reason:`${n} contains an entry that is not a directory`};i=o}catch(o){if(U(o)){try{if(B(i).isSymbolicLink())return{status:"unresolved",reason:`${n} contains a dangling symbolic link`}}catch{return{status:"missing"}}return{status:"missing"}}return{status:"unresolved",reason:O(n,o)}}}return{status:"file",path:i}}function zr(e){let t=Ee().scanDirectories.find(i=>M(i,e));if(t===void 0)return{failure:le(`${Ie(e)} is outside every scan directory`)};let n=Hr(t,e);return n.status==="file"?{path:n.path}:n.status==="missing"?{failure:le("path does not exist")}:{failure:le(n.reason)}}function Wr(e){let t=Ie(e);try{let n=pe(e);return B(n).isFile()?{path:n}:{failure:le(`${t} is not a file`)}}catch(n){return{failure:le(U(n)?"path does not exist":O(t,n))}}}function Ae(e){let t=E(e),n=nt.get(t);if(n)return n;let i=Ee().explicitInputs.has(t)?Wr(t):zr(t),s=i.path?jr(i.path):i.failure;return nt.set(t,s),s.ok||Z(t,s),s}function ct(e){let t=Ae(e);return t.ok?t.content.toString():void 0}var st={".js":{name:"javascript",supported:!0},".jsx":{name:"javascript",supported:!0},".mjs":{name:"javascript",supported:!0},".cjs":{name:"javascript",supported:!0},".ts":{name:"typescript",supported:!0},".tsx":{name:"typescript",supported:!0},".mts":{name:"typescript",supported:!0},".cts":{name:"typescript",supported:!0},".prisma":{name:"prisma",supported:!0},".liquid":{name:"liquid",supported:!0},".html":{name:"html",supported:!0},".ejs":{name:"ejs",supported:!1},".erb":{name:"erb",supported:!1},".hbs":{name:"handlebars",supported:!1},".jinja2":{name:"jinja2",supported:!1},".twig":{name:"twig",supported:!1},".rb":{name:"ruby",supported:!1},".php":{name:"php",supported:!1},".py":{name:"python",supported:!1},".java":{name:"java",supported:!1},".kt":{name:"kotlin",supported:!1},".kts":{name:"kotlin",supported:!1},".go":{name:"go",supported:!1},".rs":{name:"rust",supported:!1},".cs":{name:"csharp",supported:!1},".swift":{name:"swift",supported:!1},".scala":{name:"scala",supported:!1},".ex":{name:"elixir",supported:!1},".exs":{name:"elixir",supported:!1},".vue":{name:"vue",supported:!1},".svelte":{name:"svelte",supported:!1}};function ii(e){let t=F(e);return t in st?st[t]:void 0}function si(e){return ii(e)?.supported===!0}function ri(e){return e.flatMap(t=>{let n=ii(t);return n?[{path:t,extension:F(t),language:n.name,supported:n.supported}]:[]}).sort((t,n)=>t.path.localeCompare(n.path))}function dt(e,t){return t.filter(si).map(n=>{let i=v(e,n),s=Ae(i);return{path:n,absolutePath:i,ext:F(n),content:s.ok?s.content.toString():void 0}})}var Vr=new Set(["package-lock.json","pnpm-lock.yaml","yarn.lock"]),Br=new Set([...Object.keys(st),".md",".mdx",".markdown",".yaml",".yml",".json",".jsonc",".toml",".sh",".bash",".zsh",".fish",".properties",".ini",".cfg",".conf",".xml",".graphql",".gql",".sql",".txt",".pem"]),Qr=new Set(["Dockerfile","Containerfile","Gemfile","Rakefile","Procfile","Makefile"]);function Jr(e){if(Br.has(F(e)))return!0;let t=T(e);return t===".env"||t.startsWith(".env.")||Qr.has(t)||t.startsWith("Dockerfile.")||t.startsWith("Containerfile.")}function Kr(e){let t=e.subarray(0,Math.min(e.length,8e3));if(t.includes(0))return!0;let n=0;for(let i of t)(i<8||i>13&&i<32)&&n++;return t.length>0&&n/t.length>.1}function oi(e,t,n){return t.filter(Jr).filter(s=>!Vr.has(s)).filter(s=>!ze(T(s))||s===n).flatMap(s=>{let r=v(e,s),o=Ae(r);return o.ok?Kr(o.content)?[]:[{path:s,absolutePath:r,ext:F(s),content:o.content.toString()}]:[{path:s,absolutePath:r,ext:F(s),content:void 0}]})}function Yr(e){let t=_e(e);if(t.status!=="none")return t.status==="ambiguous"?t.reason:t.directory===e?void 0:"App root is nested below a parent Git repository"}function ai(e,t){let n;try{if(n=pe(E(e)),!B(n).isDirectory())return{files:[],unresolvedReason:"App root is not a directory"}}catch(a){return{files:[],unresolvedReason:O("app root",a)}}let i=Yr(n);if(i)return{files:[],unresolvedReason:i};let s=new Set(t),r=[],o;for(let a of Yn){if(!s.has(a))continue;let d=v(n,a),c=Ae(d);if(!c.ok){o??=c.reason==="too_large"?`${a} is too large to inspect`:c.detail??`Could not read ${a}`;continue}r.push({path:a,absolutePath:d,ext:F(a),content:c.content.toString()});break}return r.length>0?{files:r}:{files:r,...o?{unresolvedReason:o}:{}}}function ci(e){return e.filter(t=>T(t)==="package.json")}var Xr=l.object({dependencies:l.record(l.string()).optional(),devDependencies:l.record(l.string()).optional()});function di(e,t){let n=[],i=t.filter(s=>T(s)==="package.json");for(let s of i){let r=v(e,s),o=ct(r);if(o!==void 0)try{let a=Xr.parse(JSON.parse(o));n.push({path:s,absolutePath:r,type:"npm",content:o,dependencies:a.dependencies??{},devDependencies:a.devDependencies??{}})}catch{n.push({path:s,absolutePath:r,type:"npm",content:o,dependencies:{},devDependencies:{}}),Z(r,{ok:!1,reason:"unreadable",detail:"manifest could not be parsed"})}}return n}u();function li(e,t,n,i=e?[e]:[]){let s=t.some(p=>p.type==="theme"),r=t.some(p=>p.type==="theme"&&Zr(p)),o=i.some(p=>p.raw.embedded===!0),a=n.some(p=>p.content?/script[_-]?tags?|ScriptTag/i.test(p.content):!1),d=n.some(p=>p.content?/metafields?Set|metafields?\/.*(?:POST|PUT|create|update)|write.*metafield|metafield.*write/i.test(p.content):!1),c=n.some(eo);return{theme_app_extension:s,app_embed:r,embedded_app:o,script_tags:a,webhooks:!!e?.webhooks.length,app_proxy:!!e?.raw?.app_proxy,storefront_metafield_writes:d,has_backend:c,declared_ip_allowlist:!1,checkout_extension:t.some(p=>p.type==="checkout_ui"||p.type==="checkout_ui_extension")}}function pi(e,t,n){let i=new Set(e.flatMap(x=>[...Object.keys(x.dependencies),...Object.keys(x.devDependencies??{})])),s=new Set(n.map(x=>x.path)),r=i.has("@shopify/shopify-app-react-router"),o=[...s].some(x=>x.startsWith("app/routes/"))&&[...s].some(x=>/^app\/shopify\.server\.[cm]?[jt]sx?$/.test(x)),a=r&&o,d=t.filter(x=>x.type==="theme"),c=d.length>0,p=new Set(d.flatMap(x=>x.files.map(R=>R.path))),m=n.length>0,h;a&&c?h="mixed":a?h="react_router":c&&n.some(x=>!p.has(x.path))?h="mixed":c?h="theme_app_extension":m?h="unknown":h="config_only";let b;a?b="react_router":h==="config_only"||h==="theme_app_extension"?b="none":h==="mixed"?b="mixed":b="unknown";let k=new Map;for(let x of n){let R=k.get(x.language)??{supported:x.supported,files:[]};R.supported&&=x.supported,R.files.push(x.path),k.set(x.language,R)}let _=[...k.entries()].map(([x,R])=>({name:x,support:R.supported?"supported":"unsupported",files:R.files.sort()})).sort((x,R)=>x.name.localeCompare(R.name));return{framework:b,surface:h,languages:_}}function Zr(e){return e.files.some(t=>t.ext===".liquid"&&t.content?.includes('"target"')&&t.content?.includes("body"))}function eo(e){let t=e.content;return t?!!(/\b(?:app|router)\.(get|post|put|delete|patch)\s*\(/.test(t)||/export\s+(?:async\s+)?(?:function|const)\s+(?:loader|action)\b/.test(t)):!1}u();function ui(e,t,n,i,s,r){return{timestamp:new Date().toISOString(),security_version:V(),files_scanned:e,rules_run:t,rules_skipped:n,files_skipped_count:i.length,...i.length>0?{files_skipped:i}:{},coverage_gaps:r,checks_executed:s}}u();function to(e){return e.split(/[\s,]+/).map(t=>t.trim()).filter(Boolean)}var lt={id:"DEPRECATED_SCRIPT_TAG_SCOPE",title:"Deprecated ScriptTag capability",severity:"medium",points:-10,check(e){return e.appTomls.flatMap(t=>to(t.scopes??"").filter(i=>i==="write_script_tags"||i==="read_script_tags").map(i=>({id:this.id,severity:"medium",points:-10,title:this.title,message:`Scope "${i}" is deprecated. Apps should use app embeds instead of the ScriptTag API.`,location:{file:w(e.appRoot,t.path).replace(/\\/g,"/")},fix:{automated:!1,description:`Remove "${i}" from scopes and migrate to app embeds`,guide:"https://shopify.dev/docs/apps/online-store/app-embeds"}})))}},hi={id:"INSECURE_WEBHOOK_URL",title:"Configured callback URL is not HTTPS",severity:"high",points:-12,check(e){let t=[];for(let n of e.appTomls){let i=[...n.webhooks.map(s=>({kind:"Webhook URI",value:s.uri})),...n.redirectUrls.map(s=>({kind:"OAuth redirect URI",value:s}))];for(let{kind:s,value:r}of i)no(r,s==="Webhook URI")||t.push({id:this.id,severity:this.severity,points:this.points,title:this.title,message:`${s} is not a conservative secure URL. Use a Shopify-relative path or an HTTPS URL with an exact host and path.`,location:{file:w(e.appRoot,n.path).replace(/\\/g,"/")},fix:{automated:!1,description:"Use an HTTPS URL with an exact host and path, or a Shopify-relative path where supported.",guide:s==="Webhook URI"?"https://shopify.dev/docs/apps/webhooks":"https://shopify.dev/docs/apps/build/authentication-authorization"}})}return t}};function no(e,t){if(/^\/(?!\/)[^\s*]*$/.test(e)||t&&/^(?:pubsub|eventbridge):\/\/[^\s*]+$/i.test(e)||t&&/^arn:aws:events:[a-z0-9-]+:\d*:event-source\/[^\s*]+$/i.test(e))return!0;try{let n=new URL(e);return n.protocol!=="https:"||!n.hostname||n.hostname.includes("*")||n.username||n.password||n.hash?!1:n.pathname.startsWith("/")&&!n.pathname.includes("*")}catch{return!1}}u();var io=new Set([".js",".jsx",".mjs",".cjs",".ts",".tsx",".mts",".cts"]),ee="[A-Za-z_$][\\w$]*",Q="shop(?:Domain)?",ut=/\b(?:accessToken|access_token|sessionToken|session_token|apiSecret|api_secret|clientSecret|client_secret|SHOPIFY_API_SECRET|SHOPIFY_ACCESS_TOKEN)\b/i;function mi(e){let t=[],n=new Set;for(let i of e){if(!ne(i)||!i.path.includes("/routes/")||/auth[._/-]?(?:login|callback)/i.test(i.path))continue;let s=ue(i.content),r=/export\s+(?:async\s+)?(?:function\s+|const\s+)(loader|action)\b/g,o=r.exec(s);for(;o;){let a=s.slice(o.index,uo(s,r.lastIndex)),d=/\bawait\s+authenticate\.(?:admin|public\.[A-Za-z_$][\w$]*|webhook)\s*\(/.exec(a),c=/\b(?:admin\.graphql|prisma\.|db\.|session\.|metafields?Set|unauthenticated\.admin)\b/.test(a);/\bcontext\s*\.\s*shopify\s*\.\s*authenticate\s*\.\s*admin\s*\(/.test(a)?n.add(i.path):!d&&c&&t.push(Re("UNAUTHENTICATED_ENDPOINT",i,o.index,"Route handler lacks recognized auth verification","The React Router loader/action doesn't have a recognized awaited Shopify authentication barrier.","Call and await authenticate.admin(request) (or the applicable Shopify authenticator) before protected access.",-15)),o=r.exec(s)}}return{issues:t,...n.size>0?{unresolvedReason:`Context-based route authentication needs agent review in ${[...n].join(", ")}. A context.shopify.authenticate.admin call is neither proof of verification nor a missing-auth finding.`,unresolvedReasonCode:"agent_investigation_required"}:{}}}function gi(e){let t=[];for(let n of e){if(!ne(n))continue;let i=fo(yt(n.content)),s=yi(i,["request","req"],so(i)),r=/\bunauthenticated\.admin\s*\(/g,o=r.exec(i);for(;o;){let a=ho(i,r.lastIndex);a&&ht(a.text,s)&&t.push(Re("REQUEST_CONTROLLED_ADMIN_CONTEXT",n,o.index,"Request input selects Admin API shop context","A request-derived shop value reaches unauthenticated.admin(...). Authentication elsewhere in the route does not prove that this independently supplied shop belongs to that session.","Use the Admin API context returned by authenticate.admin(request), or derive the shop directly from its verified session.",-20)),r.lastIndex=a?.end??r.lastIndex,o=r.exec(i)}}return t}function yi(e,t,n=new Set){let i=t.map(te).join("|"),s=new Set,r=new Set,o=new Set,a=new Set,d=new RegExp(`(?:const|let|var)\\s+(${ee})\\s*=\\s*([\\s\\S]{1,500}?)(?:;|\\n(?=\\s*(?:const|let|var|return|await|})))`,"g"),c=d.exec(e);for(;c;){let b=c[1],k=c[2].trim();(new RegExp(`^(?:await\\s+)?(?:${i})\\.json\\s*\\(\\s*\\)$`).test(k)||new RegExp(`^(?:${i})\\.(?:body|query|params)$`).test(k)||fi(k,i,n))&&r.add(b),new RegExp(`^(?:await\\s+)?(?:${i})\\.formData\\s*\\(\\s*\\)$`).test(k)&&o.add(b),new RegExp(`^new\\s+URL\\s*\\(\\s*(?:${i})\\.url\\s*\\)$`).test(k)&&a.add(b),c=d.exec(e)}let p={requestPattern:i,requestControlled:s,requestContainers:r,formDataContainers:o,urlContainers:a,requestShopHelpers:n},m=/(?:const|let|var)\s*\{([\s\S]{1,300}?)\}\s*=\s*([\s\S]{1,300}?)(?:;|\n)/g,h=m.exec(e);for(;h;){let b=h[2].trim();if(new RegExp(`^(?:await\\s+)?(?:${i})\\.json\\s*\\(\\s*\\)$`).test(b)||new RegExp(`^(?:${i})\\.(?:body|query|params)$`).test(b)||r.has(b)||fi(b,i,n))for(let _ of h[1].split(",")){let x=new RegExp(`^\\s*${Q}(?:\\s*:\\s*(${ee}))?\\s*$`).exec(_);x&&s.add(x[1]??_.trim())}h=m.exec(e)}for(let b=0;b<5;b++){let k=!1;for(d.lastIndex=0,c=d.exec(e);c;){let _=c[1];!s.has(_)&&ht(c[2].trim(),p)&&(s.add(_),k=!0),c=d.exec(e)}if(!k)break}return p}function so(e){let t=new Set,n="(?:\\s*:\\s*[^,)]+)?",i="(?:\\s*:\\s*(?:[^{}]|\\{[^{}]*\\})*)?",s=[new RegExp(`\\bfunction\\s+(${ee})\\s*\\(\\s*(${ee})${n}[^)]*\\)${i}\\s*\\{`,"g"),new RegExp(`\\b(?:const|let|var)\\s+(${ee})\\s*=\\s*(?:async\\s+)?\\(\\s*(${ee})${n}[^)]*\\)${i}\\s*=>\\s*\\{`,"g")];for(let r of s){let o=r.exec(e);for(;o;){let a=bi(e,r.lastIndex-1);a&&ro(a.text,o[2])&&t.add(o[1]),r.lastIndex=a?.end??r.lastIndex,o=r.exec(e)}}return t}function ro(e,t){let n=yi(e,[t]);return oo(e).some(i=>ht(i,n))}function oo(e){let t=[],n=/\breturn\s*\{/g,i=n.exec(e);for(;i;){let s=bi(e,n.lastIndex-1);if(!s)break;let r=new RegExp(`(?:^|,)\\s*(${Q})(?:\\s*:\\s*([^,}]+))?\\s*(?=,|$)`,"g"),o=r.exec(s.text);for(;o;)t.push((o[2]??o[1]).trim()),o=r.exec(s.text);n.lastIndex=s.end,i=n.exec(e)}return t}function fi(e,t,n){return[...n].some(i=>new RegExp(`^${vi(i,t)}$`).test(e))}function vi(e,t){return`(?:await\\s+)?${te(e)}\\s*\\(\\s*(?:${t})(?:\\s*,[\\s\\S]*?)?\\s*\\)`}function ht(e,t){if(new RegExp(`(?:${t.requestPattern})\\.(?:body|query|params)(?:\\?\\.|\\.|\\[\\s*["'])${Q}`).test(e)||/(?:searchParams|formData)\.get\s*\(\s*["']shop(?:Domain)?["']\s*\)/.test(e)||new RegExp(`new\\s+URL\\s*\\(\\s*(?:${t.requestPattern})\\.url\\s*\\)\\.searchParams\\.get\\s*\\(\\s*["']shop(?:Domain)?["']`).test(e)||ao(e,t))return!0;for(let i of t.requestContainers)if(new RegExp(`\\b${te(i)}(?:\\?\\.|\\.|\\[\\s*["'])${Q}`).test(e))return!0;for(let i of t.formDataContainers)if(new RegExp(`\\b${te(i)}\\.get\\s*\\(\\s*["']${Q}["']\\s*\\)`).test(e))return!0;for(let i of t.urlContainers)if(new RegExp(`\\b${te(i)}\\.searchParams\\.get\\s*\\(\\s*["']${Q}["']\\s*\\)`).test(e))return!0;return[...t.requestControlled].some(i=>co(e,i))}function ao(e,t){return[...t.requestShopHelpers].some(n=>{let i=`(?:\\(\\s*)?${vi(n,t.requestPattern)}(?:\\s*\\))?`;return new RegExp(`${i}\\s*(?:\\?\\.|\\.|\\[\\s*["'])${Q}`).test(e)})}function co(e,t){let n=new RegExp(`\\b${te(t)}\\b`,"g"),i=n.exec(e);for(;i;){let s=e[i.index-1],r=e.slice(i.index+t.length);if(s!=="."&&s!=='"'&&s!=="'"&&s!=="`"&&!/^\s*:/.test(r))return!0;i=n.exec(e)}return!1}function bi(e,t){let n=0,i;for(let s=t+1;s<e.length;s++){let r=e[s];if(i){r==="\\"?s++:r===i&&(i=void 0);continue}if(r==='"'||r==="'"||r==="`"){i=r;continue}if(r==="{")n++;else if(r==="}"){if(n===0)return{text:e.slice(t+1,s),end:s+1};n--}}}function wi(e){return ki(e,"CREDENTIAL_LOG_LEAKAGE",/\b(?:console\.(?:log|info|warn|error|debug|dir)|logger\.(?:log|info|warn|error|debug))\s*\(/g,"Credential reaches a log sink")}function xi(e){let t="Credential reaches the client browser",n=ki(e,"CREDENTIAL_BROWSER_LEAKAGE",/\b(?:json|defer|(?:new\s+)?Response|(?:localStorage|sessionStorage)\.setItem)\s*\(/g,t),i=po(e,"CREDENTIAL_BROWSER_LEAKAGE",/(?:\breturn\s+\{|\b(?:window|globalThis|document)(?:\.[\w$]+|\[[^\]]+\])?\s*=|\.(?:innerHTML|outerHTML|textContent|value)\s*=)([^;\n]{0,600})/g,t),s=lo(e);return[...new Map([...n,...i,...s].map(r=>[`${r.location.file}:${r.location.line}`,r])).values()]}function ki(e,t,n,i){let s=[];for(let r of e){if(!ne(r))continue;let o=ue(r.content,{preserveTemplateExpressions:!0});n.lastIndex=0;let a=n.exec(o);for(;a;){let d=pt(o,n.lastIndex);d&&ut.test(mt(d.text))&&s.push(ft(t,r,a.index,i)),n.lastIndex=d?.end??n.lastIndex,a=n.exec(o)}}return s}function lo(e){let t=[];for(let n of e){if(!ne(n))continue;let i=yt(n.content),s=ue(n.content,{preserveTemplateExpressions:!0}),r=/\bfetch\s*\(/g,o=r.exec(i);for(;o;){let a=pt(i,r.lastIndex),d=pt(s,r.lastIndex);a&&d&&/^\s*["']https?:\/\//.test(a.text)&&ut.test(mt(d.text))&&t.push(ft("CREDENTIAL_BROWSER_LEAKAGE",n,o.index,"Credential reaches the client browser")),r.lastIndex=a?.end??r.lastIndex,o=r.exec(i)}}return t}function po(e,t,n,i){let s=[];for(let r of e){if(!ne(r))continue;let o=ue(r.content,{preserveTemplateExpressions:!0});n.lastIndex=0;let a=n.exec(o);for(;a;){let d=mt(a[0]);ut.test(d)&&s.push(ft(t,r,a.index,i)),a=n.exec(o)}}return s}function ft(e,t,n,i){return Re(e,t,n,i,"A credential value flows directly into a disclosure sink.",e==="CREDENTIAL_LOG_LEAKAGE"?"Remove the credential from logs; log only a boolean or a deliberately redacted/hash-derived value.":"Keep credentials server-side and return only non-sensitive derived data to the browser.",-20)}function mt(e){let t="(?:accessToken|access_token|sessionToken|session_token|apiSecret|api_secret|clientSecret|client_secret|SHOPIFY_API_SECRET|SHOPIFY_ACCESS_TOKEN)";return e.replace(new RegExp(`\\b(?:redact|mask|hash|digest)\\s*\\(\\s*${t}\\s*\\)`,"gi"),"").replace(new RegExp(`\\bBoolean\\s*\\(\\s*${t}\\s*\\)|!!\\s*${t}`,"gi"),"").replace(new RegExp(`\\b${t}\\s*(?:===?|!==?)\\s*(?:undefined|null|true|false)|(?:undefined|null)\\s*(?:===?|!==?)\\s*${t}`,"gi"),"").replace(new RegExp(`\\bcreateHash\\s*\\([^)]*\\)\\s*\\.update\\s*\\(\\s*${t}\\s*\\)\\s*\\.digest\\s*\\([^)]*\\)`,"gi"),"").replace(new RegExp(`\\b${t}\\s*:`,"gi"),"")}function gt(e){let t=[],n=[/\beval\s*\(\s*(?:await\s+)?[A-Za-z_$({][^)]*\)/g,/\bnew\s+Function\s*\(\s*(?:await\s+)?[A-Za-z_$({][^)]*\)/g,/\.(?:innerHTML|outerHTML)\s*=\s*(?:await\s+)?[A-Za-z_$({][^;\n]*/g,/\.insertAdjacentHTML\s*\([^,]+,\s*(?:await\s+)?[A-Za-z_$({][^)]*\)/g,/dangerouslySetInnerHTML\s*=\s*\{\s*\{\s*__html\s*:\s*(?:await\s+)?[A-Za-z_$({][^}]*/g,/(?:createElement\s*\(\s*["']script["']\s*\)[\s\S]{0,300}?\.src|\bscript\.src)\s*=\s*(?:await\s+)?[A-Za-z_$({][^;\n]*/g];for(let i of e){if(!ne(i))continue;let s=ue(i.content,{preserveTemplateExpressions:!0});for(let r of n){r.lastIndex=0;let o=r.exec(s);for(;o;)/\b(?:sanitize|DOMPurify\.sanitize|escapeHtml)\s*\(/.test(o[0])||t.push(Re("UNSAFE_INNERHTML",i,o.index,"Unsafe HTML assignment","A non-literal value is written to an HTML, script, or evaluation sink.","Avoid executable sinks, or apply a context-appropriate allowlist sanitizer immediately before the sink.",-25)),o=r.exec(s)}}return[...new Map(t.map(i=>[`${i.location.file}:${i.location.line}`,i])).values()]}function ne(e){return!!e.content&&io.has(e.ext)}function uo(e,t){let n=/export\s+(?:async\s+)?(?:function\s+|const\s+)(?:loader|action)\b/g;return n.lastIndex=t,n.exec(e)?.index??e.length}function pt(e,t){let n=0,i;for(let s=t;s<e.length;s++){let r=e[s];if(i){r==="\\"?s++:r===i&&(i=void 0);continue}if(r==='"'||r==="'"||r==="`"){i=r;continue}if(r==="("||r==="["||r==="{")n++;else if(r===")"||r==="]"||r==="}"){if(r===")"&&n===0)return{text:e.slice(t,s),end:s+1};n--}}}function ho(e,t){let n=0,i;for(let s=t;s<e.length;s++){let r=e[s];if(i){r==="\\"?s++:r===i&&(i=void 0);continue}if(r==='"'||r==="'"||r==="`"){i=r;continue}if(r==="("||r==="["||r==="{")n++;else if(r===")"||r==="]"||r==="}"){if(r===")"&&n===0)return{text:e.slice(t,s).trim(),end:s+1};n--}else if(r===","&&n===0)return{text:e.slice(t,s).trim(),end:s+1}}}function yt(e){let t=[...e],n;for(let i=0;i<t.length;i++){let s=t[i];if(n){s==="\\"?i++:s===n&&(n=void 0);continue}if(s==='"'||s==="'"||s==="`"){n=s;continue}if(s==="/"&&t[i+1]==="/")for(;i<t.length&&t[i]!==`
|
|
2354
|
+
- If you can't prove exploitability or affected authority for a specific candidate, don't report a finding: record the check as unresolved and name the candidate in the reason.
|
|
2355
|
+
- If you investigated and the code establishes the boundary the check asks about, record the check as executed. An unprovable hypothetical is not a reason to leave a check unresolved.
|
|
2356
|
+
- Don't report things you couldn't confirm \u2014 uncertainty is not a finding.`,Bn=e=>{let t=G.find(n=>n.id===e);if(!t)throw new Error(`Agent check has no catalog entry: ${e}`);return t},Qn=e=>({schema_version:Ye,engine:{name:D,version:e},generated_at:new Date().toISOString(),checks:[...Ze().values()].map(t=>({id:t.id,version:t.version,severity:t.severity,docs_url:Bn(t.id).docsUrl,prompt:t.prompt})),instructions:vr}),_e=4e3,Fn=4e3,Mn=8e3,Un=4e3,$n=100,jn=50,Hn=4e3,ke=1024,Gn=1e3,br=["executed","not_applicable","unresolved"],N=e=>typeof e=="object"&&e!==null&&!Array.isArray(e),Xe=e=>Number.isInteger(e)&&e>=1,zn=e=>!e||e.startsWith("/")||/^[a-zA-Z]:[\\/]/.test(e)?!1:!e.includes("\0"),wr=e=>{if(!N(e))return"finding must be an object";let t=e;if(typeof t.check_id!="string"||!t.check_id)return"missing or invalid check_id";if(!Xe(t.check_version))return"missing or invalid check_version";if(typeof t.file!="string"||!t.file)return"missing or invalid file";if(!Number.isInteger(t.line)||t.line<1)return`invalid line number: ${t.line}`;if(typeof t.message!="string"||!t.message)return"missing or invalid message";if(!Array.isArray(t.evidence)||t.evidence.length===0)return"finding requires at least one evidence citation";if(t.evidence.length>jn)return`finding exceeds ${jn} evidence citations`;if(t.file.length>ke)return`file path exceeds ${ke} characters`;if(!zn(t.file))return`unsafe file path (must be relative): ${t.file}`;if(t.message.length>_e)return`message exceeds ${_e} characters`;if(t.snippet!==void 0&&typeof t.snippet!="string")return"snippet must be a string";if(typeof t.snippet=="string"&&t.snippet.length>Fn)return`snippet exceeds ${Fn} characters`;if(t.reasoning!==void 0&&typeof t.reasoning!="string")return"reasoning must be a string";if(typeof t.reasoning=="string"&&t.reasoning.length>Mn)return`reasoning exceeds ${Mn} characters`;if(t.confidence!==void 0&&(typeof t.confidence!="string"||!["high","medium","low"].includes(t.confidence)))return"confidence must be high, medium, or low";if(t.suppression!==void 0){let n=t.suppression;if(!N(n)||typeof n.justification!="string"||!n.justification.trim())return"suppression requires a justification";if(n.justification.length>Un)return`suppression justification exceeds ${Un} characters`}for(let n of t.evidence){if(!N(n))return"evidence citation must be an object";if(typeof n.file!="string"||!n.file)return"evidence file must be a non-empty string";if(!zn(n.file))return`unsafe evidence file path: ${n.file}`;if(n.file.length>ke)return`evidence file path exceeds ${ke} characters`;if(n.line!==void 0&&!Xe(n.line))return`invalid evidence line number: ${n.line}`;if(n.quote!==void 0&&typeof n.quote!="string")return"evidence quote must be a string";if(typeof n.quote=="string"&&n.quote.length>Hn)return`evidence quote exceeds ${Hn} characters`}},xr=(e,t,n)=>{if(!Xe(e.check_version))return"missing or invalid check_version";if(typeof t!="string"||!br.includes(t))return"status must be executed, not_applicable, or unresolved";let i=e.reason;if(i===void 0)return t==="executed"?void 0:`${t} requires a reason`;if(!N(i)||typeof i.code!="string"||!i.code||typeof i.message!="string"||!i.message.trim())return"reason requires a code and message";if(i.code.length>$n)return`reason code exceeds ${$n} characters`;if(i.message.length>_e)return`reason message exceeds ${_e} characters`;if(t==="not_applicable"&&n.has(e.check_id))return"a not_applicable check can't have findings"};function kr(e,t,n=Ze()){let i=[],s=[],r=new Set,o=new Set(t.filter(N).map(a=>a.check_id));return e.length>n.size?(i.push(`checks_executed contains ${e.length} entries, exceeding the limit of ${n.size}`),{reports:s,errors:i}):(e.forEach((a,c)=>{let l=`checks_executed[${c}]`;if(!N(a)){i.push(`${l}: executed check must be an object`);return}if(typeof a.check_id!="string"||!a.check_id){i.push(`${l}: missing or invalid check_id`);return}let d=`${l} (${a.check_id})`;if(!n.has(a.check_id)){i.push(`${d}: unknown check_id`);return}if(r.has(a.check_id)){i.push(`${d}: duplicate check_id in checks_executed`);return}r.add(a.check_id);let f=a.status??"executed",h=xr(a,f,o);if(h){i.push(`${d}: ${h}`);return}let g=a.reason;s.push({check_id:a.check_id,check_version:a.check_version,status:f,...g?{reason:{code:y(g.code),message:y(g.message)}}:{}})}),{reports:s,errors:i})}var Wn=e=>y(e.replace(/\\/g,"/")),_r=e=>({location:{file:Wn(e.file),line:e.line},message:y(e.message),evidence:e.evidence.map(t=>({location:{file:Wn(t.file),...t.line===void 0?{}:{line:t.line}},...t.quote===void 0?{}:{quote:y(t.quote)}})),...e.snippet===void 0?{}:{snippet:y(e.snippet)},...e.confidence===void 0?{}:{confidence:e.confidence},...e.reasoning===void 0?{}:{reasoning:y(e.reasoning)},...e.suppression===void 0?{}:{suppression:{justification:y(e.suppression.justification)}}});function Sr(e,t){let n=[],i=new Map;for(let o of t)i.set(o.check_id,[...i.get(o.check_id)??[],o]);let s=new Map(e.map(o=>[o.check_id,o])),r=e.map(o=>({report:o,findings:i.get(o.check_id)??[]}));for(let[o,a]of i){let c=[...new Set(a.map(f=>f.check_version))].sort((f,h)=>f-h),l=s.get(o);if(l){c.some(f=>f!==l.check_version)&&n.push(`${o}: findings claim check_version ${c.join(", ")}, but its checks_executed entry claims ${l.check_version}`);continue}let[d]=c;if(d!==void 0){if(c.length>1){n.push(`${o}: findings claim different check_version values (${c.join(", ")})`);continue}r.push({report:{check_id:o,check_version:d,status:"executed"},findings:a})}}return{groups:r,errors:n}}function Tr(e){let t=Bn(e.id);return{title:t.title,severity:e.severity,description:t.description,...t.guide?{guide:t.guide}:{},docs_url:t.docsUrl,current_version:e.version,precedence:e.precedence}}var Er=e=>{if(!N(e))return{error:"scope is required and must be an object"};let{include_dirs:t,excludes:n,no_git_ignore:i}=e,s=r=>Array.isArray(r)&&r.every(o=>typeof o=="string");return s(t)?s(n)?typeof i!="boolean"?{error:"scope.no_git_ignore must be a boolean"}:{scope:xe({include_dirs:t,excludes:n,no_git_ignore:i})}:{error:"scope.excludes must be an array of strings"}:{error:"scope.include_dirs must be an array of strings"}},Vn=(e,t,n)=>{let i=e[t];return i===void 0?[]:Array.isArray(i)?i:(n.push(`${t} must be an array`),[])};function Ir(e,t){if(!N(e))return{ok:!1,errors:["The findings document must be a JSON object."]};let n=[];e.schema_version!==de&&n.push(`schema_version must be ${de}`);let i=Er(e.scope);"error"in i&&n.push(i.error);let s=Vn(e,"checks_executed",n),r=Vn(e,"findings",n),o=Ze(),a=kr(s,r,o);n.push(...a.errors);let c=[];r.length>Gn?n.push(`findings contains ${r.length} findings, exceeding the limit of ${Gn}`):r.forEach((f,h)=>{let g=N(f)&&typeof f.check_id=="string"?f.check_id:void 0,x=g?`findings[${h}] (${g})`:`findings[${h}]`,E=wr(f)??(o.has(g??"")?void 0:"unknown check_id");if(E){n.push(`${x}: ${E}`);return}c.push(f)});let l=Sr(a.reports,c);if(n.push(...l.errors),n.length>0||"error"in i)return{ok:!1,errors:n.map(y)};let d=l.groups.map(({report:f,findings:h})=>({id:f.check_id,version:f.check_version,status:f.status,...f.reason?{reason:f.reason}:{},snapshot:Tr(o.get(f.check_id)),findings:h.map(_r)})).sort((f,h)=>f.id.localeCompare(h.id));return{ok:!0,document:{schema_version:C,source:"agent",engine:{name:D,version:t.engineVersion},generated_at:t.generatedAt??new Date().toISOString(),scope:i.scope,checks:d}}}u();function z(){return Wt}u();u();u();u();function F(e){return e instanceof Error&&"code"in e&&(e.code==="ENOENT"||e.code==="ENOTDIR")}function q(e,t){let n=t instanceof Error&&"code"in t&&typeof t.code=="string"?t.code:void 0;return n?`Could not inspect ${e} (${n})`:`Could not inspect ${e}`}u();u();import{lstatSync as Ar}from"node:fs";function Se(e){let t=e;for(;;){try{let i=Ar(b(t,".git"));return i.isDirectory()||i.isFile()?{status:"found",directory:t}:{status:"ambiguous",reason:"Could not determine repository ownership from .git"}}catch(i){if(!F(i))return{status:"ambiguous",reason:q(".git",i)}}let n=S(t);if(n===t)return{status:"none"};t=n}}import{lstatSync as Rr,realpathSync as Cr}from"node:fs";function Jn(e){let t=Cr(L());return{excludePatterns:e.excludePatterns.filter(n=>n!=="").map(n=>Lr(n,t)),gitFiltering:!e.noGitIgnore,workingDirectory:t}}var Pr=/[*?[\]{}()!#\\]/;function Lr(e,t){let n=e.split("/"),i=n.findIndex(c=>Pr.test(c)),s=i===-1?n:n.slice(0,i),r=s.join("/")||(e.startsWith("/")?"/":"");if(r==="")return e;let o=w(t,T(t,r)),a=n.slice(s.length).join("/");return a===""?o:o===""?a:`${o}/${a}`}function et(e,t){return t.status==="listed"?{directory:e,paths:new Set(t.paths)}:void 0}function Kn(e,t,n){return e.gitFiltering&&(k(n.absolutePath)===".git"||t&&Dr(t,n))?!0:tt(e,n.absolutePath)}function Yn(e,t,n){let i=n.split("/");return i.some((s,r)=>{let o=b(t,...i.slice(0,r+1));return e.gitFiltering&&s===".git"||tt(e,o)})}function Te(e,t){let n=w(e.workingDirectory,t).split("/");return n.some((i,s)=>tt(e,b(e.workingDirectory,...n.slice(0,s+1))))}function Dr(e,t){let n=w(e.directory,t.absolutePath);return e.paths.has(t.isDirectory?`${n}/`:n)}function tt(e,t){if(e.excludePatterns.length===0)return!1;let n=w(e.workingDirectory,t);return e.excludePatterns.some(i=>Xt(n,i))}async function nt(e){let t=await Nr(e);return t.status!=="listed"&&sn(`app security check: git ignore listing skipped (${t.status})`),t}async function Nr(e){let t=await H(e,["rev-parse","--is-inside-work-tree"]);if(t===void 0)return{status:"failed"};if(t.exitCode!==0)return Se(e).status==="none"?{status:"not-a-repository"}:{status:"failed"};if(t.stdout.trim()==="false")return{status:"not-a-repository"};if(t.stdout.trim()!=="true")return{status:"failed"};let n=await H(e,["ls-files","-z","--others","--ignored","--exclude-standard","--directory","--","."]);return n===void 0||n.exitCode!==0?{status:"failed"}:{status:"listed",paths:ti(n.stdout)}}async function Xn(e){let t=S(e);return t===e?!1:(await H(t,["check-ignore","--no-index","-q","--",k(e)]))?.exitCode===0}async function Zn(e){let t=await H(e,["ls-files","-z","--cached","--","."]);if(!(t===void 0||t.exitCode!==0))return ti(t.stdout)}async function ei(e){try{let t=Rr(b(e,".git"));return t.isDirectory()||t.isFile()?await nt(e):{status:"failed"}}catch(t){return F(t)?void 0:{status:"failed"}}}function ti(e){return e.split("\0").filter(t=>t!=="")}u();var ni=[".github/dependabot.yml",".github/dependabot.yaml","renovate.json","renovate.jsonc","renovate.json5",".github/renovate.json",".github/renovate.jsonc",".github/renovate.json5",".gitlab/renovate.json",".gitlab/renovate.jsonc",".gitlab/renovate.json5",".renovaterc",".renovaterc.json",".renovaterc.jsonc",".renovaterc.json5"];function qr(e){return Object.keys(e.dependencies).length>0||Object.keys(e.devDependencies??{}).length>0}function Or(e){return e.replace(/\\/g,"/")}function Fr(e){let t=e.map(i=>Or(i.path)),n=t.find(i=>i==="package.json");return n||[...t].sort((i,s)=>{let r=i.split("/").length-s.split("/").length;return r===0?i.localeCompare(s):r})[0]}function ii(e){let t=e.manifests.filter(qr);if(t.length===0)return{issues:[]};let{files:n,unresolvedReason:i}=e.dependencyAutomation;return i?{issues:[],unresolvedReason:i}:n.length>0?{issues:[]}:{issues:[{id:"MISSING_DEPENDENCY_SECURITY_AUTOMATION",severity:"low",points:-5,title:"Repository-level dependency management configuration not detected",message:"No recognized Dependabot or Renovate configuration file was found at the repository root. Add dependency update automation there, or verify that an existing integration covers this app. This check only looks for local configuration files; it does not validate their contents or inspect hosted integrations, CI workflows, or execution results.",location:{file:Fr(t)},fix:{automated:!1,description:"Add a Dependabot or Renovate configuration file at the repository root, or verify existing coverage."}}]}}import{lstatSync as W,readdirSync as Mr,realpathSync as le}from"node:fs";function ri(e,t){let n=dt(e);if(n===void 0)return null;try{let i=Ge(n);return jr(i,e,n,t)}catch{return V(e,{ok:!1,reason:"unreadable",detail:"TOML could not be parsed"}),null}}var Ur=p.object({api_version:p.string().optional(),privacy_compliance:p.object({customer_deletion_url:p.string().optional(),customer_data_request_url:p.string().optional(),shop_deletion_url:p.string().optional()}).optional(),subscriptions:p.array(p.unknown()).optional()}),$r=cn.extend({uri:p.preprocess(e=>Ve(e),p.string({invalid_type_error:"Value must be string"}))});function jr(e,t,n,i){let s=Hr(e.access_scopes,t,i),r=Gr(e.auth,t,i),o=zr(e.webhooks,t,i);return{raw:e,path:t,content:n,scopes:s,apiVersion:o.apiVersion,redirectUrls:r,webhooks:o.subscriptions}}function Hr(e,t,n){if(e===void 0)return;let i=un.safeParse(e);if(!i.success){Ee(n,t,"access_scopes section could not be parsed");return}let s=i.data.scopes?.split(/[\s,]+/).filter(Boolean)??[],r=[...new Set([...s,...i.data.required_scopes??[]])];return r.length>0?r.join(","):void 0}function Gr(e,t,n){if(e===void 0)return[];let i=hn.safeParse(e);return i.success?i.data.redirect_urls:(Ee(n,t,"auth section could not be parsed"),[])}function zr(e,t,n){if(e===void 0)return{subscriptions:[]};let i=Ur.safeParse(e);if(!i.success)return Ee(n,t,"webhooks section could not be parsed"),{subscriptions:[]};let s=(i.data.subscriptions??[]).flatMap(a=>Vr(a,t,n)),r=i.data.privacy_compliance,o=[{topic:"customers/redact",uri:r?.customer_deletion_url},{topic:"customers/data_request",uri:r?.customer_data_request_url},{topic:"shop/redact",uri:r?.shop_deletion_url}].flatMap(({topic:a,uri:c})=>c?[{topics:[a],uri:c}]:[]);return{apiVersion:i.data.api_version,subscriptions:[...s,...o]}}var Wr=p.object({uri:p.preprocess(e=>Ve(e),p.string({invalid_type_error:"Value must be string"}))});function Vr(e,t,n){let i=$r.safeParse(e);if(i.success)return[{topics:[...i.data.topics??[],...i.data.compliance_topics??[]],uri:i.data.uri}];Ee(n,t,"webhook subscription could not be parsed");let s=Wr.safeParse(e);return s.success?[{topics:[],uri:s.data.uri}]:[]}function Ee(e,t,n){e&&V(t,{ok:!1,reason:"unreadable",detail:n})}function Br(e,t){try{return Mr(e,{withFileTypes:!0})}catch(n){V(e,{ok:!1,reason:"unreadable",detail:q(t?"scan directory":Ie(e),n)});return}}async function oi({appDirectory:e,scanDirectories:t,requestedScanDirectories:n,selectedAppConfigFilePath:i,rules:s}){let r=[];if(s.gitFiltering)for(let l of n)Te(s,l)||await Xn(l)&&r.push(l);let o=[];for(let l of t)o.push(await Jr(l,s,r.includes(l)));let a=[...o.flatMap(l=>l.absolutePaths),...i?[i]:[]],c=[...new Set(a.map(l=>R(w(e,l))))].sort();return{paths:c,ignoredScanDirectories:r,otherAppDirectories:Qr(e,c),listingStatus:o[0]?.listingStatus??(s.gitFiltering?"tracked-only":"git-ignore-off")}}function Qr(e,t){let n=t.filter(i=>We(k(i))).map(i=>S(i)).filter(i=>i!==".");return[...new Set(n)].map(i=>b(e,i))}async function Jr(e,t,n){if(!t.gitFiltering)return{absolutePaths:await si(e,t,void 0),listingStatus:"git-ignore-off"};if(n){if(Te(t,e))return{absolutePaths:[],listingStatus:"tracked-only"};let s=await Zn(e);if(s===void 0)throw new I(`Couldn't list the files Git tracks in ${w(L(),e)||"."}.`);return{absolutePaths:s.filter(r=>!Yn(t,e,r)).map(r=>b(e,r)).filter(Kr),listingStatus:"tracked-only"}}let i=await nt(e);return{absolutePaths:await si(e,t,et(e,i)),listingStatus:i.status}}function Kr(e){try{return!W(e).isDirectory()}catch{return!1}}async function si(e,t,n){if(Te(t,e))return[];let i=[],s=[{directory:e,repository:n}];for(let{directory:r,repository:o}of s){let a=Br(r,r===e);if(a!==void 0)for(let c of a){let l=b(r,c.name),d=c.isDirectory();if(Kn(t,o,{absolutePath:l,isDirectory:d}))continue;if(!d){i.push(l);continue}let f=t.gitFiltering?await ei(l):void 0;s.push({directory:l,repository:f?et(l,f):o})}}return i}function Yr(e,t){let n=new Map([...t].map(i=>[i,[]]));for(let i of e){if(!pi(i))continue;let s=S(i);for(;s!==".";)n.get(s)?.push(i),s=S(s);n.get(".")?.push(i)}return n}function ai(e,t){let n=t.filter(s=>k(s)==="shopify.extension.toml");if(n.length===0)return[];let i=Yr(t,new Set(n.map(s=>S(s))));return n.flatMap(s=>{let r=b(e,s),o=dt(r);if(o===void 0)return[];try{let c=Ge(o).type,l=lt(e,i.get(S(s))??[]);return[{path:s,type:c,content:o,files:l}]}catch{return V(r,{ok:!1,reason:"unreadable",detail:"TOML could not be parsed"}),[]}})}var Xr=5e5,ot=[],it=new Map,st;function at(e){st=e,ot=[],it.clear()}function pe(){if(!st)throw new tn("The repository reader was used before it was configured.");return st}function ct(){return[...ot]}function V(e,t){let n=w(pe().appDirectory,e).replace(/\\/g,"/");ot.push({path:n.length>0?n:e,reason:t.reason,...t.sizeBytes===void 0?{}:{size_bytes:t.sizeBytes},...t.detail?{detail:t.detail}:{}})}function ci(e){try{let t=Kt(e);return t>Xr?{ok:!1,reason:"too_large",sizeBytes:t}:{ok:!0,content:Qt(e)}}catch(t){return{ok:!1,reason:"unreadable",detail:t instanceof Error?t.message:String(t)}}}function X(e){return{ok:!1,reason:"unreadable",detail:e}}function Ie(e){let t=R(w(pe().appDirectory,e));return t.length===0||t==="."||Vt(t)?"path":t}function di(e,t){let n=T(e),i=T(n,t),s=Ie(i);if(!P(n,i))return{status:"unresolved",reason:`${s} escapes the scan directory`};let r;try{r=le(n)}catch(a){return{status:"unresolved",reason:q("scan directory",a)}}let o=R(w(n,i)).split("/").filter(a=>a.length>0&&a!==".");if(o.includes(".."))return{status:"unresolved",reason:`${s} escapes the scan directory`};if(o.length===0)return{status:"unresolved",reason:`${s} is not a file`};try{let a=le(i);return P(r,a)?W(a).isFile()?{status:"file",path:a}:{status:"unresolved",reason:`${s} is not a file`}:{status:"unresolved",reason:`${s} resolves outside the scan directory`}}catch(a){return F(a)?Zr(r,o,s):{status:"unresolved",reason:q(s,a)}}}function Zr(e,t,n){let i=e;for(let[s,r]of t.entries()){i=b(i,r);try{W(i);let o=le(i);if(!P(e,o))return{status:"unresolved",reason:`${n} resolves outside the scan directory`};let a=W(o);if(s===t.length-1){if(!a.isFile())return{status:"unresolved",reason:`${n} is not a file`}}else if(!a.isDirectory())return{status:"unresolved",reason:`${n} contains an entry that is not a directory`};i=o}catch(o){if(F(o)){try{if(W(i).isSymbolicLink())return{status:"unresolved",reason:`${n} contains a dangling symbolic link`}}catch{return{status:"missing"}}return{status:"missing"}}return{status:"unresolved",reason:q(n,o)}}}return{status:"file",path:i}}function eo(e){let t=pe().scanDirectories.find(i=>P(i,e));if(t===void 0)return{failure:X(`${Ie(e)} is outside every scan directory`)};let n=di(t,e);return n.status==="file"?{path:n.path}:n.status==="missing"?{failure:X("path does not exist")}:{failure:X(n.reason)}}function to(e){let t=Ie(e);try{let n=le(e);return W(n).isFile()?{path:n}:{failure:X(`${t} is not a file`)}}catch(n){return{failure:X(F(n)?"path does not exist":q(t,n))}}}function Ae(e){let t=T(e),n=it.get(t);if(n)return n;let i=pe().explicitInputs.has(t)?to(t):eo(t),s=i.path?ci(i.path):i.failure;return it.set(t,s),s.ok||V(t,s),s}function no(e,t){let n=di(e,t);if(n.status==="missing")return;let i=n.status==="file"?ci(n.path):X(n.reason);return i.ok||V(b(e,t),i),i}function dt(e){let t=Ae(e);return t.ok?t.content.toString():void 0}var rt={".js":{name:"javascript",supported:!0},".jsx":{name:"javascript",supported:!0},".mjs":{name:"javascript",supported:!0},".cjs":{name:"javascript",supported:!0},".ts":{name:"typescript",supported:!0},".tsx":{name:"typescript",supported:!0},".mts":{name:"typescript",supported:!0},".cts":{name:"typescript",supported:!0},".prisma":{name:"prisma",supported:!0},".liquid":{name:"liquid",supported:!0},".html":{name:"html",supported:!0},".ejs":{name:"ejs",supported:!1},".erb":{name:"erb",supported:!1},".hbs":{name:"handlebars",supported:!1},".jinja2":{name:"jinja2",supported:!1},".twig":{name:"twig",supported:!1},".rb":{name:"ruby",supported:!1},".php":{name:"php",supported:!1},".py":{name:"python",supported:!1},".java":{name:"java",supported:!1},".kt":{name:"kotlin",supported:!1},".kts":{name:"kotlin",supported:!1},".go":{name:"go",supported:!1},".rs":{name:"rust",supported:!1},".cs":{name:"csharp",supported:!1},".swift":{name:"swift",supported:!1},".scala":{name:"scala",supported:!1},".ex":{name:"elixir",supported:!1},".exs":{name:"elixir",supported:!1},".vue":{name:"vue",supported:!1},".svelte":{name:"svelte",supported:!1}};function li(e){let t=O(e);return t in rt?rt[t]:void 0}function pi(e){return li(e)?.supported===!0}function ui(e){return e.flatMap(t=>{let n=li(t);return n?[{path:t,extension:O(t),language:n.name,supported:n.supported}]:[]}).sort((t,n)=>t.path.localeCompare(n.path))}function lt(e,t){return t.filter(pi).map(n=>{let i=b(e,n),s=Ae(i);return{path:n,absolutePath:i,ext:O(n),content:s.ok?s.content.toString():void 0}})}var io=new Set(["package-lock.json","pnpm-lock.yaml","yarn.lock"]),so=new Set([...Object.keys(rt),".md",".mdx",".markdown",".yaml",".yml",".json",".jsonc",".toml",".sh",".bash",".zsh",".fish",".properties",".ini",".cfg",".conf",".xml",".graphql",".gql",".sql",".txt",".pem"]),ro=new Set(["Dockerfile","Containerfile","Gemfile","Rakefile","Procfile","Makefile"]);function oo(e){if(so.has(O(e)))return!0;let t=k(e);return t===".env"||t.startsWith(".env.")||ro.has(t)||t.startsWith("Dockerfile.")||t.startsWith("Containerfile.")}function ao(e){let t=e.subarray(0,Math.min(e.length,8e3));if(t.includes(0))return!0;let n=0;for(let i of t)(i<8||i>13&&i<32)&&n++;return t.length>0&&n/t.length>.1}function hi(e,t,n){return t.filter(oo).filter(s=>!io.has(k(s))).filter(s=>!We(k(s))||s===n).flatMap(s=>{let r=b(e,s),o=Ae(r);return o.ok?ao(o.content)?[]:[{path:s,absolutePath:r,ext:O(s),content:o.content.toString()}]:[{path:s,absolutePath:r,ext:O(s),content:void 0}]})}function co(e){let t=Se(e);return t.status==="ambiguous"?{unresolvedReason:t.reason}:{directory:t.status==="found"?t.directory:e}}function fi(e,t){let n;try{if(n=le(T(e)),!W(n).isDirectory())return{files:[],unresolvedReason:"App root is not a directory"}}catch(d){return{files:[],unresolvedReason:q("app root",d)}}let i=co(n);if("unresolvedReason"in i)return{files:[],unresolvedReason:i.unresolvedReason};let s=i.directory,{scanDirectories:r}=pe(),o=d=>s===n||r.some(f=>P(f,d)),a=new Set(t),c=[],l;for(let d of ni){let f=b(s,d),h=R(w(n,f)),g=o(f);if(g&&!a.has(h))continue;let x=g?Ae(f):no(s,d);if(x!==void 0){if(!x.ok){l??=x.reason==="too_large"?`${h} is too large to inspect`:x.detail??`Could not read ${h}`;continue}c.push({path:h,absolutePath:f,ext:O(h),content:x.content.toString()});break}}return c.length>0?{files:c}:{files:c,...l?{unresolvedReason:l}:{}}}function mi(e){return e.filter(t=>k(t)==="package.json")}var lo=p.object({dependencies:p.record(p.string()).optional(),devDependencies:p.record(p.string()).optional()});function gi(e,t){let n=[],i=t.filter(s=>k(s)==="package.json");for(let s of i){let r=b(e,s),o=dt(r);if(o!==void 0)try{let a=lo.parse(JSON.parse(o));n.push({path:s,absolutePath:r,type:"npm",content:o,dependencies:a.dependencies??{},devDependencies:a.devDependencies??{}})}catch{n.push({path:s,absolutePath:r,type:"npm",content:o,dependencies:{},devDependencies:{}}),V(r,{ok:!1,reason:"unreadable",detail:"manifest could not be parsed"})}}return n}u();var yi="@shopify/shopify-app-react-router";function vi(e,t,n,i=e?[e]:[]){let s=t.some(d=>d.type==="theme"),r=t.some(d=>d.type==="theme"&&mo(d)),o=i.some(d=>d.raw.embedded===!0),a=n.some(d=>d.content?/script[_-]?tags?|ScriptTag/i.test(d.content):!1),c=n.some(d=>d.content?/metafields?Set|metafields?\/.*(?:POST|PUT|create|update)|write.*metafield|metafield.*write/i.test(d.content):!1),l=n.some(go);return{theme_app_extension:s,app_embed:r,embedded_app:o,script_tags:a,webhooks:!!e?.webhooks.length,app_proxy:!!e?.raw?.app_proxy,storefront_metafield_writes:c,has_backend:l,declared_ip_allowlist:!1,checkout_extension:t.some(d=>d.type==="checkout_ui"||d.type==="checkout_ui_extension")}}function bi(e,t,n=[]){let i=e.filter(o=>o.dependencies[yi]!==void 0||o.devDependencies?.[yi]!==void 0);if(i.length===0)return[];let s=t.map(o=>o.path);return[...new Set([".",...i.map(o=>S(o.path))])].filter(o=>uo(o,s)).filter(o=>!ho(o,n)).sort()}function pt(e,t){let n=Z(e,t);return n!==void 0&&/^app\/shopify\.server\.[cm]?[jt]sx?$/.test(n)}function wi(e,t,n){let i=xi(n).filter(r=>!po(r,t));return(!ut(e)||t.some(r=>Z(r,e)!==void 0))&&!i.some(r=>Z(r,e)!==void 0)}function po(e,t){return t.some(n=>n==="."?!ut(e):e===n||Z(n,e)!==void 0)}function ut(e){return e===".."||e.startsWith("../")}function Z(e,t){if(e===".")return t;if(!t.startsWith(`${e}/`))return;let n=t.slice(e.length+1);return ut(n)?void 0:n}function uo(e,t){return t.some(n=>Z(e,n)?.startsWith("app/routes/"))&&t.some(n=>pt(e,n))}function ho(e,t){return e==="."?!1:t.includes(e)||xi(t).some(n=>e===n||Z(n,e)!==void 0)}function xi(e){return e.flatMap(t=>fo(t)?[`${t}/app`,`${t}/web`]:[t])}function fo(e){return e.split("/").every(t=>t==="..")}function ki(e,t,n){let i=n.length>0,s=e.filter(h=>h.type==="theme"),r=s.length>0,o=new Set(s.flatMap(h=>h.files.map(g=>g.path))),a=t.length>0,c;i&&r?c="mixed":i?c="react_router":r&&t.some(h=>!o.has(h.path))?c="mixed":r?c="theme_app_extension":a?c="unknown":c="config_only";let l;i?l="react_router":c==="config_only"||c==="theme_app_extension"?l="none":c==="mixed"?l="mixed":l="unknown";let d=new Map;for(let h of t){let g=d.get(h.language)??{supported:h.supported,files:[]};g.supported&&=h.supported,g.files.push(h.path),d.set(h.language,g)}let f=[...d.entries()].map(([h,g])=>({name:h,support:g.supported?"supported":"unsupported",files:g.files.sort()})).sort((h,g)=>h.name.localeCompare(g.name));return{framework:l,surface:c,languages:f}}function mo(e){return e.files.some(t=>t.ext===".liquid"&&t.content?.includes('"target"')&&t.content?.includes("body"))}function go(e){let t=e.content;return t?!!(/\b(?:app|router)\.(get|post|put|delete|patch)\s*\(/.test(t)||/export\s+(?:async\s+)?(?:function|const)\s+(?:loader|action)\b/.test(t)):!1}u();function _i(e,t,n,i,s,r){return{timestamp:new Date().toISOString(),security_version:z(),files_scanned:e,rules_run:t,rules_skipped:n,files_skipped_count:i.length,...i.length>0?{files_skipped:i}:{},coverage_gaps:r,checks_executed:s}}u();function yo(e){return e.split(/[\s,]+/).map(t=>t.trim()).filter(Boolean)}var ht={id:"DEPRECATED_SCRIPT_TAG_SCOPE",title:"Deprecated ScriptTag capability",severity:"medium",points:-10,check(e){return e.appTomls.flatMap(t=>yo(t.scopes??"").filter(i=>i==="write_script_tags"||i==="read_script_tags").map(i=>({id:this.id,severity:"medium",points:-10,title:this.title,message:`Scope "${i}" is deprecated. Apps should use app embeds instead of the ScriptTag API.`,location:{file:w(e.appRoot,t.path).replace(/\\/g,"/")},fix:{automated:!1,description:`Remove "${i}" from scopes and migrate to app embeds`,guide:"https://shopify.dev/docs/apps/online-store/app-embeds"}})))}},Si={id:"INSECURE_WEBHOOK_URL",title:"Configured callback URL is not HTTPS",severity:"high",points:-12,check(e){let t=[];for(let n of e.appTomls){let i=[...n.webhooks.map(s=>({kind:"Webhook URI",value:s.uri})),...n.redirectUrls.map(s=>({kind:"OAuth redirect URI",value:s}))];for(let{kind:s,value:r}of i)vo(r,s==="Webhook URI")||t.push({id:this.id,severity:this.severity,points:this.points,title:this.title,message:`${s} is not a conservative secure URL. Use a Shopify-relative path or an HTTPS URL with an exact host and path.`,location:{file:w(e.appRoot,n.path).replace(/\\/g,"/")},fix:{automated:!1,description:"Use an HTTPS URL with an exact host and path, or a Shopify-relative path where supported.",guide:s==="Webhook URI"?"https://shopify.dev/docs/apps/webhooks":"https://shopify.dev/docs/apps/build/authentication-authorization"}})}return t}};function vo(e,t){if(/^\/(?!\/)[^\s*]*$/.test(e)||t&&/^(?:pubsub|eventbridge):\/\/[^\s*]+$/i.test(e)||t&&/^arn:aws:events:[a-z0-9-]+:\d*:event-source\/[^\s*]+$/i.test(e))return!0;try{let n=new URL(e);return n.protocol!=="https:"||!n.hostname||n.hostname.includes("*")||n.username||n.password||n.hash?!1:n.pathname.startsWith("/")&&!n.pathname.includes("*")}catch{return!1}}u();var bo=new Set([".js",".jsx",".mjs",".cjs",".ts",".tsx",".mts",".cts"]),ee="[A-Za-z_$][\\w$]*",B="shop(?:Domain)?",mt=/\b(?:accessToken|access_token|sessionToken|session_token|apiSecret|api_secret|clientSecret|client_secret|SHOPIFY_API_SECRET|SHOPIFY_ACCESS_TOKEN)\b/i;function Ei(e){let t=[],n=new Set;for(let i of e){if(!ne(i)||!i.path.includes("/routes/")||/auth[._/-]?(?:login|callback)/i.test(i.path))continue;let s=ue(i.content),r=/export\s+(?:async\s+)?(?:function\s+|const\s+)(loader|action)\b/g,o=r.exec(s);for(;o;){let a=s.slice(o.index,Io(s,r.lastIndex)),c=/\bawait\s+authenticate\.(?:admin|public\.[A-Za-z_$][\w$]*|webhook)\s*\(/.exec(a),l=/\b(?:admin\.graphql|prisma\.|db\.|session\.|metafields?Set|unauthenticated\.admin)\b/.test(a);/\bcontext\s*\.\s*shopify\s*\.\s*authenticate\s*\.\s*admin\s*\(/.test(a)?n.add(i.path):!c&&l&&t.push(Re("UNAUTHENTICATED_ENDPOINT",i,o.index,"Route handler lacks recognized auth verification","The React Router loader/action doesn't have a recognized awaited Shopify authentication barrier.","Call and await authenticate.admin(request) (or the applicable Shopify authenticator) before protected access.",-15)),o=r.exec(s)}}return{issues:t,...n.size>0?{unresolvedReason:`Context-based route authentication needs agent review in ${[...n].join(", ")}. A context.shopify.authenticate.admin call is neither proof of verification nor a missing-auth finding.`,unresolvedReasonCode:"agent_investigation_required"}:{}}}function Ii(e){let t=[];for(let n of e){if(!ne(n))continue;let i=Ro(wt(n.content)),s=Ai(i,["request","req"],wo(i)),r=/\bunauthenticated\.admin\s*\(/g,o=r.exec(i);for(;o;){let a=Ao(i,r.lastIndex);a&>(a.text,s)&&t.push(Re("REQUEST_CONTROLLED_ADMIN_CONTEXT",n,o.index,"Request input selects Admin API shop context","A request-derived shop value reaches unauthenticated.admin(...). Authentication elsewhere in the route does not prove that this independently supplied shop belongs to that session.","Use the Admin API context returned by authenticate.admin(request), or derive the shop directly from its verified session.",-20)),r.lastIndex=a?.end??r.lastIndex,o=r.exec(i)}}return t}function Ai(e,t,n=new Set){let i=t.map(te).join("|"),s=new Set,r=new Set,o=new Set,a=new Set,c=new RegExp(`(?:const|let|var)\\s+(${ee})\\s*=\\s*([\\s\\S]{1,500}?)(?:;|\\n(?=\\s*(?:const|let|var|return|await|})))`,"g"),l=c.exec(e);for(;l;){let g=l[1],x=l[2].trim();(new RegExp(`^(?:await\\s+)?(?:${i})\\.json\\s*\\(\\s*\\)$`).test(x)||new RegExp(`^(?:${i})\\.(?:body|query|params)$`).test(x)||Ti(x,i,n))&&r.add(g),new RegExp(`^(?:await\\s+)?(?:${i})\\.formData\\s*\\(\\s*\\)$`).test(x)&&o.add(g),new RegExp(`^new\\s+URL\\s*\\(\\s*(?:${i})\\.url\\s*\\)$`).test(x)&&a.add(g),l=c.exec(e)}let d={requestPattern:i,requestControlled:s,requestContainers:r,formDataContainers:o,urlContainers:a,requestShopHelpers:n},f=/(?:const|let|var)\s*\{([\s\S]{1,300}?)\}\s*=\s*([\s\S]{1,300}?)(?:;|\n)/g,h=f.exec(e);for(;h;){let g=h[2].trim();if(new RegExp(`^(?:await\\s+)?(?:${i})\\.json\\s*\\(\\s*\\)$`).test(g)||new RegExp(`^(?:${i})\\.(?:body|query|params)$`).test(g)||r.has(g)||Ti(g,i,n))for(let E of h[1].split(",")){let ye=new RegExp(`^\\s*${B}(?:\\s*:\\s*(${ee}))?\\s*$`).exec(E);ye&&s.add(ye[1]??E.trim())}h=f.exec(e)}for(let g=0;g<5;g++){let x=!1;for(c.lastIndex=0,l=c.exec(e);l;){let E=l[1];!s.has(E)&>(l[2].trim(),d)&&(s.add(E),x=!0),l=c.exec(e)}if(!x)break}return d}function wo(e){let t=new Set,n="(?:\\s*:\\s*[^,)]+)?",i="(?:\\s*:\\s*(?:[^{}]|\\{[^{}]*\\})*)?",s=[new RegExp(`\\bfunction\\s+(${ee})\\s*\\(\\s*(${ee})${n}[^)]*\\)${i}\\s*\\{`,"g"),new RegExp(`\\b(?:const|let|var)\\s+(${ee})\\s*=\\s*(?:async\\s+)?\\(\\s*(${ee})${n}[^)]*\\)${i}\\s*=>\\s*\\{`,"g")];for(let r of s){let o=r.exec(e);for(;o;){let a=Ci(e,r.lastIndex-1);a&&xo(a.text,o[2])&&t.add(o[1]),r.lastIndex=a?.end??r.lastIndex,o=r.exec(e)}}return t}function xo(e,t){let n=Ai(e,[t]);return ko(e).some(i=>gt(i,n))}function ko(e){let t=[],n=/\breturn\s*\{/g,i=n.exec(e);for(;i;){let s=Ci(e,n.lastIndex-1);if(!s)break;let r=new RegExp(`(?:^|,)\\s*(${B})(?:\\s*:\\s*([^,}]+))?\\s*(?=,|$)`,"g"),o=r.exec(s.text);for(;o;)t.push((o[2]??o[1]).trim()),o=r.exec(s.text);n.lastIndex=s.end,i=n.exec(e)}return t}function Ti(e,t,n){return[...n].some(i=>new RegExp(`^${Ri(i,t)}$`).test(e))}function Ri(e,t){return`(?:await\\s+)?${te(e)}\\s*\\(\\s*(?:${t})(?:\\s*,[\\s\\S]*?)?\\s*\\)`}function gt(e,t){if(new RegExp(`(?:${t.requestPattern})\\.(?:body|query|params)(?:\\?\\.|\\.|\\[\\s*["'])${B}`).test(e)||/(?:searchParams|formData)\.get\s*\(\s*["']shop(?:Domain)?["']\s*\)/.test(e)||new RegExp(`new\\s+URL\\s*\\(\\s*(?:${t.requestPattern})\\.url\\s*\\)\\.searchParams\\.get\\s*\\(\\s*["']shop(?:Domain)?["']`).test(e)||_o(e,t))return!0;for(let i of t.requestContainers)if(new RegExp(`\\b${te(i)}(?:\\?\\.|\\.|\\[\\s*["'])${B}`).test(e))return!0;for(let i of t.formDataContainers)if(new RegExp(`\\b${te(i)}\\.get\\s*\\(\\s*["']${B}["']\\s*\\)`).test(e))return!0;for(let i of t.urlContainers)if(new RegExp(`\\b${te(i)}\\.searchParams\\.get\\s*\\(\\s*["']${B}["']\\s*\\)`).test(e))return!0;return[...t.requestControlled].some(i=>So(e,i))}function _o(e,t){return[...t.requestShopHelpers].some(n=>{let i=`(?:\\(\\s*)?${Ri(n,t.requestPattern)}(?:\\s*\\))?`;return new RegExp(`${i}\\s*(?:\\?\\.|\\.|\\[\\s*["'])${B}`).test(e)})}function So(e,t){let n=new RegExp(`\\b${te(t)}\\b`,"g"),i=n.exec(e);for(;i;){let s=e[i.index-1],r=e.slice(i.index+t.length);if(s!=="."&&s!=='"'&&s!=="'"&&s!=="`"&&!/^\s*:/.test(r))return!0;i=n.exec(e)}return!1}function Ci(e,t){let n=0,i;for(let s=t+1;s<e.length;s++){let r=e[s];if(i){r==="\\"?s++:r===i&&(i=void 0);continue}if(r==='"'||r==="'"||r==="`"){i=r;continue}if(r==="{")n++;else if(r==="}"){if(n===0)return{text:e.slice(t+1,s),end:s+1};n--}}}function Pi(e){return Di(e,"CREDENTIAL_LOG_LEAKAGE",/\b(?:console\.(?:log|info|warn|error|debug|dir)|logger\.(?:log|info|warn|error|debug))\s*\(/g,"Credential reaches a log sink")}function Li(e){let t="Credential reaches the client browser",n=Di(e,"CREDENTIAL_BROWSER_LEAKAGE",/\b(?:json|defer|(?:new\s+)?Response|(?:localStorage|sessionStorage)\.setItem)\s*\(/g,t),i=Eo(e,"CREDENTIAL_BROWSER_LEAKAGE",/(?:\breturn\s+\{|\b(?:window|globalThis|document)(?:\.[\w$]+|\[[^\]]+\])?\s*=|\.(?:innerHTML|outerHTML|textContent|value)\s*=)([^;\n]{0,600})/g,t),s=To(e);return[...new Map([...n,...i,...s].map(r=>[`${r.location.file}:${r.location.line}`,r])).values()]}function Di(e,t,n,i){let s=[];for(let r of e){if(!ne(r))continue;let o=ue(r.content,{preserveTemplateExpressions:!0});n.lastIndex=0;let a=n.exec(o);for(;a;){let c=ft(o,n.lastIndex);c&&mt.test(vt(c.text))&&s.push(yt(t,r,a.index,i)),n.lastIndex=c?.end??n.lastIndex,a=n.exec(o)}}return s}function To(e){let t=[];for(let n of e){if(!ne(n))continue;let i=wt(n.content),s=ue(n.content,{preserveTemplateExpressions:!0}),r=/\bfetch\s*\(/g,o=r.exec(i);for(;o;){let a=ft(i,r.lastIndex),c=ft(s,r.lastIndex);a&&c&&/^\s*["']https?:\/\//.test(a.text)&&mt.test(vt(c.text))&&t.push(yt("CREDENTIAL_BROWSER_LEAKAGE",n,o.index,"Credential reaches the client browser")),r.lastIndex=a?.end??r.lastIndex,o=r.exec(i)}}return t}function Eo(e,t,n,i){let s=[];for(let r of e){if(!ne(r))continue;let o=ue(r.content,{preserveTemplateExpressions:!0});n.lastIndex=0;let a=n.exec(o);for(;a;){let c=vt(a[0]);mt.test(c)&&s.push(yt(t,r,a.index,i)),a=n.exec(o)}}return s}function yt(e,t,n,i){return Re(e,t,n,i,"A credential value flows directly into a disclosure sink.",e==="CREDENTIAL_LOG_LEAKAGE"?"Remove the credential from logs; log only a boolean or a deliberately redacted/hash-derived value.":"Keep credentials server-side and return only non-sensitive derived data to the browser.",-20)}function vt(e){let t="(?:accessToken|access_token|sessionToken|session_token|apiSecret|api_secret|clientSecret|client_secret|SHOPIFY_API_SECRET|SHOPIFY_ACCESS_TOKEN)";return e.replace(new RegExp(`\\b(?:redact|mask|hash|digest)\\s*\\(\\s*${t}\\s*\\)`,"gi"),"").replace(new RegExp(`\\bBoolean\\s*\\(\\s*${t}\\s*\\)|!!\\s*${t}`,"gi"),"").replace(new RegExp(`\\b${t}\\s*(?:===?|!==?)\\s*(?:undefined|null|true|false)|(?:undefined|null)\\s*(?:===?|!==?)\\s*${t}`,"gi"),"").replace(new RegExp(`\\bcreateHash\\s*\\([^)]*\\)\\s*\\.update\\s*\\(\\s*${t}\\s*\\)\\s*\\.digest\\s*\\([^)]*\\)`,"gi"),"").replace(new RegExp(`\\b${t}\\s*:`,"gi"),"")}function bt(e){let t=[],n=[/\beval\s*\(\s*(?:await\s+)?[A-Za-z_$({][^)]*\)/g,/\bnew\s+Function\s*\(\s*(?:await\s+)?[A-Za-z_$({][^)]*\)/g,/\.(?:innerHTML|outerHTML)\s*=\s*(?:await\s+)?[A-Za-z_$({][^;\n]*/g,/\.insertAdjacentHTML\s*\([^,]+,\s*(?:await\s+)?[A-Za-z_$({][^)]*\)/g,/dangerouslySetInnerHTML\s*=\s*\{\s*\{\s*__html\s*:\s*(?:await\s+)?[A-Za-z_$({][^}]*/g,/(?:createElement\s*\(\s*["']script["']\s*\)[\s\S]{0,300}?\.src|\bscript\.src)\s*=\s*(?:await\s+)?[A-Za-z_$({][^;\n]*/g];for(let i of e){if(!ne(i))continue;let s=ue(i.content,{preserveTemplateExpressions:!0});for(let r of n){r.lastIndex=0;let o=r.exec(s);for(;o;)/\b(?:sanitize|DOMPurify\.sanitize|escapeHtml)\s*\(/.test(o[0])||t.push(Re("UNSAFE_INNERHTML",i,o.index,"Unsafe HTML assignment","A non-literal value is written to an HTML, script, or evaluation sink.","Avoid executable sinks, or apply a context-appropriate allowlist sanitizer immediately before the sink.",-25)),o=r.exec(s)}}return[...new Map(t.map(i=>[`${i.location.file}:${i.location.line}`,i])).values()]}function ne(e){return!!e.content&&bo.has(e.ext)}function Io(e,t){let n=/export\s+(?:async\s+)?(?:function\s+|const\s+)(?:loader|action)\b/g;return n.lastIndex=t,n.exec(e)?.index??e.length}function ft(e,t){let n=0,i;for(let s=t;s<e.length;s++){let r=e[s];if(i){r==="\\"?s++:r===i&&(i=void 0);continue}if(r==='"'||r==="'"||r==="`"){i=r;continue}if(r==="("||r==="["||r==="{")n++;else if(r===")"||r==="]"||r==="}"){if(r===")"&&n===0)return{text:e.slice(t,s),end:s+1};n--}}}function Ao(e,t){let n=0,i;for(let s=t;s<e.length;s++){let r=e[s];if(i){r==="\\"?s++:r===i&&(i=void 0);continue}if(r==='"'||r==="'"||r==="`"){i=r;continue}if(r==="("||r==="["||r==="{")n++;else if(r===")"||r==="]"||r==="}"){if(r===")"&&n===0)return{text:e.slice(t,s).trim(),end:s+1};n--}else if(r===","&&n===0)return{text:e.slice(t,s).trim(),end:s+1}}}function wt(e){let t=[...e],n;for(let i=0;i<t.length;i++){let s=t[i];if(n){s==="\\"?i++:s===n&&(n=void 0);continue}if(s==='"'||s==="'"||s==="`"){n=s;continue}if(s==="/"&&t[i+1]==="/")for(;i<t.length&&t[i]!==`
|
|
2346
2357
|
`;)t[i]=" ",i++;else if(s==="/"&&t[i+1]==="*"){for(t[i]=" ",t[i+1]=" ",i+=2;i<t.length&&!(t[i]==="*"&&t[i+1]==="/");)t[i]!==`
|
|
2347
|
-
`&&(t[i]=" "),i++;i<t.length&&(t[i]=" ",t[i+1]=" ",i++)}}return t.join("")}function
|
|
2358
|
+
`&&(t[i]=" "),i++;i<t.length&&(t[i]=" ",t[i+1]=" ",i++)}}return t.join("")}function Ro(e){return e.replace(/(["'])(?:\\.|(?!\1)[^\\\n])*\1|`(?:\\.|[^`\\])*`/g,t=>/^["']shop(?:Domain)?["']$/.test(t)?t:t.replace(/[^\n]/g," "))}function ue(e,t={}){return wt(e).replace(/(["'])(?:\\.|(?!\1)[^\\\n])*\1|`(?:\\.|[^`\\])*`/g,n=>{if(!t.preserveTemplateExpressions||!n.startsWith("`"))return n.replace(/[^\n]/g," ");let i=[...n],s=i.map(o=>o===`
|
|
2348
2359
|
`?`
|
|
2349
2360
|
`:" "),r=0;for(let o=0;o<i.length;o++){if(r===0&&i[o]==="$"&&i[o+1]==="{"){r=1,o++;continue}if(r!==0){if(i[o]==="{")r++;else if(i[o]==="}"){r--;continue}s[o]=i[o]}}return s.join("")})}function te(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function Re(e,t,n,i,s,r,o){return{id:e,severity:"high",points:o,title:i,message:s,location:{file:t.path,line:t.content.slice(0,n).split(`
|
|
2350
|
-
`).length},fix:{automated:!1,description:r}}}u();var he=
|
|
2351
|
-
`,Math.max(0,n-1))+1;return{id:e,pattern_id:`liquid-${
|
|
2352
|
-
`).length,column:n-
|
|
2361
|
+
`).length},fix:{automated:!1,description:r}}}u();var he=zt(js(),1);function xt(e){let t=[],n=[];for(let i of e){if(!i.content||![".liquid",".html"].includes(i.ext))continue;let s;try{s=(0,he.toLiquidHTMLAST)(i.content)}catch{n.push(i.path);continue}if(s instanceof Error){n.push(i.path);continue}t.push(...(0,he.visit)(s,Co(i))),t.push(...(0,he.visit)(s,Po(i)))}return{issues:t,parserFailures:n}}function Co(e){return{LiquidVariableOutput(t,n){let i=Oi(t);if(!/(?:\bmetafields?\b|\b(?:block|section)\.settings\b)/i.test(i))return;let s=Fi(t),r=Ni(n);if(!Lo(s,r))return[Mi("LIQUID_UNSAFE_RENDER",e,t.position.start,"Unsafe Liquid metafield or setting output","A metafield or merchant-configurable setting is emitted without context-appropriate escaping or serialization. Liquid output is not automatically HTML-escaped.",Do(r),"medium",-10,r)]}}}function Po(e){return{LiquidVariableOutput(t,n){let i=Ni(n);if(!(i!=="javascript"&&i!=="executable_attribute")&&!(i==="javascript"&&Fi(t).includes("json"))&&!No(t,n))return[Mi("UNSAFE_INNERHTML",e,t.position.start,"Unsafe HTML assignment",i==="javascript"?"Dynamic Liquid output is embedded in JavaScript without JSON serialization.":"Dynamic Liquid output is embedded in an event handler, srcdoc, or script src attribute. HTML escaping alone does not make executable attributes safe.",i==="javascript"?"Serialize the value with the json filter and consume it only as JavaScript data.":"Keep dynamic values out of executable attributes; use a fixed, versioned script asset and event listeners.","high",-25,i)]}}}function Lo(e,t){return t==="javascript"?e.includes("json"):t==="html_attribute"?e.some(n=>["escape","escape_once"].includes(n)):t==="executable_attribute"?!1:e.some(n=>["escape","escape_once","metafield_tag"].includes(n))}function Do(e){return e==="javascript"?"Serialize JavaScript data with json and consume it as data.":e==="executable_attribute"?"Don't place merchant-controlled Liquid output in event handlers, srcdoc, or script src attributes.":"Use escape/escape_once for HTML text or attributes, or metafield_tag for supported rich content in HTML text."}function Ni(e){let t=e.find(s=>s.type==="HtmlRawNode"&&s.name==="script"),n=e.find(s=>s.type.startsWith("Attr"));if(!n)return t?"javascript":"html_text";let i=qi(n);return/^on/i.test(i)||i.toLowerCase()==="srcdoc"||i.toLowerCase()==="src"&&t?"executable_attribute":"html_attribute"}function qi(e){return!("name"in e)||!Array.isArray(e.name)?"":e.name.map(t=>"value"in t&&typeof t.value=="string"?t.value:"").join("")}function No(e,t){let n=t.find(s=>s.type==="HtmlRawNode"&&s.name==="script"),i=t.find(s=>s.type.startsWith("Attr"));return!n||!i||qi(i).toLowerCase()!=="src"?!1:/^(['"])(?:\\.|(?!\1)[^\\\n])+\1\s*\|\s*asset_url\s*$/.test(Oi(e).trim())}function Oi(e){return typeof e.markup=="string"?e.markup:e.markup.rawSource}function Fi(e){return typeof e.markup=="string"?[]:e.markup.filters.map(t=>t.name)}function Mi(e,t,n,i,s,r,o,a,c){let l=t.content.lastIndexOf(`
|
|
2362
|
+
`,Math.max(0,n-1))+1;return{id:e,pattern_id:`liquid-${c}`,severity:o,points:a,title:i,message:s,location:{file:t.path,line:t.content.slice(0,n).split(`
|
|
2363
|
+
`).length,column:n-l+1},snippet:t.content.slice(n,t.content.indexOf(`
|
|
2353
2364
|
`,n)===-1?void 0:t.content.indexOf(`
|
|
2354
|
-
`,n)).trim(),fix:{automated:!1,description:r}}}u();var
|
|
2355
|
-
`).length}u();var
|
|
2356
|
-
`).length}u();var
|
|
2357
|
-
`).length},fix:{automated:!1,description:"Return inert JSON, or render only trusted templates with context-appropriate escaping.",guide:"https://shopify.dev/docs/apps/online-store/app-proxies"}})}return t}function
|
|
2358
|
-
`&&!
|
|
2359
|
-
`&&(e[i]=" ")}}function
|
|
2360
|
-
`&&(e[i]=" ")}function
|
|
2365
|
+
`,n)).trim(),fix:{automated:!1,description:r}}}u();var qo=new Set([".js",".jsx",".mjs",".cjs",".ts",".tsx",".mts",".cts"]),Oo=[/\bscriptTagCreate\b|\bscriptTagUpdate\b/,/\bnew\s+[\w.]*\bScriptTag\s*\(/,/\b(?:post|put)\b[^\n]{0,120}script_tags/i];function Ui(e){let t=[];for(let n of e){if(!n.content||!qo.has(n.ext))continue;let i=Fo(n.content);for(let s of Oo){let r=s.exec(i);if(r){t.push({id:"DEPRECATED_SCRIPT_TAG_SCOPE",severity:"medium",points:-10,title:"Deprecated ScriptTag capability",message:"This code uses the deprecated ScriptTag API. Remotely hosted storefront JavaScript can be silently replaced outside extension review and versioning.",location:{file:n.path,line:Mo(i,r.index)},fix:{automated:!1,description:"Migrate storefront UI to a theme app extension or analytics to a web pixel.",guide:"https://shopify.dev/docs/apps/build/online-store/theme-app-extensions"}});break}}}return t}function Fo(e){return e.replace(/\/\*[\s\S]*?\*\//g,t=>t.replace(/[^\n]/g," ")).replace(/\/\/[^\n]*/g,"")}function Mo(e,t){return e.slice(0,t).split(`
|
|
2366
|
+
`).length}u();var Uo=["shop/redact","customers/data_request","customers/redact"],$o=new Map([["January","01"],["April","04"],["July","07"],["October","10"]]),$i={id:"MISSING_COMPLIANCE_WEBHOOKS",title:"Missing mandatory GDPR compliance webhooks",severity:"medium",points:-10,check(e){return e.appTomls.flatMap(t=>{let n=new Set(t.webhooks.flatMap(s=>s.topics)),i=Uo.filter(s=>!n.has(s));return i.length===0?[]:[{id:this.id,severity:this.severity,points:this.points,title:this.title,message:`Missing mandatory compliance webhooks: ${i.join(", ")}.`,location:{file:w(e.appRoot,t.path).replace(/\\/g,"/")},fix:{automated:!1,description:"Add subscriptions for all three mandatory compliance topics.",guide:"https://shopify.dev/docs/apps/webhooks/configuration/mandatory-webhooks"}}]})}};function ji(e,t,n=30){let i=/^(\d{4})-(01|04|07|10)$/.exec(e);if(!i)return!1;let s=Date.UTC(Number(i[1]),Number(i[2])-1,1),o=Date.UTC(Number(i[1])+1,Number(i[2])-1,1)+n*24*60*60*1e3;return s<=t.getTime()&&t.getTime()>=o}function Hi(e,t=new Date){let n=e.appTomls.flatMap(s=>!s.apiVersion||!ji(s.apiVersion,t)?[]:[Gi(w(e.appRoot,s.path).replace(/\\/g,"/"),s.apiVersion)]);if(e.detection.framework!=="react_router")return n;let i=e.sourceFiles.flatMap(s=>jo(s,e.reactRouterRoots,t));return[...n,...i]}function jo(e,t,n){if(!e.content||!t.some(r=>pt(r,e.path)))return[];let i=zo(Go(e.content));return[...i.matchAll(/\bapiVersion\s*:\s*["'](\d{4}-(?:01|04|07|10))["']/g),...i.matchAll(/\bapiVersion\s*:\s*ApiVersion\.(January|April|July|October)(\d{2}|\d{4})\b/g)].flatMap(r=>{let o=r[1]?.includes("-")?r[1]:Ho(r[1],r[2]);return!o||!ji(o,n)?[]:[Gi(e.path,o,Wo(e.content,r.index??0))]})}function Ho(e,t){let n=$o.get(e);return n?`${t.length===2?`20${t}`:t}-${n}`:void 0}function Gi(e,t,n){return{id:"EOL_API_VERSION",severity:"low",points:-5,title:"End-of-life API version",message:`API version ${t} is past its 12-month support window and 30-day extension grace period.`,location:{file:e,...n===void 0?{}:{line:n}},fix:{automated:!1,description:"Select a currently supported Shopify API version.",guide:"https://shopify.dev/docs/api/usage/versioning"}}}function Go(e){return e.replace(/\/\*[\s\S]*?\*\//g,t=>t.replace(/[^\n]/g," ")).replace(/\/\/[^\n]*/g,"")}function zo(e){return e.replace(/(["'])(?:\\.|(?!\1)[^\\\n])*\1|`(?:\\.|[^`\\])*`/g,t=>/^["']\d{4}-(?:01|04|07|10)["']$/.test(t)?t:t.replace(/[^\n]/g," "))}function Wo(e,t){return e.slice(0,t).split(`
|
|
2367
|
+
`).length}u();var Vo=new Set([".js",".jsx",".mjs",".cjs",".ts",".tsx",".mts",".cts"]),Bo=/\btext\/html\b/i,Qo=/\b(?:application\/liquid|text\/liquid|application\/x-liquid)\b/i,Vi=/(?:req|request)\.(?:query|params|body)|(?:searchParams|formData)\.get\s*\(|await\s+(?:req|request)\.(?:text|json|formData)\s*\(/,kt="[A-Za-z_$][\\w$]*";function Bi(e){let t=[];for(let n of e){if(!n.content||!Vo.has(n.ext))continue;let i=fa(n.content),s=Jo(i),r=Yo(i),o=Zo(i).find(a=>ia(a,s,r));o&&t.push({id:"APP_PROXY_LIQUID_INJECTION",severity:"high",points:-20,title:"App proxy Liquid injection risk",message:"Request-controlled data flows into an active Liquid or HTML app-proxy response.",location:{file:n.path,line:i.slice(0,o.index).split(`
|
|
2368
|
+
`).length},fix:{automated:!1,description:"Return inert JSON, or render only trusted templates with context-appropriate escaping.",guide:"https://shopify.dev/docs/apps/online-store/app-proxies"}})}return t}function Jo(e){let t=new Set,n=Ko(It(e));for(let i=0;i<5;i++){let s=!1;for(let r of n)t.has(r.name)||(Vi.test(r.expression)||[...t].some(o=>Yi(r.expression,o)))&&(t.add(r.name),s=!0);if(!s)break}return t}function Ko(e){let t=[],n=new RegExp(`\\b(?:const|let|var)\\s+(${kt})\\s*=\\s*`,"g"),i=n.exec(e);for(;i;){let o=Wi(e,n.lastIndex);o&&!/^(?:async\s*)?\([^)]*\)\s*=>/.test(o.text)&&!/^function\b/.test(o.text)&&t.push({name:i[1],expression:o.text}),i=n.exec(e)}let s=new RegExp(`(?:^|[;{}\\n])\\s*(${kt})\\s*=\\s*`,"g"),r=s.exec(e);for(;r;){let o=Wi(e,s.lastIndex);o&&!/^(?:async\s*)?\([^)]*\)\s*=>/.test(o.text)&&!/^function\b/.test(o.text)&&t.push({name:r[1],expression:o.text}),r=s.exec(e)}return t}function Yo(e){let t=new Set,n=/\bimport\s+([A-Za-z_$][\w$]*)\s+from\s+["']escape-html["']/g,i=n.exec(e);for(;i;){let s=i[1];Xo(e,s)||t.add(s),i=n.exec(e)}return t}function Xo(e,t){if(new RegExp(`\\b(?:function|const|let|var|class)\\s+${ie(t)}\\b`).test(e))return!0;let n=[/\bfunction(?:\s+[A-Za-z_$][\w$]*)?\s*\(([^)]*)\)/g,/\(([^)]*)\)\s*(?::[^=]+)?=>/g,/(?:^|[,{;]\s*)(?:async\s+)?(?:get\s+|set\s+)?[A-Za-z_$][\w$]*\s*\(([^)]*)\)\s*(?::[^={]+)?\s*\{/gm,/\bcatch\s*\(([^)]*)\)/g],i=new RegExp(`(?:^|[,\\s{])${ie(t)}(?=\\s*(?:[,}:=]|$))`);for(let s of n){let r=s.exec(e);for(;r;){if(i.test(r[1]??""))return!0;r=s.exec(e)}}return new RegExp(`\\b${ie(t)}\\s*(?::[^=,)]*)?\\s*=>`).test(e)}function Zo(e){let t=[],n=It(e),i=/\bnew\s+Response\s*\(/g,s=i.exec(n);for(;s;){let a=s.index+s[0].lastIndexOf("("),c=Ce(e,a+1),l=Qi(c?.args[1]);c&&l&&c.args[0]!==void 0&&t.push({index:s.index,expression:c.args[0],responseType:l}),i.lastIndex=c?.end??i.lastIndex,s=i.exec(n)}let r=new RegExp(`\\b(${kt})\\.(?:send|end|write)\\s*\\(`,"g"),o=r.exec(n);for(;o;){let a=o.index+o[0].lastIndexOf("("),c=Ce(e,a+1),l=ea(e,n,o[1],o.index);c&&l&&c.args[0]!==void 0&&t.push({index:o.index,expression:c.args[0],responseType:l}),r.lastIndex=c?.end??r.lastIndex,o=r.exec(n)}return t}function ea(e,t,n,i){let s=zi(t,i),r=new RegExp(`\\b${ie(n)}\\.(setHeader|set|header|type|contentType)\\s*\\(`,"g"),o,a=r.exec(t);for(;a&&a.index<i;){let c=a.index+a[0].lastIndexOf("("),l=Ce(e,c+1);if(l&&na(zi(t,a.index),s)){let d=ta(a[1],l.args);d&&(o=d)}r.lastIndex=l?.end??r.lastIndex,a=r.exec(t)}return o}function ta(e,t){let n=t[0]?.trim()??"";if((e==="set"||e==="header")&&t.length===1){let r=Qi(n);if(r)return r}let i=/^["']([^"']+)["']$/.exec(n)?.[1];if(!i)return;if(e==="type"||e==="contentType")return/^html$/i.test(i)?"html":_t(i);if(!/^content-type$/i.test(i))return;let s=/^["']([^"']+)["']$/.exec(t[1]?.trim()??"")?.[1];return s?_t(s):void 0}function zi(e,t){let n=[];for(let i=0;i<t;i++){let s=fe(e,i);if(s!==void 0){i=s-1;continue}e[i]==="{"?n.push(i):e[i]==="}"&&n.pop()}return n}function na(e,t){return e.length<=t.length&&e.every((n,i)=>n===t[i])}function Qi(e){if(!e)return;let t=/(?:^|[,{]\s*)["']?Content-Type["']?\s*:\s*["']([^"']+)["']/i.exec(e)?.[1]??/\[\s*["']Content-Type["']\s*,\s*["']([^"']+)["']\s*\]/i.exec(e)?.[1];return t?_t(t):void 0}function _t(e){if(Qo.test(e))return"liquid";if(Bo.test(e))return"html"}function ia(e,t,n){let i=e.responseType==="html"?sa(e.expression,n):e.expression,s=It(i);return Vi.test(s)?!0:[...t].some(r=>Yi(s,r))}function sa(e,t){if(!e.startsWith("`")||Ji(e,0)!==e.length)return e;let n=[...e],i={inTag:!1,closingTag:!1},s=1,r=!0;for(let o=1;o<e.length-1;o++){if(e[o]==="\\"){o++;continue}if(e[o]!=="$"||e[o+1]!=="{")continue;aa(i,e.slice(s,o));let a=Ki(e,o+2);if(a===void 0)return e;let c=o+2,l=a-1,d=e.slice(c,l);r&&oa(i)&&ra(d,t)?se(n,c,l):r=!1,o=l,s=a}return n.join("")}function ra(e,t){let n=ca(e.trim());for(let i of t){let s=new RegExp(`^${ie(i)}\\s*\\(`).exec(n);if(!s)continue;let r=Ce(n,s[0].lastIndexOf("(")+1);if(r&&r.end===n.length)return!0}return!1}function oa(e){return!e.inTag&&e.rawTag===void 0}function aa(e,t){let n=t.toLowerCase();for(let i=0;i<n.length;i++){let s=n[i];if(e.rawTag){let r=`</${e.rawTag}`;if(!n.startsWith(r,i))continue;e.inTag=!0,e.closingTag=!0,e.pendingTag=e.rawTag,e.rawTag=void 0,i+=r.length-1;continue}if(!e.inTag){if(s!=="<")continue;let r=/^<\s*(\/)?\s*([a-z][\w:-]*)/.exec(n.slice(i));if(!r)continue;e.inTag=!0,e.closingTag=!!r[1],e.pendingTag=r[2],i+=r[0].length-1;continue}if(e.quote){s===e.quote&&(e.quote=void 0);continue}if(s==='"'||s==="'"){e.quote=s;continue}s===">"&&(!e.closingTag&&(e.pendingTag==="script"||e.pendingTag==="style")&&(e.rawTag=e.pendingTag),e.inTag=!1,e.closingTag=!1,e.pendingTag=void 0,e.quote=void 0)}}function ca(e){let t=e;for(;t.startsWith("(")&&la(t,0,"(",")")===t.length-1;)t=t.slice(1,-1).trim();return t}function Ce(e,t){let n=[],i=t,s=[];for(let r=t;r<e.length;r++){let o=fe(e,r);if(o!==void 0){r=o-1;continue}let a=e[r];if(a==="("||a==="["||a==="{"){s.push(a);continue}if(a===")"||a==="]"||a==="}"){if(a===")"&&s.length===0)return n.push(e.slice(i,r).trim()),{args:n,end:r+1};s.pop();continue}a===","&&s.length===0&&(n.push(e.slice(i,r).trim()),i=r+1)}}function Wi(e,t){let n=[];for(let s=t;s<e.length;s++){let r=fe(e,s);if(r!==void 0){s=r-1;continue}let o=e[s];if(o==="("||o==="["||o==="{"){n.push(o);continue}if(o===")"||o==="]"||o==="}"){n.pop();continue}if(n.length===0&&o===";")return{text:e.slice(t,s).trim(),end:s+1};if(n.length===0&&o===`
|
|
2369
|
+
`&&!da(e,t,s))return{text:e.slice(t,s).trim(),end:s+1}}let i=e.slice(t).trim();return i?{text:i,end:e.length}:void 0}function da(e,t,n){let i=e.slice(t,n).trimEnd().at(-1),s=e.slice(n+1).trimStart().at(0);return!!(i&&"+-*/%&|?:.,".includes(i))||!!(s&&"+-*/%&|?:.,".includes(s))}function la(e,t,n,i){let s=[];for(let r=t;r<e.length;r++){let o=fe(e,r);if(o!==void 0){r=o-1;continue}if(e[r]===n)s.push(n);else if(e[r]===i&&(s.pop(),s.length===0))return r}}function fe(e,t){let n=e[t];if(n==='"'||n==="'")return St(e,t,n);if(n==="`")return Ji(e,t);if(n==="/"&&Tt(e,t))return Et(e,t)}function St(e,t,n){for(let i=t+1;i<e.length;i++){if(e[i]==="\\"){i++;continue}if(e[i]===n)return i+1}}function Ji(e,t){for(let n=t+1;n<e.length;n++){if(e[n]==="\\"){n++;continue}if(e[n]==="`")return n+1;if(e[n]!=="$"||e[n+1]!=="{")continue;let i=Ki(e,n+2);if(i===void 0)return;n=i-1}}function Ki(e,t){let n=1;for(let i=t;i<e.length;i++){let s=fe(e,i);if(s!==void 0){i=s-1;continue}if(e[i]==="{")n++;else if(e[i]==="}"&&(n--,n===0))return i+1}}function Tt(e,t){let n=pa(e,t);if(n===void 0||"([{:;,=!?&|+-*%^~<>".includes(n))return!0;let i=e.slice(0,t).trimEnd();return/\b(?:return|throw|case|delete|void|typeof|instanceof|in|of|yield|await)$/.test(i)}function Et(e,t){let n=!1;for(let i=t+1;i<e.length;i++){if(e[i]==="\\"){i++;continue}if(e[i]==="["){n=!0;continue}if(e[i]==="]"&&n){n=!1;continue}if(!(e[i]!=="/"||n)){for(i++;/[a-z]/i.test(e[i]??"");)i++;return i}}}function pa(e,t){for(let n=t-1;n>=0;n--)if(!/\s/.test(e[n]))return e[n]}function Yi(e,t){let n=new RegExp(`\\b${ie(t)}\\b`,"g"),i=n.exec(e);for(;i;){let s=e[i.index-1],r=e.slice(i.index+t.length);if(s!=="."&&s!=='"'&&s!=="'"&&s!=="`"&&!/^\s*:/.test(r))return!0;i=n.exec(e)}return!1}function It(e){let t=[...e];return ua(t,e,0,e.length),t.join("")}function ua(e,t,n,i){for(let s=n;s<i;s++){let r=t[s];if(r==='"'||r==="'"){let o=St(t,s,r);if(o===void 0)return;se(e,s,o),s=o-1;continue}if(r==="`"){let o=Xi(e,t,s);if(o===void 0)return;s=o-1;continue}if(r==="/"&&Tt(t,s)){let o=Et(t,s);if(o===void 0)return;se(e,s,o),s=o-1}}}function Xi(e,t,n){e[n]=" ";for(let i=n+1;i<t.length;i++){if(t[i]==="\\"){se(e,i,Math.min(i+2,t.length)),i++;continue}if(t[i]==="`")return e[i]=" ",i+1;if(t[i]==="$"&&t[i+1]==="{"){e[i]=" ",e[i+1]=" ";let s=ha(e,t,i+2);if(s===void 0)return;i=s-1;continue}t[i]!==`
|
|
2370
|
+
`&&(e[i]=" ")}}function ha(e,t,n){let i=1;for(let s=n;s<t.length;s++){let r=t[s];if(r==='"'||r==="'"){let o=St(t,s,r);if(o===void 0)return;se(e,s,o),s=o-1;continue}if(r==="`"){let o=Xi(e,t,s);if(o===void 0)return;s=o-1;continue}if(r==="/"&&Tt(t,s)){let o=Et(t,s);if(o===void 0)return;se(e,s,o),s=o-1;continue}if(r==="{")i++;else if(r==="}"&&(i--,i===0))return e[s]=" ",s+1}}function se(e,t,n){for(let i=t;i<n;i++)e[i]!==`
|
|
2371
|
+
`&&(e[i]=" ")}function fa(e){let t=[...e],n;for(let i=0;i<t.length;i++){let s=t[i];if(n){s==="\\"?i++:s===n&&(n=void 0);continue}if(s==='"'||s==="'"||s==="`"){n=s;continue}if(s==="/"&&t[i+1]==="/")for(;i<t.length&&t[i]!==`
|
|
2361
2372
|
`;)t[i]=" ",i++;else if(s==="/"&&t[i+1]==="*"){for(t[i]=" ",t[i+1]=" ",i+=2;i<t.length&&!(t[i]==="*"&&t[i+1]==="/");)t[i]!==`
|
|
2362
|
-
`&&(t[i]=" "),i++;i<t.length&&(t[i]=" ",t[i+1]=" ",i++)}}return t.join("")}function ie(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}u();var
|
|
2363
|
-
`);if(/\bexpiringOfflineAccessTokens\s*:\s*false\b/.test(i)){let s=n.find(o=>/\bexpiringOfflineAccessTokens\s*:\s*false\b/.test(Pe(o.content))),r=Pe(s.content).search(/\bexpiringOfflineAccessTokens\s*:\s*false\b/);return{issues:[
|
|
2364
|
-
`).length}function
|
|
2365
|
-
`).length},fix:{automated:!1,description:"Restrict frame-ancestors to Shopify Admin and the authenticated shop origin.",guide:"https://shopify.dev/docs/apps/build/security/set-up-iframe-protection"}})}return t}function
|
|
2373
|
+
`&&(t[i]=" "),i++;i<t.length&&(t[i]=" ",t[i+1]=" ",i++)}}return t.join("")}function ie(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}u();var ma=new Set([".js",".jsx",".mjs",".cjs",".ts",".tsx",".mts",".cts"]);function Zi(e){let t=e.sourceFiles.filter(s=>s.content!==void 0).map(s=>s.path),n=e.sourceFiles.filter(s=>s.content&&ma.has(s.ext)&&/\bshopifyApp\s*\(/.test(Pe(s.content)));if(n.length===0)return{issues:[],inspectedFiles:t,unresolvedReason:"No React Router shopifyApp(...) setup was found, so offline-token expiry could not be verified."};let i=n.map(s=>Pe(s.content)).join(`
|
|
2374
|
+
`);if(/\bexpiringOfflineAccessTokens\s*:\s*false\b/.test(i)){let s=n.find(o=>/\bexpiringOfflineAccessTokens\s*:\s*false\b/.test(Pe(o.content))),r=Pe(s.content).search(/\bexpiringOfflineAccessTokens\s*:\s*false\b/);return{issues:[va(s.path,ya(s.content,r))],inspectedFiles:t}}return/\bexpiringOfflineAccessTokens\s*:\s*true\b/.test(i)?/\bsessionStorage\s*:/.test(i)?ga(e,i)?{issues:[],inspectedFiles:t}:{issues:[],inspectedFiles:t,unresolvedReason:"Expiring offline tokens are enabled, but session storage compatibility with expires/refreshToken/refreshTokenExpires metadata could not be verified."}:{issues:[],inspectedFiles:t,unresolvedReason:"Expiring offline tokens are enabled, but the configured session storage could not be identified."}:{issues:[],inspectedFiles:t,unresolvedReason:"expiringOfflineAccessTokens: true was not found in the React Router setup. The value may be absent or computed and needs review."}}function ga(e,t){return/\bnew\s+(?:Memory|Redis|MongoDB|SQLite)SessionStorage\s*\(/.test(t)?!0:/\bnew\s+PrismaSessionStorage\s*\(/.test(t)?e.sourceFiles.some(n=>{if(n.ext!==".prisma"||!n.content)return!1;let i=/\bmodel\s+Session\s*\{([^}]*)\}/.exec(n.content)?.[1];return!!(i&&/\bexpires\s+DateTime\?/.test(i)&&/\brefreshToken\s+String\?/.test(i)&&/\brefreshTokenExpires\s+DateTime\?/.test(i))}):e.sourceFiles.some(n=>n.content&&/\brefreshToken\b/.test(n.content)&&/\brefreshTokenExpires\b/.test(n.content)&&/\bexpires\b/.test(n.content)&&/\b(?:storeSession|sessionStorage|SessionStorage)\b/.test(n.content))}function Pe(e){return e.replace(/\/\*[\s\S]*?\*\//g,t=>t.replace(/[^\n]/g," ")).replace(/\/\/[^\n]*/g,"").replace(/(["'`])(?:\\.|(?!\1)[^\\\n])*\1/g,t=>t.replace(/[^\n]/g," "))}function ya(e,t){return e.slice(0,Math.max(0,t)).split(`
|
|
2375
|
+
`).length}function va(e,t){return{id:"EXPIRING_OFFLINE_TOKEN",severity:"medium",points:-10,title:"Expiring offline access tokens explicitly disabled",message:"The React Router Shopify app setup explicitly sets expiringOfflineAccessTokens to false.",location:{file:e,line:t},fix:{automated:!1,description:"Enable expiring offline access tokens and ensure session storage persists refresh metadata.",guide:"https://shopify.dev/docs/apps/build/authentication-authorization/access-token-types/online-access-tokens"}}}u();var ba=new Set([".js",".jsx",".mjs",".cjs",".ts",".tsx",".mts",".cts"]),wa="content-security-policy",xa=/\b(?:headers|response\.headers|res)\.(?:set|append|setHeader)\s*\(\s*$/i,ka=/^https?:\/\/\*(?::(?:\*|\d+))?(?:\/.*)?$/i,_a=/^(?:https?:\/\/)?\*\.myshopify\.com(?::(?:\*|\d+))?(?:\/.*)?$/i,Sa=/^(?:http|https):$/i;function ns(e){let t=[];for(let n of e){if(!n.content||!ba.has(n.ext))continue;let i=Pa(n.content);for(let s of Ta(i))Aa(s.value)&&t.push({id:"STATIC_FRAME_ANCESTORS",severity:"high",points:-12,title:"Embedded app frame-ancestors uses a wildcard",message:"A literal frame-ancestors directive allows arbitrary or wildcard embedding origins.",location:{file:n.path,line:i.slice(0,s.index).split(`
|
|
2376
|
+
`).length},fix:{automated:!1,description:"Restrict frame-ancestors to Shopify Admin and the authenticated shop origin.",guide:"https://shopify.dev/docs/apps/build/security/set-up-iframe-protection"}})}return t}function Ta(e){let t=[];for(let n=0;n<e.length;n++){let i=ge(e,n);if(i){if(i.static&&i.value.toLowerCase()===wa){let s=Ea(e,n,i.end);s!==void 0&&t.push({index:n,value:s})}n=i.end-1}}return t}function Ea(e,t,n){let i=ss(e,n);if(e[i]===":"){let s=es(e,i+1,new Set([",","}"]));return s?me(s.text):void 0}if(e[i]===","&&Ia(e,t)){let s=es(e,i+1,new Set([",",")"]));return s?me(s.text):void 0}}function Ia(e,t){return xa.test(e.slice(Math.max(0,t-100),t))}function Aa(e){return e.split(";").some(t=>{let[n,...i]=t.trim().split(/\s+/);return n?.toLowerCase()==="frame-ancestors"&&i.some(Ra)})}function Ra(e){return e==="*"||Sa.test(e)||ka.test(e)||_a.test(e)}function me(e){let t=Ca(e.trim());if(!t)return;let n=ge(t,0);if(n&&n.end===t.length&&n.static)return n.value;let i=ts(t,"+");if(i.length>1){let s=i.map(r=>me(r));if(s.every(r=>r!==void 0))return s.join("")}if(t.startsWith("[")){let s=is(t,0,"[","]");if(s!==void 0){let r=/^\.join\s*\((.*)\)$/.exec(t.slice(s+1).trim());if(r){let o=r[1].trim()?me(r[1]):",";if(o===void 0)return;let a=ts(t.slice(1,s),",").map(c=>c.trim()).filter(Boolean).map(c=>me(c));if(a.every(c=>c!==void 0))return a.join(o)}}}}function Ca(e){let t=e;for(;t.startsWith("(")&&is(t,0,"(",")")===t.length-1;)t=t.slice(1,-1).trim();return t}function es(e,t,n){let i=ss(e,t),s=0;for(let o=i;o<e.length;o++){let a=ge(e,o);if(a){o=a.end-1;continue}let c=e[o];if(c==="("||c==="["||c==="{")s++;else if(c===")"||c==="]"||c==="}"){if(s===0&&n.has(c))return{text:e.slice(i,o).trim(),end:o};s--}else if(s===0&&n.has(c))return{text:e.slice(i,o).trim(),end:o}}let r=e.slice(i).trim();return r?{text:r,end:e.length}:void 0}function ts(e,t){let n=[],i=0,s=0;for(let r=0;r<e.length;r++){let o=ge(e,r);if(o){r=o.end-1;continue}let a=e[r];a==="("||a==="["||a==="{"?s++:a===")"||a==="]"||a==="}"?s--:s===0&&a===t&&(n.push(e.slice(i,r)),i=r+1)}return n.push(e.slice(i)),n}function is(e,t,n,i){let s=0;for(let r=t;r<e.length;r++){let o=ge(e,r);if(o){r=o.end-1;continue}if(e[r]===n)s++;else if(e[r]===i&&(s--,s===0))return r}}function ge(e,t){let n=e[t];if(n!=='"'&&n!=="'"&&n!=="`")return;let i=!0;for(let s=t+1;s<e.length;s++){let r=e[s];if(r==="\\"){s++;continue}if(n==="`"&&r==="$"&&e[s+1]==="{"&&(i=!1),r===n)return{end:s+1,value:e.slice(t+1,s),static:i}}return{end:e.length,value:"",static:!1}}function ss(e,t){let n=t;for(;/\s/.test(e[n]??"");)n++;return n}function Pa(e){let t=[...e],n;for(let i=0;i<t.length;i++){let s=t[i];if(n){s==="\\"?i++:s===n&&(n=void 0);continue}if(s==='"'||s==="'"||s==="`"){n=s;continue}if(s==="/"&&t[i+1]==="/")for(;i<t.length&&t[i]!==`
|
|
2366
2377
|
`;)t[i]=" ",i++;else if(s==="/"&&t[i+1]==="*"){for(t[i]=" ",t[i+1]=" ",i+=2;i<t.length&&!(t[i]==="*"&&t[i+1]==="/");)t[i]!==`
|
|
2367
|
-
`&&(t[i]=" "),i++;i<t.length&&(t[i]=" ",t[i+1]=" ",i++)}}return t.join("")}u();u();function J(e,t){return e<t?-1:e>t?1:0}function Le(e,t){return J(e.location.file,t.location.file)||ya(e.location.line,t.location.line)}function ya(e,t){return e===t?0:e===void 0?-1:t===void 0?1:e-t}var Qi=e=>({file:g(e.file.replace(/\\/g,"/")),...e.line===void 0?{}:{line:e.line},...e.column===void 0?{}:{column:e.column}}),va=e=>(e??[]).map(t=>({location:Qi(t.location),...t.quote===void 0?{}:{quote:g(t.quote)}}));function Tt(e){return{...e,id:g(e.id),title:g(e.title),message:g(e.message),location:Qi(e.location),...e.snippet===void 0?{}:{snippet:g(e.snippet)},...e.detection_evidence===void 0?{}:{detection_evidence:e.detection_evidence.map(g)},...e.evidence===void 0?{}:{evidence:va(e.evidence)},fix:{...e.fix,description:g(e.fix.description),...e.fix.guide?{guide:g(e.fix.guide)}:{}}}}function ba(e){let t=Tt(e);return{location:t.location,message:t.message,evidence:t.evidence??[],...t.snippet===void 0?{}:{snippet:t.snippet},fix:t.fix}}function wa(e,t){return Le(e,t)||J(e.message,t.message)}function xa(e){return e==="unsupported_framework"?"unresolved":e}function ka(e){let t=W.find(n=>n.id===e.id);if(!t)throw new Error(`Deterministic check has no catalog entry: ${e.id}`);return{title:t.title,severity:t.severity,description:t.description,...t.guide?{guide:t.guide}:{},docs_url:t.docsUrl,current_version:e.version}}function _a(e,t){return{id:g(e.id),version:e.version,status:xa(e.status),...e.reason?{reason:{...e.reason,message:g(e.reason.message)}}:{},analysis_mode:e.analysis_mode,snapshot:ka(e),findings:t.map(ba).sort(wa)}}function Sa(e){let t=new Set(e.scan.checks_executed.map(i=>i.id)),n=e.issues.find(i=>!t.has(i.id));if(n)throw new Error(`Finding for a check that was not executed: ${n.id}`);return Map.groupBy(e.issues,i=>i.id)}function Ji(e,t){let n=Sa(e),i=e.scan.checks_executed.map(s=>_a(s,n.get(s.id)??[])).sort((s,r)=>J(s.id,r.id));return{schema_version:L,source:"deterministic",engine:{name:N,version:g(t.engineVersion??e.version),ruleset:g(t.ruleset??`app-security-rules@${e.version}`)},generated_at:t.generatedAt??new Date().toISOString(),detection:{...e.detection,languages:e.detection.languages.map(s=>({...s,files:s.files.map(r=>g(r))}))},coverage:{files_scanned:e.scan.files_scanned,files_skipped:(e.scan.files_skipped??[]).map(s=>({...s,path:g(s.path),...s.detail?{detail:g(s.detail)}:{}})),gaps:e.scan.coverage_gaps.map(s=>({...s,message:g(s.message),...s.file?{file:g(s.file)}:{}})),scope:we(t.scope),scan_directories:t.scanDirectories.map(s=>({...s,directory:g(s.directory)}))},checks:i}}var K=[".js",".jsx",".mjs",".cjs",".ts",".tsx",".mts",".cts"],Et=(e,t=1)=>({id:e.id,version:t,lifecycle:"active",analysisMode:"structured_config",target:"config",requires:e.requires,runner:n=>e.check(n)}),$=(e,t,n="source",i=1)=>({id:e,version:i,lifecycle:"active",analysisMode:"regex",target:n,extensions:K,runner:t}),Ta=[Et(Ri),{id:"MISSING_DEPENDENCY_SECURITY_AUTOMATION",version:1,lifecycle:"active",analysisMode:"structured_config",target:"dependency_automation",runner:e=>Xn(e)},{id:"EOL_API_VERSION",version:1,lifecycle:"active",analysisMode:"regex",target:"config_and_source",extensions:K,runner:e=>Pi(e)},{...$("EXPIRING_OFFLINE_TOKEN",e=>Hi(e)),extensions:[...K,".prisma"]},{...$("UNAUTHENTICATED_ENDPOINT",e=>mi(e.sourceFiles),"source",2),requires:"has_backend"},$("REQUEST_CONTROLLED_ADMIN_CONTEXT",e=>gi(e.sourceFiles),"source",3),{...Et(lt),target:"config_and_source",analysisMode:"regex",extensions:K,runner:e=>[...lt.check(e),...Ai(e.sourceFiles)]},Et(hi,2),{id:"COMMITTED_SECRET",version:3,lifecycle:"active",analysisMode:"regex",target:"secrets",runner:e=>Cn(e.sensitiveFiles,e.appRoot,e.gitIgnoreListing)},$("CREDENTIAL_LOG_LEAKAGE",e=>wi(e.sourceFiles)),$("CREDENTIAL_BROWSER_LEAKAGE",e=>xi(e.sourceFiles)),{id:"LIQUID_UNSAFE_RENDER",version:1,lifecycle:"active",analysisMode:"ast",target:"theme",requires:"theme_app_extension",extensions:[".liquid",".html"],runner:e=>Ia(e,"LIQUID_UNSAFE_RENDER")},{...$("UNSAFE_INNERHTML",Ea,"source_and_theme",2),analysisMode:"regex",extensions:[...K,".liquid",".html"]},{...$("APP_PROXY_LIQUID_INJECTION",e=>Oi(e.sourceFiles),"source",2),requires:"app_proxy"},{...$("STATIC_FRAME_ANCESTORS",e=>Wi(e.sourceFiles),"app_source"),requires:"embedded_app"}];function Ea(e){let t=[],n=[];if(e.detection.framework==="react_router"){let i=At(e).filter(r=>r.content!==void 0&&K.includes(r.ext)),s=gt(i);t.push(...s),n.push({id:"react-router-js-regex",analysisMode:"regex",status:"executed",inspectedFiles:i.map(r=>r.path),findings:s.length})}if(e.capabilities.theme_app_extension){let i=re(e),s=new Set(i.map(h=>h.path));e.detection.framework!=="react_router"&&e.sourceCandidates.some(h=>!s.has(h.path))&&n.push({id:"app-source-unsupported",analysisMode:"regex",status:"unsupported_framework",inspectedFiles:[],findings:0,reason:{code:"unsupported_framework",message:"The non-theme app source framework is not supported by deterministic analysis."}});let r=i.filter(h=>h.content!==void 0&&K.includes(h.ext)),o=gt(r);t.push(...o),n.push(r.length>0?{id:"theme-js-regex",analysisMode:"regex",status:"executed",inspectedFiles:r.map(h=>h.path),findings:o.length}:{id:"theme-js-regex",analysisMode:"regex",status:"not_applicable",inspectedFiles:[],findings:0,reason:{code:"no_relevant_files",message:"The theme app extensions contain no JavaScript files."}});let a=i.filter(h=>h.content!==void 0&&(h.ext===".liquid"||h.ext===".html")),d=vt(a),c=d.issues.filter(h=>h.id==="UNSAFE_INNERHTML");t.push(...c);let p="executed",m;a.length===0?(p="not_applicable",m={code:"no_relevant_files",message:"The theme app extensions contain no Liquid files."}):d.parserFailures.length>0&&(p="unresolved",m={code:"parser_unavailable",message:`Liquid parser failed for: ${d.parserFailures.join(", ")}`}),n.push({id:"theme-liquid-ast",analysisMode:"ast",status:p,inspectedFiles:a.map(h=>h.path),findings:c.length,...m?{reason:m}:{}})}return{issues:t,inspectedFiles:[...new Set(n.flatMap(i=>i.inspectedFiles))],...n.some(i=>i.status==="unresolved")?{unresolvedReason:n.filter(i=>i.status==="unresolved").map(i=>i.reason?.message).filter(Boolean).join("; ")}:{}}}function Ia(e,t){let n=vt(re(e).filter(s=>s.content!==void 0)),i=n.parserFailures;return{issues:n.issues.filter(s=>s.id===t),...i.length>0?{unresolvedReason:`Liquid parser failed for: ${i.join(", ")}`}:{}}}var De=Aa(Ta);Ra([...De.values()]);var tp=[...De.values()].map(({id:e,version:t,requires:n})=>({id:e,version:t,requires:n}));function Aa(e){let t=new Map;for(let n of e){if(t.has(n.id))throw new Error(`Duplicate deterministic stable ID: ${n.id}`);t.set(n.id,n)}return t}function Ra(e){let t=new Set(W.map(n=>n.id));for(let n of e){if(!t.has(n.id))throw new Error(`Orphan deterministic runner: ${n.id}`);if(n.lifecycle==="active"&&!n.runner)throw new Error(`Active deterministic check has no runner: ${n.id}`);if(n.lifecycle!=="active"&&n.runner)throw new Error(`A non-active deterministic check can't have a runner: ${n.id}`)}}function re(e){return e.extensions.filter(t=>t.type==="theme").flatMap(t=>t.files)}function It(e){let t=new Set(re(e).map(n=>n.path));return e.sourceFiles.filter(n=>!t.has(n.path))}function At(e){return It(e)}function Ki(e,t){let n=t.appTomls.map(r=>w(t.appRoot,r.path).replace(/\\/g,"/"));if(e.target==="config")return n;if(e.target==="dependency_automation")return[...t.manifests.map(r=>r.path),...t.dependencyAutomation.files.filter(r=>r.content!==void 0).map(r=>r.path)];if(e.target==="secrets")return t.sensitiveFiles.filter(r=>r.content!==void 0).map(r=>r.path);let i=e.target==="app_source"?It(t):At(t);e.target==="theme"?i=re(t):e.target==="source_and_theme"&&(i=[...i,...re(t)]);let s=i.filter(r=>r.content!==void 0&&(!e.extensions||e.extensions.includes(r.ext))).map(r=>r.path);return e.target==="config_and_source"?[...n,...s]:s}function Ca(e,t){return t.sourceCandidates.find(n=>n.path===e)?.language}function Pa(e,t){let n=Ki(e,t),i=new Set(re(t).map(a=>a.path)),s=t.sourceCandidates.filter(a=>!i.has(a.path)),r=t.detection.framework==="react_router",o=t.capabilities.theme_app_extension;return e.target==="dependency_automation"&&!t.manifests.some(Yi)?{status:"not_applicable",required:!1,applicable:!1,reason:{code:"no_relevant_files",message:"No supported package manifest declares dependencies."}}:e.target==="source"&&!r?s.length>0?{status:"unsupported_framework",required:!0,applicable:!0,reason:{code:"unsupported_framework",message:`${e.id} applies to app source, but deterministic analysis requires @shopify/shopify-app-react-router and its conventional app structure.`}}:{status:"not_applicable",required:!1,applicable:!1,reason:{code:"no_relevant_files",message:"No React Router app source applies to this check."}}:e.target==="source_and_theme"&&!r&&!o?s.length>0?{status:"unsupported_framework",required:!0,applicable:!0,reason:{code:"unsupported_framework",message:`${e.id} applies to app source, but its framework is not supported by deterministic analysis.`}}:{status:"not_applicable",required:!1,applicable:!1,reason:{code:"no_relevant_files",message:"No supported app or theme source applies to this check."}}:e.target==="source_and_theme"&&!r&&s.length>0?n.length>0?{status:"unresolved",required:!0,applicable:!0,reason:{code:"unsupported_framework",message:`${e.id} inspected theme extensions, but the non-theme app source framework is unsupported.`}}:{status:"unsupported_framework",required:!0,applicable:!0,reason:{code:"unsupported_framework",message:`${e.id} applies to app source, but its framework is unsupported.`}}:e.target==="theme"&&!o?{status:"not_applicable",required:!1,applicable:!1,reason:{code:"capability_absent",message:"No theme app extension was detected."}}:e.target==="config_and_source"&&!r&&s.length>0&&t.appTomls.length>0?{status:"unresolved",required:!0,applicable:!0,reason:{code:"unsupported_framework",message:`Configuration can be inspected, but ${e.id} source analysis requires @shopify/shopify-app-react-router.`}}:e.requires&&!t.capabilities[e.requires]?{status:"not_applicable",required:!1,applicable:!1,reason:{code:"capability_absent",message:`Capability ${e.requires} was not detected.`}}:(e.target==="config"||e.target==="config_and_source")&&t.appTomls.length===0?{status:"unresolved",required:!0,applicable:!0,reason:{code:"parser_unavailable",message:"No readable Shopify app configuration was available."}}:["source","app_source","theme","secrets","source_and_theme"].includes(e.target)&&n.length===0?{status:"not_applicable",required:!1,applicable:!1,reason:{code:"no_relevant_files",message:`No ${e.target.replaceAll("_"," ")} files apply to this check.`}}:{status:"executed",required:!0,applicable:!0}}function Yi(e){return Object.keys(e.dependencies).length>0||Object.keys(e.devDependencies??{}).length>0}function La(e,t,n){let i=t.extensions.filter(c=>c.type==="theme").map(c=>c.path.replace(/shopify\.extension\.toml$/,"")),s=c=>i.some(p=>c.startsWith(p))||c.endsWith("shopify.extension.toml"),r=c=>!s(c)&&!!e.extensions?.some(p=>c.endsWith(p)),o=c=>/^shopify\.app(?:\.[^/]+)?\.toml$/.test(c),a=c=>/(^|\/)package\.json$/.test(c)||t.dependencyAutomation.files.some(p=>p.path===c),d=c=>!c.detail?.includes("could not be parsed")&&(t.sourceCandidates.some(p=>p.path===c.path)||t.sensitiveFiles.some(p=>p.path===c.path)||/(^|\/)(?:\.env(?:\.[^/]+)?|secrets\.json|credentials\.json)$/.test(c.path));return n.filter(c=>e.target==="config"?o(c.path):e.target==="dependency_automation"?a(c.path):e.target==="config_and_source"?o(c.path)||r(c.path):e.target==="source"||e.target==="app_source"?r(c.path):e.target==="theme"?s(c.path):e.target==="source_and_theme"?r(c.path)||s(c.path):d(c))}function Da(e,t){return{code:t.some(i=>!!i.detail?.includes("could not be parsed")||i.path.endsWith("shopify.extension.toml")||i.path.endsWith("package.json"))?"parser_unavailable":"input_rejected",message:`${e.id} could not inspect required input: ${t.map(i=>`${i.path} (${i.reason.replaceAll("_"," ")})`).join(", ")}.`}}function Na(e,t){return e.target==="app_source"?{...t,sourceFiles:It(t)}:e.target==="source"||e.target==="config_and_source"?{...t,sourceFiles:At(t)}:t}function qa(e){return Array.isArray(e)?{issues:e}:e}function Xi({appDirectory:e,scanDirectories:t,requestedScanDirectories:n,appConfigFilePath:i},s){return ti({appDirectory:e,scanDirectories:t,requestedScanDirectories:n,selectedAppConfigFilePath:i,rules:zn({excludePatterns:s.excludePatterns??[],noGitIgnore:s.noGitIgnore??!1})})}async function Oa(e,t={}){ot({appDirectory:e.appDirectory,scanDirectories:e.scanDirectories,explicitInputs:new Set});let{paths:n,ignoredScanDirectories:i,otherAppDirectories:s}=await Xi(e,t);return{paths:n,ignoredScanDirectories:i,otherAppDirectories:s}}async function Zi(e,t={}){let{appDirectory:n,scanDirectories:i,appConfigFilePath:s}=e;ot({appDirectory:n,scanDirectories:i,explicitInputs:new Set(s?[s]:[])});let r=s?P(w(n,s)):void 0,o=s?ei(s,n):null,a=o?[o]:[],{paths:d,ignoredScanDirectories:c,otherAppDirectories:p,listingStatus:m}=await Xi(e,t),h=ni(n,d),b=ri(d),k=dt(n,d),_=oi(n,d,r),x=ci(d),R=di(n,x),xs=R.some(Yi)?ai(n,d):{files:[]},qt=li(o,h,k,a),oe=pi(R,h,b),ae={appRoot:n,appToml:o,appTomls:a,extensions:h,sourceFiles:k,manifests:R,dependencyAutomation:xs,sensitiveFiles:_,capabilities:qt,detection:oe,sourceCandidates:b,gitIgnoreListing:m},H=[],X=[],Ot=new Set;for(let f of De.values()){let S=Pa(f,ae),Me=S.status==="unsupported_framework"?[]:Ki(f,ae),Ts=H.length,Ue=La(f,ae,at()),Es=f.target==="dependency_automation"&&Ue.length>0;if((S.status==="executed"||S.status==="unresolved")&&f.runner&&!Es){let C=qa(await f.runner(Na(f,ae)));(f.target!=="dependency_automation"||!C.unresolvedReason)&&H.push(...C.issues),C.inspectedFiles&&(Me=C.inspectedFiles),C.unresolvedReason&&(S={status:"unresolved",required:!0,applicable:!0,reason:{code:C.unresolvedReasonCode??"parser_unavailable",message:C.unresolvedReason}})}S.status!=="unsupported_framework"&&Ue.length>0&&(S={status:"unresolved",required:!0,applicable:!0,reason:Da(f,Ue)}),S.required&&Ot.add(f.id);let Is=[...new Set(Me.map(C=>Ca(C,ae)).filter(C=>!!C))].sort();X.push({id:f.id,version:f.version,kind:"deterministic",status:S.status,applicable:S.applicable,languages:Is,framework:oe.framework,surface:oe.surface,inspected_files:Me,findings:H.length-Ts,analysis_mode:f.analysisMode,...S.reason?{reason:S.reason}:{}})}let ks=new Map([...De.values()].map(f=>[f.id,f.version]));H=H.map(f=>Tt({...f,rule_version:ks.get(f.id)??1}));for(let f of X)f.findings=H.filter(S=>S.id===f.id).length;let Ft=[...new Map(at().map(f=>{let S={...f,path:g(f.path),...f.detail?{detail:g(f.detail)}:{}};return[`${S.path}|${S.reason}`,S]})).values()],_s=[...Ft.map(f=>({code:"skipped_file",file:f.path,message:`The file was not inspected because it was ${f.reason.replaceAll("_"," ")}.`})),...oe.languages.filter(f=>f.support==="unsupported").map(f=>({code:"unsupported_language",message:`${f.name} source is not supported by deterministic analysis.`})),...X.flatMap(f=>!Ot.has(f.id)||f.status!=="unsupported_framework"&&f.status!=="unresolved"?[]:[{code:f.status==="unsupported_framework"?"unsupported_framework":"unresolved_check",check_id:f.id,message:f.reason?.message??`${f.id} could not execute.`}])],Mt=X.filter(f=>f.status==="executed").length,Ss=ui(k.filter(f=>f.content!==void 0).length,Mt,X.length-Mt,Ft,X,_s);return{version:V(),timestamp:new Date().toISOString(),app:{name:g(String(o?.raw.name??"Unknown")),type:"public"},capabilities:qt,detection:oe,scan:Ss,issues:H,ignoredScanDirectories:c,otherAppDirectories:p}}function Fa(){return xn}function Ma({appDirectory:e,scanDirectories:t,appConfigDirectories:n=[]}){return t.map(i=>({directory:P(w(e,i))||".",origin:Ua(i,e,n)}))}function Ua(e,t,n){return e===t?"app_directory":n.includes(e)?"app_config_directory":"include_dir"}async function $a(e,t={}){let{ignoredScanDirectories:n,otherAppDirectories:i,...s}=await Zi(e,t),r=V(),o=Ji(s,{engineVersion:r,scope:{include_dirs:[...t.includeDirs??[]],excludes:[...t.excludePatterns??[]],no_git_ignore:t.noGitIgnore??!1},scanDirectories:Ma(e)});return{scan:s,ignoredScanDirectories:n,otherAppDirectories:i,deterministicFindings:o,agentChecks:Gn(r),engine:o.engine}}u();var es={deterministic:0,agent:1};function ja(e){return[...Wa(e)].map(([t,n])=>Ba(t,n)).sort((t,n)=>G[n.severity]-G[t.severity]||J(t.id,n.id))}function Rt(e){return e.findings.filter(t=>t.disposition==="active")}function Ha(e){return e.status==="executed"&&Rt(e).length===0}function Ga(e){return e.applied_precedence!==e.precedence}function ss(e,t){return t==="agent"&&e.suppression!==void 0}function za(e){let t={withFindings:0,passed:0,notApplicable:0,unresolved:0,suppressed:0,superseded:0};for(let n of e){Rt(n).length>0?t.withFindings+=1:n.status==="executed"?t.passed+=1:n.status==="not_applicable"?t.notApplicable+=1:t.unresolved+=1;for(let i of n.findings)i.disposition==="suppressed"&&(t.suppressed+=1),i.disposition==="superseded"&&(t.superseded+=1)}return t}function Wa({deterministic:e,agent:t}){let n=new Map;if(e)for(let i of e.checks)n.set(i.id,{deterministic:ts(i,e),agent:void 0,describingCheck:i});if(t)for(let i of t.checks)n.set(i.id,{deterministic:n.get(i.id)?.deterministic,agent:ts(i,t),describingCheck:i});return n}function Va(e,t){return t.generated_at}function ts(e,t){return{check:e,generatedAt:Va(e,t)}}function Ba(e,{deterministic:t,agent:n,describingCheck:i}){let s=n?.check.snapshot.precedence??"union",{snapshot:r}=i,o=Qa(s,t,n),a=o==="prefer-agent"&&n!==void 0,d=[...(t?.check.findings??[]).map(c=>ns(c,"deterministic",a?"superseded":"active")),...(n?.check.findings??[]).map(c=>ns(c,"agent",ss(c,"agent")?"suppressed":"active"))].sort(Ya);return{id:e,title:r.title,severity:r.severity,description:r.description,...r.guide?{guide:r.guide}:{},...r.docs_url?{docs_url:r.docs_url}:{},precedence:s,applied_precedence:o,status:a?n.check.status:Ka([t,n].flatMap(c=>c?[c.check.status]:[])),by_source:{deterministic:t?is(t):null,agent:n?is(n):null},findings:d}}function Qa(e,t,n){return e!=="prefer-agent"||n===void 0?"union":Ja(t,n)?"prefer-agent":"union"}function Ja(e,t){if(e===void 0)return!0;let n=Date.parse(t.generatedAt),i=Date.parse(e.generatedAt);return Number.isNaN(n)||Number.isNaN(i)?!1:n>=i}function Ka(e){return e.includes("executed")?"executed":e.includes("unresolved")?"unresolved":"not_applicable"}function ns(e,t,n){return{...e,source:t,disposition:n}}function is({check:e,generatedAt:t}){return{version:e.version,status:e.status,...e.reason?{reason:e.reason}:{},...e.analysis_mode?{analysis_mode:e.analysis_mode}:{},generated_at:t}}function Ya(e,t){return Le(e,t)||es[e.source]-es[t.source]||J(e.message,t.message)}u();u();u();var rs=l.enum(["high","medium","low"]),os=l.enum(["executed","not_applicable","unresolved"]),as=l.enum(["union","prefer-agent"]),cs=l.object({file:l.string(),line:l.number().optional(),column:l.number().optional()}),Xa=l.object({automated:l.boolean(),guide:l.string().optional(),description:l.string()}),Za=l.object({title:l.string(),severity:rs,description:l.string(),guide:l.string().optional(),docs_url:l.string().optional(),current_version:l.number(),precedence:as.optional()}),ec=l.object({location:cs,quote:l.string().optional()}),ds=l.object({location:cs,message:l.string(),evidence:l.array(ec),snippet:l.string().optional(),fix:Xa.optional(),confidence:l.enum(["high","medium","low"]).optional(),reasoning:l.string().optional(),suppression:l.object({justification:l.string()}).optional()}),ls=l.object({id:l.string(),version:l.number(),status:os,reason:l.object({code:l.string(),message:l.string()}).optional(),analysis_mode:l.enum(["regex","structured_config","ast"]).optional(),snapshot:Za,findings:l.array(ds)}),tc=l.array(ls).superRefine((e,t)=>{let n=new Set;e.forEach((i,s)=>{n.has(i.id)&&t.addIssue({code:l.ZodIssueCode.custom,path:[s,"id"],message:`duplicate check id ${i.id}`}),n.add(i.id)})}),ps=l.object({framework:l.enum(["react_router","none","unknown","mixed"]),surface:l.enum(["react_router","theme_app_extension","config_only","unknown","mixed"]),languages:l.array(l.object({name:l.string(),support:l.enum(["supported","unsupported"]),files:l.array(l.string())}))}),us=()=>l.object({include_dirs:l.array(l.string()),excludes:l.array(l.string()),no_git_ignore:l.boolean()}),hs=l.object({files_scanned:l.number(),files_skipped:l.array(l.object({path:l.string(),reason:l.enum(["too_large","unreadable"]),size_bytes:l.number().optional(),detail:l.string().optional()})),gaps:l.array(l.object({code:l.enum(["skipped_file","unsupported_framework","unsupported_language","unresolved_check"]),message:l.string(),check_id:l.string().optional(),file:l.string().optional()})),scope:us(),scan_directories:l.array(l.object({directory:l.string(),origin:l.enum(["app_directory","include_dir","app_config_directory"])}))}),fs={schema_version:l.literal(L),generated_at:l.string(),checks:tc},ms=l.object({...fs,source:l.literal("deterministic"),engine:l.object({name:l.literal(N),version:l.string(),ruleset:l.string()}),detection:ps,coverage:hs}),gs=l.object({...fs,source:l.literal("agent"),engine:l.object({name:l.literal(N),version:l.string()}),scope:us()}),ys=l.discriminatedUnion("source",[ms,gs]);var nc=e=>e!==null&&typeof e=="object"&&!Array.isArray(e),ic=e=>e===null?"null":Array.isArray(e)?"array":typeof e;function sc(e){let t=e.path.map((n,i)=>typeof n=="number"?`[${n}]`:i===0?String(n):`.${n}`).join("");return t.length===0?e.message:`${t}: ${e.message}`}function Ct(e){if(!nc(e))return{ok:!1,errors:[`expected a JSON object, received ${ic(e)}`]};if(e.schema_version!==L)return{ok:!1,errors:[`unsupported schema_version: ${String(e.schema_version)} (expected ${L})`]};let t=ys.safeParse(e);return t.success?{ok:!0,document:t.data}:{ok:!1,errors:t.error.issues.map(sc)}}u();function rc(e){return[e.id,e.pattern_id??"",e.severity].join("|")}function oc(e){let t=new Map,n=[...e].sort((i,s)=>G[s.severity]-G[i.severity]||i.location.file.localeCompare(s.location.file)||(i.location.line??0)-(s.location.line??0)||(i.location.column??0)-(s.location.column??0)||i.id.localeCompare(s.id)||i.title.localeCompare(s.title));for(let i of n){let s=rc(i),r=t.get(s);r?(r.issues.push(i),r.files.includes(i.location.file)||r.files.push(i.location.file)):t.set(s,{severity:i.severity,issues:[i],files:[i.location.file]})}return[...t.values()]}function ac(e){return{too_large:e.filter(t=>t.reason==="too_large").length,unreadable:e.filter(t=>t.reason==="unreadable").length}}u();import{randomBytes as cc}from"node:crypto";import{lstat as Y,mkdir as dc,readdir as lc,realpath as Ne,rename as pc,rm as uc,unlink as hc,writeFile as fc}from"node:fs/promises";var mc=5e6,vs=[".shopify","app-security"];function gc(e){return v(e,...vs)}function Fe(e,t){yc(t);let n=v(e,...vs,t);return{resultsDirectory:n,deterministicFindingsPath:v(n,"deterministic-findings.json"),agentChecksPath:v(n,"agent-checks.json"),agentFindingsPath:v(n,"agent-findings.json")}}function yc(e){if(e===""||e==="."||e===".."||/[\\/]/.test(e))throw new A(`The results key "${e}" can't be used as a directory name.`,"Use a client ID without path separators.")}async function Wp(e,t,{deterministicFindings:n,agentChecks:i}){let s=Fe(e,t);return await ws(e,s.resultsDirectory),await Lt(s.deterministicFindingsPath,qe(n)),await Lt(s.agentChecksPath,qe(i)),{deterministicFindingsPath:s.deterministicFindingsPath,agentChecksPath:s.agentChecksPath}}async function Vp(e,t,n){let i=Fe(e,t);return await ws(e,i.resultsDirectory),await Lt(i.agentFindingsPath,qe(n)),i.agentFindingsPath}async function Bp(e,t){let{resultsDirectory:n}=Fe(e,t);return Nt(e,n)}async function Qp(e,t){let{resultsDirectory:n}=Fe(e,t);return await Nt(e,n)?(await bs(n),[n]):[]}async function Jp(e){let t=gc(e);if(!await Nt(e,t))return[];let n=(await lc(t)).map(r=>v(t,r)),i=await Promise.all(n.map(async r=>({path:r,stats:await Y(r)})));i.filter(({stats:r})=>r.isSymbolicLink()).forEach(({path:r})=>j(r));let s=i.filter(({stats:r})=>r.isDirectory()).map(({path:r})=>r);return await Promise.all(s.map(bs)),s}async function Kp(e,t){let n=await bc(e);if(n.status!=="ok")return n;let i=Ct(n.value);return i.ok?vc(i.document,t)?{status:"ok",value:i.document}:{status:"invalid",errors:[`source is "${i.document.source}", but this file must hold "${t}" findings.`]}:{status:"invalid",errors:i.errors}}function vc(e,t){return e.source===t}async function bc(e){if(!await Vt(e))return{status:"missing"};let t;try{if(await zt(e)>mc)return{status:"invalid",errors:["The file is larger than 5 MB."]};t=await Ht(e)}catch(n){return{status:"invalid",errors:[`Could not read the file: ${Dt(n)}`]}}try{return{status:"ok",value:JSON.parse(t)}}catch(n){return{status:"invalid",errors:[`Could not parse JSON: ${Dt(n)}`]}}}function Yp(e){return Buffer.byteLength(qe(e),"utf8")}function qe(e){return`${JSON.stringify(e,null,2)}
|
|
2368
|
-
`}async function
|
|
2378
|
+
`&&(t[i]=" "),i++;i<t.length&&(t[i]=" ",t[i+1]=" ",i++)}}return t.join("")}u();u();function Q(e,t){return e<t?-1:e>t?1:0}function Le(e,t){return Q(e.location.file,t.location.file)||La(e.location.line,t.location.line)}function La(e,t){return e===t?0:e===void 0?-1:t===void 0?1:e-t}var rs=e=>({file:y(e.file.replace(/\\/g,"/")),...e.line===void 0?{}:{line:e.line},...e.column===void 0?{}:{column:e.column}}),Da=e=>(e??[]).map(t=>({location:rs(t.location),...t.quote===void 0?{}:{quote:y(t.quote)}}));function At(e){return{...e,id:y(e.id),title:y(e.title),message:y(e.message),location:rs(e.location),...e.snippet===void 0?{}:{snippet:y(e.snippet)},...e.detection_evidence===void 0?{}:{detection_evidence:e.detection_evidence.map(y)},...e.evidence===void 0?{}:{evidence:Da(e.evidence)},fix:{...e.fix,description:y(e.fix.description),...e.fix.guide?{guide:y(e.fix.guide)}:{}}}}function Na(e){let t=At(e);return{location:t.location,message:t.message,evidence:t.evidence??[],...t.snippet===void 0?{}:{snippet:t.snippet},fix:t.fix}}function qa(e,t){return Le(e,t)||Q(e.message,t.message)}function Oa(e){return e==="unsupported_framework"?"unresolved":e}function Fa(e){let t=G.find(n=>n.id===e.id);if(!t)throw new Error(`Deterministic check has no catalog entry: ${e.id}`);return{title:t.title,severity:t.severity,description:t.description,...t.guide?{guide:t.guide}:{},docs_url:t.docsUrl,current_version:e.version}}function Ma(e,t){return{id:y(e.id),version:e.version,status:Oa(e.status),...e.reason?{reason:{...e.reason,message:y(e.reason.message)}}:{},analysis_mode:e.analysis_mode,snapshot:Fa(e),findings:t.map(Na).sort(qa)}}function Ua(e){let t=new Set(e.scan.checks_executed.map(i=>i.id)),n=e.issues.find(i=>!t.has(i.id));if(n)throw new Error(`Finding for a check that was not executed: ${n.id}`);return Map.groupBy(e.issues,i=>i.id)}function os(e,t){let n=Ua(e),i=e.scan.checks_executed.map(s=>Ma(s,n.get(s.id)??[])).sort((s,r)=>Q(s.id,r.id));return{schema_version:C,source:"deterministic",engine:{name:D,version:y(t.engineVersion??e.version),ruleset:y(t.ruleset??`app-security-rules@${e.version}`)},generated_at:t.generatedAt??new Date().toISOString(),detection:{...e.detection,languages:e.detection.languages.map(s=>({...s,files:s.files.map(r=>y(r))}))},coverage:{files_scanned:e.scan.files_scanned,files_skipped:(e.scan.files_skipped??[]).map(s=>({...s,path:y(s.path),...s.detail?{detail:y(s.detail)}:{}})),gaps:e.scan.coverage_gaps.map(s=>({...s,message:y(s.message),...s.file?{file:y(s.file)}:{}})),scope:xe(t.scope),scan_directories:t.scanDirectories.map(s=>({...s,directory:y(s.directory)}))},checks:i}}var J=[".js",".jsx",".mjs",".cjs",".ts",".tsx",".mts",".cts"],Rt=(e,t=1)=>({id:e.id,version:t,lifecycle:"active",analysisMode:"structured_config",target:"config",requires:e.requires,runner:n=>e.check(n)}),M=(e,t,n="source",i=1)=>({id:e,version:i,lifecycle:"active",analysisMode:"regex",target:n,extensions:J,runner:t}),$a=[Rt($i),{id:"MISSING_DEPENDENCY_SECURITY_AUTOMATION",version:2,lifecycle:"active",analysisMode:"structured_config",target:"dependency_automation",runner:e=>ii(e)},{id:"EOL_API_VERSION",version:2,lifecycle:"active",analysisMode:"regex",target:"config_and_source",extensions:J,runner:e=>Hi(e)},{...M("EXPIRING_OFFLINE_TOKEN",e=>Zi(e),"source",2),extensions:[...J,".prisma"]},{...M("UNAUTHENTICATED_ENDPOINT",e=>Ei(e.sourceFiles),"source",3),requires:"has_backend"},M("REQUEST_CONTROLLED_ADMIN_CONTEXT",e=>Ii(e.sourceFiles),"source",4),{...Rt(ht,2),target:"config_and_source",analysisMode:"regex",extensions:J,runner:e=>[...ht.check(e),...Ui(e.sourceFiles)]},Rt(Si,2),{id:"COMMITTED_SECRET",version:4,lifecycle:"active",analysisMode:"regex",target:"secrets",runner:e=>qn(e.sensitiveFiles,e.appRoot,e.gitIgnoreListing)},M("CREDENTIAL_LOG_LEAKAGE",e=>Pi(e.sourceFiles),"source",2),M("CREDENTIAL_BROWSER_LEAKAGE",e=>Li(e.sourceFiles),"source",2),{id:"LIQUID_UNSAFE_RENDER",version:1,lifecycle:"active",analysisMode:"ast",target:"theme",requires:"theme_app_extension",extensions:[".liquid",".html"],runner:e=>Ha(e,"LIQUID_UNSAFE_RENDER")},{...M("UNSAFE_INNERHTML",ja,"source_and_theme",3),analysisMode:"regex",extensions:[...J,".liquid",".html"]},{...M("APP_PROXY_LIQUID_INJECTION",e=>Bi(e.sourceFiles),"source",3),requires:"app_proxy"},{...M("STATIC_FRAME_ANCESTORS",e=>ns(e.sourceFiles),"app_source",2),requires:"embedded_app"}];function ja(e){let t=[],n=[];if(e.detection.framework==="react_router"){let i=Pt(e).filter(r=>r.content!==void 0&&J.includes(r.ext)),s=bt(i);t.push(...s),n.push({id:"react-router-js-regex",analysisMode:"regex",status:"executed",inspectedFiles:i.map(r=>r.path),findings:s.length})}if(e.capabilities.theme_app_extension){let i=re(e),s=new Set(i.map(h=>h.path));e.detection.framework!=="react_router"&&e.sourceCandidates.some(h=>!s.has(h.path))&&n.push({id:"app-source-unsupported",analysisMode:"regex",status:"unsupported_framework",inspectedFiles:[],findings:0,reason:{code:"unsupported_framework",message:"The non-theme app source framework is not supported by deterministic analysis."}});let r=i.filter(h=>h.content!==void 0&&J.includes(h.ext)),o=bt(r);t.push(...o),n.push(r.length>0?{id:"theme-js-regex",analysisMode:"regex",status:"executed",inspectedFiles:r.map(h=>h.path),findings:o.length}:{id:"theme-js-regex",analysisMode:"regex",status:"not_applicable",inspectedFiles:[],findings:0,reason:{code:"no_relevant_files",message:"The theme app extensions contain no JavaScript files."}});let a=i.filter(h=>h.content!==void 0&&(h.ext===".liquid"||h.ext===".html")),c=xt(a),l=c.issues.filter(h=>h.id==="UNSAFE_INNERHTML");t.push(...l);let d="executed",f;a.length===0?(d="not_applicable",f={code:"no_relevant_files",message:"The theme app extensions contain no Liquid files."}):c.parserFailures.length>0&&(d="unresolved",f={code:"parser_unavailable",message:`Liquid parser failed for: ${c.parserFailures.join(", ")}`}),n.push({id:"theme-liquid-ast",analysisMode:"ast",status:d,inspectedFiles:a.map(h=>h.path),findings:l.length,...f?{reason:f}:{}})}return{issues:t,inspectedFiles:[...new Set(n.flatMap(i=>i.inspectedFiles))],...n.some(i=>i.status==="unresolved")?{unresolvedReason:n.filter(i=>i.status==="unresolved").map(i=>i.reason?.message).filter(Boolean).join("; ")}:{}}}function Ha(e,t){let n=xt(re(e).filter(s=>s.content!==void 0)),i=n.parserFailures;return{issues:n.issues.filter(s=>s.id===t),...i.length>0?{unresolvedReason:`Liquid parser failed for: ${i.join(", ")}`}:{}}}var De=Ga($a);za([...De.values()]);var vp=[...De.values()].map(({id:e,version:t,requires:n})=>({id:e,version:t,requires:n}));function Ga(e){let t=new Map;for(let n of e){if(t.has(n.id))throw new Error(`Duplicate deterministic stable ID: ${n.id}`);t.set(n.id,n)}return t}function za(e){let t=new Set(G.map(n=>n.id));for(let n of e){if(!t.has(n.id))throw new Error(`Orphan deterministic runner: ${n.id}`);if(n.lifecycle==="active"&&!n.runner)throw new Error(`Active deterministic check has no runner: ${n.id}`);if(n.lifecycle!=="active"&&n.runner)throw new Error(`A non-active deterministic check can't have a runner: ${n.id}`)}}function re(e){return e.extensions.filter(t=>t.type==="theme").flatMap(t=>t.files)}function Ct(e){let t=new Set(re(e).map(n=>n.path));return e.sourceFiles.filter(n=>!t.has(n.path))}function as(e,t){return t.detection.framework!=="react_router"||wi(e,t.reactRouterRoots,t.otherAppDirectories)}function Pt(e){return Ct(e).filter(t=>as(t.path,e))}function cs(e,t){let n=t.appTomls.map(r=>w(t.appRoot,r.path).replace(/\\/g,"/"));if(e.target==="config")return n;if(e.target==="dependency_automation")return[...t.manifests.map(r=>r.path),...t.dependencyAutomation.files.filter(r=>r.content!==void 0).map(r=>r.path)];if(e.target==="secrets")return t.sensitiveFiles.filter(r=>r.content!==void 0).map(r=>r.path);let i=e.target==="app_source"?Ct(t):Pt(t);e.target==="theme"?i=re(t):e.target==="source_and_theme"&&(i=[...i,...re(t)]);let s=i.filter(r=>r.content!==void 0&&(!e.extensions||e.extensions.includes(r.ext))).map(r=>r.path);return e.target==="config_and_source"?[...n,...s]:s}function Wa(e,t){return t.sourceCandidates.find(n=>n.path===e)?.language}function Va(e,t){let n=cs(e,t),i=new Set(re(t).map(a=>a.path)),s=t.sourceCandidates.filter(a=>!i.has(a.path)),r=t.detection.framework==="react_router",o=t.capabilities.theme_app_extension;return e.target==="dependency_automation"&&!t.manifests.some(ds)?{status:"not_applicable",required:!1,applicable:!1,reason:{code:"no_relevant_files",message:"No supported package manifest declares dependencies."}}:e.target==="source"&&!r?s.length>0?{status:"unsupported_framework",required:!0,applicable:!0,reason:{code:"unsupported_framework",message:`${e.id} applies to app source, but deterministic analysis requires @shopify/shopify-app-react-router and its conventional app structure.`}}:{status:"not_applicable",required:!1,applicable:!1,reason:{code:"no_relevant_files",message:"No React Router app source applies to this check."}}:e.target==="source_and_theme"&&!r&&!o?s.length>0?{status:"unsupported_framework",required:!0,applicable:!0,reason:{code:"unsupported_framework",message:`${e.id} applies to app source, but its framework is not supported by deterministic analysis.`}}:{status:"not_applicable",required:!1,applicable:!1,reason:{code:"no_relevant_files",message:"No supported app or theme source applies to this check."}}:e.target==="source_and_theme"&&!r&&s.length>0?n.length>0?{status:"unresolved",required:!0,applicable:!0,reason:{code:"unsupported_framework",message:`${e.id} inspected theme extensions, but the non-theme app source framework is unsupported.`}}:{status:"unsupported_framework",required:!0,applicable:!0,reason:{code:"unsupported_framework",message:`${e.id} applies to app source, but its framework is unsupported.`}}:e.target==="theme"&&!o?{status:"not_applicable",required:!1,applicable:!1,reason:{code:"capability_absent",message:"No theme app extension was detected."}}:e.target==="config_and_source"&&!r&&s.length>0&&t.appTomls.length>0?{status:"unresolved",required:!0,applicable:!0,reason:{code:"unsupported_framework",message:`Configuration can be inspected, but ${e.id} source analysis requires @shopify/shopify-app-react-router.`}}:e.requires&&!t.capabilities[e.requires]?{status:"not_applicable",required:!1,applicable:!1,reason:{code:"capability_absent",message:`Capability ${e.requires} was not detected.`}}:(e.target==="config"||e.target==="config_and_source")&&t.appTomls.length===0?{status:"unresolved",required:!0,applicable:!0,reason:{code:"parser_unavailable",message:"No readable Shopify app configuration was available."}}:["source","app_source","theme","secrets","source_and_theme"].includes(e.target)&&n.length===0?{status:"not_applicable",required:!1,applicable:!1,reason:{code:"no_relevant_files",message:`No ${e.target.replaceAll("_"," ")} files apply to this check.`}}:{status:"executed",required:!0,applicable:!0}}function ds(e){return Object.keys(e.dependencies).length>0||Object.keys(e.devDependencies??{}).length>0}function Ba(e,t,n){let i=t.extensions.filter(d=>d.type==="theme").map(d=>d.path.replace(/shopify\.extension\.toml$/,"")),s=d=>i.some(f=>d.startsWith(f))||d.endsWith("shopify.extension.toml"),r=d=>!s(d)&&!!e.extensions?.some(f=>d.endsWith(f)),o=d=>r(d)&&as(d,t),a=d=>/^shopify\.app(?:\.[^/]+)?\.toml$/.test(d),c=d=>/(^|\/)package\.json$/.test(d)||t.dependencyAutomation.files.some(f=>f.path===d),l=d=>!d.detail?.includes("could not be parsed")&&(t.sourceCandidates.some(f=>f.path===d.path)||t.sensitiveFiles.some(f=>f.path===d.path)||/(^|\/)(?:\.env(?:\.[^/]+)?|secrets\.json|credentials\.json)$/.test(d.path));return n.filter(d=>e.target==="config"?a(d.path):e.target==="dependency_automation"?c(d.path):e.target==="config_and_source"?a(d.path)||o(d.path):e.target==="source"?o(d.path):e.target==="app_source"?r(d.path):e.target==="theme"?s(d.path):e.target==="source_and_theme"?o(d.path)||s(d.path):l(d))}function Qa(e,t){return{code:t.some(i=>!!i.detail?.includes("could not be parsed")||i.path.endsWith("shopify.extension.toml")||i.path.endsWith("package.json"))?"parser_unavailable":"input_rejected",message:`${e.id} could not inspect required input: ${t.map(i=>`${i.path} (${i.reason.replaceAll("_"," ")})`).join(", ")}.`}}function Ja(e,t){return e.target==="app_source"?{...t,sourceFiles:Ct(t)}:e.target==="source"||e.target==="config_and_source"?{...t,sourceFiles:Pt(t)}:t}function Ka(e){return Array.isArray(e)?{issues:e}:e}function ls({appDirectory:e,scanDirectories:t,requestedScanDirectories:n,appConfigFilePath:i},s){return oi({appDirectory:e,scanDirectories:t,requestedScanDirectories:n,selectedAppConfigFilePath:i,rules:Jn({excludePatterns:s.excludePatterns??[],noGitIgnore:s.noGitIgnore??!1})})}async function Ya(e,t={}){at({appDirectory:e.appDirectory,scanDirectories:e.scanDirectories,explicitInputs:new Set});let{paths:n,ignoredScanDirectories:i,otherAppDirectories:s}=await ls(e,t);return{paths:n,ignoredScanDirectories:i,otherAppDirectories:s}}async function ps(e,t={}){let{appDirectory:n,scanDirectories:i,appConfigFilePath:s}=e;at({appDirectory:n,scanDirectories:i,explicitInputs:new Set(s?[s]:[])});let r=s?R(w(n,s)):void 0,o=s?ri(s,n):null,a=o?[o]:[],{paths:c,ignoredScanDirectories:l,otherAppDirectories:d,listingStatus:f}=await ls(e,t),h=ai(n,c),g=ui(c),x=lt(n,c),E=hi(n,c,r),ye=mi(c),Me=gi(n,ye),Ds=Me.some(ds)?fi(n,c):{files:[]},Mt=vi(o,h,x,a),Ut=d.map(m=>R(w(n,m))),$t=bi(Me,g,Ut),oe=ki(h,g,$t),ae={appRoot:n,appToml:o,appTomls:a,extensions:h,sourceFiles:x,manifests:Me,dependencyAutomation:Ds,sensitiveFiles:E,capabilities:Mt,detection:oe,reactRouterRoots:$t,otherAppDirectories:Ut,sourceCandidates:g,gitIgnoreListing:f},$=[],Y=[],jt=new Set;for(let m of De.values()){let _=Va(m,ae),Ue=_.status==="unsupported_framework"?[]:cs(m,ae),Fs=$.length,$e=Ba(m,ae,ct()),Ms=m.target==="dependency_automation"&&$e.length>0;if((_.status==="executed"||_.status==="unresolved")&&m.runner&&!Ms){let A=Ka(await m.runner(Ja(m,ae)));(m.target!=="dependency_automation"||!A.unresolvedReason)&&$.push(...A.issues),A.inspectedFiles&&(Ue=A.inspectedFiles),A.unresolvedReason&&(_={status:"unresolved",required:!0,applicable:!0,reason:{code:A.unresolvedReasonCode??"parser_unavailable",message:A.unresolvedReason}})}_.status!=="unsupported_framework"&&$e.length>0&&(_={status:"unresolved",required:!0,applicable:!0,reason:Qa(m,$e)}),_.required&&jt.add(m.id);let Us=[...new Set(Ue.map(A=>Wa(A,ae)).filter(A=>!!A))].sort();Y.push({id:m.id,version:m.version,kind:"deterministic",status:_.status,applicable:_.applicable,languages:Us,framework:oe.framework,surface:oe.surface,inspected_files:Ue,findings:$.length-Fs,analysis_mode:m.analysisMode,..._.reason?{reason:_.reason}:{}})}let Ns=new Map([...De.values()].map(m=>[m.id,m.version]));$=$.map(m=>At({...m,rule_version:Ns.get(m.id)??1}));for(let m of Y)m.findings=$.filter(_=>_.id===m.id).length;let Ht=[...new Map(ct().map(m=>{let _={...m,path:y(m.path),...m.detail?{detail:y(m.detail)}:{}};return[`${_.path}|${_.reason}`,_]})).values()],qs=[...Ht.map(m=>({code:"skipped_file",file:m.path,message:`The file was not inspected because it was ${m.reason.replaceAll("_"," ")}.`})),...oe.languages.filter(m=>m.support==="unsupported").map(m=>({code:"unsupported_language",message:`${m.name} source is not supported by deterministic analysis.`})),...Y.flatMap(m=>!jt.has(m.id)||m.status!=="unsupported_framework"&&m.status!=="unresolved"?[]:[{code:m.status==="unsupported_framework"?"unsupported_framework":"unresolved_check",check_id:m.id,message:m.reason?.message??`${m.id} could not execute.`}])],Gt=Y.filter(m=>m.status==="executed").length,Os=_i(x.filter(m=>m.content!==void 0).length,Gt,Y.length-Gt,Ht,Y,qs);return{version:z(),timestamp:new Date().toISOString(),app:{name:y(String(o?.raw.name??"Unknown")),type:"public"},capabilities:Mt,detection:oe,scan:Os,issues:$,ignoredScanDirectories:l,otherAppDirectories:d}}function Xa(){return En}function Za({appDirectory:e,scanDirectories:t,appConfigDirectories:n=[]}){return t.map(i=>({directory:R(w(e,i))||".",origin:ec(i,e,n)}))}function ec(e,t,n){return e===t?"app_directory":n.includes(e)?"app_config_directory":"include_dir"}async function tc(e,t={}){let{ignoredScanDirectories:n,otherAppDirectories:i,...s}=await ps(e,t),r=z(),o=os(s,{engineVersion:r,scope:{include_dirs:[...t.includeDirs??[]],excludes:[...t.excludePatterns??[]],no_git_ignore:t.noGitIgnore??!1},scanDirectories:Za(e)});return{scan:s,ignoredScanDirectories:n,otherAppDirectories:i,deterministicFindings:o,agentChecks:Qn(r),engine:o.engine}}u();var us={deterministic:0,agent:1};function nc(e){return[...oc(e)].map(([t,n])=>cc(t,n)).sort((t,n)=>j[n.severity]-j[t.severity]||Q(t.id,n.id))}function Lt(e){return e.findings.filter(t=>t.disposition==="active")}function ic(e){return e.status==="executed"&&Lt(e).length===0}function sc(e){return e.applied_precedence!==e.precedence}function gs(e,t){return t==="agent"&&e.suppression!==void 0}function rc(e){let t={withFindings:0,passed:0,notApplicable:0,unresolved:0,suppressed:0,superseded:0};for(let n of e){Lt(n).length>0?t.withFindings+=1:n.status==="executed"?t.passed+=1:n.status==="not_applicable"?t.notApplicable+=1:t.unresolved+=1;for(let i of n.findings)i.disposition==="suppressed"&&(t.suppressed+=1),i.disposition==="superseded"&&(t.superseded+=1)}return t}function oc({deterministic:e,agent:t}){let n=new Map;if(e)for(let i of e.checks)n.set(i.id,{deterministic:hs(i,e),agent:void 0,describingCheck:i});if(t)for(let i of t.checks)n.set(i.id,{deterministic:n.get(i.id)?.deterministic,agent:hs(i,t),describingCheck:i});return n}function ac(e,t){return t.generated_at}function hs(e,t){return{check:e,generatedAt:ac(e,t)}}function cc(e,{deterministic:t,agent:n,describingCheck:i}){let s=n?.check.snapshot.precedence??"union",{snapshot:r}=i,o=dc(s,t,n),a=o==="prefer-agent"&&n!==void 0,c=[...(t?.check.findings??[]).map(l=>fs(l,"deterministic",a?"superseded":"active")),...(n?.check.findings??[]).map(l=>fs(l,"agent",gs(l,"agent")?"suppressed":"active"))].sort(uc);return{id:e,title:r.title,severity:r.severity,description:r.description,...r.guide?{guide:r.guide}:{},...r.docs_url?{docs_url:r.docs_url}:{},precedence:s,applied_precedence:o,status:a?n.check.status:pc([t,n].flatMap(l=>l?[l.check.status]:[])),by_source:{deterministic:t?ms(t):null,agent:n?ms(n):null},findings:c}}function dc(e,t,n){return e!=="prefer-agent"||n===void 0?"union":lc(t,n)?"prefer-agent":"union"}function lc(e,t){if(e===void 0)return!0;let n=Date.parse(t.generatedAt),i=Date.parse(e.generatedAt);return Number.isNaN(n)||Number.isNaN(i)?!1:n>=i}function pc(e){return e.includes("executed")?"executed":e.includes("unresolved")?"unresolved":"not_applicable"}function fs(e,t,n){return{...e,source:t,disposition:n}}function ms({check:e,generatedAt:t}){return{version:e.version,status:e.status,...e.reason?{reason:e.reason}:{},...e.analysis_mode?{analysis_mode:e.analysis_mode}:{},generated_at:t}}function uc(e,t){return Le(e,t)||us[e.source]-us[t.source]||Q(e.message,t.message)}u();u();u();var ys=p.enum(["high","medium","low"]),vs=p.enum(["executed","not_applicable","unresolved"]),bs=p.enum(["union","prefer-agent"]),ws=p.object({file:p.string(),line:p.number().optional(),column:p.number().optional()}),hc=p.object({automated:p.boolean(),guide:p.string().optional(),description:p.string()}),fc=p.object({title:p.string(),severity:ys,description:p.string(),guide:p.string().optional(),docs_url:p.string().optional(),current_version:p.number(),precedence:bs.optional()}),mc=p.object({location:ws,quote:p.string().optional()}),xs=p.object({location:ws,message:p.string(),evidence:p.array(mc),snippet:p.string().optional(),fix:hc.optional(),confidence:p.enum(["high","medium","low"]).optional(),reasoning:p.string().optional(),suppression:p.object({justification:p.string()}).optional()}),ks=p.object({id:p.string(),version:p.number(),status:vs,reason:p.object({code:p.string(),message:p.string()}).optional(),analysis_mode:p.enum(["regex","structured_config","ast"]).optional(),snapshot:fc,findings:p.array(xs)}),gc=p.array(ks).superRefine((e,t)=>{let n=new Set;e.forEach((i,s)=>{n.has(i.id)&&t.addIssue({code:p.ZodIssueCode.custom,path:[s,"id"],message:`duplicate check id ${i.id}`}),n.add(i.id)})}),_s=p.object({framework:p.enum(["react_router","none","unknown","mixed"]),surface:p.enum(["react_router","theme_app_extension","config_only","unknown","mixed"]),languages:p.array(p.object({name:p.string(),support:p.enum(["supported","unsupported"]),files:p.array(p.string())}))}),Ss=()=>p.object({include_dirs:p.array(p.string()),excludes:p.array(p.string()),no_git_ignore:p.boolean()}),Ts=p.object({files_scanned:p.number(),files_skipped:p.array(p.object({path:p.string(),reason:p.enum(["too_large","unreadable"]),size_bytes:p.number().optional(),detail:p.string().optional()})),gaps:p.array(p.object({code:p.enum(["skipped_file","unsupported_framework","unsupported_language","unresolved_check"]),message:p.string(),check_id:p.string().optional(),file:p.string().optional()})),scope:Ss(),scan_directories:p.array(p.object({directory:p.string(),origin:p.enum(["app_directory","include_dir","app_config_directory"])}))}),Es={schema_version:p.literal(C),generated_at:p.string(),checks:gc},Is=p.object({...Es,source:p.literal("deterministic"),engine:p.object({name:p.literal(D),version:p.string(),ruleset:p.string()}),detection:_s,coverage:Ts}),As=p.object({...Es,source:p.literal("agent"),engine:p.object({name:p.literal(D),version:p.string()}),scope:Ss()}),Rs=p.discriminatedUnion("source",[Is,As]);var yc=e=>e!==null&&typeof e=="object"&&!Array.isArray(e),vc=e=>e===null?"null":Array.isArray(e)?"array":typeof e;function bc(e){let t=e.path.map((n,i)=>typeof n=="number"?`[${n}]`:i===0?String(n):`.${n}`).join("");return t.length===0?e.message:`${t}: ${e.message}`}function Dt(e){if(!yc(e))return{ok:!1,errors:[`expected a JSON object, received ${vc(e)}`]};if(e.schema_version!==C)return{ok:!1,errors:[`unsupported schema_version: ${String(e.schema_version)} (expected ${C})`]};let t=Rs.safeParse(e);return t.success?{ok:!0,document:t.data}:{ok:!1,errors:t.error.issues.map(bc)}}u();function wc(e){return[e.id,e.pattern_id??"",e.severity].join("|")}function xc(e){let t=new Map,n=[...e].sort((i,s)=>j[s.severity]-j[i.severity]||i.location.file.localeCompare(s.location.file)||(i.location.line??0)-(s.location.line??0)||(i.location.column??0)-(s.location.column??0)||i.id.localeCompare(s.id)||i.title.localeCompare(s.title));for(let i of n){let s=wc(i),r=t.get(s);r?(r.issues.push(i),r.files.includes(i.location.file)||r.files.push(i.location.file)):t.set(s,{severity:i.severity,issues:[i],files:[i.location.file]})}return[...t.values()]}function kc(e){return{too_large:e.filter(t=>t.reason==="too_large").length,unreadable:e.filter(t=>t.reason==="unreadable").length}}u();import{randomBytes as _c}from"node:crypto";import{lstat as K,mkdir as Sc,readdir as Tc,realpath as Ne,rename as Ec,rm as Ic,unlink as Ac,writeFile as Rc}from"node:fs/promises";var Cc=5e6,Cs=[".shopify","app-security"];function Pc(e){return b(e,...Cs)}function Fe(e,t){Lc(t);let n=b(e,...Cs,t);return{resultsDirectory:n,deterministicFindingsPath:b(n,"deterministic-findings.json"),agentChecksPath:b(n,"agent-checks.json"),agentFindingsPath:b(n,"agent-findings.json")}}function Lc(e){if(e===""||e==="."||e===".."||/[\\/]/.test(e))throw new I(`The results key "${e}" can't be used as a directory name.`,"Use a client ID without path separators.")}async function cu(e,t,{deterministicFindings:n,agentChecks:i}){let s=Fe(e,t);return await Ls(e,s.resultsDirectory),await qt(s.deterministicFindingsPath,qe(n)),await qt(s.agentChecksPath,qe(i)),{deterministicFindingsPath:s.deterministicFindingsPath,agentChecksPath:s.agentChecksPath}}async function du(e,t,n){let i=Fe(e,t);return await Ls(e,i.resultsDirectory),await qt(i.agentFindingsPath,qe(n)),i.agentFindingsPath}async function lu(e,t){let{resultsDirectory:n}=Fe(e,t);return Ft(e,n)}async function pu(e,t){let{resultsDirectory:n}=Fe(e,t);return await Ft(e,n)?(await Ps(n),[n]):[]}async function uu(e){let t=Pc(e);if(!await Ft(e,t))return[];let n=(await Tc(t)).map(r=>b(t,r)),i=await Promise.all(n.map(async r=>({path:r,stats:await K(r)})));i.filter(({stats:r})=>r.isSymbolicLink()).forEach(({path:r})=>U(r));let s=i.filter(({stats:r})=>r.isDirectory()).map(({path:r})=>r);return await Promise.all(s.map(Ps)),s}async function hu(e,t){let n=await Nc(e);if(n.status!=="ok")return n;let i=Dt(n.value);return i.ok?Dc(i.document,t)?{status:"ok",value:i.document}:{status:"invalid",errors:[`source is "${i.document.source}", but this file must hold "${t}" findings.`]}:{status:"invalid",errors:i.errors}}function Dc(e,t){return e.source===t}async function Nc(e){if(!await Yt(e))return{status:"missing"};let t;try{if(await Jt(e)>Cc)return{status:"invalid",errors:["The file is larger than 5 MB."]};t=await Bt(e)}catch(n){return{status:"invalid",errors:[`Could not read the file: ${Ot(n)}`]}}try{return{status:"ok",value:JSON.parse(t)}}catch(n){return{status:"invalid",errors:[`Could not parse JSON: ${Ot(n)}`]}}}function fu(e){return Buffer.byteLength(qe(e),"utf8")}function qe(e){return`${JSON.stringify(e,null,2)}
|
|
2379
|
+
`}async function Ft(e,t){let n=T(e),i=T(t);Oe(n,i);let s=await K(n);(s.isSymbolicLink()||!s.isDirectory())&&U(i);let r=n;for(let o of w(n,i).split("/")){r=b(r,o);let a=await qc(r);if(!a)return!1;(a.isSymbolicLink()||!a.isDirectory())&&U(r)}return Oe(await Ne(n),await Ne(i)),!0}async function Ps(e){try{await Ic(e,{recursive:!0,force:!0})}catch(t){throw new I(`Could not remove the app security check results at ${e}.`,Ot(t))}}async function qc(e){try{return await K(e)}catch(t){if(t.code==="ENOENT")return;throw t}}async function Ls(e,t){let n=T(e),i=T(t);Oe(n,i);let s=await K(n);(s.isSymbolicLink()||!s.isDirectory())&&U(i);let r=n;for(let c of w(n,i).split("/"))r=b(r,c),await Oc(r);let o=await Ne(n),a=await Ne(i);Oe(o,a),await rn(e)}async function Oc(e){try{let n=await K(e);(n.isSymbolicLink()||!n.isDirectory())&&U(e);return}catch(n){if(n.code!=="ENOENT")throw n}await Sc(e,{mode:448});let t=await K(e);(t.isSymbolicLink()||!t.isDirectory())&&U(e)}function Oe(e,t){let n=w(e,t);(!n||n.startsWith("..")||n.startsWith("/"))&&U(t)}function U(e){throw new I(`Refusing to write app security check artifacts through a symbolic link or outside the app: ${e}`,"Remove or replace the unsafe app security check artifact path, then run the command again.")}async function qt(e,t){await Nt(e);let n=`${e}.${_c(8).toString("hex")}.tmp`;await Nt(n);try{await Rc(n,t,{encoding:"utf8",mode:384}),await Nt(e),await Ec(n,e)}catch(i){throw await Ac(n).catch(()=>{}),i}}async function Nt(e){try{(await K(e)).isSymbolicLink()&&U(e)}catch(t){if(t.code==="ENOENT")return;throw t}}function Ot(e){return e instanceof Error?e.message:String(e)}export{Hs as a,Mc as b,hr as c,j as d,Ir as e,z as f,Ya as g,Xa as h,tc as i,nc as j,Lt as k,ic as l,sc as m,gs as n,rc as o,xc as p,kc as q,Cc as r,Pc as s,Fe as t,cu as u,du as v,lu as w,pu as x,uu as y,hu as z,fu as A,wn as B,td as C,nd as D,id as E,sd as F,rd as G,od as H,hd as I,_n as J,ir as K,fd as L,md as M};
|