@apitap/core 1.0.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 (236) hide show
  1. package/LICENSE +60 -0
  2. package/README.md +362 -0
  3. package/SKILL.md +270 -0
  4. package/dist/auth/crypto.d.ts +31 -0
  5. package/dist/auth/crypto.js +66 -0
  6. package/dist/auth/crypto.js.map +1 -0
  7. package/dist/auth/handoff.d.ts +29 -0
  8. package/dist/auth/handoff.js +180 -0
  9. package/dist/auth/handoff.js.map +1 -0
  10. package/dist/auth/manager.d.ts +46 -0
  11. package/dist/auth/manager.js +127 -0
  12. package/dist/auth/manager.js.map +1 -0
  13. package/dist/auth/oauth-refresh.d.ts +16 -0
  14. package/dist/auth/oauth-refresh.js +91 -0
  15. package/dist/auth/oauth-refresh.js.map +1 -0
  16. package/dist/auth/refresh.d.ts +43 -0
  17. package/dist/auth/refresh.js +217 -0
  18. package/dist/auth/refresh.js.map +1 -0
  19. package/dist/capture/anti-bot.d.ts +15 -0
  20. package/dist/capture/anti-bot.js +43 -0
  21. package/dist/capture/anti-bot.js.map +1 -0
  22. package/dist/capture/blocklist.d.ts +6 -0
  23. package/dist/capture/blocklist.js +70 -0
  24. package/dist/capture/blocklist.js.map +1 -0
  25. package/dist/capture/body-diff.d.ts +8 -0
  26. package/dist/capture/body-diff.js +102 -0
  27. package/dist/capture/body-diff.js.map +1 -0
  28. package/dist/capture/body-variables.d.ts +13 -0
  29. package/dist/capture/body-variables.js +142 -0
  30. package/dist/capture/body-variables.js.map +1 -0
  31. package/dist/capture/domain.d.ts +8 -0
  32. package/dist/capture/domain.js +34 -0
  33. package/dist/capture/domain.js.map +1 -0
  34. package/dist/capture/entropy.d.ts +33 -0
  35. package/dist/capture/entropy.js +100 -0
  36. package/dist/capture/entropy.js.map +1 -0
  37. package/dist/capture/filter.d.ts +11 -0
  38. package/dist/capture/filter.js +49 -0
  39. package/dist/capture/filter.js.map +1 -0
  40. package/dist/capture/graphql.d.ts +21 -0
  41. package/dist/capture/graphql.js +99 -0
  42. package/dist/capture/graphql.js.map +1 -0
  43. package/dist/capture/idle.d.ts +23 -0
  44. package/dist/capture/idle.js +44 -0
  45. package/dist/capture/idle.js.map +1 -0
  46. package/dist/capture/monitor.d.ts +26 -0
  47. package/dist/capture/monitor.js +183 -0
  48. package/dist/capture/monitor.js.map +1 -0
  49. package/dist/capture/oauth-detector.d.ts +18 -0
  50. package/dist/capture/oauth-detector.js +96 -0
  51. package/dist/capture/oauth-detector.js.map +1 -0
  52. package/dist/capture/pagination.d.ts +9 -0
  53. package/dist/capture/pagination.js +40 -0
  54. package/dist/capture/pagination.js.map +1 -0
  55. package/dist/capture/parameterize.d.ts +17 -0
  56. package/dist/capture/parameterize.js +63 -0
  57. package/dist/capture/parameterize.js.map +1 -0
  58. package/dist/capture/scrubber.d.ts +5 -0
  59. package/dist/capture/scrubber.js +38 -0
  60. package/dist/capture/scrubber.js.map +1 -0
  61. package/dist/capture/session.d.ts +46 -0
  62. package/dist/capture/session.js +445 -0
  63. package/dist/capture/session.js.map +1 -0
  64. package/dist/capture/token-detector.d.ts +16 -0
  65. package/dist/capture/token-detector.js +62 -0
  66. package/dist/capture/token-detector.js.map +1 -0
  67. package/dist/capture/verifier.d.ts +17 -0
  68. package/dist/capture/verifier.js +147 -0
  69. package/dist/capture/verifier.js.map +1 -0
  70. package/dist/cli.d.ts +2 -0
  71. package/dist/cli.js +930 -0
  72. package/dist/cli.js.map +1 -0
  73. package/dist/discovery/auth.d.ts +17 -0
  74. package/dist/discovery/auth.js +81 -0
  75. package/dist/discovery/auth.js.map +1 -0
  76. package/dist/discovery/fetch.d.ts +17 -0
  77. package/dist/discovery/fetch.js +59 -0
  78. package/dist/discovery/fetch.js.map +1 -0
  79. package/dist/discovery/frameworks.d.ts +11 -0
  80. package/dist/discovery/frameworks.js +249 -0
  81. package/dist/discovery/frameworks.js.map +1 -0
  82. package/dist/discovery/index.d.ts +21 -0
  83. package/dist/discovery/index.js +219 -0
  84. package/dist/discovery/index.js.map +1 -0
  85. package/dist/discovery/openapi.d.ts +13 -0
  86. package/dist/discovery/openapi.js +175 -0
  87. package/dist/discovery/openapi.js.map +1 -0
  88. package/dist/discovery/probes.d.ts +9 -0
  89. package/dist/discovery/probes.js +70 -0
  90. package/dist/discovery/probes.js.map +1 -0
  91. package/dist/index.d.ts +25 -0
  92. package/dist/index.js +25 -0
  93. package/dist/index.js.map +1 -0
  94. package/dist/inspect/report.d.ts +52 -0
  95. package/dist/inspect/report.js +191 -0
  96. package/dist/inspect/report.js.map +1 -0
  97. package/dist/mcp.d.ts +8 -0
  98. package/dist/mcp.js +526 -0
  99. package/dist/mcp.js.map +1 -0
  100. package/dist/orchestration/browse.d.ts +38 -0
  101. package/dist/orchestration/browse.js +198 -0
  102. package/dist/orchestration/browse.js.map +1 -0
  103. package/dist/orchestration/cache.d.ts +15 -0
  104. package/dist/orchestration/cache.js +24 -0
  105. package/dist/orchestration/cache.js.map +1 -0
  106. package/dist/plugin.d.ts +17 -0
  107. package/dist/plugin.js +158 -0
  108. package/dist/plugin.js.map +1 -0
  109. package/dist/read/decoders/deepwiki.d.ts +2 -0
  110. package/dist/read/decoders/deepwiki.js +148 -0
  111. package/dist/read/decoders/deepwiki.js.map +1 -0
  112. package/dist/read/decoders/grokipedia.d.ts +2 -0
  113. package/dist/read/decoders/grokipedia.js +210 -0
  114. package/dist/read/decoders/grokipedia.js.map +1 -0
  115. package/dist/read/decoders/hackernews.d.ts +2 -0
  116. package/dist/read/decoders/hackernews.js +168 -0
  117. package/dist/read/decoders/hackernews.js.map +1 -0
  118. package/dist/read/decoders/index.d.ts +2 -0
  119. package/dist/read/decoders/index.js +12 -0
  120. package/dist/read/decoders/index.js.map +1 -0
  121. package/dist/read/decoders/reddit.d.ts +2 -0
  122. package/dist/read/decoders/reddit.js +142 -0
  123. package/dist/read/decoders/reddit.js.map +1 -0
  124. package/dist/read/decoders/twitter.d.ts +12 -0
  125. package/dist/read/decoders/twitter.js +187 -0
  126. package/dist/read/decoders/twitter.js.map +1 -0
  127. package/dist/read/decoders/wikipedia.d.ts +2 -0
  128. package/dist/read/decoders/wikipedia.js +66 -0
  129. package/dist/read/decoders/wikipedia.js.map +1 -0
  130. package/dist/read/decoders/youtube.d.ts +2 -0
  131. package/dist/read/decoders/youtube.js +69 -0
  132. package/dist/read/decoders/youtube.js.map +1 -0
  133. package/dist/read/extract.d.ts +25 -0
  134. package/dist/read/extract.js +320 -0
  135. package/dist/read/extract.js.map +1 -0
  136. package/dist/read/index.d.ts +14 -0
  137. package/dist/read/index.js +66 -0
  138. package/dist/read/index.js.map +1 -0
  139. package/dist/read/peek.d.ts +9 -0
  140. package/dist/read/peek.js +137 -0
  141. package/dist/read/peek.js.map +1 -0
  142. package/dist/read/types.d.ts +44 -0
  143. package/dist/read/types.js +3 -0
  144. package/dist/read/types.js.map +1 -0
  145. package/dist/replay/engine.d.ts +53 -0
  146. package/dist/replay/engine.js +441 -0
  147. package/dist/replay/engine.js.map +1 -0
  148. package/dist/replay/truncate.d.ts +16 -0
  149. package/dist/replay/truncate.js +92 -0
  150. package/dist/replay/truncate.js.map +1 -0
  151. package/dist/serve.d.ts +31 -0
  152. package/dist/serve.js +149 -0
  153. package/dist/serve.js.map +1 -0
  154. package/dist/skill/generator.d.ts +44 -0
  155. package/dist/skill/generator.js +419 -0
  156. package/dist/skill/generator.js.map +1 -0
  157. package/dist/skill/importer.d.ts +26 -0
  158. package/dist/skill/importer.js +80 -0
  159. package/dist/skill/importer.js.map +1 -0
  160. package/dist/skill/search.d.ts +19 -0
  161. package/dist/skill/search.js +51 -0
  162. package/dist/skill/search.js.map +1 -0
  163. package/dist/skill/signing.d.ts +16 -0
  164. package/dist/skill/signing.js +34 -0
  165. package/dist/skill/signing.js.map +1 -0
  166. package/dist/skill/ssrf.d.ts +27 -0
  167. package/dist/skill/ssrf.js +210 -0
  168. package/dist/skill/ssrf.js.map +1 -0
  169. package/dist/skill/store.d.ts +7 -0
  170. package/dist/skill/store.js +93 -0
  171. package/dist/skill/store.js.map +1 -0
  172. package/dist/stats/report.d.ts +26 -0
  173. package/dist/stats/report.js +157 -0
  174. package/dist/stats/report.js.map +1 -0
  175. package/dist/types.d.ts +214 -0
  176. package/dist/types.js +3 -0
  177. package/dist/types.js.map +1 -0
  178. package/package.json +58 -0
  179. package/src/auth/crypto.ts +92 -0
  180. package/src/auth/handoff.ts +229 -0
  181. package/src/auth/manager.ts +140 -0
  182. package/src/auth/oauth-refresh.ts +120 -0
  183. package/src/auth/refresh.ts +300 -0
  184. package/src/capture/anti-bot.ts +63 -0
  185. package/src/capture/blocklist.ts +75 -0
  186. package/src/capture/body-diff.ts +109 -0
  187. package/src/capture/body-variables.ts +156 -0
  188. package/src/capture/domain.ts +34 -0
  189. package/src/capture/entropy.ts +121 -0
  190. package/src/capture/filter.ts +56 -0
  191. package/src/capture/graphql.ts +124 -0
  192. package/src/capture/idle.ts +45 -0
  193. package/src/capture/monitor.ts +224 -0
  194. package/src/capture/oauth-detector.ts +106 -0
  195. package/src/capture/pagination.ts +49 -0
  196. package/src/capture/parameterize.ts +68 -0
  197. package/src/capture/scrubber.ts +49 -0
  198. package/src/capture/session.ts +502 -0
  199. package/src/capture/token-detector.ts +76 -0
  200. package/src/capture/verifier.ts +171 -0
  201. package/src/cli.ts +1031 -0
  202. package/src/discovery/auth.ts +99 -0
  203. package/src/discovery/fetch.ts +85 -0
  204. package/src/discovery/frameworks.ts +231 -0
  205. package/src/discovery/index.ts +256 -0
  206. package/src/discovery/openapi.ts +230 -0
  207. package/src/discovery/probes.ts +76 -0
  208. package/src/index.ts +26 -0
  209. package/src/inspect/report.ts +247 -0
  210. package/src/mcp.ts +618 -0
  211. package/src/orchestration/browse.ts +250 -0
  212. package/src/orchestration/cache.ts +37 -0
  213. package/src/plugin.ts +188 -0
  214. package/src/read/decoders/deepwiki.ts +180 -0
  215. package/src/read/decoders/grokipedia.ts +246 -0
  216. package/src/read/decoders/hackernews.ts +198 -0
  217. package/src/read/decoders/index.ts +15 -0
  218. package/src/read/decoders/reddit.ts +158 -0
  219. package/src/read/decoders/twitter.ts +211 -0
  220. package/src/read/decoders/wikipedia.ts +75 -0
  221. package/src/read/decoders/youtube.ts +75 -0
  222. package/src/read/extract.ts +396 -0
  223. package/src/read/index.ts +78 -0
  224. package/src/read/peek.ts +175 -0
  225. package/src/read/types.ts +37 -0
  226. package/src/replay/engine.ts +559 -0
  227. package/src/replay/truncate.ts +116 -0
  228. package/src/serve.ts +189 -0
  229. package/src/skill/generator.ts +473 -0
  230. package/src/skill/importer.ts +107 -0
  231. package/src/skill/search.ts +76 -0
  232. package/src/skill/signing.ts +36 -0
  233. package/src/skill/ssrf.ts +238 -0
  234. package/src/skill/store.ts +107 -0
  235. package/src/stats/report.ts +208 -0
  236. package/src/types.ts +233 -0
