@bryan-thompson/inspector-assessment-client 1.27.0 → 1.29.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.
- package/dist/assets/{OAuthCallback-CJWH8Ytw.js → OAuthCallback-9Gbb39Ii.js} +1 -1
- package/dist/assets/{OAuthDebugCallback-DL5adXJw.js → OAuthDebugCallback-B76J2MBn.js} +1 -1
- package/dist/assets/{index-Cu9XzUwB.js → index-CHTOR9VI.js} +77 -39
- package/dist/index.html +1 -1
- package/lib/lib/assessment/configTypes.d.ts +1 -0
- package/lib/lib/assessment/configTypes.d.ts.map +1 -1
- package/lib/lib/assessment/configTypes.js +10 -0
- package/lib/lib/assessment/extendedTypes.d.ts +74 -0
- package/lib/lib/assessment/extendedTypes.d.ts.map +1 -1
- package/lib/lib/assessment/resultTypes.d.ts +11 -1
- package/lib/lib/assessment/resultTypes.d.ts.map +1 -1
- package/lib/lib/securityPatterns.d.ts +8 -3
- package/lib/lib/securityPatterns.d.ts.map +1 -1
- package/lib/lib/securityPatterns.js +205 -3
- package/lib/services/assessment/AssessmentOrchestrator.d.ts +1 -0
- package/lib/services/assessment/AssessmentOrchestrator.d.ts.map +1 -1
- package/lib/services/assessment/AssessmentOrchestrator.js +31 -1
- package/lib/services/assessment/modules/FileModularizationAssessor.d.ts +87 -0
- package/lib/services/assessment/modules/FileModularizationAssessor.d.ts.map +1 -0
- package/lib/services/assessment/modules/FileModularizationAssessor.js +475 -0
- package/lib/services/assessment/modules/TemporalAssessor.d.ts +5 -129
- package/lib/services/assessment/modules/TemporalAssessor.d.ts.map +1 -1
- package/lib/services/assessment/modules/TemporalAssessor.js +18 -554
- package/lib/services/assessment/modules/ToolAnnotationAssessor.d.ts +10 -70
- package/lib/services/assessment/modules/ToolAnnotationAssessor.d.ts.map +1 -1
- package/lib/services/assessment/modules/ToolAnnotationAssessor.js +32 -625
- package/lib/services/assessment/modules/annotations/AlignmentChecker.d.ts +65 -0
- package/lib/services/assessment/modules/annotations/AlignmentChecker.d.ts.map +1 -0
- package/lib/services/assessment/modules/annotations/AlignmentChecker.js +289 -0
- package/lib/services/assessment/modules/annotations/ClaudeIntegration.d.ts +22 -0
- package/lib/services/assessment/modules/annotations/ClaudeIntegration.d.ts.map +1 -0
- package/lib/services/assessment/modules/annotations/ClaudeIntegration.js +139 -0
- package/lib/services/assessment/modules/annotations/EventEmitter.d.ts +20 -0
- package/lib/services/assessment/modules/annotations/EventEmitter.d.ts.map +1 -0
- package/lib/services/assessment/modules/annotations/EventEmitter.js +100 -0
- package/lib/services/assessment/modules/annotations/ExplanationGenerator.d.ts +25 -0
- package/lib/services/assessment/modules/annotations/ExplanationGenerator.d.ts.map +1 -0
- package/lib/services/assessment/modules/annotations/ExplanationGenerator.js +122 -0
- package/lib/services/assessment/modules/annotations/index.d.ts +5 -0
- package/lib/services/assessment/modules/annotations/index.d.ts.map +1 -1
- package/lib/services/assessment/modules/annotations/index.js +8 -0
- package/lib/services/assessment/modules/annotations/types.d.ts +33 -0
- package/lib/services/assessment/modules/annotations/types.d.ts.map +1 -0
- package/lib/services/assessment/modules/annotations/types.js +7 -0
- package/lib/services/assessment/modules/securityTests/SafeResponseDetector.d.ts +3 -0
- package/lib/services/assessment/modules/securityTests/SafeResponseDetector.d.ts.map +1 -1
- package/lib/services/assessment/modules/securityTests/SafeResponseDetector.js +14 -1
- package/lib/services/assessment/modules/securityTests/SecurityPatternLibrary.d.ts +56 -0
- package/lib/services/assessment/modules/securityTests/SecurityPatternLibrary.d.ts.map +1 -1
- package/lib/services/assessment/modules/securityTests/SecurityPatternLibrary.js +121 -0
- package/lib/services/assessment/modules/securityTests/SecurityPayloadGenerator.d.ts.map +1 -1
- package/lib/services/assessment/modules/securityTests/SecurityPayloadGenerator.js +13 -0
- package/lib/services/assessment/modules/securityTests/SecurityPayloadTester.d.ts.map +1 -1
- package/lib/services/assessment/modules/securityTests/SecurityPayloadTester.js +24 -0
- package/lib/services/assessment/modules/securityTests/SecurityResponseAnalyzer.d.ts +80 -0
- package/lib/services/assessment/modules/securityTests/SecurityResponseAnalyzer.d.ts.map +1 -1
- package/lib/services/assessment/modules/securityTests/SecurityResponseAnalyzer.js +273 -3
- package/lib/services/assessment/modules/temporal/MutationDetector.d.ts +75 -0
- package/lib/services/assessment/modules/temporal/MutationDetector.d.ts.map +1 -0
- package/lib/services/assessment/modules/temporal/MutationDetector.js +147 -0
- package/lib/services/assessment/modules/temporal/VarianceClassifier.d.ts +112 -0
- package/lib/services/assessment/modules/temporal/VarianceClassifier.d.ts.map +1 -0
- package/lib/services/assessment/modules/temporal/VarianceClassifier.js +427 -0
- package/lib/services/assessment/modules/temporal/index.d.ts +10 -0
- package/lib/services/assessment/modules/temporal/index.d.ts.map +1 -0
- package/lib/services/assessment/modules/temporal/index.js +9 -0
- package/package.json +1 -1
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Variance Classifier Module
|
|
3
|
+
* Classifies response variance to distinguish legitimate behavior from rug pulls.
|
|
4
|
+
*
|
|
5
|
+
* Extracted from TemporalAssessor as part of Issue #106 refactoring.
|
|
6
|
+
*/
|
|
7
|
+
import { VarianceClassification } from "../../../../lib/assessmentTypes.js";
|
|
8
|
+
import { Tool } from "@modelcontextprotocol/sdk/types.js";
|
|
9
|
+
import { MutationDetector } from "./MutationDetector.js";
|
|
10
|
+
/**
|
|
11
|
+
* Classifies response variance and categorizes tools by their expected behavior patterns.
|
|
12
|
+
* Used to reduce false positives in temporal assessment by understanding legitimate variance.
|
|
13
|
+
*/
|
|
14
|
+
export declare class VarianceClassifier {
|
|
15
|
+
private mutationDetector;
|
|
16
|
+
private readonly DESTRUCTIVE_PATTERNS;
|
|
17
|
+
/**
|
|
18
|
+
* Tool name patterns that are expected to have state-dependent responses.
|
|
19
|
+
* These tools legitimately return different results based on data state,
|
|
20
|
+
* which is NOT a rug pull vulnerability.
|
|
21
|
+
*
|
|
22
|
+
* Includes both:
|
|
23
|
+
* - READ operations: search, list, query return more results after data stored
|
|
24
|
+
* - ACCUMULATION operations: add, append, store return accumulated state (counts, IDs)
|
|
25
|
+
*
|
|
26
|
+
* NOTE: Does NOT include patterns already in DESTRUCTIVE_PATTERNS (create, write,
|
|
27
|
+
* insert, etc.) - those need strict comparison to detect real rug pulls.
|
|
28
|
+
*
|
|
29
|
+
* Uses word-boundary matching to prevent false matches.
|
|
30
|
+
* "add_observations" matches "add" but "address_validator" does not.
|
|
31
|
+
*/
|
|
32
|
+
private readonly STATEFUL_TOOL_PATTERNS;
|
|
33
|
+
/**
|
|
34
|
+
* Issue #69: Patterns for resource-creating operations that legitimately return
|
|
35
|
+
* different IDs/resources each invocation.
|
|
36
|
+
*
|
|
37
|
+
* These tools CREATE new resources, so they should use schema comparison + variance
|
|
38
|
+
* classification rather than exact comparison. Unlike STATEFUL_TOOL_PATTERNS, these
|
|
39
|
+
* may overlap with DESTRUCTIVE_PATTERNS (e.g., "create", "insert") but should still
|
|
40
|
+
* use intelligent variance classification to avoid false positives.
|
|
41
|
+
*
|
|
42
|
+
* Examples:
|
|
43
|
+
* - create_billing_product -> new product_id each time (LEGITIMATE variance)
|
|
44
|
+
* - generate_report -> new report_id each time (LEGITIMATE variance)
|
|
45
|
+
* - insert_record -> new record_id each time (LEGITIMATE variance)
|
|
46
|
+
*/
|
|
47
|
+
private readonly RESOURCE_CREATING_PATTERNS;
|
|
48
|
+
constructor(mutationDetector?: MutationDetector);
|
|
49
|
+
/**
|
|
50
|
+
* Normalize response for comparison by removing naturally varying data.
|
|
51
|
+
* Prevents false positives from timestamps, UUIDs, request IDs, counters, etc.
|
|
52
|
+
* Handles both direct JSON and nested JSON strings (e.g., content[].text).
|
|
53
|
+
*/
|
|
54
|
+
normalizeResponse(response: unknown): string;
|
|
55
|
+
/**
|
|
56
|
+
* Detect if a tool may have side effects based on naming patterns.
|
|
57
|
+
*/
|
|
58
|
+
isDestructiveTool(tool: Tool): boolean;
|
|
59
|
+
/**
|
|
60
|
+
* Check if a tool is expected to have state-dependent behavior.
|
|
61
|
+
* Stateful tools (search, list, add, store, etc.) legitimately return different
|
|
62
|
+
* results as underlying data changes - this is NOT a rug pull.
|
|
63
|
+
*
|
|
64
|
+
* Uses word-boundary matching to prevent false positives:
|
|
65
|
+
* - "add_observations" matches "add"
|
|
66
|
+
* - "address_validator" does NOT match "add"
|
|
67
|
+
*/
|
|
68
|
+
isStatefulTool(tool: Tool): boolean;
|
|
69
|
+
/**
|
|
70
|
+
* Issue #69: Check if a tool creates new resources that legitimately vary per invocation.
|
|
71
|
+
* Resource-creating tools return different IDs, creation timestamps, etc.
|
|
72
|
+
* for each new resource - this is expected behavior, NOT a rug pull.
|
|
73
|
+
*
|
|
74
|
+
* Unlike isStatefulTool(), this DOES include patterns that overlap with DESTRUCTIVE_PATTERNS
|
|
75
|
+
* because resource-creating tools need intelligent variance classification, not exact comparison.
|
|
76
|
+
*
|
|
77
|
+
* Uses word-boundary matching like isStatefulTool() to prevent false matches.
|
|
78
|
+
* - "create_billing_product" matches "create"
|
|
79
|
+
* - "recreate_view" does NOT match "create" (must be at word boundary)
|
|
80
|
+
*/
|
|
81
|
+
isResourceCreatingTool(tool: Tool): boolean;
|
|
82
|
+
/**
|
|
83
|
+
* Issue #69: Classify variance between two responses to reduce false positives.
|
|
84
|
+
* Returns LEGITIMATE for expected variance (IDs, timestamps), SUSPICIOUS for
|
|
85
|
+
* schema changes, and BEHAVIORAL for semantic changes (promotional keywords, errors).
|
|
86
|
+
*/
|
|
87
|
+
classifyVariance(baseline: unknown, current: unknown): VarianceClassification;
|
|
88
|
+
/**
|
|
89
|
+
* Issue #69: Check if a field name represents legitimate variance.
|
|
90
|
+
* Fields containing IDs, timestamps, tokens, etc. are expected to vary.
|
|
91
|
+
*/
|
|
92
|
+
isLegitimateFieldVariance(field: string): boolean;
|
|
93
|
+
/**
|
|
94
|
+
* Issue #69: Find which fields differ between two responses.
|
|
95
|
+
* Returns field paths that have different values.
|
|
96
|
+
*/
|
|
97
|
+
findVariedFields(obj1: unknown, obj2: unknown, prefix?: string): string[];
|
|
98
|
+
/**
|
|
99
|
+
* Compare response schemas (field names) rather than full content.
|
|
100
|
+
* Stateful tools may have different values but should have consistent fields.
|
|
101
|
+
*
|
|
102
|
+
* For stateful tools, allows schema growth (empty arrays -> populated arrays)
|
|
103
|
+
* but flags when baseline fields disappear (suspicious behavior).
|
|
104
|
+
*/
|
|
105
|
+
compareSchemas(response1: unknown, response2: unknown): boolean;
|
|
106
|
+
/**
|
|
107
|
+
* Extract all field names from an object recursively.
|
|
108
|
+
* Handles arrays by sampling multiple elements to detect heterogeneous schemas.
|
|
109
|
+
*/
|
|
110
|
+
extractFieldNames(obj: unknown, prefix?: string): string[];
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=VarianceClassifier.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"VarianceClassifier.d.ts","sourceRoot":"","sources":["../../../../../src/services/assessment/modules/temporal/VarianceClassifier.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAC/D,OAAO,EAAE,IAAI,EAAE,MAAM,oCAAoC,CAAC;AAC1D,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEtD;;;GAGG;AACH,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,gBAAgB,CAAmB;IAG3C,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAoBnC;IAEF;;;;;;;;;;;;;;OAcG;IACH,OAAO,CAAC,QAAQ,CAAC,sBAAsB,CAqBrC;IAEF;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,QAAQ,CAAC,0BAA0B,CAYzC;gBAEU,gBAAgB,CAAC,EAAE,gBAAgB;IAI/C;;;;OAIG;IACH,iBAAiB,CAAC,QAAQ,EAAE,OAAO,GAAG,MAAM;IAiF5C;;OAEG;IACH,iBAAiB,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO;IAKtC;;;;;;;;OAQG;IACH,cAAc,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO;IAenC;;;;;;;;;;;OAWG;IACH,sBAAsB,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO;IAQ3C;;;;OAIG;IACH,gBAAgB,CACd,QAAQ,EAAE,OAAO,EACjB,OAAO,EAAE,OAAO,GACf,sBAAsB;IAkEzB;;;OAGG;IACH,yBAAyB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;IAgEjD;;;OAGG;IACH,gBAAgB,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,SAAK,GAAG,MAAM,EAAE;IAuDrE;;;;;;OAMG;IACH,cAAc,CAAC,SAAS,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,GAAG,OAAO;IAuB/D;;;OAGG;IACH,iBAAiB,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,SAAK,GAAG,MAAM,EAAE;CAgCvD"}
|
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Variance Classifier Module
|
|
3
|
+
* Classifies response variance to distinguish legitimate behavior from rug pulls.
|
|
4
|
+
*
|
|
5
|
+
* Extracted from TemporalAssessor as part of Issue #106 refactoring.
|
|
6
|
+
*/
|
|
7
|
+
import { MutationDetector } from "./MutationDetector.js";
|
|
8
|
+
/**
|
|
9
|
+
* Classifies response variance and categorizes tools by their expected behavior patterns.
|
|
10
|
+
* Used to reduce false positives in temporal assessment by understanding legitimate variance.
|
|
11
|
+
*/
|
|
12
|
+
export class VarianceClassifier {
|
|
13
|
+
mutationDetector;
|
|
14
|
+
// Patterns that suggest a tool may have side effects
|
|
15
|
+
DESTRUCTIVE_PATTERNS = [
|
|
16
|
+
"create",
|
|
17
|
+
"write",
|
|
18
|
+
"delete",
|
|
19
|
+
"remove",
|
|
20
|
+
"update",
|
|
21
|
+
"insert",
|
|
22
|
+
"post",
|
|
23
|
+
"put",
|
|
24
|
+
"send",
|
|
25
|
+
"submit",
|
|
26
|
+
"execute",
|
|
27
|
+
"run",
|
|
28
|
+
// P2-3: Additional destructive patterns
|
|
29
|
+
"drop",
|
|
30
|
+
"truncate",
|
|
31
|
+
"clear",
|
|
32
|
+
"purge",
|
|
33
|
+
"destroy",
|
|
34
|
+
"reset",
|
|
35
|
+
];
|
|
36
|
+
/**
|
|
37
|
+
* Tool name patterns that are expected to have state-dependent responses.
|
|
38
|
+
* These tools legitimately return different results based on data state,
|
|
39
|
+
* which is NOT a rug pull vulnerability.
|
|
40
|
+
*
|
|
41
|
+
* Includes both:
|
|
42
|
+
* - READ operations: search, list, query return more results after data stored
|
|
43
|
+
* - ACCUMULATION operations: add, append, store return accumulated state (counts, IDs)
|
|
44
|
+
*
|
|
45
|
+
* NOTE: Does NOT include patterns already in DESTRUCTIVE_PATTERNS (create, write,
|
|
46
|
+
* insert, etc.) - those need strict comparison to detect real rug pulls.
|
|
47
|
+
*
|
|
48
|
+
* Uses word-boundary matching to prevent false matches.
|
|
49
|
+
* "add_observations" matches "add" but "address_validator" does not.
|
|
50
|
+
*/
|
|
51
|
+
STATEFUL_TOOL_PATTERNS = [
|
|
52
|
+
// READ operations - results depend on current data state
|
|
53
|
+
"search",
|
|
54
|
+
"list",
|
|
55
|
+
"query",
|
|
56
|
+
"find",
|
|
57
|
+
"get",
|
|
58
|
+
"fetch",
|
|
59
|
+
"read",
|
|
60
|
+
"browse",
|
|
61
|
+
// ACCUMULATION operations (non-destructive) that return accumulated state
|
|
62
|
+
// These legitimately return different counts/IDs as data accumulates
|
|
63
|
+
// NOTE: "add" is NOT in DESTRUCTIVE_PATTERNS, unlike "insert", "create", "write"
|
|
64
|
+
"add",
|
|
65
|
+
"append",
|
|
66
|
+
"store",
|
|
67
|
+
"save",
|
|
68
|
+
"log",
|
|
69
|
+
"record",
|
|
70
|
+
"push",
|
|
71
|
+
"enqueue",
|
|
72
|
+
];
|
|
73
|
+
/**
|
|
74
|
+
* Issue #69: Patterns for resource-creating operations that legitimately return
|
|
75
|
+
* different IDs/resources each invocation.
|
|
76
|
+
*
|
|
77
|
+
* These tools CREATE new resources, so they should use schema comparison + variance
|
|
78
|
+
* classification rather than exact comparison. Unlike STATEFUL_TOOL_PATTERNS, these
|
|
79
|
+
* may overlap with DESTRUCTIVE_PATTERNS (e.g., "create", "insert") but should still
|
|
80
|
+
* use intelligent variance classification to avoid false positives.
|
|
81
|
+
*
|
|
82
|
+
* Examples:
|
|
83
|
+
* - create_billing_product -> new product_id each time (LEGITIMATE variance)
|
|
84
|
+
* - generate_report -> new report_id each time (LEGITIMATE variance)
|
|
85
|
+
* - insert_record -> new record_id each time (LEGITIMATE variance)
|
|
86
|
+
*/
|
|
87
|
+
RESOURCE_CREATING_PATTERNS = [
|
|
88
|
+
"create",
|
|
89
|
+
"new",
|
|
90
|
+
"insert",
|
|
91
|
+
"generate",
|
|
92
|
+
"register",
|
|
93
|
+
"allocate",
|
|
94
|
+
"provision",
|
|
95
|
+
"spawn",
|
|
96
|
+
"instantiate",
|
|
97
|
+
"init",
|
|
98
|
+
"make",
|
|
99
|
+
];
|
|
100
|
+
constructor(mutationDetector) {
|
|
101
|
+
this.mutationDetector = mutationDetector ?? new MutationDetector();
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Normalize response for comparison by removing naturally varying data.
|
|
105
|
+
* Prevents false positives from timestamps, UUIDs, request IDs, counters, etc.
|
|
106
|
+
* Handles both direct JSON and nested JSON strings (e.g., content[].text).
|
|
107
|
+
*/
|
|
108
|
+
normalizeResponse(response) {
|
|
109
|
+
const str = JSON.stringify(response);
|
|
110
|
+
return (str
|
|
111
|
+
// ISO timestamps (bounded quantifier to prevent ReDoS)
|
|
112
|
+
.replace(/"\d{4}-\d{2}-\d{2}T[\d:.]{1,30}Z?"/g, '"<TIMESTAMP>"')
|
|
113
|
+
// Unix timestamps (13 digits)
|
|
114
|
+
.replace(/"\d{13}"/g, '"<TIMESTAMP>"')
|
|
115
|
+
// UUIDs
|
|
116
|
+
.replace(/"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"/gi, '"<UUID>"')
|
|
117
|
+
// Common ID fields (string values)
|
|
118
|
+
.replace(/"request_id":\s*"[^"]+"/g, '"request_id": "<ID>"')
|
|
119
|
+
.replace(/"requestId":\s*"[^"]+"/g, '"requestId": "<ID>"')
|
|
120
|
+
.replace(/"trace_id":\s*"[^"]+"/g, '"trace_id": "<ID>"')
|
|
121
|
+
// Numeric ID fields (normalize incrementing IDs) - both direct and escaped
|
|
122
|
+
.replace(/"id":\s*\d+/g, '"id": <NUMBER>')
|
|
123
|
+
.replace(/"Id":\s*\d+/g, '"Id": <NUMBER>')
|
|
124
|
+
.replace(/\\"id\\":\s*\d+/g, '\\"id\\": <NUMBER>')
|
|
125
|
+
.replace(/\\"Id\\":\s*\d+/g, '\\"Id\\": <NUMBER>')
|
|
126
|
+
// Counter/sequence fields - both direct and escaped (for nested JSON)
|
|
127
|
+
.replace(/"total_items":\s*\d+/g, '"total_items": <NUMBER>')
|
|
128
|
+
.replace(/\\"total_items\\":\s*\d+/g, '\\"total_items\\": <NUMBER>')
|
|
129
|
+
.replace(/"count":\s*\d+/g, '"count": <NUMBER>')
|
|
130
|
+
.replace(/\\"count\\":\s*\d+/g, '\\"count\\": <NUMBER>')
|
|
131
|
+
.replace(/"invocation_count":\s*\d+/g, '"invocation_count": <NUMBER>')
|
|
132
|
+
.replace(/\\"invocation_count\\":\s*\d+/g, '\\"invocation_count\\": <NUMBER>')
|
|
133
|
+
.replace(/"sequence":\s*\d+/g, '"sequence": <NUMBER>')
|
|
134
|
+
.replace(/\\"sequence\\":\s*\d+/g, '\\"sequence\\": <NUMBER>')
|
|
135
|
+
.replace(/"index":\s*\d+/g, '"index": <NUMBER>')
|
|
136
|
+
.replace(/\\"index\\":\s*\d+/g, '\\"index\\": <NUMBER>')
|
|
137
|
+
// Additional accumulation-related counter fields (defense-in-depth)
|
|
138
|
+
.replace(/"total_observations":\s*\d+/g, '"total_observations": <NUMBER>')
|
|
139
|
+
.replace(/\\"total_observations\\":\s*\d+/g, '\\"total_observations\\": <NUMBER>')
|
|
140
|
+
.replace(/"observations_count":\s*\d+/g, '"observations_count": <NUMBER>')
|
|
141
|
+
.replace(/\\"observations_count\\":\s*\d+/g, '\\"observations_count\\": <NUMBER>')
|
|
142
|
+
.replace(/"total_records":\s*\d+/g, '"total_records": <NUMBER>')
|
|
143
|
+
.replace(/\\"total_records\\":\s*\d+/g, '\\"total_records\\": <NUMBER>')
|
|
144
|
+
.replace(/"records_added":\s*\d+/g, '"records_added": <NUMBER>')
|
|
145
|
+
.replace(/\\"records_added\\":\s*\d+/g, '\\"records_added\\": <NUMBER>')
|
|
146
|
+
.replace(/"items_added":\s*\d+/g, '"items_added": <NUMBER>')
|
|
147
|
+
.replace(/\\"items_added\\":\s*\d+/g, '\\"items_added\\": <NUMBER>')
|
|
148
|
+
.replace(/"size":\s*\d+/g, '"size": <NUMBER>')
|
|
149
|
+
.replace(/\\"size\\":\s*\d+/g, '\\"size\\": <NUMBER>')
|
|
150
|
+
.replace(/"length":\s*\d+/g, '"length": <NUMBER>')
|
|
151
|
+
.replace(/\\"length\\":\s*\d+/g, '\\"length\\": <NUMBER>')
|
|
152
|
+
.replace(/"total":\s*\d+/g, '"total": <NUMBER>')
|
|
153
|
+
.replace(/\\"total\\":\s*\d+/g, '\\"total\\": <NUMBER>')
|
|
154
|
+
// String IDs
|
|
155
|
+
.replace(/"id":\s*"[^"]+"/g, '"id": "<ID>"')
|
|
156
|
+
// P2-1: Additional timestamp fields that vary between calls
|
|
157
|
+
.replace(/"(updated_at|created_at|modified_at)":\s*"[^"]+"/g, '"$1": "<TIMESTAMP>"')
|
|
158
|
+
// P2-1: Dynamic tokens/hashes that change per request
|
|
159
|
+
.replace(/"(nonce|token|hash|etag|session_id|correlation_id)":\s*"[^"]+"/g, '"$1": "<DYNAMIC>"'));
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Detect if a tool may have side effects based on naming patterns.
|
|
163
|
+
*/
|
|
164
|
+
isDestructiveTool(tool) {
|
|
165
|
+
const name = tool.name.toLowerCase();
|
|
166
|
+
return this.DESTRUCTIVE_PATTERNS.some((p) => name.includes(p));
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Check if a tool is expected to have state-dependent behavior.
|
|
170
|
+
* Stateful tools (search, list, add, store, etc.) legitimately return different
|
|
171
|
+
* results as underlying data changes - this is NOT a rug pull.
|
|
172
|
+
*
|
|
173
|
+
* Uses word-boundary matching to prevent false positives:
|
|
174
|
+
* - "add_observations" matches "add"
|
|
175
|
+
* - "address_validator" does NOT match "add"
|
|
176
|
+
*/
|
|
177
|
+
isStatefulTool(tool) {
|
|
178
|
+
const toolName = tool.name.toLowerCase();
|
|
179
|
+
// Exclude tools that are ALSO destructive - they should get strict exact comparison
|
|
180
|
+
// e.g., "get_and_delete" matches both "get" (stateful) and "delete" (destructive)
|
|
181
|
+
if (this.isDestructiveTool(tool)) {
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
// Use word-boundary matching: pattern must be at start/end or bounded by _ or -
|
|
185
|
+
// This prevents "address_validator" from matching "add"
|
|
186
|
+
return this.STATEFUL_TOOL_PATTERNS.some((pattern) => {
|
|
187
|
+
const wordBoundaryRegex = new RegExp(`(^|_|-)${pattern}($|_|-)`);
|
|
188
|
+
return wordBoundaryRegex.test(toolName);
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Issue #69: Check if a tool creates new resources that legitimately vary per invocation.
|
|
193
|
+
* Resource-creating tools return different IDs, creation timestamps, etc.
|
|
194
|
+
* for each new resource - this is expected behavior, NOT a rug pull.
|
|
195
|
+
*
|
|
196
|
+
* Unlike isStatefulTool(), this DOES include patterns that overlap with DESTRUCTIVE_PATTERNS
|
|
197
|
+
* because resource-creating tools need intelligent variance classification, not exact comparison.
|
|
198
|
+
*
|
|
199
|
+
* Uses word-boundary matching like isStatefulTool() to prevent false matches.
|
|
200
|
+
* - "create_billing_product" matches "create"
|
|
201
|
+
* - "recreate_view" does NOT match "create" (must be at word boundary)
|
|
202
|
+
*/
|
|
203
|
+
isResourceCreatingTool(tool) {
|
|
204
|
+
const toolName = tool.name.toLowerCase();
|
|
205
|
+
return this.RESOURCE_CREATING_PATTERNS.some((pattern) => {
|
|
206
|
+
const wordBoundaryRegex = new RegExp(`(^|_|-)${pattern}($|_|-)`);
|
|
207
|
+
return wordBoundaryRegex.test(toolName);
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Issue #69: Classify variance between two responses to reduce false positives.
|
|
212
|
+
* Returns LEGITIMATE for expected variance (IDs, timestamps), SUSPICIOUS for
|
|
213
|
+
* schema changes, and BEHAVIORAL for semantic changes (promotional keywords, errors).
|
|
214
|
+
*/
|
|
215
|
+
classifyVariance(baseline, current) {
|
|
216
|
+
// 1. Schema comparison - structural changes are SUSPICIOUS
|
|
217
|
+
const schemaMatch = this.compareSchemas(baseline, current);
|
|
218
|
+
if (!schemaMatch) {
|
|
219
|
+
return {
|
|
220
|
+
type: "SUSPICIOUS",
|
|
221
|
+
confidence: "high",
|
|
222
|
+
reasons: ["Schema/field structure changed between invocations"],
|
|
223
|
+
suspiciousPatterns: ["schema_change"],
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
// 2. Content change detection - promotional/error keywords are BEHAVIORAL
|
|
227
|
+
const contentChange = this.mutationDetector.detectStatefulContentChange(baseline, current);
|
|
228
|
+
if (contentChange.detected) {
|
|
229
|
+
return {
|
|
230
|
+
type: "BEHAVIORAL",
|
|
231
|
+
confidence: "high",
|
|
232
|
+
reasons: [`Behavioral change detected: ${contentChange.reason}`],
|
|
233
|
+
suspiciousPatterns: [contentChange.reason || "content_change"],
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
// 3. After normalization, if responses match = LEGITIMATE
|
|
237
|
+
const normalizedBaseline = this.normalizeResponse(baseline);
|
|
238
|
+
const normalizedCurrent = this.normalizeResponse(current);
|
|
239
|
+
if (normalizedBaseline === normalizedCurrent) {
|
|
240
|
+
return {
|
|
241
|
+
type: "LEGITIMATE",
|
|
242
|
+
confidence: "high",
|
|
243
|
+
reasons: ["All differences normalized (IDs, timestamps, counters)"],
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
// 4. Check for legitimate field variance (any _id, _at, token fields)
|
|
247
|
+
const variedFields = this.findVariedFields(baseline, current);
|
|
248
|
+
const unexplainedFields = variedFields.filter((f) => !this.isLegitimateFieldVariance(f));
|
|
249
|
+
if (unexplainedFields.length === 0) {
|
|
250
|
+
return {
|
|
251
|
+
type: "LEGITIMATE",
|
|
252
|
+
confidence: "high",
|
|
253
|
+
reasons: [
|
|
254
|
+
`Variance only in legitimate fields: ${variedFields.join(", ")}`,
|
|
255
|
+
],
|
|
256
|
+
variedFields,
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
// 5. Some unexplained variance - flag as suspicious with low confidence
|
|
260
|
+
return {
|
|
261
|
+
type: "SUSPICIOUS",
|
|
262
|
+
confidence: "low",
|
|
263
|
+
reasons: [
|
|
264
|
+
`Unexplained variance in fields: ${unexplainedFields.join(", ")}`,
|
|
265
|
+
],
|
|
266
|
+
variedFields,
|
|
267
|
+
suspiciousPatterns: ["unclassified_variance"],
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Issue #69: Check if a field name represents legitimate variance.
|
|
272
|
+
* Fields containing IDs, timestamps, tokens, etc. are expected to vary.
|
|
273
|
+
*/
|
|
274
|
+
isLegitimateFieldVariance(field) {
|
|
275
|
+
const fieldLower = field.toLowerCase();
|
|
276
|
+
// ID fields - any field ending in _id or containing "id" at word boundary
|
|
277
|
+
if (fieldLower.endsWith("_id") || fieldLower.endsWith("id"))
|
|
278
|
+
return true;
|
|
279
|
+
if (fieldLower.includes("_id_") || fieldLower.startsWith("id_"))
|
|
280
|
+
return true;
|
|
281
|
+
// Timestamp fields
|
|
282
|
+
if (fieldLower.endsWith("_at") || fieldLower.endsWith("at"))
|
|
283
|
+
return true;
|
|
284
|
+
if (fieldLower.includes("time") ||
|
|
285
|
+
fieldLower.includes("date") ||
|
|
286
|
+
fieldLower.includes("timestamp"))
|
|
287
|
+
return true;
|
|
288
|
+
// Token/session fields
|
|
289
|
+
if (fieldLower.includes("token") ||
|
|
290
|
+
fieldLower.includes("cursor") ||
|
|
291
|
+
fieldLower.includes("nonce"))
|
|
292
|
+
return true;
|
|
293
|
+
if (fieldLower.includes("session") || fieldLower.includes("correlation"))
|
|
294
|
+
return true;
|
|
295
|
+
// Pagination fields
|
|
296
|
+
if (fieldLower.includes("offset") ||
|
|
297
|
+
fieldLower.includes("page") ||
|
|
298
|
+
fieldLower.includes("next"))
|
|
299
|
+
return true;
|
|
300
|
+
// Counter/accumulation fields
|
|
301
|
+
if (fieldLower.includes("count") ||
|
|
302
|
+
fieldLower.includes("total") ||
|
|
303
|
+
fieldLower.includes("size"))
|
|
304
|
+
return true;
|
|
305
|
+
if (fieldLower.includes("length") || fieldLower.includes("index"))
|
|
306
|
+
return true;
|
|
307
|
+
// Array content fields (search results, items)
|
|
308
|
+
if (fieldLower.includes("results") ||
|
|
309
|
+
fieldLower.includes("items") ||
|
|
310
|
+
fieldLower.includes("data"))
|
|
311
|
+
return true;
|
|
312
|
+
// Hash/version fields
|
|
313
|
+
if (fieldLower.includes("hash") ||
|
|
314
|
+
fieldLower.includes("etag") ||
|
|
315
|
+
fieldLower.includes("version"))
|
|
316
|
+
return true;
|
|
317
|
+
return false;
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Issue #69: Find which fields differ between two responses.
|
|
321
|
+
* Returns field paths that have different values.
|
|
322
|
+
*/
|
|
323
|
+
findVariedFields(obj1, obj2, prefix = "") {
|
|
324
|
+
const varied = [];
|
|
325
|
+
// Handle primitives
|
|
326
|
+
if (typeof obj1 !== "object" || obj1 === null) {
|
|
327
|
+
if (obj1 !== obj2) {
|
|
328
|
+
return [prefix || "value"];
|
|
329
|
+
}
|
|
330
|
+
return [];
|
|
331
|
+
}
|
|
332
|
+
if (typeof obj2 !== "object" || obj2 === null) {
|
|
333
|
+
return [prefix || "value"];
|
|
334
|
+
}
|
|
335
|
+
// Handle arrays - just note if length or content differs
|
|
336
|
+
if (Array.isArray(obj1) || Array.isArray(obj2)) {
|
|
337
|
+
const arr1 = Array.isArray(obj1) ? obj1 : [];
|
|
338
|
+
const arr2 = Array.isArray(obj2) ? obj2 : [];
|
|
339
|
+
if (JSON.stringify(arr1) !== JSON.stringify(arr2)) {
|
|
340
|
+
return [prefix || "array"];
|
|
341
|
+
}
|
|
342
|
+
return [];
|
|
343
|
+
}
|
|
344
|
+
// Handle objects
|
|
345
|
+
const allKeys = new Set([
|
|
346
|
+
...Object.keys(obj1),
|
|
347
|
+
...Object.keys(obj2),
|
|
348
|
+
]);
|
|
349
|
+
for (const key of allKeys) {
|
|
350
|
+
const val1 = obj1[key];
|
|
351
|
+
const val2 = obj2[key];
|
|
352
|
+
const fieldPath = prefix ? `${prefix}.${key}` : key;
|
|
353
|
+
if (JSON.stringify(val1) !== JSON.stringify(val2)) {
|
|
354
|
+
// If both are objects, recurse to find specific field
|
|
355
|
+
if (typeof val1 === "object" &&
|
|
356
|
+
val1 !== null &&
|
|
357
|
+
typeof val2 === "object" &&
|
|
358
|
+
val2 !== null) {
|
|
359
|
+
const nestedVaried = this.findVariedFields(val1, val2, fieldPath);
|
|
360
|
+
varied.push(...nestedVaried);
|
|
361
|
+
}
|
|
362
|
+
else {
|
|
363
|
+
varied.push(fieldPath);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
return varied;
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Compare response schemas (field names) rather than full content.
|
|
371
|
+
* Stateful tools may have different values but should have consistent fields.
|
|
372
|
+
*
|
|
373
|
+
* For stateful tools, allows schema growth (empty arrays -> populated arrays)
|
|
374
|
+
* but flags when baseline fields disappear (suspicious behavior).
|
|
375
|
+
*/
|
|
376
|
+
compareSchemas(response1, response2) {
|
|
377
|
+
const fields1 = this.extractFieldNames(response1).sort();
|
|
378
|
+
const fields2 = this.extractFieldNames(response2).sort();
|
|
379
|
+
// Edge case: empty baseline with populated later response is suspicious
|
|
380
|
+
// An attacker could start with {} then switch to content with malicious fields
|
|
381
|
+
if (fields1.length === 0 && fields2.length > 0) {
|
|
382
|
+
return false; // Flag as schema mismatch
|
|
383
|
+
}
|
|
384
|
+
// Check for exact match (handles non-array cases)
|
|
385
|
+
const exactMatch = fields1.join(",") === fields2.join(",");
|
|
386
|
+
if (exactMatch)
|
|
387
|
+
return true;
|
|
388
|
+
// For stateful tools, allow schema to grow (empty arrays -> populated)
|
|
389
|
+
// Baseline (fields1) can be a subset of later responses (fields2)
|
|
390
|
+
// But fields2 cannot have FEWER fields than baseline (that's suspicious)
|
|
391
|
+
const set2 = new Set(fields2);
|
|
392
|
+
const baselineIsSubset = fields1.every((f) => set2.has(f));
|
|
393
|
+
return baselineIsSubset;
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* Extract all field names from an object recursively.
|
|
397
|
+
* Handles arrays by sampling multiple elements to detect heterogeneous schemas.
|
|
398
|
+
*/
|
|
399
|
+
extractFieldNames(obj, prefix = "") {
|
|
400
|
+
if (obj === null || obj === undefined || typeof obj !== "object")
|
|
401
|
+
return [];
|
|
402
|
+
const fields = [];
|
|
403
|
+
// Handle arrays: sample multiple elements to detect heterogeneous schemas
|
|
404
|
+
// An attacker could hide malicious fields in non-first array elements
|
|
405
|
+
if (Array.isArray(obj)) {
|
|
406
|
+
const samplesToCheck = Math.min(obj.length, 3); // Check up to 3 elements
|
|
407
|
+
const seenFields = new Set();
|
|
408
|
+
for (let i = 0; i < samplesToCheck; i++) {
|
|
409
|
+
if (typeof obj[i] === "object" && obj[i] !== null) {
|
|
410
|
+
const itemFields = this.extractFieldNames(obj[i], `${prefix}[]`);
|
|
411
|
+
itemFields.forEach((f) => seenFields.add(f));
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
fields.push(...seenFields);
|
|
415
|
+
return fields;
|
|
416
|
+
}
|
|
417
|
+
// Handle objects
|
|
418
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
419
|
+
const fieldPath = prefix ? `${prefix}.${key}` : key;
|
|
420
|
+
fields.push(fieldPath);
|
|
421
|
+
if (typeof value === "object" && value !== null) {
|
|
422
|
+
fields.push(...this.extractFieldNames(value, fieldPath));
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
return fields;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Temporal Assessment Module
|
|
3
|
+
* Exports all temporal assessment helper components.
|
|
4
|
+
*
|
|
5
|
+
* Created as part of Issue #106 refactoring to split TemporalAssessor.ts
|
|
6
|
+
* into focused, maintainable modules.
|
|
7
|
+
*/
|
|
8
|
+
export { MutationDetector, type DefinitionSnapshot, type DefinitionMutation, type ContentChangeResult, } from "./MutationDetector.js";
|
|
9
|
+
export { VarianceClassifier } from "./VarianceClassifier.js";
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/services/assessment/modules/temporal/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EACL,gBAAgB,EAChB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,GACzB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Temporal Assessment Module
|
|
3
|
+
* Exports all temporal assessment helper components.
|
|
4
|
+
*
|
|
5
|
+
* Created as part of Issue #106 refactoring to split TemporalAssessor.ts
|
|
6
|
+
* into focused, maintainable modules.
|
|
7
|
+
*/
|
|
8
|
+
export { MutationDetector, } from "./MutationDetector.js";
|
|
9
|
+
export { VarianceClassifier } from "./VarianceClassifier.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bryan-thompson/inspector-assessment-client",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.29.0",
|
|
4
4
|
"description": "Client-side application for the Enhanced MCP Inspector with assessment capabilities",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Bryan Thompson <bryan@triepod.ai>",
|