codeply-cli 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (375) hide show
  1. package/THIRD_PARTY_NOTICES.md +63 -63
  2. package/bin/codeply.js +129 -6
  3. package/lib/agent.mjs +2139 -2139
  4. package/lib/ai.mjs +6 -6
  5. package/lib/apply-patch.js +217 -217
  6. package/lib/arity-table.json +138 -138
  7. package/lib/arity.js +70 -70
  8. package/lib/auth.mjs +6 -6
  9. package/lib/cloud.mjs +708 -0
  10. package/lib/commands.js +103 -103
  11. package/lib/design-library/assemble-artifact.mjs +21 -21
  12. package/lib/design-library/cleanup.mjs +45 -45
  13. package/lib/design-library/index.json +22379 -22379
  14. package/lib/design-library/make-table-data.mjs +39 -39
  15. package/lib/diagnostics.js +192 -192
  16. package/lib/edit-engine.js +364 -364
  17. package/lib/github-agent.mjs +311 -290
  18. package/lib/history.js +75 -75
  19. package/lib/mcp.js +258 -258
  20. package/lib/native-tools.mjs +189 -189
  21. package/lib/oauth-connectors.js +2 -1
  22. package/lib/permissions.js +109 -109
  23. package/lib/plugins.js +353 -353
  24. package/lib/server.mjs +457 -457
  25. package/lib/session-db.js +220 -220
  26. package/lib/share.js +168 -168
  27. package/lib/skills.js +416 -416
  28. package/lib/snapshot.js +146 -146
  29. package/lib/terminal-hints.js +154 -154
  30. package/lib/tools.mjs +2012 -2012
  31. package/lib/typecheck-worker.js +147 -147
  32. package/lib/web-tools.js +158 -158
  33. package/package.json +49 -47
  34. package/skills/SOURCE.md +58 -58
  35. package/skills/accessibility/SKILL.md +147 -147
  36. package/skills/agent-architecture-audit/SKILL.md +257 -257
  37. package/skills/agent-eval/SKILL.md +147 -147
  38. package/skills/agent-harness-construction/SKILL.md +74 -74
  39. package/skills/agent-introspection-debugging/SKILL.md +154 -154
  40. package/skills/agent-payment-x402/SKILL.md +225 -225
  41. package/skills/agent-self-evaluation/SKILL.md +182 -182
  42. package/skills/agent-self-evaluation/references/evaluation-criteria.md +71 -71
  43. package/skills/agent-self-evaluation/references/hook-integration.md +64 -64
  44. package/skills/agent-sort/SKILL.md +216 -216
  45. package/skills/agentic-engineering/SKILL.md +64 -64
  46. package/skills/agentic-os/SKILL.md +388 -388
  47. package/skills/ai-first-engineering/SKILL.md +52 -52
  48. package/skills/ai-regression-testing/SKILL.md +386 -386
  49. package/skills/android-clean-architecture/SKILL.md +340 -340
  50. package/skills/angular-developer/SKILL.md +155 -155
  51. package/skills/angular-developer/references/angular-animations.md +160 -160
  52. package/skills/angular-developer/references/angular-aria.md +410 -410
  53. package/skills/angular-developer/references/cli.md +86 -86
  54. package/skills/angular-developer/references/component-harnesses.md +59 -59
  55. package/skills/angular-developer/references/component-styling.md +91 -91
  56. package/skills/angular-developer/references/components.md +117 -117
  57. package/skills/angular-developer/references/creating-services.md +97 -97
  58. package/skills/angular-developer/references/data-resolvers.md +69 -69
  59. package/skills/angular-developer/references/define-routes.md +67 -67
  60. package/skills/angular-developer/references/defining-providers.md +72 -72
  61. package/skills/angular-developer/references/di-fundamentals.md +120 -120
  62. package/skills/angular-developer/references/e2e-testing.md +56 -56
  63. package/skills/angular-developer/references/effects.md +83 -83
  64. package/skills/angular-developer/references/hierarchical-injectors.md +43 -43
  65. package/skills/angular-developer/references/host-elements.md +80 -80
  66. package/skills/angular-developer/references/injection-context.md +63 -63
  67. package/skills/angular-developer/references/inputs.md +101 -101
  68. package/skills/angular-developer/references/linked-signal.md +59 -59
  69. package/skills/angular-developer/references/loading-strategies.md +61 -61
  70. package/skills/angular-developer/references/mcp.md +108 -108
  71. package/skills/angular-developer/references/navigate-to-routes.md +69 -69
  72. package/skills/angular-developer/references/outputs.md +86 -86
  73. package/skills/angular-developer/references/reactive-forms.md +122 -122
  74. package/skills/angular-developer/references/rendering-strategies.md +44 -44
  75. package/skills/angular-developer/references/resource.md +77 -77
  76. package/skills/angular-developer/references/route-animations.md +56 -56
  77. package/skills/angular-developer/references/route-guards.md +52 -52
  78. package/skills/angular-developer/references/router-lifecycle.md +45 -45
  79. package/skills/angular-developer/references/router-testing.md +87 -87
  80. package/skills/angular-developer/references/show-routes-with-outlets.md +68 -68
  81. package/skills/angular-developer/references/signal-forms.md +795 -795
  82. package/skills/angular-developer/references/signals-overview.md +94 -94
  83. package/skills/angular-developer/references/tailwind-css.md +69 -69
  84. package/skills/angular-developer/references/template-driven-forms.md +114 -114
  85. package/skills/angular-developer/references/testing-fundamentals.md +65 -65
  86. package/skills/api-connector-builder/SKILL.md +121 -121
  87. package/skills/api-design/SKILL.md +524 -524
  88. package/skills/architecture-decision-records/SKILL.md +180 -180
  89. package/skills/article-writing/SKILL.md +80 -80
  90. package/skills/automation-audit-ops/SKILL.md +143 -143
  91. package/skills/autonomous-agent-harness/SKILL.md +274 -274
  92. package/skills/autonomous-loops/SKILL.md +611 -611
  93. package/skills/backend-patterns/SKILL.md +562 -562
  94. package/skills/benchmark/SKILL.md +95 -95
  95. package/skills/benchmark-methodology/SKILL.md +191 -191
  96. package/skills/benchmark-optimization-loop/SKILL.md +71 -71
  97. package/skills/blender-motion-state-inspection/SKILL.md +165 -165
  98. package/skills/blueprint/SKILL.md +106 -106
  99. package/skills/brand-discovery/SKILL.md +145 -145
  100. package/skills/brand-discovery/references/10_purpose-why.md +40 -40
  101. package/skills/brand-discovery/references/20_positioning.md +44 -44
  102. package/skills/brand-discovery/references/30_audience-niche.md +52 -52
  103. package/skills/brand-discovery/references/40_personality-archetype.md +57 -57
  104. package/skills/brand-discovery/references/50_voice-tone.md +59 -59
  105. package/skills/brand-discovery/references/60_narrative-story.md +50 -50
  106. package/skills/brand-discovery/references/70_founder-tension.md +49 -49
  107. package/skills/brand-discovery/references/90_SYNTHESIS.md +133 -133
  108. package/skills/brand-voice/SKILL.md +98 -98
  109. package/skills/brand-voice/references/voice-profile-schema.md +55 -55
  110. package/skills/browser-qa/SKILL.md +105 -105
  111. package/skills/bun-runtime/SKILL.md +85 -85
  112. package/skills/canary-watch/SKILL.md +108 -108
  113. package/skills/carrier-relationship-management/SKILL.md +212 -212
  114. package/skills/cisco-ios-patterns/SKILL.md +164 -164
  115. package/skills/ck/SKILL.md +148 -148
  116. package/skills/claude-devfleet/SKILL.md +112 -112
  117. package/skills/click-path-audit/SKILL.md +245 -245
  118. package/skills/clickhouse-io/SKILL.md +445 -445
  119. package/skills/code-tour/SKILL.md +254 -254
  120. package/skills/codebase-onboarding/SKILL.md +234 -234
  121. package/skills/codehealth-mcp/SKILL.md +167 -167
  122. package/skills/coding-standards/SKILL.md +551 -551
  123. package/skills/competitive-platform-analysis/SKILL.md +214 -214
  124. package/skills/competitive-report-structure/SKILL.md +162 -162
  125. package/skills/compose-multiplatform-patterns/SKILL.md +300 -300
  126. package/skills/config-gc/SKILL.md +120 -120
  127. package/skills/configure-ecc/SKILL.md +385 -385
  128. package/skills/connections-optimizer/SKILL.md +190 -190
  129. package/skills/content-engine/SKILL.md +132 -132
  130. package/skills/content-hash-cache-pattern/SKILL.md +162 -162
  131. package/skills/context-budget/SKILL.md +136 -136
  132. package/skills/continuous-agent-loop/SKILL.md +46 -46
  133. package/skills/continuous-learning/SKILL.md +132 -132
  134. package/skills/continuous-learning-v2/SKILL.md +361 -361
  135. package/skills/contract-first/SKILL.md +287 -287
  136. package/skills/cost-aware-llm-pipeline/SKILL.md +184 -184
  137. package/skills/cost-tracking/SKILL.md +97 -97
  138. package/skills/council/SKILL.md +204 -204
  139. package/skills/cpp-coding-standards/SKILL.md +724 -724
  140. package/skills/cpp-testing/SKILL.md +325 -325
  141. package/skills/crosspost/SKILL.md +112 -112
  142. package/skills/csharp-testing/SKILL.md +322 -322
  143. package/skills/customer-billing-ops/SKILL.md +141 -141
  144. package/skills/customs-trade-compliance/SKILL.md +263 -263
  145. package/skills/dart-flutter-patterns/SKILL.md +564 -564
  146. package/skills/dashboard-builder/SKILL.md +109 -109
  147. package/skills/data-scraper-agent/SKILL.md +765 -765
  148. package/skills/data-throughput-accelerator/SKILL.md +74 -74
  149. package/skills/database-migrations/SKILL.md +430 -430
  150. package/skills/deep-research/SKILL.md +160 -160
  151. package/skills/defi-amm-security/SKILL.md +167 -167
  152. package/skills/delivery-gate/SKILL.md +126 -126
  153. package/skills/deployment-patterns/SKILL.md +428 -428
  154. package/skills/design-system/SKILL.md +83 -83
  155. package/skills/django-celery/SKILL.md +458 -458
  156. package/skills/django-patterns/SKILL.md +735 -735
  157. package/skills/django-security/SKILL.md +644 -644
  158. package/skills/django-tdd/SKILL.md +730 -730
  159. package/skills/django-verification/SKILL.md +470 -470
  160. package/skills/dmux-workflows/SKILL.md +192 -192
  161. package/skills/docker-patterns/SKILL.md +365 -365
  162. package/skills/documentation-lookup/SKILL.md +91 -91
  163. package/skills/dotnet-patterns/SKILL.md +322 -322
  164. package/skills/dynamic-workflow-mode/SKILL.md +124 -124
  165. package/skills/e2e-testing/SKILL.md +327 -327
  166. package/skills/ecc-guide/SKILL.md +190 -190
  167. package/skills/ecc-recipes/SKILL.md +149 -149
  168. package/skills/ecc-tools-cost-audit/SKILL.md +161 -161
  169. package/skills/email-ops/SKILL.md +122 -122
  170. package/skills/energy-procurement/SKILL.md +228 -228
  171. package/skills/enterprise-agent-ops/SKILL.md +51 -51
  172. package/skills/error-handling/SKILL.md +377 -377
  173. package/skills/eval-harness/SKILL.md +271 -271
  174. package/skills/evm-token-decimals/SKILL.md +131 -131
  175. package/skills/exa-search/SKILL.md +108 -108
  176. package/skills/fal-ai-media/SKILL.md +289 -289
  177. package/skills/fastapi-patterns/SKILL.md +514 -514
  178. package/skills/finance-billing-ops/SKILL.md +128 -128
  179. package/skills/flox-environments/SKILL.md +497 -497
  180. package/skills/flutter-dart-code-review/SKILL.md +436 -436
  181. package/skills/foundation-models-on-device/SKILL.md +243 -243
  182. package/skills/frontend-a11y/SKILL.md +446 -446
  183. package/skills/frontend-design-direction/SKILL.md +93 -93
  184. package/skills/frontend-patterns/SKILL.md +657 -657
  185. package/skills/frontend-slides/SKILL.md +185 -185
  186. package/skills/fsharp-testing/SKILL.md +281 -281
  187. package/skills/gan-style-harness/SKILL.md +279 -279
  188. package/skills/gateguard/SKILL.md +133 -133
  189. package/skills/generating-python-installer/SKILL.md +820 -820
  190. package/skills/git-workflow/SKILL.md +716 -716
  191. package/skills/github-ops/SKILL.md +145 -145
  192. package/skills/golang-patterns/SKILL.md +676 -676
  193. package/skills/golang-testing/SKILL.md +721 -721
  194. package/skills/google-workspace-ops/SKILL.md +96 -96
  195. package/skills/growth-log/SKILL.md +128 -128
  196. package/skills/healthcare-cdss-patterns/SKILL.md +246 -246
  197. package/skills/healthcare-emr-patterns/SKILL.md +160 -160
  198. package/skills/healthcare-eval-harness/SKILL.md +208 -208
  199. package/skills/healthcare-phi-compliance/SKILL.md +146 -146
  200. package/skills/hermes-imports/SKILL.md +89 -89
  201. package/skills/hexagonal-architecture/SKILL.md +277 -277
  202. package/skills/hipaa-compliance/SKILL.md +79 -79
  203. package/skills/homelab-network-readiness/SKILL.md +170 -170
  204. package/skills/homelab-network-setup/SKILL.md +130 -130
  205. package/skills/homelab-pihole-dns/SKILL.md +275 -275
  206. package/skills/homelab-vlan-segmentation/SKILL.md +312 -312
  207. package/skills/homelab-wireguard-vpn/SKILL.md +306 -306
  208. package/skills/hookify-rules/SKILL.md +128 -128
  209. package/skills/inherit-legacy-style/SKILL.md +157 -157
  210. package/skills/intent-driven-development/SKILL.md +360 -360
  211. package/skills/inventory-demand-planning/SKILL.md +247 -247
  212. package/skills/investor-materials/SKILL.md +97 -97
  213. package/skills/investor-outreach/SKILL.md +92 -92
  214. package/skills/ios-icon-gen/SKILL.md +158 -158
  215. package/skills/iterative-retrieval/SKILL.md +212 -212
  216. package/skills/ito-basket-compare/SKILL.md +64 -64
  217. package/skills/ito-compute/SKILL.md +129 -129
  218. package/skills/ito-data-atlas-agent/SKILL.md +64 -64
  219. package/skills/ito-market-intelligence/SKILL.md +61 -61
  220. package/skills/ito-trade-planner/SKILL.md +68 -68
  221. package/skills/java-coding-standards/SKILL.md +384 -384
  222. package/skills/jira-integration/SKILL.md +303 -303
  223. package/skills/jpa-patterns/SKILL.md +152 -152
  224. package/skills/knowledge-ops/SKILL.md +155 -155
  225. package/skills/kotlin-coroutines-flows/SKILL.md +285 -285
  226. package/skills/kotlin-exposed-patterns/SKILL.md +720 -720
  227. package/skills/kotlin-ktor-patterns/SKILL.md +690 -690
  228. package/skills/kotlin-patterns/SKILL.md +712 -712
  229. package/skills/kotlin-testing/SKILL.md +825 -825
  230. package/skills/kubernetes-patterns/SKILL.md +756 -756
  231. package/skills/laravel-patterns/SKILL.md +416 -416
  232. package/skills/laravel-plugin-discovery/SKILL.md +230 -230
  233. package/skills/laravel-security/SKILL.md +948 -948
  234. package/skills/laravel-tdd/SKILL.md +675 -675
  235. package/skills/laravel-verification/SKILL.md +180 -180
  236. package/skills/latency-critical-systems/SKILL.md +75 -75
  237. package/skills/lead-intelligence/SKILL.md +322 -322
  238. package/skills/liquid-glass-design/SKILL.md +279 -279
  239. package/skills/llm-trading-agent-security/SKILL.md +147 -147
  240. package/skills/logistics-exception-management/SKILL.md +222 -222
  241. package/skills/loop-design-check/SKILL.md +143 -143
  242. package/skills/mailtrap-email-integration/SKILL.md +77 -77
  243. package/skills/make-interfaces-feel-better/SKILL.md +152 -152
  244. package/skills/manim-video/SKILL.md +90 -90
  245. package/skills/market-research/SKILL.md +76 -76
  246. package/skills/marketing-campaign/SKILL.md +114 -114
  247. package/skills/mcp-server-patterns/SKILL.md +70 -70
  248. package/skills/messages-ops/SKILL.md +105 -105
  249. package/skills/ml-adoption-playbook/SKILL.md +57 -57
  250. package/skills/mle-workflow/SKILL.md +348 -348
  251. package/skills/motion-advanced/SKILL.md +596 -596
  252. package/skills/motion-foundations/SKILL.md +299 -299
  253. package/skills/motion-patterns/SKILL.md +434 -434
  254. package/skills/motion-ui/SKILL.md +576 -576
  255. package/skills/mysql-patterns/SKILL.md +413 -413
  256. package/skills/nanoclaw-repl/SKILL.md +34 -34
  257. package/skills/nestjs-patterns/SKILL.md +231 -231
  258. package/skills/netmiko-ssh-automation/SKILL.md +174 -174
  259. package/skills/network-bgp-diagnostics/SKILL.md +168 -168
  260. package/skills/network-config-validation/SKILL.md +211 -211
  261. package/skills/network-interface-health/SKILL.md +153 -153
  262. package/skills/nextjs-turbopack/SKILL.md +58 -58
  263. package/skills/nodejs-keccak256/SKILL.md +103 -103
  264. package/skills/nutrient-document-processing/SKILL.md +168 -168
  265. package/skills/nuxt4-patterns/SKILL.md +101 -101
  266. package/skills/openclaw-persona-forge/SKILL.md +289 -289
  267. package/skills/openclaw-persona-forge/references/avatar-style.md +124 -124
  268. package/skills/openclaw-persona-forge/references/boundary-rules.md +53 -53
  269. package/skills/openclaw-persona-forge/references/error-handling.md +53 -53
  270. package/skills/openclaw-persona-forge/references/identity-tension.md +48 -48
  271. package/skills/openclaw-persona-forge/references/naming-system.md +39 -39
  272. package/skills/openclaw-persona-forge/references/output-template.md +166 -166
  273. package/skills/opensource-pipeline/SKILL.md +256 -256
  274. package/skills/orch-add-feature/SKILL.md +45 -45
  275. package/skills/orch-build-mvp/SKILL.md +49 -49
  276. package/skills/orch-change-feature/SKILL.md +43 -43
  277. package/skills/orch-fix-defect/SKILL.md +43 -43
  278. package/skills/orch-pipeline/SKILL.md +121 -121
  279. package/skills/orch-refine-code/SKILL.md +44 -44
  280. package/skills/parallel-execution-optimizer/SKILL.md +74 -74
  281. package/skills/perl-patterns/SKILL.md +505 -505
  282. package/skills/perl-security/SKILL.md +504 -504
  283. package/skills/perl-testing/SKILL.md +476 -476
  284. package/skills/plan-canvas/SKILL.md +153 -153
  285. package/skills/plan-orchestrate/SKILL.md +263 -263
  286. package/skills/plankton-code-quality/SKILL.md +237 -237
  287. package/skills/postgres-patterns/SKILL.md +148 -148
  288. package/skills/prediction-market-oracle-research/SKILL.md +64 -64
  289. package/skills/prediction-market-risk-review/SKILL.md +61 -61
  290. package/skills/premium-web-design/SKILL.md +154 -154
  291. package/skills/prisma-patterns/SKILL.md +401 -401
  292. package/skills/product-capability/SKILL.md +142 -142
  293. package/skills/product-lens/SKILL.md +93 -93
  294. package/skills/production-audit/SKILL.md +207 -207
  295. package/skills/production-scheduling/SKILL.md +238 -238
  296. package/skills/project-flow-ops/SKILL.md +112 -112
  297. package/skills/prompt-optimizer/SKILL.md +398 -398
  298. package/skills/python-patterns/SKILL.md +751 -751
  299. package/skills/python-testing/SKILL.md +817 -817
  300. package/skills/pytorch-patterns/SKILL.md +397 -397
  301. package/skills/quality-nonconformance/SKILL.md +260 -260
  302. package/skills/quarkus-patterns/SKILL.md +723 -723
  303. package/skills/quarkus-security/SKILL.md +468 -468
  304. package/skills/quarkus-tdd/SKILL.md +812 -812
  305. package/skills/quarkus-verification/SKILL.md +480 -480
  306. package/skills/ralphinho-rfc-pipeline/SKILL.md +68 -68
  307. package/skills/react-native-patterns/SKILL.md +326 -326
  308. package/skills/react-patterns/SKILL.md +342 -342
  309. package/skills/react-performance/SKILL.md +575 -575
  310. package/skills/react-testing/SKILL.md +424 -424
  311. package/skills/recsys-pipeline-architect/SKILL.md +115 -115
  312. package/skills/recursive-decision-ledger/SKILL.md +81 -81
  313. package/skills/redis-patterns/SKILL.md +404 -404
  314. package/skills/regex-vs-llm-structured-text/SKILL.md +221 -221
  315. package/skills/remotion-video-creation/SKILL.md +43 -43
  316. package/skills/repo-scan/SKILL.md +79 -79
  317. package/skills/research-ops/SKILL.md +113 -113
  318. package/skills/returns-reverse-logistics/SKILL.md +240 -240
  319. package/skills/rules-distill/SKILL.md +265 -265
  320. package/skills/rust-patterns/SKILL.md +500 -500
  321. package/skills/rust-testing/SKILL.md +501 -501
  322. package/skills/safety-guard/SKILL.md +76 -76
  323. package/skills/santa-method/SKILL.md +307 -307
  324. package/skills/scientific-db-pubmed-database/SKILL.md +176 -176
  325. package/skills/scientific-db-uspto-database/SKILL.md +178 -178
  326. package/skills/scientific-pkg-gget/SKILL.md +167 -167
  327. package/skills/scientific-thinking-literature-review/SKILL.md +193 -193
  328. package/skills/scientific-thinking-scholar-evaluation/SKILL.md +161 -161
  329. package/skills/search-first/SKILL.md +183 -183
  330. package/skills/security-bounty-hunter/SKILL.md +100 -100
  331. package/skills/security-review/SKILL.md +504 -504
  332. package/skills/security-scan/SKILL.md +166 -166
  333. package/skills/seo/SKILL.md +155 -155
  334. package/skills/skill-comply/SKILL.md +59 -59
  335. package/skills/skill-scout/SKILL.md +141 -141
  336. package/skills/skill-stocktake/SKILL.md +195 -195
  337. package/skills/social-graph-ranker/SKILL.md +155 -155
  338. package/skills/social-publisher/SKILL.md +130 -130
  339. package/skills/springboot-patterns/SKILL.md +315 -315
  340. package/skills/springboot-security/SKILL.md +273 -273
  341. package/skills/springboot-tdd/SKILL.md +159 -159
  342. package/skills/springboot-verification/SKILL.md +232 -232
  343. package/skills/strategic-compact/SKILL.md +142 -142
  344. package/skills/swift-actor-persistence/SKILL.md +144 -144
  345. package/skills/swift-concurrency-6-2/SKILL.md +216 -216
  346. package/skills/swift-protocol-di-testing/SKILL.md +191 -191
  347. package/skills/swiftui-patterns/SKILL.md +259 -259
  348. package/skills/taste/SKILL.md +264 -264
  349. package/skills/taste/references/genre-taxonomy.md +87 -87
  350. package/skills/tdd-workflow/SKILL.md +583 -583
  351. package/skills/team-agent-orchestration/SKILL.md +111 -111
  352. package/skills/team-builder/SKILL.md +169 -169
  353. package/skills/terminal-ops/SKILL.md +110 -110
  354. package/skills/tinystruct-patterns/SKILL.md +279 -279
  355. package/skills/tinystruct-patterns/references/architecture.md +90 -90
  356. package/skills/tinystruct-patterns/references/data-handling.md +60 -60
  357. package/skills/tinystruct-patterns/references/database.md +99 -99
  358. package/skills/tinystruct-patterns/references/routing.md +64 -64
  359. package/skills/tinystruct-patterns/references/system-usage.md +97 -97
  360. package/skills/tinystruct-patterns/references/testing.md +72 -72
  361. package/skills/token-budget-advisor/SKILL.md +134 -134
  362. package/skills/ui-demo/SKILL.md +466 -466
  363. package/skills/ui-to-vue/SKILL.md +135 -135
  364. package/skills/uncloud/SKILL.md +344 -344
  365. package/skills/unified-memory/SKILL.md +170 -170
  366. package/skills/unified-notifications-ops/SKILL.md +188 -188
  367. package/skills/verification-loop/SKILL.md +129 -129
  368. package/skills/video-editing/SKILL.md +311 -311
  369. package/skills/videodb/SKILL.md +375 -375
  370. package/skills/visa-doc-translate/SKILL.md +117 -117
  371. package/skills/vite-patterns/SKILL.md +450 -450
  372. package/skills/vue-patterns/SKILL.md +471 -471
  373. package/skills/windows-desktop-e2e/SKILL.md +888 -888
  374. package/skills/workspace-surface-audit/SKILL.md +126 -126
  375. 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