cello-framework 1.2.1__tar.gz → 1.2.2__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 (288) hide show
  1. {cello_framework-1.2.1 → cello_framework-1.2.2}/CLAUDE.md +3 -2
  2. {cello_framework-1.2.1 → cello_framework-1.2.2}/Cargo.lock +1 -1
  3. {cello_framework-1.2.1 → cello_framework-1.2.2}/Cargo.toml +1 -1
  4. {cello_framework-1.2.1 → cello_framework-1.2.2}/PKG-INFO +1 -1
  5. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/releases/v1.2.1.md +58 -0
  6. cello_framework-1.2.2/docs/releases/v1.2.2.md +131 -0
  7. {cello_framework-1.2.1 → cello_framework-1.2.2}/mkdocs.yml +1 -0
  8. {cello_framework-1.2.1 → cello_framework-1.2.2}/pyproject.toml +1 -1
  9. {cello_framework-1.2.1 → cello_framework-1.2.2}/python/cello/__init__.py +1 -1
  10. {cello_framework-1.2.1 → cello_framework-1.2.2}/src/middleware/auth.rs +4 -4
  11. {cello_framework-1.2.1 → cello_framework-1.2.2}/src/middleware/csrf.rs +4 -3
  12. {cello_framework-1.2.1 → cello_framework-1.2.2}/src/middleware/guards.rs +2 -2
  13. {cello_framework-1.2.1 → cello_framework-1.2.2}/src/middleware/mod.rs +13 -0
  14. {cello_framework-1.2.1 → cello_framework-1.2.2}/src/middleware/rate_limit.rs +5 -5
  15. {cello_framework-1.2.1 → cello_framework-1.2.2}/src/middleware/session.rs +3 -3
  16. {cello_framework-1.2.1 → cello_framework-1.2.2}/src/minijinja_engine.rs +5 -5
  17. {cello_framework-1.2.1 → cello_framework-1.2.2}/tests/test_cello.py +3 -3
  18. {cello_framework-1.2.1 → cello_framework-1.2.2}/tests/test_minijinja.py +1 -1
  19. {cello_framework-1.2.1 → cello_framework-1.2.2}/.github/workflows/ci.yml +0 -0
  20. {cello_framework-1.2.1 → cello_framework-1.2.2}/.github/workflows/docs.yml +0 -0
  21. {cello_framework-1.2.1 → cello_framework-1.2.2}/.github/workflows/publish.yml +0 -0
  22. {cello_framework-1.2.1 → cello_framework-1.2.2}/.gitignore +0 -0
  23. {cello_framework-1.2.1 → cello_framework-1.2.2}/CONTRIBUTING.md +0 -0
  24. {cello_framework-1.2.1 → cello_framework-1.2.2}/ENTERPRISE_ROADMAP.md +0 -0
  25. {cello_framework-1.2.1 → cello_framework-1.2.2}/LICENSE +0 -0
  26. {cello_framework-1.2.1 → cello_framework-1.2.2}/PUBLISHING.md +0 -0
  27. {cello_framework-1.2.1 → cello_framework-1.2.2}/README.md +0 -0
  28. {cello_framework-1.2.1 → cello_framework-1.2.2}/benchmarks/README.md +0 -0
  29. {cello_framework-1.2.1 → cello_framework-1.2.2}/benchmarks/benchmark.py +0 -0
  30. {cello_framework-1.2.1 → cello_framework-1.2.2}/benchmarks/compare/README.md +0 -0
  31. {cello_framework-1.2.1 → cello_framework-1.2.2}/benchmarks/compare/apps/__init__.py +0 -0
  32. {cello_framework-1.2.1 → cello_framework-1.2.2}/benchmarks/compare/apps/blacksheep_app.py +0 -0
  33. {cello_framework-1.2.1 → cello_framework-1.2.2}/benchmarks/compare/apps/cello_app.py +0 -0
  34. {cello_framework-1.2.1 → cello_framework-1.2.2}/benchmarks/compare/apps/fastapi_app.py +0 -0
  35. {cello_framework-1.2.1 → cello_framework-1.2.2}/benchmarks/compare/apps/robyn_app.py +0 -0
  36. {cello_framework-1.2.1 → cello_framework-1.2.2}/benchmarks/compare/requirements.txt +0 -0
  37. {cello_framework-1.2.1 → cello_framework-1.2.2}/benchmarks/compare/run_benchmarks.py +0 -0
  38. {cello_framework-1.2.1 → cello_framework-1.2.2}/benchmarks/quick_bench.py +0 -0
  39. {cello_framework-1.2.1 → cello_framework-1.2.2}/cello.md +0 -0
  40. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/community/code-of-conduct.md +0 -0
  41. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/community/contributing.md +0 -0
  42. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/community/index.md +0 -0
  43. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/community/support.md +0 -0
  44. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/enterprise/deployment/docker.md +0 -0
  45. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/enterprise/deployment/kubernetes.md +0 -0
  46. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/enterprise/deployment/service-mesh.md +0 -0
  47. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/enterprise/index.md +0 -0
  48. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/enterprise/integration/database.md +0 -0
  49. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/enterprise/integration/graphql.md +0 -0
  50. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/enterprise/integration/grpc.md +0 -0
  51. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/enterprise/integration/message-queues.md +0 -0
  52. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/enterprise/observability/health-checks.md +0 -0
  53. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/enterprise/observability/metrics.md +0 -0
  54. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/enterprise/observability/opentelemetry.md +0 -0
  55. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/enterprise/observability/tracing.md +0 -0
  56. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/enterprise/roadmap.md +0 -0
  57. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/examples/advanced/background-tasks.md +0 -0
  58. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/examples/advanced/file-storage.md +0 -0
  59. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/examples/advanced/fullstack.md +0 -0
  60. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/examples/advanced/graphql.md +0 -0
  61. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/examples/advanced/microservices.md +0 -0
  62. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/examples/advanced/realtime-dashboard.md +0 -0
  63. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/examples/advanced/redis-caching.md +0 -0
  64. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/examples/basic/database.md +0 -0
  65. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/examples/basic/forms.md +0 -0
  66. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/examples/basic/hello-world.md +0 -0
  67. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/examples/basic/jwt-auth.md +0 -0
  68. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/examples/basic/query-params.md +0 -0
  69. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/examples/basic/rest-api.md +0 -0
  70. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/examples/enterprise/api-gateway.md +0 -0
  71. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/examples/enterprise/event-sourcing.md +0 -0
  72. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/examples/enterprise/health-checks.md +0 -0
  73. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/examples/enterprise/multi-tenant.md +0 -0
  74. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/examples/enterprise/oauth2.md +0 -0
  75. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/examples/enterprise/rate-limiting.md +0 -0
  76. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/examples/index.md +0 -0
  77. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/examples/real/adaptive-rate-limiting.md +0 -0
  78. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/examples/real/advanced-middleware.md +0 -0
  79. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/examples/real/advanced-patterns.md +0 -0
  80. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/examples/real/all-features.md +0 -0
  81. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/examples/real/api-protocols.md +0 -0
  82. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/examples/real/async-handlers.md +0 -0
  83. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/examples/real/auto-openapi.md +0 -0
  84. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/examples/real/blueprints-advanced.md +0 -0
  85. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/examples/real/circuit-breaker.md +0 -0
  86. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/examples/real/cluster-demo.md +0 -0
  87. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/examples/real/complete-showcase.md +0 -0
  88. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/examples/real/comprehensive-demo.md +0 -0
  89. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/examples/real/database-demo.md +0 -0
  90. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/examples/real/dto-validation.md +0 -0
  91. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/examples/real/enterprise-config.md +0 -0
  92. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/examples/real/guards.md +0 -0
  93. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/examples/real/hello-world.md +0 -0
  94. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/examples/real/lifecycle-hooks.md +0 -0
  95. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/examples/real/middleware-demo.md +0 -0
  96. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/examples/real/minijinja-advanced.md +0 -0
  97. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/examples/real/minijinja-basic.md +0 -0
  98. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/examples/real/minijinja-blog.md +0 -0
  99. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/examples/real/minijinja-emails.md +0 -0
  100. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/examples/real/minijinja-forms.md +0 -0
  101. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/examples/real/minijinja-macros.md +0 -0
  102. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/examples/real/security.md +0 -0
  103. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/examples/real/simple-api.md +0 -0
  104. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/examples/real/smart-caching.md +0 -0
  105. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/examples/real/streaming-sse.md +0 -0
  106. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/features/advanced/background-tasks.md +0 -0
  107. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/features/advanced/dependency-injection.md +0 -0
  108. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/features/advanced/dto-validation.md +0 -0
  109. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/features/advanced/file-uploads.md +0 -0
  110. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/features/advanced/static-files.md +0 -0
  111. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/features/advanced/templates.md +0 -0
  112. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/features/core/async.md +0 -0
  113. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/features/core/blueprints.md +0 -0
  114. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/features/core/requests.md +0 -0
  115. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/features/core/responses.md +0 -0
  116. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/features/core/routing.md +0 -0
  117. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/features/index.md +0 -0
  118. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/features/middleware/caching.md +0 -0
  119. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/features/middleware/circuit-breaker.md +0 -0
  120. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/features/middleware/compression.md +0 -0
  121. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/features/middleware/cors.md +0 -0
  122. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/features/middleware/logging.md +0 -0
  123. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/features/middleware/overview.md +0 -0
  124. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/features/middleware/rate-limiting.md +0 -0
  125. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/features/minijinja-templates.md +0 -0
  126. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/features/realtime/sse.md +0 -0
  127. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/features/realtime/websocket.md +0 -0
  128. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/features/security/authentication.md +0 -0
  129. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/features/security/csrf.md +0 -0
  130. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/features/security/guards.md +0 -0
  131. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/features/security/headers.md +0 -0
  132. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/features/security/jwt.md +0 -0
  133. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/features/security/overview.md +0 -0
  134. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/features/security/sessions.md +0 -0
  135. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/features/templates.md +0 -0
  136. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/getting-started/configuration.md +0 -0
  137. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/getting-started/first-app.md +0 -0
  138. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/getting-started/index.md +0 -0
  139. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/getting-started/installation.md +0 -0
  140. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/getting-started/project-structure.md +0 -0
  141. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/getting-started/quickstart.md +0 -0
  142. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/includes/abbreviations.md +0 -0
  143. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/index.md +0 -0
  144. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/javascripts/extra.js +0 -0
  145. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/learn/guides/best-practices.md +0 -0
  146. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/learn/guides/deployment.md +0 -0
  147. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/learn/guides/error-handling.md +0 -0
  148. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/learn/guides/performance.md +0 -0
  149. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/learn/guides/testing.md +0 -0
  150. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/learn/index.md +0 -0
  151. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/learn/patterns/cqrs.md +0 -0
  152. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/learn/patterns/event-driven.md +0 -0
  153. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/learn/patterns/repository.md +0 -0
  154. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/learn/patterns/service-layer.md +0 -0
  155. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/learn/tutorials/auth-system.md +0 -0
  156. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/learn/tutorials/chat-app.md +0 -0
  157. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/learn/tutorials/microservices.md +0 -0
  158. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/learn/tutorials/rest-api.md +0 -0
  159. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/logo-full.png +0 -0
  160. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/logo-icon.svg +0 -0
  161. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/logo.jpg +0 -0
  162. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/logo.svg +0 -0
  163. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/overrides/main.html +0 -0
  164. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/reference/api/app.md +0 -0
  165. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/reference/api/blueprint.md +0 -0
  166. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/reference/api/context.md +0 -0
  167. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/reference/api/guards.md +0 -0
  168. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/reference/api/middleware.md +0 -0
  169. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/reference/api/request.md +0 -0
  170. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/reference/api/response.md +0 -0
  171. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/reference/cli.md +0 -0
  172. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/reference/config/middleware.md +0 -0
  173. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/reference/config/security.md +0 -0
  174. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/reference/config/server.md +0 -0
  175. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/reference/errors.md +0 -0
  176. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/reference/index.md +0 -0
  177. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/releases/changelog.md +0 -0
  178. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/releases/index.md +0 -0
  179. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/releases/migration.md +0 -0
  180. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/releases/v0.10.0.md +0 -0
  181. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/releases/v0.3.0.md +0 -0
  182. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/releases/v0.4.0.md +0 -0
  183. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/releases/v0.5.0.md +0 -0
  184. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/releases/v0.6.0.md +0 -0
  185. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/releases/v0.7.0.md +0 -0
  186. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/releases/v0.8.0.md +0 -0
  187. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/releases/v0.9.0.md +0 -0
  188. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/releases/v1.0.0.md +0 -0
  189. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/releases/v1.0.1.md +0 -0
  190. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/releases/v1.1.0.md +0 -0
  191. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/releases/v1.2.0.md +0 -0
  192. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/requirements.txt +0 -0
  193. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/stylesheets/extra.css +0 -0
  194. {cello_framework-1.2.1 → cello_framework-1.2.2}/docs/tags.md +0 -0
  195. {cello_framework-1.2.1 → cello_framework-1.2.2}/examples/adaptive_rate_limit.py +0 -0
  196. {cello_framework-1.2.1 → cello_framework-1.2.2}/examples/advanced.py +0 -0
  197. {cello_framework-1.2.1 → cello_framework-1.2.2}/examples/advanced_middleware.py +0 -0
  198. {cello_framework-1.2.1 → cello_framework-1.2.2}/examples/advanced_patterns_demo.py +0 -0
  199. {cello_framework-1.2.1 → cello_framework-1.2.2}/examples/all_features_demo.py +0 -0
  200. {cello_framework-1.2.1 → cello_framework-1.2.2}/examples/api_protocols_demo.py +0 -0
  201. {cello_framework-1.2.1 → cello_framework-1.2.2}/examples/async_demo.py +0 -0
  202. {cello_framework-1.2.1 → cello_framework-1.2.2}/examples/auto_openapi_demo.py +0 -0
  203. {cello_framework-1.2.1 → cello_framework-1.2.2}/examples/circuit_breaker.py +0 -0
  204. {cello_framework-1.2.1 → cello_framework-1.2.2}/examples/cluster_demo.py +0 -0
  205. {cello_framework-1.2.1 → cello_framework-1.2.2}/examples/complete_showcase.py +0 -0
  206. {cello_framework-1.2.1 → cello_framework-1.2.2}/examples/comprehensive_demo.py +0 -0
  207. {cello_framework-1.2.1 → cello_framework-1.2.2}/examples/database_demo.py +0 -0
  208. {cello_framework-1.2.1 → cello_framework-1.2.2}/examples/dto_validation.py +0 -0
  209. {cello_framework-1.2.1 → cello_framework-1.2.2}/examples/enterprise.py +0 -0
  210. {cello_framework-1.2.1 → cello_framework-1.2.2}/examples/guards.py +0 -0
  211. {cello_framework-1.2.1 → cello_framework-1.2.2}/examples/hello.py +0 -0
  212. {cello_framework-1.2.1 → cello_framework-1.2.2}/examples/lifecycle_hooks.py +0 -0
  213. {cello_framework-1.2.1 → cello_framework-1.2.2}/examples/middleware_demo.py +0 -0
  214. {cello_framework-1.2.1 → cello_framework-1.2.2}/examples/minijinja_advanced.py +0 -0
  215. {cello_framework-1.2.1 → cello_framework-1.2.2}/examples/minijinja_basic.py +0 -0
  216. {cello_framework-1.2.1 → cello_framework-1.2.2}/examples/minijinja_blog.py +0 -0
  217. {cello_framework-1.2.1 → cello_framework-1.2.2}/examples/minijinja_emails.py +0 -0
  218. {cello_framework-1.2.1 → cello_framework-1.2.2}/examples/minijinja_forms.py +0 -0
  219. {cello_framework-1.2.1 → cello_framework-1.2.2}/examples/minijinja_macros.py +0 -0
  220. {cello_framework-1.2.1 → cello_framework-1.2.2}/examples/security.py +0 -0
  221. {cello_framework-1.2.1 → cello_framework-1.2.2}/examples/simple_api.py +0 -0
  222. {cello_framework-1.2.1 → cello_framework-1.2.2}/examples/smart_caching.py +0 -0
  223. {cello_framework-1.2.1 → cello_framework-1.2.2}/examples/streaming_demo.py +0 -0
  224. {cello_framework-1.2.1 → cello_framework-1.2.2}/python/cello/cqrs.py +0 -0
  225. {cello_framework-1.2.1 → cello_framework-1.2.2}/python/cello/database.py +0 -0
  226. {cello_framework-1.2.1 → cello_framework-1.2.2}/python/cello/eventsourcing.py +0 -0
  227. {cello_framework-1.2.1 → cello_framework-1.2.2}/python/cello/graphql.py +0 -0
  228. {cello_framework-1.2.1 → cello_framework-1.2.2}/python/cello/grpc.py +0 -0
  229. {cello_framework-1.2.1 → cello_framework-1.2.2}/python/cello/guards.py +0 -0
  230. {cello_framework-1.2.1 → cello_framework-1.2.2}/python/cello/messaging.py +0 -0
  231. {cello_framework-1.2.1 → cello_framework-1.2.2}/python/cello/saga.py +0 -0
  232. {cello_framework-1.2.1 → cello_framework-1.2.2}/python/cello/validation.py +0 -0
  233. {cello_framework-1.2.1 → cello_framework-1.2.2}/src/arena.rs +0 -0
  234. {cello_framework-1.2.1 → cello_framework-1.2.2}/src/background.rs +0 -0
  235. {cello_framework-1.2.1 → cello_framework-1.2.2}/src/blueprint.rs +0 -0
  236. {cello_framework-1.2.1 → cello_framework-1.2.2}/src/context.rs +0 -0
  237. {cello_framework-1.2.1 → cello_framework-1.2.2}/src/dependency.rs +0 -0
  238. {cello_framework-1.2.1 → cello_framework-1.2.2}/src/dto.rs +0 -0
  239. {cello_framework-1.2.1 → cello_framework-1.2.2}/src/error.rs +0 -0
  240. {cello_framework-1.2.1 → cello_framework-1.2.2}/src/handler.rs +0 -0
  241. {cello_framework-1.2.1 → cello_framework-1.2.2}/src/http_client.rs +0 -0
  242. {cello_framework-1.2.1 → cello_framework-1.2.2}/src/json.rs +0 -0
  243. {cello_framework-1.2.1 → cello_framework-1.2.2}/src/lib.rs +0 -0
  244. {cello_framework-1.2.1 → cello_framework-1.2.2}/src/lifecycle.rs +0 -0
  245. {cello_framework-1.2.1 → cello_framework-1.2.2}/src/middleware/body_limit.rs +0 -0
  246. {cello_framework-1.2.1 → cello_framework-1.2.2}/src/middleware/cache.rs +0 -0
  247. {cello_framework-1.2.1 → cello_framework-1.2.2}/src/middleware/circuit_breaker.rs +0 -0
  248. {cello_framework-1.2.1 → cello_framework-1.2.2}/src/middleware/cors.rs +0 -0
  249. {cello_framework-1.2.1 → cello_framework-1.2.2}/src/middleware/cqrs.rs +0 -0
  250. {cello_framework-1.2.1 → cello_framework-1.2.2}/src/middleware/database.rs +0 -0
  251. {cello_framework-1.2.1 → cello_framework-1.2.2}/src/middleware/etag.rs +0 -0
  252. {cello_framework-1.2.1 → cello_framework-1.2.2}/src/middleware/eventsourcing.rs +0 -0
  253. {cello_framework-1.2.1 → cello_framework-1.2.2}/src/middleware/exception_handler.rs +0 -0
  254. {cello_framework-1.2.1 → cello_framework-1.2.2}/src/middleware/graphql.rs +0 -0
  255. {cello_framework-1.2.1 → cello_framework-1.2.2}/src/middleware/grpc.rs +0 -0
  256. {cello_framework-1.2.1 → cello_framework-1.2.2}/src/middleware/health.rs +0 -0
  257. {cello_framework-1.2.1 → cello_framework-1.2.2}/src/middleware/messaging.rs +0 -0
  258. {cello_framework-1.2.1 → cello_framework-1.2.2}/src/middleware/prometheus.rs +0 -0
  259. {cello_framework-1.2.1 → cello_framework-1.2.2}/src/middleware/redis.rs +0 -0
  260. {cello_framework-1.2.1 → cello_framework-1.2.2}/src/middleware/request_id.rs +0 -0
  261. {cello_framework-1.2.1 → cello_framework-1.2.2}/src/middleware/saga.rs +0 -0
  262. {cello_framework-1.2.1 → cello_framework-1.2.2}/src/middleware/security.rs +0 -0
  263. {cello_framework-1.2.1 → cello_framework-1.2.2}/src/middleware/static_files.rs +0 -0
  264. {cello_framework-1.2.1 → cello_framework-1.2.2}/src/middleware/telemetry.rs +0 -0
  265. {cello_framework-1.2.1 → cello_framework-1.2.2}/src/multipart.rs +0 -0
  266. {cello_framework-1.2.1 → cello_framework-1.2.2}/src/openapi.rs +0 -0
  267. {cello_framework-1.2.1 → cello_framework-1.2.2}/src/request/mod.rs +0 -0
  268. {cello_framework-1.2.1 → cello_framework-1.2.2}/src/request/multipart_streaming.rs +0 -0
  269. {cello_framework-1.2.1 → cello_framework-1.2.2}/src/request/parsing.rs +0 -0
  270. {cello_framework-1.2.1 → cello_framework-1.2.2}/src/response/mod.rs +0 -0
  271. {cello_framework-1.2.1 → cello_framework-1.2.2}/src/response/streaming.rs +0 -0
  272. {cello_framework-1.2.1 → cello_framework-1.2.2}/src/response/xml.rs +0 -0
  273. {cello_framework-1.2.1 → cello_framework-1.2.2}/src/router.rs +0 -0
  274. {cello_framework-1.2.1 → cello_framework-1.2.2}/src/routing/constraints.rs +0 -0
  275. {cello_framework-1.2.1 → cello_framework-1.2.2}/src/routing/mod.rs +0 -0
  276. {cello_framework-1.2.1 → cello_framework-1.2.2}/src/server/cluster.rs +0 -0
  277. {cello_framework-1.2.1 → cello_framework-1.2.2}/src/server/mod.rs +0 -0
  278. {cello_framework-1.2.1 → cello_framework-1.2.2}/src/server/protocols.rs +0 -0
  279. {cello_framework-1.2.1 → cello_framework-1.2.2}/src/sse.rs +0 -0
  280. {cello_framework-1.2.1 → cello_framework-1.2.2}/src/template.rs +0 -0
  281. {cello_framework-1.2.1 → cello_framework-1.2.2}/src/timeout.rs +0 -0
  282. {cello_framework-1.2.1 → cello_framework-1.2.2}/src/websocket.rs +0 -0
  283. {cello_framework-1.2.1 → cello_framework-1.2.2}/tests/verify_adaptive.py +0 -0
  284. {cello_framework-1.2.1 → cello_framework-1.2.2}/tests/verify_caching.py +0 -0
  285. {cello_framework-1.2.1 → cello_framework-1.2.2}/tests/verify_circuit_breaker.py +0 -0
  286. {cello_framework-1.2.1 → cello_framework-1.2.2}/tests/verify_dto.py +0 -0
  287. {cello_framework-1.2.1 → cello_framework-1.2.2}/tests/verify_guards_impl.py +0 -0
  288. {cello_framework-1.2.1 → cello_framework-1.2.2}/tests/verify_lifecycle.py +0 -0
