@weaveaijs/mcp-observatory 0.3.0 → 0.4.0

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.
Files changed (41) hide show
  1. package/dist/core/wrapper.d.ts.map +1 -1
  2. package/dist/core/wrapper.js +57 -8
  3. package/dist/core/wrapper.js.map +1 -1
  4. package/dist/demo/server.d.ts.map +1 -1
  5. package/dist/demo/server.js +22 -2
  6. package/dist/demo/server.js.map +1 -1
  7. package/dist/index.d.ts +1 -0
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +1 -0
  10. package/dist/index.js.map +1 -1
  11. package/dist/proposal/proposer.d.ts +21 -0
  12. package/dist/proposal/proposer.d.ts.map +1 -1
  13. package/dist/proposal/proposer.js +62 -7
  14. package/dist/proposal/proposer.js.map +1 -1
  15. package/dist/proposal/token.d.ts +18 -0
  16. package/dist/proposal/token.d.ts.map +1 -1
  17. package/dist/proposal/token.js +84 -2
  18. package/dist/proposal/token.js.map +1 -1
  19. package/dist/tests/proposer.test.js +6 -0
  20. package/dist/tests/proposer.test.js.map +1 -1
  21. package/dist/tests/proposerGuards.test.d.ts +2 -0
  22. package/dist/tests/proposerGuards.test.d.ts.map +1 -0
  23. package/dist/tests/proposerGuards.test.js +95 -0
  24. package/dist/tests/proposerGuards.test.js.map +1 -0
  25. package/dist/tests/secrets.test.d.ts +2 -0
  26. package/dist/tests/secrets.test.d.ts.map +1 -0
  27. package/dist/tests/secrets.test.js +92 -0
  28. package/dist/tests/secrets.test.js.map +1 -0
  29. package/dist/tests/wrapper.test.d.ts +2 -0
  30. package/dist/tests/wrapper.test.d.ts.map +1 -0
  31. package/dist/tests/wrapper.test.js +141 -0
  32. package/dist/tests/wrapper.test.js.map +1 -0
  33. package/dist/utils/limits.d.ts +33 -0
  34. package/dist/utils/limits.d.ts.map +1 -0
  35. package/dist/utils/limits.js +55 -0
  36. package/dist/utils/limits.js.map +1 -0
  37. package/dist/utils/secrets.d.ts +47 -0
  38. package/dist/utils/secrets.d.ts.map +1 -0
  39. package/dist/utils/secrets.js +68 -0
  40. package/dist/utils/secrets.js.map +1 -0
  41. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"wrapper.d.ts","sourceRoot":"","sources":["../../src/core/wrapper.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAM5C,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,OAAO,GAAG,QAAQ,GAAG,OAAO,CAAC;IACrC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,aAAa;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,OAAO,CAAC;IAChB,IAAI,EAAE,YAAY,CAAC;IACnB,QAAQ,EAAE,eAAe,CAAC;IAC1B,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,UAAU,CAAC,EAAE,YAAY,CAAC;CAC3B;AAED,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,MAAM,CAA0B;gBAE5B,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,aAAa;IAQ7C,MAAM,CAAC,OAAO,EAAE;QACpB,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC;QAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,MAAM,EAAE,MAAM,CAAC;QACf,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACvC,IAAI,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;QACvC,UAAU,CAAC,EAAE,OAAO,CAAC;QACrB,UAAU,CAAC,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;KAC/C,GAAG,OAAO,CAAC,aAAa,CAAC;IA0D1B,OAAO,CAAC,MAAM;IA+Bd,OAAO,CAAC,MAAM;CAKf"}
