@frontmcp/skills 1.5.3 → 1.5.4

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.
@@ -54,6 +54,13 @@ To keep DCR open but authenticated instead of closed, drop `enabled: false` and
54
54
  `Authorization: Bearer <token>` header that matches it (constant-time compared), and
55
55
  requests without it get `401 invalid_token`.
56
56
 
57
+ `dcr.maxDynamicClients` (default `1000`) caps how many dynamically-registered clients
58
+ are kept in memory: once the cap is reached, further DCR registrations are **rejected**
59
+ (`503 temporarily_unavailable`) rather than evicting an existing client, so already-registered
60
+ clients — including confidential ones — are preserved. Pre-registered `dcr.clients` never
61
+ count toward it, and `0` disables dynamic registration entirely. This bounds memory growth
62
+ from unauthenticated DCR (pair it with `initialAccessToken` for authenticated admission).
63
+
57
64
  ## What This Demonstrates
58
65
 
59
66
  - Setting `dcr.enabled: false` so `POST /oauth/register` returns 404 and `registration_endpoint` is dropped from AS metadata
@@ -8,6 +8,7 @@ features:
8
8
  - "Using `mode: 'transparent'` to validate tokens from an external identity provider"
9
9
  - 'Setting `expectedAudience` to restrict which tokens are accepted'
10
10
  - 'The server fetches JWKS from `{provider}/.well-known/jwks.json` automatically'
11
+ - 'Issuer (`iss`) is validated against `provider` by default; opt out or extend with `providerConfig.verifyIssuer` / `additionalIssuers`'
11
12
  ---
12
13
 
13
14
  # Transparent JWT Validation
@@ -56,6 +57,37 @@ class Server {}
56
57
  - Using `mode: 'transparent'` to validate tokens from an external identity provider
57
58
  - Setting `expectedAudience` to restrict which tokens are accepted
58
59
  - The server fetches JWKS from `{provider}/.well-known/jwks.json` automatically
60
+ - Issuer (`iss`) is validated against `provider` by default; opt out or extend with `providerConfig.verifyIssuer` / `additionalIssuers`
61
+
62
+ ## Claim validation
63
+
64
+ A valid JWKS signature only proves the IdP signed the token — not that it was
65
+ minted for this server. Because every service behind the same IdP shares the
66
+ signing keys, transparent mode also validates two claims:
67
+
68
+ - **Issuer (`iss`)** — validated against `provider` by default (matched with or
69
+ without a trailing slash). Add trusted extras with
70
+ `providerConfig.additionalIssuers: ['https://gateway.example']`. To turn the
71
+ check off for a trusted gateway whose re-minted issuer you cannot enumerate,
72
+ set `providerConfig.verifyIssuer: false` — and keep a strict `expectedAudience`
73
+ when you do.
74
+ - **Audience (`aud`)** — when the token carries one, it must match
75
+ `expectedAudience`. Tokens without an `aud` are accepted for IdP
76
+ compatibility; set `expectedAudience` and issue audience-bound tokens for the
77
+ strictest posture.
78
+
79
+ ```typescript
80
+ auth: {
81
+ mode: 'transparent',
82
+ provider: 'https://auth.example.com',
83
+ expectedAudience: 'my-api',
84
+ providerConfig: {
85
+ // trust one extra issuer (e.g. a gateway that re-mints tokens)
86
+ additionalIssuers: ['https://gateway.example'],
87
+ // verifyIssuer: false, // ⚠️ disables the iss check entirely — last resort
88
+ },
89
+ }
90
+ ```
59
91
 
60
92
  ## Related
61
93
 
