@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,80 @@
1
+ // src/skill/importer.ts
2
+ import { readFile } from 'node:fs/promises';
3
+ import { verifySignature } from './signing.js';
4
+ import { validateSkillFileUrls } from './ssrf.js';
5
+ import { writeSkillFile } from './store.js';
6
+ /**
7
+ * Validate a skill file for import.
8
+ * Checks structure, SSRF safety, and signature integrity.
9
+ */
10
+ export function validateImport(skill, localKey) {
11
+ // Basic structure validation
12
+ if (!skill.domain || !skill.baseUrl || !Array.isArray(skill.endpoints)) {
13
+ return { valid: false, reason: 'Invalid skill file structure', signatureStatus: 'unsigned' };
14
+ }
15
+ // Signature check
16
+ let signatureStatus = 'unsigned';
17
+ if (skill.signature) {
18
+ if (localKey && verifySignature(skill, localKey)) {
19
+ signatureStatus = 'valid';
20
+ }
21
+ else {
22
+ return {
23
+ valid: false,
24
+ reason: 'Skill file signature is invalid — file was tampered with or signed by a different instance',
25
+ signatureStatus: 'invalid',
26
+ };
27
+ }
28
+ }
29
+ // SSRF validation
30
+ const ssrfResult = validateSkillFileUrls(skill);
31
+ if (!ssrfResult.safe) {
32
+ return {
33
+ valid: false,
34
+ reason: `SSRF risk: ${ssrfResult.reason}`,
35
+ signatureStatus,
36
+ };
37
+ }
38
+ return {
39
+ valid: true,
40
+ signatureStatus,
41
+ summary: {
42
+ domain: skill.domain,
43
+ endpointCount: skill.endpoints.length,
44
+ baseUrl: skill.baseUrl,
45
+ },
46
+ };
47
+ }
48
+ /**
49
+ * Import a skill file from disk.
50
+ * Validates, strips foreign signatures, sets provenance to 'imported'.
51
+ */
52
+ export async function importSkillFile(filePath, skillsDir, localKey) {
53
+ let content;
54
+ try {
55
+ content = await readFile(filePath, 'utf-8');
56
+ }
57
+ catch (err) {
58
+ return { success: false, reason: `Cannot read file: ${err.message}` };
59
+ }
60
+ let skill;
61
+ try {
62
+ skill = JSON.parse(content);
63
+ }
64
+ catch {
65
+ return { success: false, reason: 'File is not valid JSON' };
66
+ }
67
+ const validation = validateImport(skill, localKey);
68
+ if (!validation.valid) {
69
+ return { success: false, reason: validation.reason };
70
+ }
71
+ // Strip foreign signature, set provenance
72
+ const importedSkill = {
73
+ ...skill,
74
+ provenance: 'imported',
75
+ signature: undefined,
76
+ };
77
+ const writtenPath = await writeSkillFile(importedSkill, skillsDir);
78
+ return { success: true, skillFile: writtenPath };
79
+ }
80
+ //# sourceMappingURL=importer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"importer.js","sourceRoot":"","sources":["../../src/skill/importer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ export interface SearchResult {
2
+ domain: string;
3
+ endpointId: string;
4
+ method: string;
5
+ path: string;
6
+ tier: string;
7
+ verified: boolean;
8
+ }
9
+ export interface SearchResponse {
10
+ found: boolean;
11
+ results?: SearchResult[];
12
+ suggestion?: string;
13
+ }
14
+ /**
15
+ * Search skill files for endpoints matching a query.
16
+ * Matches against domain names, endpoint IDs, and endpoint paths.
17
+ * Query terms are matched case-insensitively.
18
+ */
19
+ export declare function searchSkills(query: string, skillsDir?: string): Promise<SearchResponse>;
@@ -0,0 +1,51 @@
1
+ // src/skill/search.ts
2
+ import { listSkillFiles, readSkillFile } from './store.js';
3
+ /**
4
+ * Search skill files for endpoints matching a query.
5
+ * Matches against domain names, endpoint IDs, and endpoint paths.
6
+ * Query terms are matched case-insensitively.
7
+ */
8
+ export async function searchSkills(query, skillsDir) {
9
+ const summaries = await listSkillFiles(skillsDir);
10
+ if (summaries.length === 0) {
11
+ return {
12
+ found: false,
13
+ suggestion: 'No skill files found. Run `apitap capture <url>` to capture API traffic first.',
14
+ };
15
+ }
16
+ const terms = query.toLowerCase().split(/\s+/).filter(Boolean);
17
+ const results = [];
18
+ for (const summary of summaries) {
19
+ const skill = await readSkillFile(summary.domain, skillsDir);
20
+ if (!skill)
21
+ continue;
22
+ const domainLower = skill.domain.toLowerCase();
23
+ for (const ep of skill.endpoints) {
24
+ const endpointIdLower = ep.id.toLowerCase();
25
+ const pathLower = ep.path.toLowerCase();
26
+ const methodLower = ep.method.toLowerCase();
27
+ // Check if all query terms match against the combined searchable text
28
+ const searchText = `${domainLower} ${endpointIdLower} ${pathLower} ${methodLower}`;
29
+ const allMatch = terms.every(term => searchText.includes(term));
30
+ if (allMatch) {
31
+ results.push({
32
+ domain: skill.domain,
33
+ endpointId: ep.id,
34
+ method: ep.method,
35
+ path: ep.path,
36
+ tier: ep.replayability?.tier ?? 'unknown',
37
+ verified: ep.replayability?.verified ?? false,
38
+ });
39
+ }
40
+ }
41
+ }
42
+ if (results.length === 0) {
43
+ const domains = summaries.map(s => s.domain).join(', ');
44
+ return {
45
+ found: false,
46
+ suggestion: `No matches for "${query}". Available domains: ${domains}`,
47
+ };
48
+ }
49
+ return { found: true, results };
50
+ }
51
+ //# sourceMappingURL=search.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search.js","sourceRoot":"","sources":["../../src/skill/search.ts"],"names":[],"mappings":"AAAA,sBAAsB;AACtB,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAiB3D;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,KAAa,EACb,SAAkB;IAElB,MAAM,SAAS,GAAG,MAAM,cAAc,CAAC,SAAS,CAAC,CAAC;IAClD,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3B,OAAO;YACL,KAAK,EAAE,KAAK;YACZ,UAAU,EAAE,gFAAgF;SAC7F,CAAC;IACJ,CAAC;IAED,MAAM,KAAK,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAC/D,MAAM,OAAO,GAAmB,EAAE,CAAC;IAEnC,KAAK,MAAM,OAAO,IAAI,SAAS,EAAE,CAAC;QAChC,MAAM,KAAK,GAAG,MAAM,aAAa,CAAC,OAAO,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;QAC7D,IAAI,CAAC,KAAK;YAAE,SAAS;QAErB,MAAM,WAAW,GAAG,KAAK,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC;QAE/C,KAAK,MAAM,EAAE,IAAI,KAAK,CAAC,SAAS,EAAE,CAAC;YACjC,MAAM,eAAe,GAAG,EAAE,CAAC,EAAE,CAAC,WAAW,EAAE,CAAC;YAC5C,MAAM,SAAS,GAAG,EAAE,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;YACxC,MAAM,WAAW,GAAG,EAAE,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC;YAE5C,sEAAsE;YACtE,MAAM,UAAU,GAAG,GAAG,WAAW,IAAI,eAAe,IAAI,SAAS,IAAI,WAAW,EAAE,CAAC;YACnF,MAAM,QAAQ,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC;YAEhE,IAAI,QAAQ,EAAE,CAAC;gBACb,OAAO,CAAC,IAAI,CAAC;oBACX,MAAM,EAAE,KAAK,CAAC,MAAM;oBACpB,UAAU,EAAE,EAAE,CAAC,EAAE;oBACjB,MAAM,EAAE,EAAE,CAAC,MAAM;oBACjB,IAAI,EAAE,EAAE,CAAC,IAAI;oBACb,IAAI,EAAE,EAAE,CAAC,aAAa,EAAE,IAAI,IAAI,SAAS;oBACzC,QAAQ,EAAE,EAAE,CAAC,aAAa,EAAE,QAAQ,IAAI,KAAK;iBAC9C,CAAC,CAAC;YACL,CAAC;QACH,CAAC;IACH,CAAC;IAED,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,MAAM,OAAO,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACxD,OAAO;YACL,KAAK,EAAE,KAAK;YACZ,UAAU,EAAE,mBAAmB,KAAK,yBAAyB,OAAO,EAAE;SACvE,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;AAClC,CAAC"}
@@ -0,0 +1,16 @@
1
+ import type { SkillFile } from '../types.js';
2
+ /**
3
+ * Create a canonical JSON string from a skill file,
4
+ * excluding `signature` and `provenance` fields.
5
+ * This is the payload that gets signed.
6
+ */
7
+ export declare function canonicalize(skill: SkillFile): string;
8
+ /**
9
+ * Sign a skill file. Returns a new object with signature and provenance: 'self'.
10
+ */
11
+ export declare function signSkillFile(skill: SkillFile, key: Buffer): SkillFile;
12
+ /**
13
+ * Verify a skill file's signature.
14
+ * Returns true if the signature is valid for the given key.
15
+ */
16
+ export declare function verifySignature(skill: SkillFile, key: Buffer): boolean;
@@ -0,0 +1,34 @@
1
+ // src/skill/signing.ts
2
+ import { hmacSign, hmacVerify } from '../auth/crypto.js';
3
+ /**
4
+ * Create a canonical JSON string from a skill file,
5
+ * excluding `signature` and `provenance` fields.
6
+ * This is the payload that gets signed.
7
+ */
8
+ export function canonicalize(skill) {
9
+ const { signature: _sig, provenance: _prov, ...rest } = skill;
10
+ return JSON.stringify(rest, Object.keys(rest).sort());
11
+ }
12
+ /**
13
+ * Sign a skill file. Returns a new object with signature and provenance: 'self'.
14
+ */
15
+ export function signSkillFile(skill, key) {
16
+ const payload = canonicalize(skill);
17
+ const signature = hmacSign(payload, key);
18
+ return {
19
+ ...skill,
20
+ provenance: 'self',
21
+ signature,
22
+ };
23
+ }
24
+ /**
25
+ * Verify a skill file's signature.
26
+ * Returns true if the signature is valid for the given key.
27
+ */
28
+ export function verifySignature(skill, key) {
29
+ if (!skill.signature)
30
+ return false;
31
+ const payload = canonicalize(skill);
32
+ return hmacVerify(payload, skill.signature, key);
33
+ }
34
+ //# sourceMappingURL=signing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signing.js","sourceRoot":"","sources":["../../src/skill/signing.ts"],"names":[],"mappings":"AAAA,uBAAuB;AACvB,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAGzD;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAAC,KAAgB;IAC3C,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,GAAG,IAAI,EAAE,GAAG,KAAK,CAAC;IAC9D,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;AACxD,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,aAAa,CAAC,KAAgB,EAAE,GAAW;IACzD,MAAM,OAAO,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC;IACpC,MAAM,SAAS,GAAG,QAAQ,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;IACzC,OAAO;QACL,GAAG,KAAK;QACR,UAAU,EAAE,MAAM;QAClB,SAAS;KACV,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,KAAgB,EAAE,GAAW;IAC3D,IAAI,CAAC,KAAK,CAAC,SAAS;QAAE,OAAO,KAAK,CAAC;IACnC,MAAM,OAAO,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC;IACpC,OAAO,UAAU,CAAC,OAAO,EAAE,KAAK,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;AACnD,CAAC"}
@@ -0,0 +1,27 @@
1
+ import type { SkillFile } from '../types.js';
2
+ export interface ValidationResult {
3
+ safe: boolean;
4
+ reason?: string;
5
+ resolvedUrl?: string;
6
+ resolvedIp?: string;
7
+ originalHost?: string;
8
+ }
9
+ /**
10
+ * Check if a URL is safe to replay (not targeting internal infrastructure).
11
+ */
12
+ export declare function validateUrl(urlString: string): ValidationResult;
13
+ /**
14
+ * Resolve hostname and validate the resolved IP against private ranges.
15
+ * Prevents DNS rebinding attacks where a domain resolves to 127.0.0.1.
16
+ */
17
+ export declare function resolveAndValidateUrl(urlString: string): Promise<ValidationResult>;
18
+ /**
19
+ * Validate all URLs in a skill file with DNS resolution.
20
+ * Checks baseUrl and all endpoint example URLs.
21
+ */
22
+ export declare function resolveAndValidateSkillFileUrls(skill: SkillFile): Promise<ValidationResult>;
23
+ /**
24
+ * Validate all URLs in a skill file (sync, hostname-based only).
25
+ * Checks baseUrl and all endpoint example URLs.
26
+ */
27
+ export declare function validateSkillFileUrls(skill: SkillFile): ValidationResult;
@@ -0,0 +1,210 @@
1
+ // src/skill/ssrf.ts
2
+ import { lookup } from 'node:dns/promises';
3
+ const INTERNAL_HOSTNAMES = ['localhost'];
4
+ const INTERNAL_SUFFIXES = ['.local', '.internal'];
5
+ /**
6
+ * Check if a URL is safe to replay (not targeting internal infrastructure).
7
+ */
8
+ export function validateUrl(urlString) {
9
+ let url;
10
+ try {
11
+ url = new URL(urlString);
12
+ }
13
+ catch {
14
+ return { safe: false, reason: 'Invalid URL' };
15
+ }
16
+ // Scheme check
17
+ if (url.protocol !== 'http:' && url.protocol !== 'https:') {
18
+ return { safe: false, reason: `Non-HTTP scheme: ${url.protocol}` };
19
+ }
20
+ const hostname = url.hostname;
21
+ // Exact internal hostnames
22
+ if (INTERNAL_HOSTNAMES.includes(hostname)) {
23
+ return { safe: false, reason: `URL targets internal hostname: ${hostname}` };
24
+ }
25
+ // Internal domain suffixes
26
+ for (const suffix of INTERNAL_SUFFIXES) {
27
+ if (hostname.endsWith(suffix)) {
28
+ return { safe: false, reason: `URL targets internal domain: ${hostname}` };
29
+ }
30
+ }
31
+ // IPv6 loopback
32
+ if (hostname === '[::1]' || hostname === '::1') {
33
+ return { safe: false, reason: 'URL targets IPv6 loopback' };
34
+ }
35
+ // IPv4-mapped IPv6 — dotted-quad form (e.g. [::ffff:127.0.0.1])
36
+ const v4MappedMatch = hostname.match(/^\[?::ffff:(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\]?$/i);
37
+ if (v4MappedMatch) {
38
+ return validateUrl(`${url.protocol}//${v4MappedMatch[1]}${url.port ? ':' + url.port : ''}${url.pathname}`);
39
+ }
40
+ // IPv4-mapped IPv6 — hex form (e.g. [::ffff:7f00:1], Node normalizes to this)
41
+ const v4MappedHexMatch = hostname.match(/^\[?::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})\]?$/i);
42
+ if (v4MappedHexMatch) {
43
+ const hi = parseInt(v4MappedHexMatch[1], 16);
44
+ const lo = parseInt(v4MappedHexMatch[2], 16);
45
+ const ipv4 = `${(hi >> 8) & 0xff}.${hi & 0xff}.${(lo >> 8) & 0xff}.${lo & 0xff}`;
46
+ return validateUrl(`${url.protocol}//${ipv4}${url.port ? ':' + url.port : ''}${url.pathname}`);
47
+ }
48
+ // IPv6 link-local (fe80::/10)
49
+ if (/^\[?fe[89ab][0-9a-f]:/i.test(hostname)) {
50
+ return { safe: false, reason: `URL targets IPv6 link-local address: ${hostname}` };
51
+ }
52
+ // IPv6 unique local (fc00::/7 — includes fd00::/8)
53
+ if (/^\[?f[cd][0-9a-f]{2}:/i.test(hostname)) {
54
+ return { safe: false, reason: `URL targets IPv6 unique-local address: ${hostname}` };
55
+ }
56
+ // IPv4 private ranges
57
+ const ipv4Match = hostname.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/);
58
+ if (ipv4Match) {
59
+ const [, a, b] = ipv4Match.map(Number);
60
+ const first = Number(a);
61
+ const second = Number(b);
62
+ // 0.0.0.0 — unspecified
63
+ if (first === 0) {
64
+ return { safe: false, reason: `URL targets unspecified address: ${hostname}` };
65
+ }
66
+ // 127.x.x.x — loopback
67
+ if (first === 127) {
68
+ return { safe: false, reason: `URL targets loopback address: ${hostname}` };
69
+ }
70
+ // 10.x.x.x — private
71
+ if (first === 10) {
72
+ return { safe: false, reason: `URL targets private IP: ${hostname}` };
73
+ }
74
+ // 172.16-31.x.x — private
75
+ if (first === 172 && second >= 16 && second <= 31) {
76
+ return { safe: false, reason: `URL targets private IP: ${hostname}` };
77
+ }
78
+ // 192.168.x.x — private
79
+ if (first === 192 && second === 168) {
80
+ return { safe: false, reason: `URL targets private IP: ${hostname}` };
81
+ }
82
+ // 169.254.x.x — link-local
83
+ if (first === 169 && second === 254) {
84
+ return { safe: false, reason: `URL targets link-local address: ${hostname}` };
85
+ }
86
+ }
87
+ return { safe: true };
88
+ }
89
+ /**
90
+ * Check if a resolved IP address is in a private/reserved range.
91
+ */
92
+ function isPrivateIp(ip) {
93
+ // IPv6 loopback
94
+ if (ip === '::1')
95
+ return 'IPv6 loopback';
96
+ // IPv6 link-local (fe80::/10)
97
+ if (/^fe[89ab][0-9a-f]:/i.test(ip))
98
+ return 'IPv6 link-local';
99
+ // IPv6 unique local (fc00::/7 — includes fd00::/8)
100
+ if (/^f[cd][0-9a-f]{2}:/i.test(ip))
101
+ return 'IPv6 unique-local';
102
+ // IPv4-mapped IPv6 (e.g. ::ffff:127.0.0.1)
103
+ const v4mapped = ip.match(/^::ffff:(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/i);
104
+ const ipv4 = v4mapped ? v4mapped[1] : ip;
105
+ const parts = ipv4.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/);
106
+ if (!parts)
107
+ return null; // Not an IPv4 — let it pass (non-private IPv6)
108
+ const [, a, b] = parts;
109
+ const first = Number(a);
110
+ const second = Number(b);
111
+ if (first === 127)
112
+ return 'loopback';
113
+ if (first === 10)
114
+ return 'private (10.x)';
115
+ if (first === 172 && second >= 16 && second <= 31)
116
+ return 'private (172.16-31.x)';
117
+ if (first === 192 && second === 168)
118
+ return 'private (192.168.x)';
119
+ if (first === 169 && second === 254)
120
+ return 'link-local';
121
+ if (first === 0)
122
+ return 'unspecified';
123
+ return null;
124
+ }
125
+ /**
126
+ * Resolve hostname and validate the resolved IP against private ranges.
127
+ * Prevents DNS rebinding attacks where a domain resolves to 127.0.0.1.
128
+ */
129
+ export async function resolveAndValidateUrl(urlString) {
130
+ // First run the sync hostname-based checks
131
+ const syncResult = validateUrl(urlString);
132
+ if (!syncResult.safe)
133
+ return syncResult;
134
+ let url;
135
+ try {
136
+ url = new URL(urlString);
137
+ }
138
+ catch {
139
+ return { safe: false, reason: 'Invalid URL' };
140
+ }
141
+ const hostname = url.hostname;
142
+ // Skip DNS resolution for raw IPs (already checked by validateUrl)
143
+ if (hostname.match(/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/) || hostname.startsWith('[')) {
144
+ return { safe: true };
145
+ }
146
+ // Resolve DNS and check the actual IP
147
+ try {
148
+ const { address } = await lookup(hostname);
149
+ const privateReason = isPrivateIp(address);
150
+ if (privateReason) {
151
+ return { safe: false, reason: `DNS rebinding: ${hostname} resolves to ${address} (${privateReason})` };
152
+ }
153
+ // Return the resolved URL with IP pinned to prevent DNS rebinding
154
+ const pinnedUrl = new URL(urlString);
155
+ pinnedUrl.hostname = address;
156
+ return {
157
+ safe: true,
158
+ resolvedUrl: pinnedUrl.toString(),
159
+ resolvedIp: address,
160
+ originalHost: hostname
161
+ };
162
+ }
163
+ catch {
164
+ // DNS resolution failed — hostname doesn't exist
165
+ return { safe: false, reason: `DNS resolution failed for ${hostname}` };
166
+ }
167
+ }
168
+ /**
169
+ * Validate all URLs in a skill file with DNS resolution.
170
+ * Checks baseUrl and all endpoint example URLs.
171
+ */
172
+ export async function resolveAndValidateSkillFileUrls(skill) {
173
+ const baseResult = await resolveAndValidateUrl(skill.baseUrl);
174
+ if (!baseResult.safe) {
175
+ return { safe: false, reason: `baseUrl: ${baseResult.reason}` };
176
+ }
177
+ for (const ep of skill.endpoints) {
178
+ const exUrl = ep.examples?.request?.url;
179
+ if (exUrl) {
180
+ const result = await resolveAndValidateUrl(exUrl);
181
+ if (!result.safe) {
182
+ return { safe: false, reason: `endpoint ${ep.id}: ${result.reason}` };
183
+ }
184
+ }
185
+ }
186
+ return { safe: true };
187
+ }
188
+ /**
189
+ * Validate all URLs in a skill file (sync, hostname-based only).
190
+ * Checks baseUrl and all endpoint example URLs.
191
+ */
192
+ export function validateSkillFileUrls(skill) {
193
+ // Check baseUrl
194
+ const baseResult = validateUrl(skill.baseUrl);
195
+ if (!baseResult.safe) {
196
+ return { safe: false, reason: `baseUrl: ${baseResult.reason}` };
197
+ }
198
+ // Check endpoint example URLs
199
+ for (const ep of skill.endpoints) {
200
+ const exUrl = ep.examples?.request?.url;
201
+ if (exUrl) {
202
+ const result = validateUrl(exUrl);
203
+ if (!result.safe) {
204
+ return { safe: false, reason: `endpoint ${ep.id}: ${result.reason}` };
205
+ }
206
+ }
207
+ }
208
+ return { safe: true };
209
+ }
210
+ //# sourceMappingURL=ssrf.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ssrf.js","sourceRoot":"","sources":["../../src/skill/ssrf.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import type { SkillFile, SkillSummary } from '../types.js';
2
+ export declare function writeSkillFile(skill: SkillFile, skillsDir?: string): Promise<string>;
3
+ export declare function readSkillFile(domain: string, skillsDir?: string, options?: {
4
+ verifySignature?: boolean;
5
+ signingKey?: Buffer;
6
+ }): Promise<SkillFile | null>;
7
+ export declare function listSkillFiles(skillsDir?: string): Promise<SkillSummary[]>;
@@ -0,0 +1,93 @@
1
+ // src/skill/store.ts
2
+ import { readFile, writeFile, mkdir, readdir, access } from 'node:fs/promises';
3
+ import { join, dirname } from 'node:path';
4
+ import { homedir } from 'node:os';
5
+ const DEFAULT_SKILLS_DIR = join(homedir(), '.apitap', 'skills');
6
+ const BASE_GITIGNORE = `# ApiTap — prevent accidental credential commits
7
+ auth.enc
8
+ *.key
9
+ `;
10
+ function skillPath(domain, skillsDir) {
11
+ if (!/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(domain)) {
12
+ throw new Error(`Invalid domain: ${domain}`);
13
+ }
14
+ return join(skillsDir, `${domain}.json`);
15
+ }
16
+ async function ensureGitignore(skillsDir) {
17
+ const baseDir = dirname(skillsDir);
18
+ const gitignorePath = join(baseDir, '.gitignore');
19
+ try {
20
+ await access(gitignorePath);
21
+ // File exists, don't overwrite
22
+ }
23
+ catch {
24
+ // File doesn't exist, create it
25
+ await mkdir(baseDir, { recursive: true });
26
+ await writeFile(gitignorePath, BASE_GITIGNORE);
27
+ }
28
+ }
29
+ export async function writeSkillFile(skill, skillsDir = DEFAULT_SKILLS_DIR) {
30
+ await mkdir(skillsDir, { recursive: true });
31
+ await ensureGitignore(skillsDir);
32
+ const filePath = skillPath(skill.domain, skillsDir);
33
+ await writeFile(filePath, JSON.stringify(skill, null, 2) + '\n');
34
+ return filePath;
35
+ }
36
+ export async function readSkillFile(domain, skillsDir = DEFAULT_SKILLS_DIR, options) {
37
+ // Validate domain before file I/O — path traversal should throw, not return null
38
+ const path = skillPath(domain, skillsDir);
39
+ try {
40
+ const content = await readFile(path, 'utf-8');
41
+ const skill = JSON.parse(content);
42
+ // If verification requested, check signature
43
+ if (options?.verifySignature && options.signingKey) {
44
+ if (skill.provenance === 'imported') {
45
+ // Imported files had foreign signature stripped — can't verify, warn only
46
+ // Future: re-sign on import with local key
47
+ }
48
+ else if (!skill.signature) {
49
+ // No signature present on non-imported file
50
+ throw new Error(`Skill file for ${domain} has no signature — file may be tampered`);
51
+ }
52
+ else {
53
+ const { verifySignature } = await import('./signing.js');
54
+ if (!verifySignature(skill, options.signingKey)) {
55
+ throw new Error(`Skill file signature verification failed for ${domain} — file may be tampered`);
56
+ }
57
+ }
58
+ }
59
+ return skill;
60
+ }
61
+ catch (e) {
62
+ if (e.code === 'ENOENT')
63
+ return null;
64
+ throw e;
65
+ }
66
+ }
67
+ export async function listSkillFiles(skillsDir = DEFAULT_SKILLS_DIR) {
68
+ let files;
69
+ try {
70
+ files = await readdir(skillsDir);
71
+ }
72
+ catch {
73
+ return [];
74
+ }
75
+ const summaries = [];
76
+ for (const file of files) {
77
+ if (!file.endsWith('.json'))
78
+ continue;
79
+ const domain = file.replace(/\.json$/, '');
80
+ const skill = await readSkillFile(domain, skillsDir);
81
+ if (skill) {
82
+ summaries.push({
83
+ domain: skill.domain,
84
+ skillFile: join(skillsDir, file),
85
+ endpointCount: skill.endpoints.length,
86
+ capturedAt: skill.capturedAt,
87
+ provenance: skill.provenance ?? 'unsigned',
88
+ });
89
+ }
90
+ }
91
+ return summaries;
92
+ }
93
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/skill/store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ export interface DomainStats {
2
+ domain: string;
3
+ endpoints: number;
4
+ replayable: number;
5
+ domBytes: number;
6
+ totalNetworkBytes: number;
7
+ skillFileBytes: number;
8
+ totalResponseBytes: number;
9
+ browserTokens: number;
10
+ replayTokens: number;
11
+ savingsPercent: number;
12
+ }
13
+ export interface StatsReport {
14
+ domains: DomainStats[];
15
+ totals: {
16
+ domains: number;
17
+ endpoints: number;
18
+ replayable: number;
19
+ totalDomBytes: number;
20
+ browserTokens: number;
21
+ replayTokens: number;
22
+ savingsPercent: number;
23
+ };
24
+ }
25
+ export declare function generateStatsReport(skillsDir?: string): Promise<StatsReport>;
26
+ export declare function formatStatsHuman(report: StatsReport): string;