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.
- {snulbug-0.2.4 → snulbug-0.2.5}/CHANGELOG.md +15 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/PKG-INFO +1 -1
- {snulbug-0.2.4 → snulbug-0.2.5}/docs/devcontainers.md +2 -2
- {snulbug-0.2.4 → snulbug-0.2.5}/docs/mcp-proxy.md +64 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/docs/ngrok-end-to-end.md +27 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/docs/release.md +2 -2
- snulbug-0.2.5/docs/upgrading-to-0.2.5.md +97 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/codespace_local_gateway/.devcontainer/devcontainer.json +1 -1
- {snulbug-0.2.4 → snulbug-0.2.5}/features/snulbug/README.md +1 -1
- {snulbug-0.2.4 → snulbug-0.2.5}/features/snulbug/devcontainer-feature.json +1 -1
- {snulbug-0.2.4 → snulbug-0.2.5}/pyproject.toml +1 -1
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/__init__.py +1 -1
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/cli/rich_share.py +5 -1
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/config.py +6 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/http_mounts.py +19 -2
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/http_routes.py +25 -5
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/ngrok_drift.py +5 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/proxy.py +8 -2
- snulbug-0.2.5/snulbug/public_origin.py +136 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/release_qa.py +3 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/share.py +2 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/share_console.py +2 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/tunnel.py +53 -1
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_http_mounts.py +36 -3
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_http_routes.py +119 -3
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_ngrok_drift.py +12 -4
- snulbug-0.2.5/tests/test_public_origin.py +209 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_release_qa.py +2 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_tunnel_init.py +68 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/.gitignore +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/CODE_OF_CONDUCT.md +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/CONTRIBUTING.md +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/LICENSE +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/README.md +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/SECURITY.md +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/docs/README.md +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/docs/actions.md +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/docs/always-on-runtime.md +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/docs/auth-providers.md +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/docs/aws-mcp.md +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/docs/bundles.md +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/docs/ci-policy-gates.md +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/docs/comparison.md +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/docs/credential-brokers.md +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/docs/discovery-providers.md +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/docs/event-sink-providers.md +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/docs/evidence-exporters.md +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/docs/getting-started.md +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/docs/lua-policy-dsl.md +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/docs/lua-request-api.md +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/docs/mcp-auth-recipes.md +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/docs/mcp-client-recipes.md +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/docs/mcp-evidence.md +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/docs/mcp-fabric.md +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/docs/mcp-gateway.md +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/docs/mcp-guide.md +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/docs/mcp-impact.md +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/docs/mcp-learn.md +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/docs/mcp-policy.md +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/docs/mcp-presets.md +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/docs/mcp-protocol.md +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/docs/mcp-recorder.md +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/docs/mcp-schemas.md +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/docs/mcp-share.md +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/docs/policy-deny-backoff.md +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/docs/quickstart.md +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/docs/roadmap.md +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/docs/security-model.md +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/docs/share-doctor-checks.md +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/docs/state.md +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/docs/tool-catalog-projection.md +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/docs/tool-risk-analyzers.md +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/docs/tunnel-providers.md +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/docs/upgrading-to-0.2.4.md +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/docs/upstream-transports.md +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/auth_claim_patterns/.gitignore +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/auth_claim_patterns/README.md +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/auth_claim_patterns/ci-workload-identity.toml +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/auth_claim_patterns/group-gated-tools.toml +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/auth_claim_patterns/policy.lua +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/auth_claim_patterns/tenant-isolation.toml +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/basic/README.md +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/basic/app.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/bundles/idempotency.snulbug/README.md +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/bundles/idempotency.snulbug/fixtures/duplicate-delivery.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/bundles/idempotency.snulbug/fixtures/first-delivery.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/bundles/idempotency.snulbug/manifest.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/bundles/idempotency.snulbug/policy.lua +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/bundles/idempotency.snulbug/snapshots/duplicate.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/bundles/idempotency.snulbug/snapshots/empty.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/bundles/mcp-gateway.snulbug/README.md +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/bundles/mcp-gateway.snulbug/fixtures/allowed-tool.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/bundles/mcp-gateway.snulbug/fixtures/blocked-tool.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/bundles/mcp-gateway.snulbug/fixtures/unauthenticated.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/bundles/mcp-gateway.snulbug/manifest.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/bundles/mcp-gateway.snulbug/policy.lua +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/bundles/mcp-gateway.snulbug/snapshots/empty.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/codespace_local_gateway/README.md +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/codespace_local_gateway/mcp-client.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/codespace_local_gateway/mock_mcp_server.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/codespace_local_gateway/policy.lua +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/codespace_local_gateway/snulbug.env-gateway.toml +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/codespace_local_gateway/snulbug.local-gateway.toml +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/customer_policy/README.md +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/customer_policy/app.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/customer_policy/policies/acme.lua +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/customer_policy/policies/default.lua +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/customer_policy/policies/globex.lua +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/customer_policy/requests/acme-invalid.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/customer_policy/requests/acme-valid.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/customer_policy/requests/globex-sandbox.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/customer_policy/requests/unknown-tenant.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/github-actions/snulbug-policy-gates.yml +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/keycloak_oauth_demo/.gitignore +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/keycloak_oauth_demo/Dockerfile.gateway +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/keycloak_oauth_demo/README.md +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/keycloak_oauth_demo/auth/keycloak/README.md +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/keycloak_oauth_demo/auth/keycloak/auth-init.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/keycloak_oauth_demo/auth/keycloak/client-token-request.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/keycloak_oauth_demo/auth/keycloak/commands.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/keycloak_oauth_demo/auth/keycloak/snulbug.auth.toml +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/keycloak_oauth_demo/docker-compose.yml +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/keycloak_oauth_demo/get-token.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/keycloak_oauth_demo/keycloak/snulbug-demo-realm.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/keycloak_oauth_demo/mock_mcp_server.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/keycloak_oauth_demo/policy.snulbug/policy.lua +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/keycloak_oauth_demo/requests/blocked-write-file.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/keycloak_oauth_demo/requests/read-file.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/keycloak_oauth_demo/requests/tools-list.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/keycloak_oauth_demo/snulbug.toml +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/mcp_container_facade/Dockerfile.gateway +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/mcp_container_facade/Dockerfile.remote-peer +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/mcp_container_facade/README.md +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/mcp_container_facade/docker-compose.yml +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/mcp_container_facade/hypertele-client.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/mcp_container_facade/hypertele-server.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/mcp_container_facade/mcp-client.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/mcp_container_facade/mock_mcp_server.js +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/mcp_container_facade/mock_mcp_server.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/mcp_container_facade/policy.lua +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/mcp_container_facade/snulbug.facade.toml +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/mcp_container_facade/snulbug.local.toml +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/mcp_gateway/README.md +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/mcp_gateway/app.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/mcp_proxy_demo/.gitignore +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/mcp_proxy_demo/README.md +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/mcp_proxy_demo/requests/blocked-tool.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/mcp_proxy_demo/requests/safe-tool.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/mcp_proxy_demo/requests/tools-list.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/mcp_proxy_demo/run_demo.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/mcp_proxy_demo/upstream.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/policy_promotion/README.md +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/policy_promotion/active.lua +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/policy_promotion/draft.lua +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/policy_promotion/fixtures/acme-invalid.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/policy_promotion/fixtures/acme-valid.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/provider_policy_templates/README.md +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/provider_policy_templates/cloudflare-access-group-gate.lua +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/provider_policy_templates/entra-app-role-gate.lua +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/provider_policy_templates/github-actions-workload-gate.lua +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/provider_policy_templates/keycloak-role-gate.lua +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/state_replay/README.md +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/state_replay/duplicate-state.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/state_replay/idempotency.lua +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/state_replay/request.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/webhook_normalization/README.md +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/webhook_normalization/app.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/webhook_normalization/policies/github.lua +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/webhook_normalization/policies/stripe.lua +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/webhook_normalization/policies/unknown.lua +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/webhook_normalization/requests/github-push.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/webhook_normalization/requests/stripe-invoice.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/webhook_normalization/requests/stripe-malformed.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/examples/webhook_normalization/requests/unknown-vendor.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/features/snulbug/install.sh +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/__main__.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/_operations_smoke.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/assets/prism.css +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/assets/prism.js +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/auth_providers.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/auth_recipes.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/auth-required.snulbug/README.md +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/auth-required.snulbug/fixtures/authenticated.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/auth-required.snulbug/fixtures/missing-token.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/auth-required.snulbug/manifest.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/auth-required.snulbug/policy.lua +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/aws-mcp.snulbug/README.md +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/aws-mcp.snulbug/contexts/inventory-lease.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/aws-mcp.snulbug/contexts/sandbox-change-lease.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/aws-mcp.snulbug/fixtures/data-read.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/aws-mcp.snulbug/fixtures/iam-delete-user.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/aws-mcp.snulbug/fixtures/inventory-read.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/aws-mcp.snulbug/fixtures/knowledge-search.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/aws-mcp.snulbug/fixtures/missing-token.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/aws-mcp.snulbug/fixtures/sandbox-mutation.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/aws-mcp.snulbug/manifest.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/aws-mcp.snulbug/policy.lua +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/aws-mcp.snulbug/snapshots/empty.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/local-dev-safe.snulbug/README.md +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/local-dev-safe.snulbug/fixtures/missing-token.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/local-dev-safe.snulbug/fixtures/safe-tool.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/local-dev-safe.snulbug/fixtures/unsafe-tool.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/local-dev-safe.snulbug/manifest.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/local-dev-safe.snulbug/policy.lua +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/local-dev-safe.snulbug/snapshots/empty.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/no-shell-tools.snulbug/README.md +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/no-shell-tools.snulbug/fixtures/missing-token.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/no-shell-tools.snulbug/fixtures/safe-tool.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/no-shell-tools.snulbug/fixtures/shell-tool.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/no-shell-tools.snulbug/manifest.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/no-shell-tools.snulbug/policy.lua +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/project-path-allowlist.snulbug/README.md +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/project-path-allowlist.snulbug/fixtures/absolute-path.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/project-path-allowlist.snulbug/fixtures/allowed-path.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/project-path-allowlist.snulbug/fixtures/traversal-path.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/project-path-allowlist.snulbug/fixtures/unsafe-tool.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/project-path-allowlist.snulbug/manifest.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/project-path-allowlist.snulbug/policy.lua +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/read-only-local-dev.snulbug/README.md +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/read-only-local-dev.snulbug/fixtures/missing-token.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/read-only-local-dev.snulbug/fixtures/safe-tool.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/read-only-local-dev.snulbug/fixtures/tools-list.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/read-only-local-dev.snulbug/fixtures/write-method.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/read-only-local-dev.snulbug/manifest.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/read-only-local-dev.snulbug/policy.lua +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/read-only-local-dev.snulbug/snapshots/empty.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/tool-allowlist.snulbug/README.md +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/tool-allowlist.snulbug/fixtures/safe-tool.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/tool-allowlist.snulbug/fixtures/tools-list.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/tool-allowlist.snulbug/fixtures/unsafe-tool.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/tool-allowlist.snulbug/manifest.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/tool-allowlist.snulbug/policy.lua +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/tunnel-safe.snulbug/README.md +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/tunnel-safe.snulbug/fixtures/batch-request.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/tunnel-safe.snulbug/fixtures/missing-token.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/tunnel-safe.snulbug/fixtures/safe-tool.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/tunnel-safe.snulbug/fixtures/unsafe-tool.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/tunnel-safe.snulbug/manifest.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/tunnel-safe.snulbug/policy.lua +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/tunnel-safe.snulbug/snapshots/empty.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/workspace-firewall.snulbug/README.md +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/workspace-firewall.snulbug/fixtures/allowed-read.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/workspace-firewall.snulbug/fixtures/missing-token.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/workspace-firewall.snulbug/fixtures/secret-path.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/workspace-firewall.snulbug/fixtures/traversal-path.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/workspace-firewall.snulbug/fixtures/unsafe-tool.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/workspace-firewall.snulbug/manifest.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/builtin_presets/mcp/workspace-firewall.snulbug/policy.lua +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/bundle.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/cli/__init__.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/cli/common.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/cli/evidence.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/cli/fabric.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/cli/policy.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/cli/rich_reports.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/cli/schemas.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/cli/share.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/cli_helpers.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/cloudflare_access.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/codespaces.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/confirm.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/control_events.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/controller.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/credential_health.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/credentials.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/discovery.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/events.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/evidence_exporters.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/fabric.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/fabric_control.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/fabric_members.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/fabric_runtime.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/gateway_templates.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/guide.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/impact.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/inspection.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/json_schema.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/lab.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/learn.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/leases.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/manifests.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/mcp_auth.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/mcp_cache.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/mcp_capabilities.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/mcp_client_requests.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/mcp_completion.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/mcp_mrtr.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/mcp_parameter_headers.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/mcp_policy_bundles.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/mcp_progress.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/mcp_protocol.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/mcp_resources.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/mcp_schema_policy.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/mcp_schemas.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/mcp_spec_conformance.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/mcp_stdio.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/mcp_stream.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/mcp_subscriptions.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/mcp_tasks.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/mcp_tools.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/middleware.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/policy_activation.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/policy_backoff.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/policy_review.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/presets.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/promotion.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/py.typed +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/quickstart.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/recorder.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/redaction.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/request_explanations.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/response_policy.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/runtime.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/sarif.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/scaffolds.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/schema_policy.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/share_doctor.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/share_runtime.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/share_session.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/simulator.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/state.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/tool_catalog.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/tool_risk.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/snulbug/upstream_transports.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/fixtures/json_schema_2020_12/LICENSE +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/fixtures/json_schema_2020_12/README.md +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/fixtures/json_schema_2020_12/dependentSchemas.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/fixtures/json_schema_2020_12/dynamicRef.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/fixtures/json_schema_2020_12/if-then-else.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/fixtures/json_schema_2020_12/prefixItems.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/fixtures/json_schema_2020_12/unevaluatedProperties.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/fixtures/pocket_id/README.md +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/fixtures/pocket_id/oauth.json +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/fixtures/stdio_subscriptions.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_auth_claim_patterns.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_auth_recipes.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_automation_invites.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_cli_helpers.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_cloudflare_access.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_codespaces.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_config.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_credential_health.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_credentials.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_customer_policy_demo.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_devcontainer_feature.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_discovery.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_events.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_evidence_exporters.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_fabric.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_fabric_controller.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_gateway_snapshot.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_json_schema.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_keycloak_oauth_demo.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_leases.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_manifests.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_mcp_cache.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_mcp_gateway_demo.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_mcp_guide.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_mcp_helpers.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_mcp_impact.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_mcp_inspection.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_mcp_lab.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_mcp_learn.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_mcp_mrtr.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_mcp_parameter_headers.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_mcp_policy_bundles.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_mcp_presets.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_mcp_progress.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_mcp_protocol.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_mcp_proxy_demo.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_mcp_quickstart.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_mcp_recorder.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_mcp_resources.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_mcp_schema_policy.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_mcp_schemas.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_mcp_share.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_mcp_spec_conformance.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_mcp_stdio.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_mcp_stream.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_mcp_subscriptions.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_mcp_tools.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_middleware.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_operations_smoke.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_pocket_id_auth.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_policy_bundle.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_policy_promotion.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_policy_state.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_provider_policy_templates.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_proxy.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_redaction_audit.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_request_explanations.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_response_policy.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_sandbox_abuse.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_scaffolds.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_share_console.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_share_runtime.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_state_snapshots.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_tool_catalog.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_tool_risk.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_tunnel_audit.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_tunnel_doctor.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_upstream_transports.py +0 -0
- {snulbug-0.2.4 → snulbug-0.2.5}/tests/test_webhook_normalization_demo.py +0 -0
- {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.
|
|
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.
|
|
14
|
-
"version": "0.2.
|
|
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.
|
|
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.
|
|
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).
|
{snulbug-0.2.4 → snulbug-0.2.5}/examples/codespace_local_gateway/.devcontainer/devcontainer.json
RENAMED
|
@@ -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.
|
|
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.
|
|
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.
|
|
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",
|
|
@@ -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(
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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
|
-
[
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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(
|