codeply-cli 0.5.0 → 0.5.2

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.
Files changed (387) hide show
  1. package/LICENSE +21 -0
  2. package/THIRD_PARTY_NOTICES.md +65 -63
  3. package/bin/codeply.js +1 -1
  4. package/lib/agent.mjs +133 -24
  5. package/lib/ai.js +268 -9
  6. package/lib/ai.mjs +6 -6
  7. package/lib/apply-patch.js +217 -217
  8. package/lib/arity-table.json +138 -138
  9. package/lib/arity.js +70 -70
  10. package/lib/auth.mjs +6 -6
  11. package/lib/bots.js +493 -16
  12. package/lib/chatgpt.js +282 -0
  13. package/lib/cloud-browser.mjs +143 -143
  14. package/lib/cloud.mjs +1186 -1105
  15. package/lib/commands.js +103 -103
  16. package/lib/config.js +83 -3
  17. package/lib/design-library/assemble-artifact.mjs +21 -21
  18. package/lib/design-library/cleanup.mjs +45 -45
  19. package/lib/design-library/index.json +22379 -22379
  20. package/lib/design-library/make-table-data.mjs +39 -39
  21. package/lib/diagnostics.js +192 -192
  22. package/lib/edit-engine.js +364 -364
  23. package/lib/github-agent.mjs +311 -311
  24. package/lib/history.js +75 -75
  25. package/lib/mail-watch.js +504 -0
  26. package/lib/mcp.js +258 -258
  27. package/lib/model-sync.js +114 -0
  28. package/lib/native-tools.mjs +16 -1
  29. package/lib/oauth-connectors.js +146 -24
  30. package/lib/permissions.js +109 -109
  31. package/lib/plugins.js +353 -353
  32. package/lib/publish.js +916 -0
  33. package/lib/reach.js +67 -0
  34. package/lib/server.mjs +457 -457
  35. package/lib/session-db.js +220 -220
  36. package/lib/share.js +168 -168
  37. package/lib/skills.js +2 -0
  38. package/lib/snapshot.js +146 -146
  39. package/lib/telemetry.js +102 -0
  40. package/lib/terminal-hints.js +154 -154
  41. package/lib/tools.mjs +316 -14
  42. package/lib/typecheck-worker.js +147 -147
  43. package/lib/web-tools.js +209 -158
  44. package/package.json +49 -49
  45. package/skills/SOURCE.md +58 -58
  46. package/skills/accessibility/SKILL.md +147 -147
  47. package/skills/agent-architecture-audit/SKILL.md +257 -257
  48. package/skills/agent-eval/SKILL.md +147 -147
  49. package/skills/agent-harness-construction/SKILL.md +74 -74
  50. package/skills/agent-introspection-debugging/SKILL.md +154 -154
  51. package/skills/agent-payment-x402/SKILL.md +225 -225
  52. package/skills/agent-self-evaluation/SKILL.md +182 -182
  53. package/skills/agent-self-evaluation/references/evaluation-criteria.md +71 -71
  54. package/skills/agent-self-evaluation/references/hook-integration.md +64 -64
  55. package/skills/agent-sort/SKILL.md +216 -216
  56. package/skills/agentic-engineering/SKILL.md +64 -64
  57. package/skills/agentic-os/SKILL.md +388 -388
  58. package/skills/ai-first-engineering/SKILL.md +52 -52
  59. package/skills/ai-regression-testing/SKILL.md +386 -386
  60. package/skills/android-clean-architecture/SKILL.md +340 -340
  61. package/skills/angular-developer/SKILL.md +155 -155
  62. package/skills/angular-developer/references/angular-animations.md +160 -160
  63. package/skills/angular-developer/references/angular-aria.md +410 -410
  64. package/skills/angular-developer/references/cli.md +86 -86
  65. package/skills/angular-developer/references/component-harnesses.md +59 -59
  66. package/skills/angular-developer/references/component-styling.md +91 -91
  67. package/skills/angular-developer/references/components.md +117 -117
  68. package/skills/angular-developer/references/creating-services.md +97 -97
  69. package/skills/angular-developer/references/data-resolvers.md +69 -69
  70. package/skills/angular-developer/references/define-routes.md +67 -67
  71. package/skills/angular-developer/references/defining-providers.md +72 -72
  72. package/skills/angular-developer/references/di-fundamentals.md +120 -120
  73. package/skills/angular-developer/references/e2e-testing.md +56 -56
  74. package/skills/angular-developer/references/effects.md +83 -83
  75. package/skills/angular-developer/references/hierarchical-injectors.md +43 -43
  76. package/skills/angular-developer/references/host-elements.md +80 -80
  77. package/skills/angular-developer/references/injection-context.md +63 -63
  78. package/skills/angular-developer/references/inputs.md +101 -101
  79. package/skills/angular-developer/references/linked-signal.md +59 -59
  80. package/skills/angular-developer/references/loading-strategies.md +61 -61
  81. package/skills/angular-developer/references/mcp.md +108 -108
  82. package/skills/angular-developer/references/navigate-to-routes.md +69 -69
  83. package/skills/angular-developer/references/outputs.md +86 -86
  84. package/skills/angular-developer/references/reactive-forms.md +122 -122
  85. package/skills/angular-developer/references/rendering-strategies.md +44 -44
  86. package/skills/angular-developer/references/resource.md +77 -77
  87. package/skills/angular-developer/references/route-animations.md +56 -56
  88. package/skills/angular-developer/references/route-guards.md +52 -52
  89. package/skills/angular-developer/references/router-lifecycle.md +45 -45
  90. package/skills/angular-developer/references/router-testing.md +87 -87
  91. package/skills/angular-developer/references/show-routes-with-outlets.md +68 -68
  92. package/skills/angular-developer/references/signal-forms.md +795 -795
  93. package/skills/angular-developer/references/signals-overview.md +94 -94
  94. package/skills/angular-developer/references/tailwind-css.md +69 -69
  95. package/skills/angular-developer/references/template-driven-forms.md +114 -114
  96. package/skills/angular-developer/references/testing-fundamentals.md +65 -65
  97. package/skills/api-connector-builder/SKILL.md +121 -121
  98. package/skills/api-design/SKILL.md +524 -524
  99. package/skills/architecture-decision-records/SKILL.md +180 -180
  100. package/skills/article-writing/SKILL.md +80 -80
  101. package/skills/automation-audit-ops/SKILL.md +143 -143
  102. package/skills/autonomous-agent-harness/SKILL.md +274 -274
  103. package/skills/autonomous-loops/SKILL.md +611 -611
  104. package/skills/backend-patterns/SKILL.md +562 -562
  105. package/skills/benchmark/SKILL.md +95 -95
  106. package/skills/benchmark-methodology/SKILL.md +191 -191
  107. package/skills/benchmark-optimization-loop/SKILL.md +71 -71
  108. package/skills/blender-motion-state-inspection/SKILL.md +165 -165
  109. package/skills/blueprint/SKILL.md +106 -106
  110. package/skills/brand-discovery/SKILL.md +145 -145
  111. package/skills/brand-discovery/references/10_purpose-why.md +40 -40
  112. package/skills/brand-discovery/references/20_positioning.md +44 -44
  113. package/skills/brand-discovery/references/30_audience-niche.md +52 -52
  114. package/skills/brand-discovery/references/40_personality-archetype.md +57 -57
  115. package/skills/brand-discovery/references/50_voice-tone.md +59 -59
  116. package/skills/brand-discovery/references/60_narrative-story.md +50 -50
  117. package/skills/brand-discovery/references/70_founder-tension.md +49 -49
  118. package/skills/brand-discovery/references/90_SYNTHESIS.md +133 -133
  119. package/skills/brand-voice/SKILL.md +98 -98
  120. package/skills/brand-voice/references/voice-profile-schema.md +55 -55
  121. package/skills/browser-qa/SKILL.md +105 -105
  122. package/skills/bun-runtime/SKILL.md +85 -85
  123. package/skills/canary-watch/SKILL.md +108 -108
  124. package/skills/carrier-relationship-management/SKILL.md +212 -212
  125. package/skills/cisco-ios-patterns/SKILL.md +164 -164
  126. package/skills/ck/SKILL.md +148 -148
  127. package/skills/claude-devfleet/SKILL.md +112 -112
  128. package/skills/click-path-audit/SKILL.md +245 -245
  129. package/skills/clickhouse-io/SKILL.md +445 -445
  130. package/skills/code-tour/SKILL.md +254 -254
  131. package/skills/codebase-onboarding/SKILL.md +234 -234
  132. package/skills/codehealth-mcp/SKILL.md +167 -167
  133. package/skills/coding-standards/SKILL.md +551 -551
  134. package/skills/competitive-platform-analysis/SKILL.md +214 -214
  135. package/skills/competitive-report-structure/SKILL.md +162 -162
  136. package/skills/compose-multiplatform-patterns/SKILL.md +300 -300
  137. package/skills/config-gc/SKILL.md +120 -120
  138. package/skills/configure-ecc/SKILL.md +385 -385
  139. package/skills/connections-optimizer/SKILL.md +190 -190
  140. package/skills/content-engine/SKILL.md +132 -132
  141. package/skills/content-hash-cache-pattern/SKILL.md +162 -162
  142. package/skills/context-budget/SKILL.md +136 -136
  143. package/skills/continuous-agent-loop/SKILL.md +46 -46
  144. package/skills/continuous-learning/SKILL.md +132 -132
  145. package/skills/continuous-learning-v2/SKILL.md +361 -361
  146. package/skills/contract-first/SKILL.md +287 -287
  147. package/skills/cost-aware-llm-pipeline/SKILL.md +184 -184
  148. package/skills/cost-tracking/SKILL.md +97 -97
  149. package/skills/council/SKILL.md +204 -204
  150. package/skills/cpp-coding-standards/SKILL.md +724 -724
  151. package/skills/cpp-testing/SKILL.md +325 -325
  152. package/skills/crosspost/SKILL.md +112 -112
  153. package/skills/csharp-testing/SKILL.md +322 -322
  154. package/skills/customer-billing-ops/SKILL.md +141 -141
  155. package/skills/customs-trade-compliance/SKILL.md +263 -263
  156. package/skills/dart-flutter-patterns/SKILL.md +564 -564
  157. package/skills/dashboard-builder/SKILL.md +109 -109
  158. package/skills/data-scraper-agent/SKILL.md +765 -765
  159. package/skills/data-throughput-accelerator/SKILL.md +74 -74
  160. package/skills/database-migrations/SKILL.md +430 -430
  161. package/skills/deep-research/SKILL.md +160 -160
  162. package/skills/defi-amm-security/SKILL.md +167 -167
  163. package/skills/delivery-gate/SKILL.md +126 -126
  164. package/skills/deployment-patterns/SKILL.md +428 -428
  165. package/skills/design-system/SKILL.md +83 -83
  166. package/skills/django-celery/SKILL.md +458 -458
  167. package/skills/django-patterns/SKILL.md +735 -735
  168. package/skills/django-security/SKILL.md +644 -644
  169. package/skills/django-tdd/SKILL.md +730 -730
  170. package/skills/django-verification/SKILL.md +470 -470
  171. package/skills/dmux-workflows/SKILL.md +192 -192
  172. package/skills/docker-patterns/SKILL.md +365 -365
  173. package/skills/documentation-lookup/SKILL.md +91 -91
  174. package/skills/dotnet-patterns/SKILL.md +322 -322
  175. package/skills/dynamic-workflow-mode/SKILL.md +124 -124
  176. package/skills/e2e-testing/SKILL.md +327 -327
  177. package/skills/ecc-guide/SKILL.md +190 -190
  178. package/skills/ecc-recipes/SKILL.md +149 -149
  179. package/skills/ecc-tools-cost-audit/SKILL.md +161 -161
  180. package/skills/email-ops/SKILL.md +122 -122
  181. package/skills/energy-procurement/SKILL.md +228 -228
  182. package/skills/enterprise-agent-ops/SKILL.md +51 -51
  183. package/skills/error-handling/SKILL.md +377 -377
  184. package/skills/eval-harness/SKILL.md +271 -271
  185. package/skills/evm-token-decimals/SKILL.md +131 -131
  186. package/skills/exa-search/SKILL.md +108 -108
  187. package/skills/fal-ai-media/SKILL.md +289 -289
  188. package/skills/fastapi-patterns/SKILL.md +514 -514
  189. package/skills/finance-billing-ops/SKILL.md +128 -128
  190. package/skills/flox-environments/SKILL.md +497 -497
  191. package/skills/flutter-dart-code-review/SKILL.md +436 -436
  192. package/skills/foundation-models-on-device/SKILL.md +243 -243
  193. package/skills/frontend-a11y/SKILL.md +446 -446
  194. package/skills/frontend-design-direction/SKILL.md +93 -93
  195. package/skills/frontend-patterns/SKILL.md +657 -657
  196. package/skills/frontend-slides/SKILL.md +185 -185
  197. package/skills/fsharp-testing/SKILL.md +281 -281
  198. package/skills/gan-style-harness/SKILL.md +279 -279
  199. package/skills/gateguard/SKILL.md +133 -133
  200. package/skills/generating-python-installer/SKILL.md +820 -820
  201. package/skills/git-workflow/SKILL.md +716 -716
  202. package/skills/github-ops/SKILL.md +145 -145
  203. package/skills/golang-patterns/SKILL.md +676 -676
  204. package/skills/golang-testing/SKILL.md +721 -721
  205. package/skills/google-workspace-ops/SKILL.md +96 -96
  206. package/skills/growth-log/SKILL.md +128 -128
  207. package/skills/healthcare-cdss-patterns/SKILL.md +246 -246
  208. package/skills/healthcare-emr-patterns/SKILL.md +160 -160
  209. package/skills/healthcare-eval-harness/SKILL.md +208 -208
  210. package/skills/healthcare-phi-compliance/SKILL.md +146 -146
  211. package/skills/hermes-imports/SKILL.md +89 -89
  212. package/skills/hexagonal-architecture/SKILL.md +277 -277
  213. package/skills/hipaa-compliance/SKILL.md +79 -79
  214. package/skills/homelab-network-readiness/SKILL.md +170 -170
  215. package/skills/homelab-network-setup/SKILL.md +130 -130
  216. package/skills/homelab-pihole-dns/SKILL.md +275 -275
  217. package/skills/homelab-vlan-segmentation/SKILL.md +312 -312
  218. package/skills/homelab-wireguard-vpn/SKILL.md +306 -306
  219. package/skills/hookify-rules/SKILL.md +128 -128
  220. package/skills/inherit-legacy-style/SKILL.md +157 -157
  221. package/skills/intent-driven-development/SKILL.md +360 -360
  222. package/skills/inventory-demand-planning/SKILL.md +247 -247
  223. package/skills/investor-materials/SKILL.md +97 -97
  224. package/skills/investor-outreach/SKILL.md +92 -92
  225. package/skills/ios-icon-gen/SKILL.md +158 -158
  226. package/skills/iterative-retrieval/SKILL.md +212 -212
  227. package/skills/ito-basket-compare/SKILL.md +64 -64
  228. package/skills/ito-compute/SKILL.md +129 -129
  229. package/skills/ito-data-atlas-agent/SKILL.md +64 -64
  230. package/skills/ito-market-intelligence/SKILL.md +61 -61
  231. package/skills/ito-trade-planner/SKILL.md +68 -68
  232. package/skills/java-coding-standards/SKILL.md +384 -384
  233. package/skills/jira-integration/SKILL.md +303 -303
  234. package/skills/jpa-patterns/SKILL.md +152 -152
  235. package/skills/knowledge-ops/SKILL.md +155 -155
  236. package/skills/kotlin-coroutines-flows/SKILL.md +285 -285
  237. package/skills/kotlin-exposed-patterns/SKILL.md +720 -720
  238. package/skills/kotlin-ktor-patterns/SKILL.md +690 -690
  239. package/skills/kotlin-patterns/SKILL.md +712 -712
  240. package/skills/kotlin-testing/SKILL.md +825 -825
  241. package/skills/kubernetes-patterns/SKILL.md +756 -756
  242. package/skills/laravel-patterns/SKILL.md +416 -416
  243. package/skills/laravel-plugin-discovery/SKILL.md +230 -230
  244. package/skills/laravel-security/SKILL.md +948 -948
  245. package/skills/laravel-tdd/SKILL.md +675 -675
  246. package/skills/laravel-verification/SKILL.md +180 -180
  247. package/skills/latency-critical-systems/SKILL.md +75 -75
  248. package/skills/lead-intelligence/SKILL.md +322 -322
  249. package/skills/liquid-glass-design/SKILL.md +279 -279
  250. package/skills/llm-trading-agent-security/SKILL.md +147 -147
  251. package/skills/logistics-exception-management/SKILL.md +222 -222
  252. package/skills/loop-design-check/SKILL.md +143 -143
  253. package/skills/mailtrap-email-integration/SKILL.md +77 -77
  254. package/skills/make-interfaces-feel-better/SKILL.md +152 -152
  255. package/skills/manim-video/SKILL.md +90 -90
  256. package/skills/market-research/SKILL.md +76 -76
  257. package/skills/marketing-campaign/SKILL.md +114 -114
  258. package/skills/mcp-server-patterns/SKILL.md +70 -70
  259. package/skills/messages-ops/SKILL.md +105 -105
  260. package/skills/ml-adoption-playbook/SKILL.md +57 -57
  261. package/skills/mle-workflow/SKILL.md +348 -348
  262. package/skills/motion-advanced/SKILL.md +596 -596
  263. package/skills/motion-foundations/SKILL.md +299 -299
  264. package/skills/motion-patterns/SKILL.md +434 -434
  265. package/skills/motion-ui/SKILL.md +576 -576
  266. package/skills/mysql-patterns/SKILL.md +413 -413
  267. package/skills/nanoclaw-repl/SKILL.md +34 -34
  268. package/skills/nestjs-patterns/SKILL.md +231 -231
  269. package/skills/netmiko-ssh-automation/SKILL.md +174 -174
  270. package/skills/network-bgp-diagnostics/SKILL.md +168 -168
  271. package/skills/network-config-validation/SKILL.md +211 -211
  272. package/skills/network-interface-health/SKILL.md +153 -153
  273. package/skills/nextjs-turbopack/SKILL.md +58 -58
  274. package/skills/nodejs-keccak256/SKILL.md +103 -103
  275. package/skills/nutrient-document-processing/SKILL.md +168 -168
  276. package/skills/nuxt4-patterns/SKILL.md +101 -101
  277. package/skills/openclaw-persona-forge/SKILL.md +289 -289
  278. package/skills/openclaw-persona-forge/references/avatar-style.md +124 -124
  279. package/skills/openclaw-persona-forge/references/boundary-rules.md +53 -53
  280. package/skills/openclaw-persona-forge/references/error-handling.md +53 -53
  281. package/skills/openclaw-persona-forge/references/identity-tension.md +48 -48
  282. package/skills/openclaw-persona-forge/references/naming-system.md +39 -39
  283. package/skills/openclaw-persona-forge/references/output-template.md +166 -166
  284. package/skills/opensource-pipeline/SKILL.md +256 -256
  285. package/skills/orch-add-feature/SKILL.md +45 -45
  286. package/skills/orch-build-mvp/SKILL.md +49 -49
  287. package/skills/orch-change-feature/SKILL.md +43 -43
  288. package/skills/orch-fix-defect/SKILL.md +43 -43
  289. package/skills/orch-pipeline/SKILL.md +121 -121
  290. package/skills/orch-refine-code/SKILL.md +44 -44
  291. package/skills/parallel-execution-optimizer/SKILL.md +74 -74
  292. package/skills/perl-patterns/SKILL.md +505 -505
  293. package/skills/perl-security/SKILL.md +504 -504
  294. package/skills/perl-testing/SKILL.md +476 -476
  295. package/skills/plan-canvas/SKILL.md +153 -153
  296. package/skills/plan-orchestrate/SKILL.md +263 -263
  297. package/skills/plankton-code-quality/SKILL.md +237 -237
  298. package/skills/postgres-patterns/SKILL.md +148 -148
  299. package/skills/prediction-market-oracle-research/SKILL.md +64 -64
  300. package/skills/prediction-market-risk-review/SKILL.md +61 -61
  301. package/skills/premium-web-design/SKILL.md +154 -154
  302. package/skills/prisma-patterns/SKILL.md +401 -401
  303. package/skills/product-capability/SKILL.md +142 -142
  304. package/skills/product-lens/SKILL.md +93 -93
  305. package/skills/production-audit/SKILL.md +207 -207
  306. package/skills/production-scheduling/SKILL.md +238 -238
  307. package/skills/project-flow-ops/SKILL.md +112 -112
  308. package/skills/prompt-optimizer/SKILL.md +398 -398
  309. package/skills/publish-website/SKILL.md +76 -0
  310. package/skills/python-patterns/SKILL.md +751 -751
  311. package/skills/python-testing/SKILL.md +817 -817
  312. package/skills/pytorch-patterns/SKILL.md +397 -397
  313. package/skills/quality-nonconformance/SKILL.md +260 -260
  314. package/skills/quarkus-patterns/SKILL.md +723 -723
  315. package/skills/quarkus-security/SKILL.md +468 -468
  316. package/skills/quarkus-tdd/SKILL.md +812 -812
  317. package/skills/quarkus-verification/SKILL.md +480 -480
  318. package/skills/ralphinho-rfc-pipeline/SKILL.md +68 -68
  319. package/skills/react-native-patterns/SKILL.md +326 -326
  320. package/skills/react-patterns/SKILL.md +342 -342
  321. package/skills/react-performance/SKILL.md +575 -575
  322. package/skills/react-testing/SKILL.md +424 -424
  323. package/skills/recsys-pipeline-architect/SKILL.md +115 -115
  324. package/skills/recursive-decision-ledger/SKILL.md +81 -81
  325. package/skills/redis-patterns/SKILL.md +404 -404
  326. package/skills/regex-vs-llm-structured-text/SKILL.md +221 -221
  327. package/skills/remotion-video-creation/SKILL.md +43 -43
  328. package/skills/repo-scan/SKILL.md +79 -79
  329. package/skills/research-ops/SKILL.md +113 -113
  330. package/skills/returns-reverse-logistics/SKILL.md +240 -240
  331. package/skills/rules-distill/SKILL.md +265 -265
  332. package/skills/rust-patterns/SKILL.md +500 -500
  333. package/skills/rust-testing/SKILL.md +501 -501
  334. package/skills/safety-guard/SKILL.md +76 -76
  335. package/skills/santa-method/SKILL.md +307 -307
  336. package/skills/scientific-db-pubmed-database/SKILL.md +176 -176
  337. package/skills/scientific-db-uspto-database/SKILL.md +178 -178
  338. package/skills/scientific-pkg-gget/SKILL.md +167 -167
  339. package/skills/scientific-thinking-literature-review/SKILL.md +193 -193
  340. package/skills/scientific-thinking-scholar-evaluation/SKILL.md +161 -161
  341. package/skills/search-first/SKILL.md +183 -183
  342. package/skills/security-bounty-hunter/SKILL.md +100 -100
  343. package/skills/security-review/SKILL.md +504 -504
  344. package/skills/security-scan/SKILL.md +166 -166
  345. package/skills/seo/SKILL.md +155 -155
  346. package/skills/skill-comply/SKILL.md +59 -59
  347. package/skills/skill-scout/SKILL.md +141 -141
  348. package/skills/skill-stocktake/SKILL.md +195 -195
  349. package/skills/social-graph-ranker/SKILL.md +155 -155
  350. package/skills/social-publisher/SKILL.md +130 -130
  351. package/skills/springboot-patterns/SKILL.md +315 -315
  352. package/skills/springboot-security/SKILL.md +273 -273
  353. package/skills/springboot-tdd/SKILL.md +159 -159
  354. package/skills/springboot-verification/SKILL.md +232 -232
  355. package/skills/strategic-compact/SKILL.md +142 -142
  356. package/skills/swift-actor-persistence/SKILL.md +144 -144
  357. package/skills/swift-concurrency-6-2/SKILL.md +216 -216
  358. package/skills/swift-protocol-di-testing/SKILL.md +191 -191
  359. package/skills/swiftui-patterns/SKILL.md +259 -259
  360. package/skills/taste/SKILL.md +264 -264
  361. package/skills/taste/references/genre-taxonomy.md +87 -87
  362. package/skills/tdd-workflow/SKILL.md +583 -583
  363. package/skills/team-agent-orchestration/SKILL.md +111 -111
  364. package/skills/team-builder/SKILL.md +169 -169
  365. package/skills/terminal-ops/SKILL.md +110 -110
  366. package/skills/tinystruct-patterns/SKILL.md +279 -279
  367. package/skills/tinystruct-patterns/references/architecture.md +90 -90
  368. package/skills/tinystruct-patterns/references/data-handling.md +60 -60
  369. package/skills/tinystruct-patterns/references/database.md +99 -99
  370. package/skills/tinystruct-patterns/references/routing.md +64 -64
  371. package/skills/tinystruct-patterns/references/system-usage.md +97 -97
  372. package/skills/tinystruct-patterns/references/testing.md +72 -72
  373. package/skills/token-budget-advisor/SKILL.md +134 -134
  374. package/skills/ui-demo/SKILL.md +466 -466
  375. package/skills/ui-to-vue/SKILL.md +135 -135
  376. package/skills/uncloud/SKILL.md +344 -344
  377. package/skills/unified-memory/SKILL.md +170 -170
  378. package/skills/unified-notifications-ops/SKILL.md +188 -188
  379. package/skills/verification-loop/SKILL.md +129 -129
  380. package/skills/video-editing/SKILL.md +311 -311
  381. package/skills/videodb/SKILL.md +375 -375
  382. package/skills/visa-doc-translate/SKILL.md +117 -117
  383. package/skills/vite-patterns/SKILL.md +450 -450
  384. package/skills/vue-patterns/SKILL.md +471 -471
  385. package/skills/windows-desktop-e2e/SKILL.md +888 -888
  386. package/skills/workspace-surface-audit/SKILL.md +126 -126
  387. package/skills/x-api/SKILL.md +235 -235
