@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,31 @@
1
+ export interface EncryptedData {
2
+ salt: string;
3
+ iv: string;
4
+ ciphertext: string;
5
+ tag: string;
6
+ }
7
+ /**
8
+ * Derive a 256-bit key from a machine identifier using PBKDF2.
9
+ * Uses a fixed application salt — the entropy comes from the machine ID
10
+ * being stretched through 100K iterations.
11
+ */
12
+ export declare function deriveKey(machineId: string): Buffer;
13
+ /**
14
+ * Encrypt plaintext using AES-256-GCM.
15
+ * Each call generates a random IV for semantic security.
16
+ */
17
+ export declare function encrypt(plaintext: string, key: Buffer): EncryptedData;
18
+ /**
19
+ * Decrypt ciphertext using AES-256-GCM.
20
+ * Throws if key is wrong or data was tampered with.
21
+ */
22
+ export declare function decrypt(data: EncryptedData, key: Buffer): string;
23
+ /**
24
+ * Create an HMAC-SHA256 signature.
25
+ * Returns a prefixed string: "hmac-sha256:<hex>"
26
+ */
27
+ export declare function hmacSign(data: string, key: Buffer): string;
28
+ /**
29
+ * Verify an HMAC-SHA256 signature using timing-safe comparison.
30
+ */
31
+ export declare function hmacVerify(data: string, signature: string, key: Buffer): boolean;
@@ -0,0 +1,66 @@
1
+ // src/auth/crypto.ts
2
+ import { createCipheriv, createDecipheriv, randomBytes, pbkdf2Sync, createHmac, timingSafeEqual, } from 'node:crypto';
3
+ const ALGORITHM = 'aes-256-gcm';
4
+ const KEY_LENGTH = 32; // 256 bits
5
+ const IV_LENGTH = 16;
6
+ const PBKDF2_ITERATIONS = 100_000;
7
+ const PBKDF2_SALT = 'apitap-v0.2-key-derivation';
8
+ /**
9
+ * Derive a 256-bit key from a machine identifier using PBKDF2.
10
+ * Uses a fixed application salt — the entropy comes from the machine ID
11
+ * being stretched through 100K iterations.
12
+ */
13
+ export function deriveKey(machineId) {
14
+ return pbkdf2Sync(machineId, PBKDF2_SALT, PBKDF2_ITERATIONS, KEY_LENGTH, 'sha512');
15
+ }
16
+ /**
17
+ * Encrypt plaintext using AES-256-GCM.
18
+ * Each call generates a random IV for semantic security.
19
+ */
20
+ export function encrypt(plaintext, key) {
21
+ const iv = randomBytes(IV_LENGTH);
22
+ const cipher = createCipheriv(ALGORITHM, key, iv);
23
+ let ciphertext = cipher.update(plaintext, 'utf8', 'hex');
24
+ ciphertext += cipher.final('hex');
25
+ const tag = cipher.getAuthTag();
26
+ return {
27
+ salt: PBKDF2_SALT,
28
+ iv: iv.toString('hex'),
29
+ ciphertext,
30
+ tag: tag.toString('hex'),
31
+ };
32
+ }
33
+ /**
34
+ * Decrypt ciphertext using AES-256-GCM.
35
+ * Throws if key is wrong or data was tampered with.
36
+ */
37
+ export function decrypt(data, key) {
38
+ const decipher = createDecipheriv(ALGORITHM, key, Buffer.from(data.iv, 'hex'));
39
+ decipher.setAuthTag(Buffer.from(data.tag, 'hex'));
40
+ let plaintext = decipher.update(data.ciphertext, 'hex', 'utf8');
41
+ plaintext += decipher.final('utf8');
42
+ return plaintext;
43
+ }
44
+ /**
45
+ * Create an HMAC-SHA256 signature.
46
+ * Returns a prefixed string: "hmac-sha256:<hex>"
47
+ */
48
+ export function hmacSign(data, key) {
49
+ const hmac = createHmac('sha256', key);
50
+ hmac.update(data);
51
+ return `hmac-sha256:${hmac.digest('hex')}`;
52
+ }
53
+ /**
54
+ * Verify an HMAC-SHA256 signature using timing-safe comparison.
55
+ */
56
+ export function hmacVerify(data, signature, key) {
57
+ if (!signature.startsWith('hmac-sha256:'))
58
+ return false;
59
+ const expected = hmacSign(data, key);
60
+ const sigBuf = Buffer.from(signature);
61
+ const expBuf = Buffer.from(expected);
62
+ if (sigBuf.length !== expBuf.length)
63
+ return false;
64
+ return timingSafeEqual(sigBuf, expBuf);
65
+ }
66
+ //# sourceMappingURL=crypto.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crypto.js","sourceRoot":"","sources":["../../src/auth/crypto.ts"],"names":[],"mappings":"AAAA,qBAAqB;AACrB,OAAO,EACL,cAAc,EACd,gBAAgB,EAChB,WAAW,EACX,UAAU,EACV,UAAU,EACV,eAAe,GAChB,MAAM,aAAa,CAAC;AAErB,MAAM,SAAS,GAAG,aAAa,CAAC;AAChC,MAAM,UAAU,GAAG,EAAE,CAAC,CAAC,WAAW;AAClC,MAAM,SAAS,GAAG,EAAE,CAAC;AACrB,MAAM,iBAAiB,GAAG,OAAO,CAAC;AAClC,MAAM,WAAW,GAAG,4BAA4B,CAAC;AASjD;;;;GAIG;AACH,MAAM,UAAU,SAAS,CAAC,SAAiB;IACzC,OAAO,UAAU,CAAC,SAAS,EAAE,WAAW,EAAE,iBAAiB,EAAE,UAAU,EAAE,QAAQ,CAAC,CAAC;AACrF,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,OAAO,CAAC,SAAiB,EAAE,GAAW;IACpD,MAAM,EAAE,GAAG,WAAW,CAAC,SAAS,CAAC,CAAC;IAClC,MAAM,MAAM,GAAG,cAAc,CAAC,SAAS,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC;IAElD,IAAI,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC;IACzD,UAAU,IAAI,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAClC,MAAM,GAAG,GAAG,MAAM,CAAC,UAAU,EAAE,CAAC;IAEhC,OAAO;QACL,IAAI,EAAE,WAAW;QACjB,EAAE,EAAE,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC;QACtB,UAAU;QACV,GAAG,EAAE,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC;KACzB,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,OAAO,CAAC,IAAmB,EAAE,GAAW;IACtD,MAAM,QAAQ,GAAG,gBAAgB,CAC/B,SAAS,EACT,GAAG,EACH,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,KAAK,CAAC,CAC5B,CAAC;IACF,QAAQ,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC;IAElD,IAAI,SAAS,GAAG,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;IAChE,SAAS,IAAI,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IACpC,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,QAAQ,CAAC,IAAY,EAAE,GAAW;IAChD,MAAM,IAAI,GAAG,UAAU,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;IACvC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAClB,OAAO,eAAe,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;AAC7C,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,UAAU,CAAC,IAAY,EAAE,SAAiB,EAAE,GAAW;IACrE,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,cAAc,CAAC;QAAE,OAAO,KAAK,CAAC;IAExD,MAAM,QAAQ,GAAG,QAAQ,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IACrC,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IACtC,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAErC,IAAI,MAAM,CAAC,MAAM,KAAK,MAAM,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IAClD,OAAO,eAAe,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AACzC,CAAC"}
@@ -0,0 +1,29 @@
1
+ import type { AuthManager } from './manager.js';
2
+ export interface HandoffOptions {
3
+ domain: string;
4
+ loginUrl?: string;
5
+ timeout?: number;
6
+ }
7
+ export interface HandoffResult {
8
+ success: boolean;
9
+ cookieCount: number;
10
+ authDetected?: 'bearer' | 'cookie' | 'api-key';
11
+ error?: string;
12
+ }
13
+ /**
14
+ * Detect whether a response indicates successful login.
15
+ * Checks for session-like Set-Cookie headers on 2xx responses.
16
+ */
17
+ export declare function detectLoginSuccess(headers: Map<string, string>, status: number): boolean;
18
+ /**
19
+ * Open a visible browser for human authentication.
20
+ *
21
+ * Flow:
22
+ * 1. Launch visible Chromium browser
23
+ * 2. Navigate to login URL
24
+ * 3. Wait for human to log in (watches for session cookies / auth headers)
25
+ * 4. Capture all cookies + detected auth
26
+ * 5. Store encrypted via AuthManager
27
+ * 6. Close browser, return result
28
+ */
29
+ export declare function requestAuth(authManager: AuthManager, options: HandoffOptions): Promise<HandoffResult>;
@@ -0,0 +1,180 @@
1
+ // Session-like cookie name patterns
2
+ const SESSION_COOKIE_PATTERNS = [
3
+ /sess/i, /auth/i, /token/i, /jwt/i, /login/i,
4
+ /sid$/i, /^_session/i, /^connect\.sid$/i,
5
+ ];
6
+ // Tracking/analytics cookie patterns (exclude from session detection)
7
+ const TRACKING_COOKIE_PATTERNS = [
8
+ /^_ga/i, /^_gid/i, /^_fb/i, /^_gcl/i, /^__utm/i,
9
+ ];
10
+ /**
11
+ * Detect whether a response indicates successful login.
12
+ * Checks for session-like Set-Cookie headers on 2xx responses.
13
+ */
14
+ export function detectLoginSuccess(headers, status) {
15
+ if (status < 200 || status >= 300)
16
+ return false;
17
+ // Check for auth header
18
+ const authHeader = headers.get('authorization');
19
+ if (authHeader && (authHeader.startsWith('Bearer ') || authHeader.startsWith('Basic '))) {
20
+ return true;
21
+ }
22
+ // Check for session-like cookies
23
+ const setCookie = headers.get('set-cookie');
24
+ if (!setCookie)
25
+ return false;
26
+ // Parse cookie name from Set-Cookie header
27
+ const cookieName = setCookie.split('=')[0].trim();
28
+ // Exclude tracking cookies
29
+ if (TRACKING_COOKIE_PATTERNS.some(p => p.test(cookieName)))
30
+ return false;
31
+ // Match session-like cookies
32
+ return SESSION_COOKIE_PATTERNS.some(p => p.test(cookieName));
33
+ }
34
+ // Mutex to prevent concurrent handoffs for the same domain
35
+ const handoffLocks = new Map();
36
+ /**
37
+ * Open a visible browser for human authentication.
38
+ *
39
+ * Flow:
40
+ * 1. Launch visible Chromium browser
41
+ * 2. Navigate to login URL
42
+ * 3. Wait for human to log in (watches for session cookies / auth headers)
43
+ * 4. Capture all cookies + detected auth
44
+ * 5. Store encrypted via AuthManager
45
+ * 6. Close browser, return result
46
+ */
47
+ export async function requestAuth(authManager, options) {
48
+ const { domain } = options;
49
+ // Mutex: prevent concurrent handoffs for same domain
50
+ const existing = handoffLocks.get(domain);
51
+ if (existing)
52
+ return existing;
53
+ const promise = doHandoff(authManager, options);
54
+ handoffLocks.set(domain, promise);
55
+ try {
56
+ return await promise;
57
+ }
58
+ finally {
59
+ handoffLocks.delete(domain);
60
+ }
61
+ }
62
+ async function doHandoff(authManager, options) {
63
+ const { domain } = options;
64
+ const loginUrl = options.loginUrl || `https://${domain}`;
65
+ const timeout = options.timeout ?? 300_000; // 5 minutes
66
+ const { chromium } = await import('playwright');
67
+ const browser = await chromium.launch({ headless: false });
68
+ try {
69
+ const context = await browser.newContext();
70
+ // Restore existing session cookies if available (warm start)
71
+ const cachedSession = await authManager.retrieveSession(domain);
72
+ if (cachedSession?.cookies?.length) {
73
+ await context.addCookies(cachedSession.cookies);
74
+ }
75
+ const page = await context.newPage();
76
+ let authDetected;
77
+ let detectedAuth;
78
+ // Watch network responses for auth signals
79
+ page.on('response', (response) => {
80
+ const headers = new Map();
81
+ for (const [key, value] of Object.entries(response.headers())) {
82
+ headers.set(key, value);
83
+ }
84
+ // Detect auth from request headers
85
+ const authHeader = response.request().headers()['authorization'];
86
+ if (authHeader) {
87
+ if (authHeader.startsWith('Bearer ')) {
88
+ authDetected = 'bearer';
89
+ detectedAuth = {
90
+ type: 'bearer',
91
+ header: 'authorization',
92
+ value: authHeader,
93
+ };
94
+ }
95
+ else if (authHeader.toLowerCase().startsWith('apikey ') || authHeader.toLowerCase().startsWith('api-key ')) {
96
+ authDetected = 'api-key';
97
+ detectedAuth = {
98
+ type: 'api-key',
99
+ header: 'authorization',
100
+ value: authHeader,
101
+ };
102
+ }
103
+ }
104
+ });
105
+ // Navigate to login page
106
+ await page.goto(loginUrl, { waitUntil: 'domcontentloaded', timeout: 30_000 });
107
+ // Poll for login success: check cookies periodically
108
+ const startTime = Date.now();
109
+ let loginDetected = false;
110
+ while (Date.now() - startTime < timeout) {
111
+ await page.waitForTimeout(2000);
112
+ // Check if we've detected session cookies
113
+ const cookies = await context.cookies();
114
+ const hasSessionCookie = cookies.some(c => SESSION_COOKIE_PATTERNS.some(p => p.test(c.name)) &&
115
+ !TRACKING_COOKIE_PATTERNS.some(p => p.test(c.name)));
116
+ if (hasSessionCookie || authDetected) {
117
+ // Wait a bit more for any final redirects/requests
118
+ await page.waitForTimeout(2000);
119
+ loginDetected = true;
120
+ break;
121
+ }
122
+ // Check if browser was closed by user
123
+ if (page.isClosed())
124
+ break;
125
+ }
126
+ // Capture final cookies
127
+ const cookies = await context.cookies();
128
+ if (cookies.length === 0 && !authDetected) {
129
+ return {
130
+ success: false,
131
+ cookieCount: 0,
132
+ error: loginDetected ? undefined : 'Timeout: no login detected within the allowed time',
133
+ };
134
+ }
135
+ // Store session cookies
136
+ const session = {
137
+ cookies,
138
+ savedAt: new Date().toISOString(),
139
+ maxAgeMs: 24 * 60 * 60 * 1000, // 24 hours
140
+ };
141
+ await authManager.storeSession(domain, session);
142
+ // Store detected auth header if found
143
+ if (detectedAuth) {
144
+ await authManager.store(domain, detectedAuth);
145
+ }
146
+ else if (cookies.length > 0) {
147
+ // Store as cookie auth
148
+ const sessionCookies = cookies
149
+ .filter(c => SESSION_COOKIE_PATTERNS.some(p => p.test(c.name)))
150
+ .filter(c => !TRACKING_COOKIE_PATTERNS.some(p => p.test(c.name)));
151
+ if (sessionCookies.length > 0) {
152
+ authDetected = 'cookie';
153
+ const cookieHeader = sessionCookies
154
+ .map(c => `${c.name}=${c.value}`)
155
+ .join('; ');
156
+ await authManager.store(domain, {
157
+ type: 'cookie',
158
+ header: 'cookie',
159
+ value: cookieHeader,
160
+ });
161
+ }
162
+ }
163
+ return {
164
+ success: true,
165
+ cookieCount: cookies.length,
166
+ authDetected,
167
+ };
168
+ }
169
+ catch (error) {
170
+ return {
171
+ success: false,
172
+ cookieCount: 0,
173
+ error: error instanceof Error ? error.message : String(error),
174
+ };
175
+ }
176
+ finally {
177
+ await browser.close();
178
+ }
179
+ }
180
+ //# sourceMappingURL=handoff.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handoff.js","sourceRoot":"","sources":["../../src/auth/handoff.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ import type { StoredAuth, StoredToken, StoredSession } from '../types.js';
2
+ /**
3
+ * Manages encrypted auth credential storage.
4
+ * All credentials stored in a single encrypted file keyed by domain.
5
+ */
6
+ export declare class AuthManager {
7
+ private key;
8
+ private authPath;
9
+ constructor(baseDir: string, machineId: string);
10
+ /** Store auth credentials for a domain (overwrites existing). */
11
+ store(domain: string, auth: StoredAuth): Promise<void>;
12
+ /** Retrieve auth credentials for a domain. Returns null if not found or decryption fails. */
13
+ retrieve(domain: string): Promise<StoredAuth | null>;
14
+ /** Check if auth exists for a domain without loading the value. */
15
+ has(domain: string): Promise<boolean>;
16
+ /** Store refreshable tokens for a domain (merges with existing auth). */
17
+ storeTokens(domain: string, tokens: Record<string, StoredToken>): Promise<void>;
18
+ /** Retrieve refreshable tokens for a domain. */
19
+ retrieveTokens(domain: string): Promise<Record<string, StoredToken> | null>;
20
+ /** Store browser session (cookies) for a domain (merges with existing auth). */
21
+ storeSession(domain: string, session: StoredSession): Promise<void>;
22
+ /** Retrieve browser session for a domain. */
23
+ retrieveSession(domain: string): Promise<StoredSession | null>;
24
+ /** Store OAuth credentials for a domain (merges with existing auth). */
25
+ storeOAuthCredentials(domain: string, creds: {
26
+ refreshToken?: string;
27
+ clientSecret?: string;
28
+ }): Promise<void>;
29
+ /** Retrieve OAuth credentials for a domain. */
30
+ retrieveOAuthCredentials(domain: string): Promise<{
31
+ refreshToken?: string;
32
+ clientSecret?: string;
33
+ } | null>;
34
+ /** List all domains with stored auth. */
35
+ listDomains(): Promise<string[]>;
36
+ /** Clear all auth for a domain. */
37
+ clear(domain: string): Promise<void>;
38
+ private loadAll;
39
+ private saveAll;
40
+ }
41
+ /**
42
+ * Get the machine ID for key derivation.
43
+ * Linux: /etc/machine-id
44
+ * Fallback: hostname + homedir (less secure but portable)
45
+ */
46
+ export declare function getMachineId(): Promise<string>;
@@ -0,0 +1,127 @@
1
+ // src/auth/manager.ts
2
+ import { readFile, writeFile, mkdir, chmod } from 'node:fs/promises';
3
+ import { join } from 'node:path';
4
+ import { encrypt, decrypt, deriveKey } from './crypto.js';
5
+ const AUTH_FILENAME = 'auth.enc';
6
+ /**
7
+ * Manages encrypted auth credential storage.
8
+ * All credentials stored in a single encrypted file keyed by domain.
9
+ */
10
+ export class AuthManager {
11
+ key;
12
+ authPath;
13
+ constructor(baseDir, machineId) {
14
+ this.key = deriveKey(machineId);
15
+ this.authPath = join(baseDir, AUTH_FILENAME);
16
+ }
17
+ /** Store auth credentials for a domain (overwrites existing). */
18
+ async store(domain, auth) {
19
+ const allAuth = await this.loadAll();
20
+ allAuth[domain] = auth;
21
+ await this.saveAll(allAuth);
22
+ }
23
+ /** Retrieve auth credentials for a domain. Returns null if not found or decryption fails. */
24
+ async retrieve(domain) {
25
+ const allAuth = await this.loadAll();
26
+ return allAuth[domain] ?? null;
27
+ }
28
+ /** Check if auth exists for a domain without loading the value. */
29
+ async has(domain) {
30
+ const allAuth = await this.loadAll();
31
+ return domain in allAuth;
32
+ }
33
+ /** Store refreshable tokens for a domain (merges with existing auth). */
34
+ async storeTokens(domain, tokens) {
35
+ const all = await this.loadAll();
36
+ const existing = all[domain] || { type: 'custom', header: '', value: '' };
37
+ all[domain] = { ...existing, tokens };
38
+ await this.saveAll(all);
39
+ }
40
+ /** Retrieve refreshable tokens for a domain. */
41
+ async retrieveTokens(domain) {
42
+ const all = await this.loadAll();
43
+ return all[domain]?.tokens ?? null;
44
+ }
45
+ /** Store browser session (cookies) for a domain (merges with existing auth). */
46
+ async storeSession(domain, session) {
47
+ const all = await this.loadAll();
48
+ const existing = all[domain] || { type: 'custom', header: '', value: '' };
49
+ all[domain] = { ...existing, session };
50
+ await this.saveAll(all);
51
+ }
52
+ /** Retrieve browser session for a domain. */
53
+ async retrieveSession(domain) {
54
+ const all = await this.loadAll();
55
+ return all[domain]?.session ?? null;
56
+ }
57
+ /** Store OAuth credentials for a domain (merges with existing auth). */
58
+ async storeOAuthCredentials(domain, creds) {
59
+ const all = await this.loadAll();
60
+ const existing = all[domain] || { type: 'custom', header: '', value: '' };
61
+ if (creds.refreshToken !== undefined)
62
+ existing.refreshToken = creds.refreshToken;
63
+ if (creds.clientSecret !== undefined)
64
+ existing.clientSecret = creds.clientSecret;
65
+ all[domain] = existing;
66
+ await this.saveAll(all);
67
+ }
68
+ /** Retrieve OAuth credentials for a domain. */
69
+ async retrieveOAuthCredentials(domain) {
70
+ const all = await this.loadAll();
71
+ const auth = all[domain];
72
+ if (!auth)
73
+ return null;
74
+ if (!auth.refreshToken && !auth.clientSecret)
75
+ return null;
76
+ return { refreshToken: auth.refreshToken, clientSecret: auth.clientSecret };
77
+ }
78
+ /** List all domains with stored auth. */
79
+ async listDomains() {
80
+ const all = await this.loadAll();
81
+ return Object.keys(all);
82
+ }
83
+ /** Clear all auth for a domain. */
84
+ async clear(domain) {
85
+ const all = await this.loadAll();
86
+ delete all[domain];
87
+ await this.saveAll(all);
88
+ }
89
+ async loadAll() {
90
+ try {
91
+ const content = await readFile(this.authPath, 'utf-8');
92
+ const encrypted = JSON.parse(content);
93
+ const plaintext = decrypt(encrypted, this.key);
94
+ return JSON.parse(plaintext);
95
+ }
96
+ catch {
97
+ return {};
98
+ }
99
+ }
100
+ async saveAll(data) {
101
+ const dir = join(this.authPath, '..');
102
+ await mkdir(dir, { recursive: true });
103
+ const plaintext = JSON.stringify(data);
104
+ const encrypted = encrypt(plaintext, this.key);
105
+ await writeFile(this.authPath, JSON.stringify(encrypted, null, 2) + '\n', { mode: 0o600 });
106
+ // Ensure permissions even if file existed with different perms
107
+ await chmod(this.authPath, 0o600);
108
+ }
109
+ }
110
+ /**
111
+ * Get the machine ID for key derivation.
112
+ * Linux: /etc/machine-id
113
+ * Fallback: hostname + homedir (less secure but portable)
114
+ */
115
+ export async function getMachineId() {
116
+ try {
117
+ const id = await readFile('/etc/machine-id', 'utf-8');
118
+ return id.trim();
119
+ }
120
+ catch {
121
+ // Fallback for non-Linux systems
122
+ const { hostname } = await import('node:os');
123
+ const { homedir } = await import('node:os');
124
+ return `${hostname()}-${homedir()}`;
125
+ }
126
+ }
127
+ //# sourceMappingURL=manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manager.js","sourceRoot":"","sources":["../../src/auth/manager.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import type { OAuthConfig } from '../types.js';
2
+ import type { AuthManager } from './manager.js';
3
+ export interface OAuthRefreshResult {
4
+ success: boolean;
5
+ accessToken?: string;
6
+ tokenRotated?: boolean;
7
+ error?: string;
8
+ }
9
+ /**
10
+ * Refresh an OAuth2 access token via the token endpoint using stdlib fetch().
11
+ * Supports refresh_token and client_credentials grant types.
12
+ * Handles refresh token rotation (new refresh_token in response).
13
+ */
14
+ export declare function refreshOAuth(domain: string, oauthConfig: OAuthConfig, authManager: AuthManager, options?: {
15
+ _skipSsrfCheck?: boolean;
16
+ }): Promise<OAuthRefreshResult>;
@@ -0,0 +1,91 @@
1
+ import { resolveAndValidateUrl } from '../skill/ssrf.js';
2
+ /**
3
+ * Refresh an OAuth2 access token via the token endpoint using stdlib fetch().
4
+ * Supports refresh_token and client_credentials grant types.
5
+ * Handles refresh token rotation (new refresh_token in response).
6
+ */
7
+ export async function refreshOAuth(domain, oauthConfig, authManager, options) {
8
+ const oauthCreds = await authManager.retrieveOAuthCredentials(domain);
9
+ // Build request body based on grant type
10
+ const body = new URLSearchParams();
11
+ body.append('grant_type', oauthConfig.grantType);
12
+ body.append('client_id', oauthConfig.clientId);
13
+ if (oauthConfig.scope) {
14
+ body.append('scope', oauthConfig.scope);
15
+ }
16
+ if (oauthConfig.grantType === 'refresh_token') {
17
+ if (!oauthCreds?.refreshToken) {
18
+ return { success: false, error: 'No refresh token available' };
19
+ }
20
+ body.append('refresh_token', oauthCreds.refreshToken);
21
+ }
22
+ if (oauthCreds?.clientSecret) {
23
+ body.append('client_secret', oauthCreds.clientSecret);
24
+ }
25
+ // SSRF check on token endpoint
26
+ if (!options?._skipSsrfCheck) {
27
+ const ssrfCheck = await resolveAndValidateUrl(oauthConfig.tokenEndpoint);
28
+ if (!ssrfCheck.safe) {
29
+ return { success: false, error: `Token endpoint blocked: ${ssrfCheck.reason}` };
30
+ }
31
+ }
32
+ // Domain match: token endpoint must match skill domain or be a known OAuth provider
33
+ const KNOWN_OAUTH_HOSTS = [
34
+ 'oauth2.googleapis.com', 'accounts.google.com',
35
+ 'login.microsoftonline.com', 'github.com',
36
+ 'oauth.reddit.com', 'api.twitter.com',
37
+ ];
38
+ const tokenHost = new URL(oauthConfig.tokenEndpoint).hostname;
39
+ if (tokenHost !== domain && !tokenHost.endsWith('.' + domain) && !KNOWN_OAUTH_HOSTS.includes(tokenHost)) {
40
+ return { success: false, error: `Token endpoint domain mismatch: ${tokenHost} vs ${domain}` };
41
+ }
42
+ try {
43
+ const response = await fetch(oauthConfig.tokenEndpoint, {
44
+ method: 'POST',
45
+ headers: { 'content-type': 'application/x-www-form-urlencoded' },
46
+ body: body.toString(),
47
+ signal: AbortSignal.timeout(15_000), // 15s timeout for token refresh
48
+ });
49
+ if (!response.ok) {
50
+ const errorText = await response.text().catch(() => '');
51
+ return {
52
+ success: false,
53
+ error: `Token endpoint returned ${response.status}: ${errorText}`.trim(),
54
+ };
55
+ }
56
+ const data = await response.json();
57
+ const accessToken = data.access_token;
58
+ if (typeof accessToken !== 'string') {
59
+ return { success: false, error: 'No access_token in response' };
60
+ }
61
+ // Store new access token
62
+ const existingAuth = await authManager.retrieve(domain);
63
+ await authManager.store(domain, {
64
+ type: existingAuth?.type ?? 'bearer',
65
+ header: existingAuth?.header ?? 'authorization',
66
+ value: `Bearer ${accessToken}`,
67
+ tokens: existingAuth?.tokens,
68
+ session: existingAuth?.session,
69
+ refreshToken: existingAuth?.refreshToken,
70
+ clientSecret: existingAuth?.clientSecret,
71
+ });
72
+ // Handle refresh token rotation
73
+ let tokenRotated = false;
74
+ const newRefreshToken = data.refresh_token;
75
+ if (typeof newRefreshToken === 'string' &&
76
+ newRefreshToken !== oauthCreds?.refreshToken) {
77
+ await authManager.storeOAuthCredentials(domain, {
78
+ refreshToken: newRefreshToken,
79
+ });
80
+ tokenRotated = true;
81
+ }
82
+ return { success: true, accessToken, tokenRotated };
83
+ }
84
+ catch (error) {
85
+ return {
86
+ success: false,
87
+ error: error instanceof Error ? error.message : String(error),
88
+ };
89
+ }
90
+ }
91
+ //# sourceMappingURL=oauth-refresh.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth-refresh.js","sourceRoot":"","sources":["../../src/auth/oauth-refresh.ts"],"names":[],"mappings":"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"}