stkit 17.2.5__tar.gz → 17.2.7__tar.gz

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 (239) hide show
  1. {stkit-17.2.5 → stkit-17.2.7}/CHANGELOG.md +15 -0
  2. {stkit-17.2.5 → stkit-17.2.7}/PKG-INFO +2 -2
  3. {stkit-17.2.5 → stkit-17.2.7}/pyproject.toml +2 -2
  4. {stkit-17.2.5 → stkit-17.2.7}/stk/__init__.py +1 -1
  5. {stkit-17.2.5 → stkit-17.2.7}/stk/app.py +3 -2
  6. {stkit-17.2.5 → stkit-17.2.7}/stk/cli/copy_rules.py +5 -2
  7. {stkit-17.2.5 → stkit-17.2.7}/stk/cli/doctor.py +13 -0
  8. {stkit-17.2.5 → stkit-17.2.7}/stk/files/__init__.py +13 -1
  9. {stkit-17.2.5 → stkit-17.2.7}/stk/security.py +25 -0
  10. {stkit-17.2.5 → stkit-17.2.7}/tests/test_cli.py +10 -0
  11. {stkit-17.2.5 → stkit-17.2.7}/tests/test_copy_rules.py +3 -1
  12. {stkit-17.2.5 → stkit-17.2.7}/tests/test_files.py +13 -0
  13. {stkit-17.2.5 → stkit-17.2.7}/tests/test_security_compatibility.py +29 -0
  14. {stkit-17.2.5 → stkit-17.2.7}/.gitignore +0 -0
  15. {stkit-17.2.5 → stkit-17.2.7}/LICENSE +0 -0
  16. {stkit-17.2.5 → stkit-17.2.7}/README.md +0 -0
  17. {stkit-17.2.5 → stkit-17.2.7}/SECURITY.md +0 -0
  18. {stkit-17.2.5 → stkit-17.2.7}/examples/minimal/.env-sample +0 -0
  19. {stkit-17.2.5 → stkit-17.2.7}/examples/minimal/.gitignore +0 -0
  20. {stkit-17.2.5 → stkit-17.2.7}/examples/minimal/README.md +0 -0
  21. {stkit-17.2.5 → stkit-17.2.7}/examples/minimal/minimal/__init__.py +0 -0
  22. {stkit-17.2.5 → stkit-17.2.7}/examples/minimal/minimal/app.py +0 -0
  23. {stkit-17.2.5 → stkit-17.2.7}/examples/minimal/minimal/migrations/364232ae77d5_add_note.py +0 -0
  24. {stkit-17.2.5 → stkit-17.2.7}/examples/minimal/minimal/note/__init__.py +0 -0
  25. {stkit-17.2.5 → stkit-17.2.7}/examples/minimal/minimal/note/models.py +0 -0
  26. {stkit-17.2.5 → stkit-17.2.7}/examples/minimal/minimal/note/templates/note/index.html +0 -0
  27. {stkit-17.2.5 → stkit-17.2.7}/examples/minimal/minimal/note/views.py +0 -0
  28. {stkit-17.2.5 → stkit-17.2.7}/examples/minimal/minimal/settings.py +0 -0
  29. {stkit-17.2.5 → stkit-17.2.7}/examples/minimal/minimal/templates/layout.html +0 -0
  30. {stkit-17.2.5 → stkit-17.2.7}/examples/minimal/pyproject.toml +0 -0
  31. {stkit-17.2.5 → stkit-17.2.7}/examples/minimal/tests/__init__.py +0 -0
  32. {stkit-17.2.5 → stkit-17.2.7}/examples/minimal/tests/test_app.py +0 -0
  33. {stkit-17.2.5 → stkit-17.2.7}/examples/minimal/uv.lock +0 -0
  34. {stkit-17.2.5 → stkit-17.2.7}/stk/__main__.py +0 -0
  35. {stkit-17.2.5 → stkit-17.2.7}/stk/agent_login.py +0 -0
  36. {stkit-17.2.5 → stkit-17.2.7}/stk/checks.py +0 -0
  37. {stkit-17.2.5 → stkit-17.2.7}/stk/cli/__init__.py +0 -0
  38. {stkit-17.2.5 → stkit-17.2.7}/stk/cli/agent.py +0 -0
  39. {stkit-17.2.5 → stkit-17.2.7}/stk/cli/base.py +0 -0
  40. {stkit-17.2.5 → stkit-17.2.7}/stk/cli/database.py +0 -0
  41. {stkit-17.2.5 → stkit-17.2.7}/stk/cli/develop.py +0 -0
  42. {stkit-17.2.5 → stkit-17.2.7}/stk/cli/main.py +0 -0
  43. {stkit-17.2.5 → stkit-17.2.7}/stk/cli/reports.py +0 -0
  44. {stkit-17.2.5 → stkit-17.2.7}/stk/cli/smoke.py +0 -0
  45. {stkit-17.2.5 → stkit-17.2.7}/stk/cli/users.py +0 -0
  46. {stkit-17.2.5 → stkit-17.2.7}/stk/cli/watch.py +0 -0
  47. {stkit-17.2.5 → stkit-17.2.7}/stk/extensions.py +0 -0
  48. {stkit-17.2.5 → stkit-17.2.7}/stk/files/models.py +0 -0
  49. {stkit-17.2.5 → stkit-17.2.7}/stk/files/store.py +0 -0
  50. {stkit-17.2.5 → stkit-17.2.7}/stk/files/views.py +0 -0
  51. {stkit-17.2.5 → stkit-17.2.7}/stk/mcp/__init__.py +0 -0
  52. {stkit-17.2.5 → stkit-17.2.7}/stk/mcp/models.py +0 -0
  53. {stkit-17.2.5 → stkit-17.2.7}/stk/mcp/server.py +0 -0
  54. {stkit-17.2.5 → stkit-17.2.7}/stk/mcp/views.py +0 -0
  55. {stkit-17.2.5 → stkit-17.2.7}/stk/migrations/__init__.py +0 -0
  56. {stkit-17.2.5 → stkit-17.2.7}/stk/migrations/env.py +0 -0
  57. {stkit-17.2.5 → stkit-17.2.7}/stk/migrations/script.py.mako +0 -0
  58. {stkit-17.2.5 → stkit-17.2.7}/stk/migrations/versions/stk_0001_initial_schema.py +0 -0
  59. {stkit-17.2.5 → stkit-17.2.7}/stk/migrations/versions/stk_0002_jobs.py +0 -0
  60. {stkit-17.2.5 → stkit-17.2.7}/stk/migrations/versions/stk_0003_files.py +0 -0
  61. {stkit-17.2.5 → stkit-17.2.7}/stk/migrations/versions/stk_0004_notifications.py +0 -0
  62. {stkit-17.2.5 → stkit-17.2.7}/stk/migrations/versions/stk_0005_mcp_tokens.py +0 -0
  63. {stkit-17.2.5 → stkit-17.2.7}/stk/migrations/versions/stk_0006_stk_mcp_token.py +0 -0
  64. {stkit-17.2.5 → stkit-17.2.7}/stk/notification/__init__.py +0 -0
  65. {stkit-17.2.5 → stkit-17.2.7}/stk/notification/models.py +0 -0
  66. {stkit-17.2.5 → stkit-17.2.7}/stk/notification/views.py +0 -0
  67. {stkit-17.2.5 → stkit-17.2.7}/stk/portal/views.py +0 -0
  68. {stkit-17.2.5 → stkit-17.2.7}/stk/public/__init__.py +0 -0
  69. {stkit-17.2.5 → stkit-17.2.7}/stk/public/models.py +0 -0
  70. {stkit-17.2.5 → stkit-17.2.7}/stk/public/views.py +0 -0
  71. {stkit-17.2.5 → stkit-17.2.7}/stk/scaffold/__init__.py +0 -0
  72. {stkit-17.2.5 → stkit-17.2.7}/stk/scaffold/app/CLAUDE.md +0 -0
  73. {stkit-17.2.5 → stkit-17.2.7}/stk/scaffold/app/Dockerfile +0 -0
  74. {stkit-17.2.5 → stkit-17.2.7}/stk/scaffold/app/README.md +0 -0
  75. {stkit-17.2.5 → stkit-17.2.7}/stk/scaffold/app/__app__/__init__.py +0 -0
  76. {stkit-17.2.5 → stkit-17.2.7}/stk/scaffold/app/__app__/app.py +0 -0
  77. {stkit-17.2.5 → stkit-17.2.7}/stk/scaffold/app/__app__/settings.py +0 -0
  78. {stkit-17.2.5 → stkit-17.2.7}/stk/scaffold/app/claude/skills/stk-api/SKILL.md +0 -0
  79. {stkit-17.2.5 → stkit-17.2.7}/stk/scaffold/app/claude/skills/stk-blueprint/SKILL.md +0 -0
  80. {stkit-17.2.5 → stkit-17.2.7}/stk/scaffold/app/claude/skills/stk-deploy/SKILL.md +0 -0
  81. {stkit-17.2.5 → stkit-17.2.7}/stk/scaffold/app/claude/skills/stk-lessons/SKILL.md +0 -0
  82. {stkit-17.2.5 → stkit-17.2.7}/stk/scaffold/app/claude/skills/stk-migrate/SKILL.md +0 -0
  83. {stkit-17.2.5 → stkit-17.2.7}/stk/scaffold/app/docker-compose.yml +0 -0
  84. {stkit-17.2.5 → stkit-17.2.7}/stk/scaffold/app/dockerignore +0 -0
  85. {stkit-17.2.5 → stkit-17.2.7}/stk/scaffold/app/env-sample +0 -0
  86. {stkit-17.2.5 → stkit-17.2.7}/stk/scaffold/app/github/workflows/checks.yml +0 -0
  87. {stkit-17.2.5 → stkit-17.2.7}/stk/scaffold/app/gitignore +0 -0
  88. {stkit-17.2.5 → stkit-17.2.7}/stk/scaffold/app/nginx/nginx.conf +0 -0
  89. {stkit-17.2.5 → stkit-17.2.7}/stk/scaffold/app/nginx/stk.conf +0 -0
  90. {stkit-17.2.5 → stkit-17.2.7}/stk/scaffold/app/postgres/init-db.sh +0 -0
  91. {stkit-17.2.5 → stkit-17.2.7}/stk/scaffold/app/pyproject.toml +0 -0
  92. {stkit-17.2.5 → stkit-17.2.7}/stk/scaffold/app/tests/__init__.py +0 -0
  93. {stkit-17.2.5 → stkit-17.2.7}/stk/scaffold/app/tests/test_app.py +0 -0
  94. {stkit-17.2.5 → stkit-17.2.7}/stk/scaffold/generator.py +0 -0
  95. {stkit-17.2.5 → stkit-17.2.7}/stk/scaffold/templates.py +0 -0
  96. {stkit-17.2.5 → stkit-17.2.7}/stk/settings.py +0 -0
  97. {stkit-17.2.5 → stkit-17.2.7}/stk/shell.py +0 -0
  98. {stkit-17.2.5 → stkit-17.2.7}/stk/static/VERSIONS.txt +0 -0
  99. {stkit-17.2.5 → stkit-17.2.7}/stk/static/css/app.css +0 -0
  100. {stkit-17.2.5 → stkit-17.2.7}/stk/static/css/layout.css +0 -0
  101. {stkit-17.2.5 → stkit-17.2.7}/stk/static/css/vuetify.min.css +0 -0
  102. {stkit-17.2.5 → stkit-17.2.7}/stk/static/icons/fonts/tabler-icons.animals.woff2 +0 -0
  103. {stkit-17.2.5 → stkit-17.2.7}/stk/static/icons/fonts/tabler-icons.arrows-1.woff2 +0 -0
  104. {stkit-17.2.5 → stkit-17.2.7}/stk/static/icons/fonts/tabler-icons.arrows-2.woff2 +0 -0
  105. {stkit-17.2.5 → stkit-17.2.7}/stk/static/icons/fonts/tabler-icons.badges.woff2 +0 -0
  106. {stkit-17.2.5 → stkit-17.2.7}/stk/static/icons/fonts/tabler-icons.brand-1.woff2 +0 -0
  107. {stkit-17.2.5 → stkit-17.2.7}/stk/static/icons/fonts/tabler-icons.brand-2.woff2 +0 -0
  108. {stkit-17.2.5 → stkit-17.2.7}/stk/static/icons/fonts/tabler-icons.buildings.woff2 +0 -0
  109. {stkit-17.2.5 → stkit-17.2.7}/stk/static/icons/fonts/tabler-icons.charts.woff2 +0 -0
  110. {stkit-17.2.5 → stkit-17.2.7}/stk/static/icons/fonts/tabler-icons.communication.woff2 +0 -0
  111. {stkit-17.2.5 → stkit-17.2.7}/stk/static/icons/fonts/tabler-icons.computers.woff2 +0 -0
  112. {stkit-17.2.5 → stkit-17.2.7}/stk/static/icons/fonts/tabler-icons.core.woff2 +0 -0
  113. {stkit-17.2.5 → stkit-17.2.7}/stk/static/icons/fonts/tabler-icons.currencies.woff2 +0 -0
  114. {stkit-17.2.5 → stkit-17.2.7}/stk/static/icons/fonts/tabler-icons.database.woff2 +0 -0
  115. {stkit-17.2.5 → stkit-17.2.7}/stk/static/icons/fonts/tabler-icons.design-1.woff2 +0 -0
  116. {stkit-17.2.5 → stkit-17.2.7}/stk/static/icons/fonts/tabler-icons.design-2.woff2 +0 -0
  117. {stkit-17.2.5 → stkit-17.2.7}/stk/static/icons/fonts/tabler-icons.development.woff2 +0 -0
  118. {stkit-17.2.5 → stkit-17.2.7}/stk/static/icons/fonts/tabler-icons.devices-1.woff2 +0 -0
  119. {stkit-17.2.5 → stkit-17.2.7}/stk/static/icons/fonts/tabler-icons.devices-2.woff2 +0 -0
  120. {stkit-17.2.5 → stkit-17.2.7}/stk/static/icons/fonts/tabler-icons.document.woff2 +0 -0
  121. {stkit-17.2.5 → stkit-17.2.7}/stk/static/icons/fonts/tabler-icons.e-commerce.woff2 +0 -0
  122. {stkit-17.2.5 → stkit-17.2.7}/stk/static/icons/fonts/tabler-icons.electrical.woff2 +0 -0
  123. {stkit-17.2.5 → stkit-17.2.7}/stk/static/icons/fonts/tabler-icons.extensions.woff2 +0 -0
  124. {stkit-17.2.5 → stkit-17.2.7}/stk/static/icons/fonts/tabler-icons.food.woff2 +0 -0
  125. {stkit-17.2.5 → stkit-17.2.7}/stk/static/icons/fonts/tabler-icons.games.woff2 +0 -0
  126. {stkit-17.2.5 → stkit-17.2.7}/stk/static/icons/fonts/tabler-icons.gender.woff2 +0 -0
  127. {stkit-17.2.5 → stkit-17.2.7}/stk/static/icons/fonts/tabler-icons.gestures.woff2 +0 -0
  128. {stkit-17.2.5 → stkit-17.2.7}/stk/static/icons/fonts/tabler-icons.health.woff2 +0 -0
  129. {stkit-17.2.5 → stkit-17.2.7}/stk/static/icons/fonts/tabler-icons.laundry.woff2 +0 -0
  130. {stkit-17.2.5 → stkit-17.2.7}/stk/static/icons/fonts/tabler-icons.letters.woff2 +0 -0
  131. {stkit-17.2.5 → stkit-17.2.7}/stk/static/icons/fonts/tabler-icons.logic.woff2 +0 -0
  132. {stkit-17.2.5 → stkit-17.2.7}/stk/static/icons/fonts/tabler-icons.map.woff2 +0 -0
  133. {stkit-17.2.5 → stkit-17.2.7}/stk/static/icons/fonts/tabler-icons.math.woff2 +0 -0
  134. {stkit-17.2.5 → stkit-17.2.7}/stk/static/icons/fonts/tabler-icons.media.woff2 +0 -0
  135. {stkit-17.2.5 → stkit-17.2.7}/stk/static/icons/fonts/tabler-icons.mood.woff2 +0 -0
  136. {stkit-17.2.5 → stkit-17.2.7}/stk/static/icons/fonts/tabler-icons.nature.woff2 +0 -0
  137. {stkit-17.2.5 → stkit-17.2.7}/stk/static/icons/fonts/tabler-icons.numbers.woff2 +0 -0
  138. {stkit-17.2.5 → stkit-17.2.7}/stk/static/icons/fonts/tabler-icons.photography.woff2 +0 -0
  139. {stkit-17.2.5 → stkit-17.2.7}/stk/static/icons/fonts/tabler-icons.shapes.woff2 +0 -0
  140. {stkit-17.2.5 → stkit-17.2.7}/stk/static/icons/fonts/tabler-icons.sport.woff2 +0 -0
  141. {stkit-17.2.5 → stkit-17.2.7}/stk/static/icons/fonts/tabler-icons.symbols.woff2 +0 -0
  142. {stkit-17.2.5 → stkit-17.2.7}/stk/static/icons/fonts/tabler-icons.system-1.woff2 +0 -0
  143. {stkit-17.2.5 → stkit-17.2.7}/stk/static/icons/fonts/tabler-icons.system-2.woff2 +0 -0
  144. {stkit-17.2.5 → stkit-17.2.7}/stk/static/icons/fonts/tabler-icons.system-3.woff2 +0 -0
  145. {stkit-17.2.5 → stkit-17.2.7}/stk/static/icons/fonts/tabler-icons.text.woff2 +0 -0
  146. {stkit-17.2.5 → stkit-17.2.7}/stk/static/icons/fonts/tabler-icons.vehicles.woff2 +0 -0
  147. {stkit-17.2.5 → stkit-17.2.7}/stk/static/icons/fonts/tabler-icons.version-control.woff2 +0 -0
  148. {stkit-17.2.5 → stkit-17.2.7}/stk/static/icons/fonts/tabler-icons.weather.woff2 +0 -0
  149. {stkit-17.2.5 → stkit-17.2.7}/stk/static/icons/fonts/tabler-icons.zodiac.woff2 +0 -0
  150. {stkit-17.2.5 → stkit-17.2.7}/stk/static/icons/tabler-icons.min.css +0 -0
  151. {stkit-17.2.5 → stkit-17.2.7}/stk/static/img/auth-bg.webp +0 -0
  152. {stkit-17.2.5 → stkit-17.2.7}/stk/static/img/favicon.ico +0 -0
  153. {stkit-17.2.5 → stkit-17.2.7}/stk/static/img/stk.svg +0 -0
  154. {stkit-17.2.5 → stkit-17.2.7}/stk/static/js/axios.min.js +0 -0
  155. {stkit-17.2.5 → stkit-17.2.7}/stk/static/js/components/NotificationDropdown.js +0 -0
  156. {stkit-17.2.5 → stkit-17.2.7}/stk/static/js/components/ThemeSwitcher.js +0 -0
  157. {stkit-17.2.5 → stkit-17.2.7}/stk/static/js/components/TransitionExpand.js +0 -0
  158. {stkit-17.2.5 → stkit-17.2.7}/stk/static/js/components/VerticalNavGroup.js +0 -0
  159. {stkit-17.2.5 → stkit-17.2.7}/stk/static/js/components/VerticalNavLink.js +0 -0
  160. {stkit-17.2.5 → stkit-17.2.7}/stk/static/js/components/VerticalNavSectionTitle.js +0 -0
  161. {stkit-17.2.5 → stkit-17.2.7}/stk/static/js/components/index.js +0 -0
  162. {stkit-17.2.5 → stkit-17.2.7}/stk/static/js/config.js +0 -0
  163. {stkit-17.2.5 → stkit-17.2.7}/stk/static/js/vue.min.js +0 -0
  164. {stkit-17.2.5 → stkit-17.2.7}/stk/static/js/vuetify.min.js +0 -0
  165. {stkit-17.2.5 → stkit-17.2.7}/stk/static/robots.txt +0 -0
  166. {stkit-17.2.5 → stkit-17.2.7}/stk/tasks/__init__.py +0 -0
  167. {stkit-17.2.5 → stkit-17.2.7}/stk/tasks/models.py +0 -0
  168. {stkit-17.2.5 → stkit-17.2.7}/stk/templates/401.html +0 -0
  169. {stkit-17.2.5 → stkit-17.2.7}/stk/templates/404.html +0 -0
  170. {stkit-17.2.5 → stkit-17.2.7}/stk/templates/500.html +0 -0
  171. {stkit-17.2.5 → stkit-17.2.7}/stk/templates/_brand.html +0 -0
  172. {stkit-17.2.5 → stkit-17.2.7}/stk/templates/auth_layout.html +0 -0
  173. {stkit-17.2.5 → stkit-17.2.7}/stk/templates/cms/activities.html +0 -0
  174. {stkit-17.2.5 → stkit-17.2.7}/stk/templates/cms/jobs.html +0 -0
  175. {stkit-17.2.5 → stkit-17.2.7}/stk/templates/cms/roles.html +0 -0
  176. {stkit-17.2.5 → stkit-17.2.7}/stk/templates/cms/users.html +0 -0
  177. {stkit-17.2.5 → stkit-17.2.7}/stk/templates/dashboard.html +0 -0
  178. {stkit-17.2.5 → stkit-17.2.7}/stk/templates/index.html +0 -0
  179. {stkit-17.2.5 → stkit-17.2.7}/stk/templates/layout.html +0 -0
  180. {stkit-17.2.5 → stkit-17.2.7}/stk/templates/login-layout.html +0 -0
  181. {stkit-17.2.5 → stkit-17.2.7}/stk/templates/mcp/tokens.html +0 -0
  182. {stkit-17.2.5 → stkit-17.2.7}/stk/templates/security/_macros.html +0 -0
  183. {stkit-17.2.5 → stkit-17.2.7}/stk/templates/security/_messages.html +0 -0
  184. {stkit-17.2.5 → stkit-17.2.7}/stk/templates/security/change_password.html +0 -0
  185. {stkit-17.2.5 → stkit-17.2.7}/stk/templates/security/confirm_email.html +0 -0
  186. {stkit-17.2.5 → stkit-17.2.7}/stk/templates/security/email/change_notice.html +0 -0
  187. {stkit-17.2.5 → stkit-17.2.7}/stk/templates/security/email/change_notice.txt +0 -0
  188. {stkit-17.2.5 → stkit-17.2.7}/stk/templates/security/email/confirmation_instructions.html +0 -0
  189. {stkit-17.2.5 → stkit-17.2.7}/stk/templates/security/email/confirmation_instructions.txt +0 -0
  190. {stkit-17.2.5 → stkit-17.2.7}/stk/templates/security/email/login_instructions.html +0 -0
  191. {stkit-17.2.5 → stkit-17.2.7}/stk/templates/security/email/login_instructions.txt +0 -0
  192. {stkit-17.2.5 → stkit-17.2.7}/stk/templates/security/email/reset_instructions.html +0 -0
  193. {stkit-17.2.5 → stkit-17.2.7}/stk/templates/security/email/reset_instructions.txt +0 -0
  194. {stkit-17.2.5 → stkit-17.2.7}/stk/templates/security/email/reset_notice.html +0 -0
  195. {stkit-17.2.5 → stkit-17.2.7}/stk/templates/security/email/reset_notice.txt +0 -0
  196. {stkit-17.2.5 → stkit-17.2.7}/stk/templates/security/email/welcome.html +0 -0
  197. {stkit-17.2.5 → stkit-17.2.7}/stk/templates/security/email/welcome.txt +0 -0
  198. {stkit-17.2.5 → stkit-17.2.7}/stk/templates/security/forgot_password.html +0 -0
  199. {stkit-17.2.5 → stkit-17.2.7}/stk/templates/security/login_user.html +0 -0
  200. {stkit-17.2.5 → stkit-17.2.7}/stk/templates/security/mf_recovery.html +0 -0
  201. {stkit-17.2.5 → stkit-17.2.7}/stk/templates/security/mf_recovery_codes.html +0 -0
  202. {stkit-17.2.5 → stkit-17.2.7}/stk/templates/security/register_user.html +0 -0
  203. {stkit-17.2.5 → stkit-17.2.7}/stk/templates/security/reset_password.html +0 -0
  204. {stkit-17.2.5 → stkit-17.2.7}/stk/templates/security/send_confirmation.html +0 -0
  205. {stkit-17.2.5 → stkit-17.2.7}/stk/templates/security/send_login.html +0 -0
  206. {stkit-17.2.5 → stkit-17.2.7}/stk/templates/security/two_factor_select.html +0 -0
  207. {stkit-17.2.5 → stkit-17.2.7}/stk/templates/security/two_factor_setup.html +0 -0
  208. {stkit-17.2.5 → stkit-17.2.7}/stk/templates/security/two_factor_verify_code.html +0 -0
  209. {stkit-17.2.5 → stkit-17.2.7}/stk/templates/security/wan_register.html +0 -0
  210. {stkit-17.2.5 → stkit-17.2.7}/stk/templates/security/wan_signin.html +0 -0
  211. {stkit-17.2.5 → stkit-17.2.7}/stk/templates/security/wan_verify.html +0 -0
  212. {stkit-17.2.5 → stkit-17.2.7}/stk/user/__init__.py +0 -0
  213. {stkit-17.2.5 → stkit-17.2.7}/stk/user/forms.py +0 -0
  214. {stkit-17.2.5 → stkit-17.2.7}/stk/user/models.py +0 -0
  215. {stkit-17.2.5 → stkit-17.2.7}/stk/user/views.py +0 -0
  216. {stkit-17.2.5 → stkit-17.2.7}/stk/utils/email.py +0 -0
  217. {stkit-17.2.5 → stkit-17.2.7}/stk/utils/netguard.py +0 -0
  218. {stkit-17.2.5 → stkit-17.2.7}/stk/utils/ratelimit.py +0 -0
  219. {stkit-17.2.5 → stkit-17.2.7}/stk/websocket.py +0 -0
  220. {stkit-17.2.5 → stkit-17.2.7}/tests/test_agent_operability.py +0 -0
  221. {stkit-17.2.5 → stkit-17.2.7}/tests/test_app_index.py +0 -0
  222. {stkit-17.2.5 → stkit-17.2.7}/tests/test_branding.py +0 -0
  223. {stkit-17.2.5 → stkit-17.2.7}/tests/test_builder_ux.py +0 -0
  224. {stkit-17.2.5 → stkit-17.2.7}/tests/test_composition.py +0 -0
  225. {stkit-17.2.5 → stkit-17.2.7}/tests/test_csrf_exempt.py +0 -0
  226. {stkit-17.2.5 → stkit-17.2.7}/tests/test_error_handler.py +0 -0
  227. {stkit-17.2.5 → stkit-17.2.7}/tests/test_jobs.py +0 -0
  228. {stkit-17.2.5 → stkit-17.2.7}/tests/test_mcp.py +0 -0
  229. {stkit-17.2.5 → stkit-17.2.7}/tests/test_mcp_migration.py +0 -0
  230. {stkit-17.2.5 → stkit-17.2.7}/tests/test_migration_drift.py +0 -0
  231. {stkit-17.2.5 → stkit-17.2.7}/tests/test_netguard.py +0 -0
  232. {stkit-17.2.5 → stkit-17.2.7}/tests/test_notifications.py +0 -0
  233. {stkit-17.2.5 → stkit-17.2.7}/tests/test_production_security.py +0 -0
  234. {stkit-17.2.5 → stkit-17.2.7}/tests/test_revision_imports.py +0 -0
  235. {stkit-17.2.5 → stkit-17.2.7}/tests/test_route_collisions.py +0 -0
  236. {stkit-17.2.5 → stkit-17.2.7}/tests/test_scaffolder.py +0 -0
  237. {stkit-17.2.5 → stkit-17.2.7}/tests/test_shell.py +0 -0
  238. {stkit-17.2.5 → stkit-17.2.7}/tests/test_smoke_pages.py +0 -0
  239. {stkit-17.2.5 → stkit-17.2.7}/tests/test_static_versioning.py +0 -0