@@ -1,948 +1,948 @@
1
- ---
2
- name: laravel-security
3
- description: Laravel security best practices — authentication, authorization, Eloquent safety, CSRF, XSS prevention, API security, and secure deployment configurations.
4
- metadata:
5
- origin: ECC
6
- ---
7
-
8
- # Laravel Security Best Practices
9
-
10
- Comprehensive security guidelines for Laravel applications to protect against common vulnerabilities.
11
-
12
- ## When to Activate
13
-
14
- - Setting up Laravel authentication and authorization (Sanctum, Passport, Jetstream, Breeze)
15
- - Implementing user roles, permissions, and policies
16
- - Configuring production security settings and environment variables
17
- - Reviewing Laravel applications for security vulnerabilities
18
- - Deploying Laravel applications to production
19
- - Writing secure Eloquent queries and migrations
20
-
21
- ## Production Configuration
22
-
23
- ### Essential Production Settings
24
-
25
- ```php
26
- // config/app.php
27
- 'env' => env('APP_ENV', 'production'),
28
- 'debug' => (bool) env('APP_DEBUG', false), // CRITICAL: Never true in production
29
- 'key' => env('APP_KEY'), // Must be set: php artisan key:generate
30
-
31
- // config/session.php
32
- 'secure' => env('SESSION_SECURE_COOKIE', true),
33
- 'http_only' => true,
34
- 'same_site' => 'lax',
35
-
36
- // Verify APP_KEY is set at boot
37
- // bootstrap/app.php or a service provider
38
- if (empty(config('app.key'))) {
39
- throw new RuntimeException('APP_KEY is not set. Run: php artisan key:generate');
40
- }
41
- ```
42
-
43
- ### Environment File Security
44
-
45
- ```bash
46
- # NEVER commit .env to version control
47
- # .gitignore already includes .env by default
48
-
49
- # Use .env.example with placeholders instead
50
- DB_PASSWORD=
51
- APP_KEY=
52
- SANCTUM_TOKEN_PREFIX=
53
-
54
- # Validate required variables at boot
55
- // In AppServiceProvider::boot()
56
- $requiredKeys = ['app.key', 'database.connections.mysql.database', 'database.connections.mysql.username'];
57
- foreach ($requiredKeys as $key) {
58
- if (empty(config($key))) {
59
- throw new RuntimeException("Missing required config key: {$key}");
60
- }
61
- }
62
- ```
63
-
64
- ### HTTPS Enforcement
65
-
66
- ```php
67
- // AppServiceProvider::boot() or middleware
68
- if (app()->environment('production')) {
69
- URL::forceScheme('https');
70
- request()->server->set('HTTPS', 'on');
71
- }
72
-
73
- // config/app.php for trusted proxies (load balancers)
74
- // Use specific IP ranges — * trusts all, allowing X-Forwarded-* spoofing
75
- // AWS: '10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16'
76
- 'trusted_proxies' => ['10.0.0.0/8', '172.16.0.0/12'],
77
-
78
- // Force HTTPS in production via middleware
79
- // app/Http/Middleware/ForceHttps.php
80
- public function handle($request, Closure $next)
81
- {
82
- if (!$request->secure() && app()->environment('production')) {
83
- return redirect()->secure($request->getRequestUri());
84
- }
85
- return $next($request);
86
- }
87
- ```
88
-
89
- ## Authentication
90
-
91
- ### Sanctum (API Token Authentication)
92
-
93
- ```php
94
- // config/sanctum.php
95
- 'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf(
96
- '%s%s',
97
- 'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1',
98
- env('APP_URL') ? ',' . parse_url(env('APP_URL'), PHP_URL_HOST) : ''
99
- )));
100
-
101
- 'expiration' => 60 * 24, // Token expiration in minutes (null = never)
102
- 'token_prefix' => env('SANCTUM_TOKEN_PREFIX', ''),
103
-
104
- // Issuing tokens with abilities
105
- $token = $user->createToken('api-token', ['read', 'write'])->plainTextToken;
106
-
107
- // Validate abilities on routes
108
- Route::middleware('auth:sanctum')->group(function () {
109
- Route::get('/orders', function () {
110
- // User must have 'read' ability
111
- abort_unless(Auth::user()->tokenCan('read'), 403);
112
- // ...
113
- })->middleware('abilities:read');
114
-
115
- Route::post('/orders', function () {
116
- // User must have 'write' ability
117
- abort_unless(Auth::user()->tokenCan('write'), 403);
118
- // ...
119
- })->middleware('abilities:write');
120
- });
121
- ```
122
-
123
- ### Password Security
124
-
125
- ```php
126
- // config/hashing.php
127
- // Default is bcrypt. Argon2id is stronger.
128
- 'bcrypt' => [
129
- 'rounds' => env('BCRYPT_ROUNDS', 12), // Increase for stronger hashing
130
- ],
131
-
132
- 'argon' => [
133
- 'memory' => 65536,
134
- 'threads' => 4,
135
- 'time' => 4,
136
- ],
137
-
138
- // Password validation in RegisterRequest
139
- public function rules(): array
140
- {
141
- return [
142
- 'password' => [
143
- 'required',
144
- 'confirmed',
145
- Password::min(12)
146
- ->letters()
147
- ->mixedCase()
148
- ->numbers()
149
- ->symbols()
150
- ->uncompromised(), // Checks haveibeenpwned
151
- ],
152
- ];
153
- }
154
-
155
- // Rate limit login attempts
156
- // App\Http\Controllers\Auth\AuthenticatedSessionController
157
- protected function authenticated(Request $request, $user)
158
- {
159
- if ($user->wasRecentlyLockedOut()) {
160
- // Notify user of suspicious login
161
- $user->notify(new SuspiciousLoginNotification($request->ip()));
162
- }
163
- }
164
- ```
165
-
166
- ### Session Management
167
-
168
- ```php
169
- // config/session.php
170
- 'driver' => env('SESSION_DRIVER', 'database'), // database/redis > file
171
- 'lifetime' => env('SESSION_LIFETIME', 120),
172
- 'expire_on_close' => env('SESSION_EXPIRE_ON_CLOSE', false),
173
- 'encrypt' => env('SESSION_ENCRYPT', false),
174
-
175
- // Regenerate session on login
176
- // App\Http\Controllers\Auth\AuthenticatedSessionController
177
- public function store(LoginRequest $request): RedirectResponse
178
- {
179
- $request->authenticate();
180
- $request->session()->regenerate(); // CRITICAL: prevents session fixation
181
- return redirect()->intended(RouteServiceProvider::HOME);
182
- }
183
-
184
- // Invalidate session on logout
185
- public function destroy(Request $request): RedirectResponse
186
- {
187
- Auth::guard('web')->logout();
188
- $request->session()->invalidate();
189
- $request->session()->regenerateToken();
190
- return redirect('/');
191
- }
192
- ```
193
-
194
- ## Authorization
195
-
196
- ### Gates
197
-
198
- ```php
199
- // App\Providers\AuthServiceProvider
200
- use App\Models\Post;
201
- use App\Models\User;
202
- use Illuminate\Support\Facades\Gate;
203
-
204
- public function boot(): void
205
- {
206
- Gate::define('update-post', function (User $user, Post $post): bool {
207
- return $user->id === $post->user_id;
208
- });
209
-
210
- Gate::define('publish-post', function (User $user): bool {
211
- return $user->role === 'editor' || $user->role === 'admin';
212
- });
213
-
214
- // Using before() for super-admin override
215
- Gate::before(function (User $user, string $ability): ?bool {
216
- if ($user->role === 'super-admin') {
217
- return true; // Grants all abilities
218
- }
219
- return null; // Fall through to normal checks
220
- });
221
- }
222
-
223
- // Usage in controllers
224
- public function update(Request $request, Post $post): RedirectResponse
225
- {
226
- Gate::authorize('update-post', $post);
227
- // Or: $this->authorize('update-post', $post);
228
- // Or: abort_unless(Auth::user()->can('update-post', $post), 403);
229
- // ...
230
- }
231
- ```
232
-
233
- ### Policies
234
-
235
- ```php
236
- // App\Policies\PostPolicy
237
- class PostPolicy
238
- {
239
- use HandlesAuthorization;
240
-
241
- public function viewAny(?User $user): bool
242
- {
243
- return true; // Public listing
244
- }
245
-
246
- public function view(?User $user, Post $post): bool
247
- {
248
- return $post->is_published || ($user && $user->id === $post->user_id);
249
- }
250
-
251
- public function create(User $user): bool
252
- {
253
- return $user->hasVerifiedEmail(); // Must verify email first
254
- }
255
-
256
- public function update(User $user, Post $post): bool
257
- {
258
- return $user->id === $post->user_id;
259
- }
260
-
261
- public function delete(User $user, Post $post): bool
262
- {
263
- return $user->id === $post->user_id && $post->created_at->diffInDays(now()) <= 30;
264
- }
265
-
266
- public function restore(User $user, Post $post): bool
267
- {
268
- return $user->role === 'admin';
269
- }
270
-
271
- public function forceDelete(User $user, Post $post): bool
272
- {
273
- return $user->role === 'super-admin';
274
- }
275
- }
276
-
277
- // Register in AuthServiceProvider
278
- protected $policies = [
279
- Post::class => PostPolicy::class,
280
- ];
281
-
282
- // Controller usage
283
- public function show(Post $post): View
284
- {
285
- $this->authorize('view', $post);
286
- return view('posts.show', compact('post'));
287
- }
288
-
289
- // Blade usage
290
- @can('update', $post)
291
- <a href="{{ route('posts.edit', $post) }}">Edit</a>
292
- @endcan
293
-
294
- @cannot('update', $post)
295
- <span>You cannot edit this post</span>
296
- @endcannot
297
- ```
298
-
299
- ### Middleware Authorization
300
-
301
- ```php
302
- // Using middleware in routes
303
- Route::put('/posts/{post}', [PostController::class, 'update'])
304
- ->middleware('can:update,post');
305
-
306
- Route::get('/posts/create', [PostController::class, 'create'])
307
- ->middleware('can:create,App\Models\Post');
308
-
309
- // Custom authorization middleware
310
- // app/Http/Middleware/CheckRole.php
311
- class CheckRole
312
- {
313
- public function handle(Request $request, Closure $next, string $role): mixed
314
- {
315
- if (!$request->user() || $request->user()->role !== $role) {
316
- abort(403, 'Unauthorized. This area requires role: ' . $role);
317
- }
318
- return $next($request);
319
- }
320
- }
321
-
322
- // Register in Kernel
323
- protected $routeMiddleware = [
324
- 'role' => \App\Http\Middleware\CheckRole::class,
325
- ];
326
-
327
- // Route usage
328
- Route::middleware(['auth', 'role:admin'])->group(function () {
329
- Route::get('/admin', [AdminController::class, 'index']);
330
- });
331
- ```
332
-
333
- ## Eloquent Security
334
-
335
- ### Mass Assignment Protection
336
-
337
- ```php
338
- // BAD: $guarded = [] allows ALL columns to be mass-assigned
339
- // NEVER use $guarded = [] in production
340
-
341
- // GOOD: Whitelist fillable attributes
342
- final class User extends Authenticatable
343
- {
344
- protected $fillable = [
345
- 'name',
346
- 'email',
347
- 'phone',
348
- 'avatar',
349
- ];
350
- // NEVER add 'role', 'is_admin', 'is_verified' here
351
- }
352
-
353
- // GOOD: Explicitly control which fields can be filled in requests
354
- public function store(StoreUserRequest $request): RedirectResponse
355
- {
356
- $user = User::create($request->safe()->only([
357
- 'name', 'email', 'phone', 'avatar'
358
- ]));
359
- // $request->safe() uses validated data only
360
- // $request->only() is NOT safe on its own without validation rules
361
- }
362
-
363
- // BAD: Creating a user with request data directly
364
- User::create($request->all()); // VULNERABLE to mass assignment!
365
-
366
- // BETTER: Use DTOs for creation
367
- $user = User::create($request->validated()); // Only validated fields
368
- ```
369
-
370
- ### SQL Injection Prevention
371
-
372
- ```php
373
- // GOOD: Eloquent automatically parameterizes queries
374
- User::where('email', $userInput)->first();
375
- User::whereRaw('email = ?', [$userInput])->first();
376
-
377
- // GOOD: Query Builder also parameterizes
378
- DB::table('users')->where('email', $userInput)->first();
379
- DB::select('SELECT * FROM users WHERE email = ?', [$userInput]);
380
-
381
- // BAD: Raw string interpolation
382
- DB::select("SELECT * FROM users WHERE email = '{$userInput}'"); // VULNERABLE!
383
- User::whereRaw("email = '{$userInput}'")->first(); // VULNERABLE!
384
-
385
- // BAD: whereRaw/orderByRaw with unescaped input
386
- User::orderByRaw($userInput); // VULNERABLE!
387
- User::groupByRaw($userInput); // VULNERABLE!
388
-
389
- // BAD: DB::statement with concatenation
390
- DB::statement("INSERT INTO users (email) VALUES ('{$userInput}')"); // VULNERABLE!
391
- ```
392
-
393
- ### Attribute Casting
394
-
395
- ```php
396
- final class User extends Authenticatable
397
- {
398
- protected $casts = [
399
- 'email_verified_at' => 'datetime',
400
- 'is_admin' => 'boolean', // Cast to boolean prevents string injection
401
- 'settings' => 'array', // Automatically json_encode/json_decode
402
- 'metadata' => 'encrypted:array', // Laravel 11+ encrypted casting
403
- 'password' => 'hashed', // Laravel 10+ auto-hashes on set
404
- ];
405
- }
406
- ```
407
-
408
- ### Model Security
409
-
410
- ```php
411
- final class User extends Authenticatable
412
- {
413
- // Hide sensitive attributes from JSON/API responses
414
- protected $hidden = [
415
- 'password',
416
- 'remember_token',
417
- 'two_factor_secret',
418
- 'two_factor_recovery_codes',
419
- ];
420
-
421
- // Append only safe computed attributes
422
- protected $appends = ['full_name']; // safe
423
- // NEVER append sensitive computed data
424
- }
425
-
426
- final class Post extends Model
427
- {
428
- // Global scope to filter soft deleted records
429
- use SoftDeletes;
430
-
431
- // Prevent N+1 by restricting lazy loading (optional strict mode)
432
- // AppServiceProvider::boot()
433
- // Model::preventLazyLoading(!app()->isProduction());
434
- }
435
- ```
436
-
437
- ## CSRF Protection
438
-
439
- ### Default Protection
440
-
441
- ```php
442
- // Laravel CSRF is enabled by default via VerifyCsrfToken middleware
443
- // app/Http/Kernel.php (protected $middlewareGroups['web'])
444
-
445
- // All POST/PUT/PATCH/DELETE forms must include @csrf
446
- <form method="POST" action="/posts">
447
- @csrf
448
- <input type="text" name="title">
449
- <button type="submit">Create</button>
450
- </form>
451
- ```
452
-
453
- ### Excluding Routes (Carefully)
454
-
455
- ```php
456
- // app/Http/Middleware/VerifyCsrfToken.php
457
- class VerifyCsrfToken extends Middleware
458
- {
459
- // Only exclude routes that have external CSRF protection (webhooks, etc.)
460
- protected $except = [
461
- 'stripe/*', // Stripe webhooks use their own signature verification
462
- // Avoid blanket 'api/*' — stateful Sanctum routes need CSRF.
463
- // Exclude only specific stateless webhook/endpoint routes.
464
- ];
465
- }
466
- ```
467
-
468
- ### CSRF with JavaScript
469
-
470
- ```html
471
- <meta name="csrf-token" content="{{ csrf_token() }}">
472
-
473
- <script>
474
- // Axios example (Laravel ships with Axios)
475
- axios.defaults.headers.common['X-CSRF-TOKEN'] = document.querySelector(
476
- 'meta[name="csrf-token"]'
477
- ).getAttribute('content');
478
-
479
- // Fetch example
480
- fetch('/posts', {
481
- method: 'POST',
482
- headers: {
483
- 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content'),
484
- 'Content-Type': 'application/json',
485
- },
486
- body: JSON.stringify(data),
487
- });
488
- </script>
489
- ```
490
-
491
- ## XSS Prevention
492
-
493
- ### Blade Templating Security
494
-
495
- ```blade
496
- {{-- SAFE: Auto-escaped by Blade --}}
497
- {{ $userInput }}
498
-
499
- {{-- DANGEROUS: Raw output — NEVER use with user input --}}
500
- {!! $userInput !!}
501
-
502
- {{-- SAFE: Only use {!! !!} with trusted content you control --}}
503
- {!! $trustedHtmlFromYourServer !!}
504
-
505
- {{-- GOOD: Use specific escaping directives --}}
506
- @js($data) {{-- JSON encode for JavaScript --}}
507
- @json($data) {{-- JSON encode in templates --}}
508
-
509
- {{-- BAD: Direct user input in raw HTML --}}
510
- <div>{!! $user->bio !!}</div> {{-- VULNERABLE if user provides bio --}}
511
- ```
512
-
513
- ### Safe HTML Handling
514
-
515
- ```php
516
- // When you must allow some HTML, use a whitelist approach
517
- use HTMLPurifier; // Requires: composer require ezyang/htmlpurifier
518
-
519
- public function sanitizeHtml(string $dirty): string
520
- {
521
- $config = \HTMLPurifier_Config::createDefault();
522
- $config->set('HTML.Allowed', 'p,b,i,a[href],ul,ol,li,br');
523
- $config->set('URI.AllowedSchemes', ['http', 'https', 'mailto']);
524
- $purifier = new \HTMLPurifier($config);
525
- return $purifier->purify($dirty);
526
- }
527
-
528
- // In blade:
529
- <div>{!! $sanitizedContent !!}</div> {{-- Safe after purification --}}
530
- ```
531
-
532
- ### JavaScript Context Escaping
533
-
534
- ```blade
535
- {{-- SAFE: Blade @js escapes for JavaScript context --}}
536
- <script>
537
- const user = @js($user); // JSON + escaped for JS context
538
- const settings = @json($settings); // Direct JSON encode
539
- </script>
540
-
541
- {{-- DANGEROUS: Manual JSON in JS context --}}
542
- <script>
543
- const user = {{ json_encode($user) }}; // NOT escaped for JS!
544
- </script>
545
- ```
546
-
547
- ### HTTP Headers for XSS Protection
548
-
549
- ```php
550
- // App\Http\Middleware\SecurityHeaders.php
551
- class SecurityHeaders
552
- {
553
- public function handle(Request $request, Closure $next): mixed
554
- {
555
- $response = $next($request);
556
-
557
- $response->headers->set('X-Content-Type-Options', 'nosniff');
558
- $response->headers->set('X-Frame-Options', 'DENY');
559
- $response->headers->set('X-XSS-Protection', '1; mode=block');
560
- $response->headers->set('Referrer-Policy', 'strict-origin-when-cross-origin');
561
- $response->headers->set(
562
- 'Content-Security-Policy',
563
- "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; connect-src 'self'; frame-ancestors 'none'"
564
- );
565
-
566
- return $response;
567
- }
568
- }
569
-
570
- // Register in kernel
571
- protected $middleware = [
572
- \App\Http\Middleware\SecurityHeaders::class,
573
- ];
574
- ```
575
-
576
- ## Input Validation
577
-
578
- ### Form Request Validation
579
-
580
- ```php
581
- final class StorePostRequest extends FormRequest
582
- {
583
- public function authorize(): bool
584
- {
585
- return $this->user()?->can('create', Post::class) ?? false;
586
- }
587
-
588
- public function rules(): array
589
- {
590
- return [
591
- 'title' => ['required', 'string', 'max:255', 'sanitize_html'],
592
- 'content' => ['required', 'string', 'max:10000'],
593
- 'image' => [
594
- 'required',
595
- 'image',
596
- 'mimes:jpg,jpeg,png,gif,webp', // Whitelist specific types
597
- 'max:2048', // 2MB max
598
- ],
599
- 'tags' => ['array'],
600
- 'tags.*' => ['integer', 'exists:tags,id'],
601
- ];
602
- }
603
-
604
- public function messages(): array
605
- {
606
- return [
607
- 'title.max' => 'Post title must not exceed 255 characters.',
608
- 'image.max' => 'Image must be under 2MB.',
609
- ];
610
- }
611
-
612
- // Sanitize input after validation
613
- public function validated($key = null, $default = null): mixed
614
- {
615
- $validated = parent::validated();
616
- $validated['title'] = strip_tags($validated['title']);
617
- return $key ? ($validated[$key] ?? $default) : $validated;
618
- }
619
- }
620
- ```
621
-
622
- ### Custom Validation Rules
623
-
624
- ```php
625
- // app/Rules/StrongPassword.php
626
- class StrongPassword implements Rule
627
- {
628
- public function passes($attribute, $value): bool
629
- {
630
- return preg_match('/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&#^()_\-+=])[A-Za-z\d@$!%*?&#^()_\-+=]{12,}$/', $value);
631
- }
632
-
633
- public function message(): string
634
- {
635
- return 'The :attribute must be at least 12 characters with uppercase, lowercase, number, and symbol.';
636
- }
637
- }
638
-
639
- // app/Rules/NotBlacklistedDomain.php
640
- class NotBlacklistedDomain implements Rule
641
- {
642
- private array $blacklisted = ['mailinator.com', 'guerrillamail.com'];
643
-
644
- public function passes($attribute, $value): bool
645
- {
646
- $domain = substr(strrchr($value, '@'), 1);
647
- return !in_array(strtolower($domain), $this->blacklisted);
648
- }
649
-
650
- public function message(): string
651
- {
652
- return 'Email from disposable domains is not allowed.';
653
- }
654
- }
655
- ```
656
-
657
- ## API Security
658
-
659
- ### Rate Limiting
660
-
661
- ```php
662
- // App/Providers/RouteServiceProvider
663
- protected function configureRateLimiting(): void
664
- {
665
- RateLimiter::for('api', function (Request $request) {
666
- return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
667
- });
668
-
669
- RateLimiter::for('auth', function (Request $request) {
670
- return Limit::perMinute(5)->by($request->ip())
671
- ->response(function () {
672
- return response()->json([
673
- 'message' => 'Too many login attempts. Try again in 1 minute.',
674
- ], 429);
675
- });
676
- });
677
-
678
- RateLimiter::for('uploads', function (Request $request) {
679
- return Limit::perHour(10)->by($request->user()?->id ?? $request->ip())
680
- ->response(function () {
681
- return response()->json([
682
- 'message' => 'Upload limit reached. Try again later.',
683
- ], 429);
684
- });
685
- });
686
- }
687
-
688
- // Route usage
689
- Route::middleware(['auth:sanctum', 'throttle:api'])->group(function () {
690
- Route::apiResource('posts', PostController::class);
691
- });
692
-
693
- Route::post('/login', [AuthController::class, 'login'])
694
- ->middleware('throttle:auth');
695
- ```
696
-
697
- ### API Authentication — Sanctum vs Passport
698
-
699
- ```php
700
- // Sanctum (recommended for most apps — simple, first-party, SPA)
701
- // config/sanctum.php
702
- 'expiration' => 60 * 24, // Tokens expire after 24 hours
703
- 'model' => User::class,
704
-
705
- // Issuing scoped tokens
706
- $token = $user->createToken('client-name', [
707
- 'posts:read',
708
- 'posts:write',
709
- ])->plainTextToken;
710
-
711
- // Middleware scoping
712
- Route::middleware('auth:sanctum')->group(function () {
713
- Route::get('/posts', [PostController::class, 'index'])
714
- ->middleware('abilities:posts:read');
715
-
716
- Route::post('/posts', [PostController::class, 'store'])
717
- ->middleware('abilities:posts:write');
718
- });
719
-
720
- // Passport (OAuth2 — for third-party clients or complex auth flows)
721
- // Install: composer require laravel/passport
722
- Passport::tokensExpireIn(now()->addDays(15));
723
- Passport::refreshTokensExpireIn(now()->addDays(30));
724
- Passport::personalAccessTokensExpireIn(now()->addMonths(6));
725
- ```
726
-
727
- ### CORS Configuration
728
-
729
- ```php
730
- // config/cors.php
731
- return [
732
- 'paths' => ['api/*', 'sanctum/csrf-cookie'],
733
- 'allowed_methods' => ['*'],
734
- 'allowed_origins' => explode(',', env('CORS_ALLOWED_ORIGINS', '')), // Whitelist specific origins
735
- 'allowed_origins_patterns' => [],
736
- 'allowed_headers' => ['*'],
737
- 'exposed_headers' => ['X-Total-Count', 'X-Pagination-Page'],
738
- 'max_age' => 0,
739
- 'supports_credentials' => true, // Required for Sanctum SPA auth
740
- ];
741
-
742
- // NEVER: Allow all origins in production unless absolutely necessary
743
- // 'allowed_origins' => ['*'], // Only for truly public APIs
744
- ```
745
-
746
- ## File Upload Security
747
-
748
- ### Validation
749
-
750
- ```php
751
- public function rules(): array
752
- {
753
- return [
754
- 'document' => [
755
- 'required',
756
- 'file',
757
- 'mimes:pdf,doc,docx,xls,xlsx', // Whitelist specific MIME types
758
- 'max:10240', // 10MB
759
- 'extensions:pdf,doc,docx,xls,xlsx', // Verify extension matches MIME
760
- ],
761
- 'avatar' => [
762
- 'nullable',
763
- 'image', // Ensures it's a valid image
764
- 'mimes:jpg,jpeg,png,webp',
765
- 'max:2048',
766
- 'dimensions:min_width=100,min_height=100,max_width=2000,max_height=2000',
767
- ],
768
- ];
769
- }
770
- ```
771
-
772
- ### Secure Storage
773
-
774
- ```php
775
- // Store files outside public directory
776
- $path = $request->file('document')->store('documents', 'local');
777
- // Never use 'public' disk for sensitive documents
778
-
779
- // Use signed URLs for temporary file access
780
- use Illuminate\Support\Facades\Storage;
781
-
782
- public function download(Request $request, string $path)
783
- {
784
- // Generate temporary signed URL (expires in 15 minutes)
785
- $url = Storage::temporaryUrl($path, now()->addMinutes(15));
786
-
787
- // Validate user has permission
788
- $this->authorize('download', $path);
789
-
790
- return redirect($url);
791
- }
792
-
793
- // Storage configuration for cloud with encryption
794
- // config/filesystems.php
795
- 's3' => [
796
- 'driver' => 's3',
797
- 'key' => env('AWS_ACCESS_KEY_ID'),
798
- 'secret' => env('AWS_SECRET_ACCESS_KEY'),
799
- 'region' => env('AWS_DEFAULT_REGION'),
800
- 'bucket' => env('AWS_BUCKET'),
801
- 'url' => env('AWS_URL'),
802
- 'endpoint' => env('AWS_ENDPOINT'),
803
- 'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false),
804
- 'throw' => false,
805
- 'server_side_encryption' => 'AES256', // Encrypt at rest
806
- ],
807
- ```
808
-
809
- ## Dependencies and Secrets
810
-
811
- ### Composer Security
812
-
813
- ```bash
814
- # Always audit dependencies in CI
815
- composer audit
816
-
817
- # Pin major versions in composer.json
818
- "laravel/framework": "^11.0",
819
- "spatie/laravel-permission": "^6.0"
820
-
821
- # Check for abandoned packages
822
- composer why-not
823
-
824
- # Keep lock file in version control (it pins exact versions)
825
- # Run `composer update` deliberately, never in CI/CD
826
- ```
827
-
828
- ### Secret Management
829
-
830
- ```bash
831
- # .env file (NEVER commit)
832
- # .gitignore includes .env by default
833
-
834
- APP_KEY=base64:abc123...
835
- DB_PASSWORD=secure_password
836
- STRIPE_KEY=sk_live_...
837
- SANCTUM_TOKEN_PREFIX=myapp_
838
-
839
- # For production: Use a secret manager
840
- # Deploy with: env $(aws secretsmanager get-secret-value --secret-id prod/db | jq ...) php artisan serve
841
-
842
- # Validate secrets at boot (AppServiceProvider::boot)
843
- $secrets = ['services.stripe.key', 'services.stripe.webhook_secret'];
844
- foreach ($secrets as $key) {
845
- if (empty(config($key))) {
846
- Log::critical("Missing secret: {$key}");
847
- }
848
- }
849
- ```
850
-
851
- ## Queue Security
852
-
853
- ```php
854
- // Define a named rate limiter (typically in AppServiceProvider::boot())
855
- RateLimiter::for('payments', fn () => Limit::perMinute(5));
856
- ```
857
-
858
- ```php
859
- // Encrypt sensitive job data by implementing the interface
860
- final class ProcessPaymentJob implements ShouldQueue, ShouldBeEncrypted
861
- {
862
- use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
863
-
864
- public function __construct(
865
- private readonly string $paymentIntentId, // Public IDs are fine
866
- private readonly string $cardFingerprint, // Encrypted via ShouldBeEncrypted
867
- ) {}
868
-
869
- public function handle(): void
870
- {
871
- // Process payment
872
- }
873
-
874
- // Limit retries and delay between attempts
875
- public function retryUntil(): Carbon
876
- {
877
- return now()->addMinutes(5);
878
- }
879
-
880
- // Rate limit how many jobs of this type can run
881
- public function middleware(): array
882
- {
883
- return [
884
- new RateLimited('payments'),
885
- ];
886
- }
887
- }
888
- ```
889
-
890
- ## Logging Security Events
891
-
892
- ```php
893
- // config/logging.php
894
- 'channels' => [
895
- 'security' => [
896
- 'driver' => 'single',
897
- 'path' => storage_path('logs/security.log'),
898
- 'level' => 'warning',
899
- ],
900
- ],
901
-
902
- // Audit log helper
903
- final class SecurityLogger
904
- {
905
- public static function log(string $event, array $context = []): void
906
- {
907
- Log::channel('security')->warning($event, array_merge([
908
- 'user_id' => Auth::id(),
909
- 'ip' => request()->ip(),
910
- 'user_agent' => request()->userAgent(),
911
- 'url' => request()->fullUrl(),
912
- 'timestamp' => now()->toIso8601String(),
913
- ], $context));
914
- }
915
- }
916
-
917
- // Usage
918
- SecurityLogger::log('failed_login_attempt', ['email' => $email]);
919
- SecurityLogger::log('password_change');
920
- SecurityLogger::log('role_change', ['target_user' => $targetId, 'new_role' => 'admin']);
921
- SecurityLogger::log('suspicious_activity', ['reason' => 'multiple_attempts_from_different_ips']);
922
- ```
923
-
924
- ## Quick Security Checklist
925
-
926
- | Check | Description |
927
- |-------|-------------|
928
- | `APP_DEBUG=false` | Never run with debug enabled in production |
929
- | `APP_KEY` set | Always run `php artisan key:generate` |
930
- | HTTPS enforced | Force HTTPS in production via middleware or proxy |
931
- | `$fillable` whitelisted | Never use `$guarded = []` |
932
- | CSRF active | `@csrf` on all state-changing forms |
933
- | Sanctum/Passport configured | API authentication with token abilities/scopes |
934
- | Rate limiting applied | Throttle API and auth endpoints |
935
- | Input validation | FormRequest with specific rules, never `$request->all()` |
936
- | File upload restrictions | Validate MIME types, size, dimensions |
937
- | `composer audit` in CI | Check dependencies for known vulnerabilities |
938
- | `password_hash` / `password_verify` | Use Laravel's built-in hashing (bcrypt/Argon2) |
939
- | Session regeneration on login | Call `$request->session()->regenerate()` |
940
- | Security headers middleware | CSP, X-Frame-Options, X-Content-Type-Options |
941
- | Logged security events | Audit log for auth failures, role changes, suspicious activity |
942
- | `.env` not committed | Verify `.gitignore` includes `.env` |
943
-
944
- ## Related Skills
945
-
946
- - `laravel-patterns` — Laravel architecture, routing, Eloquent, and API patterns
947
- - `backend-patterns` — General backend API and database patterns
948
- - `laravel-tdd` — Laravel testing with PHPUnit and Pest
1
+ ---
2
+ name: laravel-security
3
+ description: Laravel security best practices — authentication, authorization, Eloquent safety, CSRF, XSS prevention, API security, and secure deployment configurations.
4
+ metadata:
5
+ origin: ECC
6
+ ---
7
+
8
+ # Laravel Security Best Practices
9
+
10
+ Comprehensive security guidelines for Laravel applications to protect against common vulnerabilities.
11
+
12
+ ## When to Activate
13
+
14
+ - Setting up Laravel authentication and authorization (Sanctum, Passport, Jetstream, Breeze)
15
+ - Implementing user roles, permissions, and policies
16
+ - Configuring production security settings and environment variables
17
+ - Reviewing Laravel applications for security vulnerabilities
18
+ - Deploying Laravel applications to production
19
+ - Writing secure Eloquent queries and migrations
20
+
21
+ ## Production Configuration
22
+
23
+ ### Essential Production Settings
24
+
25
+ ```php
26
+ // config/app.php
27
+ 'env' => env('APP_ENV', 'production'),
28
+ 'debug' => (bool) env('APP_DEBUG', false), // CRITICAL: Never true in production
29
+ 'key' => env('APP_KEY'), // Must be set: php artisan key:generate
30
+
31
+ // config/session.php
32
+ 'secure' => env('SESSION_SECURE_COOKIE', true),
33
+ 'http_only' => true,
34
+ 'same_site' => 'lax',
35
+
36
+ // Verify APP_KEY is set at boot
37
+ // bootstrap/app.php or a service provider
38
+ if (empty(config('app.key'))) {
39
+ throw new RuntimeException('APP_KEY is not set. Run: php artisan key:generate');
40
+ }
41
+ ```
42
+
43
+ ### Environment File Security
44
+
45
+ ```bash
46
+ # NEVER commit .env to version control
47
+ # .gitignore already includes .env by default
48
+
49
+ # Use .env.example with placeholders instead
50
+ DB_PASSWORD=
51
+ APP_KEY=
52
+ SANCTUM_TOKEN_PREFIX=
53
+
54
+ # Validate required variables at boot
55
+ // In AppServiceProvider::boot()
56
+ $requiredKeys = ['app.key', 'database.connections.mysql.database', 'database.connections.mysql.username'];
57
+ foreach ($requiredKeys as $key) {
58
+ if (empty(config($key))) {
59
+ throw new RuntimeException("Missing required config key: {$key}");
60
+ }
61
+ }
62
+ ```
63
+
64
+ ### HTTPS Enforcement
65
+
66
+ ```php
67
+ // AppServiceProvider::boot() or middleware
68
+ if (app()->environment('production')) {
69
+ URL::forceScheme('https');
70
+ request()->server->set('HTTPS', 'on');
71
+ }
72
+
73
+ // config/app.php for trusted proxies (load balancers)
74
+ // Use specific IP ranges — * trusts all, allowing X-Forwarded-* spoofing
75
+ // AWS: '10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16'
76
+ 'trusted_proxies' => ['10.0.0.0/8', '172.16.0.0/12'],
77
+
78
+ // Force HTTPS in production via middleware
79
+ // app/Http/Middleware/ForceHttps.php
80
+ public function handle($request, Closure $next)
81
+ {
82
+ if (!$request->secure() && app()->environment('production')) {
83
+ return redirect()->secure($request->getRequestUri());
84
+ }
85
+ return $next($request);
86
+ }
87
+ ```
88
+
89
+ ## Authentication
90
+
91
+ ### Sanctum (API Token Authentication)
92
+
93
+ ```php
94
+ // config/sanctum.php
95
+ 'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf(
96
+ '%s%s',
97
+ 'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1',
98
+ env('APP_URL') ? ',' . parse_url(env('APP_URL'), PHP_URL_HOST) : ''
99
+ )));
100
+
101
+ 'expiration' => 60 * 24, // Token expiration in minutes (null = never)
102
+ 'token_prefix' => env('SANCTUM_TOKEN_PREFIX', ''),
103
+
104
+ // Issuing tokens with abilities
105
+ $token = $user->createToken('api-token', ['read', 'write'])->plainTextToken;
106
+
107
+ // Validate abilities on routes
108
+ Route::middleware('auth:sanctum')->group(function () {
109
+ Route::get('/orders', function () {
110
+ // User must have 'read' ability
111
+ abort_unless(Auth::user()->tokenCan('read'), 403);
112
+ // ...
113
+ })->middleware('abilities:read');
114
+
115
+ Route::post('/orders', function () {
116
+ // User must have 'write' ability
117
+ abort_unless(Auth::user()->tokenCan('write'), 403);
118
+ // ...
119
+ })->middleware('abilities:write');
120
+ });
121
+ ```
122
+
123
+ ### Password Security
124
+
125
+ ```php
126
+ // config/hashing.php
127
+ // Default is bcrypt. Argon2id is stronger.
128
+ 'bcrypt' => [
129
+ 'rounds' => env('BCRYPT_ROUNDS', 12), // Increase for stronger hashing
130
+ ],
131
+
132
+ 'argon' => [
133
+ 'memory' => 65536,
134
+ 'threads' => 4,
135
+ 'time' => 4,
136
+ ],
137
+
138
+ // Password validation in RegisterRequest
139
+ public function rules(): array
140
+ {
141
+ return [
142
+ 'password' => [
143
+ 'required',
144
+ 'confirmed',
145
+ Password::min(12)
146
+ ->letters()
147
+ ->mixedCase()
148
+ ->numbers()
149
+ ->symbols()
150
+ ->uncompromised(), // Checks haveibeenpwned
151
+ ],
152
+ ];
153
+ }
154
+
155
+ // Rate limit login attempts
156
+ // App\Http\Controllers\Auth\AuthenticatedSessionController
157
+ protected function authenticated(Request $request, $user)
158
+ {
159
+ if ($user->wasRecentlyLockedOut()) {
160
+ // Notify user of suspicious login
161
+ $user->notify(new SuspiciousLoginNotification($request->ip()));
162
+ }
163
+ }
164
+ ```
165
+
166
+ ### Session Management
167
+
168
+ ```php
169
+ // config/session.php
170
+ 'driver' => env('SESSION_DRIVER', 'database'), // database/redis > file
171
+ 'lifetime' => env('SESSION_LIFETIME', 120),
172
+ 'expire_on_close' => env('SESSION_EXPIRE_ON_CLOSE', false),
173
+ 'encrypt' => env('SESSION_ENCRYPT', false),
174
+
175
+ // Regenerate session on login
176
+ // App\Http\Controllers\Auth\AuthenticatedSessionController
177
+ public function store(LoginRequest $request): RedirectResponse
178
+ {
179
+ $request->authenticate();
180
+ $request->session()->regenerate(); // CRITICAL: prevents session fixation
181
+ return redirect()->intended(RouteServiceProvider::HOME);
182
+ }
183
+
184
+ // Invalidate session on logout
185
+ public function destroy(Request $request): RedirectResponse
186
+ {
187
+ Auth::guard('web')->logout();
188
+ $request->session()->invalidate();
189
+ $request->session()->regenerateToken();
190
+ return redirect('/');
191
+ }
192
+ ```
193
+
194
+ ## Authorization
195
+
196
+ ### Gates
197
+
198
+ ```php
199
+ // App\Providers\AuthServiceProvider
200
+ use App\Models\Post;
201
+ use App\Models\User;
202
+ use Illuminate\Support\Facades\Gate;
203
+
204
+ public function boot(): void
205
+ {
206
+ Gate::define('update-post', function (User $user, Post $post): bool {
207
+ return $user->id === $post->user_id;
208
+ });
209
+
210
+ Gate::define('publish-post', function (User $user): bool {
211
+ return $user->role === 'editor' || $user->role === 'admin';
212
+ });
213
+
214
+ // Using before() for super-admin override
215
+ Gate::before(function (User $user, string $ability): ?bool {
216
+ if ($user->role === 'super-admin') {
217
+ return true; // Grants all abilities
218
+ }
219
+ return null; // Fall through to normal checks
220
+ });
221
+ }
222
+
223
+ // Usage in controllers
224
+ public function update(Request $request, Post $post): RedirectResponse
225
+ {
226
+ Gate::authorize('update-post', $post);
227
+ // Or: $this->authorize('update-post', $post);
228
+ // Or: abort_unless(Auth::user()->can('update-post', $post), 403);
229
+ // ...
230
+ }
231
+ ```
232
+
233
+ ### Policies
234
+
235
+ ```php
236
+ // App\Policies\PostPolicy
237
+ class PostPolicy
238
+ {
239
+ use HandlesAuthorization;
240
+
241
+ public function viewAny(?User $user): bool
242
+ {
243
+ return true; // Public listing
244
+ }
245
+
246
+ public function view(?User $user, Post $post): bool
247
+ {
248
+ return $post->is_published || ($user && $user->id === $post->user_id);
249
+ }
250
+
251
+ public function create(User $user): bool
252
+ {
253
+ return $user->hasVerifiedEmail(); // Must verify email first
254
+ }
255
+
256
+ public function update(User $user, Post $post): bool
257
+ {
258
+ return $user->id === $post->user_id;
259
+ }
260
+
261
+ public function delete(User $user, Post $post): bool
262
+ {
263
+ return $user->id === $post->user_id && $post->created_at->diffInDays(now()) <= 30;
264
+ }
265
+
266
+ public function restore(User $user, Post $post): bool
267
+ {
268
+ return $user->role === 'admin';
269
+ }
270
+
271
+ public function forceDelete(User $user, Post $post): bool
272
+ {
273
+ return $user->role === 'super-admin';
274
+ }
275
+ }
276
+
277
+ // Register in AuthServiceProvider
278
+ protected $policies = [
279
+ Post::class => PostPolicy::class,
280
+ ];
281
+
282
+ // Controller usage
283
+ public function show(Post $post): View
284
+ {
285
+ $this->authorize('view', $post);
286
+ return view('posts.show', compact('post'));
287
+ }
288
+
289
+ // Blade usage
290
+ @can('update', $post)
291
+ <a href="{{ route('posts.edit', $post) }}">Edit</a>
292
+ @endcan
293
+
294
+ @cannot('update', $post)
295
+ <span>You cannot edit this post</span>
296
+ @endcannot
297
+ ```
298
+
299
+ ### Middleware Authorization
300
+
301
+ ```php
302
+ // Using middleware in routes
303
+ Route::put('/posts/{post}', [PostController::class, 'update'])
304
+ ->middleware('can:update,post');
305
+
306
+ Route::get('/posts/create', [PostController::class, 'create'])
307
+ ->middleware('can:create,App\Models\Post');
308
+
309
+ // Custom authorization middleware
310
+ // app/Http/Middleware/CheckRole.php
311
+ class CheckRole
312
+ {
313
+ public function handle(Request $request, Closure $next, string $role): mixed
314
+ {
315
+ if (!$request->user() || $request->user()->role !== $role) {
316
+ abort(403, 'Unauthorized. This area requires role: ' . $role);
317
+ }
318
+ return $next($request);
319
+ }
320
+ }
321
+
322
+ // Register in Kernel
323
+ protected $routeMiddleware = [
324
+ 'role' => \App\Http\Middleware\CheckRole::class,
325
+ ];
326
+
327
+ // Route usage
328
+ Route::middleware(['auth', 'role:admin'])->group(function () {
329
+ Route::get('/admin', [AdminController::class, 'index']);
330
+ });
331
+ ```
332
+
333
+ ## Eloquent Security
334
+
335
+ ### Mass Assignment Protection
336
+
337
+ ```php
338
+ // BAD: $guarded = [] allows ALL columns to be mass-assigned
339
+ // NEVER use $guarded = [] in production
340
+
341
+ // GOOD: Whitelist fillable attributes
342
+ final class User extends Authenticatable
343
+ {
344
+ protected $fillable = [
345
+ 'name',
346
+ 'email',
347
+ 'phone',
348
+ 'avatar',
349
+ ];
350
+ // NEVER add 'role', 'is_admin', 'is_verified' here
351
+ }
352
+
353
+ // GOOD: Explicitly control which fields can be filled in requests
354
+ public function store(StoreUserRequest $request): RedirectResponse
355
+ {
356
+ $user = User::create($request->safe()->only([
357
+ 'name', 'email', 'phone', 'avatar'
358
+ ]));
359
+ // $request->safe() uses validated data only
360
+ // $request->only() is NOT safe on its own without validation rules
361
+ }
362
+
363
+ // BAD: Creating a user with request data directly
364
+ User::create($request->all()); // VULNERABLE to mass assignment!
365
+
366
+ // BETTER: Use DTOs for creation
367
+ $user = User::create($request->validated()); // Only validated fields
368
+ ```
369
+
370
+ ### SQL Injection Prevention
371
+
372
+ ```php
373
+ // GOOD: Eloquent automatically parameterizes queries
374
+ User::where('email', $userInput)->first();
375
+ User::whereRaw('email = ?', [$userInput])->first();
376
+
377
+ // GOOD: Query Builder also parameterizes
378
+ DB::table('users')->where('email', $userInput)->first();
379
+ DB::select('SELECT * FROM users WHERE email = ?', [$userInput]);
380
+
381
+ // BAD: Raw string interpolation
382
+ DB::select("SELECT * FROM users WHERE email = '{$userInput}'"); // VULNERABLE!
383
+ User::whereRaw("email = '{$userInput}'")->first(); // VULNERABLE!
384
+
385
+ // BAD: whereRaw/orderByRaw with unescaped input
386
+ User::orderByRaw($userInput); // VULNERABLE!
387
+ User::groupByRaw($userInput); // VULNERABLE!
388
+
389
+ // BAD: DB::statement with concatenation
390
+ DB::statement("INSERT INTO users (email) VALUES ('{$userInput}')"); // VULNERABLE!
391
+ ```
392
+
393
+ ### Attribute Casting
394
+
395
+ ```php
396
+ final class User extends Authenticatable
397
+ {
398
+ protected $casts = [
399
+ 'email_verified_at' => 'datetime',
400
+ 'is_admin' => 'boolean', // Cast to boolean prevents string injection
401
+ 'settings' => 'array', // Automatically json_encode/json_decode
402
+ 'metadata' => 'encrypted:array', // Laravel 11+ encrypted casting
403
+ 'password' => 'hashed', // Laravel 10+ auto-hashes on set
404
+ ];
405
+ }
406
+ ```
407
+
408
+ ### Model Security
409
+
410
+ ```php
411
+ final class User extends Authenticatable
412
+ {
413
+ // Hide sensitive attributes from JSON/API responses
414
+ protected $hidden = [
415
+ 'password',
416
+ 'remember_token',
417
+ 'two_factor_secret',
418
+ 'two_factor_recovery_codes',
419
+ ];
420
+
421
+ // Append only safe computed attributes
422
+ protected $appends = ['full_name']; // safe
423
+ // NEVER append sensitive computed data
424
+ }
425
+
426
+ final class Post extends Model
427
+ {
428
+ // Global scope to filter soft deleted records
429
+ use SoftDeletes;
430
+
431
+ // Prevent N+1 by restricting lazy loading (optional strict mode)
432
+ // AppServiceProvider::boot()
433
+ // Model::preventLazyLoading(!app()->isProduction());
434
+ }
435
+ ```
436
+
437
+ ## CSRF Protection
438
+
439
+ ### Default Protection
440
+
441
+ ```php
442
+ // Laravel CSRF is enabled by default via VerifyCsrfToken middleware
443
+ // app/Http/Kernel.php (protected $middlewareGroups['web'])
444
+
445
+ // All POST/PUT/PATCH/DELETE forms must include @csrf
446
+ <form method="POST" action="/posts">
447
+ @csrf
448
+ <input type="text" name="title">
449
+ <button type="submit">Create</button>
450
+ </form>
451
+ ```
452
+
453
+ ### Excluding Routes (Carefully)
454
+
455
+ ```php
456
+ // app/Http/Middleware/VerifyCsrfToken.php
457
+ class VerifyCsrfToken extends Middleware
458
+ {
459
+ // Only exclude routes that have external CSRF protection (webhooks, etc.)
460
+ protected $except = [
461
+ 'stripe/*', // Stripe webhooks use their own signature verification
462
+ // Avoid blanket 'api/*' — stateful Sanctum routes need CSRF.
463
+ // Exclude only specific stateless webhook/endpoint routes.
464
+ ];
465
+ }
466
+ ```
467
+
468
+ ### CSRF with JavaScript
469
+
470
+ ```html
471
+ <meta name="csrf-token" content="{{ csrf_token() }}">
472
+
473
+ <script>
474
+ // Axios example (Laravel ships with Axios)
475
+ axios.defaults.headers.common['X-CSRF-TOKEN'] = document.querySelector(
476
+ 'meta[name="csrf-token"]'
477
+ ).getAttribute('content');
478
+
479
+ // Fetch example
480
+ fetch('/posts', {
481
+ method: 'POST',
482
+ headers: {
483
+ 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content'),
484
+ 'Content-Type': 'application/json',
485
+ },
486
+ body: JSON.stringify(data),
487
+ });
488
+ </script>
489
+ ```
490
+
491
+ ## XSS Prevention
492
+
493
+ ### Blade Templating Security
494
+
495
+ ```blade
496
+ {{-- SAFE: Auto-escaped by Blade --}}
497
+ {{ $userInput }}
498
+
499
+ {{-- DANGEROUS: Raw output — NEVER use with user input --}}
500
+ {!! $userInput !!}
501
+
502
+ {{-- SAFE: Only use {!! !!} with trusted content you control --}}
503
+ {!! $trustedHtmlFromYourServer !!}
504
+
505
+ {{-- GOOD: Use specific escaping directives --}}
506
+ @js($data) {{-- JSON encode for JavaScript --}}
507
+ @json($data) {{-- JSON encode in templates --}}
508
+
509
+ {{-- BAD: Direct user input in raw HTML --}}
510
+ <div>{!! $user->bio !!}</div> {{-- VULNERABLE if user provides bio --}}
511
+ ```
512
+
513
+ ### Safe HTML Handling
514
+
515
+ ```php
516
+ // When you must allow some HTML, use a whitelist approach
517
+ use HTMLPurifier; // Requires: composer require ezyang/htmlpurifier
518
+
519
+ public function sanitizeHtml(string $dirty): string
520
+ {
521
+ $config = \HTMLPurifier_Config::createDefault();
522
+ $config->set('HTML.Allowed', 'p,b,i,a[href],ul,ol,li,br');
523
+ $config->set('URI.AllowedSchemes', ['http', 'https', 'mailto']);
524
+ $purifier = new \HTMLPurifier($config);
525
+ return $purifier->purify($dirty);
526
+ }
527
+
528
+ // In blade:
529
+ <div>{!! $sanitizedContent !!}</div> {{-- Safe after purification --}}
530
+ ```
531
+
532
+ ### JavaScript Context Escaping
533
+
534
+ ```blade
535
+ {{-- SAFE: Blade @js escapes for JavaScript context --}}
536
+ <script>
537
+ const user = @js($user); // JSON + escaped for JS context
538
+ const settings = @json($settings); // Direct JSON encode
539
+ </script>
540
+
541
+ {{-- DANGEROUS: Manual JSON in JS context --}}
542
+ <script>
543
+ const user = {{ json_encode($user) }}; // NOT escaped for JS!
544
+ </script>
545
+ ```
546
+
547
+ ### HTTP Headers for XSS Protection
548
+
549
+ ```php
550
+ // App\Http\Middleware\SecurityHeaders.php
551
+ class SecurityHeaders
552
+ {
553
+ public function handle(Request $request, Closure $next): mixed
554
+ {
555
+ $response = $next($request);
556
+
557
+ $response->headers->set('X-Content-Type-Options', 'nosniff');
558
+ $response->headers->set('X-Frame-Options', 'DENY');
559
+ $response->headers->set('X-XSS-Protection', '1; mode=block');
560
+ $response->headers->set('Referrer-Policy', 'strict-origin-when-cross-origin');
561
+ $response->headers->set(
562
+ 'Content-Security-Policy',
563
+ "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; connect-src 'self'; frame-ancestors 'none'"
564
+ );
565
+
566
+ return $response;
567
+ }
568
+ }
569
+
570
+ // Register in kernel
571
+ protected $middleware = [
572
+ \App\Http\Middleware\SecurityHeaders::class,
573
+ ];
574
+ ```
575
+
576
+ ## Input Validation
577
+
578
+ ### Form Request Validation
579
+
580
+ ```php
581
+ final class StorePostRequest extends FormRequest
582
+ {
583
+ public function authorize(): bool
584
+ {
585
+ return $this->user()?->can('create', Post::class) ?? false;
586
+ }
587
+
588
+ public function rules(): array
589
+ {
590
+ return [
591
+ 'title' => ['required', 'string', 'max:255', 'sanitize_html'],
592
+ 'content' => ['required', 'string', 'max:10000'],
593
+ 'image' => [
594
+ 'required',
595
+ 'image',
596
+ 'mimes:jpg,jpeg,png,gif,webp', // Whitelist specific types
597
+ 'max:2048', // 2MB max
598
+ ],
599
+ 'tags' => ['array'],
600
+ 'tags.*' => ['integer', 'exists:tags,id'],
601
+ ];
602
+ }
603
+
604
+ public function messages(): array
605
+ {
606
+ return [
607
+ 'title.max' => 'Post title must not exceed 255 characters.',
608
+ 'image.max' => 'Image must be under 2MB.',
609
+ ];
610
+ }
611
+
612
+ // Sanitize input after validation
613
+ public function validated($key = null, $default = null): mixed
614
+ {
615
+ $validated = parent::validated();
616
+ $validated['title'] = strip_tags($validated['title']);
617
+ return $key ? ($validated[$key] ?? $default) : $validated;
618
+ }
619
+ }
620
+ ```
621
+
622
+ ### Custom Validation Rules
623
+
624
+ ```php
625
+ // app/Rules/StrongPassword.php
626
+ class StrongPassword implements Rule
627
+ {
628
+ public function passes($attribute, $value): bool
629
+ {
630
+ return preg_match('/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&#^()_\-+=])[A-Za-z\d@$!%*?&#^()_\-+=]{12,}$/', $value);
631
+ }
632
+
633
+ public function message(): string
634
+ {
635
+ return 'The :attribute must be at least 12 characters with uppercase, lowercase, number, and symbol.';
636
+ }
637
+ }
638
+
639
+ // app/Rules/NotBlacklistedDomain.php
640
+ class NotBlacklistedDomain implements Rule
641
+ {
642
+ private array $blacklisted = ['mailinator.com', 'guerrillamail.com'];
643
+
644
+ public function passes($attribute, $value): bool
645
+ {
646
+ $domain = substr(strrchr($value, '@'), 1);
647
+ return !in_array(strtolower($domain), $this->blacklisted);
648
+ }
649
+
650
+ public function message(): string
651
+ {
652
+ return 'Email from disposable domains is not allowed.';
653
+ }
654
+ }
655
+ ```
656
+
657
+ ## API Security
658
+
659
+ ### Rate Limiting
660
+
661
+ ```php
662
+ // App/Providers/RouteServiceProvider
663
+ protected function configureRateLimiting(): void
664
+ {
665
+ RateLimiter::for('api', function (Request $request) {
666
+ return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
667
+ });
668
+
669
+ RateLimiter::for('auth', function (Request $request) {
670
+ return Limit::perMinute(5)->by($request->ip())
671
+ ->response(function () {
672
+ return response()->json([
673
+ 'message' => 'Too many login attempts. Try again in 1 minute.',
674
+ ], 429);
675
+ });
676
+ });
677
+
678
+ RateLimiter::for('uploads', function (Request $request) {
679
+ return Limit::perHour(10)->by($request->user()?->id ?? $request->ip())
680
+ ->response(function () {
681
+ return response()->json([
682
+ 'message' => 'Upload limit reached. Try again later.',
683
+ ], 429);
684
+ });
685
+ });
686
+ }
687
+
688
+ // Route usage
689
+ Route::middleware(['auth:sanctum', 'throttle:api'])->group(function () {
690
+ Route::apiResource('posts', PostController::class);
691
+ });
692
+
693
+ Route::post('/login', [AuthController::class, 'login'])
694
+ ->middleware('throttle:auth');
695
+ ```
696
+
697
+ ### API Authentication — Sanctum vs Passport
698
+
699
+ ```php
700
+ // Sanctum (recommended for most apps — simple, first-party, SPA)
701
+ // config/sanctum.php
702
+ 'expiration' => 60 * 24, // Tokens expire after 24 hours
703
+ 'model' => User::class,
704
+
705
+ // Issuing scoped tokens
706
+ $token = $user->createToken('client-name', [
707
+ 'posts:read',
708
+ 'posts:write',
709
+ ])->plainTextToken;
710
+
711
+ // Middleware scoping
712
+ Route::middleware('auth:sanctum')->group(function () {
713
+ Route::get('/posts', [PostController::class, 'index'])
714
+ ->middleware('abilities:posts:read');
715
+
716
+ Route::post('/posts', [PostController::class, 'store'])
717
+ ->middleware('abilities:posts:write');
718
+ });
719
+
720
+ // Passport (OAuth2 — for third-party clients or complex auth flows)
721
+ // Install: composer require laravel/passport
722
+ Passport::tokensExpireIn(now()->addDays(15));
723
+ Passport::refreshTokensExpireIn(now()->addDays(30));
724
+ Passport::personalAccessTokensExpireIn(now()->addMonths(6));
725
+ ```
726
+
727
+ ### CORS Configuration
728
+
729
+ ```php
730
+ // config/cors.php
731
+ return [
732
+ 'paths' => ['api/*', 'sanctum/csrf-cookie'],
733
+ 'allowed_methods' => ['*'],
734
+ 'allowed_origins' => explode(',', env('CORS_ALLOWED_ORIGINS', '')), // Whitelist specific origins
735
+ 'allowed_origins_patterns' => [],
736
+ 'allowed_headers' => ['*'],
737
+ 'exposed_headers' => ['X-Total-Count', 'X-Pagination-Page'],
738
+ 'max_age' => 0,
739
+ 'supports_credentials' => true, // Required for Sanctum SPA auth
740
+ ];
741
+
742
+ // NEVER: Allow all origins in production unless absolutely necessary
743
+ // 'allowed_origins' => ['*'], // Only for truly public APIs
744
+ ```
745
+
746
+ ## File Upload Security
747
+
748
+ ### Validation
749
+
750
+ ```php
751
+ public function rules(): array
752
+ {
753
+ return [
754
+ 'document' => [
755
+ 'required',
756
+ 'file',
757
+ 'mimes:pdf,doc,docx,xls,xlsx', // Whitelist specific MIME types
758
+ 'max:10240', // 10MB
759
+ 'extensions:pdf,doc,docx,xls,xlsx', // Verify extension matches MIME
760
+ ],
761
+ 'avatar' => [
762
+ 'nullable',
763
+ 'image', // Ensures it's a valid image
764
+ 'mimes:jpg,jpeg,png,webp',
765
+ 'max:2048',
766
+ 'dimensions:min_width=100,min_height=100,max_width=2000,max_height=2000',
767
+ ],
768
+ ];
769
+ }
770
+ ```
771
+
772
+ ### Secure Storage
773
+
774
+ ```php
775
+ // Store files outside public directory
776
+ $path = $request->file('document')->store('documents', 'local');
777
+ // Never use 'public' disk for sensitive documents
778
+
779
+ // Use signed URLs for temporary file access
780
+ use Illuminate\Support\Facades\Storage;
781
+
782
+ public function download(Request $request, string $path)
783
+ {
784
+ // Generate temporary signed URL (expires in 15 minutes)
785
+ $url = Storage::temporaryUrl($path, now()->addMinutes(15));
786
+
787
+ // Validate user has permission
788
+ $this->authorize('download', $path);
789
+
790
+ return redirect($url);
791
+ }
792
+
793
+ // Storage configuration for cloud with encryption
794
+ // config/filesystems.php
795
+ 's3' => [
796
+ 'driver' => 's3',
797
+ 'key' => env('AWS_ACCESS_KEY_ID'),
798
+ 'secret' => env('AWS_SECRET_ACCESS_KEY'),
799
+ 'region' => env('AWS_DEFAULT_REGION'),
800
+ 'bucket' => env('AWS_BUCKET'),
801
+ 'url' => env('AWS_URL'),
802
+ 'endpoint' => env('AWS_ENDPOINT'),
803
+ 'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false),
804
+ 'throw' => false,
805
+ 'server_side_encryption' => 'AES256', // Encrypt at rest
806
+ ],
807
+ ```
808
+
809
+ ## Dependencies and Secrets
810
+
811
+ ### Composer Security
812
+
813
+ ```bash
814
+ # Always audit dependencies in CI
815
+ composer audit
816
+
817
+ # Pin major versions in composer.json
818
+ "laravel/framework": "^11.0",
819
+ "spatie/laravel-permission": "^6.0"
820
+
821
+ # Check for abandoned packages
822
+ composer why-not
823
+
824
+ # Keep lock file in version control (it pins exact versions)
825
+ # Run `composer update` deliberately, never in CI/CD
826
+ ```
827
+
828
+ ### Secret Management
829
+
830
+ ```bash
831
+ # .env file (NEVER commit)
832
+ # .gitignore includes .env by default
833
+
834
+ APP_KEY=base64:abc123...
835
+ DB_PASSWORD=secure_password
836
+ STRIPE_KEY=sk_live_...
837
+ SANCTUM_TOKEN_PREFIX=myapp_
838
+
839
+ # For production: Use a secret manager
840
+ # Deploy with: env $(aws secretsmanager get-secret-value --secret-id prod/db | jq ...) php artisan serve
841
+
842
+ # Validate secrets at boot (AppServiceProvider::boot)
843
+ $secrets = ['services.stripe.key', 'services.stripe.webhook_secret'];
844
+ foreach ($secrets as $key) {
845
+ if (empty(config($key))) {
846
+ Log::critical("Missing secret: {$key}");
847
+ }
848
+ }
849
+ ```
850
+
851
+ ## Queue Security
852
+
853
+ ```php
854
+ // Define a named rate limiter (typically in AppServiceProvider::boot())
855
+ RateLimiter::for('payments', fn () => Limit::perMinute(5));
856
+ ```
857
+
858
+ ```php
859
+ // Encrypt sensitive job data by implementing the interface
860
+ final class ProcessPaymentJob implements ShouldQueue, ShouldBeEncrypted
861
+ {
862
+ use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
863
+
864
+ public function __construct(
865
+ private readonly string $paymentIntentId, // Public IDs are fine
866
+ private readonly string $cardFingerprint, // Encrypted via ShouldBeEncrypted
867
+ ) {}
868
+
869
+ public function handle(): void
870
+ {
871
+ // Process payment
872
+ }
873
+
874
+ // Limit retries and delay between attempts
875
+ public function retryUntil(): Carbon
876
+ {
877
+ return now()->addMinutes(5);
878
+ }
879
+
880
+ // Rate limit how many jobs of this type can run
881
+ public function middleware(): array
882
+ {
883
+ return [
884
+ new RateLimited('payments'),
885
+ ];
886
+ }
887
+ }
888
+ ```
889
+
890
+ ## Logging Security Events
891
+
892
+ ```php
893
+ // config/logging.php
894
+ 'channels' => [
895
+ 'security' => [
896
+ 'driver' => 'single',
897
+ 'path' => storage_path('logs/security.log'),
898
+ 'level' => 'warning',
899
+ ],
900
+ ],
901
+
902
+ // Audit log helper
903
+ final class SecurityLogger
904
+ {
905
+ public static function log(string $event, array $context = []): void
906
+ {
907
+ Log::channel('security')->warning($event, array_merge([
908
+ 'user_id' => Auth::id(),
909
+ 'ip' => request()->ip(),
910
+ 'user_agent' => request()->userAgent(),
911
+ 'url' => request()->fullUrl(),
912
+ 'timestamp' => now()->toIso8601String(),
913
+ ], $context));
914
+ }
915
+ }
916
+
917
+ // Usage
918
+ SecurityLogger::log('failed_login_attempt', ['email' => $email]);
919
+ SecurityLogger::log('password_change');
920
+ SecurityLogger::log('role_change', ['target_user' => $targetId, 'new_role' => 'admin']);
921
+ SecurityLogger::log('suspicious_activity', ['reason' => 'multiple_attempts_from_different_ips']);
922
+ ```
923
+
924
+ ## Quick Security Checklist
925
+
926
+ | Check | Description |
927
+ |-------|-------------|
928
+ | `APP_DEBUG=false` | Never run with debug enabled in production |
929
+ | `APP_KEY` set | Always run `php artisan key:generate` |
930
+ | HTTPS enforced | Force HTTPS in production via middleware or proxy |
931
+ | `$fillable` whitelisted | Never use `$guarded = []` |
932
+ | CSRF active | `@csrf` on all state-changing forms |
933
+ | Sanctum/Passport configured | API authentication with token abilities/scopes |
934
+ | Rate limiting applied | Throttle API and auth endpoints |
935
+ | Input validation | FormRequest with specific rules, never `$request->all()` |
936
+ | File upload restrictions | Validate MIME types, size, dimensions |
937
+ | `composer audit` in CI | Check dependencies for known vulnerabilities |
938
+ | `password_hash` / `password_verify` | Use Laravel's built-in hashing (bcrypt/Argon2) |
939
+ | Session regeneration on login | Call `$request->session()->regenerate()` |
940
+ | Security headers middleware | CSP, X-Frame-Options, X-Content-Type-Options |
941
+ | Logged security events | Audit log for auth failures, role changes, suspicious activity |
942
+ | `.env` not committed | Verify `.gitignore` includes `.env` |
943
+
944
+ ## Related Skills
945
+
946
+ - `laravel-patterns` — Laravel architecture, routing, Eloquent, and API patterns
947
+ - `backend-patterns` — General backend API and database patterns
948
+ - `laravel-tdd` — Laravel testing with PHPUnit and Pest