snulbug 0.2.2__tar.gz → 0.2.3__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.2 → snulbug-0.2.3}/CHANGELOG.md +12 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/PKG-INFO +2 -2
- {snulbug-0.2.2 → snulbug-0.2.3}/README.md +1 -1
- {snulbug-0.2.2 → snulbug-0.2.3}/docs/auth-providers.md +5 -1
- {snulbug-0.2.2 → snulbug-0.2.3}/docs/devcontainers.md +2 -2
- {snulbug-0.2.2 → snulbug-0.2.3}/docs/mcp-auth-recipes.md +112 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/docs/release.md +2 -2
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/codespace_local_gateway/.devcontainer/devcontainer.json +1 -1
- {snulbug-0.2.2 → snulbug-0.2.3}/features/snulbug/README.md +1 -1
- {snulbug-0.2.2 → snulbug-0.2.3}/features/snulbug/devcontainer-feature.json +1 -1
- {snulbug-0.2.2 → snulbug-0.2.3}/pyproject.toml +1 -1
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/__init__.py +1 -1
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/auth_recipes.py +114 -7
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/share.py +66 -0
- snulbug-0.2.3/tests/fixtures/pocket_id/README.md +19 -0
- snulbug-0.2.3/tests/fixtures/pocket_id/oauth.json +66 -0
- snulbug-0.2.3/tests/test_pocket_id_auth.py +345 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/.gitignore +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/CODE_OF_CONDUCT.md +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/CONTRIBUTING.md +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/LICENSE +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/SECURITY.md +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/docs/README.md +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/docs/actions.md +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/docs/aws-mcp.md +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/docs/bundles.md +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/docs/ci-policy-gates.md +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/docs/comparison.md +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/docs/credential-brokers.md +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/docs/discovery-providers.md +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/docs/event-sink-providers.md +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/docs/evidence-exporters.md +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/docs/getting-started.md +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/docs/lua-policy-dsl.md +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/docs/lua-request-api.md +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/docs/mcp-client-recipes.md +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/docs/mcp-evidence.md +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/docs/mcp-fabric.md +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/docs/mcp-gateway.md +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/docs/mcp-guide.md +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/docs/mcp-impact.md +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/docs/mcp-learn.md +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/docs/mcp-policy.md +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/docs/mcp-presets.md +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/docs/mcp-protocol.md +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/docs/mcp-proxy.md +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/docs/mcp-recorder.md +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/docs/mcp-schemas.md +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/docs/mcp-share.md +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/docs/ngrok-end-to-end.md +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/docs/policy-deny-backoff.md +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/docs/quickstart.md +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/docs/roadmap.md +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/docs/security-model.md +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/docs/share-doctor-checks.md +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/docs/state.md +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/docs/tool-catalog-projection.md +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/docs/tool-risk-analyzers.md +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/docs/tunnel-providers.md +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/docs/upstream-transports.md +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/auth_claim_patterns/.gitignore +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/auth_claim_patterns/README.md +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/auth_claim_patterns/ci-workload-identity.toml +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/auth_claim_patterns/group-gated-tools.toml +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/auth_claim_patterns/policy.lua +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/auth_claim_patterns/tenant-isolation.toml +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/basic/README.md +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/basic/app.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/bundles/idempotency.snulbug/README.md +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/bundles/idempotency.snulbug/fixtures/duplicate-delivery.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/bundles/idempotency.snulbug/fixtures/first-delivery.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/bundles/idempotency.snulbug/manifest.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/bundles/idempotency.snulbug/policy.lua +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/bundles/idempotency.snulbug/snapshots/duplicate.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/bundles/idempotency.snulbug/snapshots/empty.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/bundles/mcp-gateway.snulbug/README.md +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/bundles/mcp-gateway.snulbug/fixtures/allowed-tool.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/bundles/mcp-gateway.snulbug/fixtures/blocked-tool.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/bundles/mcp-gateway.snulbug/fixtures/unauthenticated.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/bundles/mcp-gateway.snulbug/manifest.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/bundles/mcp-gateway.snulbug/policy.lua +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/bundles/mcp-gateway.snulbug/snapshots/empty.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/codespace_local_gateway/README.md +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/codespace_local_gateway/mcp-client.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/codespace_local_gateway/mock_mcp_server.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/codespace_local_gateway/policy.lua +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/codespace_local_gateway/snulbug.env-gateway.toml +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/codespace_local_gateway/snulbug.local-gateway.toml +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/customer_policy/README.md +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/customer_policy/app.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/customer_policy/policies/acme.lua +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/customer_policy/policies/default.lua +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/customer_policy/policies/globex.lua +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/customer_policy/requests/acme-invalid.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/customer_policy/requests/acme-valid.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/customer_policy/requests/globex-sandbox.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/customer_policy/requests/unknown-tenant.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/github-actions/snulbug-policy-gates.yml +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/keycloak_oauth_demo/.gitignore +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/keycloak_oauth_demo/Dockerfile.gateway +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/keycloak_oauth_demo/README.md +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/keycloak_oauth_demo/auth/keycloak/README.md +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/keycloak_oauth_demo/auth/keycloak/auth-init.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/keycloak_oauth_demo/auth/keycloak/client-token-request.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/keycloak_oauth_demo/auth/keycloak/commands.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/keycloak_oauth_demo/auth/keycloak/snulbug.auth.toml +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/keycloak_oauth_demo/docker-compose.yml +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/keycloak_oauth_demo/get-token.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/keycloak_oauth_demo/keycloak/snulbug-demo-realm.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/keycloak_oauth_demo/mock_mcp_server.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/keycloak_oauth_demo/policy.snulbug/policy.lua +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/keycloak_oauth_demo/requests/blocked-write-file.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/keycloak_oauth_demo/requests/read-file.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/keycloak_oauth_demo/requests/tools-list.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/keycloak_oauth_demo/snulbug.toml +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/mcp_container_facade/Dockerfile.gateway +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/mcp_container_facade/Dockerfile.remote-peer +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/mcp_container_facade/README.md +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/mcp_container_facade/docker-compose.yml +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/mcp_container_facade/hypertele-client.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/mcp_container_facade/hypertele-server.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/mcp_container_facade/mcp-client.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/mcp_container_facade/mock_mcp_server.js +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/mcp_container_facade/mock_mcp_server.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/mcp_container_facade/policy.lua +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/mcp_container_facade/snulbug.facade.toml +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/mcp_container_facade/snulbug.local.toml +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/mcp_gateway/README.md +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/mcp_gateway/app.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/mcp_proxy_demo/.gitignore +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/mcp_proxy_demo/README.md +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/mcp_proxy_demo/requests/blocked-tool.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/mcp_proxy_demo/requests/safe-tool.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/mcp_proxy_demo/requests/tools-list.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/mcp_proxy_demo/run_demo.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/mcp_proxy_demo/upstream.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/policy_promotion/README.md +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/policy_promotion/active.lua +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/policy_promotion/draft.lua +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/policy_promotion/fixtures/acme-invalid.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/policy_promotion/fixtures/acme-valid.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/provider_policy_templates/README.md +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/provider_policy_templates/cloudflare-access-group-gate.lua +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/provider_policy_templates/entra-app-role-gate.lua +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/provider_policy_templates/github-actions-workload-gate.lua +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/provider_policy_templates/keycloak-role-gate.lua +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/state_replay/README.md +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/state_replay/duplicate-state.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/state_replay/idempotency.lua +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/state_replay/request.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/webhook_normalization/README.md +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/webhook_normalization/app.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/webhook_normalization/policies/github.lua +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/webhook_normalization/policies/stripe.lua +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/webhook_normalization/policies/unknown.lua +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/webhook_normalization/requests/github-push.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/webhook_normalization/requests/stripe-invoice.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/webhook_normalization/requests/stripe-malformed.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/examples/webhook_normalization/requests/unknown-vendor.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/features/snulbug/install.sh +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/__main__.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/assets/prism.css +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/assets/prism.js +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/auth_providers.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/builtin_presets/mcp/auth-required.snulbug/README.md +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/builtin_presets/mcp/auth-required.snulbug/fixtures/authenticated.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/builtin_presets/mcp/auth-required.snulbug/fixtures/missing-token.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/builtin_presets/mcp/auth-required.snulbug/manifest.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/builtin_presets/mcp/auth-required.snulbug/policy.lua +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/builtin_presets/mcp/aws-mcp.snulbug/README.md +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/builtin_presets/mcp/aws-mcp.snulbug/contexts/inventory-lease.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/builtin_presets/mcp/aws-mcp.snulbug/contexts/sandbox-change-lease.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/builtin_presets/mcp/aws-mcp.snulbug/fixtures/data-read.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/builtin_presets/mcp/aws-mcp.snulbug/fixtures/iam-delete-user.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/builtin_presets/mcp/aws-mcp.snulbug/fixtures/inventory-read.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/builtin_presets/mcp/aws-mcp.snulbug/fixtures/knowledge-search.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/builtin_presets/mcp/aws-mcp.snulbug/fixtures/missing-token.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/builtin_presets/mcp/aws-mcp.snulbug/fixtures/sandbox-mutation.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/builtin_presets/mcp/aws-mcp.snulbug/manifest.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/builtin_presets/mcp/aws-mcp.snulbug/policy.lua +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/builtin_presets/mcp/aws-mcp.snulbug/snapshots/empty.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/builtin_presets/mcp/local-dev-safe.snulbug/README.md +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/builtin_presets/mcp/local-dev-safe.snulbug/fixtures/missing-token.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/builtin_presets/mcp/local-dev-safe.snulbug/fixtures/safe-tool.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/builtin_presets/mcp/local-dev-safe.snulbug/fixtures/unsafe-tool.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/builtin_presets/mcp/local-dev-safe.snulbug/manifest.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/builtin_presets/mcp/local-dev-safe.snulbug/policy.lua +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/builtin_presets/mcp/local-dev-safe.snulbug/snapshots/empty.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/builtin_presets/mcp/no-shell-tools.snulbug/README.md +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/builtin_presets/mcp/no-shell-tools.snulbug/fixtures/missing-token.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/builtin_presets/mcp/no-shell-tools.snulbug/fixtures/safe-tool.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/builtin_presets/mcp/no-shell-tools.snulbug/fixtures/shell-tool.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/builtin_presets/mcp/no-shell-tools.snulbug/manifest.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/builtin_presets/mcp/no-shell-tools.snulbug/policy.lua +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/builtin_presets/mcp/project-path-allowlist.snulbug/README.md +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/builtin_presets/mcp/project-path-allowlist.snulbug/fixtures/absolute-path.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/builtin_presets/mcp/project-path-allowlist.snulbug/fixtures/allowed-path.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/builtin_presets/mcp/project-path-allowlist.snulbug/fixtures/traversal-path.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/builtin_presets/mcp/project-path-allowlist.snulbug/fixtures/unsafe-tool.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/builtin_presets/mcp/project-path-allowlist.snulbug/manifest.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/builtin_presets/mcp/project-path-allowlist.snulbug/policy.lua +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/builtin_presets/mcp/read-only-local-dev.snulbug/README.md +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/builtin_presets/mcp/read-only-local-dev.snulbug/fixtures/missing-token.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/builtin_presets/mcp/read-only-local-dev.snulbug/fixtures/safe-tool.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/builtin_presets/mcp/read-only-local-dev.snulbug/fixtures/tools-list.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/builtin_presets/mcp/read-only-local-dev.snulbug/fixtures/write-method.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/builtin_presets/mcp/read-only-local-dev.snulbug/manifest.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/builtin_presets/mcp/read-only-local-dev.snulbug/policy.lua +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/builtin_presets/mcp/read-only-local-dev.snulbug/snapshots/empty.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/builtin_presets/mcp/tool-allowlist.snulbug/README.md +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/builtin_presets/mcp/tool-allowlist.snulbug/fixtures/safe-tool.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/builtin_presets/mcp/tool-allowlist.snulbug/fixtures/tools-list.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/builtin_presets/mcp/tool-allowlist.snulbug/fixtures/unsafe-tool.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/builtin_presets/mcp/tool-allowlist.snulbug/manifest.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/builtin_presets/mcp/tool-allowlist.snulbug/policy.lua +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/builtin_presets/mcp/tunnel-safe.snulbug/README.md +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/builtin_presets/mcp/tunnel-safe.snulbug/fixtures/batch-request.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/builtin_presets/mcp/tunnel-safe.snulbug/fixtures/missing-token.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/builtin_presets/mcp/tunnel-safe.snulbug/fixtures/safe-tool.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/builtin_presets/mcp/tunnel-safe.snulbug/fixtures/unsafe-tool.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/builtin_presets/mcp/tunnel-safe.snulbug/manifest.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/builtin_presets/mcp/tunnel-safe.snulbug/policy.lua +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/builtin_presets/mcp/tunnel-safe.snulbug/snapshots/empty.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/builtin_presets/mcp/workspace-firewall.snulbug/README.md +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/builtin_presets/mcp/workspace-firewall.snulbug/fixtures/allowed-read.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/builtin_presets/mcp/workspace-firewall.snulbug/fixtures/missing-token.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/builtin_presets/mcp/workspace-firewall.snulbug/fixtures/secret-path.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/builtin_presets/mcp/workspace-firewall.snulbug/fixtures/traversal-path.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/builtin_presets/mcp/workspace-firewall.snulbug/fixtures/unsafe-tool.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/builtin_presets/mcp/workspace-firewall.snulbug/manifest.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/builtin_presets/mcp/workspace-firewall.snulbug/policy.lua +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/bundle.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/cli/__init__.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/cli/common.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/cli/evidence.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/cli/fabric.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/cli/policy.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/cli/rich_reports.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/cli/rich_share.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/cli/schemas.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/cli/share.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/cli_helpers.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/cloudflare_access.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/codespaces.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/config.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/confirm.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/control_events.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/controller.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/credentials.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/discovery.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/events.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/evidence_exporters.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/fabric.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/fabric_control.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/fabric_members.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/fabric_runtime.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/gateway_templates.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/guide.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/http_routes.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/impact.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/inspection.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/json_schema.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/lab.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/learn.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/leases.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/manifests.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/mcp_auth.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/mcp_cache.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/mcp_capabilities.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/mcp_client_requests.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/mcp_completion.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/mcp_mrtr.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/mcp_parameter_headers.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/mcp_policy_bundles.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/mcp_progress.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/mcp_protocol.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/mcp_resources.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/mcp_schema_policy.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/mcp_schemas.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/mcp_spec_conformance.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/mcp_stdio.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/mcp_stream.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/mcp_subscriptions.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/mcp_tasks.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/mcp_tools.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/middleware.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/policy_activation.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/policy_backoff.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/policy_review.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/presets.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/promotion.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/proxy.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/py.typed +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/quickstart.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/recorder.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/redaction.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/release_qa.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/response_policy.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/runtime.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/sarif.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/scaffolds.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/schema_policy.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/share_console.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/share_doctor.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/share_session.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/simulator.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/state.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/tool_catalog.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/tool_risk.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/tunnel.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/snulbug/upstream_transports.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/tests/fixtures/json_schema_2020_12/LICENSE +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/tests/fixtures/json_schema_2020_12/README.md +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/tests/fixtures/json_schema_2020_12/dependentSchemas.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/tests/fixtures/json_schema_2020_12/dynamicRef.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/tests/fixtures/json_schema_2020_12/if-then-else.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/tests/fixtures/json_schema_2020_12/prefixItems.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/tests/fixtures/json_schema_2020_12/unevaluatedProperties.json +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/tests/fixtures/stdio_subscriptions.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/tests/test_auth_claim_patterns.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/tests/test_auth_recipes.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/tests/test_automation_invites.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/tests/test_cli_helpers.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/tests/test_cloudflare_access.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/tests/test_codespaces.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/tests/test_config.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/tests/test_credentials.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/tests/test_customer_policy_demo.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/tests/test_devcontainer_feature.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/tests/test_discovery.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/tests/test_events.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/tests/test_evidence_exporters.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/tests/test_fabric.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/tests/test_fabric_controller.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/tests/test_http_routes.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/tests/test_json_schema.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/tests/test_keycloak_oauth_demo.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/tests/test_leases.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/tests/test_manifests.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/tests/test_mcp_cache.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/tests/test_mcp_gateway_demo.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/tests/test_mcp_guide.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/tests/test_mcp_helpers.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/tests/test_mcp_impact.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/tests/test_mcp_inspection.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/tests/test_mcp_lab.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/tests/test_mcp_learn.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/tests/test_mcp_mrtr.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/tests/test_mcp_parameter_headers.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/tests/test_mcp_policy_bundles.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/tests/test_mcp_presets.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/tests/test_mcp_progress.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/tests/test_mcp_protocol.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/tests/test_mcp_proxy_demo.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/tests/test_mcp_quickstart.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/tests/test_mcp_recorder.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/tests/test_mcp_resources.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/tests/test_mcp_schema_policy.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/tests/test_mcp_schemas.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/tests/test_mcp_share.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/tests/test_mcp_spec_conformance.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/tests/test_mcp_stdio.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/tests/test_mcp_stream.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/tests/test_mcp_subscriptions.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/tests/test_mcp_tools.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/tests/test_middleware.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/tests/test_policy_bundle.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/tests/test_policy_promotion.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/tests/test_policy_state.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/tests/test_provider_policy_templates.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/tests/test_proxy.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/tests/test_redaction_audit.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/tests/test_release_qa.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/tests/test_response_policy.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/tests/test_sandbox_abuse.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/tests/test_scaffolds.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/tests/test_share_console.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/tests/test_state_snapshots.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/tests/test_tool_catalog.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/tests/test_tool_risk.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/tests/test_tunnel_audit.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/tests/test_tunnel_doctor.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/tests/test_tunnel_init.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/tests/test_upstream_transports.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/tests/test_webhook_normalization_demo.py +0 -0
- {snulbug-0.2.2 → snulbug-0.2.3}/tests/test_webhooks.py +0 -0
|
@@ -6,6 +6,18 @@ The format is based on Keep a Changelog, and this project follows semantic versi
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.2.3] - 2026-10-18
|
|
10
|
+
|
|
11
|
+
Add Pocket ID setup through the existing OAuth provider and lease machinery.
|
|
12
|
+
|
|
13
|
+
- Added `share auth init --provider pocket-id` with resource-bound API permissions, deny-by-default scope maps, PKCE/CIMD registration instructions, automation guidance, and lease-aware doctor commands.
|
|
14
|
+
- Extended auth doctor to check exact issuer metadata identity, advertised PKCE S256, and CIMD availability without claiming access to private provider grants or allowlists.
|
|
15
|
+
- Added synthetic RS256 user/bot token fixtures, rejection and JWKS rotation tests, and existing auth conformance-pack coverage; documented setup and identity-claim limitations.
|
|
16
|
+
|
|
17
|
+
Pocket ID live passkey/client interoperability remains unverified. This release
|
|
18
|
+
does not add browser console login or authentication to HTTP mounts. No change
|
|
19
|
+
to the default MCP protocol version or the experimental profile's limitations.
|
|
20
|
+
|
|
9
21
|
## [0.2.2] - 2026-10-16
|
|
10
22
|
|
|
11
23
|
Support always-on MCP bots with renewable automation invites, without introducing
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: snulbug
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3
|
|
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
|
|
@@ -468,7 +468,7 @@ Workflow:
|
|
|
468
468
|
- optional OAuth protected-resource mode with JWT/JWKS, token introspection, DPoP validation, and MCP auth challenges
|
|
469
469
|
- OAuth scope-to-MCP method/tool mapping for least-privilege public shares
|
|
470
470
|
- OAuth resource/audience drift checks for tunnel-safe public shares
|
|
471
|
-
- generated auth setup flows for Keycloak, Auth0, Okta, Entra, Cloudflare Access, and GitHub OIDC
|
|
471
|
+
- generated auth setup flows for Keycloak, Pocket ID, Auth0, Okta, Entra, Cloudflare Access, and GitHub OIDC
|
|
472
472
|
- composable OAuth + auth-bound task lease + Lua policy access decisions
|
|
473
473
|
- anti-passthrough credential brokering so caller OAuth tokens stop at snulbug
|
|
474
474
|
- learned least-privilege bundles from observed traffic
|
|
@@ -425,7 +425,7 @@ Workflow:
|
|
|
425
425
|
- optional OAuth protected-resource mode with JWT/JWKS, token introspection, DPoP validation, and MCP auth challenges
|
|
426
426
|
- OAuth scope-to-MCP method/tool mapping for least-privilege public shares
|
|
427
427
|
- OAuth resource/audience drift checks for tunnel-safe public shares
|
|
428
|
-
- generated auth setup flows for Keycloak, Auth0, Okta, Entra, Cloudflare Access, and GitHub OIDC
|
|
428
|
+
- generated auth setup flows for Keycloak, Pocket ID, Auth0, Okta, Entra, Cloudflare Access, and GitHub OIDC
|
|
429
429
|
- composable OAuth + auth-bound task lease + Lua policy access decisions
|
|
430
430
|
- anti-passthrough credential brokering so caller OAuth tokens stop at snulbug
|
|
431
431
|
- learned least-privilege bundles from observed traffic
|
|
@@ -5,9 +5,13 @@ snulbug has a small auth provider extension point for two related jobs:
|
|
|
5
5
|
- generate `snulbug mcp share auth init --provider ...` setup recipes
|
|
6
6
|
- map provider-specific token claims into `context.auth.provider.*` for Lua policy helpers
|
|
7
7
|
|
|
8
|
-
Built-ins cover Keycloak, Auth0, Okta, Entra, Cloudflare Access, and GitHub
|
|
8
|
+
Built-ins cover Keycloak, Pocket ID, Auth0, Okta, Entra, Cloudflare Access, and GitHub
|
|
9
9
|
Actions OIDC. External providers can register the same surface from Python.
|
|
10
10
|
|
|
11
|
+
The [Pocket ID recipe](mcp-auth-recipes.md#pocket-id) uses standard verified
|
|
12
|
+
subject/client identity and API scopes. It does not invent provider claims or
|
|
13
|
+
retrieve identity claims from ID tokens/userinfo during MCP requests.
|
|
14
|
+
|
|
11
15
|
```python
|
|
12
16
|
from collections.abc import Mapping
|
|
13
17
|
from typing import Any
|
|
@@ -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.3": {
|
|
14
|
+
"version": "0.2.3",
|
|
15
15
|
"mode": "member-agent",
|
|
16
16
|
"policy_profile": "tunnel-safe",
|
|
17
17
|
"registry": "redis://redis:6379/0",
|
|
@@ -37,6 +37,7 @@ snulbug mcp share auth recipe \
|
|
|
37
37
|
Supported providers:
|
|
38
38
|
|
|
39
39
|
- `keycloak`
|
|
40
|
+
- `pocket-id`
|
|
40
41
|
- `auth0`
|
|
41
42
|
- `okta`
|
|
42
43
|
- `okta-xaa`
|
|
@@ -51,6 +52,8 @@ provider can be validated against a live dev account:
|
|
|
51
52
|
|
|
52
53
|
- Keycloak: runnable Docker Compose demo in
|
|
53
54
|
[`examples/keycloak_oauth_demo`](../examples/keycloak_oauth_demo/README.md).
|
|
55
|
+
- Pocket ID: generated setup and synthetic RS256 token/discovery fixtures are
|
|
56
|
+
covered; a live passkey login or real-client demo has not been validated.
|
|
54
57
|
- Auth0: generated setup recipe is available; live provider demo forthcoming.
|
|
55
58
|
- Okta: generated setup recipe is available; live provider demo forthcoming.
|
|
56
59
|
- Okta XAA / MCP Enterprise-Managed Authorization: generated setup recipe is
|
|
@@ -137,9 +140,92 @@ Lua identity policy to constrain the workflow.
|
|
|
137
140
|
|
|
138
141
|
## Provider Notes
|
|
139
142
|
|
|
143
|
+
### Keycloak
|
|
144
|
+
|
|
140
145
|
Keycloak often needs an explicit audience mapper so access tokens contain the
|
|
141
146
|
MCP public URL in `aud`.
|
|
142
147
|
|
|
148
|
+
### Pocket ID
|
|
149
|
+
|
|
150
|
+
Use Pocket ID as the external issuer, with snulbug as its protected API:
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
snulbug mcp share auth init \
|
|
154
|
+
--provider pocket-id \
|
|
155
|
+
--url https://share.example.com/mcp \
|
|
156
|
+
--issuer https://id.example.com \
|
|
157
|
+
--client-id "$POCKET_ID_CLIENT_ID"
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Use the actual client ID from Pocket ID, not the client's display name. Omit
|
|
161
|
+
`--client-id` to generate instructions before registering a client.
|
|
162
|
+
|
|
163
|
+
This writes `.snulbug/auth/pocket-id/` with the normal five setup files. Review
|
|
164
|
+
the generated README and merge `snulbug.auth.toml` into your existing share
|
|
165
|
+
config; the overlay is not a standalone upstream/policy configuration. From a
|
|
166
|
+
source checkout, prefix these commands with `uv run`.
|
|
167
|
+
|
|
168
|
+
In Pocket ID, create an **API** with resource `https://share.example.com/mcp`,
|
|
169
|
+
then add the permission keys listed in the generated setup. Grant those
|
|
170
|
+
permissions to the MCP client under **User-delegated access**. Public clients
|
|
171
|
+
use authorization code with PKCE S256 and their exact callback URLs. The client
|
|
172
|
+
requests `resource=https://share.example.com/mcp` and `openid` plus the API
|
|
173
|
+
permissions. It sends the resulting **access token**, not its ID token, to
|
|
174
|
+
snulbug. See [Pocket ID APIs and permissions](https://pocket-id.org/docs/guides/apis).
|
|
175
|
+
|
|
176
|
+
`--scope` can narrow the generated permission set. `mcp:connect` is always
|
|
177
|
+
required. Default tool selectors are examples (`filesystem.read_file` and
|
|
178
|
+
`git.status`); change them to match discovered tool names. Custom permissions
|
|
179
|
+
start with empty selector lists, which grant no MCP actions until mapped. A
|
|
180
|
+
connect-only configuration does not implicitly grant all tools. Identity scopes
|
|
181
|
+
such as `openid` are not API permission keys; the client request adds `openid`
|
|
182
|
+
automatically.
|
|
183
|
+
|
|
184
|
+
For CIMD clients, Pocket ID requires an explicit metadata-document URL allowlist
|
|
185
|
+
and a separate API permission grant. Prefer exact metadata URLs. The API-wide
|
|
186
|
+
metadata-client grant applies to future allowed clients too; it is not a grant
|
|
187
|
+
to just one client. Manual registration remains supported, so disabled CIMD is
|
|
188
|
+
not a doctor failure. See [Pocket ID client metadata](https://pocket-id.org/docs/guides/client-id-metadata-documents).
|
|
189
|
+
|
|
190
|
+
Keep `lease_required = true` and give the consumer its `x-snulbug-lease` header.
|
|
191
|
+
Automation uses a separate confidential client with **Client access (M2M)**,
|
|
192
|
+
resource-bound API permissions, and an identity-bound renewable invite. Pocket
|
|
193
|
+
ID token acquisition/refresh and snulbug lease renewal remain separate consumer
|
|
194
|
+
operations. No client secret or refresh token belongs in the gateway config.
|
|
195
|
+
|
|
196
|
+
Do not assume Pocket ID groups or custom tenant claims are present in API access
|
|
197
|
+
tokens: profile/custom claims are documented for ID tokens/userinfo. This recipe
|
|
198
|
+
uses subject/client identity, scopes, and leases, without a new claim mapper or
|
|
199
|
+
userinfo fetch. Inspect actual API tokens before adding claim-dependent Lua.
|
|
200
|
+
See [Pocket ID scopes and claims](https://pocket-id.org/docs/guides/scopes-and-claims).
|
|
201
|
+
|
|
202
|
+
After starting the share, verify it with the existing doctor:
|
|
203
|
+
|
|
204
|
+
```bash
|
|
205
|
+
snulbug mcp share auth doctor \
|
|
206
|
+
--config .snulbug/shares/my-share/snulbug.toml \
|
|
207
|
+
--url https://share.example.com/mcp \
|
|
208
|
+
--token "$ACCESS_TOKEN" \
|
|
209
|
+
--header "x-snulbug-lease: $SNULBUG_LEASE"
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Doctor checks public resource/audience alignment, metadata issuer identity,
|
|
213
|
+
PKCE S256 when authorization code is advertised, reachable JWKS, and scope-map
|
|
214
|
+
tool targets. It reports CIMD advertisement but cannot inspect Pocket ID's
|
|
215
|
+
private API grants or allowlist. Global issuer `scopes_supported` is not proof
|
|
216
|
+
that a particular API permission was granted. Prove sample tokens, discovered
|
|
217
|
+
schemas, and recorded evidence with the existing [auth conformance pack](#auth-conformance-pack).
|
|
218
|
+
Token checks cover authentication on initialization; tool-scope behavior also
|
|
219
|
+
needs recorded/replayed calls. DPoP stays optional pending issuer/client testing.
|
|
220
|
+
|
|
221
|
+
Use stable HTTPS issuer and public resource URLs. If a tunnel URL changes,
|
|
222
|
+
register a replacement Pocket ID API resource, grant it to the client, and align
|
|
223
|
+
the snulbug configuration rather than accepting client-ID audiences. Pocket ID
|
|
224
|
+
API resources cannot be edited after creation. Console authentication is
|
|
225
|
+
unchanged; this recipe does not add Pocket ID console login.
|
|
226
|
+
|
|
227
|
+
### Other Providers
|
|
228
|
+
|
|
143
229
|
Auth0 should model the MCP endpoint as an API whose Identifier is the public MCP
|
|
144
230
|
URL, then issue access tokens for that audience.
|
|
145
231
|
|
|
@@ -185,3 +271,29 @@ For provider-specific Lua helper patterns, see
|
|
|
185
271
|
[`examples/provider_policy_templates`](../examples/provider_policy_templates/README.md).
|
|
186
272
|
It includes copyable Keycloak role gates, Entra app-role gates, GitHub Actions
|
|
187
273
|
workload gates, and Cloudflare Access group gates.
|
|
274
|
+
|
|
275
|
+
## Auth Conformance Pack
|
|
276
|
+
|
|
277
|
+
After recording authenticated MCP calls and discovering schemas, use the existing
|
|
278
|
+
auth conformance workflow for any OAuth provider. Set `ACCESS_TOKEN` to a valid
|
|
279
|
+
API access token and `WRONG_AUDIENCE_TOKEN` to a token for a different resource
|
|
280
|
+
or a client-bound ID token. Keep both in the environment, not checked-in files:
|
|
281
|
+
|
|
282
|
+
```bash
|
|
283
|
+
snulbug mcp share auth conformance generate \
|
|
284
|
+
--config .snulbug/shares/my-share/snulbug.toml \
|
|
285
|
+
--url https://share.example.com/mcp \
|
|
286
|
+
--schema-catalog schemas.json \
|
|
287
|
+
--log audit.jsonl --kind audit \
|
|
288
|
+
--token-env valid=ACCESS_TOKEN \
|
|
289
|
+
--denied-token-env wrong-audience=WRONG_AUDIENCE_TOKEN \
|
|
290
|
+
--output-dir .snulbug/auth-conformance
|
|
291
|
+
|
|
292
|
+
snulbug mcp share auth conformance run .snulbug/auth-conformance \
|
|
293
|
+
--header "x-snulbug-lease: $SNULBUG_LEASE"
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
Use your actual schema/audit paths. The pack stores token environment references,
|
|
297
|
+
config/schema fingerprints, and evidence summaries, not raw sample tokens. Run
|
|
298
|
+
with `--no-live-checks` for offline validation; this does not prove live login,
|
|
299
|
+
private issuer grants, or client interoperability.
|
|
@@ -49,7 +49,7 @@ python -m zipfile -l dist/snulbug-*.whl
|
|
|
49
49
|
8. Install from TestPyPI in a clean environment and smoke test:
|
|
50
50
|
|
|
51
51
|
```bash
|
|
52
|
-
uvx --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ snulbug==0.2.
|
|
52
|
+
uvx --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ snulbug==0.2.3 --help
|
|
53
53
|
```
|
|
54
54
|
|
|
55
55
|
9. Run the manual `Publish` workflow with `repository = pypi` when the TestPyPI
|
|
@@ -57,4 +57,4 @@ uvx --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org
|
|
|
57
57
|
10. Create the GitHub release and tag after the PyPI package is available.
|
|
58
58
|
|
|
59
59
|
The `Publish` workflow uses `workflow_dispatch`, not a tag trigger. Creating or
|
|
60
|
-
pushing `v0.2.
|
|
60
|
+
pushing `v0.2.3` does not publish a package or automatically create a GitHub release.
|
{snulbug-0.2.2 → snulbug-0.2.3}/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.3": {
|
|
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.3": {
|
|
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.3",
|
|
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",
|
|
@@ -4,6 +4,7 @@ import json
|
|
|
4
4
|
from collections.abc import Mapping, Sequence
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
from typing import Any
|
|
7
|
+
from urllib.parse import urlsplit
|
|
7
8
|
|
|
8
9
|
from .auth_providers import (
|
|
9
10
|
AuthProvider,
|
|
@@ -19,6 +20,11 @@ DEFAULT_AUTH_SCOPES = (
|
|
|
19
20
|
"mcp:tool.files.read",
|
|
20
21
|
"mcp:tool.git.status",
|
|
21
22
|
)
|
|
23
|
+
_DEFAULT_AUTH_SCOPE_MAP = {
|
|
24
|
+
"mcp:tools.read": ("tools/list", "resources/list"),
|
|
25
|
+
"mcp:tool.files.read": ("tools/call:filesystem.read_file",),
|
|
26
|
+
"mcp:tool.git.status": ("tools/call:git.status",),
|
|
27
|
+
}
|
|
22
28
|
ENTERPRISE_MANAGED_AUTH_EXTENSION = "io.modelcontextprotocol/enterprise-managed-authorization"
|
|
23
29
|
|
|
24
30
|
DEFAULT_AUTH_INIT_ROOT = Path(".snulbug/auth")
|
|
@@ -147,6 +153,15 @@ def _register_builtin_auth_recipe_providers() -> None:
|
|
|
147
153
|
title="Keycloak",
|
|
148
154
|
docs=("https://www.keycloak.org/docs/latest/server_admin/#_clients",),
|
|
149
155
|
),
|
|
156
|
+
_OAuthRecipeProvider(
|
|
157
|
+
name="pocket-id",
|
|
158
|
+
title="Pocket ID",
|
|
159
|
+
docs=(
|
|
160
|
+
"https://pocket-id.org/docs/guides/apis",
|
|
161
|
+
"https://pocket-id.org/docs/guides/client-id-metadata-documents",
|
|
162
|
+
"https://pocket-id.org/docs/guides/scopes-and-claims",
|
|
163
|
+
),
|
|
164
|
+
),
|
|
150
165
|
_OAuthRecipeProvider(
|
|
151
166
|
name="auth0",
|
|
152
167
|
title="Auth0",
|
|
@@ -405,6 +420,30 @@ def _oauth_provider_recipe(
|
|
|
405
420
|
auth_server_id: str | None,
|
|
406
421
|
scopes: Sequence[str],
|
|
407
422
|
) -> dict[str, Any]:
|
|
423
|
+
if provider == "pocket-id":
|
|
424
|
+
for name, value in (("issuer", issuer), ("public MCP URL", public_url)):
|
|
425
|
+
parsed = urlsplit(value)
|
|
426
|
+
local = parsed.hostname in {"localhost", "127.0.0.1", "::1"}
|
|
427
|
+
if (
|
|
428
|
+
not parsed.hostname
|
|
429
|
+
or parsed.scheme not in {"http", "https"}
|
|
430
|
+
or (parsed.scheme != "https" and not local)
|
|
431
|
+
or parsed.username is not None
|
|
432
|
+
or parsed.password is not None
|
|
433
|
+
or parsed.query
|
|
434
|
+
or parsed.fragment
|
|
435
|
+
):
|
|
436
|
+
raise ValueError(
|
|
437
|
+
f"Pocket ID {name} must be an absolute HTTPS URL (HTTP only for localhost), "
|
|
438
|
+
"without credentials, query, or fragment"
|
|
439
|
+
)
|
|
440
|
+
identity_scopes = {"openid", "profile", "email", "groups", "offline_access"}.intersection(scopes)
|
|
441
|
+
if identity_scopes:
|
|
442
|
+
raise ValueError(
|
|
443
|
+
"Pocket ID --scope values must be API permission keys, not identity scopes; "
|
|
444
|
+
"openid is added to the client request automatically"
|
|
445
|
+
)
|
|
446
|
+
scopes = _unique_strings(["mcp:connect", *scopes])
|
|
408
447
|
provider_steps = _oauth_provider_steps(
|
|
409
448
|
provider,
|
|
410
449
|
public_url=public_url,
|
|
@@ -416,7 +455,7 @@ def _oauth_provider_recipe(
|
|
|
416
455
|
auth_server_id=auth_server_id,
|
|
417
456
|
scopes=scopes,
|
|
418
457
|
)
|
|
419
|
-
|
|
458
|
+
recipe = {
|
|
420
459
|
"ok": True,
|
|
421
460
|
"kind": "snulbug.auth.recipe",
|
|
422
461
|
"provider": provider,
|
|
@@ -452,6 +491,23 @@ def _oauth_provider_recipe(
|
|
|
452
491
|
"notes": _oauth_provider_notes(provider, public_url=public_url, audience=audience),
|
|
453
492
|
"docs": list(docs),
|
|
454
493
|
}
|
|
494
|
+
if provider == "pocket-id":
|
|
495
|
+
if audience != public_url:
|
|
496
|
+
raise ValueError("Pocket ID audience must equal the public MCP URL used as the API resource")
|
|
497
|
+
# Keep an explicit, deny-by-default map even for connect-only or custom permissions.
|
|
498
|
+
scope_map = {scope: _DEFAULT_AUTH_SCOPE_MAP.get(scope, ()) for scope in scopes}
|
|
499
|
+
recipe["snulbug_config"] = _oauth_snulbug_config(
|
|
500
|
+
public_url=public_url, issuer=issuer, audience=audience, scopes=scopes, scope_map=scope_map
|
|
501
|
+
)
|
|
502
|
+
recipe["client_request"].update(
|
|
503
|
+
resource=public_url,
|
|
504
|
+
scopes=_unique_strings(["openid", *scopes]),
|
|
505
|
+
grant_type="authorization_code",
|
|
506
|
+
code_challenge_method="S256",
|
|
507
|
+
token_endpoint_auth_method="none",
|
|
508
|
+
)
|
|
509
|
+
recipe["commands"]["doctor"] += ' --header "x-snulbug-lease: ${SNULBUG_LEASE}"'
|
|
510
|
+
return recipe
|
|
455
511
|
|
|
456
512
|
|
|
457
513
|
def _cloudflare_access_recipe(
|
|
@@ -627,8 +683,14 @@ def _oauth_snulbug_config(
|
|
|
627
683
|
issuer: str,
|
|
628
684
|
audience: str,
|
|
629
685
|
scopes: Sequence[str],
|
|
686
|
+
scope_map: Mapping[str, Sequence[str]] | None = None,
|
|
630
687
|
) -> str:
|
|
631
688
|
scope_list = _toml_string_list(scopes)
|
|
689
|
+
if scope_map is None:
|
|
690
|
+
scope_map = _DEFAULT_AUTH_SCOPE_MAP
|
|
691
|
+
mappings = "\n".join(
|
|
692
|
+
f"{json.dumps(scope)} = {_toml_string_list(selectors)}" for scope, selectors in scope_map.items()
|
|
693
|
+
)
|
|
632
694
|
return f"""[mcp.proxy]
|
|
633
695
|
tunnel_public_url = {json.dumps(public_url)}
|
|
634
696
|
lease_required = true
|
|
@@ -647,9 +709,7 @@ strip_authorization_upstream = true
|
|
|
647
709
|
dpop_mode = "optional" # set to "required" for DPoP-only clients
|
|
648
710
|
|
|
649
711
|
[mcp.auth.scope_map]
|
|
650
|
-
|
|
651
|
-
"mcp:tool.files.read" = ["tools/call:filesystem.read_file"]
|
|
652
|
-
"mcp:tool.git.status" = ["tools/call:git.status"]
|
|
712
|
+
{mappings}
|
|
653
713
|
"""
|
|
654
714
|
|
|
655
715
|
|
|
@@ -735,6 +795,28 @@ def _oauth_provider_steps(
|
|
|
735
795
|
scopes: Sequence[str],
|
|
736
796
|
) -> list[str]:
|
|
737
797
|
scope_text = ", ".join(f"`{scope}`" for scope in scopes)
|
|
798
|
+
if provider == "pocket-id":
|
|
799
|
+
return [
|
|
800
|
+
f"Under Administration -> APIs, add an API named snulbug with resource `{public_url}`. "
|
|
801
|
+
"Use a stable public MCP URL without a trailing slash; Pocket ID normalizes trailing slashes.",
|
|
802
|
+
f"Add API permission keys: {scope_text}. These are API permissions, not identity/profile scopes.",
|
|
803
|
+
f"Create or choose an OIDC client for the MCP client{_client_suffix(client_id)}. "
|
|
804
|
+
"For a desktop/public client, use authorization code with PKCE S256, no client secret, "
|
|
805
|
+
"and the client's exact callback URLs.",
|
|
806
|
+
"On the client's Access tab, add the snulbug API under User-delegated access and select "
|
|
807
|
+
"the permissions it may request. Restrict allowed user groups as appropriate.",
|
|
808
|
+
f"Request `resource={public_url}` and `openid` plus the needed API permissions. "
|
|
809
|
+
"Send the access token to snulbug, never the ID token.",
|
|
810
|
+
"For Client ID Metadata Document (CIMD) clients, allowlist exact HTTPS metadata document URLs "
|
|
811
|
+
"under Application Configuration -> OIDC. CIMD is disabled by default. Separately grant API "
|
|
812
|
+
"permissions to metadata-document clients under Administration -> APIs; this grant covers "
|
|
813
|
+
"current and future allowed metadata-document clients, so keep the allowlist narrow.",
|
|
814
|
+
"For an automation bot, use a separate confidential OIDC client with Client access (M2M) "
|
|
815
|
+
"to the API. Request grant_type=client_credentials with the same resource and needed "
|
|
816
|
+
"permissions. Keep its secret or federated client assertion in the consumer, not snulbug.",
|
|
817
|
+
"Review the generated scope_map selectors against discovered tool names. Empty selector "
|
|
818
|
+
"lists grant no MCP actions; add concrete selectors for custom permissions before using them.",
|
|
819
|
+
]
|
|
738
820
|
if provider == "keycloak":
|
|
739
821
|
return [
|
|
740
822
|
f"In realm `{realm or '<realm>'}`, create or choose an OpenID Connect client for the MCP client.",
|
|
@@ -787,6 +869,28 @@ def _oauth_provider_notes(provider: str, *, public_url: str, audience: str) -> l
|
|
|
787
869
|
)
|
|
788
870
|
if provider == "keycloak":
|
|
789
871
|
notes.append("Keycloak often needs an explicit audience mapper for access-token `aud` values.")
|
|
872
|
+
if provider == "pocket-id":
|
|
873
|
+
notes.extend(
|
|
874
|
+
[
|
|
875
|
+
"Use a stable HTTPS Pocket ID issuer reachable from both the MCP client and snulbug. "
|
|
876
|
+
"Set --issuer or --domain to its APP_URL, not a discovery-document or token-endpoint URL.",
|
|
877
|
+
"A normal OIDC login token targets the client ID. Only an API access token requested with "
|
|
878
|
+
"the MCP resource has the required audience; do not relax audience validation.",
|
|
879
|
+
"Pocket ID custom claims are in ID tokens/userinfo, not access tokens. Do not assume "
|
|
880
|
+
"groups or tenant claims are available to Lua; inspect an API access token first. "
|
|
881
|
+
"This recipe uses verified subject/client identity, API scopes, and leases.",
|
|
882
|
+
"OAuth does not replace a snulbug lease. Supply x-snulbug-lease for MCP calls; bind "
|
|
883
|
+
"renewable automation invites to the verified issuer and subject or client ID. OAuth "
|
|
884
|
+
"token refresh/acquisition and snulbug lease renewal are separate consumer operations.",
|
|
885
|
+
"Doctor checks metadata, JWKS, public resource/audience alignment, and discovered scope-map "
|
|
886
|
+
"targets. It cannot inspect Pocket ID's private API grants or CIMD allowlist; prove actual "
|
|
887
|
+
"permissions with sample tokens in the existing share auth conformance workflow.",
|
|
888
|
+
"DPoP is optional in the generated config. Require it only after verifying issuer/client "
|
|
889
|
+
"support. Console login remains protected by the console secret, not Pocket ID.",
|
|
890
|
+
"This is a generated setup recipe with synthetic token fixture coverage, not a live "
|
|
891
|
+
"Pocket ID or real-client interoperability certification.",
|
|
892
|
+
]
|
|
893
|
+
)
|
|
790
894
|
return notes
|
|
791
895
|
|
|
792
896
|
|
|
@@ -804,6 +908,8 @@ def _provider_issuer(
|
|
|
804
908
|
if provider == "keycloak":
|
|
805
909
|
base = _https_domain(domain or "KEYCLOAK.example")
|
|
806
910
|
return f"{base}/realms/{realm or 'YOUR_REALM'}"
|
|
911
|
+
if provider == "pocket-id":
|
|
912
|
+
return _https_domain(domain or "POCKET_ID.example")
|
|
807
913
|
if provider == "auth0":
|
|
808
914
|
return f"{_https_domain(domain or 'YOUR_TENANT.auth0.com')}/"
|
|
809
915
|
if provider == "okta":
|
|
@@ -825,13 +931,14 @@ def _auth_recipe_commands(public_url: str) -> dict[str, str]:
|
|
|
825
931
|
def _auth_init_commands(recipe: Mapping[str, Any], *, directory: Path) -> dict[str, str]:
|
|
826
932
|
public_url = str(recipe.get("public_url") or "")
|
|
827
933
|
config = directory / "snulbug.auth.toml"
|
|
934
|
+
doctor = _mapping(recipe.get("commands")).get("doctor") or (
|
|
935
|
+
f"uv run snulbug mcp share auth doctor --config snulbug.toml --url {public_url} --token $ACCESS_TOKEN"
|
|
936
|
+
)
|
|
828
937
|
return {
|
|
829
938
|
"review": f"less {directory / 'README.md'}",
|
|
830
939
|
"show_config": f"cat {config}",
|
|
831
940
|
"run": "uv run snulbug mcp share run --config snulbug.toml",
|
|
832
|
-
"doctor": (
|
|
833
|
-
f"uv run snulbug mcp share auth doctor --config snulbug.toml --url {public_url} --token $ACCESS_TOKEN"
|
|
834
|
-
),
|
|
941
|
+
"doctor": str(doctor),
|
|
835
942
|
}
|
|
836
943
|
|
|
837
944
|
|
|
@@ -2348,6 +2348,9 @@ def doctor_mcp_share_auth(
|
|
|
2348
2348
|
details={"attempted_urls": issuer_urls},
|
|
2349
2349
|
)
|
|
2350
2350
|
|
|
2351
|
+
if issuer_metadata:
|
|
2352
|
+
_add_auth_issuer_metadata_checks(checks, auth, issuer_metadata, recommendations)
|
|
2353
|
+
|
|
2351
2354
|
_add_jwks_or_introspection_check(
|
|
2352
2355
|
checks,
|
|
2353
2356
|
issuer_metadata,
|
|
@@ -4007,6 +4010,69 @@ def _issuer_metadata_urls(auth: Mapping[str, Any]) -> list[str]:
|
|
|
4007
4010
|
return _unique_strings(urls)
|
|
4008
4011
|
|
|
4009
4012
|
|
|
4013
|
+
def _add_auth_issuer_metadata_checks(
|
|
4014
|
+
checks: list[dict[str, Any]],
|
|
4015
|
+
auth: Mapping[str, Any],
|
|
4016
|
+
metadata: Mapping[str, Any],
|
|
4017
|
+
recommendations: list[str],
|
|
4018
|
+
) -> None:
|
|
4019
|
+
configured_issuer = auth.get("issuer")
|
|
4020
|
+
expected = (
|
|
4021
|
+
[configured_issuer]
|
|
4022
|
+
if isinstance(configured_issuer, str) and configured_issuer
|
|
4023
|
+
else _auth_issuer_candidates(auth)
|
|
4024
|
+
)
|
|
4025
|
+
issuer = metadata.get("issuer")
|
|
4026
|
+
issuer_ok = isinstance(issuer, str) and bool(issuer) and issuer in expected
|
|
4027
|
+
_add_share_doctor_check(
|
|
4028
|
+
checks,
|
|
4029
|
+
"auth.issuer_metadata.issuer",
|
|
4030
|
+
issuer_ok,
|
|
4031
|
+
"issuer metadata identifies the configured authorization server"
|
|
4032
|
+
if issuer_ok
|
|
4033
|
+
else "issuer metadata issuer is missing or does not exactly match the configured authorization server",
|
|
4034
|
+
component="auth",
|
|
4035
|
+
details={"issuer": issuer, "expected": expected},
|
|
4036
|
+
)
|
|
4037
|
+
if not issuer_ok:
|
|
4038
|
+
recommendations.append(
|
|
4039
|
+
"Align mcp.auth.issuer with the issuer's canonical external URL (APP_URL for Pocket ID); "
|
|
4040
|
+
"issuer identifiers must match exactly, including trailing slashes."
|
|
4041
|
+
)
|
|
4042
|
+
|
|
4043
|
+
code_flow = "authorization_code" in _sequence(metadata.get("grant_types_supported")) or "code" in _sequence(
|
|
4044
|
+
metadata.get("response_types_supported")
|
|
4045
|
+
)
|
|
4046
|
+
pkce_methods = _sequence(metadata.get("code_challenge_methods_supported"))
|
|
4047
|
+
pkce_ok = "S256" in pkce_methods
|
|
4048
|
+
_add_share_doctor_check(
|
|
4049
|
+
checks,
|
|
4050
|
+
"auth.issuer_metadata.pkce_s256",
|
|
4051
|
+
pkce_ok if code_flow else None,
|
|
4052
|
+
"authorization server advertises PKCE S256"
|
|
4053
|
+
if pkce_ok
|
|
4054
|
+
else "authorization code flow is advertised without PKCE S256"
|
|
4055
|
+
if code_flow
|
|
4056
|
+
else "authorization code support is not advertised; PKCE check skipped",
|
|
4057
|
+
component="auth",
|
|
4058
|
+
details={"code_challenge_methods_supported": pkce_methods},
|
|
4059
|
+
)
|
|
4060
|
+
if code_flow and not pkce_ok:
|
|
4061
|
+
recommendations.append("Enable PKCE S256 on the authorization server for public MCP clients.")
|
|
4062
|
+
|
|
4063
|
+
cimd = metadata.get("client_id_metadata_document_supported") is True
|
|
4064
|
+
_add_share_doctor_check(
|
|
4065
|
+
checks,
|
|
4066
|
+
"auth.issuer_metadata.cimd",
|
|
4067
|
+
True if cimd else None,
|
|
4068
|
+
"authorization server advertises Client ID Metadata Document support; verify its allowlist and API grants"
|
|
4069
|
+
if cimd
|
|
4070
|
+
else "CIMD is not advertised; register clients manually or enable explicitly allowed metadata documents",
|
|
4071
|
+
component="auth",
|
|
4072
|
+
details={"client_id_metadata_document_supported": cimd},
|
|
4073
|
+
)
|
|
4074
|
+
|
|
4075
|
+
|
|
4010
4076
|
def _add_jwks_or_introspection_check(
|
|
4011
4077
|
checks: list[dict[str, Any]],
|
|
4012
4078
|
issuer_metadata: Mapping[str, Any] | None,
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Pocket ID Auth Fixtures
|
|
2
|
+
|
|
3
|
+
These are synthetic claim and discovery-document shapes, not captured production
|
|
4
|
+
tokens or a claim of live Pocket ID interoperability. Tests sign fresh RS256 JWTs
|
|
5
|
+
with an ephemeral test key and relative expiry times. No private signing key or
|
|
6
|
+
usable provider credential is checked in.
|
|
7
|
+
|
|
8
|
+
The fixtures exercise resource-bound user and M2M access, client-bound ID token
|
|
9
|
+
rejection, scope mapping, issuer/audience checks, and expiry through snulbug's
|
|
10
|
+
existing OAuth validator. API permissions need not appear in the issuer's global
|
|
11
|
+
`scopes_supported` metadata; actual token grants are tested separately.
|
|
12
|
+
|
|
13
|
+
Provider references:
|
|
14
|
+
|
|
15
|
+
- https://pocket-id.org/docs/guides/apis
|
|
16
|
+
- https://pocket-id.org/docs/guides/scopes-and-claims
|
|
17
|
+
- https://pocket-id.org/docs/guides/client-id-metadata-documents
|
|
18
|
+
|
|
19
|
+
The live passkey/client login flow is outside this fixture suite.
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"issuer_metadata": {
|
|
3
|
+
"issuer": "https://id.example.test",
|
|
4
|
+
"authorization_endpoint": "https://id.example.test/authorize",
|
|
5
|
+
"token_endpoint": "https://id.example.test/api/oidc/token",
|
|
6
|
+
"jwks_uri": "https://id.example.test/.well-known/jwks.json",
|
|
7
|
+
"response_types_supported": ["code"],
|
|
8
|
+
"grant_types_supported": ["authorization_code", "refresh_token", "client_credentials"],
|
|
9
|
+
"code_challenge_methods_supported": ["S256"],
|
|
10
|
+
"id_token_signing_alg_values_supported": ["RS256"],
|
|
11
|
+
"scopes_supported": ["openid", "profile", "email", "groups", "offline_access"],
|
|
12
|
+
"client_id_metadata_document_supported": true
|
|
13
|
+
},
|
|
14
|
+
"cases": [
|
|
15
|
+
{
|
|
16
|
+
"name": "human-api-access",
|
|
17
|
+
"claims": {"sub": "user-123", "scope": "mcp:connect mcp:tools.read"},
|
|
18
|
+
"allowed": true,
|
|
19
|
+
"reason_code": "oauth.allowed"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"name": "automation-api-access",
|
|
23
|
+
"claims": {"sub": "client-agent-client", "scope": "mcp:connect mcp:tools.read"},
|
|
24
|
+
"allowed": true,
|
|
25
|
+
"reason_code": "oauth.allowed"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"name": "wrong-api-audience",
|
|
29
|
+
"claims": {"aud": "https://another-api.example.test", "scope": "mcp:connect mcp:tools.read"},
|
|
30
|
+
"allowed": false,
|
|
31
|
+
"reason_code": "oauth.invalid_token"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"name": "id-token-is-not-api-access",
|
|
35
|
+
"typ": "JWT",
|
|
36
|
+
"claims": {"aud": "agent-client", "nonce": "login-nonce", "groups": ["admins"]},
|
|
37
|
+
"allowed": false,
|
|
38
|
+
"reason_code": "oauth.invalid_token"
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"name": "missing-connect-permission",
|
|
42
|
+
"claims": {"scope": "mcp:tools.read"},
|
|
43
|
+
"allowed": false,
|
|
44
|
+
"reason_code": "oauth.insufficient_scope"
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"name": "missing-tools-permission",
|
|
48
|
+
"claims": {"scope": "mcp:connect"},
|
|
49
|
+
"allowed": false,
|
|
50
|
+
"reason_code": "oauth.scope_map_denied"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"name": "expired-access-token",
|
|
54
|
+
"expires_in": -3600,
|
|
55
|
+
"claims": {"scope": "mcp:connect mcp:tools.read"},
|
|
56
|
+
"allowed": false,
|
|
57
|
+
"reason_code": "oauth.invalid_token"
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"name": "wrong-issuer",
|
|
61
|
+
"claims": {"iss": "https://other-id.example.test", "scope": "mcp:connect mcp:tools.read"},
|
|
62
|
+
"allowed": false,
|
|
63
|
+
"reason_code": "oauth.invalid_token"
|
|
64
|
+
}
|
|
65
|
+
]
|
|
66
|
+
}
|