@@ -1,5 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ ## v17.2.7 (2026-10-31)
4
+
5
+ No migration required. Requires quart-security 2.0.6.
6
+
7
+ - Registration no longer reveals which emails have an account: an existing email gets the same answer as a new one and nothing is created. With `MAIL_SERVER` set, the owner is emailed in the background that someone tried; its link comes from `STK_PUBLIC_URL`, never the request's Host header (Authentication).
8
+ - After login, users land back on the page that sent them to `/login`, including through 2FA and passkeys (Authentication).
9
+ - `stk.files` refuses to start when `STK_UPLOAD_MAX_BYTES` does not fit under Quart's `MAX_CONTENT_LENGTH`. Before, any limit above about 16 MiB was silently capped with a generic 413 (Configuration).
10
+ - `stk doctor` warns when `STK_PUBLIC_URL` is unset outside development, since any Host header is then accepted.
11
+
12
+ ## v17.2.6 (2026-10-31)
13
+
14
+ No migration required.
15
+
16
+ - `stk smoke`'s `hex-id` rule no longer flags mixed-case names such as `Ed25519`. Ids are printed in one case, so `1fbaac41` and `1FBAAC41` still fail (Build Loop).
17
+
3
18
  ## v17.2.5 (2026-10-31)
4
19
 