@@ -4,7 +4,7 @@
4
4
 
5
5
  **Cello** is an ultra-fast, Rust-powered Python async web framework designed to achieve C-level performance on the hot path while maintaining Python's developer experience. It's the successor to frameworks like FastAPI, Robyn, and Litestar, combining their best features with pure Rust implementation for maximum performance.
6
6
 
7
- **Version:** 1.1.0
7
+ **Version:** 1.2.2
8
8
  **License:** MIT
9
9
  **Python Requirement:** 3.12+
10
10
  **Author:** Jagadeesh Katla
@@ -346,7 +346,8 @@ def handle_value_error(request, exc):
346
346
 
347
347
  ## Version History
348
348
 
349
- - **v1.2.1**: Bug fixes — server port never bound (`pyo3_asyncio` replaced with native `py.allow_threads` + `tokio::block_on`); `ProblemDetails` was missing from Python module export; `And`/`Or` guards now accept both `*args` and list styles; doc corrections (`type_uri` not `type_url`)
349
+ - **v1.2.2**: Security & bug fixes — CSRF `HttpOnly` on double-submit cookie (critical, broke all AJAX CSRF); all middleware `skip_path` prefix bypass fixed via `path_matches_skip()` helper; `FixedWindowStore` window_start never updated after reset; unused `mut` cleaned in minijinja tests
350
+ - **v1.2.1**: Bug fixes — server port never bound (`pyo3_asyncio` replaced with native `py.allow_threads` + `tokio::block_on`); `ProblemDetails` was missing from Python module export; `And`/`Or` guards now accept both `*args` and list styles; CSRF `HttpOnly` removed from double-submit cookie (JS must read it); `FixedWindowStore` window_start never updated after reset; all middleware `skip_path` used raw `starts_with` allowing prefix bypass; doc corrections (`type_uri` not `type_url`)
350
351
  - **v1.2.0**: Bug fixes (shutdown coroutine never awaited, KeyboardInterrupt in shutdown handler, `request.redis` AttributeError); Redis Lua scripting (`eval`, `evalsha`, `script_load`); Rust-native `AsyncClient` backed by `reqwest + Tokio` — GIL never held during HTTP I/O, HTTP/2, gzip, rustls
