@apitap/core 1.0.5 → 1.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # ApiTap
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/@apitap/core)](https://www.npmjs.com/package/@apitap/core)
4
- [![tests](https://img.shields.io/badge/tests-721%20passing-brightgreen)](https://github.com/n1byn1kt/apitap)
4
+ [![tests](https://img.shields.io/badge/tests-789%20passing-brightgreen)](https://github.com/n1byn1kt/apitap)
5
5
  [![license](https://img.shields.io/badge/license-BSL--1.1-blue)](./LICENSE)
6
6
 
7
7
  **The MCP server that turns any website into an API — no docs, no SDK, no browser.**
@@ -28,6 +28,8 @@ apitap replay gamma-api.polymarket.com get-events # Call the API directly
28
28
 
29
29
  No scraping. No browser. Just the API.
30
30
 
31
+ ![ApiTap demo](https://raw.githubusercontent.com/n1byn1kt/apitap/main/docs/demo.gif)
32
+
31
33
  ---
32
34
 
33
35
  ## How It Works
@@ -332,7 +334,7 @@ Endpoint replayed
332
334
 
333
335
  This is especially relevant now that [MCP servers are being used as attack vectors in the wild](https://cloud.google.com/blog/topics/threat-intelligence/distillation-experimentation-integration-ai-adversarial-use) — Google's Threat Intelligence Group recently documented underground toolkits built on compromised MCP servers. ApiTap is designed to be safe even when processing untrusted inputs.
334
336
 
335
- See [docs/security-audit-v1.md](./docs/security-audit-v1.md) for the full security audit (19 findings, current posture 9/10).
337
+
336
338
 
337
339
  ## CLI Reference
338
340
 
@@ -376,7 +378,7 @@ All commands support `--json` for machine-readable output.
376
378
  git clone https://github.com/n1byn1kt/apitap.git
377
379
  cd apitap
378
380
  npm install
379
- npm test # 721 tests, Node built-in test runner
381
+ npm test # 789 tests, Node built-in test runner
380
382
  npm run typecheck # Type checking
381
383
  npm run build # Compile to dist/
382
384
  npx tsx src/cli.ts capture <url> # Run from source
@@ -9,7 +9,7 @@ export interface EncryptedData {
9
9
  * Uses a fixed application salt — the entropy comes from the machine ID
10
10
  * being stretched through 100K iterations.
11
11
  */
12
- export declare function deriveKey(machineId: string): Buffer;
12
+ export declare function deriveKey(machineId: string, saltFile?: string): Buffer;
13
13
  /**
14
14
  * Encrypt plaintext using AES-256-GCM.
15
15
  * Each call generates a random IV for semantic security.
@@ -4,14 +4,37 @@ const ALGORITHM = 'aes-256-gcm';
4
4
  const KEY_LENGTH = 32; // 256 bits
5
5
  const IV_LENGTH = 16;
6
6
  const PBKDF2_ITERATIONS = 100_000;
7
- const PBKDF2_SALT = 'apitap-v0.2-key-derivation';
7
+ // const PBKDF2_SALT = 'apitap-v0.2-key-derivation'; // fallback for migration
8
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';
9
+ import { homedir } from 'node:os';
10
+ function getInstallSalt(saltFile) {
11
+ const saltPath = saltFile || `${homedir()}/.apitap/install-salt`;
12
+ if (existsSync(saltPath)) {
13
+ return readFileSync(saltPath, 'utf8').trim();
14
+ }
15
+ // Generate and save new salt
16
+ const salt = randomBytes(32).toString('hex');
17
+ try {
18
+ mkdirSync(saltPath.replace(/\/[^/]+$/, ''), { recursive: true });
19
+ writeFileSync(saltPath, salt, { mode: 0o600 });
20
+ }
21
+ catch { }
22
+ return salt;
23
+ }
8
24
  /**
9
25
  * Derive a 256-bit key from a machine identifier using PBKDF2.
10
26
  * Uses a fixed application salt — the entropy comes from the machine ID
11
27
  * being stretched through 100K iterations.
12
28
  */
13
- export function deriveKey(machineId) {
14
- return pbkdf2Sync(machineId, PBKDF2_SALT, PBKDF2_ITERATIONS, KEY_LENGTH, 'sha512');
29
+ export function deriveKey(machineId, saltFile) {
30
+ // Try per-install salt first, fallback to old constant for migration
31
+ try {
32
+ return pbkdf2Sync(machineId, getInstallSalt(saltFile), PBKDF2_ITERATIONS, KEY_LENGTH, 'sha512');
33
+ }
34
+ catch {
35
+ // Fallback for old installs (migration note: remove after all users migrated)
36
+ return pbkdf2Sync(machineId, 'apitap-v0.2-key-derivation', PBKDF2_ITERATIONS, KEY_LENGTH, 'sha512');
37
+ }
15
38
  }
16
39
  /**
17
40
  * Encrypt plaintext using AES-256-GCM.
@@ -24,7 +47,7 @@ export function encrypt(plaintext, key) {
24
47
  ciphertext += cipher.final('hex');
25
48
  const tag = cipher.getAuthTag();
26
49
  return {
27
- salt: PBKDF2_SALT,
50
+ salt: 'apitap-v0.2-key-derivation', // Legacy: stored for reference, not used in decrypt
28
51
  iv: iv.toString('hex'),
29
52
  ciphertext,
30
53
  tag: tag.toString('hex'),
@@ -1 +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"}
1
+ {"version":3,"file":"crypto.js","sourceRoot":"","sources":["../../src/auth/crypto.ts"],"names":[],"mappings":"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"}
@@ -6,7 +6,7 @@ import type { StoredAuth, StoredToken, StoredSession } from '../types.js';
6
6
  export declare class AuthManager {
7
7
  private key;
8
8
  private authPath;
9
- constructor(baseDir: string, machineId: string);
9
+ constructor(baseDir: string, machineId: string, saltFile?: string);
10
10
  /** Store auth credentials for a domain (overwrites existing). */
11
11
  store(domain: string, auth: StoredAuth): Promise<void>;
12
12
  /** Retrieve auth credentials for a domain. Returns null if not found or decryption fails. */
@@ -10,8 +10,8 @@ const AUTH_FILENAME = 'auth.enc';
10
10
  export class AuthManager {
11
11
  key;
12
12
  authPath;
13
- constructor(baseDir, machineId) {
14
- this.key = deriveKey(machineId);
13
+ constructor(baseDir, machineId, saltFile) {
14
+ this.key = deriveKey(machineId, saltFile);
15
15
  this.authPath = join(baseDir, AUTH_FILENAME);
16
16
  }
17
17
  /** Store auth credentials for a domain (overwrites existing). */
@@ -1 +1 @@
1
- {"version":3,"file":"manager.js","sourceRoot":"","sources":["../../src/auth/manager.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"manager.js","sourceRoot":"","sources":["../../src/auth/manager.ts"],"names":[],"mappings":"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"}
@@ -27,9 +27,21 @@ export async function safeFetch(url, options = {}) {
27
27
  'User-Agent': USER_AGENT,
28
28
  'Accept': 'text/html,application/json,*/*',
29
29
  },
30
- redirect: 'follow',
30
+ redirect: 'manual',
31
31
  });
32
32
  clearTimeout(timer);
33
+ // SSRF-safe manual redirect (one hop max)
34
+ if (response.status >= 300 && response.status < 400 && response.headers.has('location')) {
35
+ const location = response.headers.get('location');
36
+ if (!location)
37
+ return null;
38
+ const redirectUrl = new URL(location, url).toString();
39
+ const ssrfResult = validateUrl(redirectUrl);
40
+ if (!ssrfResult.safe)
41
+ return null;
42
+ // Follow one redirect hop only
43
+ return await safeFetch(redirectUrl, { ...options, skipSsrf: true });
44
+ }
33
45
  // Extract headers
34
46
  const headers = {};
35
47
  response.headers.forEach((value, key) => {
@@ -1 +1 @@
1
- {"version":3,"file":"fetch.js","sourceRoot":"","sources":["../../src/discovery/fetch.ts"],"names":[],"mappings":"AAAA,yBAAyB;AACzB,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAgB/C,MAAM,eAAe,GAAG,IAAI,CAAC;AAC7B,MAAM,gBAAgB,GAAG,GAAG,GAAG,IAAI,CAAC,CAAC,QAAQ;AAC7C,MAAM,UAAU,GAAG,sBAAsB,CAAC;AAE1C;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,GAAW,EACX,UAA4B,EAAE;IAE9B,aAAa;IACb,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC;QACtB,MAAM,UAAU,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;QACpC,IAAI,CAAC,UAAU,CAAC,IAAI;YAAE,OAAO,IAAI,CAAC;IACpC,CAAC;IAED,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,eAAe,CAAC;IACnD,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,KAAK,CAAC;IACvC,MAAM,OAAO,GAAG,OAAO,CAAC,WAAW,IAAI,gBAAgB,CAAC;IAExD,IAAI,CAAC;QACH,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;QACzC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,OAAO,CAAC,CAAC;QAE5D,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;YAChC,MAAM;YACN,MAAM,EAAE,UAAU,CAAC,MAAM;YACzB,OAAO,EAAE;gBACP,YAAY,EAAE,UAAU;gBACxB,QAAQ,EAAE,gCAAgC;aAC3C;YACD,QAAQ,EAAE,QAAQ;SACnB,CAAC,CAAC;QAEH,YAAY,CAAC,KAAK,CAAC,CAAC;QAEpB,kBAAkB;QAClB,MAAM,OAAO,GAA2B,EAAE,CAAC;QAC3C,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;YACtC,OAAO,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC,GAAG,KAAK,CAAC;QACrC,CAAC,CAAC,CAAC;QAEH,MAAM,WAAW,GAAG,OAAO,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC;QAElD,qCAAqC;QACrC,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;YACtB,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,WAAW,EAAE,CAAC;QACrE,CAAC;QAED,4BAA4B;QAC5B,MAAM,IAAI,GAAG,MAAM,eAAe,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QAEtD,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;IACjE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,KAAK,UAAU,eAAe,CAAC,QAAkB,EAAE,OAAe;IAChE,yEAAyE;IACzE,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;IACnC,IAAI,IAAI,CAAC,MAAM,GAAG,OAAO,EAAE,CAAC;QAC1B,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;IAChC,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
1
+ {"version":3,"file":"fetch.js","sourceRoot":"","sources":["../../src/discovery/fetch.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@apitap/core",
3
- "version": "1.0.5",
3
+ "version": "1.0.7",
4
4
  "description": "Intercept web API traffic during browsing. Generate portable skill files so AI agents can call APIs directly instead of scraping.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -12,7 +12,24 @@ const ALGORITHM = 'aes-256-gcm';
12
12
  const KEY_LENGTH = 32; // 256 bits
13
13
  const IV_LENGTH = 16;
14
14
  const PBKDF2_ITERATIONS = 100_000;
15
- const PBKDF2_SALT = 'apitap-v0.2-key-derivation';
15
+ // const PBKDF2_SALT = 'apitap-v0.2-key-derivation'; // fallback for migration
16
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';
17
+ import { homedir } from 'node:os';
18
+
19
+ function getInstallSalt(saltFile?: string): string {
20
+ const saltPath = saltFile || `${homedir()}/.apitap/install-salt`;
21
+ if (existsSync(saltPath)) {
22
+ return readFileSync(saltPath, 'utf8').trim();
23
+ }
24
+ // Generate and save new salt
25
+ const salt = randomBytes(32).toString('hex');
26
+ try {
27
+ mkdirSync(saltPath.replace(/\/[^/]+$/, ''), { recursive: true });
28
+ writeFileSync(saltPath, salt, { mode: 0o600 });
29
+ } catch {}
30
+ return salt;
31
+ }
32
+
16
33
 
17
34
  export interface EncryptedData {
18
35
  salt: string;
@@ -26,8 +43,14 @@ export interface EncryptedData {
26
43
  * Uses a fixed application salt — the entropy comes from the machine ID
27
44
  * being stretched through 100K iterations.
28
45
  */
29
- export function deriveKey(machineId: string): Buffer {
30
- return pbkdf2Sync(machineId, PBKDF2_SALT, PBKDF2_ITERATIONS, KEY_LENGTH, 'sha512');
46
+ export function deriveKey(machineId: string, saltFile?: string): Buffer {
47
+ // Try per-install salt first, fallback to old constant for migration
48
+ try {
49
+ return pbkdf2Sync(machineId, getInstallSalt(saltFile), PBKDF2_ITERATIONS, KEY_LENGTH, 'sha512');
50
+ } catch {
51
+ // Fallback for old installs (migration note: remove after all users migrated)
52
+ return pbkdf2Sync(machineId, 'apitap-v0.2-key-derivation', PBKDF2_ITERATIONS, KEY_LENGTH, 'sha512');
53
+ }
31
54
  }
32
55
 
33
56
  /**
@@ -43,7 +66,7 @@ export function encrypt(plaintext: string, key: Buffer): EncryptedData {
43
66
  const tag = cipher.getAuthTag();
44
67
 
45
68
  return {
46
- salt: PBKDF2_SALT,
69
+ salt: 'apitap-v0.2-key-derivation', // Legacy: stored for reference, not used in decrypt
47
70
  iv: iv.toString('hex'),
48
71
  ciphertext,
49
72
  tag: tag.toString('hex'),
@@ -14,8 +14,8 @@ export class AuthManager {
14
14
  private key: Buffer;
15
15
  private authPath: string;
16
16
 
17
- constructor(baseDir: string, machineId: string) {
18
- this.key = deriveKey(machineId);
17
+ constructor(baseDir: string, machineId: string, saltFile?: string) {
18
+ this.key = deriveKey(machineId, saltFile);
19
19
  this.authPath = join(baseDir, AUTH_FILENAME);
20
20
  }
21
21
 
@@ -6,6 +6,7 @@ export interface OAuthInfo {
6
6
  grantType: 'refresh_token' | 'client_credentials';
7
7
  scope?: string;
8
8
  clientSecret?: string;
9
+ refreshToken?: string;
9
10
  }
10
11
 
11
12
  /**
@@ -61,6 +62,8 @@ export function isOAuthTokenRequest(req: {
61
62
  const scope = params.get('scope');
62
63
  if (scope) result.scope = scope;
63
64
  if (clientSecret) result.clientSecret = clientSecret;
65
+ const refreshToken = params.get('refresh_token');
66
+ if (refreshToken) result.refreshToken = refreshToken;
64
67
 
65
68
  return result;
66
69
  }
@@ -224,8 +224,12 @@ export class CaptureSession {
224
224
  const oauthConfig = generator.getOAuthConfig();
225
225
  if (oauthConfig) {
226
226
  const clientSecret = generator.getOAuthClientSecret();
227
- if (clientSecret) {
228
- await authManager.storeOAuthCredentials(domain, { clientSecret });
227
+ const refreshToken = generator.getOAuthRefreshToken();
228
+ if (clientSecret || refreshToken) {
229
+ await authManager.storeOAuthCredentials(domain, {
230
+ ...(clientSecret ? { clientSecret } : {}),
231
+ ...(refreshToken ? { refreshToken } : {}),
232
+ });
229
233
  }
230
234
  }
231
235
 
package/src/cli.ts CHANGED
@@ -211,8 +211,12 @@ async function handleCapture(positional: string[], flags: Record<string, string
211
211
  const oauthConfig = generator.getOAuthConfig();
212
212
  if (oauthConfig) {
213
213
  const clientSecret = generator.getOAuthClientSecret();
214
- if (clientSecret) {
215
- await authManager.storeOAuthCredentials(domain, { clientSecret });
214
+ const refreshToken = generator.getOAuthRefreshToken();
215
+ if (clientSecret || refreshToken) {
216
+ await authManager.storeOAuthCredentials(domain, {
217
+ ...(clientSecret ? { clientSecret } : {}),
218
+ ...(refreshToken ? { refreshToken } : {}),
219
+ });
216
220
  }
217
221
  }
218
222
 
@@ -48,11 +48,22 @@ export async function safeFetch(
48
48
  'User-Agent': USER_AGENT,
49
49
  'Accept': 'text/html,application/json,*/*',
50
50
  },
51
- redirect: 'follow',
51
+ redirect: 'manual',
52
52
  });
53
53
 
54
54
  clearTimeout(timer);
55
55
 
56
+ // SSRF-safe manual redirect (one hop max)
57
+ if (response.status >= 300 && response.status < 400 && response.headers.has('location')) {
58
+ const location = response.headers.get('location');
59
+ if (!location) return null;
60
+ const redirectUrl = new URL(location, url).toString();
61
+ const ssrfResult = validateUrl(redirectUrl);
62
+ if (!ssrfResult.safe) return null;
63
+ // Follow one redirect hop only
64
+ return await safeFetch(redirectUrl, { ...options, skipSsrf: true });
65
+ }
66
+
56
67
  // Extract headers
57
68
  const headers: Record<string, string> = {};
58
69
  response.headers.forEach((value, key) => {
@@ -180,6 +180,7 @@ export class SkillGenerator {
180
180
  private captchaRisk = false;
181
181
  private oauthConfig: OAuthConfig | null = null;
182
182
  private oauthClientSecret: string | undefined;
183
+ private oauthRefreshToken: string | undefined;
183
184
  private totalNetworkBytes = 0; // v1.0: accumulate all response sizes
184
185
 
185
186
  /** Number of unique endpoints captured so far */
@@ -255,6 +256,7 @@ export class SkillGenerator {
255
256
  ...(oauthInfo.scope ? { scope: oauthInfo.scope } : {}),
256
257
  };
257
258
  this.oauthClientSecret = oauthInfo.clientSecret;
259
+ this.oauthRefreshToken = oauthInfo.refreshToken;
258
260
  }
259
261
 
260
262
  // Extract auth before filtering headers (includes entropy-based detection)
@@ -402,6 +404,11 @@ export class SkillGenerator {
402
404
  return this.oauthClientSecret;
403
405
  }
404
406
 
407
+ /** Get the refresh token captured from OAuth traffic (for encrypted storage). */
408
+ getOAuthRefreshToken(): string | undefined {
409
+ return this.oauthRefreshToken;
410
+ }
411
+
405
412
  /** Check if any endpoint has refreshable tokens. */
406
413
  private hasRefreshableTokens(): boolean {
407
414
  for (const endpoint of this.endpoints.values()) {