5
20
  No migration required.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: stkit
3
- Version: 17.2.5
3
+ Version: 17.2.7
4
4
  Summary: An async Quart framework optimized for AI-assisted development workflows
5
5
  Project-URL: Documentation, https://docs.stkit.dev
6
6
  Project-URL: Source, https://github.com/level09/stk
@@ -28,7 +28,7 @@ Requires-Dist: pillow>=13.0.0
28
28
  Requires-Dist: psycopg2-binary>=2.9.10
29
29
  Requires-Dist: python-dotenv>=1.2.2
30
30
  Requires-Dist: qrcode>=8.2
31
- Requires-Dist: quart-security<3,>=2.0.5
31
+ Requires-Dist: quart-security<3,>=2.0.6
32
32
  Requires-Dist: quart-session>=3.0.0
33
33
  Requires-Dist: quart>=0.20.0
34
34
  Requires-Dist: rich>=14.0.0
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "stkit"
3
- version = "17.2.5"
3
+ version = "17.2.7"
4
4
  description = "An async Quart framework optimized for AI-assisted development workflows"
5
5
  authors = [
6
6
  {name = "Level09", email = "hello@level09.com"}
@@ -30,7 +30,7 @@ dependencies = [
30
30
  "python-dotenv>=1.2.2",
31
31
  "qrcode>=8.2",
32
32
  "quart>=0.20.0",
33
- "quart-security>=2.0.5,<3",
33
+ "quart-security>=2.0.6,<3",
34
34
  "quart-session>=3.0.0",
35
35
  "rich>=14.0.0",
36
36
  "sqlalchemy[asyncio]>=2.0.41",
@@ -3,7 +3,7 @@ stk Framework
3
3
  A modern Python web framework optimized for AI-assisted development.
4
4
  """
5
5
 
6
- __version__ = "17.2.5"
6
+ __version__ = "17.2.7"
7
7
 
8
8
 
9
9
  def __getattr__(name):
@@ -18,7 +18,7 @@ from quart import (
18
18
  send_from_directory,
19
19
  url_for,
20
20
  )
21
- from quart_security import Security, SQLAlchemyUserDatastore
21
+ from quart_security import Security, SQLAlchemyUserDatastore, user_register_conflict
22
22
  from quart_security.views import _ensure_csrf_token
23
23
  from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine
24
24
  from werkzeug.exceptions import HTTPException, NotFound
@@ -172,7 +172,7 @@ def register_extensions(app):
172
172
  except TimeoutError:
173
173
  app.logger.warning("Timed out while disposing SQLAlchemy engine")
174
174
 
175
- from stk.security import register_security_controls
175
+ from stk.security import notify_register_conflict, register_security_controls
176
176
  from stk.utils.ratelimit import register_rate_limits
177
177
 
178
178
  register_rate_limits(app)
@@ -184,6 +184,7 @@ def register_extensions(app):
184
184
  register_form=ExtendedRegisterForm,
185
185
  change_password_form=OAuthAwareChangePasswordForm,
186
186
  )
187
+ user_register_conflict.connect(notify_register_conflict, sender=app, weak=False)
187
188
  # The library resets its session before loading the user. Share that session
188
189
  # so signals and application routes participate in the same transaction.
189
190
  app.before_request(_open_session)
@@ -83,14 +83,17 @@ def uuid(text):
83
83
 
84
84
 
85
85
  def hex_id(text):
86
- """6+ hex characters with at least two digits and a letter, alone or glued to a
87
- word: 'Request 1fbaac41'. Uuids are their own rule."""
86
+ """6+ hex characters in one case with at least two digits and a letter, alone
87
+ or glued to a word: 'Request 1fbaac41'. Uuids are their own rule. Mixed case
88
+ is a name, not an id: 'Ed25519'."""
88
89
  text = UUID.sub(" ", text)
89
90
  out = []
90
91
  for m in HEX.finditer(text):
91
92
  token = m.group()
92
93
  if sum(c.isdigit() for c in token) < 2 or token.isdigit():
93
94
  continue
95
+ if token not in (token.lower(), token.upper()):
96
+ continue
94
97
  if HASH_LABEL.search(text[max(0, m.start() - 20) : m.start()]):
95
98
  continue
96
99
  out.append(_around(text, m))
@@ -193,6 +193,18 @@ def _check_agent_login(config):
193
193
  )
194
194
 
195
195
 
196
+ def _check_public_url(config):
197
+ env = config.get("STK_ENV", "production")
198
+ if env != "development" and not config.get("STK_PUBLIC_URL"):
199
+ return _finding(
200
+ "public url",
201
+ WARN,
202
+ f"STK_PUBLIC_URL unset with STK_ENV={env}: any Host header is accepted",
203
+ "set STK_PUBLIC_URL to the origin users visit, e.g. https://app.example.com",
204
+ )
205
+ return _finding("public url", OK, config.get("STK_PUBLIC_URL") or "development")
206
+
207
+
196
208
  def _check_browser():
197
209
  try:
198
210
  from playwright.sync_api import sync_playwright
@@ -244,6 +256,7 @@ def build_doctor_report():
244
256
  _check_migrations(app),
245
257
  _check_admin(),
246
258
  _check_agent_login(app.config),
259
+ _check_public_url(app.config),
247
260
  ]
248
261
  findings += [_check_assets(), _check_browser(), _check_repl()]
249
262
  if any(item["status"] == FAIL for item in findings):
@@ -8,10 +8,22 @@ from pathlib import Path
8
8
 
9
9
 
10
10
  def init_app(app):
11
- from stk.files.views import SNIFFED, bp, can_read
11
+ from stk.files.views import MULTIPART_SLACK, SNIFFED, bp, can_read
12
12
 
13
13
  app.config.setdefault("STK_UPLOAD_DIR", str(Path(app.instance_path) / "files"))
14
14
  app.config.setdefault("STK_UPLOAD_MAX_BYTES", 10 * 1024 * 1024)
15
+ # Quart caps every request body at MAX_CONTENT_LENGTH before a view runs, so
16
+ # a larger upload limit would never take effect.
17
+ limit, body_max = (
18
+ app.config["STK_UPLOAD_MAX_BYTES"],
19
+ app.config["MAX_CONTENT_LENGTH"],
20
+ )
21
+ if body_max is not None and limit + MULTIPART_SLACK > body_max:
22
+ raise ValueError(
23
+ f"STK_UPLOAD_MAX_BYTES ({limit}) plus {MULTIPART_SLACK} bytes of"
24
+ f" multipart framing exceeds MAX_CONTENT_LENGTH ({body_max}): raise"
25
+ " MAX_CONTENT_LENGTH or lower STK_UPLOAD_MAX_BYTES"
26
+ )
15
27
  app.config.setdefault(
16
28
  "STK_UPLOAD_TYPES",
17
29
  ("image/png", "image/jpeg", "image/gif", "image/webp", "application/pdf"),
@@ -124,6 +124,31 @@ def register_security_controls(app):
124
124
  return response
125
125
 
126
126
 
127
+ async def notify_register_conflict(app, user):
128
+ """Tell an account's owner that someone tried to register with their email.
129
+
130
+ Registration answers as if it succeeded, so this mail is the owner's only
131
+ sign. It goes out in the background: waiting on SMTP would make this one
132
+ response slower and reveal the account. Links come from STK_PUBLIC_URL,
133
+ never the request's Host header, which the sender controls."""
134
+ if not app.config.get("MAIL_SERVER"):
135
+ app.logger.warning("Sign-up attempt for an existing account: no MAIL_SERVER")
136
+ return
137
+ from stk.utils.email import send_email
138
+
139
+ public_url = app.config.get("STK_PUBLIC_URL")
140
+ where = f" at {public_url}" if public_url else ""
141
+ sign_in = f"\n\nSign in at {public_url.rstrip('/')}/login" if public_url else ""
142
+ body = (
143
+ f"Someone tried to create a {app.config['APP_NAME']} account{where} with"
144
+ " this email address. You already have one, so nothing changed."
145
+ f"{sign_in}\n\nIf it was not you, you can ignore this email."
146
+ )
147
+ app.add_background_task(
148
+ send_email, "Sign-up attempt with your email", user.email, body
149
+ )
150
+
151
+
127
152
  async def check_password_policy(password):
128
153
  from quart import current_app
129
154
 
@@ -55,6 +55,16 @@ if __name__ == "__main__":
55
55
  unittest.main()
56
56
 
57
57
 
58
+ class PublicUrlDoctorTest(unittest.TestCase):
59
+ def test_warns_only_outside_development_without_a_public_url(self):
60
+ from stk.cli.doctor import _check_public_url
61
+
62
+ self.assertEqual(_check_public_url({})["status"], "warn")
63
+ self.assertEqual(_check_public_url({"STK_ENV": "development"})["status"], "ok")
64
+ set_url = {"STK_PUBLIC_URL": "https://app.example.com"}
65
+ self.assertEqual(_check_public_url(set_url)["status"], "ok")
66
+
67
+
58
68
  class DowngradeArgumentTest(unittest.TestCase):
59
69
  def test_relative_revision_is_accepted(self):
60
70
  with patch("stk.cli.database.run_alembic") as run:
@@ -31,9 +31,11 @@ class CopyRuleTests(unittest.TestCase):
31
31
  def test_hex_id(self):
32
32
  self.assertRule(
33
33
  hex_id,
34
- ["Request 1fbaac41 failed", "texteb148f8f"],
34
+ ["Request 1fbaac41 failed", "texteb148f8f", "Build 1FBAAC41"],
35
35
  [
36
36
  "SHA-256: 9f86d081884c7d65",
37
+ "Every run gets a fresh Ed25519 key",
38
+ "a facade from a decade ago",
37
39
  "Total 1234567",
38
40
  "Accepted",
39
41
  "Owner 3f2b8c1e-9a4d-4e2f-8b1c-0d9e7f6a5b4c",
@@ -82,6 +82,19 @@ class FilesTests(unittest.IsolatedAsyncioTestCase):
82
82
  def stored(self):
83
83
  return sorted(p.name for p in Path(self.dir.name).rglob("*") if p.is_file())
84
84
 
85
+ def test_upload_limit_above_the_body_limit_refuses_to_start(self):
86
+ class Config(TestingConfig):
87
+ STK_UPLOAD_DIR = self.dir.name
88
+ STK_UPLOAD_MAX_BYTES = 20 * 1024 * 1024
89
+
90
+ with self.assertRaisesRegex(ValueError, "MAX_CONTENT_LENGTH"):
91
+ Stk(__name__, config=Config, extensions=[stk.files])
92
+
93
+ class Raised(Config):
94
+ MAX_CONTENT_LENGTH = 21 * 1024 * 1024
95
+
96
+ Stk(__name__, config=Raised, extensions=[stk.files])
97
+
85
98
  async def test_same_bytes_are_stored_once(self):
86
99
  alice, bob = await self.client(), await self.client("bob@example.com")
87
100
  data = png()
@@ -146,5 +146,34 @@ class SecurityCompatibilityTests(unittest.IsolatedAsyncioTestCase):
146
146
  )
147
147
  self.assertNotEqual((await second.get("/dashboard/")).status_code, 200)
148
148
 
149
+ async def test_registering_an_existing_email_mails_the_owner_in_background(self):
150
+ self.app.config.update(
151
+ SECURITY_REGISTERABLE=True,
152
+ MAIL_SERVER="smtp.example.com",
153
+ STK_PUBLIC_URL="http://localhost",
154
+ )
155
+ queued = []
156
+ self.app.add_background_task = lambda func, *args: queued.append(args)
157
+ client = self.app.test_client()
158
+ await client.get("/login")
159
+ async with client.session_transaction() as cookie:
160
+ csrf = cookie["_csrf_token"]
161
+ form = {
162
+ "email": "USER@example.com",
163
+ "password": "AnotherPassword123!",
164
+ "password_confirm": "AnotherPassword123!",
165
+ "csrf_token": csrf,
166
+ }
167
+ response = await client.post("/register", form=form)
168
+ self.assertEqual(response.status_code, 302)
169
+ self.assertEqual(len(queued), 1)
170
+ subject, recipient, body = queued[0]
171
+ self.assertEqual(recipient, "user@example.com")
172
+ self.assertIn("http://localhost/login", body)
173
+ # A forged Host header never reaches the mail's links.
174
+ self.app.config["STK_PUBLIC_URL"] = None
175
+ await client.post("/register", form=form, headers={"Host": "evil.example"})
176
+ self.assertNotIn("evil.example", queued[1][2])
177
+
149
178
  def test_cookie_setting_preserves_local_http_support(self):
150
179
  self.assertFalse(self.app.config["SESSION_COOKIE_SECURE"])
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes