@frontmcp/adapters 1.11.1-rc.1 → 1.11.1-rc.2
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.
- package/esm/package.json +6 -6
- package/esm/skills/index.mjs +1 -1
- package/package.json +6 -6
- package/skills/audit/audit-record.types.d.ts +16 -0
- package/skills/audit/audit-record.types.d.ts.map +1 -1
- package/skills/audit/audit-writer.d.ts +3 -1
- package/skills/audit/audit-writer.d.ts.map +1 -1
- package/skills/audit/index.d.ts +1 -1
- package/skills/audit/index.d.ts.map +1 -1
- package/skills/bundle/bundle-version.d.ts +11 -0
- package/skills/bundle/bundle-version.d.ts.map +1 -0
- package/skills/index.d.ts +5 -3
- package/skills/index.d.ts.map +1 -1
- package/skills/index.js +1 -1
- package/skills/security/replay-nonce-store.d.ts +47 -0
- package/skills/security/replay-nonce-store.d.ts.map +1 -0
- package/skills/security/webhook-replay-guard.d.ts +12 -12
- package/skills/security/webhook-replay-guard.d.ts.map +1 -1
- package/skills/source-options.d.ts +7 -0
- package/skills/source-options.d.ts.map +1 -1
- package/skills/sources/index.d.ts +3 -3
- package/skills/sources/index.d.ts.map +1 -1
- package/skills/sources/saas-pull.source.d.ts +60 -21
- package/skills/sources/saas-pull.source.d.ts.map +1 -1
- package/skills/sources/skill-bundle-source.interface.d.ts +49 -4
- package/skills/sources/skill-bundle-source.interface.d.ts.map +1 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { type ReplayNonceStore } from './replay-nonce-store';
|
|
1
2
|
/** Why a check refused: also the `reason` label of `frontmcp_skills_replay_rejects_total`. */
|
|
2
|
-
export type ReplayRejectCode = 'invalid_timestamp' | 'invalid_nonce' | 'outside_window' | 'nonce_replay';
|
|
3
|
+
export type ReplayRejectCode = 'invalid_timestamp' | 'invalid_nonce' | 'outside_window' | 'nonce_replay' | 'store_unavailable';
|
|
3
4
|
export interface ReplayCheckResult {
|
|
4
5
|
ok: boolean;
|
|
5
6
|
reason?: string;
|
|
@@ -19,8 +20,13 @@ export interface ReplayGuardTelemetry {
|
|
|
19
20
|
export interface WebhookReplayGuardOptions {
|
|
20
21
|
/** Allowed clock skew window in ms (default 300_000 = 5min). */
|
|
21
22
|
windowMs?: number;
|
|
22
|
-
/** Max nonces
|
|
23
|
+
/** Max nonces the default in-memory store tracks simultaneously (default 5_000). */
|
|
23
24
|
capacity?: number;
|
|
25
|
+
/**
|
|
26
|
+
* Where the accepted nonces are recorded. Default: this process's memory, which replicas and Worker isolates don't
|
|
27
|
+
* share. Give every instance that serves the server the same store.
|
|
28
|
+
*/
|
|
29
|
+
store?: ReplayNonceStore;
|
|
24
30
|
/**
|
|
25
31
|
* Optional telemetry hook. When provided, every `check()` rejection is
|
|
26
32
|
* recorded against `frontmcp_skills_replay_rejects_total` with a low-cardinality
|
|
@@ -30,8 +36,7 @@ export interface WebhookReplayGuardOptions {
|
|
|
30
36
|
}
|
|
31
37
|
export declare class WebhookReplayGuard {
|
|
32
38
|
private readonly windowMs;
|
|
33
|
-
private readonly
|
|
34
|
-
private readonly seen;
|
|
39
|
+
private readonly store;
|
|
35
40
|
private now;
|
|
36
41
|
private readonly rejectsCounter;
|
|
37
42
|
private readonly checksCounter;
|
|
@@ -39,17 +44,12 @@ export declare class WebhookReplayGuard {
|
|
|
39
44
|
/** For tests: override the time source. */
|
|
40
45
|
setNowProvider(fn: () => number): void;
|
|
41
46
|
/**
|
|
42
|
-
* Check that a (timestamp, nonce) pair is fresh and unseen. Returns ok=false
|
|
43
|
-
*
|
|
44
|
-
* been observed within that window.
|
|
47
|
+
* Check that a (timestamp, nonce) pair is fresh and unseen, and record the nonce. Returns ok=false if the timestamp
|
|
48
|
+
* is outside the freshness window, the nonce was already recorded within that window, or the store failed.
|
|
45
49
|
*/
|
|
46
50
|
check(args: {
|
|
47
51
|
timestampMs: number;
|
|
48
52
|
nonce: string;
|
|
49
|
-
}): ReplayCheckResult
|
|
50
|
-
/** Test/maintenance helper: drop expired entries proactively. */
|
|
51
|
-
prune(): number;
|
|
52
|
-
/** Inspect: number of nonces currently tracked. */
|
|
53
|
-
get size(): number;
|
|
53
|
+
}): Promise<ReplayCheckResult>;
|
|
54
54
|
}
|
|
55
55
|
//# sourceMappingURL=webhook-replay-guard.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"webhook-replay-guard.d.ts","sourceRoot":"","sources":["../../../src/skills/security/webhook-replay-guard.ts"],"names":[],"mappings":"AAMA,8FAA8F;AAC9F,MAAM,MAAM,gBAAgB,
|
|
1
|
+
{"version":3,"file":"webhook-replay-guard.d.ts","sourceRoot":"","sources":["../../../src/skills/security/webhook-replay-guard.ts"],"names":[],"mappings":"AAMA,OAAO,EAA0B,KAAK,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAErF,8FAA8F;AAC9F,MAAM,MAAM,gBAAgB,GACxB,mBAAmB,GACnB,eAAe,GACf,gBAAgB,GAChB,cAAc,GACd,mBAAmB,CAAC;AAExB,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,gBAAgB,CAAC;CACzB;AAED;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC,GAAG,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC;CAC7D;AACD,MAAM,WAAW,oBAAoB;IACnC,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,kBAAkB,CAAC;CACvE;AAED,MAAM,WAAW,yBAAyB;IACxC,gEAAgE;IAChE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,oFAAoF;IACpF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,KAAK,CAAC,EAAE,gBAAgB,CAAC;IACzB;;;;OAIG;IACH,SAAS,CAAC,EAAE,oBAAoB,CAAC;CAClC;AAID,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAmB;IACzC,OAAO,CAAC,GAAG,CAA0B;IACrC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAiC;IAChE,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAiC;gBAEnD,OAAO,GAAE,yBAA8B;IAwBnD,2CAA2C;IAC3C,cAAc,CAAC,EAAE,EAAE,MAAM,MAAM,GAAG,IAAI;IAItC;;;OAGG;IACG,KAAK,CAAC,IAAI,EAAE;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,iBAAiB,CAAC;CAiCtF"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from '@frontmcp/lazy-zod';
|
|
2
|
+
import type { StorageConfig } from '@frontmcp/utils';
|
|
2
3
|
export declare const staticSourceSchema: import("@frontmcp/lazy-zod").ZodObject<{
|
|
3
4
|
type: import("@frontmcp/lazy-zod").ZodLiteral<"static">;
|
|
4
5
|
path: import("@frontmcp/lazy-zod").ZodString;
|
|
@@ -17,6 +18,9 @@ export declare const saasSourceSchema: import("@frontmcp/lazy-zod").ZodObject<{
|
|
|
17
18
|
expectedAudience: import("@frontmcp/lazy-zod").ZodString;
|
|
18
19
|
pollIntervalMs: import("@frontmcp/lazy-zod").ZodDefault<import("@frontmcp/lazy-zod").ZodNumber>;
|
|
19
20
|
enableWebhook: import("@frontmcp/lazy-zod").ZodDefault<import("@frontmcp/lazy-zod").ZodBoolean>;
|
|
21
|
+
requirePushBodyDigest: import("@frontmcp/lazy-zod").ZodOptional<import("@frontmcp/lazy-zod").ZodBoolean>;
|
|
22
|
+
allowRollback: import("@frontmcp/lazy-zod").ZodOptional<import("@frontmcp/lazy-zod").ZodBoolean>;
|
|
23
|
+
pushReplayStorage: import("@frontmcp/lazy-zod").ZodOptional<import("zod").ZodCustom<StorageConfig, StorageConfig>>;
|
|
20
24
|
jwksUrl: import("@frontmcp/lazy-zod").ZodString;
|
|
21
25
|
expectedIssuer: import("@frontmcp/lazy-zod").ZodString;
|
|
22
26
|
}, import("zod/v4/core").$strip>;
|
|
@@ -40,6 +44,9 @@ export declare const bundleSourceSchema: import("@frontmcp/lazy-zod").ZodDiscrim
|
|
|
40
44
|
expectedAudience: import("@frontmcp/lazy-zod").ZodString;
|
|
41
45
|
pollIntervalMs: import("@frontmcp/lazy-zod").ZodDefault<import("@frontmcp/lazy-zod").ZodNumber>;
|
|
42
46
|
enableWebhook: import("@frontmcp/lazy-zod").ZodDefault<import("@frontmcp/lazy-zod").ZodBoolean>;
|
|
47
|
+
requirePushBodyDigest: import("@frontmcp/lazy-zod").ZodOptional<import("@frontmcp/lazy-zod").ZodBoolean>;
|
|
48
|
+
allowRollback: import("@frontmcp/lazy-zod").ZodOptional<import("@frontmcp/lazy-zod").ZodBoolean>;
|
|
49
|
+
pushReplayStorage: import("@frontmcp/lazy-zod").ZodOptional<import("zod").ZodCustom<StorageConfig, StorageConfig>>;
|
|
43
50
|
jwksUrl: import("@frontmcp/lazy-zod").ZodString;
|
|
44
51
|
expectedIssuer: import("@frontmcp/lazy-zod").ZodString;
|
|
45
52
|
}, import("zod/v4/core").$strip>, import("@frontmcp/lazy-zod").ZodObject<{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"source-options.d.ts","sourceRoot":"","sources":["../../src/skills/source-options.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,CAAC,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"source-options.d.ts","sourceRoot":"","sources":["../../src/skills/source-options.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,CAAC,EAAE,MAAM,oBAAoB,CAAC;AACvC,OAAO,KAAK,EAAE,aAAa,EAAe,MAAM,iBAAiB,CAAC;AA0ClE,eAAO,MAAM,kBAAkB;;;;gCAM7B,CAAC;AAEH,eAAO,MAAM,eAAe;;;;;gCAiB1B,CAAC;AAEH,eAAO,MAAM,gBAAgB;;;;;;;;;;;;gCA4C3B,CAAC;AAEH,eAAO,MAAM,kBAAkB;;;gCAS7B,CAAC;AAEH,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;0CAK7B,CAAC;AAEH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AACrE,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAC/D,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AACjE,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AACrE,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAIrE,eAAO,MAAM,kBAAkB;;;;;;;gCAO7B,CAAC;AAEH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC"}
|
|
@@ -2,10 +2,10 @@ import type { FrontMcpLogger } from '@frontmcp/sdk';
|
|
|
2
2
|
import type { BundleSourceOptions } from '../source-options';
|
|
3
3
|
import { InlineSource } from './inline.source';
|
|
4
4
|
import { NpmSource } from './npm.source';
|
|
5
|
-
import { SaasPullSource, SaasPushRefusedError } from './saas-pull.source';
|
|
5
|
+
import { PUSH_BODY_DIGEST_CLAIM, pushBodyDigest, SaasJwksUnavailableError, SaasPullSource, SaasPullTokenRejectedError, SaasPushRefusedError, tokenFailureOf } from './saas-pull.source';
|
|
6
6
|
import type { BundleSourceDeps, SkillBundleSource } from './skill-bundle-source.interface';
|
|
7
7
|
import { StaticSource } from './static.source';
|
|
8
|
-
export { StaticSource, NpmSource, SaasPullSource, SaasPushRefusedError, InlineSource };
|
|
9
|
-
export type { BundleCacheStore, BundlePushReceiver, BundleSourceDeps, BundleSourceListener, SkillBundleSource, } from './skill-bundle-source.interface';
|
|
8
|
+
export { StaticSource, NpmSource, SaasPullSource, SaasPullTokenRejectedError, SaasJwksUnavailableError, SaasPushRefusedError, tokenFailureOf, PUSH_BODY_DIGEST_CLAIM, pushBodyDigest, InlineSource, };
|
|
9
|
+
export type { BundleCacheStore, BundleOrigin, BundlePullFailureListener, BundlePushReceiver, BundleSourceDeps, BundleSourceListener, BundleTokenFailure, SkillBundleSource, } from './skill-bundle-source.interface';
|
|
10
10
|
export declare function createBundleSource(source: BundleSourceOptions, cacheDir: string | undefined, logger: FrontMcpLogger, deps?: BundleSourceDeps): SkillBundleSource;
|
|
11
11
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/skills/sources/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAEpD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAC7D,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/skills/sources/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAEpD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAC7D,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EACL,sBAAsB,EACtB,cAAc,EACd,wBAAwB,EACxB,cAAc,EACd,0BAA0B,EAC1B,oBAAoB,EACpB,cAAc,EACf,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AAC3F,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAE/C,OAAO,EACL,YAAY,EACZ,SAAS,EACT,cAAc,EACd,0BAA0B,EAC1B,wBAAwB,EACxB,oBAAoB,EACpB,cAAc,EACd,sBAAsB,EACtB,cAAc,EACd,YAAY,GACb,CAAC;AACF,YAAY,EACV,gBAAgB,EAChB,YAAY,EACZ,yBAAyB,EACzB,kBAAkB,EAClB,gBAAgB,EAChB,oBAAoB,EACpB,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,iCAAiC,CAAC;AAEzC,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,mBAAmB,EAC3B,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,MAAM,EAAE,cAAc,EAGtB,IAAI,CAAC,EAAE,gBAAgB,GACtB,iBAAiB,CAWnB"}
|
|
@@ -1,40 +1,63 @@
|
|
|
1
1
|
import type { FrontMcpLogger } from '@frontmcp/sdk';
|
|
2
2
|
import type { ResolvedBundle } from '../bundle/bundle.types';
|
|
3
3
|
import type { SaasSourceOptions } from '../source-options';
|
|
4
|
-
import type { BundlePushReceiver, BundleSourceDeps, BundleSourceListener, SkillBundleSource } from './skill-bundle-source.interface';
|
|
4
|
+
import type { BundlePullFailureListener, BundlePushReceiver, BundleSourceDeps, BundleSourceListener, BundleTokenFailure, SkillBundleSource } from './skill-bundle-source.interface';
|
|
5
5
|
/**
|
|
6
6
|
* The pinned pull token failed verification against `jwksUrl`, `expectedIssuer`
|
|
7
7
|
* or `expectedAudience`. Unlike an outage, this never falls back to a cached
|
|
8
8
|
* bundle: the configured SaaS is not the one the server was set up to trust.
|
|
9
9
|
*/
|
|
10
10
|
export declare class SaasPullTokenRejectedError extends Error {
|
|
11
|
-
|
|
11
|
+
readonly tokenFailure: BundleTokenFailure;
|
|
12
|
+
constructor(reason: string, tokenFailure: BundleTokenFailure);
|
|
13
|
+
}
|
|
14
|
+
/** The issuer's JWKS could not be read, or holds no usable signing key: no token can be checked, an outage. */
|
|
15
|
+
export declare class SaasJwksUnavailableError extends Error {
|
|
16
|
+
readonly tokenFailure = "jwks_unavailable";
|
|
17
|
+
constructor(message: string, options?: ErrorOptions);
|
|
12
18
|
}
|
|
13
19
|
/**
|
|
14
|
-
* The token of a pushed bundle was refused: it fails the checks the pull token passes (`invalid-token`),
|
|
15
|
-
* already used it (`replayed`)
|
|
20
|
+
* The token of a pushed bundle was refused: it fails the checks the pull token passes (`invalid-token`), a push
|
|
21
|
+
* already used it (`replayed`), or the replay store could not record its `jti` (`replay-unchecked`). The pushed
|
|
22
|
+
* bundle is not applied.
|
|
16
23
|
*/
|
|
17
24
|
export declare class SaasPushRefusedError extends Error {
|
|
18
|
-
readonly refusal: 'invalid-token' | 'replayed';
|
|
19
|
-
|
|
25
|
+
readonly refusal: 'invalid-token' | 'replayed' | 'replay-unchecked';
|
|
26
|
+
/** The check that refused the token, when the issuer's checks did rather than the replay guard. */
|
|
27
|
+
readonly tokenFailure?: BundleTokenFailure | undefined;
|
|
28
|
+
constructor(refusal: 'invalid-token' | 'replayed' | 'replay-unchecked', reason: string,
|
|
29
|
+
/** The check that refused the token, when the issuer's checks did rather than the replay guard. */
|
|
30
|
+
tokenFailure?: BundleTokenFailure | undefined);
|
|
20
31
|
}
|
|
32
|
+
/** The token check a pull or push failed on, from the error it threw; undefined for any other failure. */
|
|
33
|
+
export declare function tokenFailureOf(error: unknown): BundleTokenFailure | undefined;
|
|
34
|
+
/** The push token claim that binds it to the body it came with: {@link pushBodyDigest} of that body. */
|
|
35
|
+
export declare const PUSH_BODY_DIGEST_CLAIM = "body_sha256";
|
|
36
|
+
/** The `body_sha256` claim a push token carries for `body`: the base64url SHA-256 of its exact bytes, unpadded. */
|
|
37
|
+
export declare function pushBodyDigest(body: string | Uint8Array): string;
|
|
21
38
|
/**
|
|
22
39
|
* Pulls bundles from a configured SaaS endpoint via authenticated HTTPS.
|
|
23
40
|
*
|
|
24
41
|
* Boot semantics:
|
|
25
42
|
* - Always attempts a fresh pull on `start()`.
|
|
26
|
-
* - If the pull fails AND a cached bundle exists on disk, falls back to it
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
43
|
+
* - If the pull fails AND a cached bundle exists on disk, falls back to it,
|
|
44
|
+
* emits a warning, and hands it to its listeners with why the pull failed.
|
|
45
|
+
* This prevents a SaaS outage at boot from killing the customer's MCP
|
|
46
|
+
* server. The cache holds the last bundle the plugin applied
|
|
47
|
+
* ({@link SaasPullSource.keepBundle}): a pulled bundle that fails its
|
|
48
|
+
* signature or apply never replaces it, and the plugin doesn't write back
|
|
49
|
+
* the cached bundle it serves. While no bundle serves, the plugin reads the
|
|
50
|
+
* cache ({@link SaasPullSource.keptBundle}), and serves it in place of a
|
|
51
|
+
* pulled bundle that is older or refused.
|
|
31
52
|
* - If both pull and cache fail, `start()` throws. Polling still starts, so a
|
|
32
53
|
* later pull that succeeds loads the bundle.
|
|
33
54
|
*
|
|
34
55
|
* Refresh: poll on `pollIntervalMs`. Single-flight per source — overlapping
|
|
35
|
-
* polls are skipped, never queued.
|
|
36
|
-
*
|
|
37
|
-
*
|
|
56
|
+
* polls are skipped, never queued. A poll that fails is logged and reported to
|
|
57
|
+
* the {@link SaasPullSource.onPullFailure} listeners. With `enableWebhook` the
|
|
58
|
+
* source also takes pushed bundles ({@link SaasPullSource.verifyPushToken}):
|
|
59
|
+
* the plugin serves them at `POST /__skilled_openapi/push`, and a push token
|
|
60
|
+
* with a `body_sha256` claim is good only for the body it was issued for.
|
|
38
61
|
*
|
|
39
62
|
* Before every pull the pinned `authToken` is verified: it must be a JWT signed
|
|
40
63
|
* by a key in the issuer's JWKS (`jwksUrl`), with `iss` equal to
|
|
@@ -45,7 +68,8 @@ export declare class SaasPushRefusedError extends Error {
|
|
|
45
68
|
* other pull failure (the cache fallback still applies).
|
|
46
69
|
*
|
|
47
70
|
* NOTE: Bundle signature verification is applied by the bundle-sync service
|
|
48
|
-
* before the bundle becomes active
|
|
71
|
+
* before the bundle becomes active, and so is the version order: the source is
|
|
72
|
+
* {@link SaasPullSource.forwardOnly}, so a bundle older than the served one is refused.
|
|
49
73
|
*/
|
|
50
74
|
export declare class SaasPullSource implements SkillBundleSource, BundlePushReceiver {
|
|
51
75
|
private readonly options;
|
|
@@ -54,6 +78,7 @@ export declare class SaasPullSource implements SkillBundleSource, BundlePushRece
|
|
|
54
78
|
private readonly deps;
|
|
55
79
|
readonly id: string;
|
|
56
80
|
private listeners;
|
|
81
|
+
private pullFailureListeners;
|
|
57
82
|
private pollHandle;
|
|
58
83
|
/** The pull running now, from start, a poll or a refresh; only one runs at a time. */
|
|
59
84
|
private inFlightPull;
|
|
@@ -62,22 +87,35 @@ export declare class SaasPullSource implements SkillBundleSource, BundlePushRece
|
|
|
62
87
|
private readonly replayGuard?;
|
|
63
88
|
private pushSigningKeys?;
|
|
64
89
|
private pushSigningKeysLoading?;
|
|
90
|
+
private warnedOfUnboundPushToken;
|
|
65
91
|
constructor(options: SaasSourceOptions, cacheDir: string | undefined, logger: FrontMcpLogger, deps?: BundleSourceDeps);
|
|
92
|
+
/** The store an injected `pushReplayStore` or the `pushReplayStorage` option names; none keeps the jtis in memory. */
|
|
93
|
+
private pushReplayStore;
|
|
66
94
|
/** This source, when `enableWebhook` is on: it then takes bundles pushed to the server. */
|
|
67
95
|
get push(): BundlePushReceiver | undefined;
|
|
96
|
+
/** A pulled or pushed bundle older than the one being served is not applied, unless `allowRollback` is on. */
|
|
97
|
+
get forwardOnly(): boolean;
|
|
68
98
|
/**
|
|
69
99
|
* Check the token of a pushed bundle: by the rules the pull token passes (a key in `jwksUrl`, `iss`, `aud`,
|
|
70
|
-
* `resource`, expiry), then
|
|
71
|
-
*
|
|
100
|
+
* `resource`, expiry), then against `body`, the exact bytes of the request body ({@link PUSH_BODY_DIGEST_CLAIM}),
|
|
101
|
+
* then by the replay guard, so its `iat` must be within five minutes and its `jti` unused by every instance sharing
|
|
102
|
+
* the replay store. A token refused for its body leaves its `jti` unused. Throws {@link SaasPushRefusedError}, or
|
|
103
|
+
* {@link SaasJwksUnavailableError} when the JWKS can't be read.
|
|
72
104
|
*/
|
|
73
|
-
verifyPushToken(token: string): Promise<void>;
|
|
105
|
+
verifyPushToken(token: string, body: Uint8Array): Promise<void>;
|
|
106
|
+
/** Refuse a push whose body isn't the one its token was issued for, or, with `requirePushBodyDigest`, unbound. */
|
|
107
|
+
private checkPushBody;
|
|
74
108
|
/** Keep a bundle the plugin applied, pulled or pushed, as the last good one a start falls back to. */
|
|
75
109
|
keepBundle(bundle: ResolvedBundle): Promise<void>;
|
|
110
|
+
/** The last good bundle {@link keepBundle} kept, schema-checked as a pull is; undefined when there is none. */
|
|
111
|
+
keptBundle(): Promise<ResolvedBundle | undefined>;
|
|
76
112
|
start(): Promise<void>;
|
|
77
113
|
private pullAtStart;
|
|
78
114
|
/** Run `pull` as the one pull in flight, so a start, a poll and a refresh never pull at once. */
|
|
79
115
|
private exclusivePull;
|
|
80
116
|
onChange(listener: BundleSourceListener): () => void;
|
|
117
|
+
/** Subscribe to the polls that fail, with why; a failed start or refresh throws to its caller instead. */
|
|
118
|
+
onPullFailure(listener: BundlePullFailureListener): () => void;
|
|
81
119
|
stop(): Promise<void>;
|
|
82
120
|
private schedulePoll;
|
|
83
121
|
/**
|
|
@@ -96,9 +134,9 @@ export declare class SaasPullSource implements SkillBundleSource, BundlePushRece
|
|
|
96
134
|
body: string;
|
|
97
135
|
}>;
|
|
98
136
|
/**
|
|
99
|
-
* Verify the pinned pull token against the issuer's JWKS, issuer and audience.
|
|
100
|
-
* Throws {@link SaasPullTokenRejectedError} when the token is refused, and
|
|
101
|
-
*
|
|
137
|
+
* Verify the pinned pull token against the issuer's JWKS, issuer and audience, and log the check it fails on.
|
|
138
|
+
* Throws {@link SaasPullTokenRejectedError} when the token is refused, and {@link SaasJwksUnavailableError} when
|
|
139
|
+
* the JWKS can't be fetched (an outage).
|
|
102
140
|
*/
|
|
103
141
|
private verifyPullToken;
|
|
104
142
|
/**
|
|
@@ -116,5 +154,6 @@ export declare class SaasPullSource implements SkillBundleSource, BundlePushRece
|
|
|
116
154
|
private persistCache;
|
|
117
155
|
private loadCache;
|
|
118
156
|
private notify;
|
|
157
|
+
private notifyPullFailure;
|
|
119
158
|
}
|
|
120
159
|
//# sourceMappingURL=saas-pull.source.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"saas-pull.source.d.ts","sourceRoot":"","sources":["../../../src/skills/sources/saas-pull.source.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"saas-pull.source.d.ts","sourceRoot":"","sources":["../../../src/skills/sources/saas-pull.source.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import type { TokenVerifyFailure } from '@frontmcp/auth';
|
|
1
2
|
import type { ResolvedBundle } from '../bundle/bundle.types';
|
|
3
|
+
import type { ReplayNonceStore } from '../security/replay-nonce-store';
|
|
2
4
|
/**
|
|
3
5
|
* Pluggable source of skill bundles. Static, npm-installed, and SaaS-pull
|
|
4
6
|
* implementations all conform to this interface.
|
|
@@ -32,6 +34,11 @@ export interface SkillBundleSource {
|
|
|
32
34
|
* notification fires on or shortly after `start()` resolves.
|
|
33
35
|
*/
|
|
34
36
|
onChange(listener: BundleSourceListener): () => void;
|
|
37
|
+
/**
|
|
38
|
+
* Subscribe to the pulls that fail in the background (a poll), which no caller sees: `start()` and `refresh()` throw
|
|
39
|
+
* theirs. Returns an unsubscribe function. Present on a polling source: the `saas` source.
|
|
40
|
+
*/
|
|
41
|
+
onPullFailure?(listener: BundlePullFailureListener): () => void;
|
|
35
42
|
/**
|
|
36
43
|
* Pull a fresh bundle on demand and notify listeners — the manual,
|
|
37
44
|
* externally-driven counterpart to background polling. Optional: only
|
|
@@ -48,18 +55,49 @@ export interface SkillBundleSource {
|
|
|
48
55
|
* a source with a last-good cache: the `saas` source.
|
|
49
56
|
*/
|
|
50
57
|
keepBundle?(bundle: ResolvedBundle): Promise<void>;
|
|
58
|
+
/**
|
|
59
|
+
* The last good bundle {@link keepBundle} kept, read back from the cache; undefined when none is kept or it can't be
|
|
60
|
+
* read. While no bundle serves, the plugin compares a new bundle with it, and serves it in place of one it refuses.
|
|
61
|
+
*/
|
|
62
|
+
keptBundle?(): Promise<ResolvedBundle | undefined>;
|
|
51
63
|
/** Present on a source that takes pushed bundles: the `saas` source with `enableWebhook`. */
|
|
52
64
|
readonly push?: BundlePushReceiver;
|
|
65
|
+
/**
|
|
66
|
+
* True on a source whose bundles only move forward: the `saas` source, unless `allowRollback`. The plugin then
|
|
67
|
+
* refuses a bundle older than the one being served, as `compareBundleVersions` orders them.
|
|
68
|
+
*/
|
|
69
|
+
readonly forwardOnly?: boolean;
|
|
53
70
|
/** Halt background work; idempotent. */
|
|
54
71
|
stop(): Promise<void>;
|
|
55
72
|
}
|
|
56
73
|
/** The check a pushed bundle's token passes before the bundle is applied. */
|
|
57
74
|
export interface BundlePushReceiver {
|
|
58
|
-
/**
|
|
59
|
-
|
|
75
|
+
/**
|
|
76
|
+
* Resolves when the push token is accepted for `body`, the exact bytes of the request body; throws when it is
|
|
77
|
+
* refused or can't be checked.
|
|
78
|
+
*/
|
|
79
|
+
verifyPushToken(token: string, body: Uint8Array): Promise<void>;
|
|
60
80
|
}
|
|
61
81
|
/** Takes each new bundle; a pulling source waits for a promise it returns before its pull counts as done. */
|
|
62
|
-
export type BundleSourceListener = (bundle: ResolvedBundle) => unknown;
|
|
82
|
+
export type BundleSourceListener = (bundle: ResolvedBundle, origin?: BundleOrigin) => unknown;
|
|
83
|
+
/** How a source came by a bundle it hands its listeners. */
|
|
84
|
+
export interface BundleOrigin {
|
|
85
|
+
/**
|
|
86
|
+
* Set when the pull failed and the bundle is the last good one the source kept, served in its place: why it failed.
|
|
87
|
+
* The plugin doesn't keep such a bundle again, since the cache holds it.
|
|
88
|
+
*/
|
|
89
|
+
pullFailure?: string;
|
|
90
|
+
/** Set when that pull failed on its token: the check that refused it, or `jwks_unavailable`. */
|
|
91
|
+
tokenFailure?: BundleTokenFailure;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Which check refused a pull or push token, or `jwks_unavailable` when the issuer's keys could not be read to check
|
|
95
|
+
* it; `body_mismatch` is a push token issued for another body. For the server's logs and `/readyz`: a caller learns
|
|
96
|
+
* only that its token was refused.
|
|
97
|
+
*/
|
|
98
|
+
export type BundleTokenFailure = TokenVerifyFailure | 'wrong_audience' | 'body_mismatch';
|
|
99
|
+
/** Takes the reason a background pull failed, and the token check it failed on, if any. */
|
|
100
|
+
export type BundlePullFailureListener = (reason: string, tokenFailure?: BundleTokenFailure) => void;
|
|
63
101
|
/**
|
|
64
102
|
* Pluggable last-good bundle cache. The default (Node) implementation writes to
|
|
65
103
|
* disk; V8-isolate runtimes (Cloudflare Workers) inject a KV-backed store
|
|
@@ -76,7 +114,9 @@ export interface BundleCacheStore {
|
|
|
76
114
|
* - `cache` replaces the on-disk last-good cache (no filesystem on a Worker),
|
|
77
115
|
* - `disablePolling` turns off the internal `setInterval` loop (no background
|
|
78
116
|
* execution on a Worker; a Cron Trigger / DO alarm drives {@link
|
|
79
|
-
* SkillBundleSource.refresh} instead)
|
|
117
|
+
* SkillBundleSource.refresh} instead),
|
|
118
|
+
* - `pushReplayStore` records the pushed tokens' `jti`s where every instance
|
|
119
|
+
* reaches them (a Durable Object on a Worker).
|
|
80
120
|
*/
|
|
81
121
|
export interface BundleSourceDeps {
|
|
82
122
|
/**
|
|
@@ -90,5 +130,10 @@ export interface BundleSourceDeps {
|
|
|
90
130
|
* {@link SkillBundleSource.refresh} instead.
|
|
91
131
|
*/
|
|
92
132
|
disablePolling?: boolean;
|
|
133
|
+
/**
|
|
134
|
+
* Where the push replay guard records the `jti`s it accepted, shared by every instance that serves the server.
|
|
135
|
+
* Replaces the `saas` source's `pushReplayStorage` option and its in-memory default.
|
|
136
|
+
*/
|
|
137
|
+
pushReplayStore?: ReplayNonceStore;
|
|
93
138
|
}
|
|
94
139
|
//# sourceMappingURL=skill-bundle-source.interface.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"skill-bundle-source.interface.d.ts","sourceRoot":"","sources":["../../../src/skills/sources/skill-bundle-source.interface.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"skill-bundle-source.interface.d.ts","sourceRoot":"","sources":["../../../src/skills/sources/skill-bundle-source.interface.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAEzD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAEvE;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,WAAW,iBAAiB;IAChC,qEAAqE;IACrE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IAEpB;;;;OAIG;IACH,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAEvB;;;OAGG;IACH,QAAQ,CAAC,QAAQ,EAAE,oBAAoB,GAAG,MAAM,IAAI,CAAC;IAErD;;;OAGG;IACH,aAAa,CAAC,CAAC,QAAQ,EAAE,yBAAyB,GAAG,MAAM,IAAI,CAAC;IAEhE;;;;;;;;;OASG;IACH,OAAO,CAAC,IAAI,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC,CAAC;IAEhD;;;OAGG;IACH,UAAU,CAAC,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEnD;;;OAGG;IACH,UAAU,CAAC,IAAI,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC,CAAC;IAEnD,6FAA6F;IAC7F,QAAQ,CAAC,IAAI,CAAC,EAAE,kBAAkB,CAAC;IAEnC;;;OAGG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,CAAC;IAE/B,wCAAwC;IACxC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACvB;AAED,6EAA6E;AAC7E,MAAM,WAAW,kBAAkB;IACjC;;;OAGG;IACH,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACjE;AAED,6GAA6G;AAC7G,MAAM,MAAM,oBAAoB,GAAG,CAAC,MAAM,EAAE,cAAc,EAAE,MAAM,CAAC,EAAE,YAAY,KAAK,OAAO,CAAC;AAE9F,4DAA4D;AAC5D,MAAM,WAAW,YAAY;IAC3B;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gGAAgG;IAChG,YAAY,CAAC,EAAE,kBAAkB,CAAC;CACnC;AAED;;;;GAIG;AACH,MAAM,MAAM,kBAAkB,GAAG,kBAAkB,GAAG,gBAAgB,GAAG,eAAe,CAAC;AAEzF,2FAA2F;AAC3F,MAAM,MAAM,yBAAyB,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,kBAAkB,KAAK,IAAI,CAAC;AAEpG;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC/B,IAAI,IAAI,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC,CAAC;IAC5C,KAAK,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9C;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;;OAGG;IACH,KAAK,CAAC,EAAE,gBAAgB,CAAC;IACzB;;;;OAIG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;OAGG;IACH,eAAe,CAAC,EAAE,gBAAgB,CAAC;CACpC"}
|