snulbug 0.2.4__tar.gz → 0.2.5__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 (404) hide show
  1. {snulbug-0.2.4 → snulbug-0.2.5}/CHANGELOG.md +15 -0
  2. {snulbug-0.2.4 → snulbug-0.2.5}/PKG-INFO +1 -1
  3. {snulbug-0.2.4 → snulbug-0.2.5}/docs/devcontainers.md +2 -2
  4. {snulbug-0.2.4 → snulbug-0.2.5}/docs/mcp-proxy.md +64 -0
  5. {snulbug-0.2.4 → snulbug-0.2.5}/docs/ngrok-end-to-end.md +27 -0
  6. {snulbug-0.2.4 → snulbug-0.2.5}/docs/release.md +2 -2
  7. snulbug-0.2.5/docs/upgrading-to-0.2.5.md +97 -0
  8. {snulbug-0.2.4 → snulbug-0.2.5}/examples/codespace_local_gateway/.devcontainer/devcontainer.json +1 -1
  9. {snulbug-0.2.4 → snulbug-0.2.5}/features/snulbug/README.md +1 -1
  10. {snulbug-0.2.4 → snulbug-0.2.5}/features/snulbug/devcontainer-feature.json +1 -1
  11. {snulbug-0.2.4 → snulbug-0.2.5}/pyproject.toml +1 -1
  12. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/__init__.py +1 -1
  13. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/cli/rich_share.py +5 -1
  14. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/config.py +6 -0
  15. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/http_mounts.py +19 -2
  16. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/http_routes.py +25 -5
  17. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/ngrok_drift.py +5 -0
  18. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/proxy.py +8 -2
  19. snulbug-0.2.5/snulbug/public_origin.py +136 -0
  20. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/release_qa.py +3 -0
  21. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/share.py +2 -0
  22. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/share_console.py +2 -0
  23. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/tunnel.py +53 -1
  24. {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_http_mounts.py +36 -3
  25. {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_http_routes.py +119 -3
  26. {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_ngrok_drift.py +12 -4
  27. snulbug-0.2.5/tests/test_public_origin.py +209 -0
  28. {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_release_qa.py +2 -0
  29. {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_tunnel_init.py +68 -0
  30. {snulbug-0.2.4 → snulbug-0.2.5}/.gitignore +0 -0
  31. {snulbug-0.2.4 → snulbug-0.2.5}/CODE_OF_CONDUCT.md +0 -0
  32. {snulbug-0.2.4 → snulbug-0.2.5}/CONTRIBUTING.md +0 -0
  33. {snulbug-0.2.4 → snulbug-0.2.5}/LICENSE +0 -0
  34. {snulbug-0.2.4 → snulbug-0.2.5}/README.md +0 -0
  35. {snulbug-0.2.4 → snulbug-0.2.5}/SECURITY.md +0 -0
  36. {snulbug-0.2.4 → snulbug-0.2.5}/docs/README.md +0 -0
  37. {snulbug-0.2.4 → snulbug-0.2.5}/docs/actions.md +0 -0
  38. {snulbug-0.2.4 → snulbug-0.2.5}/docs/always-on-runtime.md +0 -0
  39. {snulbug-0.2.4 → snulbug-0.2.5}/docs/auth-providers.md +0 -0
  40. {snulbug-0.2.4 → snulbug-0.2.5}/docs/aws-mcp.md +0 -0
  41. {snulbug-0.2.4 → snulbug-0.2.5}/docs/bundles.md +0 -0
  42. {snulbug-0.2.4 → snulbug-0.2.5}/docs/ci-policy-gates.md +0 -0
  43. {snulbug-0.2.4 → snulbug-0.2.5}/docs/comparison.md +0 -0
  44. {snulbug-0.2.4 → snulbug-0.2.5}/docs/credential-brokers.md +0 -0
  45. {snulbug-0.2.4 → snulbug-0.2.5}/docs/discovery-providers.md +0 -0
  46. {snulbug-0.2.4 → snulbug-0.2.5}/docs/event-sink-providers.md +0 -0
  47. {snulbug-0.2.4 → snulbug-0.2.5}/docs/evidence-exporters.md +0 -0
  48. {snulbug-0.2.4 → snulbug-0.2.5}/docs/getting-started.md +0 -0
  49. {snulbug-0.2.4 → snulbug-0.2.5}/docs/lua-policy-dsl.md +0 -0
  50. {snulbug-0.2.4 → snulbug-0.2.5}/docs/lua-request-api.md +0 -0
  51. {snulbug-0.2.4 → snulbug-0.2.5}/docs/mcp-auth-recipes.md +0 -0
  52. {snulbug-0.2.4 → snulbug-0.2.5}/docs/mcp-client-recipes.md +0 -0
  53. {snulbug-0.2.4 → snulbug-0.2.5}/docs/mcp-evidence.md +0 -0
  54. {snulbug-0.2.4 → snulbug-0.2.5}/docs/mcp-fabric.md +0 -0
  55. {snulbug-0.2.4 → snulbug-0.2.5}/docs/mcp-gateway.md +0 -0
  56. {snulbug-0.2.4 → snulbug-0.2.5}/docs/mcp-guide.md +0 -0
  57. {snulbug-0.2.4 → snulbug-0.2.5}/docs/mcp-impact.md +0 -0
  58. {snulbug-0.2.4 → snulbug-0.2.5}/docs/mcp-learn.md +0 -0
  59. {snulbug-0.2.4 → snulbug-0.2.5}/docs/mcp-policy.md +0 -0
  60. {snulbug-0.2.4 → snulbug-0.2.5}/docs/mcp-presets.md +0 -0
  61. {snulbug-0.2.4 → snulbug-0.2.5}/docs/mcp-protocol.md +0 -0
  62. {snulbug-0.2.4 → snulbug-0.2.5}/docs/mcp-recorder.md +0 -0
  63. {snulbug-0.2.4 → snulbug-0.2.5}/docs/mcp-schemas.md +0 -0
  64. {snulbug-0.2.4 → snulbug-0.2.5}/docs/mcp-share.md +0 -0
  65. {snulbug-0.2.4 → snulbug-0.2.5}/docs/policy-deny-backoff.md +0 -0
  66. {snulbug-0.2.4 → snulbug-0.2.5}/docs/quickstart.md +0 -0
  67. {snulbug-0.2.4 → snulbug-0.2.5}/docs/roadmap.md +0 -0
  68. {snulbug-0.2.4 → snulbug-0.2.5}/docs/security-model.md +0 -0
  69. {snulbug-0.2.4 → snulbug-0.2.5}/docs/share-doctor-checks.md +0 -0
  70. {snulbug-0.2.4 → snulbug-0.2.5}/docs/state.md +0 -0
  71. {snulbug-0.2.4 → snulbug-0.2.5}/docs/tool-catalog-projection.md +0 -0
  72. {snulbug-0.2.4 → snulbug-0.2.5}/docs/tool-risk-analyzers.md +0 -0
  73. {snulbug-0.2.4 → snulbug-0.2.5}/docs/tunnel-providers.md +0 -0
  74. {snulbug-0.2.4 → snulbug-0.2.5}/docs/upgrading-to-0.2.4.md +0 -0
  75. {snulbug-0.2.4 → snulbug-0.2.5}/docs/upstream-transports.md +0 -0
  76. {snulbug-0.2.4 → snulbug-0.2.5}/examples/auth_claim_patterns/.gitignore +0 -0
  77. {snulbug-0.2.4 → snulbug-0.2.5}/examples/auth_claim_patterns/README.md +0 -0
  78. {snulbug-0.2.4 → snulbug-0.2.5}/examples/auth_claim_patterns/ci-workload-identity.toml +0 -0
  79. {snulbug-0.2.4 → snulbug-0.2.5}/examples/auth_claim_patterns/group-gated-tools.toml +0 -0
  80. {snulbug-0.2.4 → snulbug-0.2.5}/examples/auth_claim_patterns/policy.lua +0 -0
  81. {snulbug-0.2.4 → snulbug-0.2.5}/examples/auth_claim_patterns/tenant-isolation.toml +0 -0
  82. {snulbug-0.2.4 → snulbug-0.2.5}/examples/basic/README.md +0 -0
  83. {snulbug-0.2.4 → snulbug-0.2.5}/examples/basic/app.py +0 -0
  84. {snulbug-0.2.4 → snulbug-0.2.5}/examples/bundles/idempotency.snulbug/README.md +0 -0
  85. {snulbug-0.2.4 → snulbug-0.2.5}/examples/bundles/idempotency.snulbug/fixtures/duplicate-delivery.json +0 -0
  86. {snulbug-0.2.4 → snulbug-0.2.5}/examples/bundles/idempotency.snulbug/fixtures/first-delivery.json +0 -0
  87. {snulbug-0.2.4 → snulbug-0.2.5}/examples/bundles/idempotency.snulbug/manifest.json +0 -0
  88. {snulbug-0.2.4 → snulbug-0.2.5}/examples/bundles/idempotency.snulbug/policy.lua +0 -0
  89. {snulbug-0.2.4 → snulbug-0.2.5}/examples/bundles/idempotency.snulbug/snapshots/duplicate.json +0 -0
  90. {snulbug-0.2.4 → snulbug-0.2.5}/examples/bundles/idempotency.snulbug/snapshots/empty.json +0 -0
  91. {snulbug-0.2.4 → snulbug-0.2.5}/examples/bundles/mcp-gateway.snulbug/README.md +0 -0
  92. {snulbug-0.2.4 → snulbug-0.2.5}/examples/bundles/mcp-gateway.snulbug/fixtures/allowed-tool.json +0 -0
  93. {snulbug-0.2.4 → snulbug-0.2.5}/examples/bundles/mcp-gateway.snulbug/fixtures/blocked-tool.json +0 -0
  94. {snulbug-0.2.4 → snulbug-0.2.5}/examples/bundles/mcp-gateway.snulbug/fixtures/unauthenticated.json +0 -0
  95. {snulbug-0.2.4 → snulbug-0.2.5}/examples/bundles/mcp-gateway.snulbug/manifest.json +0 -0
  96. {snulbug-0.2.4 → snulbug-0.2.5}/examples/bundles/mcp-gateway.snulbug/policy.lua +0 -0
  97. {snulbug-0.2.4 → snulbug-0.2.5}/examples/bundles/mcp-gateway.snulbug/snapshots/empty.json +0 -0
  98. {snulbug-0.2.4 → snulbug-0.2.5}/examples/codespace_local_gateway/README.md +0 -0
  99. {snulbug-0.2.4 → snulbug-0.2.5}/examples/codespace_local_gateway/mcp-client.json +0 -0
  100. {snulbug-0.2.4 → snulbug-0.2.5}/examples/codespace_local_gateway/mock_mcp_server.py +0 -0
  101. {snulbug-0.2.4 → snulbug-0.2.5}/examples/codespace_local_gateway/policy.lua +0 -0
  102. {snulbug-0.2.4 → snulbug-0.2.5}/examples/codespace_local_gateway/snulbug.env-gateway.toml +0 -0
  103. {snulbug-0.2.4 → snulbug-0.2.5}/examples/codespace_local_gateway/snulbug.local-gateway.toml +0 -0
  104. {snulbug-0.2.4 → snulbug-0.2.5}/examples/customer_policy/README.md +0 -0
  105. {snulbug-0.2.4 → snulbug-0.2.5}/examples/customer_policy/app.py +0 -0
  106. {snulbug-0.2.4 → snulbug-0.2.5}/examples/customer_policy/policies/acme.lua +0 -0
  107. {snulbug-0.2.4 → snulbug-0.2.5}/examples/customer_policy/policies/default.lua +0 -0
  108. {snulbug-0.2.4 → snulbug-0.2.5}/examples/customer_policy/policies/globex.lua +0 -0
  109. {snulbug-0.2.4 → snulbug-0.2.5}/examples/customer_policy/requests/acme-invalid.json +0 -0
  110. {snulbug-0.2.4 → snulbug-0.2.5}/examples/customer_policy/requests/acme-valid.json +0 -0
  111. {snulbug-0.2.4 → snulbug-0.2.5}/examples/customer_policy/requests/globex-sandbox.json +0 -0
  112. {snulbug-0.2.4 → snulbug-0.2.5}/examples/customer_policy/requests/unknown-tenant.json +0 -0
  113. {snulbug-0.2.4 → snulbug-0.2.5}/examples/github-actions/snulbug-policy-gates.yml +0 -0
  114. {snulbug-0.2.4 → snulbug-0.2.5}/examples/keycloak_oauth_demo/.gitignore +0 -0
  115. {snulbug-0.2.4 → snulbug-0.2.5}/examples/keycloak_oauth_demo/Dockerfile.gateway +0 -0
  116. {snulbug-0.2.4 → snulbug-0.2.5}/examples/keycloak_oauth_demo/README.md +0 -0
  117. {snulbug-0.2.4 → snulbug-0.2.5}/examples/keycloak_oauth_demo/auth/keycloak/README.md +0 -0
  118. {snulbug-0.2.4 → snulbug-0.2.5}/examples/keycloak_oauth_demo/auth/keycloak/auth-init.json +0 -0
  119. {snulbug-0.2.4 → snulbug-0.2.5}/examples/keycloak_oauth_demo/auth/keycloak/client-token-request.json +0 -0
  120. {snulbug-0.2.4 → snulbug-0.2.5}/examples/keycloak_oauth_demo/auth/keycloak/commands.json +0 -0
  121. {snulbug-0.2.4 → snulbug-0.2.5}/examples/keycloak_oauth_demo/auth/keycloak/snulbug.auth.toml +0 -0
  122. {snulbug-0.2.4 → snulbug-0.2.5}/examples/keycloak_oauth_demo/docker-compose.yml +0 -0
  123. {snulbug-0.2.4 → snulbug-0.2.5}/examples/keycloak_oauth_demo/get-token.py +0 -0
  124. {snulbug-0.2.4 → snulbug-0.2.5}/examples/keycloak_oauth_demo/keycloak/snulbug-demo-realm.json +0 -0
  125. {snulbug-0.2.4 → snulbug-0.2.5}/examples/keycloak_oauth_demo/mock_mcp_server.py +0 -0
  126. {snulbug-0.2.4 → snulbug-0.2.5}/examples/keycloak_oauth_demo/policy.snulbug/policy.lua +0 -0
  127. {snulbug-0.2.4 → snulbug-0.2.5}/examples/keycloak_oauth_demo/requests/blocked-write-file.json +0 -0
  128. {snulbug-0.2.4 → snulbug-0.2.5}/examples/keycloak_oauth_demo/requests/read-file.json +0 -0
  129. {snulbug-0.2.4 → snulbug-0.2.5}/examples/keycloak_oauth_demo/requests/tools-list.json +0 -0
  130. {snulbug-0.2.4 → snulbug-0.2.5}/examples/keycloak_oauth_demo/snulbug.toml +0 -0
  131. {snulbug-0.2.4 → snulbug-0.2.5}/examples/mcp_container_facade/Dockerfile.gateway +0 -0
  132. {snulbug-0.2.4 → snulbug-0.2.5}/examples/mcp_container_facade/Dockerfile.remote-peer +0 -0
  133. {snulbug-0.2.4 → snulbug-0.2.5}/examples/mcp_container_facade/README.md +0 -0
  134. {snulbug-0.2.4 → snulbug-0.2.5}/examples/mcp_container_facade/docker-compose.yml +0 -0
  135. {snulbug-0.2.4 → snulbug-0.2.5}/examples/mcp_container_facade/hypertele-client.json +0 -0
  136. {snulbug-0.2.4 → snulbug-0.2.5}/examples/mcp_container_facade/hypertele-server.json +0 -0
  137. {snulbug-0.2.4 → snulbug-0.2.5}/examples/mcp_container_facade/mcp-client.json +0 -0
  138. {snulbug-0.2.4 → snulbug-0.2.5}/examples/mcp_container_facade/mock_mcp_server.js +0 -0
  139. {snulbug-0.2.4 → snulbug-0.2.5}/examples/mcp_container_facade/mock_mcp_server.py +0 -0
  140. {snulbug-0.2.4 → snulbug-0.2.5}/examples/mcp_container_facade/policy.lua +0 -0
  141. {snulbug-0.2.4 → snulbug-0.2.5}/examples/mcp_container_facade/snulbug.facade.toml +0 -0
  142. {snulbug-0.2.4 → snulbug-0.2.5}/examples/mcp_container_facade/snulbug.local.toml +0 -0
  143. {snulbug-0.2.4 → snulbug-0.2.5}/examples/mcp_gateway/README.md +0 -0
  144. {snulbug-0.2.4 → snulbug-0.2.5}/examples/mcp_gateway/app.py +0 -0
  145. {snulbug-0.2.4 → snulbug-0.2.5}/examples/mcp_proxy_demo/.gitignore +0 -0
  146. {snulbug-0.2.4 → snulbug-0.2.5}/examples/mcp_proxy_demo/README.md +0 -0
  147. {snulbug-0.2.4 → snulbug-0.2.5}/examples/mcp_proxy_demo/requests/blocked-tool.json +0 -0
  148. {snulbug-0.2.4 → snulbug-0.2.5}/examples/mcp_proxy_demo/requests/safe-tool.json +0 -0
  149. {snulbug-0.2.4 → snulbug-0.2.5}/examples/mcp_proxy_demo/requests/tools-list.json +0 -0
  150. {snulbug-0.2.4 → snulbug-0.2.5}/examples/mcp_proxy_demo/run_demo.py +0 -0
  151. {snulbug-0.2.4 → snulbug-0.2.5}/examples/mcp_proxy_demo/upstream.py +0 -0
  152. {snulbug-0.2.4 → snulbug-0.2.5}/examples/policy_promotion/README.md +0 -0
  153. {snulbug-0.2.4 → snulbug-0.2.5}/examples/policy_promotion/active.lua +0 -0
  154. {snulbug-0.2.4 → snulbug-0.2.5}/examples/policy_promotion/draft.lua +0 -0
  155. {snulbug-0.2.4 → snulbug-0.2.5}/examples/policy_promotion/fixtures/acme-invalid.json +0 -0
  156. {snulbug-0.2.4 → snulbug-0.2.5}/examples/policy_promotion/fixtures/acme-valid.json +0 -0
  157. {snulbug-0.2.4 → snulbug-0.2.5}/examples/provider_policy_templates/README.md +0 -0
  158. {snulbug-0.2.4 → snulbug-0.2.5}/examples/provider_policy_templates/cloudflare-access-group-gate.lua +0 -0
  159. {snulbug-0.2.4 → snulbug-0.2.5}/examples/provider_policy_templates/entra-app-role-gate.lua +0 -0
  160. {snulbug-0.2.4 → snulbug-0.2.5}/examples/provider_policy_templates/github-actions-workload-gate.lua +0 -0
  161. {snulbug-0.2.4 → snulbug-0.2.5}/examples/provider_policy_templates/keycloak-role-gate.lua +0 -0
  162. {snulbug-0.2.4 → snulbug-0.2.5}/examples/state_replay/README.md +0 -0
  163. {snulbug-0.2.4 → snulbug-0.2.5}/examples/state_replay/duplicate-state.json +0 -0
  164. {snulbug-0.2.4 → snulbug-0.2.5}/examples/state_replay/idempotency.lua +0 -0
  165. {snulbug-0.2.4 → snulbug-0.2.5}/examples/state_replay/request.json +0 -0
  166. {snulbug-0.2.4 → snulbug-0.2.5}/examples/webhook_normalization/README.md +0 -0
  167. {snulbug-0.2.4 → snulbug-0.2.5}/examples/webhook_normalization/app.py +0 -0
  168. {snulbug-0.2.4 → snulbug-0.2.5}/examples/webhook_normalization/policies/github.lua +0 -0
  169. {snulbug-0.2.4 → snulbug-0.2.5}/examples/webhook_normalization/policies/stripe.lua +0 -0
  170. {snulbug-0.2.4 → snulbug-0.2.5}/examples/webhook_normalization/policies/unknown.lua +0 -0
  171. {snulbug-0.2.4 → snulbug-0.2.5}/examples/webhook_normalization/requests/github-push.json +0 -0
  172. {snulbug-0.2.4 → snulbug-0.2.5}/examples/webhook_normalization/requests/stripe-invoice.json +0 -0
  173. {snulbug-0.2.4 → snulbug-0.2.5}/examples/webhook_normalization/requests/stripe-malformed.json +0 -0
  174. {snulbug-0.2.4 → snulbug-0.2.5}/examples/webhook_normalization/requests/unknown-vendor.json +0 -0
  175. {snulbug-0.2.4 → snulbug-0.2.5}/features/snulbug/install.sh +0 -0
  176. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/__main__.py +0 -0
  177. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/_operations_smoke.py +0 -0
  178. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/assets/prism.css +0 -0
  179. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/assets/prism.js +0 -0
  180. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/auth_providers.py +0 -0
  181. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/auth_recipes.py +0 -0
  182. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/auth-required.snulbug/README.md +0 -0
  183. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/auth-required.snulbug/fixtures/authenticated.json +0 -0
  184. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/auth-required.snulbug/fixtures/missing-token.json +0 -0
  185. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/auth-required.snulbug/manifest.json +0 -0
  186. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/auth-required.snulbug/policy.lua +0 -0
  187. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/aws-mcp.snulbug/README.md +0 -0
  188. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/aws-mcp.snulbug/contexts/inventory-lease.json +0 -0
  189. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/aws-mcp.snulbug/contexts/sandbox-change-lease.json +0 -0
  190. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/aws-mcp.snulbug/fixtures/data-read.json +0 -0
  191. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/aws-mcp.snulbug/fixtures/iam-delete-user.json +0 -0
  192. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/aws-mcp.snulbug/fixtures/inventory-read.json +0 -0
  193. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/aws-mcp.snulbug/fixtures/knowledge-search.json +0 -0
  194. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/aws-mcp.snulbug/fixtures/missing-token.json +0 -0
  195. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/aws-mcp.snulbug/fixtures/sandbox-mutation.json +0 -0
  196. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/aws-mcp.snulbug/manifest.json +0 -0
  197. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/aws-mcp.snulbug/policy.lua +0 -0
  198. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/aws-mcp.snulbug/snapshots/empty.json +0 -0
  199. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/local-dev-safe.snulbug/README.md +0 -0
  200. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/local-dev-safe.snulbug/fixtures/missing-token.json +0 -0
  201. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/local-dev-safe.snulbug/fixtures/safe-tool.json +0 -0
  202. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/local-dev-safe.snulbug/fixtures/unsafe-tool.json +0 -0
  203. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/local-dev-safe.snulbug/manifest.json +0 -0
  204. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/local-dev-safe.snulbug/policy.lua +0 -0
  205. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/local-dev-safe.snulbug/snapshots/empty.json +0 -0
  206. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/no-shell-tools.snulbug/README.md +0 -0
  207. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/no-shell-tools.snulbug/fixtures/missing-token.json +0 -0
  208. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/no-shell-tools.snulbug/fixtures/safe-tool.json +0 -0
  209. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/no-shell-tools.snulbug/fixtures/shell-tool.json +0 -0
  210. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/no-shell-tools.snulbug/manifest.json +0 -0
  211. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/no-shell-tools.snulbug/policy.lua +0 -0
  212. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/project-path-allowlist.snulbug/README.md +0 -0
  213. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/project-path-allowlist.snulbug/fixtures/absolute-path.json +0 -0
  214. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/project-path-allowlist.snulbug/fixtures/allowed-path.json +0 -0
  215. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/project-path-allowlist.snulbug/fixtures/traversal-path.json +0 -0
  216. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/project-path-allowlist.snulbug/fixtures/unsafe-tool.json +0 -0
  217. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/project-path-allowlist.snulbug/manifest.json +0 -0
  218. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/project-path-allowlist.snulbug/policy.lua +0 -0
  219. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/read-only-local-dev.snulbug/README.md +0 -0
  220. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/read-only-local-dev.snulbug/fixtures/missing-token.json +0 -0
  221. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/read-only-local-dev.snulbug/fixtures/safe-tool.json +0 -0
  222. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/read-only-local-dev.snulbug/fixtures/tools-list.json +0 -0
  223. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/read-only-local-dev.snulbug/fixtures/write-method.json +0 -0
  224. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/read-only-local-dev.snulbug/manifest.json +0 -0
  225. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/read-only-local-dev.snulbug/policy.lua +0 -0
  226. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/read-only-local-dev.snulbug/snapshots/empty.json +0 -0
  227. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/tool-allowlist.snulbug/README.md +0 -0
  228. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/tool-allowlist.snulbug/fixtures/safe-tool.json +0 -0
  229. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/tool-allowlist.snulbug/fixtures/tools-list.json +0 -0
  230. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/tool-allowlist.snulbug/fixtures/unsafe-tool.json +0 -0
  231. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/tool-allowlist.snulbug/manifest.json +0 -0
  232. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/tool-allowlist.snulbug/policy.lua +0 -0
  233. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/tunnel-safe.snulbug/README.md +0 -0
  234. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/tunnel-safe.snulbug/fixtures/batch-request.json +0 -0
  235. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/tunnel-safe.snulbug/fixtures/missing-token.json +0 -0
  236. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/tunnel-safe.snulbug/fixtures/safe-tool.json +0 -0
  237. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/tunnel-safe.snulbug/fixtures/unsafe-tool.json +0 -0
  238. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/tunnel-safe.snulbug/manifest.json +0 -0
  239. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/tunnel-safe.snulbug/policy.lua +0 -0
  240. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/tunnel-safe.snulbug/snapshots/empty.json +0 -0
  241. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/workspace-firewall.snulbug/README.md +0 -0
  242. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/workspace-firewall.snulbug/fixtures/allowed-read.json +0 -0
  243. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/workspace-firewall.snulbug/fixtures/missing-token.json +0 -0
  244. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/workspace-firewall.snulbug/fixtures/secret-path.json +0 -0
  245. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/workspace-firewall.snulbug/fixtures/traversal-path.json +0 -0
  246. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/workspace-firewall.snulbug/fixtures/unsafe-tool.json +0 -0
  247. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/workspace-firewall.snulbug/manifest.json +0 -0
  248. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/workspace-firewall.snulbug/policy.lua +0 -0
  249. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/bundle.py +0 -0
  250. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/cli/__init__.py +0 -0
  251. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/cli/common.py +0 -0
  252. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/cli/evidence.py +0 -0
  253. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/cli/fabric.py +0 -0
  254. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/cli/policy.py +0 -0
  255. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/cli/rich_reports.py +0 -0
  256. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/cli/schemas.py +0 -0
  257. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/cli/share.py +0 -0
  258. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/cli_helpers.py +0 -0
  259. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/cloudflare_access.py +0 -0
  260. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/codespaces.py +0 -0
  261. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/confirm.py +0 -0
  262. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/control_events.py +0 -0
  263. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/controller.py +0 -0
  264. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/credential_health.py +0 -0
  265. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/credentials.py +0 -0
  266. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/discovery.py +0 -0
  267. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/events.py +0 -0
  268. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/evidence_exporters.py +0 -0
  269. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/fabric.py +0 -0
  270. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/fabric_control.py +0 -0
  271. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/fabric_members.py +0 -0
  272. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/fabric_runtime.py +0 -0
  273. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/gateway_templates.py +0 -0
  274. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/guide.py +0 -0
  275. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/impact.py +0 -0
  276. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/inspection.py +0 -0
  277. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/json_schema.py +0 -0
  278. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/lab.py +0 -0
  279. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/learn.py +0 -0
  280. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/leases.py +0 -0
  281. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/manifests.py +0 -0
  282. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/mcp_auth.py +0 -0
  283. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/mcp_cache.py +0 -0
  284. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/mcp_capabilities.py +0 -0
  285. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/mcp_client_requests.py +0 -0
  286. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/mcp_completion.py +0 -0
  287. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/mcp_mrtr.py +0 -0
  288. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/mcp_parameter_headers.py +0 -0
  289. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/mcp_policy_bundles.py +0 -0
  290. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/mcp_progress.py +0 -0
  291. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/mcp_protocol.py +0 -0
  292. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/mcp_resources.py +0 -0
  293. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/mcp_schema_policy.py +0 -0
  294. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/mcp_schemas.py +0 -0
  295. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/mcp_spec_conformance.py +0 -0
  296. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/mcp_stdio.py +0 -0
  297. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/mcp_stream.py +0 -0
  298. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/mcp_subscriptions.py +0 -0
  299. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/mcp_tasks.py +0 -0
  300. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/mcp_tools.py +0 -0
  301. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/middleware.py +0 -0
  302. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/policy_activation.py +0 -0
  303. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/policy_backoff.py +0 -0
  304. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/policy_review.py +0 -0
  305. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/presets.py +0 -0
  306. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/promotion.py +0 -0
  307. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/py.typed +0 -0
  308. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/quickstart.py +0 -0
  309. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/recorder.py +0 -0
  310. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/redaction.py +0 -0
  311. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/request_explanations.py +0 -0
  312. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/response_policy.py +0 -0
  313. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/runtime.py +0 -0
  314. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/sarif.py +0 -0
  315. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/scaffolds.py +0 -0
  316. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/schema_policy.py +0 -0
  317. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/share_doctor.py +0 -0
  318. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/share_runtime.py +0 -0
  319. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/share_session.py +0 -0
  320. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/simulator.py +0 -0
  321. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/state.py +0 -0
  322. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/tool_catalog.py +0 -0
  323. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/tool_risk.py +0 -0
  324. {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/upstream_transports.py +0 -0
  325. {snulbug-0.2.4 → snulbug-0.2.5}/tests/fixtures/json_schema_2020_12/LICENSE +0 -0
  326. {snulbug-0.2.4 → snulbug-0.2.5}/tests/fixtures/json_schema_2020_12/README.md +0 -0
  327. {snulbug-0.2.4 → snulbug-0.2.5}/tests/fixtures/json_schema_2020_12/dependentSchemas.json +0 -0
  328. {snulbug-0.2.4 → snulbug-0.2.5}/tests/fixtures/json_schema_2020_12/dynamicRef.json +0 -0
  329. {snulbug-0.2.4 → snulbug-0.2.5}/tests/fixtures/json_schema_2020_12/if-then-else.json +0 -0
  330. {snulbug-0.2.4 → snulbug-0.2.5}/tests/fixtures/json_schema_2020_12/prefixItems.json +0 -0
  331. {snulbug-0.2.4 → snulbug-0.2.5}/tests/fixtures/json_schema_2020_12/unevaluatedProperties.json +0 -0
  332. {snulbug-0.2.4 → snulbug-0.2.5}/tests/fixtures/pocket_id/README.md +0 -0
  333. {snulbug-0.2.4 → snulbug-0.2.5}/tests/fixtures/pocket_id/oauth.json +0 -0
  334. {snulbug-0.2.4 → snulbug-0.2.5}/tests/fixtures/stdio_subscriptions.py +0 -0
  335. {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_auth_claim_patterns.py +0 -0
  336. {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_auth_recipes.py +0 -0
  337. {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_automation_invites.py +0 -0
  338. {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_cli_helpers.py +0 -0
  339. {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_cloudflare_access.py +0 -0
  340. {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_codespaces.py +0 -0
  341. {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_config.py +0 -0
  342. {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_credential_health.py +0 -0
  343. {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_credentials.py +0 -0
  344. {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_customer_policy_demo.py +0 -0
  345. {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_devcontainer_feature.py +0 -0
  346. {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_discovery.py +0 -0
  347. {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_events.py +0 -0
  348. {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_evidence_exporters.py +0 -0
  349. {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_fabric.py +0 -0
  350. {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_fabric_controller.py +0 -0
  351. {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_gateway_snapshot.py +0 -0
  352. {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_json_schema.py +0 -0
  353. {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_keycloak_oauth_demo.py +0 -0
  354. {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_leases.py +0 -0
  355. {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_manifests.py +0 -0
  356. {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_mcp_cache.py +0 -0
  357. {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_mcp_gateway_demo.py +0 -0
  358. {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_mcp_guide.py +0 -0
  359. {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_mcp_helpers.py +0 -0
  360. {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_mcp_impact.py +0 -0
  361. {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_mcp_inspection.py +0 -0
  362. {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_mcp_lab.py +0 -0
  363. {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_mcp_learn.py +0 -0
  364. {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_mcp_mrtr.py +0 -0
  365. {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_mcp_parameter_headers.py +0 -0
  366. {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_mcp_policy_bundles.py +0 -0
  367. {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_mcp_presets.py +0 -0
  368. {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_mcp_progress.py +0 -0
  369. {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_mcp_protocol.py +0 -0
  370. {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_mcp_proxy_demo.py +0 -0
  371. {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_mcp_quickstart.py +0 -0
  372. {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_mcp_recorder.py +0 -0
  373. {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_mcp_resources.py +0 -0
  374. {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_mcp_schema_policy.py +0 -0
  375. {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_mcp_schemas.py +0 -0
  376. {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_mcp_share.py +0 -0
  377. {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_mcp_spec_conformance.py +0 -0
  378. {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_mcp_stdio.py +0 -0
  379. {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_mcp_stream.py +0 -0
  380. {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_mcp_subscriptions.py +0 -0
  381. {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_mcp_tools.py +0 -0
  382. {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_middleware.py +0 -0
  383. {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_operations_smoke.py +0 -0
  384. {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_pocket_id_auth.py +0 -0
  385. {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_policy_bundle.py +0 -0
  386. {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_policy_promotion.py +0 -0
  387. {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_policy_state.py +0 -0
  388. {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_provider_policy_templates.py +0 -0
  389. {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_proxy.py +0 -0
  390. {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_redaction_audit.py +0 -0
  391. {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_request_explanations.py +0 -0
  392. {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_response_policy.py +0 -0
  393. {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_sandbox_abuse.py +0 -0
  394. {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_scaffolds.py +0 -0
  395. {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_share_console.py +0 -0
  396. {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_share_runtime.py +0 -0
  397. {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_state_snapshots.py +0 -0
  398. {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_tool_catalog.py +0 -0
  399. {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_tool_risk.py +0 -0
  400. {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_tunnel_audit.py +0 -0
  401. {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_tunnel_doctor.py +0 -0
  402. {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_upstream_transports.py +0 -0
  403. {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_webhook_normalization_demo.py +0 -0
  404. {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_webhooks.py +0 -0
@@ -6,6 +6,21 @@ The format is based on Keep a Changelog, and this project follows semantic versi
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.2.5] - 2026-10-23
10
+
11
+ Public-origin preservation for shared MCP and HTTP gateways.
12
+ Read the [0.2.5 upgrade notes](docs/upgrading-to-0.2.5.md) before deploying an
13
+ existing ngrok share or a share with required signed HTTP mount contracts.
14
+
15
+ - Forward canonical public host, scheme, and port from the configured share URL across internal hops. HTTP mounts can opt into `preserve_host = true`; backend Host remains the default and upstream TLS verification is unchanged.
16
+ - Generate ngrok policies that capture and validate the original public authority/scheme before forwarding and replace caller-supplied origin/audit headers. Reject invalid capture pairs at the gateway and unsafe public URLs at config load; keep observed ingress headers for audit provenance, not forwarding authority.
17
+ - Include effective origin and Host mode in mount health checks, doctor, status, reports, the web console, readiness reviews, and signed mount contracts. Existing ngrok policies need manual review and reattachment; no cloud changes or share regeneration are applied automatically.
18
+ - Add regression coverage for spoofed/duplicate headers, redirects, prefixes, ports, IPv6, MCP forwarding, health/cache invalidation, and contract drift, including installed-wheel packaging checks.
19
+
20
+ No change to the default MCP protocol or preview limitations. Application auth,
21
+ proxy trust, base paths, and CSRF defenses remain the mounted app's responsibility;
22
+ this release does not rewrite HTML, redirect locations, or cookie paths.
23
+
9
24
  ## [0.2.4] - 2026-10-19
10
25
 
11
26
  Operational hardening for always-on shares and shared HTTP/MCP gateways.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: snulbug
3
- Version: 0.2.4
3
+ Version: 0.2.5
4
4
  Summary: A local-dev MCP policy proxy with Lua policies, replay logs, and learned least-privilege bundles
5
5
  Project-URL: Homepage, https://github.com/nahuaque/snulbug
6
6
  Project-URL: Repository, https://github.com/nahuaque/snulbug
@@ -10,8 +10,8 @@ servers with a shared control plane.
10
10
  ```json
11
11
  {
12
12
  "features": {
13
- "ghcr.io/nahuaque/snulbug/features/snulbug:0.2.4": {
14
- "version": "0.2.4",
13
+ "ghcr.io/nahuaque/snulbug/features/snulbug:0.2.5": {
14
+ "version": "0.2.5",
15
15
  "mode": "member-agent",
16
16
  "policy_profile": "tunnel-safe",
17
17
  "registry": "redis://redis:6379/0",
@@ -123,6 +123,7 @@ auth = "upstream"
123
123
  methods = ["GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"]
124
124
  forward_authorization = true
125
125
  forward_cookies = true
126
+ preserve_host = false
126
127
  timeout = 30.0
127
128
  health_path = "/healthz"
128
129
  health_status = 200
@@ -214,6 +215,69 @@ the forwarded host, protocol, and prefix headers. Snulbug does not rewrite HTML,
214
215
  redirect locations, or cookie paths. Use `strip_prefix = false` for an app that
215
216
  already serves its prefix. WebSocket upgrades are not supported by these HTTP mounts.
216
217
 
218
+ ### Public origin and Host preservation
219
+
220
+ The share's existing `mcp.proxy.tunnel_public_url` is the single source of truth
221
+ for the external scheme, authority, and port. Its MCP path is not part of the
222
+ origin: `https://share.example.com/mcp` yields `https://share.example.com`, and
223
+ the `/app` mount separately supplies `X-Forwarded-Prefix: /app`.
224
+
225
+ ```toml
226
+ [mcp.proxy]
227
+ tunnel_public_url = "https://share.example.com/mcp"
228
+
229
+ [[mcp.proxy.http_routes]]
230
+ prefix = "/app"
231
+ upstream = "http://127.0.0.1:3000"
232
+ auth = "upstream"
233
+ preserve_host = true
234
+ ```
235
+
236
+ Merge these values into existing tables rather than adding duplicate mounts.
237
+ The app receives `Host: share.example.com`, `X-Forwarded-Host: share.example.com`,
238
+ `X-Forwarded-Proto: https`, `X-Forwarded-Port: 443`, and
239
+ `X-Forwarded-Prefix: /app`. Explicit non-default public ports are retained.
240
+ The connection destination, HTTPS SNI, and certificate verification still use
241
+ the configured upstream URL. Client Origin/Referer headers are not rewritten.
242
+
243
+ `preserve_host` defaults to `false`: Host remains the backend authority for apps
244
+ that use internal virtual hosts, while the canonical forwarding headers still
245
+ advertise the public URL. MCP upstreams also retain their backend Host and
246
+ receive canonical host/protocol/port forwarding metadata. This does not change
247
+ OAuth resource/audience or DPoP checks, or pass client OAuth credentials upstream.
248
+
249
+ Caller-supplied forwarding/original-origin headers do not select the public
250
+ origin. Generated ngrok Cloud Endpoint policies capture and check the original
251
+ public authority/scheme before forwarding, replacing caller-supplied capture
252
+ and audit headers. Snulbug rejects duplicate, incomplete, malformed, or
253
+ mismatched capture pairs with HTTP 400, then removes them before dispatch.
254
+ Those headers are consistency checks, not proof that a request came from ngrok.
255
+ Direct local requests without capture headers are supported and intentionally
256
+ advertise the same configured public origin. No extra trusted-header opt-in is
257
+ needed. Only one canonical origin is supported; aliases/wildcards are not
258
+ automatically trusted.
259
+
260
+ Without `tunnel_public_url`, HTTP mounts retain local-request forwarding
261
+ behavior and doctor warns that the external origin is unknown. `preserve_host =
262
+ true` requires an explicit public URL. Invalid public URLs, including credentials,
263
+ query strings, fragments, malformed authorities, and control characters, fail
264
+ configuration validation.
265
+
266
+ Configure the app/framework to trust forwarding headers only from snulbug's
267
+ immediate backend connection, with its public host in any allowed-host list.
268
+ Prefix stripping does not automatically configure an application's root path;
269
+ set the app's `/app` base path or use its supported forwarded-prefix integration.
270
+ Host preservation alone does not implement CSRF protection or application auth.
271
+
272
+ Doctor/status/report and the console's HTTP mount contracts show the effective
273
+ public origin and Host mode. Backend health checks use the same origin/Host
274
+ contract without client credentials. Changing the public origin or Host mode
275
+ invalidates cached health and readiness review and changes signed mount bindings.
276
+ After upgrading an existing ngrok share, review the desired policy in share
277
+ doctor, update the generated Cloud Endpoint policy file, and reattach it in ngrok.
278
+ Restart the gateway after config changes. This feature does not rewrite a live
279
+ Cloud Endpoint or regenerate existing shares automatically.
280
+
217
281
  Restart `share run` after editing mounts. The router works with proxy and facade
218
282
  MCP modes and any tunnel that forwards the gateway origin. For generated ngrok
219
283
  setup, register routes at share creation so the Cloud Endpoint policy permits
@@ -106,6 +106,33 @@ and preserving prefixes. Existing shares need both a TOML route and an updated
106
106
  Cloud Endpoint Traffic Policy; changing TOML alone leaves ngrok's old deny rule
107
107
  in effect.
108
108
 
109
+ ### Public Links and Redirects
110
+
111
+ The generated share sets `mcp.proxy.tunnel_public_url` to the exact public MCP
112
+ URL. Snulbug derives the public origin from that URL, not from the internal
113
+ Agent Endpoint or caller-supplied forwarding headers. Mounted apps receive the
114
+ public host, scheme, and port through `X-Forwarded-*`, plus their mount prefix.
115
+ This also applies to local requests through snulbug while the public URL is set.
116
+
117
+ For apps that need the literal public Host header, add `preserve_host = true`
118
+ to that existing `[[mcp.proxy.http_routes]]` table. Leave it false (the default)
119
+ when the backend expects its own internal host. Configure the app to trust
120
+ snulbug as its immediate proxy and serve its public base path, such as `/app`.
121
+ See [public-origin forwarding](mcp-proxy.md#public-origin-and-host-preservation).
122
+
123
+ The generated Cloud Endpoint policy captures the original host/scheme before
124
+ forwarding, rejects authorities/schemes different from the configured public
125
+ origin with HTTP 421, and replaces caller-supplied origin/audit headers.
126
+ Snulbug validates captured pairs and rejects invalid or mismatched values with
127
+ HTTP 400. These checks do not authenticate the forwarding agent or replace
128
+ application/MCP authentication.
129
+
130
+ Changing the public URL requires updating TOML and the generated Cloud Endpoint
131
+ policy and reattaching that policy in ngrok. Existing policies from older
132
+ versions do not gain capture rules automatically; use share doctor's desired
133
+ baseline and drift checks to review and update them. Do not recreate the share
134
+ just to update its tunnel policy or discard its invites and evidence.
135
+
109
136
  ## 3. Export the generated client headers
110
137
 
111
138
  The generated share requires both bearer auth and an `x-snulbug-lease` header
@@ -59,7 +59,7 @@ python -m zipfile -l dist/snulbug-*.whl
59
59
  8. Install from TestPyPI in a clean environment and smoke test:
60
60
 
61
61
  ```bash
62
- uvx --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ snulbug==0.2.4 --help
62
+ uvx --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ snulbug==0.2.5 --help
63
63
  ```
64
64
 
65
65
  9. Run the manual `Publish` workflow with `repository = pypi` when the TestPyPI
@@ -67,4 +67,4 @@ uvx --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org
67
67
  10. Create the GitHub release and tag after the PyPI package is available.
68
68
 
69
69
  The `Publish` workflow uses `workflow_dispatch`, not a tag trigger. Creating or
70
- pushing `v0.2.4` does not publish a package or automatically create a GitHub release.
70
+ pushing `v0.2.5` does not publish a package or automatically create a GitHub release.
@@ -0,0 +1,97 @@
1
+ # Upgrading to 0.2.5
2
+
3
+ Snulbug 0.2.5 preserves the configured public origin across internal tunnel hops
4
+ and downstream MCP/HTTP forwarding. Back up the complete share directory before
5
+ upgrading and treat it as secret-bearing material. Do not recreate an existing
6
+ share with `share create --force` to update its tunnel policy.
7
+
8
+ For upgrades from before 0.2.4, also review the
9
+ [always-on runtime and HTTP mount upgrade notes](upgrading-to-0.2.4.md).
10
+
11
+ ## Check the canonical public URL
12
+
13
+ The existing `mcp.proxy.tunnel_public_url` now determines downstream
14
+ `X-Forwarded-Host`, `X-Forwarded-Proto`, and `X-Forwarded-Port`. The MCP path is
15
+ excluded from the origin; HTTP mounts independently provide their public prefix.
16
+ Local requests through the gateway intentionally advertise the same configured
17
+ public origin, not the local/internal hostname.
18
+
19
+ Use the exact intended HTTP(S) share URL without credentials, query strings, or
20
+ fragments. Malformed authorities and control characters now fail configuration
21
+ validation. Without a public URL, HTTP mounts retain local-request forwarding
22
+ and doctor warns that the external origin is unknown. Aliases and wildcard hosts
23
+ are not automatically trusted.
24
+
25
+ ## Decide whether the app needs the public Host
26
+
27
+ By default, the literal Host remains the backend authority. The canonical
28
+ forwarding headers are sufficient for apps that support trusted proxy headers.
29
+ For an app that needs the public Host for virtual hosting or URL generation,
30
+ merge this option into its existing mount:
31
+
32
+ ```toml
33
+ [mcp.proxy]
34
+ tunnel_public_url = "https://share.example.com/mcp"
35
+
36
+ [[mcp.proxy.http_routes]]
37
+ prefix = "/app"
38
+ upstream = "http://127.0.0.1:3000"
39
+ auth = "upstream"
40
+ preserve_host = true
41
+ ```
42
+
43
+ Do not add duplicate tables for an existing prefix. `preserve_host = true`
44
+ requires an explicit public URL. The connection target, HTTPS SNI, and
45
+ certificate verification remain tied to the upstream URL. MCP upstreams retain
46
+ their own backend Host while receiving canonical forwarding metadata.
47
+
48
+ Configure the app to trust snulbug's immediate backend connection, accept its
49
+ public hostname, and generate URLs for `/app`. Prefix stripping does not set an
50
+ application's root path automatically. Snulbug does not rewrite HTML, Location
51
+ headers, cookie paths, Origin, or Referer. Application login, permissions, and
52
+ CSRF protection remain the app's responsibility; MCP leases do not guard it.
53
+
54
+ ## Review and reattach the ngrok policy
55
+
56
+ Newly generated ngrok Cloud Endpoint policies capture the public host/scheme
57
+ before internal forwarding, reject unexpected origins with HTTP 421, and replace
58
+ caller-supplied forwarding/capture/audit headers. Snulbug rejects malformed,
59
+ duplicate, incomplete, or mismatched captured pairs with HTTP 400. These headers
60
+ are consistency checks, not authentication of the tunnel agent.
61
+
62
+ Existing policy files and live Cloud Endpoints are not changed automatically:
63
+
64
+ 1. Confirm the intended public URL in the existing share configuration.
65
+ 2. Run `snulbug mcp share doctor DIRECTORY` and review the desired ngrok baseline
66
+ and drift result. Add `--compact` for JSON, which includes `ngrok.desired_policy` when
67
+ inspection is available; the discovery extra supplies YAML inspection.
68
+ 3. Review and update `tunnel/ngrok-traffic-policy.yml`, retaining any deliberately
69
+ customized rules, and reattach the reviewed policy to the public Cloud Endpoint.
70
+ 4. Restart the gateway after config changes and rerun doctor. A running agent or
71
+ valid local YAML does not prove that the public endpoint has the new policy.
72
+
73
+ Direct requests without captured headers remain supported. Older policies do not
74
+ gain capture checks by upgrading Python alone, and doctor can flag them as drifted.
75
+ Passive console refreshes do not apply cloud changes or probe upstreams repeatedly.
76
+
77
+ ## Renew reviews and contracts deliberately
78
+
79
+ Effective public origins and Host modes now participate in HTTP mount bindings.
80
+ Existing mount health results and readiness reviews can become stale, and a
81
+ required signed contract created before these fields existed can prevent startup.
82
+ Inspect the intended exposure, run health/doctor checks, then renew the review
83
+ and regenerate/sign the contract through the existing `share contract` workflow.
84
+ Do not bypass a required contract just to suppress drift.
85
+
86
+ ## Verify before handoff
87
+
88
+ ```bash
89
+ snulbug mcp share doctor /path/to/share
90
+ snulbug mcp share status /path/to/share
91
+ snulbug mcp share report /path/to/share
92
+ ```
93
+
94
+ Check a safe MCP operation and an app link/redirect through the public URL.
95
+ The default MCP protocol and experimental profile limitations are unchanged.
96
+ Local release QA and ngrok config validation do not certify live provider/client
97
+ interoperability. See [public-origin forwarding](mcp-proxy.md#public-origin-and-host-preservation).
@@ -2,7 +2,7 @@
2
2
  "name": "snulbug Codespace MCP member",
3
3
  "features": {
4
4
  "ghcr.io/devcontainers/features/python:1": {},
5
- "ghcr.io/nahuaque/snulbug/features/snulbug:0.2.4": {
5
+ "ghcr.io/nahuaque/snulbug/features/snulbug:0.2.5": {
6
6
  "install_source": "github",
7
7
  "github_ref": "main",
8
8
  "mode": "member-agent",
@@ -6,7 +6,7 @@ workspace helpers for Codespaces/devcontainers.
6
6
  ```json
7
7
  {
8
8
  "features": {
9
- "ghcr.io/nahuaque/snulbug/features/snulbug:0.2.4": {
9
+ "ghcr.io/nahuaque/snulbug/features/snulbug:0.2.5": {
10
10
  "install_source": "github",
11
11
  "github_ref": "main",
12
12
  "mode": "member-agent",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "snulbug",
3
- "version": "0.2.4",
3
+ "version": "0.2.5",
4
4
  "name": "snulbug",
5
5
  "description": "Install snulbug and optional helpers for MCP policy gateways and remote fabric members in devcontainers.",
6
6
  "documentationURL": "https://github.com/nahuaque/snulbug/blob/main/docs/devcontainers.md",
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "snulbug"
3
- version = "0.2.4"
3
+ version = "0.2.5"
4
4
  description = "A local-dev MCP policy proxy with Lua policies, replay logs, and learned least-privilege bundles"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -1,6 +1,6 @@
1
1
  """Local-dev MCP policy proxy and programmable Lua ASGI policy layer."""
2
2
 
3
- __version__ = "0.2.4"
3
+ __version__ = "0.2.5"
4
4
 
5
5
  from .auth_providers import (
6
6
  AuthProvider,
@@ -161,7 +161,11 @@ def http_mounts_table(mounts: Mapping[str, Any]) -> Table:
161
161
  Text(f"{mount.get('prefix')}\n{mount.get('upstream')}"),
162
162
  _value(mount.get("auth")),
163
163
  Text(", ".join(mount.get("methods", []))),
164
- Text(f"Authorization: {mount.get('forward_authorization')}; cookies: {mount.get('forward_cookies')}"),
164
+ Text(
165
+ f"Authorization: {mount.get('forward_authorization')}; cookies: {mount.get('forward_cookies')}\n"
166
+ f"Origin: {mount.get('public_origin') or 'local request'}\n"
167
+ f"Host: {'public' if mount.get('preserve_host') else 'upstream'}"
168
+ ),
165
169
  Text(("pass" if health.get("ok") else "fail") if health.get("checked") else "not checked"),
166
170
  )
167
171
  if not mounts.get("items"):
@@ -15,6 +15,7 @@ from .policy_backoff import (
15
15
  DEFAULT_POLICY_BACKOFF_KEY_FIELDS,
16
16
  DEFAULT_POLICY_BACKOFF_REASON_CODES,
17
17
  )
18
+ from .public_origin import public_origin
18
19
  from .tool_catalog import CATALOG_PROJECTION_MODES
19
20
  from .upstream_transports import get_upstream_transport
20
21
 
@@ -249,6 +250,8 @@ lease_file = "leases.json"
249
250
  lease_required = true
250
251
  lease_header = "x-snulbug-lease"
251
252
  tunnel_provider = "auto"
253
+ # Canonical public origin for forwarded host/protocol/port; MCP path is excluded.
254
+ # HTTP mounts can opt into preserve_host = true to send the public Host as well.
252
255
  tunnel_public_url = ""
253
256
  cloudflare_access_profile = ""
254
257
  tailscale_profile = ""
@@ -598,6 +601,7 @@ def normalize_mcp_proxy_config(config: Mapping[str, Any], *, base_dir: str | Pat
598
601
  raise ValueError("mcp.proxy.tunnel_public_url must be a string")
599
602
  if normalized.get("tunnel_public_url") == "":
600
603
  normalized["tunnel_public_url"] = None
604
+ origin = public_origin(normalized.get("tunnel_public_url"))
601
605
  for field in ("policy", "record_out", "lease_file"):
602
606
  value = normalized.get(field)
603
607
  if value is not None and not isinstance(value, str | Path):
@@ -676,6 +680,8 @@ def normalize_mcp_proxy_config(config: Mapping[str, Any], *, base_dir: str | Pat
676
680
  normalized["http_routes"] = normalize_http_routes(
677
681
  normalized.get("http_routes", []), mcp_path=normalized["streamable_http_endpoint_path"]
678
682
  )
683
+ if origin is None and any(route.get("preserve_host") for route in normalized["http_routes"]):
684
+ raise ValueError("HTTP route preserve_host requires mcp.proxy.tunnel_public_url")
679
685
  normalized["streamable_http_allowed_origins"] = _normalize_string_list(
680
686
  normalized.get("streamable_http_allowed_origins", []),
681
687
  field="streamable_http_allowed_origins",
@@ -12,6 +12,7 @@ from typing import Any
12
12
  import httpx
13
13
 
14
14
  from .http_routes import http_route_contract
15
+ from .public_origin import public_origin
15
16
  from .share_doctor import ShareDoctorCheck, ShareDoctorCheckResult, ShareDoctorContext
16
17
 
17
18
 
@@ -31,7 +32,9 @@ class ShareHttpMountsDoctorCheck(ShareDoctorCheck):
31
32
  def http_mount_contracts(proxy: Mapping[str, Any]) -> list[dict[str, Any]]:
32
33
  return sorted(
33
34
  [
34
- http_route_contract(route, timeout=float(proxy.get("timeout", 30.0)))
35
+ http_route_contract(
36
+ route, timeout=float(proxy.get("timeout", 30.0)), public_url=proxy.get("tunnel_public_url")
37
+ )
35
38
  for route in proxy.get("http_routes", [])
36
39
  ],
37
40
  key=lambda route: route["prefix"],
@@ -60,6 +63,13 @@ def inspect_http_mounts(proxy: Mapping[str, Any], *, live_checks: bool = False,
60
63
  )
61
64
 
62
65
  declared = contract["auth"] != "undeclared"
66
+ origin_message = (
67
+ f"{prefix}: public origin is {contract['public_origin']}; Host uses "
68
+ + ("the public authority" if contract["preserve_host"] else "the upstream authority")
69
+ if contract["public_origin"]
70
+ else f"{prefix}: public origin is not configured; forwarded URLs use the local request origin"
71
+ )
72
+ check("origin", "pass" if contract["public_origin"] else "warn", origin_message)
63
73
  check(
64
74
  "auth",
65
75
  "pass" if declared else "fail",
@@ -130,7 +140,14 @@ def _probe_health(contract: Mapping[str, Any], *, timeout: float) -> dict[str, A
130
140
  }
131
141
  try:
132
142
  with httpx.Client(timeout=min(timeout, contract["timeout"]), trust_env=False, follow_redirects=False) as client:
133
- with client.stream("GET", target, headers={"accept": "*/*"}) as response:
143
+ origin = public_origin(contract["public_origin"])
144
+ headers = {"accept": "*/*"}
145
+ if origin:
146
+ headers.update({name.decode(): value.decode() for name, value in origin.headers()})
147
+ headers["x-forwarded-prefix"] = contract["prefix"]
148
+ if contract["preserve_host"]:
149
+ headers["host"] = origin.authority
150
+ with client.stream("GET", target, headers=headers) as response:
134
151
  result["status"] = response.status_code
135
152
  result["ok"] = response.status_code == contract["health_status"]
136
153
  result["message"] = (
@@ -10,6 +10,8 @@ from urllib.parse import quote, urlsplit
10
10
 
11
11
  import httpx
12
12
 
13
+ from .public_origin import public_origin
14
+
13
15
  if TYPE_CHECKING:
14
16
  from .middleware import ASGIApp, Receive, Scope, Send
15
17
 
@@ -33,6 +35,7 @@ HTTP_ROUTE_FIELDS = {
33
35
  "methods",
34
36
  "forward_authorization",
35
37
  "forward_cookies",
38
+ "preserve_host",
36
39
  "timeout",
37
40
  "health_path",
38
41
  "health_status",
@@ -109,7 +112,7 @@ def _normalize_contract(route: Mapping[str, Any]) -> dict[str, Any]:
109
112
  contract = {key: value for key, value in route.items() if key not in {"prefix", "upstream", "strip_prefix"}}
110
113
  if "auth" in contract and (not isinstance(contract["auth"], str) or contract["auth"] not in {"public", "upstream"}):
111
114
  raise ValueError("HTTP route auth must be 'public' or 'upstream'; MCP auth does not protect HTTP mounts")
112
- for key in ("forward_authorization", "forward_cookies"):
115
+ for key in ("forward_authorization", "forward_cookies", "preserve_host"):
113
116
  if key in contract and not isinstance(contract[key], bool):
114
117
  raise ValueError(f"HTTP route {key} must be a boolean")
115
118
  if "methods" in contract:
@@ -156,9 +159,14 @@ def _normalize_contract(route: Mapping[str, Any]) -> dict[str, Any]:
156
159
  return contract
157
160
 
158
161
 
159
- def http_route_contract(route: Mapping[str, Any], *, timeout: float = 30.0) -> dict[str, Any]:
162
+ def http_route_contract(
163
+ route: Mapping[str, Any], *, timeout: float = 30.0, public_url: str | None = None
164
+ ) -> dict[str, Any]:
160
165
  """One effective contract for routing, inspection, and signed share bindings."""
161
166
 
167
+ origin = public_origin(public_url)
168
+ if route.get("preserve_host") and origin is None:
169
+ raise ValueError("HTTP route preserve_host requires mcp.proxy.tunnel_public_url")
162
170
  return {
163
171
  "prefix": route["prefix"],
164
172
  "upstream": route["upstream"],
@@ -167,6 +175,8 @@ def http_route_contract(route: Mapping[str, Any], *, timeout: float = 30.0) -> d
167
175
  "methods": sorted(route.get("methods", DEFAULT_HTTP_METHODS)),
168
176
  "forward_authorization": route.get("forward_authorization", route.get("auth") != "public"),
169
177
  "forward_cookies": route.get("forward_cookies", route.get("auth") != "public"),
178
+ "preserve_host": route.get("preserve_host", False),
179
+ "public_origin": origin.url if origin else None,
170
180
  "timeout": route.get("timeout", timeout),
171
181
  "health_path": route.get("health_path"),
172
182
  "health_status": route.get("health_status", 200),
@@ -196,12 +206,16 @@ class HttpRoutesMiddleware:
196
206
  mcp_path: str = "/mcp",
197
207
  timeout: float = 30.0,
198
208
  lease_header: str = "x-snulbug-lease",
209
+ public_url: str | None = None,
199
210
  ) -> None:
200
211
  self.app = app
201
212
  if not math.isfinite(timeout) or timeout <= 0:
202
213
  raise ValueError("HTTP route timeout must be positive and finite")
203
214
  self.routes = sorted(
204
- [http_route_contract(route, timeout=timeout) for route in normalize_http_routes(routes, mcp_path=mcp_path)],
215
+ [
216
+ http_route_contract(route, timeout=timeout, public_url=public_url)
217
+ for route in normalize_http_routes(routes, mcp_path=mcp_path)
218
+ ],
205
219
  key=lambda r: -len(r["prefix"]),
206
220
  )
207
221
  self.timeout = timeout
@@ -312,9 +326,15 @@ class HttpRoutesMiddleware:
312
326
  or (name == b"cookie" and not route["forward_cookies"])
313
327
  ):
314
328
  headers.append((name, value))
315
- if original_host:
329
+ origin = public_origin(route["public_origin"])
330
+ if origin:
331
+ headers.extend(origin.headers())
332
+ if route["preserve_host"]:
333
+ headers.append((b"host", origin.authority.encode("ascii")))
334
+ elif original_host:
316
335
  headers.append((b"x-forwarded-host", original_host))
317
- headers.append((b"x-forwarded-proto", scope.get("scheme", "http").encode("ascii")))
336
+ if not origin:
337
+ headers.append((b"x-forwarded-proto", scope.get("scheme", "http").encode("ascii")))
318
338
  headers.append((b"x-forwarded-prefix", route["prefix"].encode("ascii")))
319
339
  if scope.get("client"):
320
340
  headers.append((b"x-forwarded-for", scope["client"][0].encode("ascii")))
@@ -36,6 +36,11 @@ _SAFE_KEYS = _PHASES | {
36
36
  "x-snulbug-traffic-policy",
37
37
  "x-snulbug-public-url",
38
38
  "x-snulbug-edge-client-ip",
39
+ "x-snulbug-original-host",
40
+ "x-snulbug-original-proto",
41
+ "vars",
42
+ "snulbug_original_host",
43
+ "snulbug_original_proto",
39
44
  }
40
45
 
41
46
 
@@ -96,6 +96,7 @@ from .mcp_stream import AUTH_EXPIRY_SCOPE_KEY, STREAM_SCOPE_KEY, McpStreamError,
96
96
  from .mcp_tasks import mcp_task_request_metadata
97
97
  from .middleware import ASGIApp, LuaConfig, LuaMiddleware, Receive, Scope, Send
98
98
  from .policy_backoff import PolicyBackoffConfig
99
+ from .public_origin import PublicOriginMiddleware
99
100
  from .recorder import append_record, build_request_record, record_audit_event
100
101
  from .request_explanations import (
101
102
  upstream_failure,
@@ -3371,9 +3372,14 @@ def create_proxy_application(
3371
3372
  )
3372
3373
  if http_routes:
3373
3374
  app = HttpRoutesMiddleware(
3374
- app, http_routes, mcp_path=streamable_http_config.endpoint_path, timeout=timeout, lease_header=lease_header
3375
+ app,
3376
+ http_routes,
3377
+ mcp_path=streamable_http_config.endpoint_path,
3378
+ timeout=timeout,
3379
+ lease_header=lease_header,
3380
+ public_url=tunnel_public_url,
3375
3381
  )
3376
- return app
3382
+ return PublicOriginMiddleware(app, public_url=tunnel_public_url)
3377
3383
 
3378
3384
 
3379
3385
  def run_proxy(