@@ -39,6 +39,8 @@ auth: {
39
39
 
40
40
  > Transparent also accepts `allowAnonymous` (default `false`) + `anonymousScopes` (default `['anonymous']`) to admit tokenless requests as anonymous, and `requiredScopes` to reject tokens missing a scope. `expectedAudience` is shared across transparent/local/remote, not transparent-only.
41
41
 
42
+ > **Claim validation (transparent):** a valid JWKS signature alone does not bind a token to this server — every service behind the same IdP shares the signing keys. FrontMCP therefore validates the token `iss` against `provider` (plus any `providerConfig.additionalIssuers`, each matched with/without a trailing slash) **by default**, and validates `aud` against `expectedAudience` when the token carries one. This blocks replay of a token minted by the same IdP for a different issuer or audience. Set `providerConfig.additionalIssuers: ['https://gateway.example']` to trust a known extra issuer. `providerConfig.verifyIssuer: false` **disables the issuer check entirely** (accepts any issuer signed by the JWKS) — only for a trusted gateway whose re-minted issuer you cannot enumerate, and always paired with a strict `expectedAudience`.
43
+
42
44
  ## Local Mode
43
45
 
44
46
  Built-in OAuth 2.1 server that signs its own JWT tokens. Full control over token lifecycle.
@@ -61,6 +63,10 @@ Signing is **HS256 with a symmetric `JWT_SECRET`** (no key pair). Set a stable `
61
63
 
62
64
  Local mode also accepts `allowDefaultPublic` (default `false` — set `true` to admit tokenless requests as anonymous instead of returning 401), `anonymousScopes` (default `['anonymous']` — scopes for those anonymous sessions), and `expectedAudience` (reject tokens minted for a different `aud`).
63
65
 
66
+ > **Client registration (security):** by default an unregistered `client_id` is accepted with whatever `redirect_uri` it presents. Set `requireRegisteredClients: true` (local/remote) to require every client to be registered (DCR / `dcr.clients`) or a CIMD client-id URL, so `redirect_uri` is exact-matched (OAuth 2.1) — this prevents auth-code interception via an attacker-chosen redirect. Confidential clients (`token_endpoint_auth_method: client_secret_basic`/`client_secret_post`) are authenticated with a constant-time `client_secret` check on both the code-exchange and refresh grants (Basic header or body param).
67
+
68
+ > **Public origin (security):** pin `FRONTMCP_PUBLIC_URL` in production. The issuer / resource / OAuth-discovery URLs and the transparent expected audience derive from it rather than from request headers; `X-Forwarded-Host`/`X-Forwarded-Proto` are ignored unless `FRONTMCP_TRUST_PROXY=1` (a trusted proxy that strips client-supplied forwarded headers).
69
+
64
70
  **Progressive / incremental authorization** (opt-in via `incrementalAuth`): when enabled, the minted token carries an `authorized_apps` claim and a `tools/call` for an app NOT in that claim resolves to a `CallToolResult` with `isError: true` and `_meta.code === 'AUTHORIZATION_REQUIRED'` (fields: `authorization_required: true`, `app`, `tool`, `auth_url`, `required_scopes`, `session_mode`, `supports_incremental`). The client declares the initial grant on `/oauth/authorize?…&apps=crm` (omit `apps` to grant all apps) and expands it later via an incremental authorize `…&mode=incremental&app=slack&apps=crm` — the new token's claim is the **union** of the prior apps plus the target (the user identity and already-granted apps are preserved; upstream tokens stay server-side). Without an `incrementalAuth` block, no claim is minted and there is **no** app-level gating (allow-all preserved). `consent` (tool-level) and `incrementalAuth` (app-level) are independent.
65
71
 
66
72
  To collect and verify your own credentials, add a declarative `login` (custom page fields / title / subject strategy) and an `authenticate(input, ctx)` verifier that returns `{ ok: true, sub?, claims? }` (custom claims are embedded in the token; reserved claims are stripped) or `{ ok: false, message }` (re-renders the login page; no code issued). Both are optional and default to the built-in email login. See `configure-auth.md` for a full example.
@@ -18,6 +18,9 @@ These checks apply to ALL deployment targets. Run them first, then proceed to yo
18
18
  - [ ] Session TTL is configured appropriately (not infinite)
19
19
  - [ ] Tool-level authorization is enforced where needed (ApprovalPlugin or custom)
20
20
  - [ ] OAuth redirect URIs are restricted to known domains
21
+ - [ ] `FRONTMCP_PUBLIC_URL` is pinned to the canonical origin — issuer / resource / OAuth-discovery URLs and the transparent-mode expected audience derive from it, not from request headers. `X-Forwarded-Host`/`X-Forwarded-Proto` are ignored by default; only set `FRONTMCP_TRUST_PROXY=1` behind a proxy that strips client-supplied forwarded headers
22
+ - [ ] `auth.requireRegisteredClients: true` (local/remote) so unknown clients can't present an attacker-chosen `redirect_uri` (auth-code interception). Clients register via DCR / `dcr.clients` / CIMD
23
+ - [ ] Transparent mode sets `auth.expectedAudience` (bind tokens to this resource) and validates issuer (`providerConfig.verifyIssuer`, default on)
21
24
 
22
25
  ### CORS Configuration
23
26
 
@@ -630,7 +630,8 @@
630
630
  "features": [
631
631
  "Using `mode: 'transparent'` to validate tokens from an external identity provider",
632
632
  "Setting `expectedAudience` to restrict which tokens are accepted",
633
- "The server fetches JWKS from `{provider}/.well-known/jwks.json` automatically"
633
+ "The server fetches JWKS from `{provider}/.well-known/jwks.json` automatically",
634
+ "Issuer (`iss`) is validated against `provider` by default; opt out or extend with `providerConfig.verifyIssuer` / `additionalIssuers`"
634
635
  ]
635
636
  }
636
637
  ]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frontmcp/skills",
3
- "version": "1.5.3",
3
+ "version": "1.5.4",
4
4
  "description": "Curated skills catalog for FrontMCP projects",
5
5
  "author": "AgentFront <info@agentfront.dev>",
6
6
  "homepage": "https://docs.agentfront.dev",