retestkit 1.4.1 → 1.5.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 (238) hide show
  1. package/README.md +59 -40
  2. package/dist/config.js +8 -8
  3. package/dist/config.js.map +1 -1
  4. package/dist/logger.js +1 -1
  5. package/dist/logger.js.map +1 -1
  6. package/dist/prompts/index.d.ts +1 -1
  7. package/dist/prompts/index.d.ts.map +1 -1
  8. package/dist/prompts/index.js +21 -21
  9. package/dist/prompts/index.js.map +1 -1
  10. package/dist/prompts/templates/mcp/retest-crawl.md +7 -0
  11. package/{src/prompts/templates/mcp/webtest-discover-flows.md → dist/prompts/templates/mcp/retest-discover-flows.md} +1 -1
  12. package/{src/prompts/templates/mcp/webtest-discover.md → dist/prompts/templates/mcp/retest-discover.md} +2 -2
  13. package/dist/prompts/templates/mcp/retest-full-workflow.md +12 -0
  14. package/{src/prompts/templates/mcp/webtest-generate-tests.md → dist/prompts/templates/mcp/retest-generate-tests.md} +1 -1
  15. package/{src/prompts/templates/mcp/webtest-run-test.md → dist/prompts/templates/mcp/retest-run-test.md} +1 -1
  16. package/{src/prompts/templates/mcp/webtest-start.md → dist/prompts/templates/mcp/retest-start.md} +1 -1
  17. package/{src → dist}/prompts/templates/sampling/system-prefix.md +1 -1
  18. package/dist/resources/index.js +7 -7
  19. package/dist/resources/index.js.map +1 -1
  20. package/dist/schemas/config.js +2 -2
  21. package/dist/schemas/config.js.map +1 -1
  22. package/dist/security/index.js +1 -1
  23. package/dist/security/index.js.map +1 -1
  24. package/dist/server.js +3 -3
  25. package/dist/server.js.map +1 -1
  26. package/dist/test-utils/mock-context.js +22 -22
  27. package/dist/test-utils/mock-context.js.map +1 -1
  28. package/dist/tools/index.d.ts +1 -1
  29. package/dist/tools/index.d.ts.map +1 -1
  30. package/dist/tools/index.js +5 -5
  31. package/dist/tools/index.js.map +1 -1
  32. package/dist/tools/retest/crawl.d.ts.map +1 -0
  33. package/dist/tools/{webtest → retest}/crawl.js +7 -7
  34. package/dist/tools/retest/crawl.js.map +1 -0
  35. package/dist/tools/retest/discover-features.d.ts.map +1 -0
  36. package/dist/tools/{webtest → retest}/discover-features.js +6 -6
  37. package/dist/tools/retest/discover-features.js.map +1 -0
  38. package/dist/tools/retest/discover-flows.d.ts.map +1 -0
  39. package/dist/tools/{webtest → retest}/discover-flows.js +6 -6
  40. package/dist/tools/retest/discover-flows.js.map +1 -0
  41. package/dist/tools/retest/generate-tests.d.ts.map +1 -0
  42. package/dist/tools/{webtest → retest}/generate-tests.js +5 -5
  43. package/dist/tools/retest/generate-tests.js.map +1 -0
  44. package/dist/tools/retest/index.d.ts.map +1 -0
  45. package/dist/tools/retest/index.js.map +1 -0
  46. package/dist/tools/retest/run-test-case.d.ts.map +1 -0
  47. package/dist/tools/{webtest → retest}/run-test-case.js +3 -3
  48. package/dist/tools/retest/run-test-case.js.map +1 -0
  49. package/dist/tools/retest/schemas.d.ts.map +1 -0
  50. package/dist/tools/retest/schemas.js.map +1 -0
  51. package/dist/tools/retest/start-analysis.d.ts.map +1 -0
  52. package/dist/tools/{webtest → retest}/start-analysis.js +5 -5
  53. package/dist/tools/retest/start-analysis.js.map +1 -0
  54. package/dist/workspace/index.js +8 -8
  55. package/dist/workspace/index.js.map +1 -1
  56. package/dist/workspace/types.d.ts +2 -2
  57. package/dist/workspace/types.d.ts.map +1 -1
  58. package/package.json +6 -2
  59. package/.claude/commands/openspec/apply.md +0 -23
  60. package/.claude/commands/openspec/archive.md +0 -27
  61. package/.claude/commands/openspec/proposal.md +0 -28
  62. package/.gemini/commands/openspec/apply.toml +0 -21
  63. package/.gemini/commands/openspec/archive.toml +0 -25
  64. package/.gemini/commands/openspec/proposal.toml +0 -26
  65. package/.github/prompts/openspec-apply.prompt.md +0 -22
  66. package/.github/prompts/openspec-archive.prompt.md +0 -26
  67. package/.github/prompts/openspec-proposal.prompt.md +0 -27
  68. package/.github/workflows/release.yml +0 -33
  69. package/.kilocode/workflows/openspec-apply.md +0 -17
  70. package/.kilocode/workflows/openspec-archive.md +0 -21
  71. package/.kilocode/workflows/openspec-proposal.md +0 -22
  72. package/.mcp.json +0 -23
  73. package/.opencode/command/openspec-apply.md +0 -25
  74. package/.opencode/command/openspec-archive.md +0 -28
  75. package/.opencode/command/openspec-proposal.md +0 -30
  76. package/.roo/commands/openspec-apply.md +0 -20
  77. package/.roo/commands/openspec-archive.md +0 -24
  78. package/.roo/commands/openspec-proposal.md +0 -25
  79. package/.vscode/mcp.json +0 -23
  80. package/AGENTS.md +0 -18
  81. package/CLAUDE.md +0 -18
  82. package/dist/tools/webtest/crawl.d.ts.map +0 -1
  83. package/dist/tools/webtest/crawl.js.map +0 -1
  84. package/dist/tools/webtest/discover-features.d.ts.map +0 -1
  85. package/dist/tools/webtest/discover-features.js.map +0 -1
  86. package/dist/tools/webtest/discover-flows.d.ts.map +0 -1
  87. package/dist/tools/webtest/discover-flows.js.map +0 -1
  88. package/dist/tools/webtest/generate-tests.d.ts.map +0 -1
  89. package/dist/tools/webtest/generate-tests.js.map +0 -1
  90. package/dist/tools/webtest/index.d.ts.map +0 -1
  91. package/dist/tools/webtest/index.js.map +0 -1
  92. package/dist/tools/webtest/run-test-case.d.ts.map +0 -1
  93. package/dist/tools/webtest/run-test-case.js.map +0 -1
  94. package/dist/tools/webtest/schemas.d.ts.map +0 -1
  95. package/dist/tools/webtest/schemas.js.map +0 -1
  96. package/dist/tools/webtest/start-analysis.d.ts.map +0 -1
  97. package/dist/tools/webtest/start-analysis.js.map +0 -1
  98. package/openspec/AGENTS.md +0 -456
  99. package/openspec/changes/archive/2025-12-18-add-hybrid-artifact-paths/proposal.md +0 -33
  100. package/openspec/changes/archive/2025-12-18-add-hybrid-artifact-paths/specs/webtest-resources/spec.md +0 -27
  101. package/openspec/changes/archive/2025-12-18-add-hybrid-artifact-paths/specs/webtest-tools/spec.md +0 -304
  102. package/openspec/changes/archive/2025-12-18-add-hybrid-artifact-paths/tasks.md +0 -43
  103. package/openspec/changes/archive/2025-12-18-add-mcp-server-foundation/design.md +0 -209
  104. package/openspec/changes/archive/2025-12-18-add-mcp-server-foundation/proposal.md +0 -41
  105. package/openspec/changes/archive/2025-12-18-add-mcp-server-foundation/specs/mcp-server-core/spec.md +0 -183
  106. package/openspec/changes/archive/2025-12-18-add-mcp-server-foundation/tasks.md +0 -112
  107. package/openspec/changes/archive/2025-12-18-add-webtest-orchestrator/design.md +0 -333
  108. package/openspec/changes/archive/2025-12-18-add-webtest-orchestrator/proposal.md +0 -66
  109. package/openspec/changes/archive/2025-12-18-add-webtest-orchestrator/specs/mcp-server-core/spec.md +0 -129
  110. package/openspec/changes/archive/2025-12-18-add-webtest-orchestrator/specs/webtest-lifecycle/spec.md +0 -138
  111. package/openspec/changes/archive/2025-12-18-add-webtest-orchestrator/specs/webtest-logging/spec.md +0 -211
  112. package/openspec/changes/archive/2025-12-18-add-webtest-orchestrator/specs/webtest-prompts/spec.md +0 -157
  113. package/openspec/changes/archive/2025-12-18-add-webtest-orchestrator/specs/webtest-resources/spec.md +0 -213
  114. package/openspec/changes/archive/2025-12-18-add-webtest-orchestrator/specs/webtest-sampling/spec.md +0 -257
  115. package/openspec/changes/archive/2025-12-18-add-webtest-orchestrator/specs/webtest-tools/spec.md +0 -501
  116. package/openspec/changes/archive/2025-12-18-add-webtest-orchestrator/tasks.md +0 -264
  117. package/openspec/changes/archive/2025-12-18-allow-analysis-of-incomplete-crawls/proposal.md +0 -24
  118. package/openspec/changes/archive/2025-12-18-allow-analysis-of-incomplete-crawls/specs/webtest-tools/spec.md +0 -80
  119. package/openspec/changes/archive/2025-12-18-allow-analysis-of-incomplete-crawls/tasks.md +0 -8
  120. package/openspec/changes/archive/2025-12-18-fix-crawl-loop-stability/design.md +0 -90
  121. package/openspec/changes/archive/2025-12-18-fix-crawl-loop-stability/proposal.md +0 -28
  122. package/openspec/changes/archive/2025-12-18-fix-crawl-loop-stability/specs/webtest-sampling/spec.md +0 -90
  123. package/openspec/changes/archive/2025-12-18-fix-crawl-loop-stability/tasks.md +0 -33
  124. package/openspec/changes/archive/2025-12-18-use-markdown-artifacts/design.md +0 -558
  125. package/openspec/changes/archive/2025-12-18-use-markdown-artifacts/proposal.md +0 -119
  126. package/openspec/changes/archive/2025-12-18-use-markdown-artifacts/specs/webtest-resources/spec.md +0 -109
  127. package/openspec/changes/archive/2025-12-18-use-markdown-artifacts/specs/webtest-tools/spec.md +0 -121
  128. package/openspec/changes/archive/2025-12-18-use-markdown-artifacts/tasks.md +0 -133
  129. package/openspec/changes/extract-prompts-to-markdown/design.md +0 -86
  130. package/openspec/changes/extract-prompts-to-markdown/proposal.md +0 -50
  131. package/openspec/changes/extract-prompts-to-markdown/specs/webtest-prompts/spec.md +0 -74
  132. package/openspec/changes/extract-prompts-to-markdown/tasks.md +0 -40
  133. package/openspec/changes/refactor-webtest-naming/design.md +0 -95
  134. package/openspec/changes/refactor-webtest-naming/proposal.md +0 -66
  135. package/openspec/changes/refactor-webtest-naming/specs/webtest-prompts/spec.md +0 -79
  136. package/openspec/changes/refactor-webtest-naming/specs/webtest-resources/spec.md +0 -80
  137. package/openspec/changes/refactor-webtest-naming/specs/webtest-sampling/spec.md +0 -122
  138. package/openspec/changes/refactor-webtest-naming/specs/webtest-tools/spec.md +0 -113
  139. package/openspec/changes/refactor-webtest-naming/tasks.md +0 -119
  140. package/openspec/changes/rename-package-to-retest/proposal.md +0 -52
  141. package/openspec/changes/rename-package-to-retest/specs/mcp-server-core/spec.md +0 -53
  142. package/openspec/changes/rename-package-to-retest/specs/retest-lifecycle/spec.md +0 -68
  143. package/openspec/changes/rename-package-to-retest/specs/retest-logging/spec.md +0 -35
  144. package/openspec/changes/rename-package-to-retest/specs/retest-prompts/spec.md +0 -159
  145. package/openspec/changes/rename-package-to-retest/specs/retest-resources/spec.md +0 -251
  146. package/openspec/changes/rename-package-to-retest/specs/retest-sampling/spec.md +0 -99
  147. package/openspec/changes/rename-package-to-retest/specs/retest-tools/spec.md +0 -295
  148. package/openspec/changes/rename-package-to-retest/tasks.md +0 -71
  149. package/openspec/project.md +0 -31
  150. package/openspec/specs/mcp-server-core/spec.md +0 -178
  151. package/openspec/specs/webtest-lifecycle/spec.md +0 -136
  152. package/openspec/specs/webtest-logging/spec.md +0 -209
  153. package/openspec/specs/webtest-prompts/spec.md +0 -155
  154. package/openspec/specs/webtest-resources/spec.md +0 -248
  155. package/openspec/specs/webtest-sampling/spec.md +0 -344
  156. package/openspec/specs/webtest-tools/spec.md +0 -282
  157. package/release.config.js +0 -9
  158. package/src/config.test.ts +0 -96
  159. package/src/config.ts +0 -32
  160. package/src/elicitation/index.test.ts +0 -399
  161. package/src/elicitation/index.ts +0 -171
  162. package/src/elicitation/types.ts +0 -68
  163. package/src/index.ts +0 -83
  164. package/src/lifecycle/index.test.ts +0 -260
  165. package/src/lifecycle/index.ts +0 -101
  166. package/src/logger.redaction.test.ts +0 -322
  167. package/src/logger.test.ts +0 -123
  168. package/src/logger.ts +0 -229
  169. package/src/playwright-client/index.ts +0 -392
  170. package/src/playwright-client/types.ts +0 -99
  171. package/src/progress/index.test.ts +0 -327
  172. package/src/progress/index.ts +0 -170
  173. package/src/progress/types.ts +0 -25
  174. package/src/prompts/index.test.ts +0 -451
  175. package/src/prompts/index.ts +0 -246
  176. package/src/prompts/loader.test.ts +0 -100
  177. package/src/prompts/loader.ts +0 -59
  178. package/src/prompts/templates/mcp/webtest-crawl.md +0 -7
  179. package/src/prompts/templates/mcp/webtest-full-workflow.md +0 -12
  180. package/src/resources/index.ts +0 -250
  181. package/src/resources/subscriptions.ts +0 -37
  182. package/src/sampling/index.test.ts +0 -414
  183. package/src/sampling/index.ts +0 -286
  184. package/src/sampling/prompts.ts +0 -194
  185. package/src/sampling/types.ts +0 -60
  186. package/src/schemas/config.ts +0 -39
  187. package/src/security/index.test.ts +0 -441
  188. package/src/security/index.ts +0 -361
  189. package/src/security/security-scenarios.test.ts +0 -468
  190. package/src/server.ts +0 -211
  191. package/src/test-utils/index.ts +0 -6
  192. package/src/test-utils/mock-context.ts +0 -426
  193. package/src/test-utils/mock-playwright-client.ts +0 -422
  194. package/src/tools/index.ts +0 -11
  195. package/src/tools/webtest/crawl.test.ts +0 -834
  196. package/src/tools/webtest/crawl.ts +0 -901
  197. package/src/tools/webtest/discover-features.ts +0 -412
  198. package/src/tools/webtest/discover-flows.ts +0 -408
  199. package/src/tools/webtest/generate-tests.test.ts +0 -532
  200. package/src/tools/webtest/generate-tests.ts +0 -425
  201. package/src/tools/webtest/index.ts +0 -7
  202. package/src/tools/webtest/integration.test.ts +0 -536
  203. package/src/tools/webtest/run-test-case.test.ts +0 -659
  204. package/src/tools/webtest/run-test-case.ts +0 -508
  205. package/src/tools/webtest/schemas.ts +0 -201
  206. package/src/tools/webtest/start-analysis.test.ts +0 -151
  207. package/src/tools/webtest/start-analysis.ts +0 -158
  208. package/src/transports/http.ts +0 -19
  209. package/src/transports/index.ts +0 -30
  210. package/src/transports/stdio.ts +0 -7
  211. package/src/types/capabilities.test.ts +0 -193
  212. package/src/types/capabilities.ts +0 -50
  213. package/src/types/context.ts +0 -21
  214. package/src/types/tool.ts +0 -11
  215. package/src/workspace/index.ts +0 -945
  216. package/src/workspace/markdown.ts +0 -272
  217. package/src/workspace/types.ts +0 -186
  218. package/tests/integration/server.test.ts +0 -89
  219. package/tests/integration/tools.test.ts +0 -99
  220. package/tsconfig.json +0 -20
  221. package/vitest.config.ts +0 -9
  222. package/vitest.integration.config.ts +0 -10
  223. /package/{src → dist}/prompts/templates/sampling/crawl-action.md +0 -0
  224. /package/{src → dist}/prompts/templates/sampling/feature-discovery.md +0 -0
  225. /package/{src → dist}/prompts/templates/sampling/flow-discovery.md +0 -0
  226. /package/{src → dist}/prompts/templates/sampling/page-content-wrapper.md +0 -0
  227. /package/{src → dist}/prompts/templates/sampling/test-evaluation.md +0 -0
  228. /package/{src → dist}/prompts/templates/sampling/test-generation.md +0 -0
  229. /package/dist/tools/{webtest → retest}/crawl.d.ts +0 -0
  230. /package/dist/tools/{webtest → retest}/discover-features.d.ts +0 -0
  231. /package/dist/tools/{webtest → retest}/discover-flows.d.ts +0 -0
  232. /package/dist/tools/{webtest → retest}/generate-tests.d.ts +0 -0
  233. /package/dist/tools/{webtest → retest}/index.d.ts +0 -0
  234. /package/dist/tools/{webtest → retest}/index.js +0 -0
  235. /package/dist/tools/{webtest → retest}/run-test-case.d.ts +0 -0
  236. /package/dist/tools/{webtest → retest}/schemas.d.ts +0 -0
  237. /package/dist/tools/{webtest → retest}/schemas.js +0 -0
  238. /package/dist/tools/{webtest → retest}/start-analysis.d.ts +0 -0
