Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion dtwo/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "dtwo",
"version": "1.3.4",
"version": "1.3.5",
"description": "Manage Dtwo gateways, policies, and Rego with the Dtwo MCP server.",
"author": {
"name": "Dtwo",
Expand Down
2 changes: 1 addition & 1 deletion dtwo/.codex-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "dtwo",
"version": "1.3.4",
"version": "1.3.5",
"description": "Manage Dtwo gateways, policies, and Rego with the Dtwo MCP server.",
"author": {
"name": "Dtwo",
Expand Down
2 changes: 1 addition & 1 deletion dtwo/.cursor-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "dtwo",
"version": "1.3.4",
"version": "1.3.5",
"description": "Manage Dtwo gateways, policies, and Rego with the Dtwo MCP server.",
"author": {
"name": "Dtwo",
Expand Down
15 changes: 13 additions & 2 deletions dtwo/skills/dtwo-gateway-config/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ Human-gated session clearing (session-control) registration: the IdP app the bro

| Field | Required | Type | Default | Target | Guidance (from artifact) |
|---|---|---|---|---|---|
| `client_id` | no | string | `not declared` | `SESSION_CONTROL_CLIENT_ID` | Leave unset on a gateway using Dtwo authentication. Set it only when the gateway trusts your own IdP: the ceremony app must live in the IdP that issues the gateway's inbound tokens, so register a dedicated public client there (authorization-code + PKCE, no refresh grant) and paste its client id here. Do not reuse the gateway API client. |
| `client_id` | no | string | `not declared` | `SESSION_CONTROL_CLIENT_ID` | Leave unset on a gateway using Dtwo authentication. Set it only when the gateway trusts your own IdP: the ceremony app must live in the IdP that issues the gateway's inbound tokens, so register a dedicated public client there (authorization-code + PKCE, no refresh grant) and paste its client id here. Do not reuse the gateway API client. On Okta that is a Native app with an Okta-generated client ID, included in the custom authorization server's access policy. The gateway picks the identity claim it binds on from the issuer host (Entra, Okta or other); most gateways need nothing more. For an Okta org on a custom domain or a sovereign-cloud Entra issuer — and only then — add `SESSION_SUBJECT_PROVIDER=okta` (or `entra`) to `gateway.advanced`, or every clear fails `subject_mismatch`. |
| `redirect_uri` | no | string | `not declared` | `SESSION_CONTROL_REDIRECT_URI` | Leave unset in almost all cases — the gateway derives `<origin of the first jwt_audience>/session-control/clear/callback` and the platform registers that. Set it when a reverse proxy or split-horizon DNS puts the browser on a different origin than the audience: give the URL the browser can actually reach, at the root path (`/mcp/*` routes to the streamable-HTTP handler, which rejects browser GETs). It is required when the first `jwt_audience` is not an absolute URL — e.g. an Entra ID v2.0 gateway, whose audience is the gateway app's client ID (a GUID) — since there is no origin to derive from, and the deploy rejects an armed gateway that leaves it unset. |

**Cross-field constraints** (verbatim from artifact):
Expand Down Expand Up @@ -398,7 +398,7 @@ Every field marked `secret: true` in the artifact. Put a Vault reference there (
- **`targetKind: platform`** — value is applied as a platform-side control at the named `platform.*` path rather than written to the gateway env file.
- **`targetKind: sotwPath`** — value is written into the SOTW YAML at the named dotted path (e.g. `sotw.url`, `sotw.oauth_config.client_secret`). Read the `Target` column per field; do not infer a field's target from its section.

<!-- schema-reference.json sha256:4dc79f71108e6c02642bd5fc8463e310924d0c3c453e634cd5afc60d9b46af16 -->
<!-- schema-reference.json sha256:9133ce3ac2650844dc11ad453ed36ae929f11ab7b3df4d4842c1504292075313 -->

<!-- END SCHEMA DIGEST -->

Expand All @@ -409,6 +409,17 @@ Controls authentication, SSRF protection, `log_level`, `advanced` flags, DCR / d
- **Authentication** defaults to enabled when omitted. Every field in `gateway.authentication` — including the token-validation flags (`jwt_issuer_verification`, `jwt_audience_verification`, `require_jti`, `require_token_expiration`, `mcp_oauth_resource_metadata_enabled`) — is listed in the Schema Digest above with its target, its default, and the schema's own guidance on when to set it. Read the table there rather than guessing from this summary; most of those flags carry a gateway-applied default (their Default column reads `` `true` (gateway) ``), and where the artifact declares none (`mcp_oauth_resource_metadata_enabled` reads `not declared`) the Guidance column states the effective value.
- **Gateway-side `jwks_info` is independent of any `mcp_servers[].authentication` block.** When the prompt supplies an IdP tenant and audience (e.g. Auth0), populate `gateway.authentication.jwks_info` (`jwt_algorithm`, `jwt_jwks_uri`, `jwt_issuer`, `jwt_audience`) — even when the upstream MCP server uses OAuth/DCR, and even when the prompt says the upstream server "only supports OAuth" or "does not accept bearer tokens." Those statements describe the outbound leg to the MCP server, not the inbound leg from clients to the gateway.
- **SSRF** defaults to strict (block localhost, block private networks, fail-closed DNS) when omitted. Set `allow_private_networks: true` to permit access to `host.docker.internal` and other private addresses.
- **Human-gated clearing on your own IdP** (a gateway whose `jwks_info` points at Okta, Entra ID or another IdP rather than Dtwo authentication) needs a `session_control.client_id` for a dedicated public client in that same IdP. The digest's `client_id` guidance covers the general rule. Things users get wrong:
- **Okta:** create a **Native** OIDC app (authorization code with PKCE, no client secret, no refresh token) and choose **Okta-generated client ID**, not a Client ID Metadata Document. Add it, and every app the MCP clients sign in with, to the custom authorization server's **access policy**, or Okta refuses the sign-in before the gateway sees it.
- **Sign-in redirect URI:** register `<gateway origin>/session-control/clear/callback` on the clearing app, or whatever `session_control.redirect_uri` names. It is required when the first `jwt_audience` is not a URL (Entra ID v2.0), and when the browser reaches the gateway at a different origin than its audience.
- **Intent clears need intent capture:** set `gateway.intent.enabled: true`, or `set_intent` succeeds but nothing is stored and a clear finds nothing to clear.
- **Identity claim — most gateways set nothing.** The gateway detects Entra ID (`login.microsoftonline.com`, `sts.windows.net`) and Okta (`*.okta.com`, `*.oktapreview.com`, `*.okta-emea.com`, `*.okta-gov.com`) from the `jwt_issuer` host, treats every other issuer (Auth0 included) as standard OIDC, and binds clears on the claim that identifies the same person in both tokens. Do not add `SESSION_SUBJECT_PROVIDER` unless the issuer is an **Okta org on a custom domain** or a **sovereign-cloud Entra ID** host; for those two only, add the provider to `gateway.advanced` as a list entry, or every clear fails `subject_mismatch`. The accepted values are `auto` (the default), `oidc`, `entra` and `okta`; anything else fails the deploy.

```yaml
gateway:
advanced:
- SESSION_SUBJECT_PROVIDER=okta
```

### MCP Servers Section

Expand Down
2 changes: 1 addition & 1 deletion dtwo/skills/dtwo-gateway-config/schema-reference.json
Original file line number Diff line number Diff line change
Expand Up @@ -717,7 +717,7 @@
"deployDefault": null,
"target": "SESSION_CONTROL_CLIENT_ID",
"description": "Public client id of the dedicated session-control OAuth app (native, PKCE, no client secret). Optional on a gateway authenticating against Dtwo's Auth0: the platform provisions one per organization and renders it at deploy. An explicit value always wins.",
"rationale": "Leave unset on a gateway using Dtwo authentication. Set it only when the gateway trusts your own IdP: the ceremony app must live in the IdP that issues the gateway's inbound tokens, so register a dedicated public client there (authorization-code + PKCE, no refresh grant) and paste its client id here. Do not reuse the gateway API client.",
"rationale": "Leave unset on a gateway using Dtwo authentication. Set it only when the gateway trusts your own IdP: the ceremony app must live in the IdP that issues the gateway's inbound tokens, so register a dedicated public client there (authorization-code + PKCE, no refresh grant) and paste its client id here. Do not reuse the gateway API client. On Okta that is a Native app with an Okta-generated client ID, included in the custom authorization server's access policy. The gateway picks the identity claim it binds on from the issuer host (Entra, Okta or other); most gateways need nothing more. For an Okta org on a custom domain or a sovereign-cloud Entra issuer — and only then — add `SESSION_SUBJECT_PROVIDER=okta` (or `entra`) to `gateway.advanced`, or every clear fails `subject_mismatch`.",
"targetKind": "envVar"
},
{
Expand Down
2 changes: 1 addition & 1 deletion skill-harness/src/schemaArtifact.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export const SCHEMA_ARTIFACT_VERSION = '1.1.0';
* in reviewed source. It cannot stop a PR that bumps both the artifact and
* this constant — judging that change is still review's job.
*/
export const EXPECTED_SCHEMA_ARTIFACT_SHA256 = '4dc79f71108e6c02642bd5fc8463e310924d0c3c453e634cd5afc60d9b46af16';
export const EXPECTED_SCHEMA_ARTIFACT_SHA256 = '9133ce3ac2650844dc11ad453ed36ae929f11ab7b3df4d4842c1504292075313';

/**
* `platform` is emitted by the currently vendored artifact (every
Expand Down
2 changes: 1 addition & 1 deletion skill-harness/src/validatorBundle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export const EXPECTED_VALIDATOR_BUNDLE_VERSION = '4.0.0';
* `__tests__/schemaDigest.test.ts`, not at module load. Bump this together
* with `EXPECTED_VALIDATOR_BUNDLE_VERSION` when the bundle is re-vendored.
*/
export const EXPECTED_VALIDATOR_BUNDLE_SHA256 = '9dba8b63ab3b7ef486aa3e448048ebe8eea04fac3e26598cc75f57e112d1474f';
export const EXPECTED_VALIDATOR_BUNDLE_SHA256 = '27c46ab1263fe0b75f71e60a8a37f4f401c5699651542565859cd0843ff9868c';

const HERE = dirname(fileURLToPath(import.meta.url));

Expand Down
6 changes: 3 additions & 3 deletions skill-harness/vendor/config-validator.bundle.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -23447,7 +23447,7 @@ var SessionControlSchema = external_exports.object({
description: "Public client id of the dedicated session-control OAuth app (native, PKCE, no client secret). Optional on a gateway authenticating against Dtwo's Auth0: the platform provisions one per organization and renders it at deploy. An explicit value always wins.",
target: "SESSION_CONTROL_CLIENT_ID",
audience: "user",
rationale: "Leave unset on a gateway using Dtwo authentication. Set it only when the gateway trusts your own IdP: the ceremony app must live in the IdP that issues the gateway's inbound tokens, so register a dedicated public client there (authorization-code + PKCE, no refresh grant) and paste its client id here. Do not reuse the gateway API client."
rationale: "Leave unset on a gateway using Dtwo authentication. Set it only when the gateway trusts your own IdP: the ceremony app must live in the IdP that issues the gateway's inbound tokens, so register a dedicated public client there (authorization-code + PKCE, no refresh grant) and paste its client id here. Do not reuse the gateway API client. On Okta that is a Native app with an Okta-generated client ID, included in the custom authorization server's access policy. The gateway picks the identity claim it binds on from the issuer host (Entra, Okta or other); most gateways need nothing more. For an Okta org on a custom domain or a sovereign-cloud Entra issuer \u2014 and only then \u2014 add `SESSION_SUBJECT_PROVIDER=okta` (or `entra`) to `gateway.advanced`, or every clear fails `subject_mismatch`."
})
),
redirect_uri: external_exports.string().min(1).refine(isRenderableRedirectUri, {
Expand All @@ -23462,8 +23462,8 @@ var SessionControlSchema = external_exports.object({
),
// There is deliberately NO `issuer` field. The ceremony's issuer is always
// `gateway.authentication.jwks_info.jwt_issuer` — the callback binds the
// inbound and browser-login identities as raw `iss|<subject claim>` (`sub`, or `oid`
// on an Entra issuer), so any other value
// inbound and browser-login identities as raw `iss|<subject claim>` (`sub`, `oid`
// on an Entra issuer, `uid` against the ID token's `sub` on Okta), so any other value
// makes every ceremony deterministically fail subject_mismatch. A field
// whose only legal value is a copy of another field is pure misconfiguration
// surface, so the deploy derives SESSION_CONTROL_ISSUER from jwt_issuer
Expand Down
Loading