1
+ {"version":3,"file":"wrapper.d.ts","sourceRoot":"","sources":["../../src/core/wrapper.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AA8B5C,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,OAAO,GAAG,QAAQ,GAAG,OAAO,CAAC;IACrC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,aAAa;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,OAAO,CAAC;IAChB,IAAI,EAAE,YAAY,CAAC;IACnB,QAAQ,EAAE,eAAe,CAAC;IAC1B,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,UAAU,CAAC,EAAE,YAAY,CAAC;CAC3B;AAED,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,MAAM,CAA0B;gBAE5B,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,aAAa;IAQ7C,MAAM,CAAC,OAAO,EAAE;QACpB,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC;QAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,MAAM,EAAE,MAAM,CAAC;QACf,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACvC,IAAI,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;QACvC,UAAU,CAAC,EAAE,OAAO,CAAC;QACrB,UAAU,CAAC,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;KAC/C,GAAG,OAAO,CAAC,aAAa,CAAC;IAkF1B,OAAO,CAAC,MAAM;IA+Bd,OAAO,CAAC,MAAM;CAKf"}
@@ -2,6 +2,31 @@ import { estimateTokens } from '../cost/tokenizer.js';
2
2
  import { estimateCost } from '../cost/pricing.js';
3
3
  import { hashText } from '../utils/hashing.js';
4
4
  import { Tracer } from './tracer.js';
5
+ /**
6
+ * JSON.stringify() is not a total function: it throws for BigInt values and
7
+ * for circular references, and it returns the *value* `undefined` (not a
8
+ * string) when given `undefined`. Span bookkeeping must never throw on
9
+ * account of what a wrapped call happened to return, so fall back to
10
+ * String() -- and finally to a fixed placeholder -- for anything
11
+ * JSON.stringify can't turn into a string.
12
+ */
13
+ function safeStringifyForSpan(value) {
14
+ try {
15
+ const json = JSON.stringify(value);
16
+ if (typeof json === 'string') {
17
+ return json;
18
+ }
19
+ }
20
+ catch {
21
+ // fall through to the String() fallback below
22
+ }
23
+ try {
24
+ return String(value);
25
+ }
26
+ catch {
27
+ return '[unserializable]';
28
+ }
29
+ }
5
30
  export class InvocationWrapper {
6
31
  constructor(service, policy) {
7
32
  this.tracer = new Tracer(service);
@@ -21,6 +46,7 @@ export class InvocationWrapper {
21
46
  const startTime = Date.now();
22
47
  let output;
23
48
  let shadowOutput;
49
+ let callFailed = false;
24
50
  try {
25
51
  output = await Promise.resolve(options.call());
26
52
  if (options.dualInvoke && options.shadowCall) {
@@ -28,17 +54,40 @@ export class InvocationWrapper {
28
54
  }
29
55
  }
30
56
  catch (error) {
57
+ callFailed = true;
31
58
  span.statusCode = 500;
32
59
  throw error;
33
60
  }
34
61
  finally {
35
- const outputText = JSON.stringify(output);
36
- span.outputHash = hashText(outputText);
37
- const outputTokens = estimateTokens(outputText, options.model);
38
- span.outputTokens = outputTokens;
39
- span.totalTokens = (span.inputTokens || 0) + outputTokens;
40
- span.costUsd = estimateCost(span.totalTokens, options.model || 'gpt-4', options.source);
41
- span.finish();
62
+ // Span finalisation must never throw: this runs inside the try/catch's
63
+ // finally, so an exception here (e.g. hashText(undefined) when a
64
+ // rejected call() left `output` unset, or JSON.stringify choking on a
65
+ // BigInt/circular output) would replace whatever the catch block above
66
+ // just threw, hiding the caller's real error behind an unrelated
67
+ // TypeError. Telemetry for a failed call still matters, so we still
68
+ // finish and export the span on that path -- just without an output
69
+ // hash/tokens -- and any unexpected error while computing metrics is
70
+ // swallowed rather than allowed to propagate.
71
+ try {
72
+ if (callFailed) {
73
+ span.outputTokens = 0;
74
+ span.totalTokens = span.inputTokens || 0;
75
+ }
76
+ else {
77
+ const outputText = safeStringifyForSpan(output);
78
+ span.outputHash = hashText(outputText);
79
+ const outputTokens = estimateTokens(outputText, options.model);
80
+ span.outputTokens = outputTokens;
81
+ span.totalTokens = (span.inputTokens || 0) + outputTokens;
82
+ }
83
+ span.costUsd = estimateCost(span.totalTokens, options.model || 'gpt-4', options.source);
84
+ }
85
+ catch (finalizationError) {
86
+ console.error('[mcp-observatory] span finalisation failed; continuing without output hash/tokens', finalizationError);
87
+ }
88
+ finally {
89
+ span.finish();
90
+ }
42
91
  }
43
92
  const decision = this.decide(span, output);
44
93
  const result = {
@@ -87,7 +136,7 @@ export class InvocationWrapper {
87
136
  return output;
88
137
  if (output === null || output === undefined)
89
138
  return '';
90
- return JSON.stringify(output);
139
+ return safeStringifyForSpan(output);
91
140
  }
92
141
  }
93
142
  //# sourceMappingURL=wrapper.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"wrapper.js","sourceRoot":"","sources":["../../src/core/wrapper.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"wrapper.js","sourceRoot":"","sources":["../../src/core/wrapper.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/demo/server.ts"],"names":[],"mappings":"AAIA,UAAU,QAAQ;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/B;AAED,qBAAa,SAAS;IACpB,OAAO,CAAC,QAAQ,CAAe;IAC/B,OAAO,CAAC,QAAQ,CAAiB;IACjC,OAAO,CAAC,MAAM,CAAS;;IAQjB,cAAc,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC;YAcxC,cAAc;YAsBd,YAAY;YAkCZ,WAAW;CAyB1B;AAED,wBAAsB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAwBnD"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/demo/server.ts"],"names":[],"mappings":"AASA,UAAU,QAAQ;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/B;AAED,qBAAa,SAAS;IACpB,OAAO,CAAC,QAAQ,CAAe;IAC/B,OAAO,CAAC,QAAQ,CAAiB;IACjC,OAAO,CAAC,MAAM,CAAS;;IA+BjB,cAAc,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC;YAcxC,cAAc;YAsBd,YAAY;YAkCZ,WAAW;CAyB1B;AAED,wBAAsB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAwBnD"}
@@ -1,10 +1,30 @@
1
1
  import { ToolProposer } from '../proposal/proposer.js';
2
2
  import { CommitVerifier } from '../proposal/verifier.js';
3
+ import { TokenManager } from '../proposal/token.js';
3
4
  import { Tracer } from '../core/tracer.js';
5
+ import { ALLOW_DEV_SECRET_ENV, COMMIT_SECRET_ENV, } from '../utils/secrets.js';
4
6
  export class MCPServer {
5
7
  constructor() {
6
- this.proposer = new ToolProposer();
7
- this.verifier = new CommitVerifier();
8
+ // The demo is meant to run with no configuration, so it opts itself
9
+ // into the public development secret — loudly, and only if the real
10
+ // one is absent. The opt-in happens here rather than at module scope
11
+ // because `src/index.ts` re-exports this class, and a module-scope
12
+ // `process.env` write would silently weaken every consumer that merely
13
+ // imports the package.
14
+ if (!process.env[COMMIT_SECRET_ENV] &&
15
+ process.env[ALLOW_DEV_SECRET_ENV] !== '1') {
16
+ console.warn(`[mcp-observatory] demo: ${COMMIT_SECRET_ENV} is unset, enabling ` +
17
+ `${ALLOW_DEV_SECRET_ENV}=1 for this process. Never do this outside a demo.`);
18
+ process.env[ALLOW_DEV_SECRET_ENV] = '1';
19
+ }
20
+ // One manager, shared. Issuing and verifying under two independently
21
+ // constructed managers is what made the demo's commit phase fail with
22
+ // `bad_signature` while the random per-instance secret was in place;
23
+ // sharing the instance makes the demo independent of how the secret
24
+ // happens to be resolved.
25
+ const tokenManager = new TokenManager();
26
+ this.proposer = new ToolProposer(tokenManager);
27
+ this.verifier = new CommitVerifier(tokenManager);
8
28
  this.tracer = new Tracer('mcp-server');
9
29
  }
10
30
  async handleToolCall(call) {
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/demo/server.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/demo/server.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -8,6 +8,7 @@ export { CommitVerifier } from './proposal/verifier.js';
8
8
  export { InMemoryProposalStorage, PostgresProposalStorage, } from './proposal/storage.js';
9
9
  export type { ProposalOptions, ProposalResult, } from './proposal/proposer.js';
10
10
  export { hashText, hashJson, normalizePrompt, computePromptHash, } from './utils/hashing.js';
11
+ export { InsecureDefaultSecretError, resolveSecret, COMMIT_SECRET_ENV, ALLOW_DEV_SECRET_ENV, DEV_COMMIT_SECRET, } from './utils/secrets.js';
11
12
  export { getCurrentTimeMs, getCurrentTimeIso, addMs, diffMs, formatDuration, } from './utils/time.js';
12
13
  export { estimateTokens, getTokenLimits } from './cost/tokenizer.js';
13
14
  export { estimateCost, getPricing } from './cost/pricing.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACtD,YAAY,EAAE,eAAe,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAGvF,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EACL,uBAAuB,EACvB,uBAAuB,GACxB,MAAM,uBAAuB,CAAC;AAC/B,YAAY,EACV,eAAe,EACf,cAAc,GACf,MAAM,wBAAwB,CAAC;AAGhC,OAAO,EACL,QAAQ,EACR,QAAQ,EACR,eAAe,EACf,iBAAiB,GAClB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,gBAAgB,EAChB,iBAAiB,EACjB,KAAK,EACL,MAAM,EACN,cAAc,GACf,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrE,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAG7D,OAAO,EACL,6BAA6B,EAC7B,iBAAiB,EACjB,qBAAqB,EACrB,2BAA2B,EAC3B,2BAA2B,EAC3B,uBAAuB,GACxB,MAAM,4BAA4B,CAAC;AACpC,YAAY,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AAGvE,OAAO,EACL,gBAAgB,EAChB,cAAc,GACf,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAGrD,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACtD,YAAY,EAAE,eAAe,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAGvF,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EACL,uBAAuB,EACvB,uBAAuB,GACxB,MAAM,uBAAuB,CAAC;AAC/B,YAAY,EACV,eAAe,EACf,cAAc,GACf,MAAM,wBAAwB,CAAC;AAGhC,OAAO,EACL,QAAQ,EACR,QAAQ,EACR,eAAe,EACf,iBAAiB,GAClB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,0BAA0B,EAC1B,aAAa,EACb,iBAAiB,EACjB,oBAAoB,EACpB,iBAAiB,GAClB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,gBAAgB,EAChB,iBAAiB,EACjB,KAAK,EACL,MAAM,EACN,cAAc,GACf,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrE,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAG7D,OAAO,EACL,6BAA6B,EAC7B,iBAAiB,EACjB,qBAAqB,EACrB,2BAA2B,EAC3B,2BAA2B,EAC3B,uBAAuB,GACxB,MAAM,4BAA4B,CAAC;AACpC,YAAY,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AAGvE,OAAO,EACL,gBAAgB,EAChB,cAAc,GACf,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAGrD,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC"}
package/dist/index.js CHANGED
@@ -9,6 +9,7 @@ export { CommitVerifier } from './proposal/verifier.js';
9
9
  export { InMemoryProposalStorage, PostgresProposalStorage, } from './proposal/storage.js';
10
10
  // Utils
11
11
  export { hashText, hashJson, normalizePrompt, computePromptHash, } from './utils/hashing.js';
12
+ export { InsecureDefaultSecretError, resolveSecret, COMMIT_SECRET_ENV, ALLOW_DEV_SECRET_ENV, DEV_COMMIT_SECRET, } from './utils/secrets.js';
12
13
  export { getCurrentTimeMs, getCurrentTimeIso, addMs, diffMs, formatDuration, } from './utils/time.js';
13
14
  // Cost
14
15
  export { estimateTokens, getTokenLimits } from './cost/tokenizer.js';
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO;AACP,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAGtD,kBAAkB;AAClB,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EACL,uBAAuB,EACvB,uBAAuB,GACxB,MAAM,uBAAuB,CAAC;AAM/B,QAAQ;AACR,OAAO,EACL,QAAQ,EACR,QAAQ,EACR,eAAe,EACf,iBAAiB,GAClB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,gBAAgB,EAChB,iBAAiB,EACjB,KAAK,EACL,MAAM,EACN,cAAc,GACf,MAAM,iBAAiB,CAAC;AAEzB,OAAO;AACP,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrE,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAE7D,gBAAgB;AAChB,OAAO,EACL,6BAA6B,EAC7B,iBAAiB,EACjB,qBAAqB,EACrB,2BAA2B,EAC3B,2BAA2B,EAC3B,uBAAuB,GACxB,MAAM,4BAA4B,CAAC;AAGpC,OAAO;AACP,OAAO,EACL,gBAAgB,EAChB,cAAc,GACf,MAAM,mBAAmB,CAAC;AAG3B,OAAO;AACP,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO;AACP,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAGtD,kBAAkB;AAClB,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EACL,uBAAuB,EACvB,uBAAuB,GACxB,MAAM,uBAAuB,CAAC;AAM/B,QAAQ;AACR,OAAO,EACL,QAAQ,EACR,QAAQ,EACR,eAAe,EACf,iBAAiB,GAClB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,0BAA0B,EAC1B,aAAa,EACb,iBAAiB,EACjB,oBAAoB,EACpB,iBAAiB,GAClB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,gBAAgB,EAChB,iBAAiB,EACjB,KAAK,EACL,MAAM,EACN,cAAc,GACf,MAAM,iBAAiB,CAAC;AAEzB,OAAO;AACP,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrE,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAE7D,gBAAgB;AAChB,OAAO,EACL,6BAA6B,EAC7B,iBAAiB,EACjB,qBAAqB,EACrB,2BAA2B,EAC3B,2BAA2B,EAC3B,uBAAuB,GACxB,MAAM,4BAA4B,CAAC;AAGpC,OAAO;AACP,OAAO,EACL,gBAAgB,EAChB,cAAc,GACf,MAAM,mBAAmB,CAAC;AAG3B,OAAO;AACP,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC"}
@@ -13,6 +13,12 @@ export interface ProposalResult {
13
13
  commitToken?: string;
14
14
  fallbackResponse?: unknown;
15
15
  }
16
+ /** A composite score, plus how many signals informed it. */
17
+ export interface CompositeScore {
18
+ /** `undefined` when no signal was supplied — not zero. */
19
+ score: number | undefined;
20
+ definedCount: number;
21
+ }
16
22
  export interface ScoringOptions {
17
23
  outputInstability?: number;
18
24
  numericVariance?: number;
@@ -24,6 +30,21 @@ export declare class ToolProposer {
24
30
  private reviewThreshold;
25
31
  constructor(tokenManager?: TokenManager);
26
32
  propose(options: ProposalOptions): Promise<ProposalResult>;
33
+ /**
34
+ * Weighted mean over the signals that were actually supplied.
35
+ *
36
+ * Returns `undefined` when none was, which is not the same as zero. The
37
+ * previous form read each optional signal as `value || 0` and divided by a
38
+ * fixed denominator of 1.0, so an absent signal contributed no risk at full
39
+ * weight: a proposal carrying no signals at all scored 0 and was allowed,
40
+ * with a commit token. That is the fail-open this renormalisation exists to
41
+ * prevent — missing evidence must not read as evidence of safety — and it
42
+ * was reachable from the ordinary API, since all three signals are optional
43
+ * parameters.
44
+ *
45
+ * `|| 0` also conflated a genuine instability of 0 with an absent one. The
46
+ * explicit `undefined` check keeps a real zero in the defined set.
47
+ */
27
48
  private computeCompositeScore;
28
49
  private deterministicFallback;
29
50
  }
@@ -1 +1 @@
1
- {"version":3,"file":"proposer.d.ts","sourceRoot":"","sources":["../../src/proposal/proposer.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE1C,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,SAAS,GAAG,SAAS,GAAG,QAAQ,CAAC;IACzC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,MAAM,WAAW,cAAc;IAC7B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,qBAAa,YAAY;IACvB,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,cAAc,CAAe;IACrC,OAAO,CAAC,eAAe,CAAe;gBAE1B,YAAY,CAAC,EAAE,YAAY;IAIjC,OAAO,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,cAAc,CAAC;IA+ChE,OAAO,CAAC,qBAAqB;IAkB7B,OAAO,CAAC,qBAAqB;CAW9B"}
1
+ {"version":3,"file":"proposer.d.ts","sourceRoot":"","sources":["../../src/proposal/proposer.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE1C,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,SAAS,GAAG,SAAS,GAAG,QAAQ,CAAC;IACzC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,4DAA4D;AAC5D,MAAM,WAAW,cAAc;IAC7B,0DAA0D;IAC1D,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,cAAc;IAC7B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,qBAAa,YAAY;IACvB,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,cAAc,CAAe;IACrC,OAAO,CAAC,eAAe,CAAe;gBAE1B,YAAY,CAAC,EAAE,YAAY;IAIjC,OAAO,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,cAAc,CAAC;IA0EhE;;;;;;;;;;;;;;OAcG;IACH,OAAO,CAAC,qBAAqB;IAkC7B,OAAO,CAAC,qBAAqB;CAW9B"}
@@ -1,5 +1,6 @@
1
1
  import { randomUUID } from 'crypto';
2
2
  import { hashJson } from '../utils/hashing.js';
3
+ import { argsExceedLimit } from '../utils/limits.js';
3
4
  import { TokenManager } from './token.js';
4
5
  export class ToolProposer {
5
6
  constructor(tokenManager) {
@@ -9,12 +10,35 @@ export class ToolProposer {
9
10
  }
10
11
  async propose(options) {
11
12
  const proposalId = randomUUID();
13
+ // Cost is linear in input size and the gate cannot bound its own latency,
14
+ // so refuse before hashing rather than after (see utils/limits.ts).
15
+ if (argsExceedLimit(options.toolArgs)) {
16
+ return {
17
+ proposalId,
18
+ status: 'blocked',
19
+ reason: 'input_too_large',
20
+ fallbackResponse: this.deterministicFallback(options.toolName, options.toolArgs),
21
+ };
22
+ }
12
23
  const toolArgsHash = hashJson(options.toolArgs);
13
- const compositeScore = this.computeCompositeScore({
24
+ const { score, definedCount } = this.computeCompositeScore({
14
25
  outputInstability: options.outputInstability,
15
26
  numericVariance: options.numericVariance,
16
27
  promptDrift: options.promptDrift,
17
28
  });
29
+ // No computable signal is not zero risk. Refuse rather than allow, and
30
+ // refuse rather than `review`: a `review` result still issues a usable
31
+ // commit token here, so it is not a refusal.
32
+ if (score === undefined) {
33
+ return {
34
+ proposalId,
35
+ status: 'blocked',
36
+ reason: 'no_signals',
37
+ fallbackResponse: this.deterministicFallback(options.toolName, options.toolArgs),
38
+ };
39
+ }
40
+ const compositeScore = score;
41
+ void definedCount;
18
42
  if (compositeScore > this.blockThreshold) {
19
43
  return {
20
44
  proposalId,
@@ -48,18 +72,49 @@ export class ToolProposer {
48
72
  commitToken: token,
49
73
  };
50
74
  }
75
+ /**
76
+ * Weighted mean over the signals that were actually supplied.
77
+ *
78
+ * Returns `undefined` when none was, which is not the same as zero. The
79
+ * previous form read each optional signal as `value || 0` and divided by a
80
+ * fixed denominator of 1.0, so an absent signal contributed no risk at full
81
+ * weight: a proposal carrying no signals at all scored 0 and was allowed,
82
+ * with a commit token. That is the fail-open this renormalisation exists to
83
+ * prevent — missing evidence must not read as evidence of safety — and it
84
+ * was reachable from the ordinary API, since all three signals are optional
85
+ * parameters.
86
+ *
87
+ * `|| 0` also conflated a genuine instability of 0 with an absent one. The
88
+ * explicit `undefined` check keeps a real zero in the defined set.
89
+ */
51
90
  computeCompositeScore(scoring) {
52
- const instability = Math.max(0, Math.min(1, scoring.outputInstability || 0));
53
- const variance = Math.max(0, Math.min(1, scoring.numericVariance || 0));
54
- const drift = Math.max(0, Math.min(1, scoring.promptDrift || 0));
55
91
  const weights = {
56
92
  instability: 0.5,
57
93
  variance: 0.3,
58
94
  drift: 0.2,
59
95
  };
60
- return (instability * weights.instability +
61
- variance * weights.variance +
62
- drift * weights.drift);
96
+ const supplied = [
97
+ [scoring.outputInstability, weights.instability],
98
+ [scoring.numericVariance, weights.variance],
99
+ [scoring.promptDrift, weights.drift],
100
+ ];
101
+ let weightedSum = 0;
102
+ let totalWeight = 0;
103
+ let definedCount = 0;
104
+ for (const [value, weight] of supplied) {
105
+ if (value === undefined || value === null || Number.isNaN(value))
106
+ continue;
107
+ weightedSum += Math.max(0, Math.min(1, value)) * weight;
108
+ totalWeight += weight;
109
+ definedCount += 1;
110
+ }
111
+ if (totalWeight === 0) {
112
+ return { score: undefined, definedCount: 0 };
113
+ }
114
+ return {
115
+ score: Math.max(0, Math.min(1, weightedSum / totalWeight)),
116
+ definedCount,
117
+ };
63
118
  }
64
119
  deterministicFallback(toolName, toolArgs) {
65
120
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"proposer.js","sourceRoot":"","sources":["../../src/proposal/proposer.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"proposer.js","sourceRoot":"","sources":["../../src/proposal/proposer.ts"],"names":[],"mappings":"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"}
@@ -13,9 +13,27 @@ export interface TokenVerificationResult {
13
13
  reason?: string;
14
14
  payload?: TokenPayload;
15
15
  }
16
+ /** A token string that decodes, but not from its one canonical spelling. */
17
+ export declare class NonCanonicalTokenError extends Error {
18
+ constructor(message: string);
19
+ }
16
20
  export declare class TokenManager {
17
21
  private secret;
18
22
  private tokenExpiryMs;
23
+ /**
24
+ * @param secret HMAC key for commit tokens. Falls back to
25
+ * `MCP_OBSERVATORY_COMMIT_SECRET`. Throws `InsecureDefaultSecretError`
26
+ * when neither is set, unless `MCP_OBSERVATORY_ALLOW_DEV_SECRET=1`.
27
+ *
28
+ * This used to default to `randomBytes(32)`, which made the key
29
+ * per-instance: a restarted or scaled-out process could not verify a
30
+ * token it had issued, and the proposer and the verifier — each of which
31
+ * builds its own `TokenManager` when none is supplied — never shared a
32
+ * key at all. Both failures surfaced as `bad_signature`, which is what a
33
+ * forgery looks like, so missing configuration was indistinguishable
34
+ * from an attack. Resolving the secret explicitly moves that failure to
35
+ * construction time, where the message can name the cause.
36
+ */
19
37
  constructor(secret?: string);
20
38
  issueToken(options: {
21
39
  proposalId: string;
@@ -1 +1 @@
1
- {"version":3,"file":"token.d.ts","sourceRoot":"","sources":["../../src/proposal/token.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,YAAY,CAAC;CACxB;AAED,qBAAa,YAAY;IACvB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,aAAa,CAAkB;gBAE3B,MAAM,CAAC,EAAE,MAAM;IAI3B,UAAU,CAAC,OAAO,EAAE;QAClB,UAAU,EAAE,MAAM,CAAC;QACnB,QAAQ,EAAE,MAAM,CAAC;QACjB,YAAY,EAAE,MAAM,CAAC;QACrB,cAAc,EAAE,MAAM,CAAC;QACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,GAAG;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,YAAY,CAAA;KAAE;IAuB5C,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,uBAAuB;IAuBnD,OAAO,CAAC,IAAI;IAKZ,OAAO,CAAC,QAAQ;CAGjB"}
1
+ {"version":3,"file":"token.d.ts","sourceRoot":"","sources":["../../src/proposal/token.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,YAAY,CAAC;CACxB;AAED,4EAA4E;AAC5E,qBAAa,sBAAuB,SAAQ,KAAK;gBACnC,OAAO,EAAE,MAAM;CAK5B;AAmDD,qBAAa,YAAY;IACvB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,aAAa,CAAkB;IAEvC;;;;;;;;;;;;;OAaG;gBACS,MAAM,CAAC,EAAE,MAAM;IAW3B,UAAU,CAAC,OAAO,EAAE;QAClB,UAAU,EAAE,MAAM,CAAC;QACnB,QAAQ,EAAE,MAAM,CAAC;QACjB,YAAY,EAAE,MAAM,CAAC;QACrB,cAAc,EAAE,MAAM,CAAC;QACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,GAAG;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,YAAY,CAAA;KAAE;IAuB5C,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,uBAAuB;IAgCnD,OAAO,CAAC,IAAI;IAKZ,OAAO,CAAC,QAAQ;CAGjB"}
@@ -1,9 +1,82 @@
1
1
  import { createHmac, randomBytes } from 'crypto';
2
2
  import { getCurrentTimeMs, addMs } from '../utils/time.js';
3
+ import { COMMIT_SECRET_ENV, DEV_COMMIT_SECRET, resolveSecret, } from '../utils/secrets.js';
4
+ /** A token string that decodes, but not from its one canonical spelling. */
5
+ export class NonCanonicalTokenError extends Error {
6
+ constructor(message) {
7
+ super(message);
8
+ this.name = 'NonCanonicalTokenError';
9
+ Object.setPrototypeOf(this, NonCanonicalTokenError.prototype);
10
+ }
11
+ }
12
+ /**
13
+ * Decode a token, accepting only the exact string the issuer would produce.
14
+ *
15
+ * The HMAC covers the payload's *values*, not the token *string*, and both
16
+ * layers of the encoding are lenient, so one authorisation had unboundedly
17
+ * many accepted spellings:
18
+ *
19
+ * - Node's base64 decoder silently drops characters outside the alphabet,
20
+ * so `token + "!"`, `token + "=="`, a trailing newline and whitespace
21
+ * injected anywhere in the middle all decoded to identical bytes and
22
+ * verified.
23
+ * - The signature is recomputed from the re-serialised payload object
24
+ * rather than from the received bytes, so any JSON spelling that parses
25
+ * to the same object verified too — a pretty-printed payload, or one
26
+ * with a space after `{`.
27
+ *
28
+ * Replay is still caught either way, because the nonce is inside the
29
+ * signed payload. The cost is in anything keyed on the token string: a
30
+ * caller that records `sha256(token)` on a span or an audit row would file
31
+ * one authorisation under arbitrarily many hashes, and a nonce cache keyed
32
+ * on the string rather than on `payload.nonce` would not catch a replay at
33
+ * all.
34
+ *
35
+ * Requiring both round-trips — re-encoding the bytes must reproduce the
36
+ * base64, and re-serialising the object must reproduce the JSON — makes
37
+ * the token string canonical, so `sha256(token)` identifies exactly one
38
+ * authorisation. This is the same property the Python line states as P3 in
39
+ * `docs/gate-properties.md`; note the encoding here is plain base64, not
40
+ * the base64url Python uses, so the re-encode is checked against the
41
+ * padded standard alphabet that `issueToken` emits.
42
+ */
43
+ function decodeCanonicalToken(token) {
44
+ const raw = Buffer.from(token, 'base64');
45
+ if (raw.toString('base64') !== token) {
46
+ throw new NonCanonicalTokenError('token is not canonical base64');
47
+ }
48
+ const json = raw.toString('utf8');
49
+ const data = JSON.parse(json);
50
+ if (data === null || typeof data !== 'object' || Array.isArray(data)) {
51
+ throw new NonCanonicalTokenError('token payload is not an object');
52
+ }
53
+ if (JSON.stringify(data) !== json) {
54
+ throw new NonCanonicalTokenError('token payload is not canonical JSON');
55
+ }
56
+ return data;
57
+ }
3
58
  export class TokenManager {
59
+ /**
60
+ * @param secret HMAC key for commit tokens. Falls back to
61
+ * `MCP_OBSERVATORY_COMMIT_SECRET`. Throws `InsecureDefaultSecretError`
62
+ * when neither is set, unless `MCP_OBSERVATORY_ALLOW_DEV_SECRET=1`.
63
+ *
64
+ * This used to default to `randomBytes(32)`, which made the key
65
+ * per-instance: a restarted or scaled-out process could not verify a
66
+ * token it had issued, and the proposer and the verifier — each of which
67
+ * builds its own `TokenManager` when none is supplied — never shared a
68
+ * key at all. Both failures surfaced as `bad_signature`, which is what a
69
+ * forgery looks like, so missing configuration was indistinguishable
70
+ * from an attack. Resolving the secret explicitly moves that failure to
71
+ * construction time, where the message can name the cause.
72
+ */
4
73
  constructor(secret) {
5
74
  this.tokenExpiryMs = 300000; // 5 minutes
6
- this.secret = secret ? Buffer.from(secret) : randomBytes(32);
75
+ this.secret = Buffer.from(resolveSecret(secret, {
76
+ envVar: COMMIT_SECRET_ENV,
77
+ devDefault: DEV_COMMIT_SECRET,
78
+ label: COMMIT_SECRET_ENV,
79
+ }), 'utf8');
7
80
  }
8
81
  issueToken(options) {
9
82
  const now = getCurrentTimeMs();
@@ -23,8 +96,17 @@ export class TokenManager {
23
96
  return { token, payload };
24
97
  }
25
98
  verifyToken(token) {
99
+ let data;
100
+ try {
101
+ data = decodeCanonicalToken(token);
102
+ }
103
+ catch (error) {
104
+ if (error instanceof NonCanonicalTokenError) {
105
+ return { valid: false, reason: 'non_canonical_token' };
106
+ }
107
+ return { valid: false, reason: 'invalid_token_format' };
108
+ }
26
109
  try {
27
- const data = JSON.parse(Buffer.from(token, 'base64').toString());
28
110
  const { signature, ...payload } = data;
29
111
  if (!signature) {
30
112
  return { valid: false, reason: 'missing_signature' };
@@ -1 +1 @@
1
- {"version":3,"file":"token.js","sourceRoot":"","sources":["../../src/proposal/token.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"token.js","sourceRoot":"","sources":["../../src/proposal/token.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,12 @@
1
1
  import { test } from 'node:test';
2
2
  import { strict as assert } from 'node:assert';
3
3
  import { ToolProposer } from '../proposal/proposer.js';
4
+ // `ToolProposer` builds a `TokenManager` when none is supplied, and that
5
+ // constructor now refuses to invent a signing secret. Tests opt into the
6
+ // public development secret. The secret is read when a manager is
7
+ // constructed, not when the module loads, so setting it here is enough;
8
+ // `node --test` gives each file its own process, so it does not leak.
9
+ process.env.MCP_OBSERVATORY_ALLOW_DEV_SECRET = '1';
4
10
  test('Proposer creates proposals', async () => {
5
11
  const proposer = new ToolProposer();
6
12
  const result = await proposer.propose({
@@ -1 +1 @@
1
- {"version":3,"file":"proposer.test.js","sourceRoot":"","sources":["../../src/tests/proposer.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,MAAM,IAAI,MAAM,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAGvD,IAAI,CAAC,4BAA4B,EAAE,KAAK,IAAI,EAAE;IAC5C,MAAM,QAAQ,GAAG,IAAI,YAAY,EAAE,CAAC;IAEpC,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC;QACpC,QAAQ,EAAE,gBAAgB;QAC1B,QAAQ,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,EAAE,EAAE,UAAU,EAAE;QACzC,iBAAiB,EAAE,GAAG;KACvB,CAAC,CAAC;IAEH,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IAC7B,MAAM,CAAC,EAAE,CAAC,CAAC,SAAS,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;AACtE,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,qCAAqC,EAAE,KAAK,IAAI,EAAE;IACrD,MAAM,QAAQ,GAAG,IAAI,YAAY,EAAE,CAAC;IAEpC,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC;QACpC,QAAQ,EAAE,gBAAgB;QAC1B,QAAQ,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,EAAE,SAAS,EAAE;QAC5C,iBAAiB,EAAE,GAAG;QACtB,eAAe,EAAE,GAAG;QACpB,WAAW,EAAE,GAAG;KACjB,CAAC,CAAC;IAEH,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IACvC,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC;AACrC,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,oCAAoC,EAAE,KAAK,IAAI,EAAE;IACpD,MAAM,QAAQ,GAAG,IAAI,YAAY,EAAE,CAAC;IAEpC,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC;QACpC,QAAQ,EAAE,kBAAkB;QAC5B,QAAQ,EAAE,EAAE,SAAS,EAAE,UAAU,EAAE;QACnC,iBAAiB,EAAE,IAAI;QACvB,eAAe,EAAE,IAAI;QACrB,WAAW,EAAE,IAAI;KAClB,CAAC,CAAC;IAEH,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IACvC,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;AAChC,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,0DAA0D,EAAE,KAAK,IAAI,EAAE;IAC1E,MAAM,QAAQ,GAAG,IAAI,YAAY,EAAE,CAAC;IAEpC,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC;QACpC,QAAQ,EAAE,WAAW;QACrB,QAAQ,EAAE,EAAE,IAAI,EAAE,eAAe,EAAE;QACnC,iBAAiB,EAAE,GAAG;KACvB,CAAC,CAAC;IAEH,IAAI,MAAM,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;QAChC,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;IAChC,CAAC;AACH,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"proposer.test.js","sourceRoot":"","sources":["../../src/tests/proposer.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,MAAM,IAAI,MAAM,EAAE,MAAM,aAAa,CAAC;AAE/C,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAGvD,yEAAyE;AACzE,yEAAyE;AACzE,kEAAkE;AAClE,wEAAwE;AACxE,sEAAsE;AACtE,OAAO,CAAC,GAAG,CAAC,gCAAgC,GAAG,GAAG,CAAC;AAEnD,IAAI,CAAC,4BAA4B,EAAE,KAAK,IAAI,EAAE;IAC5C,MAAM,QAAQ,GAAG,IAAI,YAAY,EAAE,CAAC;IAEpC,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC;QACpC,QAAQ,EAAE,gBAAgB;QAC1B,QAAQ,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,EAAE,EAAE,UAAU,EAAE;QACzC,iBAAiB,EAAE,GAAG;KACvB,CAAC,CAAC;IAEH,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IAC7B,MAAM,CAAC,EAAE,CAAC,CAAC,SAAS,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;AACtE,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,qCAAqC,EAAE,KAAK,IAAI,EAAE;IACrD,MAAM,QAAQ,GAAG,IAAI,YAAY,EAAE,CAAC;IAEpC,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC;QACpC,QAAQ,EAAE,gBAAgB;QAC1B,QAAQ,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,EAAE,SAAS,EAAE;QAC5C,iBAAiB,EAAE,GAAG;QACtB,eAAe,EAAE,GAAG;QACpB,WAAW,EAAE,GAAG;KACjB,CAAC,CAAC;IAEH,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IACvC,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC;AACrC,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,oCAAoC,EAAE,KAAK,IAAI,EAAE;IACpD,MAAM,QAAQ,GAAG,IAAI,YAAY,EAAE,CAAC;IAEpC,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC;QACpC,QAAQ,EAAE,kBAAkB;QAC5B,QAAQ,EAAE,EAAE,SAAS,EAAE,UAAU,EAAE;QACnC,iBAAiB,EAAE,IAAI;QACvB,eAAe,EAAE,IAAI;QACrB,WAAW,EAAE,IAAI;KAClB,CAAC,CAAC;IAEH,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IACvC,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;AAChC,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,0DAA0D,EAAE,KAAK,IAAI,EAAE;IAC1E,MAAM,QAAQ,GAAG,IAAI,YAAY,EAAE,CAAC;IAEpC,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC;QACpC,QAAQ,EAAE,WAAW;QACrB,QAAQ,EAAE,EAAE,IAAI,EAAE,eAAe,EAAE;QACnC,iBAAiB,EAAE,GAAG;KACvB,CAAC,CAAC;IAEH,IAAI,MAAM,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;QAChC,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;IAChC,CAAC;AACH,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=proposerGuards.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proposerGuards.test.d.ts","sourceRoot":"","sources":["../../src/tests/proposerGuards.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,95 @@
1
+ /**
2
+ * The two fail-opens the guards exist to prevent.
3
+ *
4
+ * Both were reachable from the ordinary API and both returned a usable commit
5
+ * token, so each test asserts the absence of the token as well as the status.
6
+ */
7
+ import { test } from 'node:test';
8
+ import assert from 'node:assert/strict';
9
+ import { ToolProposer } from '../proposal/proposer.js';
10
+ import { DEFAULT_MAX_INPUT_BYTES, MAX_INPUT_BYTES_ENV, argsExceedLimit, exceedsLimit, maxInputBytes, } from '../utils/limits.js';
11
+ process.env.MCP_OBSERVATORY_ALLOW_DEV_SECRET = '1';
12
+ // --- no evidence must not read as evidence of safety ------------------------
13
+ test('a proposal carrying no signals is refused, not allowed', async () => {
14
+ const result = await new ToolProposer().propose({
15
+ toolName: 'delete_all_users',
16
+ toolArgs: { confirm: true },
17
+ });
18
+ assert.equal(result.status, 'blocked');
19
+ assert.equal(result.reason, 'no_signals');
20
+ assert.equal(result.commitToken, undefined);
21
+ });
22
+ test('a genuine zero signal is an observation and still scores', async () => {
23
+ // Distinct from the case above: 0 is a real measurement, not an absence.
24
+ const result = await new ToolProposer().propose({
25
+ toolName: 'read_file',
26
+ toolArgs: {},
27
+ outputInstability: 0,
28
+ });
29
+ assert.equal(result.status, 'allowed');
30
+ assert.ok(result.commitToken);
31
+ });
32
+ test('one supplied signal is renormalised, not diluted by the absent ones', async () => {
33
+ // Instability 1.0 alone: weight 0.5 over a defined weight of 0.5 is 1.0, so
34
+ // it clears the 0.7 block threshold. Under the old fixed denominator it
35
+ // scored 0.5 and was merely 'review' — the absent signals voted "safe".
36
+ const result = await new ToolProposer().propose({
37
+ toolName: 'drop_table',
38
+ toolArgs: {},
39
+ outputInstability: 1,
40
+ });
41
+ assert.equal(result.status, 'blocked');
42
+ assert.equal(result.reason, 'low_integrity');
43
+ });
44
+ // --- input size is bounded upstream of hashing ------------------------------
45
+ test('an oversized argument mapping is refused before it is hashed', async () => {
46
+ const result = await new ToolProposer().propose({
47
+ toolName: 'upload',
48
+ toolArgs: { blob: 'A'.repeat(DEFAULT_MAX_INPUT_BYTES * 2) },
49
+ outputInstability: 0,
50
+ });
51
+ assert.equal(result.status, 'blocked');
52
+ assert.equal(result.reason, 'input_too_large');
53
+ assert.equal(result.commitToken, undefined);
54
+ });
55
+ test('the ceiling is configurable and a malformed value does not disable it', () => {
56
+ const original = process.env[MAX_INPUT_BYTES_ENV];
57
+ try {
58
+ process.env[MAX_INPUT_BYTES_ENV] = '64';
59
+ assert.equal(maxInputBytes(), 64);
60
+ assert.equal(exceedsLimit('A'.repeat(65)), true);
61
+ assert.equal(exceedsLimit('A'.repeat(64)), false);
62
+ for (const bad of ['0', '-1', 'not-a-number', '']) {
63
+ process.env[MAX_INPUT_BYTES_ENV] = bad;
64
+ assert.equal(maxInputBytes(), DEFAULT_MAX_INPUT_BYTES, `bad value ${bad}`);
65
+ }
66
+ }
67
+ finally {
68
+ if (original === undefined)
69
+ delete process.env[MAX_INPUT_BYTES_ENV];
70
+ else
71
+ process.env[MAX_INPUT_BYTES_ENV] = original;
72
+ }
73
+ });
74
+ test('size is measured in UTF-8 bytes, not characters', () => {
75
+ const original = process.env[MAX_INPUT_BYTES_ENV];
76
+ try {
77
+ process.env[MAX_INPUT_BYTES_ENV] = '10';
78
+ // Five 3-byte characters are 15 bytes despite being 5 characters.
79
+ assert.equal(exceedsLimit('あいうえお'), true);
80
+ }
81
+ finally {
82
+ if (original === undefined)
83
+ delete process.env[MAX_INPUT_BYTES_ENV];
84
+ else
85
+ process.env[MAX_INPUT_BYTES_ENV] = original;
86
+ }
87
+ });
88
+ test('an unserialisable argument mapping counts as over the limit', () => {
89
+ const circular = {};
90
+ circular.self = circular;
91
+ assert.equal(argsExceedLimit(circular), true);
92
+ assert.equal(argsExceedLimit({ n: 1n }), true);
93
+ assert.equal(argsExceedLimit({ ok: 'small' }), false);
94
+ });
95
+ //# sourceMappingURL=proposerGuards.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proposerGuards.test.js","sourceRoot":"","sources":["../../src/tests/proposerGuards.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=secrets.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secrets.test.d.ts","sourceRoot":"","sources":["../../src/tests/secrets.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,92 @@
1
+ import { test } from 'node:test';
2
+ import { strict as assert } from 'node:assert';
3
+ import { TokenManager } from '../proposal/token.js';
4
+ import { ALLOW_DEV_SECRET_ENV, COMMIT_SECRET_ENV, DEV_COMMIT_SECRET, InsecureDefaultSecretError, } from '../utils/secrets.js';
5
+ function withEnv(env, fn) {
6
+ const saved = {};
7
+ for (const key of Object.keys(env)) {
8
+ saved[key] = process.env[key];
9
+ if (env[key] === undefined)
10
+ delete process.env[key];
11
+ else
12
+ process.env[key] = env[key];
13
+ }
14
+ try {
15
+ return fn();
16
+ }
17
+ finally {
18
+ for (const key of Object.keys(saved)) {
19
+ if (saved[key] === undefined)
20
+ delete process.env[key];
21
+ else
22
+ process.env[key] = saved[key];
23
+ }
24
+ }
25
+ }
26
+ test('TokenManager construction throws when no secret is configured', async () => {
27
+ withEnv({ [COMMIT_SECRET_ENV]: undefined, [ALLOW_DEV_SECRET_ENV]: undefined }, () => {
28
+ assert.throws(() => new TokenManager(), (error) => {
29
+ assert.ok(error instanceof InsecureDefaultSecretError);
30
+ assert.equal(error.name, 'InsecureDefaultSecretError');
31
+ // The message must name the configuration that is missing;
32
+ // a bare `bad_signature` at verify time is what this replaces.
33
+ assert.match(error.message, new RegExp(COMMIT_SECRET_ENV));
34
+ return true;
35
+ });
36
+ });
37
+ });
38
+ test('TokenManager construction throws when the dev secret is passed explicitly', async () => {
39
+ withEnv({ [ALLOW_DEV_SECRET_ENV]: undefined }, () => {
40
+ assert.throws(() => new TokenManager(DEV_COMMIT_SECRET), InsecureDefaultSecretError);
41
+ });
42
+ });
43
+ test('TokenManager reads the secret from the environment', async () => {
44
+ const token = withEnv({ [COMMIT_SECRET_ENV]: 'env-provided-secret', [ALLOW_DEV_SECRET_ENV]: undefined }, () => {
45
+ const manager = new TokenManager();
46
+ return manager.issueToken({
47
+ proposalId: 'p1',
48
+ toolName: 'transfer_funds',
49
+ toolArgsHash: 'hash',
50
+ compositeScore: 0.1,
51
+ }).token;
52
+ });
53
+ // A second manager built from the same environment verifies the token:
54
+ // this is what a restart or a second replica does, and what a
55
+ // per-instance random secret made impossible.
56
+ const verified = withEnv({ [COMMIT_SECRET_ENV]: 'env-provided-secret', [ALLOW_DEV_SECRET_ENV]: undefined }, () => new TokenManager().verifyToken(token));
57
+ assert.equal(verified.valid, true);
58
+ });
59
+ test('TokenManager construction succeeds under the dev-secret override', async () => {
60
+ withEnv({ [COMMIT_SECRET_ENV]: undefined, [ALLOW_DEV_SECRET_ENV]: '1' }, () => {
61
+ const a = new TokenManager();
62
+ const b = new TokenManager();
63
+ const { token } = a.issueToken({
64
+ proposalId: 'p1',
65
+ toolName: 'transfer_funds',
66
+ toolArgsHash: 'hash',
67
+ compositeScore: 0.1,
68
+ });
69
+ // Two independently constructed managers now agree on the key.
70
+ assert.equal(b.verifyToken(token).valid, true);
71
+ });
72
+ });
73
+ test('demo server completes the commit phase it previously failed', async () => {
74
+ const { MCPServer } = await import('../demo/server.js');
75
+ const server = await withEnv({ [COMMIT_SECRET_ENV]: undefined, [ALLOW_DEV_SECRET_ENV]: '1' }, () => new MCPServer());
76
+ const proposal = (await server.handleToolCall({
77
+ toolName: 'transfer_funds_propose',
78
+ args: { amount: 1000, to: 'acct_bob' },
79
+ }));
80
+ assert.notEqual(proposal.status, 'blocked');
81
+ const commit = (await server.handleToolCall({
82
+ toolName: 'transfer_funds_commit',
83
+ proposalId: proposal.proposalId,
84
+ commitToken: proposal.commitToken,
85
+ args: { amount: 1000, to: 'acct_bob' },
86
+ }));
87
+ // Before the fix this was `{ success: false, reason: 'bad_signature' }`,
88
+ // because the proposer and the verifier each held their own random key.
89
+ assert.equal(commit.reason, undefined);
90
+ assert.equal(commit.success, true);
91
+ });
92
+ //# sourceMappingURL=secrets.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secrets.test.js","sourceRoot":"","sources":["../../src/tests/secrets.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=wrapper.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wrapper.test.d.ts","sourceRoot":"","sources":["../../src/tests/wrapper.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,141 @@
1
+ import { test } from 'node:test';
2
+ import { strict as assert } from 'node:assert';
3
+ import { InvocationWrapper } from '../core/wrapper.js';
4
+ import { Tracer } from '../core/tracer.js';
5
+ import { hashText } from '../utils/hashing.js';
6
+ import { estimateTokens } from '../cost/tokenizer.js';
7
+ class BoomError extends Error {
8
+ constructor(message) {
9
+ super(message);
10
+ this.name = 'BoomError';
11
+ }
12
+ }
13
+ /**
14
+ * `invoke()` never hands its internal span back out on the failure path (it
15
+ * only throws), so to assert on span state after a rejection we spy on
16
+ * `Tracer.prototype.startSpan` and capture every span it creates.
17
+ */
18
+ function captureSpans() {
19
+ const original = Tracer.prototype.startSpan;
20
+ const spans = [];
21
+ Tracer.prototype.startSpan = function (...args) {
22
+ const span = original.apply(this, args);
23
+ spans.push(span);
24
+ return span;
25
+ };
26
+ return {
27
+ spans,
28
+ restore: () => {
29
+ Tracer.prototype.startSpan = original;
30
+ },
31
+ };
32
+ }
33
+ test('a rejecting sync call() propagates the original error unchanged', async () => {
34
+ const wrapper = new InvocationWrapper('test-service');
35
+ const original = new BoomError('tool exploded');
36
+ await assert.rejects(() => wrapper.invoke({
37
+ source: 'model',
38
+ prompt: 'do the thing',
39
+ call: () => {
40
+ throw original;
41
+ },
42
+ }), (error) => {
43
+ // Must be the exact same error instance -- not a TypeError from
44
+ // hashText(undefined) replacing it in the `finally` block.
45
+ assert.equal(error, original);
46
+ assert.ok(error instanceof BoomError);
47
+ assert.equal(error.message, 'tool exploded');
48
+ return true;
49
+ });
50
+ });
51
+ test('a rejecting async call() propagates the original error unchanged', async () => {
52
+ const wrapper = new InvocationWrapper('test-service');
53
+ const original = new Error('async rejection');
54
+ await assert.rejects(() => wrapper.invoke({
55
+ source: 'model',
56
+ prompt: 'do the thing',
57
+ call: async () => {
58
+ throw original;
59
+ },
60
+ }), (error) => {
61
+ assert.equal(error, original);
62
+ assert.equal(error.message, 'async rejection');
63
+ return true;
64
+ });
65
+ });
66
+ test('failed call: span is still finished with statusCode 500, no output hash, zero output tokens', async () => {
67
+ const wrapper = new InvocationWrapper('test-service');
68
+ const capture = captureSpans();
69
+ try {
70
+ await assert.rejects(() => wrapper.invoke({
71
+ source: 'model',
72
+ prompt: 'do the thing',
73
+ call: () => {
74
+ throw new Error('boom');
75
+ },
76
+ }));
77
+ assert.equal(capture.spans.length, 1);
78
+ const [span] = capture.spans;
79
+ assert.equal(span.statusCode, 500);
80
+ assert.ok(span.endTime, 'span.finish() should have been called');
81
+ assert.equal(span.outputHash, undefined);
82
+ assert.equal(span.outputTokens, 0);
83
+ assert.equal(span.totalTokens, span.inputTokens || 0);
84
+ assert.equal(typeof span.costUsd, 'number');
85
+ }
86
+ finally {
87
+ capture.restore();
88
+ }
89
+ });
90
+ test('successful call still produces the pre-fix outputHash and outputTokens', async () => {
91
+ const wrapper = new InvocationWrapper('test-service');
92
+ const output = { result: 'ok', count: 3 };
93
+ const result = await wrapper.invoke({
94
+ source: 'model',
95
+ prompt: 'do the thing',
96
+ call: () => output,
97
+ });
98
+ const expectedText = JSON.stringify(output);
99
+ assert.equal(result.span.outputHash, hashText(expectedText));
100
+ assert.equal(result.span.outputTokens, estimateTokens(expectedText, undefined));
101
+ assert.equal(result.span.totalTokens, (result.span.inputTokens || 0) + result.span.outputTokens);
102
+ assert.equal(result.output, output);
103
+ assert.notEqual(result.span.endTime, undefined);
104
+ });
105
+ test('invoke() does not throw when a successful output contains a BigInt (JSON.stringify would throw)', async () => {
106
+ const wrapper = new InvocationWrapper('test-service');
107
+ const output = { big: 10n };
108
+ const result = await wrapper.invoke({
109
+ source: 'model',
110
+ prompt: 'do the thing',
111
+ call: () => output,
112
+ });
113
+ assert.ok(result.span.outputHash);
114
+ assert.equal(typeof result.span.outputTokens, 'number');
115
+ assert.equal(result.decision.action, 'allow');
116
+ });
117
+ test('invoke() does not throw when a successful output is circular (JSON.stringify would throw)', async () => {
118
+ const wrapper = new InvocationWrapper('test-service');
119
+ const output = { name: 'circular' };
120
+ output.self = output;
121
+ const result = await wrapper.invoke({
122
+ source: 'model',
123
+ prompt: 'do the thing',
124
+ call: () => output,
125
+ });
126
+ assert.ok(result.span.outputHash);
127
+ assert.equal(typeof result.span.outputTokens, 'number');
128
+ assert.equal(result.decision.action, 'allow');
129
+ });
130
+ test('invoke() does not throw when a successful output is undefined (JSON.stringify returns the value undefined)', async () => {
131
+ const wrapper = new InvocationWrapper('test-service');
132
+ const result = await wrapper.invoke({
133
+ source: 'model',
134
+ prompt: 'do the thing',
135
+ call: () => undefined,
136
+ });
137
+ // Empty/undefined output is a legitimate 'blocked' decision, not a crash.
138
+ assert.equal(result.decision.action, 'block');
139
+ assert.equal(result.output, undefined);
140
+ });
141
+ //# sourceMappingURL=wrapper.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wrapper.test.js","sourceRoot":"","sources":["../../src/tests/wrapper.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Input-size ceiling applied before anything expensive touches a payload.
3
+ *
4
+ * Every step of the gate is linear in the size of its input — canonical JSON
5
+ * serialisation, SHA-256 hashing, the signal computations — so the gate cannot
6
+ * bound its own latency. A caller (or an attacker) handing it a multi-megabyte
7
+ * argument mapping buys multi-megabyte hashing on the request path.
8
+ *
9
+ * The control therefore has to sit *upstream of scoring*: refuse the call
10
+ * before hashing rather than after. This mirrors the Python line's P5, where
11
+ * the same bound is documented in `docs/gate-properties.md` and the same
12
+ * `MCP_OBSERVATORY_MAX_INPUT_BYTES` variable names the ceiling.
13
+ *
14
+ * Read from the environment on each call rather than cached at module load, so
15
+ * a test or a host can change it without re-importing the module.
16
+ */
17
+ export declare const MAX_INPUT_BYTES_ENV = "MCP_OBSERVATORY_MAX_INPUT_BYTES";
18
+ /** Default ceiling, in bytes. Matches the Python package's default. */
19
+ export declare const DEFAULT_MAX_INPUT_BYTES = 10240;
20
+ export declare function maxInputBytes(): number;
21
+ /** True when `value`'s UTF-8 length exceeds the configured ceiling. */
22
+ export declare function exceedsLimit(value: string | undefined | null): boolean;
23
+ /**
24
+ * True when any argument mapping is over the ceiling.
25
+ *
26
+ * Serialising to measure is itself linear, but it is one pass with no hashing
27
+ * and no signal work behind it, which is the cost the ceiling exists to avoid.
28
+ * A value that cannot be serialised at all (a circular reference, a BigInt) is
29
+ * treated as over the limit: the gate cannot hash what it cannot serialise, so
30
+ * refusing is the fail-secure reading rather than proceeding to a crash.
31
+ */
32
+ export declare function argsExceedLimit(args: unknown): boolean;
33
+ //# sourceMappingURL=limits.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"limits.d.ts","sourceRoot":"","sources":["../../src/utils/limits.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,eAAO,MAAM,mBAAmB,oCAAoC,CAAC;AAErE,uEAAuE;AACvE,eAAO,MAAM,uBAAuB,QAAQ,CAAC;AAE7C,wBAAgB,aAAa,IAAI,MAAM,CAOtC;AAED,uEAAuE;AACvE,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,OAAO,CAGtE;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAQtD"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Input-size ceiling applied before anything expensive touches a payload.
3
+ *
4
+ * Every step of the gate is linear in the size of its input — canonical JSON
5
+ * serialisation, SHA-256 hashing, the signal computations — so the gate cannot
6
+ * bound its own latency. A caller (or an attacker) handing it a multi-megabyte
7
+ * argument mapping buys multi-megabyte hashing on the request path.
8
+ *
9
+ * The control therefore has to sit *upstream of scoring*: refuse the call
10
+ * before hashing rather than after. This mirrors the Python line's P5, where
11
+ * the same bound is documented in `docs/gate-properties.md` and the same
12
+ * `MCP_OBSERVATORY_MAX_INPUT_BYTES` variable names the ceiling.
13
+ *
14
+ * Read from the environment on each call rather than cached at module load, so
15
+ * a test or a host can change it without re-importing the module.
16
+ */
17
+ export const MAX_INPUT_BYTES_ENV = 'MCP_OBSERVATORY_MAX_INPUT_BYTES';
18
+ /** Default ceiling, in bytes. Matches the Python package's default. */
19
+ export const DEFAULT_MAX_INPUT_BYTES = 10240;
20
+ export function maxInputBytes() {
21
+ const raw = process.env[MAX_INPUT_BYTES_ENV];
22
+ if (raw === undefined || raw === '')
23
+ return DEFAULT_MAX_INPUT_BYTES;
24
+ const parsed = Number(raw);
25
+ // A malformed ceiling must not silently disable the limit.
26
+ if (!Number.isFinite(parsed) || parsed <= 0)
27
+ return DEFAULT_MAX_INPUT_BYTES;
28
+ return Math.floor(parsed);
29
+ }
30
+ /** True when `value`'s UTF-8 length exceeds the configured ceiling. */
31
+ export function exceedsLimit(value) {
32
+ if (value === undefined || value === null)
33
+ return false;
34
+ return Buffer.byteLength(value, 'utf8') > maxInputBytes();
35
+ }
36
+ /**
37
+ * True when any argument mapping is over the ceiling.
38
+ *
39
+ * Serialising to measure is itself linear, but it is one pass with no hashing
40
+ * and no signal work behind it, which is the cost the ceiling exists to avoid.
41
+ * A value that cannot be serialised at all (a circular reference, a BigInt) is
42
+ * treated as over the limit: the gate cannot hash what it cannot serialise, so
43
+ * refusing is the fail-secure reading rather than proceeding to a crash.
44
+ */
45
+ export function argsExceedLimit(args) {
46
+ let serialised;
47
+ try {
48
+ serialised = JSON.stringify(args) ?? '';
49
+ }
50
+ catch {
51
+ return true;
52
+ }
53
+ return exceedsLimit(serialised);
54
+ }
55
+ //# sourceMappingURL=limits.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"limits.js","sourceRoot":"","sources":["../../src/utils/limits.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,MAAM,CAAC,MAAM,mBAAmB,GAAG,iCAAiC,CAAC;AAErE,uEAAuE;AACvE,MAAM,CAAC,MAAM,uBAAuB,GAAG,KAAK,CAAC;AAE7C,MAAM,UAAU,aAAa;IAC3B,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC;IAC7C,IAAI,GAAG,KAAK,SAAS,IAAI,GAAG,KAAK,EAAE;QAAE,OAAO,uBAAuB,CAAC;IACpE,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;IAC3B,2DAA2D;IAC3D,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,MAAM,IAAI,CAAC;QAAE,OAAO,uBAAuB,CAAC;IAC5E,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;AAC5B,CAAC;AAED,uEAAuE;AACvE,MAAM,UAAU,YAAY,CAAC,KAAgC;IAC3D,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IACxD,OAAO,MAAM,CAAC,UAAU,CAAC,KAAK,EAAE,MAAM,CAAC,GAAG,aAAa,EAAE,CAAC;AAC5D,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,eAAe,CAAC,IAAa;IAC3C,IAAI,UAAkB,CAAC;IACvB,IAAI,CAAC;QACH,UAAU,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;IAC1C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,YAAY,CAAC,UAAU,CAAC,CAAC;AAClC,CAAC"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Fail-closed resolution for the HMAC signing secret used by commit tokens.
3
+ *
4
+ * `TokenManager` used to fall back to `randomBytes(32)` whenever no secret
5
+ * was passed. That is worse than a hardcoded default rather than better:
6
+ * the key is unguessable, but it is also unshareable. Two `TokenManager`
7
+ * instances in one process (the bundled demo builds one inside
8
+ * `ToolProposer` and another inside `CommitVerifier`) never agreed on a
9
+ * key, so every commit failed with `bad_signature`; and a process that
10
+ * restarted, or a second replica behind a load balancer, could not verify
11
+ * a token it had itself issued moments earlier. The failure surfaced as
12
+ * `bad_signature` — indistinguishable from an actual forgery — with
13
+ * nothing pointing at the missing configuration that caused it.
14
+ *
15
+ * `resolveSecret` makes the development key explicit and opt-in instead:
16
+ * the secret comes from the caller or the environment, and anything else
17
+ * throws at construction time, where the cause is obvious, rather than at
18
+ * verification time, where it is not.
19
+ */
20
+ export declare const COMMIT_SECRET_ENV = "MCP_OBSERVATORY_COMMIT_SECRET";
21
+ export declare const ALLOW_DEV_SECRET_ENV = "MCP_OBSERVATORY_ALLOW_DEV_SECRET";
22
+ /**
23
+ * The documented development secret. It is public — it is in this file and
24
+ * in every clone of the repository — and is only ever used when
25
+ * `MCP_OBSERVATORY_ALLOW_DEV_SECRET=1` asks for it explicitly.
26
+ */
27
+ export declare const DEV_COMMIT_SECRET = "dev-commit-secret";
28
+ /** Raised when a signing secret would fall back to a known-insecure default. */
29
+ export declare class InsecureDefaultSecretError extends Error {
30
+ constructor(message: string);
31
+ }
32
+ export interface ResolveSecretOptions {
33
+ envVar: string;
34
+ devDefault: string;
35
+ label: string;
36
+ }
37
+ /**
38
+ * Resolve a signing secret, refusing to silently use a development default.
39
+ *
40
+ * Returns `explicit` when given, otherwise `process.env[envVar]`. If neither
41
+ * is set, or the resolved value is the known development default (someone
42
+ * copied it out of an example), the secret is treated as insecure: this
43
+ * throws unless `MCP_OBSERVATORY_ALLOW_DEV_SECRET=1`, in which case
44
+ * `devDefault` is returned and a warning is emitted once per process.
45
+ */
46
+ export declare function resolveSecret(explicit: string | undefined, options: ResolveSecretOptions): string;
47
+ //# sourceMappingURL=secrets.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secrets.d.ts","sourceRoot":"","sources":["../../src/utils/secrets.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,eAAO,MAAM,iBAAiB,kCAAkC,CAAC;AACjE,eAAO,MAAM,oBAAoB,qCAAqC,CAAC;AAEvE;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,sBAAsB,CAAC;AAErD,gFAAgF;AAChF,qBAAa,0BAA2B,SAAQ,KAAK;gBACvC,OAAO,EAAE,MAAM;CAM5B;AAID,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,OAAO,EAAE,oBAAoB,GAC5B,MAAM,CA0BR"}
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Fail-closed resolution for the HMAC signing secret used by commit tokens.
3
+ *
4
+ * `TokenManager` used to fall back to `randomBytes(32)` whenever no secret
5
+ * was passed. That is worse than a hardcoded default rather than better:
6
+ * the key is unguessable, but it is also unshareable. Two `TokenManager`
7
+ * instances in one process (the bundled demo builds one inside
8
+ * `ToolProposer` and another inside `CommitVerifier`) never agreed on a
9
+ * key, so every commit failed with `bad_signature`; and a process that
10
+ * restarted, or a second replica behind a load balancer, could not verify
11
+ * a token it had itself issued moments earlier. The failure surfaced as
12
+ * `bad_signature` — indistinguishable from an actual forgery — with
13
+ * nothing pointing at the missing configuration that caused it.
14
+ *
15
+ * `resolveSecret` makes the development key explicit and opt-in instead:
16
+ * the secret comes from the caller or the environment, and anything else
17
+ * throws at construction time, where the cause is obvious, rather than at
18
+ * verification time, where it is not.
19
+ */
20
+ export const COMMIT_SECRET_ENV = 'MCP_OBSERVATORY_COMMIT_SECRET';
21
+ export const ALLOW_DEV_SECRET_ENV = 'MCP_OBSERVATORY_ALLOW_DEV_SECRET';
22
+ /**
23
+ * The documented development secret. It is public — it is in this file and
24
+ * in every clone of the repository — and is only ever used when
25
+ * `MCP_OBSERVATORY_ALLOW_DEV_SECRET=1` asks for it explicitly.
26
+ */
27
+ export const DEV_COMMIT_SECRET = 'dev-commit-secret';
28
+ /** Raised when a signing secret would fall back to a known-insecure default. */
29
+ export class InsecureDefaultSecretError extends Error {
30
+ constructor(message) {
31
+ super(message);
32
+ this.name = 'InsecureDefaultSecretError';
33
+ // Required for `instanceof` to work when compiled to ES2020 targets.
34
+ Object.setPrototypeOf(this, InsecureDefaultSecretError.prototype);
35
+ }
36
+ }
37
+ let devSecretWarned = false;
38
+ /**
39
+ * Resolve a signing secret, refusing to silently use a development default.
40
+ *
41
+ * Returns `explicit` when given, otherwise `process.env[envVar]`. If neither
42
+ * is set, or the resolved value is the known development default (someone
43
+ * copied it out of an example), the secret is treated as insecure: this
44
+ * throws unless `MCP_OBSERVATORY_ALLOW_DEV_SECRET=1`, in which case
45
+ * `devDefault` is returned and a warning is emitted once per process.
46
+ */
47
+ export function resolveSecret(explicit, options) {
48
+ const fromEnv = process.env[options.envVar];
49
+ const value = explicit !== undefined ? explicit : fromEnv;
50
+ if (value === undefined || value === '' || value === options.devDefault) {
51
+ if (process.env[ALLOW_DEV_SECRET_ENV] === '1') {
52
+ if (!devSecretWarned) {
53
+ devSecretWarned = true;
54
+ console.warn(`[mcp-observatory] ${ALLOW_DEV_SECRET_ENV}=1: signing commit tokens ` +
55
+ `with the public development secret. Tokens issued by this process ` +
56
+ `can be forged by anyone. Set ${options.envVar} for any real deployment.`);
57
+ }
58
+ return options.devDefault;
59
+ }
60
+ throw new InsecureDefaultSecretError(`${options.label} is unset (or set to the known development default). ` +
61
+ `Set ${options.envVar} to a strong, private secret shared by every ` +
62
+ `process that issues or verifies commit tokens, or set ` +
63
+ `${ALLOW_DEV_SECRET_ENV}=1 for local development, demos, and tests ` +
64
+ `only — never in production.`);
65
+ }
66
+ return value;
67
+ }
68
+ //# sourceMappingURL=secrets.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secrets.js","sourceRoot":"","sources":["../../src/utils/secrets.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,MAAM,CAAC,MAAM,iBAAiB,GAAG,+BAA+B,CAAC;AACjE,MAAM,CAAC,MAAM,oBAAoB,GAAG,kCAAkC,CAAC;AAEvE;;;;GAIG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,mBAAmB,CAAC;AAErD,gFAAgF;AAChF,MAAM,OAAO,0BAA2B,SAAQ,KAAK;IACnD,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,4BAA4B,CAAC;QACzC,qEAAqE;QACrE,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,0BAA0B,CAAC,SAAS,CAAC,CAAC;IACpE,CAAC;CACF;AAED,IAAI,eAAe,GAAG,KAAK,CAAC;AAQ5B;;;;;;;;GAQG;AACH,MAAM,UAAU,aAAa,CAC3B,QAA4B,EAC5B,OAA6B;IAE7B,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IAC5C,MAAM,KAAK,GAAG,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC;IAE1D,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,EAAE,IAAI,KAAK,KAAK,OAAO,CAAC,UAAU,EAAE,CAAC;QACxE,IAAI,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,KAAK,GAAG,EAAE,CAAC;YAC9C,IAAI,CAAC,eAAe,EAAE,CAAC;gBACrB,eAAe,GAAG,IAAI,CAAC;gBACvB,OAAO,CAAC,IAAI,CACV,qBAAqB,oBAAoB,4BAA4B;oBACnE,oEAAoE;oBACpE,gCAAgC,OAAO,CAAC,MAAM,2BAA2B,CAC5E,CAAC;YACJ,CAAC;YACD,OAAO,OAAO,CAAC,UAAU,CAAC;QAC5B,CAAC;QACD,MAAM,IAAI,0BAA0B,CAClC,GAAG,OAAO,CAAC,KAAK,uDAAuD;YACrE,OAAO,OAAO,CAAC,MAAM,+CAA+C;YACpE,wDAAwD;YACxD,GAAG,oBAAoB,6CAA6C;YACpE,6BAA6B,CAChC,CAAC;IACJ,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@weaveaijs/mcp-observatory",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Observability and APM toolkit for MCP servers",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",