@kya-os/mcp-i-cloudflare 1.13.3 → 1.13.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.
@@ -9,7 +9,7 @@
9
9
  import { ConsentConfigService } from "./consent-config.service";
10
10
  import { TemplateRenderer } from "./consent-templates/template-renderer";
11
11
  import { DEFAULT_AGENTSHIELD_URL, DEFAULT_SESSION_CACHE_TTL, KEY_PAIR_TTL_SECONDS, CONSENT_PROVIDER_TYPES, } from "../constants";
12
- import { STORAGE_KEYS } from "../constants/storage-keys";
12
+ import { STORAGE_KEYS, isPendingDelegationId, } from "../constants/storage-keys";
13
13
  import { loadDay0Config, getDelegationFieldName } from "../utils/day0-config";
14
14
  import { buildProviderAuthorizeUrl } from "../runtime/oidc/authorize-url.js";
15
15
  import { resolveIdpScopes } from "../runtime/oidc/idp-scopes.js";
@@ -4045,7 +4045,34 @@ export class ConsentService {
4045
4045
  const delegationStorage = this.env.DELEGATION_STORAGE;
4046
4046
  // Task B2: normalize once (folds deprecated aliases: oauth→oauth2) so every
4047
4047
  // stored record and every read compares in the same vocabulary.
4048
- const normalizedAuthorizationType = normalizeAuthType(authorizationType);
4048
+ //
4049
+ // SECURITY — the authoritative pre-consent guard. This method is the single
4050
+ // choke point through which an authorization type reaches BOTH destinations
4051
+ // the enforcement gate can read: the Durable Object record
4052
+ // (`storeDelegationViaDO` below) and the KV sidecar. A `pending_` delegation
4053
+ // id means the OAuth callback parked a placeholder whose token is still the
4054
+ // raw IdP access token — the real delegation is only minted once the user
4055
+ // approves on the clickwrap. Recording a type for it would let the gate
4056
+ // treat a pre-consent token as a satisfying delegation and run a protected
4057
+ // tool before the user ever consented.
4058
+ //
4059
+ // Guarding HERE rather than at each caller is deliberate: `oauth-handler.ts`
4060
+ // reaches this via `storeDelegationForOAuth` with a hardcoded "oauth2" and
4061
+ // the pending tokenData, so a per-call-site guard would have to be repeated
4062
+ // at every present and future writer. Dropping the type to undefined makes
4063
+ // every downstream consumer fail safe, because absent already means
4064
+ // "reject" at the gate.
4065
+ const isPendingDelegation = isPendingDelegationId(delegationId);
4066
+ const normalizedAuthorizationType = isPendingDelegation
4067
+ ? undefined
4068
+ : normalizeAuthType(authorizationType);
4069
+ if (isPendingDelegation && authorizationType) {
4070
+ logger.warn("[ConsentService] Refusing to record an authorization type for a pending (pre-consent) delegation", {
4071
+ delegationId,
4072
+ requestedAuthorizationType: authorizationType,
4073
+ note: "The real delegation is minted after clickwrap approval; typing this token would authorize it pre-consent.",
4074
+ });
4075
+ }
4049
4076
  // Resolve userDid if not provided
4050
4077
  let userDid = providedUserDid;
4051
4078
  if (!userDid && delegationStorage) {
@@ -4079,6 +4106,36 @@ export class ConsentService {
4079
4106
  await delegationStorage.put(userAgentKey, token, {
4080
4107
  expirationTtl: ttl,
4081
4108
  });
4109
+ // Record the auth type alongside it (separate key — the value above
4110
+ // is a bare token that several readers consume as the JWT itself).
4111
+ // Without this, a delegation recovered via the KV-legacy path in
4112
+ // agent.ts carries no type and can never satisfy a typed tool, so a
4113
+ // DO miss becomes a permanent re-challenge loop rather than a
4114
+ // one-time re-auth.
4115
+ //
4116
+ // INVARIANT: a sidecar present ⇒ it describes the token currently
4117
+ // under the key above. So an untyped write must CLEAR any existing
4118
+ // sidecar rather than leave it: the token was just replaced, and a
4119
+ // leftover type from a previous mint would be inherited by the new
4120
+ // (weaker or unknown) token on the next KV read. Revocation already
4121
+ // deletes the sidecar for exactly this reason; a remint must too.
4122
+ const authTypeKey = STORAGE_KEYS.delegationAuthType(userDid, agentDid);
4123
+ if (normalizedAuthorizationType) {
4124
+ await delegationStorage.put(authTypeKey, normalizedAuthorizationType, { expirationTtl: ttl });
4125
+ }
4126
+ else {
4127
+ // Clearing a stale sidecar is cleanup, not the primary write: it
4128
+ // must never abort the session-key write that follows. Swallow its
4129
+ // failure rather than letting it escape to the enclosing catch.
4130
+ // Failing to clear is safe in the conservative direction only for
4131
+ // the token it described, so it is logged rather than ignored.
4132
+ try {
4133
+ await delegationStorage.delete?.(authTypeKey);
4134
+ }
4135
+ catch (clearError) {
4136
+ logger.warn("[ConsentService] Could not clear stale delegation auth-type sidecar", clearError);
4137
+ }
4138
+ }
4082
4139
  logger.debug("[ConsentService] Token also stored to KV (user+agent key)");
4083
4140
  }
4084
4141
  // Store session key (for session data preservation)