@omnizap-system/omnizap 2.5.12

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 (425) hide show
  1. package/.clusterfuzzlite/Dockerfile +10 -0
  2. package/.env.example +907 -0
  3. package/.github/codeql/codeql-config.yml +10 -0
  4. package/.github/dependabot.yml +35 -0
  5. package/.github/workflows/ci.yml +73 -0
  6. package/.github/workflows/codeql.yml +106 -0
  7. package/.github/workflows/db-migration-check.yml +98 -0
  8. package/.github/workflows/dependency-review.yml +22 -0
  9. package/.github/workflows/deploy.yml +95 -0
  10. package/.github/workflows/release.yml +106 -0
  11. package/.github/workflows/security-attest-provenance.yml +51 -0
  12. package/.github/workflows/security-gitleaks.yml +34 -0
  13. package/.github/workflows/security-runner-hardening.yml +31 -0
  14. package/.github/workflows/security-scorecard.yml +44 -0
  15. package/.github/workflows/security-zap-baseline.yml +44 -0
  16. package/.github/workflows/security-zap-full-scan.yml +43 -0
  17. package/.github/workflows/security-zizmor.yml +36 -0
  18. package/.github/workflows/wiki-sync.yml +44 -0
  19. package/.gitleaks.toml +15 -0
  20. package/.prettierrc +34 -0
  21. package/CODE_OF_CONDUCT.md +114 -0
  22. package/LICENSE +56 -0
  23. package/README.md +110 -0
  24. package/SECURITY.md +110 -0
  25. package/app/config/index.js +4 -0
  26. package/app/configParts/adminIdentity.js +92 -0
  27. package/app/configParts/baileysConfig.js +1818 -0
  28. package/app/configParts/groupUtils.js +692 -0
  29. package/app/configParts/loggerConfig.js +394 -0
  30. package/app/configParts/messagePersistenceService.js +305 -0
  31. package/app/connection/baileysCompatibility.test.js +40 -0
  32. package/app/connection/baileysDbAuthState.js +344 -0
  33. package/app/connection/socketController.js +2243 -0
  34. package/app/controllers/messageController.js +7 -0
  35. package/app/controllers/messagePipeline/commandMiddleware.js +146 -0
  36. package/app/controllers/messagePipeline/conversationMiddleware.js +183 -0
  37. package/app/controllers/messagePipeline/messagePipelineMiddlewares.test.js +522 -0
  38. package/app/controllers/messagePipeline/postProcessingMiddleware.js +41 -0
  39. package/app/controllers/messagePipeline/preProcessingMiddlewares.js +166 -0
  40. package/app/controllers/messageProcessingPipeline.js +699 -0
  41. package/app/modules/adminModule/AGENT.md +4056 -0
  42. package/app/modules/adminModule/adminAiHelpService.js +56 -0
  43. package/app/modules/adminModule/adminConfigRuntime.js +177 -0
  44. package/app/modules/adminModule/commandConfig.json +7122 -0
  45. package/app/modules/adminModule/groupCommandHandlers.js +1823 -0
  46. package/app/modules/adminModule/groupCommandHandlers.test.js +350 -0
  47. package/app/modules/adminModule/groupEventHandlers.js +399 -0
  48. package/app/modules/aiModule/AGENT.md +547 -0
  49. package/app/modules/aiModule/aiAiHelpService.js +14 -0
  50. package/app/modules/aiModule/aiConfigRuntime.js +135 -0
  51. package/app/modules/aiModule/catCommand.js +967 -0
  52. package/app/modules/aiModule/commandConfig.json +981 -0
  53. package/app/modules/analyticsModule/messageAnalysisEventRepository.js +83 -0
  54. package/app/modules/gameModule/AGENT.md +196 -0
  55. package/app/modules/gameModule/commandConfig.json +366 -0
  56. package/app/modules/gameModule/diceCommand.js +42 -0
  57. package/app/modules/gameModule/gameAiHelpService.js +14 -0
  58. package/app/modules/gameModule/gameConfigRuntime.js +68 -0
  59. package/app/modules/menuModule/AGENT.md +205 -0
  60. package/app/modules/menuModule/commandConfig.json +366 -0
  61. package/app/modules/menuModule/common.js +316 -0
  62. package/app/modules/menuModule/menuAiHelpService.js +14 -0
  63. package/app/modules/menuModule/menuConfigRuntime.js +68 -0
  64. package/app/modules/menuModule/menus.js +66 -0
  65. package/app/modules/playModule/AGENT.md +321 -0
  66. package/app/modules/playModule/commandConfig.json +584 -0
  67. package/app/modules/playModule/playAiHelpService.js +14 -0
  68. package/app/modules/playModule/playCommand.js +1417 -0
  69. package/app/modules/playModule/playConfigRuntime.js +68 -0
  70. package/app/modules/quoteModule/AGENT.md +199 -0
  71. package/app/modules/quoteModule/commandConfig.json +366 -0
  72. package/app/modules/quoteModule/quoteAiHelpService.js +14 -0
  73. package/app/modules/quoteModule/quoteCommand.js +842 -0
  74. package/app/modules/quoteModule/quoteConfigRuntime.js +68 -0
  75. package/app/modules/rpgPokemonModule/AGENT.md +229 -0
  76. package/app/modules/rpgPokemonModule/commandConfig.json +386 -0
  77. package/app/modules/rpgPokemonModule/rpgBattleCanvasRenderer.js +795 -0
  78. package/app/modules/rpgPokemonModule/rpgBattleService.js +2110 -0
  79. package/app/modules/rpgPokemonModule/rpgBattleService.test.js +770 -0
  80. package/app/modules/rpgPokemonModule/rpgEvolutionUtils.js +22 -0
  81. package/app/modules/rpgPokemonModule/rpgPokemonAiHelpService.js +14 -0
  82. package/app/modules/rpgPokemonModule/rpgPokemonCommand.js +174 -0
  83. package/app/modules/rpgPokemonModule/rpgPokemonConfigRuntime.js +68 -0
  84. package/app/modules/rpgPokemonModule/rpgPokemonDomain.js +192 -0
  85. package/app/modules/rpgPokemonModule/rpgPokemonDomain.test.js +93 -0
  86. package/app/modules/rpgPokemonModule/rpgPokemonEvolution.test.js +46 -0
  87. package/app/modules/rpgPokemonModule/rpgPokemonMessages.js +746 -0
  88. package/app/modules/rpgPokemonModule/rpgPokemonRepository.js +1847 -0
  89. package/app/modules/rpgPokemonModule/rpgPokemonService.js +6839 -0
  90. package/app/modules/rpgPokemonModule/rpgProfileCanvasRenderer.js +354 -0
  91. package/app/modules/statsModule/AGENT.md +320 -0
  92. package/app/modules/statsModule/commandConfig.json +540 -0
  93. package/app/modules/statsModule/globalRankingCommand.js +64 -0
  94. package/app/modules/statsModule/rankingCommand.js +41 -0
  95. package/app/modules/statsModule/rankingCommon.js +1305 -0
  96. package/app/modules/statsModule/statsAiHelpService.js +14 -0
  97. package/app/modules/statsModule/statsConfigRuntime.js +68 -0
  98. package/app/modules/stickerModule/AGENT.md +692 -0
  99. package/app/modules/stickerModule/addStickerMetadata.js +239 -0
  100. package/app/modules/stickerModule/commandConfig.json +1216 -0
  101. package/app/modules/stickerModule/convertToWebp.js +367 -0
  102. package/app/modules/stickerModule/stickerAiHelpService.js +14 -0
  103. package/app/modules/stickerModule/stickerCommand.js +446 -0
  104. package/app/modules/stickerModule/stickerConfigRuntime.js +68 -0
  105. package/app/modules/stickerModule/stickerConvertCommand.js +159 -0
  106. package/app/modules/stickerModule/stickerTextCommand.js +653 -0
  107. package/app/modules/stickerPackModule/AGENT.md +215 -0
  108. package/app/modules/stickerPackModule/autoPackCollectorRuntime.js +20 -0
  109. package/app/modules/stickerPackModule/autoPackCollectorService.js +357 -0
  110. package/app/modules/stickerPackModule/commandConfig.json +387 -0
  111. package/app/modules/stickerPackModule/domainEventOutboxRepository.js +227 -0
  112. package/app/modules/stickerPackModule/domainEvents.js +52 -0
  113. package/app/modules/stickerPackModule/semanticReclassificationEngine.js +429 -0
  114. package/app/modules/stickerPackModule/semanticReclassificationEngine.test.js +75 -0
  115. package/app/modules/stickerPackModule/semanticThemeClusterService.js +544 -0
  116. package/app/modules/stickerPackModule/stickerAssetClassificationRepository.js +400 -0
  117. package/app/modules/stickerPackModule/stickerAssetRepository.js +400 -0
  118. package/app/modules/stickerPackModule/stickerAssetReprocessQueueRepository.js +175 -0
  119. package/app/modules/stickerPackModule/stickerAutoPackByTagsRuntime.js +3702 -0
  120. package/app/modules/stickerPackModule/stickerClassificationBackgroundRuntime.js +559 -0
  121. package/app/modules/stickerPackModule/stickerClassificationService.js +557 -0
  122. package/app/modules/stickerPackModule/stickerDedicatedTaskWorkerRuntime.js +249 -0
  123. package/app/modules/stickerPackModule/stickerDomainEventBus.js +65 -0
  124. package/app/modules/stickerPackModule/stickerDomainEventConsumerRuntime.js +208 -0
  125. package/app/modules/stickerPackModule/stickerMarketplaceDriftService.js +99 -0
  126. package/app/modules/stickerPackModule/stickerObjectStorageService.js +285 -0
  127. package/app/modules/stickerPackModule/stickerPackAiHelpService.js +14 -0
  128. package/app/modules/stickerPackModule/stickerPackCommandHandlers.js +1148 -0
  129. package/app/modules/stickerPackModule/stickerPackConfigRuntime.js +68 -0
  130. package/app/modules/stickerPackModule/stickerPackEngagementRepository.js +152 -0
  131. package/app/modules/stickerPackModule/stickerPackErrors.js +30 -0
  132. package/app/modules/stickerPackModule/stickerPackInteractionEventRepository.js +101 -0
  133. package/app/modules/stickerPackModule/stickerPackItemRepository.js +432 -0
  134. package/app/modules/stickerPackModule/stickerPackMarketplaceService.js +313 -0
  135. package/app/modules/stickerPackModule/stickerPackMessageService.js +268 -0
  136. package/app/modules/stickerPackModule/stickerPackRepository.js +450 -0
  137. package/app/modules/stickerPackModule/stickerPackScoreSnapshotRepository.js +179 -0
  138. package/app/modules/stickerPackModule/stickerPackScoreSnapshotRuntime.js +271 -0
  139. package/app/modules/stickerPackModule/stickerPackService.js +733 -0
  140. package/app/modules/stickerPackModule/stickerPackServiceRuntime.js +32 -0
  141. package/app/modules/stickerPackModule/stickerPackUtils.js +107 -0
  142. package/app/modules/stickerPackModule/stickerStorageService.js +559 -0
  143. package/app/modules/stickerPackModule/stickerWorkerPipelineRuntime.js +242 -0
  144. package/app/modules/stickerPackModule/stickerWorkerTaskQueueRepository.js +242 -0
  145. package/app/modules/systemMetricsModule/AGENT.md +193 -0
  146. package/app/modules/systemMetricsModule/commandConfig.json +344 -0
  147. package/app/modules/systemMetricsModule/pingCommand.js +399 -0
  148. package/app/modules/systemMetricsModule/systemMetricsAiHelpService.js +14 -0
  149. package/app/modules/systemMetricsModule/systemMetricsConfigRuntime.js +68 -0
  150. package/app/modules/tiktokModule/AGENT.md +196 -0
  151. package/app/modules/tiktokModule/commandConfig.json +366 -0
  152. package/app/modules/tiktokModule/tiktokAiHelpService.js +14 -0
  153. package/app/modules/tiktokModule/tiktokCommand.js +716 -0
  154. package/app/modules/tiktokModule/tiktokConfigRuntime.js +68 -0
  155. package/app/modules/userModule/AGENT.md +200 -0
  156. package/app/modules/userModule/commandConfig.json +386 -0
  157. package/app/modules/userModule/userAiHelpService.js +14 -0
  158. package/app/modules/userModule/userCommand.js +1155 -0
  159. package/app/modules/userModule/userConfigRuntime.js +68 -0
  160. package/app/modules/waifuPicsModule/AGENT.md +431 -0
  161. package/app/modules/waifuPicsModule/commandConfig.json +780 -0
  162. package/app/modules/waifuPicsModule/waifuPicsAiHelpService.js +14 -0
  163. package/app/modules/waifuPicsModule/waifuPicsCommand.js +586 -0
  164. package/app/modules/waifuPicsModule/waifuPicsConfigRuntime.js +68 -0
  165. package/app/observability/metrics.js +766 -0
  166. package/app/services/ai/aiHelpResponseCacheRepository.js +280 -0
  167. package/app/services/ai/aiLearningRepository.js +400 -0
  168. package/app/services/ai/commandConfigEnrichmentRepository.js +769 -0
  169. package/app/services/ai/commandConfigEnrichmentService.js +452 -0
  170. package/app/services/ai/commandConfigValidationService.js +443 -0
  171. package/app/services/ai/commandToolBuilderService.js +192 -0
  172. package/app/services/ai/conversationRouterService.js +516 -0
  173. package/app/services/ai/geminiService.js +115 -0
  174. package/app/services/ai/geminiService.test.js +87 -0
  175. package/app/services/ai/globalModuleAiHelpService.js +1412 -0
  176. package/app/services/ai/globalToolCallingService.js +203 -0
  177. package/app/services/ai/messageCommandExecutionService.js +391 -0
  178. package/app/services/ai/moduleAiHelpCoreService.js +1099 -0
  179. package/app/services/ai/moduleAiHelpWrapperFactory.js +65 -0
  180. package/app/services/ai/moduleCommandConfigRuntimeService.js +113 -0
  181. package/app/services/ai/moduleToolExecutorService.js +464 -0
  182. package/app/services/ai/moduleToolRegistryService.js +178 -0
  183. package/app/services/ai/toolCandidateSelectorService.js +781 -0
  184. package/app/services/auth/googleWebLinkService.js +80 -0
  185. package/app/services/auth/whatsappLoginLinkService.js +230 -0
  186. package/app/services/external/pokeApiService.js +398 -0
  187. package/app/services/group/groupMetadataService.js +311 -0
  188. package/app/services/infra/dbWriteQueue.js +874 -0
  189. package/app/services/infra/featureFlagService.js +131 -0
  190. package/app/services/infra/queueUtils.js +55 -0
  191. package/app/services/messaging/captchaService.js +491 -0
  192. package/app/services/messaging/messagePersistenceService.js +1 -0
  193. package/app/services/messaging/newsBroadcastService.js +347 -0
  194. package/app/services/sticker/stickerFocusService.js +347 -0
  195. package/app/services/sticker/stickerFocusService.test.js +43 -0
  196. package/app/store/aiPromptStore.js +38 -0
  197. package/app/store/conversationSessionStore.js +131 -0
  198. package/app/store/groupConfigStore.js +58 -0
  199. package/app/store/premiumUserStore.js +54 -0
  200. package/app/utils/antiLink/antiLinkModule.js +700 -0
  201. package/app/utils/http/getImageBufferModule.js +18 -0
  202. package/app/utils/json/jsonSanitizer.js +113 -0
  203. package/app/utils/json/jsonSanitizer.test.js +40 -0
  204. package/app/utils/systemMetrics/systemMetricsModule.js +88 -0
  205. package/app/workers/aiLearningWorker.js +605 -0
  206. package/app/workers/commandConfigEnrichmentWorker.js +242 -0
  207. package/database/index.js +2075 -0
  208. package/database/init.js +151 -0
  209. package/database/migrations/.gitkeep +0 -0
  210. package/database/migrations/20260307_d0_hardening_down.sql +64 -0
  211. package/database/migrations/20260307_d0_hardening_up.sql +79 -0
  212. package/database/migrations/20260307_d1_terms_acceptance_down.sql +11 -0
  213. package/database/migrations/20260307_d1_terms_acceptance_up.sql +37 -0
  214. package/database/migrations/20260307_d2_auth_hardening_down.sql +75 -0
  215. package/database/migrations/20260307_d2_auth_hardening_up.sql +100 -0
  216. package/database/migrations/20260314_d7_canonical_sender_down.sql +53 -0
  217. package/database/migrations/20260314_d7_canonical_sender_up.sql +114 -0
  218. package/database/migrations/20260406_d30_security_analytics_down.sql +95 -0
  219. package/database/migrations/20260406_d30_security_analytics_up.sql +292 -0
  220. package/database/migrations/20260407_d31_web_google_session_token_hardening_down.sql +2 -0
  221. package/database/migrations/20260407_d31_web_google_session_token_hardening_up.sql +17 -0
  222. package/database/migrations/20260408_d32_ai_help_response_cache_down.sql +1 -0
  223. package/database/migrations/20260408_d32_ai_help_response_cache_up.sql +22 -0
  224. package/database/migrations/20260409_d33_ai_learning_tables_down.sql +4 -0
  225. package/database/migrations/20260409_d33_ai_learning_tables_up.sql +52 -0
  226. package/database/migrations/20260410_d34_command_config_enrichment_down.sql +3 -0
  227. package/database/migrations/20260410_d34_command_config_enrichment_up.sql +48 -0
  228. package/database/schema.sql +1186 -0
  229. package/docker-compose.yml +104 -0
  230. package/docs/audits/stickerCatalogController-out-of-scope.md +103 -0
  231. package/docs/audits/stickerCatalogController-symbols.md +58 -0
  232. package/docs/compliance/acceptable-use-policy-2026-03-07.md +35 -0
  233. package/docs/compliance/dpa-b2b-standard-2026-03-07.md +80 -0
  234. package/docs/compliance/monthly-compliance-checklist-2026-03-07.md +88 -0
  235. package/docs/compliance/notice-and-takedown-policy-2026-03-07.md +34 -0
  236. package/docs/compliance/privacy-policy-2026-03-07.md +75 -0
  237. package/docs/compliance/subprocessors-inventory-2026-03-07.md +16 -0
  238. package/docs/database/production-db-evolution-runbook-2026q1.md +365 -0
  239. package/docs/security/dsar-lgpd-runbook-2026-03-07.md +86 -0
  240. package/docs/security/incident-response-lgpd-anpd-runbook-2026-03-07.md +77 -0
  241. package/docs/security/network-hardening-runbook-2026-03-07.md +137 -0
  242. package/docs/seo/omnizap-seo-playbook-br-2026-02-28.md +238 -0
  243. package/docs/seo/satellite-page-template.md +116 -0
  244. package/docs/seo/satellite-pages-phase1.json +364 -0
  245. package/docs/wiki/Home.md +120 -0
  246. package/docs/wiki/pair-extraordinaire-2026-03-08.md +3 -0
  247. package/docs/wiki/recent-changes-2026-03-08.md +47 -0
  248. package/ecosystem.prod.config.cjs +135 -0
  249. package/eslint.config.js +89 -0
  250. package/index.js +488 -0
  251. package/ml/clip_classifier/Dockerfile +18 -0
  252. package/ml/clip_classifier/README.md +118 -0
  253. package/ml/clip_classifier/adaptive_scoring.py +40 -0
  254. package/ml/clip_classifier/classifier.py +654 -0
  255. package/ml/clip_classifier/embedding_store.py +481 -0
  256. package/ml/clip_classifier/env_loader.py +15 -0
  257. package/ml/clip_classifier/llm_label_expander.py +144 -0
  258. package/ml/clip_classifier/main.py +213 -0
  259. package/ml/clip_classifier/requirements.txt +10 -0
  260. package/ml/clip_classifier/similarity_engine.py +74 -0
  261. package/new-logo.png +0 -0
  262. package/observability/alert-rules.yml +60 -0
  263. package/observability/grafana/dashboards/omnizap-mysql.json +136 -0
  264. package/observability/grafana/dashboards/omnizap-overview.json +170 -0
  265. package/observability/grafana/provisioning/dashboards/dashboards.yml +11 -0
  266. package/observability/grafana/provisioning/datasources/datasources.yml +15 -0
  267. package/observability/loki-config.yml +38 -0
  268. package/observability/mysql-setup.sql +46 -0
  269. package/observability/prometheus.yml +35 -0
  270. package/observability/promtail-config.yml +84 -0
  271. package/observability/sticker-catalog-slo.md +83 -0
  272. package/observability/sticker-scale-hardening-rollout.md +128 -0
  273. package/package.json +144 -0
  274. package/public/apple-touch-icon.png +0 -0
  275. package/public/assets/css/commands-react.input.css +71 -0
  276. package/public/assets/css/create-pack-react.input.css +31 -0
  277. package/public/assets/css/home-react.input.css +106 -0
  278. package/public/assets/css/login-react.input.css +58 -0
  279. package/public/assets/css/stickers-react.input.css +18 -0
  280. package/public/assets/css/terms-react.input.css +115 -0
  281. package/public/assets/css/user-react.input.css +57 -0
  282. package/public/assets/images/brand-icon-192.png +0 -0
  283. package/public/assets/images/brand-logo-128.webp +0 -0
  284. package/public/assets/images/hero-banner-1280.jpg +0 -0
  285. package/public/comandos/commands-catalog.json +4517 -0
  286. package/public/css/api-docs.css +161 -0
  287. package/public/css/stickers-admin.css +1288 -0
  288. package/public/css/styles.css +679 -0
  289. package/public/css/systemadm/admin.css +474 -0
  290. package/public/css/systemadm/base.css +73 -0
  291. package/public/css/systemadm/components.css +662 -0
  292. package/public/css/systemadm/layout.css +229 -0
  293. package/public/css/systemadm/tokens.css +56 -0
  294. package/public/favicon-16x16.png +0 -0
  295. package/public/favicon-32x32.png +0 -0
  296. package/public/favicon.ico +0 -0
  297. package/public/js/apps/apiDocsApp.js +235 -0
  298. package/public/js/apps/commandsReactApp.js +528 -0
  299. package/public/js/apps/createPackApp.js +1646 -0
  300. package/public/js/apps/homeReactApp.js +942 -0
  301. package/public/js/apps/loginReactApp.js +496 -0
  302. package/public/js/apps/stickersAdminApp.js +1753 -0
  303. package/public/js/apps/stickersApp.js +3797 -0
  304. package/public/js/apps/termsReactApp.js +528 -0
  305. package/public/js/apps/userApp.js +2540 -0
  306. package/public/js/apps/userProfile/actions.js +66 -0
  307. package/public/js/apps/userReactApp.js +547 -0
  308. package/public/js/catalog.js +950 -0
  309. package/public/pages/api-docs.html +40 -0
  310. package/public/pages/aup.html +158 -0
  311. package/public/pages/comandos.html +41 -0
  312. package/public/pages/dpa.html +227 -0
  313. package/public/pages/home.html +45 -0
  314. package/public/pages/licenca.html +182 -0
  315. package/public/pages/login.html +40 -0
  316. package/public/pages/notice-and-takedown.html +234 -0
  317. package/public/pages/politica-de-privacidade.html +251 -0
  318. package/public/pages/seo-bot-whatsapp-para-grupo.html +350 -0
  319. package/public/pages/seo-bot-whatsapp-sem-programar.html +350 -0
  320. package/public/pages/seo-como-automatizar-avisos-no-whatsapp.html +350 -0
  321. package/public/pages/seo-como-criar-comandos-whatsapp.html +350 -0
  322. package/public/pages/seo-como-evitar-spam-no-whatsapp.html +350 -0
  323. package/public/pages/seo-como-moderar-grupo-whatsapp.html +350 -0
  324. package/public/pages/seo-como-organizar-comunidade-whatsapp.html +350 -0
  325. package/public/pages/seo-melhor-bot-whatsapp-para-grupos.html +350 -0
  326. package/public/pages/stickers-admin.html +31 -0
  327. package/public/pages/stickers-create.html +41 -0
  328. package/public/pages/stickers.html +45 -0
  329. package/public/pages/suboperadores.html +237 -0
  330. package/public/pages/termos-de-uso-texto-integral.html +241 -0
  331. package/public/pages/termos-de-uso.html +41 -0
  332. package/public/pages/user-password-reset.html +32 -0
  333. package/public/pages/user-systemadm.html +508 -0
  334. package/public/pages/user.html +39 -0
  335. package/public/robots.txt +9 -0
  336. package/public/site.webmanifest +24 -0
  337. package/public/sitemap.xml +98 -0
  338. package/schemas/command-config.schema.json +582 -0
  339. package/scripts/baileys-compat-smoke.mjs +12 -0
  340. package/scripts/cache-bust.mjs +142 -0
  341. package/scripts/deploy.sh +916 -0
  342. package/scripts/email-broadcast-terms-update.mjs +170 -0
  343. package/scripts/enrich-command-discovery-fields.mjs +286 -0
  344. package/scripts/generate-command-config-schema.mjs +273 -0
  345. package/scripts/generate-commands-catalog.mjs +308 -0
  346. package/scripts/generate-module-agents.mjs +631 -0
  347. package/scripts/generate-seo-satellite-pages.mjs +400 -0
  348. package/scripts/github-deploy-notify.mjs +174 -0
  349. package/scripts/github-release-notify.mjs +219 -0
  350. package/scripts/release.sh +599 -0
  351. package/scripts/run-codeql-local.sh +116 -0
  352. package/scripts/run-prettier-all.mjs +25 -0
  353. package/scripts/security-smoketest.mjs +581 -0
  354. package/scripts/sticker-catalog-loadtest.mjs +210 -0
  355. package/scripts/sticker-worker-task.mjs +119 -0
  356. package/scripts/sync-readme-snapshot.mjs +133 -0
  357. package/scripts/validate-command-config-schema.mjs +130 -0
  358. package/scripts/validate-command-configs.mjs +15 -0
  359. package/scripts/wiki-sync.sh +191 -0
  360. package/server/auth/googleWebAuth/googleWebAuthRuntime.js +62 -0
  361. package/server/auth/googleWebAuth/googleWebAuthService.js +807 -0
  362. package/server/auth/jwt/webJwtService.js +147 -0
  363. package/server/auth/stickerCatalogAuthContext.js +165 -0
  364. package/server/auth/termsAcceptance/termsAcceptanceHandler.js +189 -0
  365. package/server/auth/userPassword/index.js +14 -0
  366. package/server/auth/userPassword/userPasswordAuthService.js +422 -0
  367. package/server/auth/userPassword/userPasswordCrypto.js +199 -0
  368. package/server/auth/userPassword/userPasswordCrypto.test.js +76 -0
  369. package/server/auth/userPassword/userPasswordRecoveryService.js +728 -0
  370. package/server/auth/validation/authSchemas.js +236 -0
  371. package/server/auth/webAccount/webAccountHandlers.js +1434 -0
  372. package/server/controllers/admin/adminBanService.js +138 -0
  373. package/server/controllers/admin/adminPanelHandlers.js +2083 -0
  374. package/server/controllers/admin/stickerCatalogAdminContext.js +17 -0
  375. package/server/controllers/admin/systemAdminController.js +201 -0
  376. package/server/controllers/email/emailAutomationController.js +239 -0
  377. package/server/controllers/metricsController.js +21 -0
  378. package/server/controllers/seo/stickerCatalogSeoContext.js +514 -0
  379. package/server/controllers/sticker/nonCatalogHandlers.js +303 -0
  380. package/server/controllers/sticker/stickerCatalogController.js +4700 -0
  381. package/server/controllers/system/contactController.js +115 -0
  382. package/server/controllers/system/githubController.js +137 -0
  383. package/server/controllers/system/stickerCatalogSystemContext.js +758 -0
  384. package/server/controllers/system/storageController.js +154 -0
  385. package/server/controllers/system/systemController.js +135 -0
  386. package/server/controllers/system/systemMetricsController.js +156 -0
  387. package/server/controllers/system/visitController.js +90 -0
  388. package/server/controllers/userController.js +145 -0
  389. package/server/email/emailAutomationRuntime.js +225 -0
  390. package/server/email/emailAutomationService.js +125 -0
  391. package/server/email/emailOutboxRepository.js +282 -0
  392. package/server/email/emailTemplateService.js +480 -0
  393. package/server/email/emailTransportService.js +156 -0
  394. package/server/http/clientIp.js +95 -0
  395. package/server/http/httpRequestUtils.js +262 -0
  396. package/server/http/httpRequestUtils.test.js +80 -0
  397. package/server/http/httpServer.js +180 -0
  398. package/server/http/requestContext.js +20 -0
  399. package/server/http/siteRoutingUtils.js +87 -0
  400. package/server/index.js +1 -0
  401. package/server/middleware/cachePolicy.js +26 -0
  402. package/server/middleware/cachePolicyHelpers.js +1 -0
  403. package/server/middleware/endpointRateLimit.js +181 -0
  404. package/server/middleware/rateLimit.js +70 -0
  405. package/server/middleware/requireAdminAuth.js +48 -0
  406. package/server/middleware/securityHeaders.js +97 -0
  407. package/server/routes/admin/systemAdminRouter.js +64 -0
  408. package/server/routes/email/emailAutomationRouter.js +46 -0
  409. package/server/routes/health/healthRouter.js +41 -0
  410. package/server/routes/indexRouter.js +234 -0
  411. package/server/routes/metrics/metricsRouter.js +58 -0
  412. package/server/routes/static/staticPageRouter.js +134 -0
  413. package/server/routes/sticker/catalogHandlers/catalogAdminHttp.js +105 -0
  414. package/server/routes/sticker/catalogHandlers/catalogAuthHttp.js +77 -0
  415. package/server/routes/sticker/catalogHandlers/catalogPublicHttp.js +120 -0
  416. package/server/routes/sticker/catalogHandlers/catalogUploadHttp.js +83 -0
  417. package/server/routes/sticker/catalogRouter.js +77 -0
  418. package/server/routes/sticker/stickerApiRouter.js +84 -0
  419. package/server/routes/sticker/stickerDataRouter.js +145 -0
  420. package/server/routes/sticker/stickerSiteRouter.js +43 -0
  421. package/server/routes/user/userApiPaths.js +66 -0
  422. package/server/routes/user/userRouter.js +65 -0
  423. package/server/utils/safePath.js +26 -0
  424. package/utils/logger/loggerModule.js +35 -0
  425. package/vite.config.mjs +38 -0