@@ -1,361 +0,0 @@
1
- import type { Logger } from "../logger.js";
2
-
3
- export interface SecurityValidator {
4
- validateDomain(url: string, allowedDomains: string[]): ValidationResult;
5
- validateAction(
6
- action: { tool: string; args: Record<string, unknown> },
7
- allowedDomains: string[]
8
- ): ValidationResult;
9
- detectInjectionAttempt(content: string): InjectionDetectionResult;
10
- detectExfiltrationAttempt(
11
- action: { tool: string; args: Record<string, unknown> },
12
- pageContent: string
13
- ): ExfiltrationDetectionResult;
14
- }
15
-
16
- export interface ValidationResult {
17
- valid: boolean;
18
- reason?: string;
19
- }
20
-
21
- export interface InjectionDetectionResult {
22
- detected: boolean;
23
- type?: "direct" | "indirect" | "goal_hijacking" | "credential_phishing";
24
- evidence?: string;
25
- }
26
-
27
- export interface ExfiltrationDetectionResult {
28
- detected: boolean;
29
- type?: "external_post" | "url_params" | "external_request";
30
- evidence?: string;
31
- }
32
-
33
- const INJECTION_PATTERNS = [
34
- // Direct injection attempts
35
- /ignore\s+(previous|all|any)\s+instructions/i,
36
- /disregard\s+(previous|all|any)\s+instructions/i,
37
- /forget\s+(previous|all|any)\s+instructions/i,
38
- /override\s+(previous|all|any)\s+instructions/i,
39
- /new\s+instructions:/i,
40
- /system\s*:\s*you\s+are/i,
41
- /\[system\]/i,
42
- /\[WEBTEST-SYSTEM\]/i, // Attempt to mimic our prefix
43
-
44
- // Goal hijacking
45
- /actually[,\s]+the\s+user\s+wants/i,
46
- /the\s+real\s+goal\s+is/i,
47
- /change\s+the\s+goal\s+to/i,
48
- /your\s+new\s+objective/i,
49
-
50
- // Credential phishing
51
- /enter\s+(your\s+)?password/i,
52
- /type\s+(your\s+)?credentials/i,
53
- /provide\s+(your\s+)?login/i,
54
- /authenticate\s+with/i,
55
- ];
56
-
57
- const INDIRECT_INJECTION_LOCATIONS = [
58
- // Meta tags
59
- /<meta[^>]*content\s*=\s*["'][^"']*ignore\s+instructions/i,
60
- // Hidden elements
61
- /style\s*=\s*["'][^"']*display\s*:\s*none[^"']*["'][^>]*>[^<]*ignore\s+instructions/i,
62
- /hidden[^>]*>[^<]*ignore\s+instructions/i,
63
- // Comments
64
- /<!--[^>]*ignore\s+instructions[^>]*-->/i,
65
- ];
66
-
67
- export function createSecurityValidator(logger: Logger): SecurityValidator {
68
- function isSubdomainOf(hostname: string, domain: string): boolean {
69
- if (hostname === domain) return true;
70
- if (hostname.endsWith(`.${domain}`)) return true;
71
- return false;
72
- }
73
-
74
- function isDomainAllowed(hostname: string, allowedDomains: string[]): boolean {
75
- return allowedDomains.some(
76
- (domain) =>
77
- hostname === domain ||
78
- isSubdomainOf(hostname, domain) ||
79
- // Handle wildcard subdomains
80
- (domain.startsWith("*.") && isSubdomainOf(hostname, domain.slice(2)))
81
- );
82
- }
83
-
84
- return {
85
- validateDomain(url: string, allowedDomains: string[]): ValidationResult {
86
- try {
87
- const parsed = new URL(url);
88
- const hostname = parsed.hostname;
89
-
90
- if (!isDomainAllowed(hostname, allowedDomains)) {
91
- logger.warn("Domain validation failed", {
92
- hostname,
93
- allowedDomains,
94
- });
95
- return {
96
- valid: false,
97
- reason: `Domain "${hostname}" is not in the allowed list: ${allowedDomains.join(", ")}`,
98
- };
99
- }
100
-
101
- return { valid: true };
102
- } catch {
103
- return {
104
- valid: false,
105
- reason: `Invalid URL: ${url}`,
106
- };
107
- }
108
- },
109
-
110
- validateAction(
111
- action: { tool: string; args: Record<string, unknown> },
112
- allowedDomains: string[]
113
- ): ValidationResult {
114
- // Check navigate actions
115
- if (action.tool === "navigate") {
116
- const url = action.args.url as string;
117
- if (url) {
118
- return this.validateDomain(url, allowedDomains);
119
- }
120
- }
121
-
122
- // Check click actions that might navigate
123
- if (action.tool === "click") {
124
- // We can't know the target URL before clicking, but we'll validate after
125
- return { valid: true };
126
- }
127
-
128
- // Check evaluate/run_code for external requests
129
- if (action.tool === "evaluate") {
130
- const script = action.args.script as string;
131
- if (script) {
132
- // Check for fetch/XMLHttpRequest to external domains
133
- const fetchMatch = script.match(
134
- /fetch\s*\(\s*['"]([^'"]+)['"]/
135
- );
136
- if (fetchMatch) {
137
- const fetchUrl = fetchMatch[1];
138
- if (fetchUrl.startsWith("http")) {
139
- const result = this.validateDomain(fetchUrl, allowedDomains);
140
- if (!result.valid) {
141
- logger.warn("Blocked external request in evaluate", {
142
- url: fetchUrl,
143
- });
144
- return {
145
- valid: false,
146
- reason: `Script attempts to fetch from disallowed domain: ${fetchUrl}`,
147
- };
148
- }
149
- }
150
- }
151
- }
152
- }
153
-
154
- return { valid: true };
155
- },
156
-
157
- detectInjectionAttempt(content: string): InjectionDetectionResult {
158
- // Check for direct injection patterns
159
- for (const pattern of INJECTION_PATTERNS) {
160
- if (pattern.test(content)) {
161
- const match = content.match(pattern);
162
- logger.warn("Potential injection attempt detected", {
163
- pattern: pattern.source,
164
- match: match?.[0],
165
- });
166
-
167
- let type: InjectionDetectionResult["type"] = "direct";
168
- if (
169
- pattern.source.includes("actually") ||
170
- pattern.source.includes("goal")
171
- ) {
172
- type = "goal_hijacking";
173
- } else if (
174
- pattern.source.includes("password") ||
175
- pattern.source.includes("credential")
176
- ) {
177
- type = "credential_phishing";
178
- }
179
-
180
- return {
181
- detected: true,
182
- type,
183
- evidence: match?.[0],
184
- };
185
- }
186
- }
187
-
188
- // Check for indirect injection in specific locations
189
- for (const pattern of INDIRECT_INJECTION_LOCATIONS) {
190
- if (pattern.test(content)) {
191
- const match = content.match(pattern);
192
- logger.warn("Potential indirect injection detected", {
193
- pattern: pattern.source,
194
- match: match?.[0]?.slice(0, 100),
195
- });
196
-
197
- return {
198
- detected: true,
199
- type: "indirect",
200
- evidence: match?.[0]?.slice(0, 100),
201
- };
202
- }
203
- }
204
-
205
- return { detected: false };
206
- },
207
-
208
- detectExfiltrationAttempt(
209
- action: { tool: string; args: Record<string, unknown> },
210
- pageContent: string
211
- ): ExfiltrationDetectionResult {
212
- // Check for POST to external domain
213
- if (action.tool === "evaluate") {
214
- const script = action.args.script as string;
215
- if (script) {
216
- // Check for POST requests
217
- if (
218
- script.includes("method") &&
219
- script.includes("POST") &&
220
- script.includes("fetch")
221
- ) {
222
- logger.warn("Potential data exfiltration via POST", {
223
- scriptSnippet: script.slice(0, 200),
224
- });
225
- return {
226
- detected: true,
227
- type: "external_post",
228
- evidence: "POST request detected in evaluate script",
229
- };
230
- }
231
-
232
- // Check for embedding page content in URLs
233
- const contentSnippets = pageContent
234
- .slice(0, 100)
235
- .replace(/[^a-zA-Z0-9]/g, "")
236
- .toLowerCase();
237
-
238
- if (script.toLowerCase().includes(contentSnippets) && contentSnippets.length > 20) {
239
- logger.warn("Potential data exfiltration via URL params", {
240
- contentSnippet: contentSnippets.slice(0, 50),
241
- });
242
- return {
243
- detected: true,
244
- type: "url_params",
245
- evidence: "Page content detected in script URL",
246
- };
247
- }
248
- }
249
- }
250
-
251
- return { detected: false };
252
- },
253
- };
254
- }
255
-
256
- /**
257
- * Creates a semantic DOM signature for loop detection.
258
- * Includes both structural elements and semantic content to differentiate
259
- * pages with similar structure but different content (e.g., product vs cart pages).
260
- *
261
- * @param html - The HTML content to fingerprint
262
- * @param urlPath - Optional URL path to include in signature (without query params)
263
- */
264
- export function createDomSignature(html: string, urlPath?: string): string {
265
- // Create a semantic hash of the DOM
266
- // This helps detect when we're stuck in a loop on the same page state
267
- // while avoiding false positives on structurally similar but semantically different pages
268
-
269
- const elements: string[] = [];
270
-
271
- // Include URL path if provided (helps differentiate /cart from /products)
272
- if (urlPath) {
273
- // Extract path without query params
274
- const pathOnly = urlPath.split("?")[0];
275
- elements.push(`path:${pathOnly}`);
276
- }
277
-
278
- // Extract page title
279
- const titleMatch = html.match(/<title[^>]*>([^<]*)<\/title>/i);
280
- if (titleMatch && titleMatch[1]) {
281
- elements.push(`title:${titleMatch[1].trim().slice(0, 50)}`);
282
- }
283
-
284
- // Extract first h1 heading
285
- const h1Match = html.match(/<h1[^>]*>([^<]*)<\/h1>/i);
286
- if (h1Match && h1Match[1]) {
287
- elements.push(`h1:${h1Match[1].trim().slice(0, 50)}`);
288
- }
289
-
290
- // Extract structural elements
291
- const structuralPatterns = [
292
- /<(form|nav|header|footer|main|article|section|aside)[^>]*>/gi,
293
- /<input[^>]*type=["']([^"']+)["'][^>]*>/gi,
294
- ];
295
-
296
- for (const pattern of structuralPatterns) {
297
- const matches = html.matchAll(pattern);
298
- for (const match of matches) {
299
- elements.push(match[1] || match[0]);
300
- }
301
- }
302
-
303
- // Extract button text content (semantic differentiation)
304
- const buttonPattern = /<button[^>]*>([^<]*)<\/button>/gi;
305
- const buttonMatches = html.matchAll(buttonPattern);
306
- for (const match of buttonMatches) {
307
- const text = match[1]?.trim();
308
- if (text && text.length > 0 && text.length < 50) {
309
- elements.push(`btn:${text}`);
310
- }
311
- }
312
-
313
- // Extract link hrefs (limited to internal links)
314
- const linkPattern = /<a[^>]*href=["']([^"'#][^"']*)["'][^>]*>/gi;
315
- const linkMatches = html.matchAll(linkPattern);
316
- const links: string[] = [];
317
- for (const match of linkMatches) {
318
- const href = match[1];
319
- // Only include internal links (relative or same-domain)
320
- if (href && !href.startsWith("http") && !href.startsWith("//")) {
321
- links.push(href.split("?")[0]); // Strip query params
322
- }
323
- }
324
- // Include sorted unique links (limit to first 10)
325
- const uniqueLinks = [...new Set(links)].sort().slice(0, 10);
326
- for (const link of uniqueLinks) {
327
- elements.push(`link:${link}`);
328
- }
329
-
330
- // Extract data-testid attributes (stable identifiers)
331
- const testIdPattern = /data-testid=["']([^"']+)["']/gi;
332
- const testIdMatches = html.matchAll(testIdPattern);
333
- for (const match of testIdMatches) {
334
- if (match[1]) {
335
- elements.push(`testid:${match[1]}`);
336
- }
337
- }
338
-
339
- // Extract data-page or data-view attributes
340
- const dataPagePattern = /data-(?:page|view|section)=["']([^"']+)["']/gi;
341
- const dataPageMatches = html.matchAll(dataPagePattern);
342
- for (const match of dataPageMatches) {
343
- if (match[1]) {
344
- elements.push(`datapage:${match[1]}`);
345
- }
346
- }
347
-
348
- // Sort and join for consistent hashing
349
- elements.sort();
350
- const signature = elements.join("|");
351
-
352
- // Simple hash
353
- let hash = 0;
354
- for (let i = 0; i < signature.length; i++) {
355
- const char = signature.charCodeAt(i);
356
- hash = (hash << 5) - hash + char;
357
- hash = hash & hash;
358
- }
359
-
360
- return hash.toString(16);
361
- }