351
352
  - **v1.1.0**: MiniJinja Jinja2-compatible template engine (`MiniJinjaEngine`, `App.enable_templates()`, `App.render()`, `App.render_string()`); minijinja 2 Rust crate; HTML auto-escaping; globals; 47 new tests; 6 examples
352
353
  - **v1.0.1**: Cross-platform fixes (Windows multi-worker, signal handling, UNC paths; ARM JSON fallback; Linux-only CPU affinity), async compatibility fixes (handler validation, guards, cache decorator, blueprints), guards and database exports in `__all__`
@@ -341,7 +341,7 @@ dependencies = [
341
341
 
342
342
  [[package]]
343
343
  name = "cello-framework"
344
- version = "1.2.1"
344
+ version = "1.2.2"
345
345
  dependencies = [
346
346
  "async-graphql",
347
347
  "async-graphql-value",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "cello-framework"
3
- version = "1.2.1"
3
+ version = "1.2.2"
4
4
  edition = "2021"
5
5
  description = "Ultra-fast Rust-powered Python async web framework"
6
6
  license = "MIT"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cello-framework
3
- Version: 1.2.1
3
+ Version: 1.2.2
4
4
  Classifier: Development Status :: 5 - Production/Stable
5
5
  Classifier: Intended Audience :: Developers
6
6
  Classifier: License :: OSI Approved :: MIT License
@@ -100,6 +100,64 @@ admin_or_editor = Or(RoleGuard(["admin"]), RoleGuard(["editor"]))
100
100
 
101
101
  ---
102
102
 
103
+ ### CSRF Cookie Readable by JavaScript (Security)
104
+
105
+ **Symptom:** AJAX/SPA applications using CSRF protection failed all POST/PUT/DELETE requests with 403.
106
+
107
+ **Root cause:** The CSRF double-submit cookie was set with the `HttpOnly` flag. This prevents
108
+ JavaScript from reading the cookie value, making it impossible for the frontend to copy it into
109
+ the `X-CSRF-Token` header — which is the entire point of the double-submit pattern.
110
+
111
+ **Fix:** Removed `HttpOnly` from the CSRF cookie. The CSRF cookie is intentionally readable by
112
+ JavaScript; the session cookie (where sensitive data lives) remains `HttpOnly`.
113
+
114
+ ```javascript
115
+ // Now works — JS can read the CSRF cookie and send it back
116
+ const csrf = document.cookie.match(/_csrf=([^;]+)/)?.[1];
117
+ fetch("/api/data", {
118
+ method: "POST",
119
+ headers: { "X-CSRF-Token": csrf },
120
+ body: JSON.stringify(data),
121
+ });
122
+ ```
123
+
124
+ ---
125
+
126
+ ### Rate Limiter Fixed-Window Counter Reset (Bug)
127
+
128
+ **Symptom:** After the first rate-limit window expired, the fixed-window counter reset to zero
129
+ on every single subsequent request, making rate limiting completely ineffective.
130
+
131
+ **Root cause:** When the time window rolled over, `entry.count` was reset to 0 but
132
+ `entry.window_start` was never updated to the new window. Every request in the new window
133
+ saw `window_start != current_window` and reset the counter again.
134
+
135
+ **Fix:** Update `entry.window_start = current_window` alongside the count reset.
136
+
137
+ ---
138
+
139
+ ### Auth Skip-Path Prefix Bypass (Security)
140
+
141
+ **Symptom:** Calling `skip_path("/health")` to exclude the health endpoint from authentication
142
+ also excluded `/healthz`, `/healthy`, and any other path starting with `/health`.
143
+
144
+ **Root cause:** Path matching used `request.path.starts_with(skip_path)` without checking that
145
+ the following character is `/` or end-of-string.
146
+
147
+ **Fix:** Path is now only skipped when it equals the pattern exactly or starts with
148
+ `{pattern}/`. Affects `JwtAuth`, `BasicAuth`, `ApiKeyAuth`, `RateLimitMiddleware`,
149
+ `SessionMiddleware`, `CsrfMiddleware`, and `GuardsMiddleware`.
150
+
151
+ ```python
152
+ # Before: /healthz would also be skipped (security bypass)
153
+ jwt.skip_path("/health")
154
+
155
+ # After: only /health and /health/* are skipped
156
+ jwt.skip_path("/health")
157
+ ```
158
+
159
+ ---
160
+
103
161
  ## Upgrade
104
162
 
105
163
  ```bash
@@ -0,0 +1,131 @@
1
+ ---
2
+ title: v1.2.2 Release Notes
3
+ description: Cello Framework v1.2.2 — Security & Bug Fixes
4
+ tags:
5
+ - v1.2.2
6
+ - Release Notes
7
+ - Security
8
+ - Bug Fixes
9
+ ---
10
+
11
+ # Cello v1.2.2 — Security & Bug Fixes
12
+
13
+ **Release Date:** June 14, 2026
14
+ **License:** MIT
15
+ **Python:** 3.12+
16
+
17
+ ---
18
+
19
+ ## Overview
20
+
21
+ Cello v1.2.2 is a patch release that fixes one critical security bug, one security
22
+ vulnerability, and two correctness bugs discovered during a thorough code review of v1.2.1.
23
+ All changes are fully backwards-compatible.
24
+
25
+ **Upgrade immediately** if you use CSRF protection or authentication middleware skip-paths.
26
+
27
+ ---
28
+
29
+ ## Security Fixes
30
+
31
+ ### CSRF Double-Submit Cookie Was `HttpOnly` (Critical)
32
+
33
+ **Symptom:** AJAX applications and SPAs using `CsrfMiddleware` received 403 on every
34
+ POST/PUT/DELETE request, even when the CSRF token was correctly set.
35
+
36
+ **Root cause:** The CSRF double-submit cookie was set with `HttpOnly`, which prevents
37
+ JavaScript from reading cookie values. The double-submit pattern *requires* JavaScript
38
+ to read the cookie and copy its value into the `X-CSRF-Token` request header. With
39
+ `HttpOnly` set, this was impossible — every state-changing request failed CSRF validation.
40
+
41
+ **Fix:** Removed `HttpOnly` from the CSRF token cookie. The CSRF cookie is intentionally
42
+ readable by JavaScript (that is the design). Your session cookie, which holds sensitive
43
+ data, remains `HttpOnly`.
44
+
45
+ ```javascript
46
+ // Now works — JavaScript can read the CSRF cookie
47
+ const csrf = document.cookie.match(/_csrf=([^;]+)/)?.[1];
48
+
49
+ fetch("/api/submit", {
50
+ method: "POST",
51
+ headers: { "X-CSRF-Token": csrf, "Content-Type": "application/json" },
52
+ body: JSON.stringify({ name: "Alice" }),
53
+ });
54
+ ```
55
+
56
+ ---
57
+
58
+ ### Auth Middleware `skip_path` Allowed Prefix Bypass (Security)
59
+
60
+ **Symptom:** Calling `jwt.skip_path("/health")` to exclude the health-check endpoint
61
+ from authentication also excluded `/healthz`, `/healthy`, `/health-admin`, and any
62
+ other path whose string representation started with `/health`.
63
+
64
+ **Root cause:** All middleware used `request.path.starts_with(skip_path)` with no
65
+ boundary check. An attacker aware of a skipped path could craft a URL that bypasses
66
+ authentication.
67
+
68
+ **Affected middleware:** `JwtAuth`, `BasicAuth`, `ApiKeyAuth`, `RateLimitMiddleware`,
69
+ `SessionMiddleware`, `CsrfMiddleware`, `GuardsMiddleware`.
70
+
71
+ **Fix:** Paths are now only skipped when:
72
+
73
+ - The path **exactly equals** the pattern, or
74
+ - The path **starts with** `{pattern}/` (sub-path match).
75
+
76
+ ```python
77
+ # Before: /healthz was also skipped — security bypass
78
+ jwt_auth.skip_path("/health")
79
+
80
+ # After: only /health and /health/* are skipped
81
+ jwt_auth.skip_path("/health")
82
+ ```
83
+
84
+ ---
85
+
86
+ ## Bug Fixes
87
+
88
+ ### Rate Limiter Fixed-Window Counter Broke After First Window
89
+
90
+ **Symptom:** After the first time-window expired, the `FixedWindowStore` rate limiter
91
+ stopped enforcing limits entirely — every request was allowed regardless of how many
92
+ had been made.
93
+
94
+ **Root cause:** When the window rolled over, the per-client entry's `count` was reset
95
+ to 0 but `window_start` was never updated to the new window ID. Every subsequent
96
+ request also saw `window_start != current_window` and reset the counter to 0 again,
97
+ making the limiter permanently ineffective after the first window.
98
+
99
+ **Fix:** `window_start` is now updated to `current_window` alongside the count reset.
100
+
101
+ ---
102
+
103
+ ### Unused `mut` Warnings in Template Engine Tests
104
+
105
+ Five test functions in `minijinja_engine.rs` declared `let mut env = Environment::new()`
106
+ where `mut` was not needed (the variable was never mutated after construction). These
107
+ were cleaned up to eliminate Clippy warnings.
108
+
109
+ ---
110
+
111
+ ## Files Changed
112
+
113
+ | File | Change |
114
+ |------|--------|
115
+ | `src/middleware/csrf.rs` | Remove `HttpOnly` from CSRF cookie |
116
+ | `src/middleware/mod.rs` | Add `path_matches_skip()` shared helper |
117
+ | `src/middleware/auth.rs` | Use `path_matches_skip()` in JWT/Basic/ApiKey |
118
+ | `src/middleware/session.rs` | Use `path_matches_skip()` |
119
+ | `src/middleware/rate_limit.rs` | Use `path_matches_skip()`; fix `window_start` reset |
120
+ | `src/middleware/guards.rs` | Use `path_matches_skip()` |
121
+ | `src/minijinja_engine.rs` | Remove unused `mut` from test bindings |
122
+
123
+ ---
124
+
125
+ ## Upgrade
126
+
127
+ ```bash
128
+ pip install --upgrade cello-framework==1.2.2
129
+ ```
130
+
131
+ Drop-in replacement for v1.2.1. No API changes.
@@ -364,6 +364,7 @@ nav:
364
364
 
365
365
  - Release Notes:
366
366
  - releases/index.md
367
+ - v1.2.2: releases/v1.2.2.md
367
368
  - v1.2.1: releases/v1.2.1.md
368
369
  - v1.2.0: releases/v1.2.0.md
369
370
  - v1.1.0: releases/v1.1.0.md
@@ -4,7 +4,7 @@ build-backend = "maturin"
4
4
 
5
5
  [project]
6
6
  name = "cello-framework"
7
- version = "1.2.1"
7
+ version = "1.2.2"
8
8
  description = "Ultra-fast Rust-powered Python async web framework"
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -296,7 +296,7 @@ __all__ = [
296
296
  "validate_rate_limit_config",
297
297
  "validate_tls_config",
298
298
  ]
299
- __version__ = "1.2.1"
299
+ __version__ = "1.2.2"
300
300
 
301
301
 
302
302
  class Blueprint:
@@ -14,7 +14,7 @@ use std::sync::Arc;
14
14
  use std::time::{Duration, SystemTime, UNIX_EPOCH};
15
15
  use subtle::ConstantTimeEq;
16
16
 
17
- use super::{Middleware, MiddlewareAction, MiddlewareError, MiddlewareResult};
17
+ use super::{path_matches_skip, Middleware, MiddlewareAction, MiddlewareError, MiddlewareResult};
18
18
  use crate::request::Request;
19
19
  use crate::response::Response;
20
20
 
@@ -299,7 +299,7 @@ impl Middleware for JwtAuth {
299
299
  fn before(&self, request: &mut Request) -> MiddlewareResult {
300
300
  // Check if path should be skipped
301
301
  for skip_path in &self.skip_paths {
302
- if request.path.starts_with(skip_path) {
302
+ if path_matches_skip(&request.path, skip_path) {
303
303
  return Ok(MiddlewareAction::Continue);
304
304
  }
305
305
  }
@@ -421,7 +421,7 @@ impl Middleware for BasicAuth {
421
421
  fn before(&self, request: &mut Request) -> MiddlewareResult {
422
422
  // Check if path should be skipped
423
423
  for skip_path in &self.skip_paths {
424
- if request.path.starts_with(skip_path) {
424
+ if path_matches_skip(&request.path, skip_path) {
425
425
  return Ok(MiddlewareAction::Continue);
426
426
  }
427
427
  }
@@ -591,7 +591,7 @@ impl Middleware for ApiKeyAuth {
591
591
  fn before(&self, request: &mut Request) -> MiddlewareResult {
592
592
  // Check if path should be skipped
593
593
  for skip_path in &self.skip_paths {
594
- if request.path.starts_with(skip_path) {
594
+ if path_matches_skip(&request.path, skip_path) {
595
595
  return Ok(MiddlewareAction::Continue);
596
596
  }
597
597
  }
@@ -13,7 +13,7 @@ use sha2::Sha256;
13
13
  use std::time::{Duration, SystemTime, UNIX_EPOCH};
14
14
  use subtle::ConstantTimeEq;
15
15
 
16
- use super::{Middleware, MiddlewareAction, MiddlewareError, MiddlewareResult};
16
+ use super::{path_matches_skip, Middleware, MiddlewareAction, MiddlewareError, MiddlewareResult};
17
17
  use crate::request::Request;
18
18
  use crate::response::Response;
19
19
 
@@ -316,7 +316,7 @@ impl CsrfMiddleware {
316
316
 
317
317
  /// Check if path should skip CSRF.
318
318
  fn should_skip(&self, path: &str) -> bool {
319
- self.config.skip_paths.iter().any(|p| path.starts_with(p))
319
+ self.config.skip_paths.iter().any(|p| path_matches_skip(path, p))
320
320
  }
321
321
 
322
322
  /// Extract token from cookie.
@@ -389,7 +389,8 @@ impl CsrfMiddleware {
389
389
  cookie.push_str("; Secure");
390
390
  }
391
391
 
392
- cookie.push_str("; HttpOnly");
392
+ // CSRF double-submit cookie must NOT be HttpOnly — JavaScript needs to
393
+ // read this value to send it back in the X-CSRF-Token request header.
393
394
  cookie.push_str(&format!("; SameSite={}", self.config.cookie_same_site));
394
395
 
395
396
  cookie
@@ -10,7 +10,7 @@
10
10
  use std::collections::HashSet;
11
11
  use std::sync::Arc;
12
12
 
13
- use super::{Middleware, MiddlewareAction, MiddlewareError, MiddlewareResult};
13
+ use super::{path_matches_skip, Middleware, MiddlewareAction, MiddlewareError, MiddlewareResult};
14
14
  use crate::request::Request;
15
15
  use pyo3::prelude::*;
16
16
 
@@ -547,7 +547,7 @@ impl Middleware for GuardsMiddleware {
547
547
 
548
548
  // Check if path should be skipped
549
549
  for skip_path in &self.skip_paths {
550
- if request.path.starts_with(skip_path) {
550
+ if path_matches_skip(&request.path, skip_path) {
551
551
  return Ok(MiddlewareAction::Continue);
552
552
  }
553
553
  }
@@ -201,6 +201,19 @@ impl MiddlewareError {
201
201
  }
202
202
  }
203
203
 
204
+ /// Check if a request path matches a skip pattern.
205
+ ///
206
+ /// Matches when:
207
+ /// - `path == pattern` (exact match), or
208
+ /// - `path` starts with `pattern/` (sub-path match)
209
+ ///
210
+ /// Without the trailing-slash check, a pattern like `/health` would also
211
+ /// skip `/healthz`, allowing an auth bypass.
212
+ #[inline]
213
+ pub(super) fn path_matches_skip(path: &str, pattern: &str) -> bool {
214
+ path == pattern || path.starts_with(&format!("{}/", pattern))
215
+ }
216
+
204
217
  impl std::fmt::Display for MiddlewareError {
205
218
  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
206
219
  if let Some(code) = &self.code {
@@ -11,7 +11,7 @@ use std::sync::atomic::{AtomicU64, Ordering};
11
11
  use std::sync::Arc;
12
12
  use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
13
13
 
14
- use super::{Middleware, MiddlewareAction, MiddlewareResult};
14
+ use super::{path_matches_skip, Middleware, MiddlewareAction, MiddlewareResult};
15
15
  use crate::request::Request;
16
16
  use crate::response::Response;
17
17
 
@@ -563,7 +563,7 @@ impl RateLimitStore for FixedWindowStore {
563
563
  let current_window = self.current_window();
564
564
  let reset_time = (current_window + 1) * self.window_seconds;
565
565
 
566
- let entry = self
566
+ let mut entry = self
567
567
  .windows
568
568
  .entry(key.to_string())
569
569
  .or_insert_with(|| FixedWindowState {
@@ -571,10 +571,10 @@ impl RateLimitStore for FixedWindowStore {
571
571
  window_start: current_window,
572
572
  });
573
573
 
574
- // Check if window has changed
574
+ // Reset counter when window rolls over
575
575
  if entry.window_start != current_window {
576
576
  entry.count.store(0, Ordering::SeqCst);
577
- // Note: This is a simplified approach; in production, use compare_exchange
577
+ entry.window_start = current_window;
578
578
  }
579
579
 
580
580
  let count = entry.count.fetch_add(1, Ordering::SeqCst);
@@ -848,7 +848,7 @@ impl Middleware for RateLimitMiddleware {
848
848
  fn before(&self, request: &mut Request) -> MiddlewareResult {
849
849
  // Check if path should be skipped
850
850
  for skip_path in &self.skip_paths {
851
- if request.path.starts_with(skip_path) {
851
+ if path_matches_skip(&request.path, skip_path) {
852
852
  return Ok(MiddlewareAction::Continue);
853
853
  }
854
854
  }
@@ -14,7 +14,7 @@ use std::collections::HashMap;
14
14
  use std::sync::Arc;
15
15
  use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
16
16
 
17
- use super::{Middleware, MiddlewareAction, MiddlewareResult};
17
+ use super::{path_matches_skip, Middleware, MiddlewareAction, MiddlewareResult};
18
18
  use crate::request::Request;
19
19
  use crate::response::Response;
20
20
 
@@ -486,7 +486,7 @@ impl Middleware for SessionMiddleware {
486
486
  fn before(&self, request: &mut Request) -> MiddlewareResult {
487
487
  // Check if path should be skipped
488
488
  for skip_path in &self.skip_paths {
489
- if request.path.starts_with(skip_path) {
489
+ if path_matches_skip(&request.path, skip_path) {
490
490
  return Ok(MiddlewareAction::Continue);
491
491
  }
492
492
  }
@@ -513,7 +513,7 @@ impl Middleware for SessionMiddleware {
513
513
  fn after(&self, request: &Request, response: &mut Response) -> MiddlewareResult {
514
514
  // Check if path was skipped
515
515
  for skip_path in &self.skip_paths {
516
- if request.path.starts_with(skip_path) {
516
+ if path_matches_skip(&request.path, skip_path) {
517
517
  return Ok(MiddlewareAction::Continue);
518
518
  }
519
519
  }
@@ -361,7 +361,7 @@ mod tests {
361
361
 
362
362
  #[test]
363
363
  fn test_render_if_block() {
364
- let mut env = Environment::new();
364
+ let env = Environment::new();
365
365
  let tmpl = "{% if active %}yes{% else %}no{% endif %}";
366
366
  let result = env
367
367
  .render_str(tmpl, serde_json::json!({ "active": true }))
@@ -371,7 +371,7 @@ mod tests {
371
371
 
372
372
  #[test]
373
373
  fn test_render_for_loop() {
374
- let mut env = Environment::new();
374
+ let env = Environment::new();
375
375
  let tmpl = "{% for i in items %}{{ i }},{% endfor %}";
376
376
  let result = env
377
377
  .render_str(tmpl, serde_json::json!({ "items": [1, 2, 3] }))
@@ -381,7 +381,7 @@ mod tests {
381
381
 
382
382
  #[test]
383
383
  fn test_render_filters() {
384
- let mut env = Environment::new();
384
+ let env = Environment::new();
385
385
  let result = env
386
386
  .render_str("{{ name | upper }}", serde_json::json!({ "name": "cello" }))
387
387
  .unwrap();
@@ -390,7 +390,7 @@ mod tests {
390
390
 
391
391
  #[test]
392
392
  fn test_render_nested_dict() {
393
- let mut env = Environment::new();
393
+ let env = Environment::new();
394
394
  let ctx = serde_json::json!({
395
395
  "user": { "name": "Alice", "age": 30 }
396
396
  });
@@ -445,7 +445,7 @@ mod tests {
445
445
 
446
446
  #[test]
447
447
  fn test_json_filter() {
448
- let mut env = Environment::new();
448
+ let env = Environment::new();
449
449
  let result = env
450
450
  .render_str(
451
451
  "{{ data | tojson }}",
@@ -1042,7 +1042,7 @@ def test_version():
1042
1042
  """Test that version is 1.2.0."""
1043
1043
  import cello
1044
1044
 
1045
- assert cello.__version__ == "1.2.0"
1045
+ assert cello.__version__ == "1.2.2"
1046
1046
 
1047
1047
 
1048
1048
  def test_all_exports():
@@ -2203,7 +2203,7 @@ def test_version_v090():
2203
2203
  """Test that version is 1.2.0 (updated from 0.9.0)."""
2204
2204
  import cello
2205
2205
 
2206
- assert cello.__version__ == "1.2.0"
2206
+ assert cello.__version__ == "1.2.2"
2207
2207
 
2208
2208
 
2209
2209
  def test_v090_exports_in_all():
@@ -3989,7 +3989,7 @@ def test_version_v0100():
3989
3989
  """Test that version is 1.2.0."""
3990
3990
  import cello
3991
3991
 
3992
- assert cello.__version__ == "1.2.0"
3992
+ assert cello.__version__ == "1.2.2"
3993
3993
 
3994
3994
 
3995
3995
  def test_v0100_exports_in_all():
@@ -480,4 +480,4 @@ class TestComplexContextTypes:
480
480
  def test_version_is_1_2_0():
481
481
  import cello
482
482
 
483
- assert cello.__version__ == "1.2.0"
483
+ assert cello.__version__ == "1.2.2"
File without changes