@@ -0,0 +1,581 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from 'node:fs/promises';
4
+ import path from 'node:path';
5
+ import process from 'node:process';
6
+
7
+ const BASE_URL = String(process.env.SECURITY_TEST_BASE_URL || 'http://127.0.0.1:9102').replace(/\/+$/, '');
8
+ const REPORT_PATH = String(process.env.SECURITY_TEST_REPORT_PATH || './temp/security-smoketest-report.json').trim();
9
+ const REQUEST_TIMEOUT_MS = Math.max(1_000, Number(process.env.SECURITY_TEST_TIMEOUT_MS || 10_000));
10
+
11
+ const PASS = 'PASS';
12
+ const WARN = 'WARN';
13
+ const FAIL = 'FAIL';
14
+ const MANUAL = 'MANUAL';
15
+
16
+ const sqlErrorRegex = /(sql syntax|syntax error|mysql|sqlite|postgres|odbc|query failed|unclosed quotation|ORA-\d+)/i;
17
+
18
+ const nowIso = () => new Date().toISOString();
19
+
20
+ const safeJson = async (value) => {
21
+ try {
22
+ return JSON.parse(value);
23
+ } catch {
24
+ return null;
25
+ }
26
+ };
27
+
28
+ const request = async (path, { method = 'GET', headers = {}, body = undefined } = {}) => {
29
+ const url = `${BASE_URL}${path.startsWith('/') ? path : `/${path}`}`;
30
+ const controller = new AbortController();
31
+ const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
32
+
33
+ try {
34
+ const response = await fetch(url, {
35
+ method,
36
+ headers,
37
+ body,
38
+ redirect: 'manual',
39
+ signal: controller.signal,
40
+ });
41
+ const text = await response.text();
42
+ const json = await safeJson(text);
43
+ return {
44
+ ok: true,
45
+ url,
46
+ status: response.status,
47
+ headers: Object.fromEntries(response.headers.entries()),
48
+ text,
49
+ json,
50
+ };
51
+ } catch (error) {
52
+ return {
53
+ ok: false,
54
+ url,
55
+ status: null,
56
+ headers: {},
57
+ text: '',
58
+ json: null,
59
+ error: error?.message || String(error),
60
+ };
61
+ } finally {
62
+ clearTimeout(timeout);
63
+ }
64
+ };
65
+
66
+ const summarizeStatuses = (items) =>
67
+ items.reduce((acc, item) => {
68
+ acc[item.status] = (acc[item.status] || 0) + 1;
69
+ return acc;
70
+ }, {});
71
+
72
+ const testSqlInjection = async () => {
73
+ const payloads = ["' OR 1=1--", '" OR "1"="1', '1;DROP TABLE users;--'];
74
+ const routes = ['/api/sticker-packs?q=', '/api/sticker-packs/creators?q=', '/api/sticker-packs/recommendations?q='];
75
+ const evidence = [];
76
+ let hasFailure = false;
77
+
78
+ for (const route of routes) {
79
+ for (const payload of payloads) {
80
+ const res = await request(`${route}${encodeURIComponent(payload)}`);
81
+ const hasSqlError = sqlErrorRegex.test(res.text || '');
82
+ const bad = !res.ok || res.status >= 500 || hasSqlError;
83
+ if (bad) hasFailure = true;
84
+ evidence.push({
85
+ route,
86
+ payload,
87
+ status: res.status,
88
+ network_ok: res.ok,
89
+ sql_error_pattern: hasSqlError,
90
+ });
91
+ }
92
+ }
93
+
94
+ return {
95
+ id: 1,
96
+ name: 'SQL Injection',
97
+ status: hasFailure ? FAIL : PASS,
98
+ note: hasFailure ? 'Há indícios de falha/erro SQL em payload de injeção.' : 'Payloads SQLi não causaram erro SQL nem 5xx.',
99
+ evidence,
100
+ };
101
+ };
102
+
103
+ const testXss = async () => {
104
+ const payload = '<svg/onload=alert(1)>';
105
+ const routes = [`/stickers?q=${encodeURIComponent(payload)}`, `/user?q=${encodeURIComponent(payload)}`, `/api/sticker-packs?q=${encodeURIComponent(payload)}`];
106
+ const evidence = [];
107
+ let htmlRawReflected = false;
108
+ let apiRawReflected = false;
109
+
110
+ for (const path of routes) {
111
+ const res = await request(path);
112
+ const reflectedRaw = (res.text || '').includes(payload);
113
+ const contentType = String(res.headers['content-type'] || '');
114
+ const isHtml = /text\/html/i.test(contentType);
115
+ const isJson = /application\/json/i.test(contentType);
116
+ if (reflectedRaw && isHtml) htmlRawReflected = true;
117
+ if (reflectedRaw && isJson) apiRawReflected = true;
118
+ evidence.push({
119
+ path,
120
+ status: res.status,
121
+ content_type: contentType || null,
122
+ reflected_raw: reflectedRaw,
123
+ });
124
+ }
125
+
126
+ const status = htmlRawReflected ? FAIL : apiRawReflected ? WARN : PASS;
127
+
128
+ return {
129
+ id: 2,
130
+ name: 'Cross-Site Scripting (XSS)',
131
+ status,
132
+ note: status === FAIL ? 'Payload XSS refletido em resposta HTML.' : status === WARN ? 'Payload refletido em API JSON; revisar sanitização no frontend consumidor.' : 'Sem reflexão bruta do payload XSS nas rotas testadas.',
133
+ evidence,
134
+ };
135
+ };
136
+
137
+ const testCsrf = async () => {
138
+ const resSession = await request('/api/sticker-packs/admin/session', {
139
+ method: 'DELETE',
140
+ headers: { 'x-forwarded-proto': 'https' },
141
+ });
142
+ const cookieHeader = String(resSession.headers['set-cookie'] || '');
143
+ const hasHttpOnly = /httponly/i.test(cookieHeader);
144
+ const hasSameSite = /samesite=/i.test(cookieHeader);
145
+ const hasSecure = /secure/i.test(cookieHeader);
146
+
147
+ const resOrigin = await request('/api/sticker-packs/create', {
148
+ method: 'POST',
149
+ headers: {
150
+ Origin: 'https://evil.example',
151
+ 'Content-Type': 'application/json',
152
+ },
153
+ body: JSON.stringify({ name: 'csrf-test' }),
154
+ });
155
+
156
+ const blockedByAuth = [401, 403].includes(resOrigin.status);
157
+ const cookieFlagsOk = hasHttpOnly && hasSameSite && hasSecure;
158
+ const status = blockedByAuth && cookieFlagsOk ? PASS : WARN;
159
+
160
+ return {
161
+ id: 3,
162
+ name: 'Cross-Site Request Forgery (CSRF)',
163
+ status,
164
+ note: status === PASS ? 'Fluxo testado bloqueou mutação não autenticada e cookies possuem flags de proteção.' : 'Validação parcial: revisar proteção CSRF em rotas autenticadas com sessão ativa.',
165
+ evidence: [
166
+ {
167
+ path: '/api/sticker-packs/admin/session',
168
+ status: resSession.status,
169
+ cookie_flags: { http_only: hasHttpOnly, same_site: hasSameSite, secure: hasSecure },
170
+ },
171
+ {
172
+ path: '/api/sticker-packs/create',
173
+ status: resOrigin.status,
174
+ blocked_by_auth: blockedByAuth,
175
+ },
176
+ ],
177
+ };
178
+ };
179
+
180
+ const testDdosSafe = async () => {
181
+ const total = 80;
182
+ const concurrency = 16;
183
+ let cursor = 0;
184
+ let failures = 0;
185
+ let serverErrors = 0;
186
+ const latencies = [];
187
+
188
+ const worker = async () => {
189
+ while (true) {
190
+ const idx = cursor;
191
+ cursor += 1;
192
+ if (idx >= total) return;
193
+ const startedAt = Date.now();
194
+ const res = await request('/healthz');
195
+ const elapsed = Date.now() - startedAt;
196
+ latencies.push(elapsed);
197
+ if (!res.ok) failures += 1;
198
+ if (res.status >= 500) serverErrors += 1;
199
+ }
200
+ };
201
+
202
+ await Promise.all(Array.from({ length: concurrency }, () => worker()));
203
+ latencies.sort((a, b) => a - b);
204
+ const p95 = latencies[Math.floor(latencies.length * 0.95)] || 0;
205
+ const failureRate = total > 0 ? failures / total : 1;
206
+ const serverErrorRate = total > 0 ? serverErrors / total : 1;
207
+ const status = failureRate <= 0.1 && serverErrorRate === 0 ? PASS : WARN;
208
+
209
+ return {
210
+ id: 4,
211
+ name: 'Distributed Denial-of-Service (safe simulation)',
212
+ status,
213
+ note: status === PASS ? 'Serviço permaneceu estável em burst controlado local.' : 'Houve instabilidade em burst local; revisar capacidade e limites.',
214
+ evidence: [
215
+ {
216
+ total,
217
+ concurrency,
218
+ failures,
219
+ server_errors: serverErrors,
220
+ failure_rate: failureRate,
221
+ p95_ms: p95,
222
+ },
223
+ ],
224
+ };
225
+ };
226
+
227
+ const testBruteForce = async () => {
228
+ const codes = [];
229
+ for (let i = 0; i < 14; i += 1) {
230
+ const res = await request('/api/sticker-packs/auth/login', {
231
+ method: 'POST',
232
+ headers: { 'Content-Type': 'application/json' },
233
+ body: JSON.stringify({ probe: 'bruteforce' }),
234
+ });
235
+ codes.push(res.status);
236
+ }
237
+ const has429 = codes.includes(429);
238
+ return {
239
+ id: 5,
240
+ name: 'Brute Force Attack',
241
+ status: has429 ? PASS : WARN,
242
+ note: has429 ? 'Rate limiting acionado para tentativas repetidas.' : 'Não foi observado 429 no cenário simulado de força bruta.',
243
+ evidence: [{ codes }],
244
+ };
245
+ };
246
+
247
+ const testCredentialStuffing = async () => {
248
+ const identities = ['a@example.com', 'b@example.com', 'c@example.com', 'd@example.com'];
249
+ const codes = [];
250
+ for (let i = 0; i < 16; i += 1) {
251
+ const identity = identities[i % identities.length];
252
+ const res = await request('/api/sticker-packs/auth/login', {
253
+ method: 'POST',
254
+ headers: { 'Content-Type': 'application/json' },
255
+ body: JSON.stringify({ identity, password: `P@ss${i}` }),
256
+ });
257
+ codes.push(res.status);
258
+ }
259
+ const has429 = codes.includes(429);
260
+ return {
261
+ id: 6,
262
+ name: 'Credential Stuffing',
263
+ status: has429 ? PASS : WARN,
264
+ note: has429 ? 'Rate limiting também mitigou tentativas com credenciais variadas.' : 'Sem 429 no padrão simulado de credential stuffing.',
265
+ evidence: [{ codes }],
266
+ };
267
+ };
268
+
269
+ const testDirectoryTraversal = async () => {
270
+ const paths = ['/.env', '/data/../../.env', '/data/%2e%2e/%2e%2e/.env', '/data/%252e%252e/%252e%252e/.env'];
271
+ const evidence = [];
272
+ let leaked = false;
273
+
274
+ for (const path of paths) {
275
+ const res = await request(path);
276
+ const body = String(res.text || '');
277
+ const maybeLeak = res.status === 200 || /DB_PASSWORD|MYSQL_PASSWORD|GITHUB_TOKEN/i.test(body);
278
+ if (maybeLeak) leaked = true;
279
+ evidence.push({ path, status: res.status, potential_leak: maybeLeak });
280
+ }
281
+
282
+ return {
283
+ id: 7,
284
+ name: 'Directory Traversal',
285
+ status: leaked ? FAIL : PASS,
286
+ note: leaked ? 'Há indício de leitura indevida de arquivo sensível.' : 'Tentativas de traversal foram bloqueadas.',
287
+ evidence,
288
+ };
289
+ };
290
+
291
+ const testFileUploadAttack = async () => {
292
+ const binaryPayload = Buffer.from('MZ_fake_executable_content', 'utf8');
293
+ const resCreate = await request('/api/sticker-packs/create', {
294
+ method: 'POST',
295
+ headers: { 'Content-Type': 'application/json' },
296
+ body: JSON.stringify({ name: '../../../etc/passwd', cover: 'data:text/plain;base64,QQ==' }),
297
+ });
298
+ const resUpload = await request('/api/sticker-packs/fake-pack/manage/stickers-upload', {
299
+ method: 'POST',
300
+ headers: { 'Content-Type': 'application/octet-stream' },
301
+ body: binaryPayload,
302
+ });
303
+ const blocked = ![200, 201, 202].includes(resCreate.status) && ![200, 201, 202].includes(resUpload.status);
304
+
305
+ return {
306
+ id: 8,
307
+ name: 'File Upload Attack',
308
+ status: blocked ? PASS : WARN,
309
+ note: blocked ? 'Uploads malformados/suspeitos não foram aceitos no cenário sem autenticação.' : 'Algum upload suspeito retornou sucesso; revisar validações.',
310
+ evidence: [
311
+ { path: '/api/sticker-packs/create', status: resCreate.status },
312
+ { path: '/api/sticker-packs/fake-pack/manage/stickers-upload', status: resUpload.status },
313
+ ],
314
+ };
315
+ };
316
+
317
+ const testSessionHijacking = async () => {
318
+ const resCookie = await request('/api/sticker-packs/admin/session', {
319
+ method: 'DELETE',
320
+ headers: { 'x-forwarded-proto': 'https' },
321
+ });
322
+ const cookieHeader = String(resCookie.headers['set-cookie'] || '');
323
+ const hasHttpOnly = /httponly/i.test(cookieHeader);
324
+ const hasSameSite = /samesite=/i.test(cookieHeader);
325
+ const hasSecure = /secure/i.test(cookieHeader);
326
+
327
+ const resForged = await request('/api/sticker-packs/admin/overview', {
328
+ headers: { Cookie: 'omnizap_admin_panel_session=fake-session-token' },
329
+ });
330
+ const blocked = [401, 403].includes(resForged.status);
331
+
332
+ const status = blocked && hasHttpOnly && hasSameSite && hasSecure ? PASS : WARN;
333
+
334
+ return {
335
+ id: 9,
336
+ name: 'Session Hijacking',
337
+ status,
338
+ note: status === PASS ? 'Cookie protegido e sessão forjada bloqueada no endpoint admin.' : 'Validação parcial: revisar sessão/cookie para evitar hijacking.',
339
+ evidence: [{ forged_request_status: resForged.status }, { cookie_flags: { http_only: hasHttpOnly, same_site: hasSameSite, secure: hasSecure } }],
340
+ };
341
+ };
342
+
343
+ const testClickjacking = async () => {
344
+ const res = await request('/user/systemadm');
345
+ const xfo = String(res.headers['x-frame-options'] || '');
346
+ const csp = String(res.headers['content-security-policy'] || '');
347
+ const ok = /sameorigin/i.test(xfo) && /frame-ancestors\s+'self'/i.test(csp);
348
+
349
+ return {
350
+ id: 10,
351
+ name: 'Clickjacking',
352
+ status: ok ? PASS : WARN,
353
+ note: ok ? 'Proteções anti-frame detectadas (X-Frame-Options + frame-ancestors).' : 'Cabeçalhos anti-clickjacking incompletos.',
354
+ evidence: [
355
+ {
356
+ status: res.status,
357
+ x_frame_options: xfo || null,
358
+ csp_has_frame_ancestors_self: /frame-ancestors\s+'self'/i.test(csp),
359
+ },
360
+ ],
361
+ };
362
+ };
363
+
364
+ const testMitm = async () => {
365
+ const res = await request('/api/home-bootstrap');
366
+ const hsts = String(res.headers['strict-transport-security'] || '');
367
+ const hasHsts = /max-age=\d+/i.test(hsts);
368
+ return {
369
+ id: 11,
370
+ name: 'Man-in-the-Middle (header posture)',
371
+ status: hasHsts ? PASS : WARN,
372
+ note: hasHsts ? 'HSTS presente. Teste MITM real ainda requer ambiente de rede controlado.' : 'HSTS ausente; reforçar proteção em tráfego HTTPS.',
373
+ evidence: [{ status: res.status, hsts: hsts || null }],
374
+ };
375
+ };
376
+
377
+ const testSsrf = async () => {
378
+ const payload = 'http://169.254.169.254/latest/meta-data/';
379
+ const res = await request('/api/sticker-packs/create', {
380
+ method: 'POST',
381
+ headers: { 'Content-Type': 'application/json' },
382
+ body: JSON.stringify({ name: 'ssrf-test', source_url: payload }),
383
+ });
384
+ const blocked = [400, 401, 403, 405, 422].includes(res.status);
385
+ return {
386
+ id: 12,
387
+ name: 'Server-Side Request Forgery (SSRF)',
388
+ status: blocked ? PASS : MANUAL,
389
+ note: blocked ? 'Payload SSRF não foi processado no cenário testado sem autenticação.' : 'Necessário teste autenticado em endpoint que realmente consome URLs.',
390
+ evidence: [{ path: '/api/sticker-packs/create', status: res.status }],
391
+ };
392
+ };
393
+
394
+ const testRce = async () => {
395
+ const payload = '$(id)';
396
+ const res = await request(`/api/sticker-packs?q=${encodeURIComponent(payload)}`);
397
+ const hasExecutionEvidence = /uid=\d+/.test(res.text || '');
398
+ const failed = !res.ok || res.status >= 500 || hasExecutionEvidence;
399
+ return {
400
+ id: 13,
401
+ name: 'Remote Code Execution (RCE)',
402
+ status: failed ? WARN : PASS,
403
+ note: failed ? 'Houve erro/indício que merece investigação manual de RCE.' : 'Sem indício de execução remota nos payloads de smoke test.',
404
+ evidence: [{ status: res.status, execution_pattern_found: hasExecutionEvidence }],
405
+ };
406
+ };
407
+
408
+ const testCommandInjection = async () => {
409
+ const payload = ';cat /etc/passwd';
410
+ const res = await request(`/api/sticker-packs/recommendations?q=${encodeURIComponent(payload)}`);
411
+ const leaked = /root:.*:0:0:/.test(res.text || '');
412
+ const failed = !res.ok || res.status >= 500 || leaked;
413
+ return {
414
+ id: 14,
415
+ name: 'Command Injection',
416
+ status: failed ? WARN : PASS,
417
+ note: failed ? 'Houve erro/indício de injeção de comando a investigar.' : 'Sem indício de command injection nos payloads aplicados.',
418
+ evidence: [{ status: res.status, passwd_pattern_found: leaked }],
419
+ };
420
+ };
421
+
422
+ const testXxe = async () => {
423
+ const xml = `<?xml version="1.0" encoding="UTF-8"?>\n<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>\n<root><name>&xxe;</name></root>`;
424
+ const res = await request('/api/sticker-packs/auth/login', {
425
+ method: 'POST',
426
+ headers: { 'Content-Type': 'application/xml' },
427
+ body: xml,
428
+ });
429
+ const leaked = /root:.*:0:0:/.test(res.text || '');
430
+ const blocked = [400, 401, 403, 405, 415, 422, 429].includes(res.status) && !leaked;
431
+ return {
432
+ id: 15,
433
+ name: 'XML External Entity (XXE)',
434
+ status: blocked ? PASS : WARN,
435
+ note: blocked ? 'Payload XXE bloqueado/não processado.' : 'Comportamento inesperado para payload XXE.',
436
+ evidence: [{ status: res.status, passwd_pattern_found: leaked }],
437
+ };
438
+ };
439
+
440
+ const testBrokenAuth = async () => {
441
+ const checks = ['/api/sticker-packs/admin/overview', '/api/system-summary', '/api/email/health'];
442
+ const evidence = [];
443
+ let allBlocked = true;
444
+ for (const path of checks) {
445
+ const res = await request(path);
446
+ const blocked = [401, 403].includes(res.status);
447
+ if (!blocked) allBlocked = false;
448
+ evidence.push({ path, status: res.status, blocked });
449
+ }
450
+ return {
451
+ id: 16,
452
+ name: 'Broken Authentication',
453
+ status: allBlocked ? PASS : FAIL,
454
+ note: allBlocked ? 'Endpoints protegidos bloquearam acesso sem credenciais.' : 'Algum endpoint protegido respondeu sem bloqueio esperado.',
455
+ evidence,
456
+ };
457
+ };
458
+
459
+ const testBrokenAccessControl = async () => {
460
+ const resAdminFake = await request('/api/sticker-packs/admin/users', {
461
+ headers: { 'x-admin-token': 'fake-token' },
462
+ });
463
+ const resContact = await request('/api/support');
464
+ const blocked = [401, 403].includes(resAdminFake.status) && [401, 403].includes(resContact.status);
465
+ return {
466
+ id: 17,
467
+ name: 'Broken Access Control',
468
+ status: blocked ? PASS : FAIL,
469
+ note: blocked ? 'Acesso privilegiado foi negado sem autorização válida.' : 'Há rota sensível sem bloqueio consistente.',
470
+ evidence: [
471
+ { path: '/api/sticker-packs/admin/users', status: resAdminFake.status },
472
+ { path: '/api/support', status: resContact.status },
473
+ ],
474
+ };
475
+ };
476
+
477
+ const testOpenRedirect = async () => {
478
+ const probes = ['/stickers/create?next=https://evil.example', '/stickers/admin?next=//evil.example'];
479
+ const evidence = [];
480
+ let vulnerable = false;
481
+ for (const path of probes) {
482
+ const res = await request(path);
483
+ const location = String(res.headers.location || '');
484
+ const open = /^https?:\/\/evil\.example/i.test(location) || /^\/\/evil\.example/i.test(location);
485
+ if (open) vulnerable = true;
486
+ evidence.push({ path, status: res.status, location: location || null, open_redirect: open });
487
+ }
488
+ return {
489
+ id: 18,
490
+ name: 'Open Redirect',
491
+ status: vulnerable ? FAIL : PASS,
492
+ note: vulnerable ? 'Foi observada possibilidade de redirecionamento aberto.' : 'Não houve redirecionamento aberto nas rotas testadas.',
493
+ evidence,
494
+ };
495
+ };
496
+
497
+ const testParameterTampering = async () => {
498
+ const probes = ['/api/sticker-packs?limit=-1000&page=-1', '/api/sticker-packs?limit=1000000&page=999999', '/api/sticker-packs/creators?limit=999999'];
499
+ const evidence = [];
500
+ let failed = false;
501
+ for (const path of probes) {
502
+ const res = await request(path);
503
+ const bad = !res.ok || res.status >= 500;
504
+ if (bad) failed = true;
505
+ evidence.push({ path, status: res.status, ok: res.ok });
506
+ }
507
+ return {
508
+ id: 19,
509
+ name: 'Parameter Tampering',
510
+ status: failed ? WARN : PASS,
511
+ note: failed ? 'Algum parâmetro adulterado gerou falha de servidor.' : 'Parâmetros adulterados não causaram erro crítico no smoke test.',
512
+ evidence,
513
+ };
514
+ };
515
+
516
+ const testPathManipulation = async () => {
517
+ const probes = ['/api/sticker-packs/%2e%2e/admin/overview', '/api/sticker-packs/..%2Fadmin%2Foverview', '/api/sticker-packs/%2e%2e/%2e%2e/.env', '/api/sticker-packs/%252e%252e/%252e%252e/.env'];
518
+ const evidence = [];
519
+ let bypass = false;
520
+ for (const path of probes) {
521
+ const res = await request(path);
522
+ const bad = [200, 201, 202].includes(res.status);
523
+ if (bad) bypass = true;
524
+ evidence.push({ path, status: res.status, suspicious_success: bad });
525
+ }
526
+ return {
527
+ id: 20,
528
+ name: 'Path Manipulation',
529
+ status: bypass ? FAIL : PASS,
530
+ note: bypass ? 'Payload de manipulação de caminho obteve sucesso inesperado.' : 'Manipulações de caminho foram bloqueadas.',
531
+ evidence,
532
+ };
533
+ };
534
+
535
+ const run = async () => {
536
+ const startedAt = nowIso();
537
+ const tests = [testSqlInjection, testXss, testCsrf, testDdosSafe, testBruteForce, testCredentialStuffing, testDirectoryTraversal, testFileUploadAttack, testSessionHijacking, testClickjacking, testMitm, testSsrf, testRce, testCommandInjection, testXxe, testBrokenAuth, testBrokenAccessControl, testOpenRedirect, testParameterTampering, testPathManipulation];
538
+
539
+ const results = [];
540
+ for (const testFn of tests) {
541
+ try {
542
+ const result = await testFn();
543
+ results.push(result);
544
+ console.log(`[security-smoketest] ${String(result.id).padStart(2, '0')} ${result.name}: ${result.status}`);
545
+ } catch (error) {
546
+ const fallbackResult = {
547
+ id: results.length + 1,
548
+ name: testFn.name || 'unknown-test',
549
+ status: WARN,
550
+ note: `Falha ao executar teste: ${error?.message || error}`,
551
+ evidence: [],
552
+ };
553
+ results.push(fallbackResult);
554
+ console.log(`[security-smoketest] ${String(fallbackResult.id).padStart(2, '0')} ${fallbackResult.name}: ${fallbackResult.status}`);
555
+ }
556
+ }
557
+
558
+ const endedAt = nowIso();
559
+ const summary = summarizeStatuses(results);
560
+ const report = {
561
+ base_url: BASE_URL,
562
+ started_at: startedAt,
563
+ ended_at: endedAt,
564
+ summary,
565
+ results,
566
+ };
567
+
568
+ const reportDir = path.dirname(path.resolve(REPORT_PATH));
569
+ await fs.mkdir(reportDir, { recursive: true });
570
+ await fs.writeFile(REPORT_PATH, `${JSON.stringify(report, null, 2)}\n`, 'utf8');
571
+
572
+ console.log('[security-smoketest] ---');
573
+ console.log(`[security-smoketest] base_url=${BASE_URL}`);
574
+ console.log(`[security-smoketest] summary=${JSON.stringify(summary)}`);
575
+ console.log(`[security-smoketest] report_path=${REPORT_PATH}`);
576
+ };
577
+
578
+ run().catch((error) => {
579
+ console.error('[security-smoketest] fatal_error', error?.message || error);
580
+ process.exit(2);
581
+ });