@@ -0,0 +1,142 @@
1
+ // src/capture/body-variables.ts
2
+ // Strategy 2: Name-based heuristics — keys implying dynamic values
3
+ const DYNAMIC_KEY_PATTERNS = [
4
+ // Time
5
+ /timestamp/i, /\btime\b/i, /\bdate\b/i, /created[_-]?at/i, /updated[_-]?at/i,
6
+ /\bsince\b/i, /\buntil\b/i, /\bbefore\b/i, /\bafter\b/i, /\bexpires?\b/i,
7
+ // Pagination
8
+ /\bcursor\b/i, /\boffset\b/i, /\bpage\b/i, /page[_-]?number/i,
9
+ /next[_-]?token/i, /continuation/i,
10
+ // Identity
11
+ /request[_-]?id/i, /correlation[_-]?id/i, /trace[_-]?id/i,
12
+ /\bnonce\b/i, /idempotency[_-]?key/i,
13
+ // Session
14
+ /session[_-]?id/i, /\bcsrf\b/i, /\bxsrf\b/i,
15
+ // Geolocation
16
+ /\bgeo(code|loc(ation)?)\b/i, /\blat(itude)?\b/i, /\blo?ng(itude)?\b/i,
17
+ /\bcoord/i, /\bzip\b/i, /\bpostal/i,
18
+ // Search / user input
19
+ /\bquery\b/i, /\bsearch/i, /\bkeyword/i, /\bterm\b/i, /\bfilter\b/i,
20
+ ];
21
+ function isDynamicKeyName(key) {
22
+ return DYNAMIC_KEY_PATTERNS.some(p => p.test(key));
23
+ }
24
+ // Strategy 3: Pattern-based detection — value patterns implying dynamic data
25
+ function isTimestampOrPattern(value) {
26
+ if (typeof value === 'number') {
27
+ // Unix epoch seconds (roughly 2001–2603)
28
+ if (Number.isInteger(value) && value >= 1e9 && value < 2e10)
29
+ return true;
30
+ // Unix epoch milliseconds
31
+ if (Number.isInteger(value) && value >= 1e12 && value < 2e13)
32
+ return true;
33
+ return false;
34
+ }
35
+ // ISO 8601 datetime
36
+ if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}/.test(value))
37
+ return true;
38
+ // ISO 8601 date only
39
+ if (/^\d{4}-\d{2}-\d{2}$/.test(value))
40
+ return true;
41
+ // Prefixed IDs (req_xxx, id_xxx, txn_xxx, msg_xxx, evt_xxx)
42
+ if (/^(req|id|txn|msg|evt)_[a-zA-Z0-9]+$/.test(value))
43
+ return true;
44
+ return false;
45
+ }
46
+ /**
47
+ * Detect which fields in a JSON body are likely dynamic variables.
48
+ * Uses three strategies:
49
+ * Strategy 1 (cross-request diffing) is in body-diff.ts
50
+ * Strategy 2: Name-based key heuristics
51
+ * Strategy 3: Pattern-based value detection
52
+ * Plus existing: numeric values, UUIDs, base64 cursors, numeric strings
53
+ */
54
+ export function detectBodyVariables(body, prefix = '') {
55
+ if (!body || typeof body !== 'object' || Array.isArray(body)) {
56
+ return [];
57
+ }
58
+ const detected = [];
59
+ const obj = body;
60
+ for (const [key, value] of Object.entries(obj)) {
61
+ const path = prefix ? `${prefix}.${key}` : key;
62
+ // Strategy 2: key name implies dynamic value
63
+ if (isDynamicKeyName(key) && value != null) {
64
+ detected.push(path);
65
+ continue;
66
+ }
67
+ if (typeof value === 'number') {
68
+ // Strategy 3: epoch timestamp detection
69
+ if (isTimestampOrPattern(value)) {
70
+ detected.push(path);
71
+ }
72
+ else {
73
+ // Original: numeric values are often IDs
74
+ detected.push(path);
75
+ }
76
+ }
77
+ else if (typeof value === 'string') {
78
+ // Strategy 3: timestamp/pattern detection (checked first, catches more)
79
+ if (isTimestampOrPattern(value)) {
80
+ detected.push(path);
81
+ }
82
+ else if (isLikelyDynamic(value)) {
83
+ detected.push(path);
84
+ }
85
+ }
86
+ else if (value && typeof value === 'object' && !Array.isArray(value)) {
87
+ // Recurse into nested objects
88
+ detected.push(...detectBodyVariables(value, path));
89
+ }
90
+ }
91
+ return detected;
92
+ }
93
+ function isLikelyDynamic(value) {
94
+ // UUID
95
+ if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value)) {
96
+ return true;
97
+ }
98
+ // Base64-ish cursor (long alphanumeric with optional padding)
99
+ if (value.length > 15 && /^[a-zA-Z0-9+/=_-]+$/.test(value)) {
100
+ return true;
101
+ }
102
+ // Numeric string (ID)
103
+ if (/^\d{4,}$/.test(value)) {
104
+ return true;
105
+ }
106
+ return false;
107
+ }
108
+ /**
109
+ * Substitute variables in a body template.
110
+ */
111
+ export function substituteBodyVariables(template, values) {
112
+ if (typeof template === 'string') {
113
+ // String template with :param placeholders
114
+ return template.replace(/:([a-zA-Z_][a-zA-Z0-9_]*)/g, (match, name) => {
115
+ return values[name] ?? match;
116
+ });
117
+ }
118
+ // Deep clone and substitute
119
+ const result = JSON.parse(JSON.stringify(template));
120
+ for (const [path, value] of Object.entries(values)) {
121
+ setNestedValue(result, path, value);
122
+ }
123
+ return result;
124
+ }
125
+ function setNestedValue(obj, path, value) {
126
+ const parts = path.split('.');
127
+ let current = obj;
128
+ for (let i = 0; i < parts.length - 1; i++) {
129
+ const part = parts[i];
130
+ if (current[part] && typeof current[part] === 'object') {
131
+ current = current[part];
132
+ }
133
+ else {
134
+ return; // Path doesn't exist
135
+ }
136
+ }
137
+ const lastPart = parts[parts.length - 1];
138
+ if (lastPart in current) {
139
+ current[lastPart] = value;
140
+ }
141
+ }
142
+ //# sourceMappingURL=body-variables.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"body-variables.js","sourceRoot":"","sources":["../../src/capture/body-variables.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Check if a hostname matches the target domain.
3
+ * Uses dot-prefix matching to prevent evil-example.com matching example.com.
4
+ *
5
+ * @param hostname - The hostname to check (e.g. "api.example.com")
6
+ * @param target - The target domain or URL (e.g. "example.com" or "https://example.com/path")
7
+ */
8
+ export declare function isDomainMatch(hostname: string, target: string): boolean;
@@ -0,0 +1,34 @@
1
+ // src/capture/domain.ts
2
+ /**
3
+ * Check if a hostname matches the target domain.
4
+ * Uses dot-prefix matching to prevent evil-example.com matching example.com.
5
+ *
6
+ * @param hostname - The hostname to check (e.g. "api.example.com")
7
+ * @param target - The target domain or URL (e.g. "example.com" or "https://example.com/path")
8
+ */
9
+ export function isDomainMatch(hostname, target) {
10
+ // Extract hostname from URL if target looks like a URL
11
+ let targetHost;
12
+ try {
13
+ if (target.includes('://')) {
14
+ targetHost = new URL(target).hostname;
15
+ }
16
+ else {
17
+ targetHost = target;
18
+ }
19
+ }
20
+ catch {
21
+ targetHost = target;
22
+ }
23
+ // Strip www. prefix from target for broader matching
24
+ if (targetHost.startsWith('www.')) {
25
+ targetHost = targetHost.slice(4);
26
+ }
27
+ // Exact match
28
+ if (hostname === targetHost)
29
+ return true;
30
+ // Dot-prefix suffix match: hostname must end with ".targetHost"
31
+ // This prevents evil-example.com from matching example.com
32
+ return hostname.endsWith('.' + targetHost);
33
+ }
34
+ //# sourceMappingURL=domain.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"domain.js","sourceRoot":"","sources":["../../src/capture/domain.ts"],"names":[],"mappings":"AAAA,wBAAwB;AAExB;;;;;;GAMG;AACH,MAAM,UAAU,aAAa,CAAC,QAAgB,EAAE,MAAc;IAC5D,uDAAuD;IACvD,IAAI,UAAkB,CAAC;IACvB,IAAI,CAAC;QACH,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;YAC3B,UAAU,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC;QACxC,CAAC;aAAM,CAAC;YACN,UAAU,GAAG,MAAM,CAAC;QACtB,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,UAAU,GAAG,MAAM,CAAC;IACtB,CAAC;IAED,qDAAqD;IACrD,IAAI,UAAU,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;QAClC,UAAU,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACnC,CAAC;IAED,cAAc;IACd,IAAI,QAAQ,KAAK,UAAU;QAAE,OAAO,IAAI,CAAC;IAEzC,gEAAgE;IAChE,2DAA2D;IAC3D,OAAO,QAAQ,CAAC,QAAQ,CAAC,GAAG,GAAG,UAAU,CAAC,CAAC;AAC7C,CAAC"}
@@ -0,0 +1,33 @@
1
+ export interface TokenClassification {
2
+ isToken: boolean;
3
+ confidence: 'high' | 'medium';
4
+ format: 'jwt' | 'opaque';
5
+ jwtClaims?: JwtClaims;
6
+ }
7
+ export interface JwtClaims {
8
+ exp?: number;
9
+ iat?: number;
10
+ iss?: string;
11
+ aud?: string;
12
+ scope?: string;
13
+ }
14
+ /**
15
+ * Calculate Shannon entropy (bits per character) of a string.
16
+ * Higher values indicate more randomness.
17
+ */
18
+ export declare function shannonEntropy(value: string): number;
19
+ /**
20
+ * Parse JWT claims from a token string.
21
+ * Returns null if not a valid JWT structure.
22
+ */
23
+ export declare function parseJwtClaims(token: string): JwtClaims | null;
24
+ /**
25
+ * Classify whether a header/cookie value is likely an auth token.
26
+ *
27
+ * Detection hierarchy:
28
+ * 1. JWT (eyJ prefix, 2 dots) → decode and classify with rich metadata
29
+ * 2. UUID → skip (entity ID, not token)
30
+ * 3. Short values (<16 chars) → skip
31
+ * 4. High-entropy opaque string → classify by entropy threshold
32
+ */
33
+ export declare function isLikelyToken(name: string, value: string): TokenClassification;
@@ -0,0 +1,100 @@
1
+ // src/capture/entropy.ts
2
+ const MIN_TOKEN_LENGTH = 16;
3
+ const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
4
+ /**
5
+ * Calculate Shannon entropy (bits per character) of a string.
6
+ * Higher values indicate more randomness.
7
+ */
8
+ export function shannonEntropy(value) {
9
+ if (value.length === 0)
10
+ return 0;
11
+ const freq = new Map();
12
+ for (const ch of value) {
13
+ freq.set(ch, (freq.get(ch) ?? 0) + 1);
14
+ }
15
+ let entropy = 0;
16
+ const len = value.length;
17
+ for (const count of freq.values()) {
18
+ const p = count / len;
19
+ entropy -= p * Math.log2(p);
20
+ }
21
+ return entropy;
22
+ }
23
+ /**
24
+ * Parse JWT claims from a token string.
25
+ * Returns null if not a valid JWT structure.
26
+ */
27
+ export function parseJwtClaims(token) {
28
+ // JWT: starts with eyJ, has exactly 2 dots
29
+ if (!token.startsWith('eyJ'))
30
+ return null;
31
+ const parts = token.split('.');
32
+ if (parts.length !== 3)
33
+ return null;
34
+ try {
35
+ // Decode payload (second part), base64url → JSON
36
+ const payload = parts[1];
37
+ const padded = payload.replace(/-/g, '+').replace(/_/g, '/');
38
+ const json = Buffer.from(padded, 'base64').toString('utf-8');
39
+ const claims = JSON.parse(json);
40
+ if (typeof claims !== 'object' || claims === null)
41
+ return null;
42
+ const result = {};
43
+ if (typeof claims.exp === 'number')
44
+ result.exp = claims.exp;
45
+ if (typeof claims.iat === 'number')
46
+ result.iat = claims.iat;
47
+ if (typeof claims.iss === 'string')
48
+ result.iss = claims.iss;
49
+ if (typeof claims.aud === 'string')
50
+ result.aud = claims.aud;
51
+ if (typeof claims.scope === 'string')
52
+ result.scope = claims.scope;
53
+ return result;
54
+ }
55
+ catch {
56
+ return null;
57
+ }
58
+ }
59
+ /**
60
+ * Classify whether a header/cookie value is likely an auth token.
61
+ *
62
+ * Detection hierarchy:
63
+ * 1. JWT (eyJ prefix, 2 dots) → decode and classify with rich metadata
64
+ * 2. UUID → skip (entity ID, not token)
65
+ * 3. Short values (<16 chars) → skip
66
+ * 4. High-entropy opaque string → classify by entropy threshold
67
+ */
68
+ export function isLikelyToken(name, value) {
69
+ // Strip "Bearer " prefix for analysis
70
+ const raw = value.startsWith('Bearer ') ? value.slice(7) : value;
71
+ // JWT detection — takes priority
72
+ const jwtClaims = parseJwtClaims(raw);
73
+ if (jwtClaims) {
74
+ return {
75
+ isToken: true,
76
+ confidence: 'high',
77
+ format: 'jwt',
78
+ jwtClaims,
79
+ };
80
+ }
81
+ // UUID exclusion — almost always entity IDs, not tokens
82
+ if (UUID_PATTERN.test(raw)) {
83
+ return { isToken: false, confidence: 'medium', format: 'opaque' };
84
+ }
85
+ // Minimum length gate
86
+ if (raw.length < MIN_TOKEN_LENGTH) {
87
+ return { isToken: false, confidence: 'medium', format: 'opaque' };
88
+ }
89
+ // Entropy-based classification
90
+ const entropy = shannonEntropy(raw);
91
+ if (entropy >= 4.5) {
92
+ return { isToken: true, confidence: 'high', format: 'opaque' };
93
+ }
94
+ if (entropy >= 3.5) {
95
+ return { isToken: true, confidence: 'medium', format: 'opaque' };
96
+ }
97
+ // Below threshold — not a token
98
+ return { isToken: false, confidence: 'medium', format: 'opaque' };
99
+ }
100
+ //# sourceMappingURL=entropy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entropy.js","sourceRoot":"","sources":["../../src/capture/entropy.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ export interface FilterableResponse {
2
+ url: string;
3
+ status: number;
4
+ contentType: string;
5
+ }
6
+ /**
7
+ * Check if a URL path is framework or telemetry noise.
8
+ * Exported for testing.
9
+ */
10
+ export declare function isPathNoise(pathname: string): boolean;
11
+ export declare function shouldCapture(response: FilterableResponse): boolean;
@@ -0,0 +1,49 @@
1
+ // src/capture/filter.ts
2
+ import { isBlocklisted } from './blocklist.js';
3
+ const JSON_CONTENT_TYPES = [
4
+ 'application/json',
5
+ 'application/vnd.api+json',
6
+ 'text/json',
7
+ ];
8
+ /** Exact path matches that are telemetry/framework noise */
9
+ const NOISE_PATHS = new Set([
10
+ '/monitoring',
11
+ '/telemetry',
12
+ '/track',
13
+ '/manifest.json',
14
+ ]);
15
+ /**
16
+ * Check if a URL path is framework or telemetry noise.
17
+ * Exported for testing.
18
+ */
19
+ export function isPathNoise(pathname) {
20
+ // Exact match noise paths
21
+ if (NOISE_PATHS.has(pathname))
22
+ return true;
23
+ // Next.js static build assets (not data routes)
24
+ if (pathname.startsWith('/_next/static/'))
25
+ return true;
26
+ return false;
27
+ }
28
+ export function shouldCapture(response) {
29
+ // Only keep 2xx success responses
30
+ if (response.status < 200 || response.status >= 300)
31
+ return false;
32
+ // Content-type must indicate JSON
33
+ const ct = response.contentType.toLowerCase().split(';')[0].trim();
34
+ if (!JSON_CONTENT_TYPES.some(t => ct === t))
35
+ return false;
36
+ // Check domain and path
37
+ try {
38
+ const url = new URL(response.url);
39
+ if (isBlocklisted(url.hostname))
40
+ return false;
41
+ if (isPathNoise(url.pathname))
42
+ return false;
43
+ }
44
+ catch {
45
+ return false;
46
+ }
47
+ return true;
48
+ }
49
+ //# sourceMappingURL=filter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filter.js","sourceRoot":"","sources":["../../src/capture/filter.ts"],"names":[],"mappings":"AAAA,wBAAwB;AACxB,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAQ/C,MAAM,kBAAkB,GAAG;IACzB,kBAAkB;IAClB,0BAA0B;IAC1B,WAAW;CACZ,CAAC;AAEF,4DAA4D;AAC5D,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC;IAC1B,aAAa;IACb,YAAY;IACZ,QAAQ;IACR,gBAAgB;CACjB,CAAC,CAAC;AAEH;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,QAAgB;IAC1C,0BAA0B;IAC1B,IAAI,WAAW,CAAC,GAAG,CAAC,QAAQ,CAAC;QAAE,OAAO,IAAI,CAAC;IAE3C,gDAAgD;IAChD,IAAI,QAAQ,CAAC,UAAU,CAAC,gBAAgB,CAAC;QAAE,OAAO,IAAI,CAAC;IAEvD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,QAA4B;IACxD,kCAAkC;IAClC,IAAI,QAAQ,CAAC,MAAM,GAAG,GAAG,IAAI,QAAQ,CAAC,MAAM,IAAI,GAAG;QAAE,OAAO,KAAK,CAAC;IAElE,kCAAkC;IAClC,MAAM,EAAE,GAAG,QAAQ,CAAC,WAAW,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IACnE,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,KAAK,CAAC,CAAC;QAAE,OAAO,KAAK,CAAC;IAE1D,wBAAwB;IACxB,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;QAClC,IAAI,aAAa,CAAC,GAAG,CAAC,QAAQ,CAAC;YAAE,OAAO,KAAK,CAAC;QAC9C,IAAI,WAAW,CAAC,GAAG,CAAC,QAAQ,CAAC;YAAE,OAAO,KAAK,CAAC;IAC9C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1,21 @@
1
+ export interface GraphQLParsed {
2
+ operationName: string | null;
3
+ query: string;
4
+ variables: Record<string, unknown> | null;
5
+ }
6
+ /**
7
+ * Detect if a request is to a GraphQL endpoint.
8
+ */
9
+ export declare function isGraphQLEndpoint(path: string, contentType: string, body: string | null): boolean;
10
+ /**
11
+ * Parse a GraphQL request body.
12
+ */
13
+ export declare function parseGraphQLBody(body: string): GraphQLParsed | null;
14
+ /**
15
+ * Extract operation name from query string or explicit operationName.
16
+ */
17
+ export declare function extractOperationName(query: string, explicitName: string | null): string;
18
+ /**
19
+ * Detect which variables are likely dynamic (IDs, cursors, pagination).
20
+ */
21
+ export declare function detectGraphQLVariables(variables: Record<string, unknown> | null, prefix?: string): string[];
@@ -0,0 +1,99 @@
1
+ // src/capture/graphql.ts
2
+ /**
3
+ * Detect if a request is to a GraphQL endpoint.
4
+ */
5
+ export function isGraphQLEndpoint(path, contentType, body) {
6
+ // Path contains /graphql
7
+ if (path.includes('/graphql')) {
8
+ return true;
9
+ }
10
+ // Content-Type is application/graphql
11
+ if (contentType.includes('application/graphql')) {
12
+ return true;
13
+ }
14
+ // Body contains a "query" field (GraphQL-style)
15
+ if (body) {
16
+ try {
17
+ const parsed = JSON.parse(body);
18
+ if (typeof parsed.query === 'string') {
19
+ return true;
20
+ }
21
+ }
22
+ catch {
23
+ // Not JSON
24
+ }
25
+ }
26
+ return false;
27
+ }
28
+ /**
29
+ * Parse a GraphQL request body.
30
+ */
31
+ export function parseGraphQLBody(body) {
32
+ try {
33
+ const parsed = JSON.parse(body);
34
+ if (typeof parsed.query !== 'string') {
35
+ return null;
36
+ }
37
+ return {
38
+ operationName: parsed.operationName ?? null,
39
+ query: parsed.query,
40
+ variables: parsed.variables ?? null,
41
+ };
42
+ }
43
+ catch {
44
+ return null;
45
+ }
46
+ }
47
+ /**
48
+ * Extract operation name from query string or explicit operationName.
49
+ */
50
+ export function extractOperationName(query, explicitName) {
51
+ if (explicitName) {
52
+ return explicitName;
53
+ }
54
+ // Match "query Name" or "mutation Name" at start
55
+ const match = query.match(/^\s*(query|mutation|subscription)\s+(\w+)/);
56
+ if (match) {
57
+ return match[2];
58
+ }
59
+ return 'Anonymous';
60
+ }
61
+ /**
62
+ * Detect which variables are likely dynamic (IDs, cursors, pagination).
63
+ */
64
+ export function detectGraphQLVariables(variables, prefix = '') {
65
+ if (!variables || typeof variables !== 'object') {
66
+ return [];
67
+ }
68
+ const detected = [];
69
+ for (const [key, value] of Object.entries(variables)) {
70
+ const path = prefix ? `${prefix}.${key}` : key;
71
+ if (typeof value === 'number') {
72
+ // Numbers are often IDs or pagination values
73
+ detected.push(path);
74
+ }
75
+ else if (typeof value === 'string') {
76
+ // Cursor-like strings (base64, long alphanumeric)
77
+ if (isLikelyCursor(value)) {
78
+ detected.push(path);
79
+ }
80
+ }
81
+ else if (value && typeof value === 'object' && !Array.isArray(value)) {
82
+ // Recurse into nested objects
83
+ detected.push(...detectGraphQLVariables(value, path));
84
+ }
85
+ }
86
+ return detected;
87
+ }
88
+ function isLikelyCursor(value) {
89
+ // Base64-ish: long alphanumeric, possibly with = padding
90
+ if (value.length > 10 && /^[a-zA-Z0-9+/=_-]+$/.test(value)) {
91
+ return true;
92
+ }
93
+ // UUID-like
94
+ if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value)) {
95
+ return true;
96
+ }
97
+ return false;
98
+ }
99
+ //# sourceMappingURL=graphql.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graphql.js","sourceRoot":"","sources":["../../src/capture/graphql.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Tracks unique endpoint discoveries and detects idle periods.
3
+ * Used during interactive capture to nudge the user when no new
4
+ * endpoints have been found for a while.
5
+ */
6
+ export declare class IdleTracker {
7
+ private seen;
8
+ private lastNewTime;
9
+ private thresholdMs;
10
+ private fired;
11
+ private now;
12
+ constructor(thresholdMs?: number, now?: () => number);
13
+ /**
14
+ * Record an endpoint key (e.g. "GET /api/items").
15
+ * Returns true if it's genuinely new (not seen before).
16
+ */
17
+ recordEndpoint(key: string): boolean;
18
+ /**
19
+ * Check if the idle threshold has been exceeded.
20
+ * Returns true exactly once per idle period (until reset by a new endpoint).
21
+ */
22
+ checkIdle(): boolean;
23
+ }
@@ -0,0 +1,44 @@
1
+ // src/capture/idle.ts
2
+ /**
3
+ * Tracks unique endpoint discoveries and detects idle periods.
4
+ * Used during interactive capture to nudge the user when no new
5
+ * endpoints have been found for a while.
6
+ */
7
+ export class IdleTracker {
8
+ seen = new Set();
9
+ lastNewTime;
10
+ thresholdMs;
11
+ fired = false;
12
+ now;
13
+ constructor(thresholdMs = 15000, now = Date.now) {
14
+ this.thresholdMs = thresholdMs;
15
+ this.now = now;
16
+ this.lastNewTime = this.now();
17
+ }
18
+ /**
19
+ * Record an endpoint key (e.g. "GET /api/items").
20
+ * Returns true if it's genuinely new (not seen before).
21
+ */
22
+ recordEndpoint(key) {
23
+ if (this.seen.has(key))
24
+ return false;
25
+ this.seen.add(key);
26
+ this.lastNewTime = this.now();
27
+ this.fired = false;
28
+ return true;
29
+ }
30
+ /**
31
+ * Check if the idle threshold has been exceeded.
32
+ * Returns true exactly once per idle period (until reset by a new endpoint).
33
+ */
34
+ checkIdle() {
35
+ if (this.fired)
36
+ return false;
37
+ if (this.now() - this.lastNewTime >= this.thresholdMs) {
38
+ this.fired = true;
39
+ return true;
40
+ }
41
+ return false;
42
+ }
43
+ }
44
+ //# sourceMappingURL=idle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"idle.js","sourceRoot":"","sources":["../../src/capture/idle.ts"],"names":[],"mappings":"AAAA,sBAAsB;AAEtB;;;;GAIG;AACH,MAAM,OAAO,WAAW;IACd,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IACzB,WAAW,CAAS;IACpB,WAAW,CAAS;IACpB,KAAK,GAAG,KAAK,CAAC;IACd,GAAG,CAAe;IAE1B,YAAY,WAAW,GAAG,KAAK,EAAE,MAAoB,IAAI,CAAC,GAAG;QAC3D,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;QAC/B,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;QACf,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAChC,CAAC;IAED;;;OAGG;IACH,cAAc,CAAC,GAAW;QACxB,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,OAAO,KAAK,CAAC;QACrC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACnB,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAC9B,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;;OAGG;IACH,SAAS;QACP,IAAI,IAAI,CAAC,KAAK;YAAE,OAAO,KAAK,CAAC;QAC7B,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,WAAW,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;YACtD,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;YAClB,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;CACF"}
@@ -0,0 +1,26 @@
1
+ import { SkillGenerator } from '../skill/generator.js';
2
+ export interface CaptureOptions {
3
+ url: string;
4
+ port?: number;
5
+ launch?: boolean;
6
+ attach?: boolean;
7
+ headless?: boolean;
8
+ duration?: number;
9
+ allDomains?: boolean;
10
+ enablePreview?: boolean;
11
+ scrub?: boolean;
12
+ onEndpoint?: (endpoint: {
13
+ id: string;
14
+ method: string;
15
+ path: string;
16
+ }) => void;
17
+ onFiltered?: () => void;
18
+ onIdle?: () => void;
19
+ }
20
+ export interface CaptureResult {
21
+ generators: Map<string, SkillGenerator>;
22
+ totalRequests: number;
23
+ filteredRequests: number;
24
+ domBytes?: number;
25
+ }
26
+ export declare function capture(options: CaptureOptions): Promise<CaptureResult>;