@zivis/cli 0.1.0-alpha.5 → 0.1.0-alpha.52

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 (197) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +31 -0
  3. package/dist/commands/app/index.d.ts +0 -8
  4. package/dist/commands/app/index.js +8 -18
  5. package/dist/commands/assurance/index.d.ts +35 -0
  6. package/dist/commands/assurance/index.js +154 -0
  7. package/dist/commands/auth/bind.d.ts +4 -0
  8. package/dist/commands/auth/bind.js +97 -0
  9. package/dist/commands/auth/index.js +21 -2
  10. package/dist/commands/auth/init.d.ts +2 -0
  11. package/dist/commands/auth/init.js +248 -96
  12. package/dist/commands/auth/login.d.ts +2 -0
  13. package/dist/commands/auth/login.js +136 -18
  14. package/dist/commands/auth/logout.js +19 -7
  15. package/dist/commands/auth/sessions.js +7 -4
  16. package/dist/commands/auth/status.js +33 -12
  17. package/dist/commands/auth/token.d.ts +4 -0
  18. package/dist/commands/auth/token.js +73 -0
  19. package/dist/commands/check/index.d.ts +2 -0
  20. package/dist/commands/check/index.js +463 -0
  21. package/dist/commands/credits/index.js +2 -3
  22. package/dist/commands/gate/index.d.ts +2 -0
  23. package/dist/commands/gate/index.js +171 -0
  24. package/dist/commands/inspect/index.d.ts +2 -0
  25. package/dist/commands/inspect/index.js +136 -0
  26. package/dist/commands/mcp/index.js +113 -14
  27. package/dist/commands/open/index.d.ts +0 -8
  28. package/dist/commands/open/index.js +0 -11
  29. package/dist/commands/run/index.d.ts +2 -0
  30. package/dist/commands/run/index.js +206 -0
  31. package/dist/commands/scan/index.js +3 -8
  32. package/dist/commands/share/index.d.ts +14 -0
  33. package/dist/commands/share/index.js +100 -0
  34. package/dist/commands/sync/index.d.ts +2 -0
  35. package/dist/commands/sync/index.js +193 -0
  36. package/dist/commands/target/index.js +0 -3
  37. package/dist/commands/test/index.d.ts +2 -0
  38. package/dist/commands/test/index.js +198 -0
  39. package/dist/commands/threatmodel/index.d.ts +2 -0
  40. package/dist/commands/threatmodel/index.js +167 -0
  41. package/dist/index.js +28 -3
  42. package/dist/internal/application-binding.d.ts +8 -0
  43. package/dist/internal/application-binding.js +167 -0
  44. package/dist/internal/binding-token-mint.d.ts +33 -0
  45. package/dist/internal/binding-token-mint.js +174 -0
  46. package/dist/internal/change-recommendation.d.ts +6 -0
  47. package/dist/internal/change-recommendation.js +113 -0
  48. package/dist/internal/cli-output.d.ts +16 -0
  49. package/dist/internal/cli-output.js +39 -0
  50. package/dist/internal/devx-run.d.ts +129 -0
  51. package/dist/internal/devx-run.js +39 -0
  52. package/dist/internal/extractor/adapters/go/index.d.ts +0 -4
  53. package/dist/internal/extractor/adapters/go/index.js +0 -6
  54. package/dist/internal/extractor/adapters/go/manifest.d.ts +0 -17
  55. package/dist/internal/extractor/adapters/go/manifest.js +19 -25
  56. package/dist/internal/extractor/adapters/go/parser.d.ts +0 -4
  57. package/dist/internal/extractor/adapters/go/parser.js +0 -5
  58. package/dist/internal/extractor/adapters/go/scanner.d.ts +0 -20
  59. package/dist/internal/extractor/adapters/go/scanner.js +0 -43
  60. package/dist/internal/extractor/adapters/python/index.d.ts +0 -3
  61. package/dist/internal/extractor/adapters/python/index.js +0 -4
  62. package/dist/internal/extractor/adapters/python/manifest.d.ts +0 -8
  63. package/dist/internal/extractor/adapters/python/manifest.js +21 -18
  64. package/dist/internal/extractor/adapters/python/parser.d.ts +0 -3
  65. package/dist/internal/extractor/adapters/python/parser.js +0 -4
  66. package/dist/internal/extractor/adapters/python/scanner.d.ts +0 -18
  67. package/dist/internal/extractor/adapters/python/scanner.js +1 -54
  68. package/dist/internal/extractor/adapters/typescript/index.d.ts +0 -7
  69. package/dist/internal/extractor/adapters/typescript/index.js +1 -21
  70. package/dist/internal/extractor/adapters/typescript/manifest.d.ts +0 -14
  71. package/dist/internal/extractor/adapters/typescript/manifest.js +26 -17
  72. package/dist/internal/extractor/adapters/typescript/parser.d.ts +0 -8
  73. package/dist/internal/extractor/adapters/typescript/parser.js +0 -9
  74. package/dist/internal/extractor/adapters/typescript/scanner.d.ts +0 -24
  75. package/dist/internal/extractor/adapters/typescript/scanner.js +0 -43
  76. package/dist/internal/extractor/graph-builder.d.ts +10 -0
  77. package/dist/internal/extractor/graph-builder.js +245 -0
  78. package/dist/internal/extractor/graph-types.d.ts +89 -0
  79. package/dist/internal/extractor/graph-types.js +1 -0
  80. package/dist/internal/extractor/index.d.ts +0 -14
  81. package/dist/internal/extractor/index.js +0 -13
  82. package/dist/internal/extractor/types.d.ts +0 -45
  83. package/dist/internal/extractor/types.js +0 -27
  84. package/dist/internal/extractor/walker.d.ts +0 -18
  85. package/dist/internal/extractor/walker.js +0 -18
  86. package/dist/internal/gate-evaluate.d.ts +50 -0
  87. package/dist/internal/gate-evaluate.js +111 -0
  88. package/dist/internal/gate-policy.d.ts +38 -0
  89. package/dist/internal/gate-policy.js +167 -0
  90. package/dist/internal/git-metadata.d.ts +8 -0
  91. package/dist/internal/git-metadata.js +38 -0
  92. package/dist/internal/git.d.ts +9 -0
  93. package/dist/internal/git.js +45 -0
  94. package/dist/internal/github-linkage-prompt.d.ts +7 -0
  95. package/dist/internal/github-linkage-prompt.js +95 -0
  96. package/dist/internal/ide-setup.d.ts +17 -7
  97. package/dist/internal/ide-setup.js +379 -46
  98. package/dist/internal/inventory-sync.d.ts +74 -0
  99. package/dist/internal/inventory-sync.js +189 -0
  100. package/dist/internal/org-picker.d.ts +11 -0
  101. package/dist/internal/org-picker.js +34 -0
  102. package/dist/internal/orgs.js +0 -1
  103. package/dist/internal/packs/cache.d.ts +8 -0
  104. package/dist/internal/packs/cache.js +60 -0
  105. package/dist/internal/packs/index.d.ts +10 -0
  106. package/dist/internal/packs/index.js +8 -0
  107. package/dist/internal/packs/integrity.d.ts +9 -0
  108. package/dist/internal/packs/integrity.js +24 -0
  109. package/dist/internal/packs/jcs.d.ts +2 -0
  110. package/dist/internal/packs/jcs.js +57 -0
  111. package/dist/internal/packs/local-paths.d.ts +4 -0
  112. package/dist/internal/packs/local-paths.js +26 -0
  113. package/dist/internal/packs/npm-registry-client.d.ts +14 -0
  114. package/dist/internal/packs/npm-registry-client.js +87 -0
  115. package/dist/internal/packs/registry-client.d.ts +9 -0
  116. package/dist/internal/packs/registry-client.js +25 -0
  117. package/dist/internal/packs/resolve.d.ts +8 -0
  118. package/dist/internal/packs/resolve.js +89 -0
  119. package/dist/internal/packs/semver.d.ts +9 -0
  120. package/dist/internal/packs/semver.js +57 -0
  121. package/dist/internal/packs/signing.d.ts +5 -0
  122. package/dist/internal/packs/signing.js +56 -0
  123. package/dist/internal/packs/types.d.ts +77 -0
  124. package/dist/internal/packs/types.js +20 -0
  125. package/dist/internal/project-binding.d.ts +1 -0
  126. package/dist/internal/project-binding.js +16 -11
  127. package/dist/internal/resolve-init-session.d.ts +8 -0
  128. package/dist/internal/resolve-init-session.js +36 -0
  129. package/dist/internal/run-workdir.d.ts +2 -0
  130. package/dist/internal/run-workdir.js +19 -0
  131. package/dist/internal/security-context.d.ts +63 -0
  132. package/dist/internal/security-context.js +50 -0
  133. package/dist/internal/sync-outbox.d.ts +40 -0
  134. package/dist/internal/sync-outbox.js +66 -0
  135. package/dist/internal/target-auth/api-config.d.ts +0 -17
  136. package/dist/internal/target-auth/api-config.js +0 -18
  137. package/dist/internal/target-auth/config.d.ts +0 -12
  138. package/dist/internal/target-auth/config.js +0 -13
  139. package/dist/internal/target-auth/index.d.ts +0 -18
  140. package/dist/internal/target-auth/index.js +0 -19
  141. package/dist/internal/target-auth/keychain.d.ts +0 -18
  142. package/dist/internal/target-auth/keychain.js +3 -23
  143. package/dist/internal/target-auth/types.d.ts +0 -26
  144. package/dist/internal/target-auth/types.js +0 -10
  145. package/dist/internal/target-auth/workos.d.ts +0 -15
  146. package/dist/internal/target-auth/workos.js +9 -18
  147. package/dist/internal/test-scope.d.ts +99 -0
  148. package/dist/internal/test-scope.js +101 -0
  149. package/dist/internal/threatmodel-run-state.d.ts +15 -0
  150. package/dist/internal/threatmodel-run-state.js +43 -0
  151. package/dist/internal/threatmodel-run.d.ts +85 -0
  152. package/dist/internal/threatmodel-run.js +159 -0
  153. package/dist/internal/trust-room-link.d.ts +10 -0
  154. package/dist/internal/trust-room-link.js +15 -0
  155. package/dist/internal/zivis-local-state.d.ts +1 -0
  156. package/dist/internal/zivis-local-state.js +22 -0
  157. package/package.json +21 -5
  158. package/dist/commands/app/index.js.map +0 -1
  159. package/dist/commands/auth/index.js.map +0 -1
  160. package/dist/commands/auth/init.js.map +0 -1
  161. package/dist/commands/auth/login.js.map +0 -1
  162. package/dist/commands/auth/logout.js.map +0 -1
  163. package/dist/commands/auth/sessions.js.map +0 -1
  164. package/dist/commands/auth/status.js.map +0 -1
  165. package/dist/commands/credits/index.js.map +0 -1
  166. package/dist/commands/mcp/index.js.map +0 -1
  167. package/dist/commands/open/index.js.map +0 -1
  168. package/dist/commands/scan/index.js.map +0 -1
  169. package/dist/commands/target/index.js.map +0 -1
  170. package/dist/commands/tm/index.d.ts +0 -2
  171. package/dist/commands/tm/index.js +0 -266
  172. package/dist/commands/tm/index.js.map +0 -1
  173. package/dist/index.js.map +0 -1
  174. package/dist/internal/extractor/adapters/go/index.js.map +0 -1
  175. package/dist/internal/extractor/adapters/go/manifest.js.map +0 -1
  176. package/dist/internal/extractor/adapters/go/parser.js.map +0 -1
  177. package/dist/internal/extractor/adapters/go/scanner.js.map +0 -1
  178. package/dist/internal/extractor/adapters/python/index.js.map +0 -1
  179. package/dist/internal/extractor/adapters/python/manifest.js.map +0 -1
  180. package/dist/internal/extractor/adapters/python/parser.js.map +0 -1
  181. package/dist/internal/extractor/adapters/python/scanner.js.map +0 -1
  182. package/dist/internal/extractor/adapters/typescript/index.js.map +0 -1
  183. package/dist/internal/extractor/adapters/typescript/manifest.js.map +0 -1
  184. package/dist/internal/extractor/adapters/typescript/parser.js.map +0 -1
  185. package/dist/internal/extractor/adapters/typescript/scanner.js.map +0 -1
  186. package/dist/internal/extractor/index.js.map +0 -1
  187. package/dist/internal/extractor/types.js.map +0 -1
  188. package/dist/internal/extractor/walker.js.map +0 -1
  189. package/dist/internal/ide-setup.js.map +0 -1
  190. package/dist/internal/orgs.js.map +0 -1
  191. package/dist/internal/project-binding.js.map +0 -1
  192. package/dist/internal/target-auth/api-config.js.map +0 -1
  193. package/dist/internal/target-auth/config.js.map +0 -1
  194. package/dist/internal/target-auth/index.js.map +0 -1
  195. package/dist/internal/target-auth/keychain.js.map +0 -1
  196. package/dist/internal/target-auth/types.js.map +0 -1
  197. package/dist/internal/target-auth/workos.js.map +0 -1
@@ -0,0 +1,189 @@
1
+ export function validateInventorySyncInput(raw) {
2
+ const errors = [];
3
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
4
+ return { ok: false, errors: ["input must be a JSON object"] };
5
+ }
6
+ const obj = raw;
7
+ const allowedKeys = new Set(["endpoints", "features", "technologyStack"]);
8
+ for (const key of Object.keys(obj)) {
9
+ if (!allowedKeys.has(key))
10
+ errors.push(`unknown field "${key}"`);
11
+ }
12
+ let endpoints;
13
+ if (obj.endpoints !== undefined) {
14
+ if (!Array.isArray(obj.endpoints)) {
15
+ errors.push("endpoints must be an array");
16
+ }
17
+ else {
18
+ endpoints = [];
19
+ obj.endpoints.forEach((ep, i) => {
20
+ if (typeof ep !== "object" || ep === null) {
21
+ errors.push(`endpoints[${i}] must be an object`);
22
+ return;
23
+ }
24
+ const e = ep;
25
+ if (typeof e.path !== "string" || !e.path) {
26
+ errors.push(`endpoints[${i}].path must be a non-empty string`);
27
+ }
28
+ if (typeof e.method !== "string" || !e.method) {
29
+ errors.push(`endpoints[${i}].method must be a non-empty string`);
30
+ }
31
+ if (e.tags !== undefined && (!Array.isArray(e.tags) || !e.tags.every((t) => typeof t === "string"))) {
32
+ errors.push(`endpoints[${i}].tags must be an array of strings`);
33
+ }
34
+ if (errors.length === 0 || (typeof e.path === "string" && typeof e.method === "string")) {
35
+ endpoints.push({
36
+ path: e.path,
37
+ method: e.method,
38
+ summary: typeof e.summary === "string" ? e.summary : undefined,
39
+ description: typeof e.description === "string" ? e.description : undefined,
40
+ authRequired: typeof e.authRequired === "boolean" ? e.authRequired : undefined,
41
+ tags: Array.isArray(e.tags) ? e.tags : undefined,
42
+ deprecated: typeof e.deprecated === "boolean" ? e.deprecated : undefined,
43
+ });
44
+ }
45
+ });
46
+ }
47
+ }
48
+ const validCriticalities = new Set(["critical", "high", "medium", "low"]);
49
+ let features;
50
+ if (obj.features !== undefined) {
51
+ if (!Array.isArray(obj.features)) {
52
+ errors.push("features must be an array");
53
+ }
54
+ else {
55
+ features = [];
56
+ obj.features.forEach((f, i) => {
57
+ if (typeof f !== "object" || f === null) {
58
+ errors.push(`features[${i}] must be an object`);
59
+ return;
60
+ }
61
+ const feat = f;
62
+ if (typeof feat.name !== "string" || !feat.name) {
63
+ errors.push(`features[${i}].name must be a non-empty string`);
64
+ }
65
+ if (feat.criticality !== undefined && !validCriticalities.has(feat.criticality)) {
66
+ errors.push(`features[${i}].criticality must be one of: ${[...validCriticalities].join(", ")}`);
67
+ }
68
+ if (feat.endpoints !== undefined && (!Array.isArray(feat.endpoints) || !feat.endpoints.every((p) => typeof p === "string"))) {
69
+ errors.push(`features[${i}].endpoints must be an array of strings`);
70
+ }
71
+ if (typeof feat.name === "string" && feat.name) {
72
+ features.push({
73
+ name: feat.name,
74
+ description: typeof feat.description === "string" ? feat.description : undefined,
75
+ criticality: validCriticalities.has(feat.criticality)
76
+ ? feat.criticality
77
+ : undefined,
78
+ endpoints: Array.isArray(feat.endpoints) ? feat.endpoints : undefined,
79
+ tags: Array.isArray(feat.tags) ? feat.tags : undefined,
80
+ });
81
+ }
82
+ });
83
+ }
84
+ }
85
+ let technologyStack;
86
+ if (obj.technologyStack !== undefined) {
87
+ if (!Array.isArray(obj.technologyStack) || !obj.technologyStack.every((t) => typeof t === "string")) {
88
+ errors.push("technologyStack must be an array of strings");
89
+ }
90
+ else {
91
+ technologyStack = obj.technologyStack;
92
+ }
93
+ }
94
+ if (errors.length > 0)
95
+ return { ok: false, errors };
96
+ if (endpoints === undefined && features === undefined && technologyStack === undefined) {
97
+ return { ok: false, errors: ["input must include at least one of: endpoints, features, technologyStack"] };
98
+ }
99
+ const secretHits = scanForSecrets({ endpoints, features, technologyStack });
100
+ if (secretHits.length > 0) {
101
+ return {
102
+ ok: false,
103
+ errors: secretHits.map((hit) => `${hit} looks like it contains a credential/token — refusing to sync`),
104
+ };
105
+ }
106
+ return { ok: true, value: { endpoints, features, technologyStack } };
107
+ }
108
+ const SECRET_PATTERNS = [
109
+ /sk-[a-zA-Z0-9]{20,}/,
110
+ /ghp_[a-zA-Z0-9]{30,}/,
111
+ /gh[oprs]_[a-zA-Z0-9]{30,}/,
112
+ /AKIA[0-9A-Z]{16}/,
113
+ /-----BEGIN[ A-Z]*PRIVATE KEY-----/,
114
+ /\bBearer\s+[a-zA-Z0-9._-]{20,}/i,
115
+ /\bAuthorization\s*:\s*\S+/i,
116
+ ];
117
+ function looksLikeSecret(value) {
118
+ return SECRET_PATTERNS.some((pattern) => pattern.test(value));
119
+ }
120
+ function scanForSecrets(input) {
121
+ const hits = [];
122
+ const check = (label, value) => {
123
+ if (value && looksLikeSecret(value))
124
+ hits.push(label);
125
+ };
126
+ (input.endpoints ?? []).forEach((ep, i) => {
127
+ check(`endpoints[${i}].summary`, ep.summary);
128
+ check(`endpoints[${i}].description`, ep.description);
129
+ (ep.tags ?? []).forEach((t, j) => check(`endpoints[${i}].tags[${j}]`, t));
130
+ });
131
+ (input.features ?? []).forEach((f, i) => {
132
+ check(`features[${i}].name`, f.name);
133
+ check(`features[${i}].description`, f.description);
134
+ (f.tags ?? []).forEach((t, j) => check(`features[${i}].tags[${j}]`, t));
135
+ });
136
+ (input.technologyStack ?? []).forEach((t, i) => check(`technologyStack[${i}]`, t));
137
+ return hits;
138
+ }
139
+ function toEndpointReconcilePayload(input, provenance) {
140
+ return {
141
+ endpoints: input.endpoints,
142
+ gitCommitSha: provenance.gitCommitSha ?? undefined,
143
+ source: provenance.source,
144
+ };
145
+ }
146
+ function toFeatureReconcilePayload(input, provenance) {
147
+ return {
148
+ features: (input.features ?? []).map((f) => ({
149
+ name: f.name,
150
+ description: f.description,
151
+ criticality: f.criticality ?? "medium",
152
+ endpoint_count: f.endpoints?.length ?? 0,
153
+ endpoints: f.endpoints ?? [],
154
+ tags: f.tags ?? [],
155
+ })),
156
+ endpoints: [],
157
+ integrations: [],
158
+ frameworks: [],
159
+ repos_scanned: 1,
160
+ files_scanned: 0,
161
+ gitCommitSha: provenance.gitCommitSha ?? undefined,
162
+ source: provenance.source,
163
+ };
164
+ }
165
+ function toTechStackPayload(input) {
166
+ return { technologies: input.technologyStack };
167
+ }
168
+ export async function syncApplicationInventory(params) {
169
+ const { client, applicationId, input, provenance } = params;
170
+ const summary = {};
171
+ if (input.technologyStack !== undefined) {
172
+ const result = await client.put(`/api/rt/applications/${encodeURIComponent(applicationId)}/technology-stack`, toTechStackPayload(input));
173
+ summary.technologyStack = { count: result.techStack.length };
174
+ }
175
+ if (input.endpoints !== undefined) {
176
+ summary.endpoints = await client.post(`/api/rt/applications/${encodeURIComponent(applicationId)}/endpoints/reconcile`, toEndpointReconcilePayload(input, provenance));
177
+ }
178
+ if (input.features !== undefined) {
179
+ summary.features = await client.post(`/api/features/applications/${encodeURIComponent(applicationId)}/reconcile`, toFeatureReconcilePayload(input, provenance));
180
+ }
181
+ if (provenance.repoFullName && provenance.gitCommitSha) {
182
+ summary.repo = await client.put(`/api/rt/applications/${encodeURIComponent(applicationId)}/repos/sync`, {
183
+ repoFullName: provenance.repoFullName,
184
+ latestCommitSha: provenance.gitCommitSha,
185
+ defaultBranch: provenance.defaultBranch ?? undefined,
186
+ });
187
+ }
188
+ return summary;
189
+ }
@@ -0,0 +1,11 @@
1
+ import type { ZivisConfig } from "@zivis/mcp/types";
2
+ import { type OrgInfo } from "./orgs.js";
3
+ export interface PickOrgOpts {
4
+ currentOrgId?: string;
5
+ header?: string;
6
+ promptText?: string;
7
+ }
8
+ export interface PickedOrg extends OrgInfo {
9
+ workosOrgId: string;
10
+ }
11
+ export declare function pickOrg(config: ZivisConfig, identityAccessToken: string, opts?: PickOrgOpts): Promise<PickedOrg>;
@@ -0,0 +1,34 @@
1
+ import * as readline from "node:readline/promises";
2
+ import { fetchUserOrgs } from "./orgs.js";
3
+ export async function pickOrg(config, identityAccessToken, opts = {}) {
4
+ const all = await fetchUserOrgs(config, identityAccessToken);
5
+ const eligible = all.filter((o) => typeof o.workosOrgId === "string" && o.workosOrgId.length > 0);
6
+ if (eligible.length === 0) {
7
+ throw new Error("No active organizations available for this account. " +
8
+ "Ask an org admin to invite you, or contact support.");
9
+ }
10
+ if (eligible.length === 1) {
11
+ const only = eligible[0];
12
+ console.log(`One organization available: ${only.name}`);
13
+ return only;
14
+ }
15
+ console.log("");
16
+ console.log(opts.header ?? "Your organizations:");
17
+ eligible.forEach((o, i) => {
18
+ const marker = opts.currentOrgId && o.workosOrgId === opts.currentOrgId ? " (current)" : "";
19
+ console.log(` ${i + 1}. ${o.name}${marker}`);
20
+ });
21
+ const promptText = opts.promptText ?? `\nSelect org [1-${eligible.length}]: `;
22
+ const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
23
+ try {
24
+ const answer = (await rl.question(promptText)).trim();
25
+ const idx = Number.parseInt(answer, 10) - 1;
26
+ if (Number.isNaN(idx) || idx < 0 || idx >= eligible.length) {
27
+ throw new Error("Invalid selection.");
28
+ }
29
+ return eligible[idx];
30
+ }
31
+ finally {
32
+ rl.close();
33
+ }
34
+ }
@@ -28,4 +28,3 @@ export function extractEmail(idToken) {
28
28
  return "unknown";
29
29
  }
30
30
  }
31
- //# sourceMappingURL=orgs.js.map
@@ -0,0 +1,8 @@
1
+ import type { PackManifestV1, PackType } from "./types.js";
2
+ export declare function writeVerifiedPackToCache(packType: PackType, version: string, manifest: PackManifestV1, contentByPath: Map<string, string>): Promise<string>;
3
+ export declare function loadVerifiedFromCache(packType: PackType, version: string): Promise<{
4
+ manifest: PackManifestV1;
5
+ contentByPath: Map<string, string>;
6
+ cacheDir: string;
7
+ } | null>;
8
+ export declare function listCachedVersions(packType: PackType): Promise<string[]>;
@@ -0,0 +1,60 @@
1
+ import * as fs from "node:fs/promises";
2
+ import * as path from "node:path";
3
+ import { packTypeDir, packVersionDir } from "./local-paths.js";
4
+ import { verifyPack } from "./integrity.js";
5
+ function contentPaths(manifest) {
6
+ return [manifest.base.path, ...(manifest.scopes ?? []).map((s) => s.path)];
7
+ }
8
+ export async function writeVerifiedPackToCache(packType, version, manifest, contentByPath) {
9
+ const dir = packVersionDir(packType, version);
10
+ await fs.mkdir(dir, { recursive: true });
11
+ await atomicWrite(path.join(dir, "manifest.json"), JSON.stringify(manifest, null, 2));
12
+ for (const relPath of contentPaths(manifest)) {
13
+ const content = contentByPath.get(relPath);
14
+ if (content === undefined)
15
+ continue;
16
+ const target = path.join(dir, relPath);
17
+ await fs.mkdir(path.dirname(target), { recursive: true });
18
+ await atomicWrite(target, content);
19
+ }
20
+ return dir;
21
+ }
22
+ async function atomicWrite(target, content) {
23
+ const tmp = `${target}.tmp-${process.pid}-${Date.now()}`;
24
+ await fs.writeFile(tmp, content, "utf-8");
25
+ await fs.rename(tmp, target);
26
+ }
27
+ export async function loadVerifiedFromCache(packType, version) {
28
+ const dir = packVersionDir(packType, version);
29
+ let manifest;
30
+ try {
31
+ const raw = await fs.readFile(path.join(dir, "manifest.json"), "utf-8");
32
+ manifest = JSON.parse(raw);
33
+ }
34
+ catch {
35
+ return null;
36
+ }
37
+ const contentByPath = new Map();
38
+ for (const relPath of contentPaths(manifest)) {
39
+ try {
40
+ contentByPath.set(relPath, await fs.readFile(path.join(dir, relPath), "utf-8"));
41
+ }
42
+ catch {
43
+ return null;
44
+ }
45
+ }
46
+ const result = verifyPack(manifest, contentByPath);
47
+ if (!result.valid) {
48
+ return null;
49
+ }
50
+ return { manifest, contentByPath, cacheDir: dir };
51
+ }
52
+ export async function listCachedVersions(packType) {
53
+ try {
54
+ const entries = await fs.readdir(packTypeDir(packType), { withFileTypes: true });
55
+ return entries.filter((e) => e.isDirectory()).map((e) => e.name);
56
+ }
57
+ catch {
58
+ return [];
59
+ }
60
+ }
@@ -0,0 +1,10 @@
1
+ export type { PackType, PackContentEntry, PackScopeEntry, PackTestEntry, PackSignature, PackManifestV1, PackVersionSummary, PackVersionListing, PackSource, ResolvedScope, ResolvedPack, PackRegistryClient, } from "./types.js";
2
+ export { PackIntegrityError, PackUnavailableError, UnknownScopeError } from "./types.js";
3
+ export { resolveCompatiblePack, resolveScope, listAvailableScopes } from "./resolve.js";
4
+ export type { ResolveOptions } from "./resolve.js";
5
+ export { LocalPackRegistryClient, registryDirOverride, } from "./registry-client.js";
6
+ export { NpmPackRegistryClient } from "./npm-registry-client.js";
7
+ export { userCacheRoot, packsRoot, packTypeDir, packVersionDir } from "./local-paths.js";
8
+ export { verifyManifestSignature, manifestSigningPayload, PRODUCTION_PACK_SIGNING_KEY_ID } from "./signing.js";
9
+ export { sha256Hex, verifyContentHash, verifyPack } from "./integrity.js";
10
+ export { compareVersions, satisfiesMinVersion, parseVersion } from "./semver.js";
@@ -0,0 +1,8 @@
1
+ export { PackIntegrityError, PackUnavailableError, UnknownScopeError } from "./types.js";
2
+ export { resolveCompatiblePack, resolveScope, listAvailableScopes } from "./resolve.js";
3
+ export { LocalPackRegistryClient, registryDirOverride, } from "./registry-client.js";
4
+ export { NpmPackRegistryClient } from "./npm-registry-client.js";
5
+ export { userCacheRoot, packsRoot, packTypeDir, packVersionDir } from "./local-paths.js";
6
+ export { verifyManifestSignature, manifestSigningPayload, PRODUCTION_PACK_SIGNING_KEY_ID } from "./signing.js";
7
+ export { sha256Hex, verifyContentHash, verifyPack } from "./integrity.js";
8
+ export { compareVersions, satisfiesMinVersion, parseVersion } from "./semver.js";
@@ -0,0 +1,9 @@
1
+ import type { PackContentEntry, PackManifestV1 } from "./types.js";
2
+ export declare function sha256Hex(content: string): string;
3
+ export declare function verifyContentHash(content: string, entry: PackContentEntry): boolean;
4
+ export declare function verifyPack(manifest: PackManifestV1, contentByPath: Map<string, string>): {
5
+ valid: true;
6
+ } | {
7
+ valid: false;
8
+ reason: string;
9
+ };
@@ -0,0 +1,24 @@
1
+ import * as crypto from "node:crypto";
2
+ import { verifyManifestSignature } from "./signing.js";
3
+ export function sha256Hex(content) {
4
+ return crypto.createHash("sha256").update(content, "utf8").digest("hex");
5
+ }
6
+ export function verifyContentHash(content, entry) {
7
+ return sha256Hex(content) === entry.sha256;
8
+ }
9
+ export function verifyPack(manifest, contentByPath) {
10
+ if (!verifyManifestSignature(manifest)) {
11
+ return { valid: false, reason: "manifest signature verification failed" };
12
+ }
13
+ const entries = [manifest.base, ...(manifest.scopes ?? [])];
14
+ for (const entry of entries) {
15
+ const content = contentByPath.get(entry.path);
16
+ if (content === undefined) {
17
+ return { valid: false, reason: `missing content for "${entry.path}"` };
18
+ }
19
+ if (!verifyContentHash(content, entry)) {
20
+ return { valid: false, reason: `content hash mismatch for "${entry.path}"` };
21
+ }
22
+ }
23
+ return { valid: true };
24
+ }
@@ -0,0 +1,2 @@
1
+ export declare function canonicalize(value: unknown): string;
2
+ export declare function jcsSha256Hex(value: unknown): string;
@@ -0,0 +1,57 @@
1
+ import crypto from "node:crypto";
2
+ function compareUtf16(a, b) {
3
+ return a < b ? -1 : a > b ? 1 : 0;
4
+ }
5
+ function serialize(value) {
6
+ if (value !== null &&
7
+ typeof value === "object" &&
8
+ typeof value.toJSON === "function") {
9
+ value = value.toJSON();
10
+ }
11
+ if (value === null)
12
+ return "null";
13
+ switch (typeof value) {
14
+ case "boolean":
15
+ return value ? "true" : "false";
16
+ case "number":
17
+ if (!Number.isFinite(value)) {
18
+ throw new TypeError(`JCS: non-finite number is not representable in JSON: ${value}`);
19
+ }
20
+ return JSON.stringify(value);
21
+ case "string":
22
+ return JSON.stringify(value);
23
+ case "bigint":
24
+ throw new TypeError("JCS: BigInt values are not representable in JSON");
25
+ case "undefined":
26
+ case "function":
27
+ case "symbol":
28
+ return undefined;
29
+ case "object": {
30
+ if (Array.isArray(value)) {
31
+ const items = value.map((item) => serialize(item) ?? "null");
32
+ return `[${items.join(",")}]`;
33
+ }
34
+ const keys = Object.keys(value).sort(compareUtf16);
35
+ const parts = [];
36
+ for (const key of keys) {
37
+ const serialized = serialize(value[key]);
38
+ if (serialized !== undefined) {
39
+ parts.push(`${JSON.stringify(key)}:${serialized}`);
40
+ }
41
+ }
42
+ return `{${parts.join(",")}}`;
43
+ }
44
+ default:
45
+ throw new TypeError(`JCS: cannot canonicalize value of type ${typeof value}`);
46
+ }
47
+ }
48
+ export function canonicalize(value) {
49
+ const result = serialize(value);
50
+ if (result === undefined) {
51
+ throw new TypeError("JCS: top-level value has no JSON representation");
52
+ }
53
+ return result;
54
+ }
55
+ export function jcsSha256Hex(value) {
56
+ return crypto.createHash("sha256").update(canonicalize(value), "utf8").digest("hex");
57
+ }
@@ -0,0 +1,4 @@
1
+ export declare function userCacheRoot(): string;
2
+ export declare function packsRoot(): string;
3
+ export declare function packVersionDir(packType: string, version: string): string;
4
+ export declare function packTypeDir(packType: string): string;
@@ -0,0 +1,26 @@
1
+ import * as os from "node:os";
2
+ import * as path from "node:path";
3
+ export function userCacheRoot() {
4
+ if (process.env.ZIVIS_CACHE_ROOT) {
5
+ return process.env.ZIVIS_CACHE_ROOT;
6
+ }
7
+ const home = os.homedir();
8
+ if (process.platform === "darwin") {
9
+ return path.join(home, "Library", "Caches", "zivis");
10
+ }
11
+ if (process.platform === "win32") {
12
+ const localAppData = process.env.LOCALAPPDATA ?? path.join(home, "AppData", "Local");
13
+ return path.join(localAppData, "zivis", "Cache");
14
+ }
15
+ const xdgCacheHome = process.env.XDG_CACHE_HOME ?? path.join(home, ".cache");
16
+ return path.join(xdgCacheHome, "zivis");
17
+ }
18
+ export function packsRoot() {
19
+ return path.join(userCacheRoot(), "packs");
20
+ }
21
+ export function packVersionDir(packType, version) {
22
+ return path.join(packsRoot(), packType, version);
23
+ }
24
+ export function packTypeDir(packType) {
25
+ return path.join(packsRoot(), packType);
26
+ }
@@ -0,0 +1,14 @@
1
+ import type { PackManifestV1, PackRegistryClient, PackType, PackVersionListing } from "./types.js";
2
+ export declare class NpmPackRegistryClient implements PackRegistryClient {
3
+ private readonly registryUrl;
4
+ private readonly packumentCache;
5
+ private readonly extractionCache;
6
+ constructor(registryUrl?: string);
7
+ private packageName;
8
+ private fetchPackument;
9
+ listVersions(packType: PackType): Promise<PackVersionListing>;
10
+ private ensureExtracted;
11
+ private downloadAndExtract;
12
+ getManifest(packType: PackType, version: string): Promise<PackManifestV1>;
13
+ getContent(packType: PackType, version: string, relativePath: string): Promise<string>;
14
+ }
@@ -0,0 +1,87 @@
1
+ import * as fsp from "node:fs/promises";
2
+ import * as os from "node:os";
3
+ import * as path from "node:path";
4
+ import * as zlib from "node:zlib";
5
+ import { pipeline } from "node:stream/promises";
6
+ import { Readable } from "node:stream";
7
+ import * as tar from "tar";
8
+ const NPM_PACKAGE_NAMES = {
9
+ appsec: "@zivis/appsec-pack",
10
+ threatmodel: "@zivis/threatmodel-pack",
11
+ };
12
+ function encodeScopedName(name) {
13
+ return name.replaceAll("/", "%2f");
14
+ }
15
+ export class NpmPackRegistryClient {
16
+ registryUrl;
17
+ packumentCache = new Map();
18
+ extractionCache = new Map();
19
+ constructor(registryUrl = "https://registry.npmjs.org") {
20
+ this.registryUrl = registryUrl;
21
+ }
22
+ packageName(packType) {
23
+ const name = NPM_PACKAGE_NAMES[packType];
24
+ if (!name)
25
+ throw new Error(`No npm package mapping for pack type "${packType}"`);
26
+ return name;
27
+ }
28
+ async fetchPackument(packType) {
29
+ let cached = this.packumentCache.get(packType);
30
+ if (!cached) {
31
+ cached = (async () => {
32
+ const url = `${this.registryUrl}/${encodeScopedName(this.packageName(packType))}`;
33
+ const res = await fetch(url, { headers: { Accept: "application/json" } });
34
+ if (!res.ok) {
35
+ throw new Error(`npm registry returned HTTP ${res.status} for ${url}`);
36
+ }
37
+ return (await res.json());
38
+ })();
39
+ this.packumentCache.set(packType, cached);
40
+ }
41
+ return cached;
42
+ }
43
+ async listVersions(packType) {
44
+ const packument = await this.fetchPackument(packType);
45
+ const versions = Object.values(packument.versions)
46
+ .filter((v) => v.zivisPack?.minCliVersion && v.zivisPack.packType === packType)
47
+ .map((v) => ({
48
+ version: v.version,
49
+ min_cli_version: v.zivisPack.minCliVersion,
50
+ manifest_sha256: v.dist.shasum ?? "",
51
+ published_at: packument.time?.[v.version] ?? "",
52
+ }));
53
+ return { pack_type: packType, versions };
54
+ }
55
+ async ensureExtracted(packType, version) {
56
+ const key = `${packType}@${version}`;
57
+ let cached = this.extractionCache.get(key);
58
+ if (!cached) {
59
+ cached = this.downloadAndExtract(packType, version);
60
+ this.extractionCache.set(key, cached);
61
+ }
62
+ return cached;
63
+ }
64
+ async downloadAndExtract(packType, version) {
65
+ const packument = await this.fetchPackument(packType);
66
+ const versionMeta = packument.versions[version];
67
+ if (!versionMeta) {
68
+ throw new Error(`${this.packageName(packType)}@${version} not found on the npm registry`);
69
+ }
70
+ const tarballRes = await fetch(versionMeta.dist.tarball);
71
+ if (!tarballRes.ok || !tarballRes.body) {
72
+ throw new Error(`Failed to download tarball for ${this.packageName(packType)}@${version}: HTTP ${tarballRes.status}`);
73
+ }
74
+ const extractDir = await fsp.mkdtemp(path.join(os.tmpdir(), "zivis-pack-"));
75
+ await pipeline(Readable.fromWeb(tarballRes.body), zlib.createGunzip(), tar.extract({ cwd: extractDir, strip: 1 }));
76
+ return extractDir;
77
+ }
78
+ async getManifest(packType, version) {
79
+ const extractDir = await this.ensureExtracted(packType, version);
80
+ const raw = await fsp.readFile(path.join(extractDir, "dist", "manifest.json"), "utf-8");
81
+ return JSON.parse(raw);
82
+ }
83
+ async getContent(packType, version, relativePath) {
84
+ const extractDir = await this.ensureExtracted(packType, version);
85
+ return fsp.readFile(path.join(extractDir, "dist", relativePath), "utf-8");
86
+ }
87
+ }
@@ -0,0 +1,9 @@
1
+ import type { PackManifestV1, PackRegistryClient, PackType, PackVersionListing } from "./types.js";
2
+ export declare class LocalPackRegistryClient implements PackRegistryClient {
3
+ private readonly rootDir;
4
+ constructor(rootDir: string);
5
+ listVersions(packType: PackType): Promise<PackVersionListing>;
6
+ getManifest(packType: PackType, version: string): Promise<PackManifestV1>;
7
+ getContent(packType: PackType, version: string, relativePath: string): Promise<string>;
8
+ }
9
+ export declare function registryDirOverride(): string | null;
@@ -0,0 +1,25 @@
1
+ import * as fs from "node:fs/promises";
2
+ import * as path from "node:path";
3
+ export class LocalPackRegistryClient {
4
+ rootDir;
5
+ constructor(rootDir) {
6
+ this.rootDir = rootDir;
7
+ }
8
+ async listVersions(packType) {
9
+ const listingPath = path.join(this.rootDir, packType, "versions.json");
10
+ const raw = await fs.readFile(listingPath, "utf-8");
11
+ return JSON.parse(raw);
12
+ }
13
+ async getManifest(packType, version) {
14
+ const manifestPath = path.join(this.rootDir, packType, version, "manifest.json");
15
+ const raw = await fs.readFile(manifestPath, "utf-8");
16
+ return JSON.parse(raw);
17
+ }
18
+ async getContent(packType, version, relativePath) {
19
+ const contentPath = path.join(this.rootDir, packType, version, relativePath);
20
+ return fs.readFile(contentPath, "utf-8");
21
+ }
22
+ }
23
+ export function registryDirOverride() {
24
+ return process.env.ZIVIS_PACK_REGISTRY_DIR || null;
25
+ }
@@ -0,0 +1,8 @@
1
+ import type { PackRegistryClient, PackScopeEntry, PackType, ResolvedPack, ResolvedScope } from "./types.js";
2
+ export interface ResolveOptions {
3
+ registryClient: PackRegistryClient;
4
+ cliVersion: string;
5
+ }
6
+ export declare function resolveCompatiblePack(packType: PackType, opts: ResolveOptions): Promise<ResolvedPack>;
7
+ export declare function resolveScope(resolved: ResolvedPack, scopeId: string): Promise<ResolvedScope>;
8
+ export declare function listAvailableScopes(resolved: ResolvedPack): Pick<PackScopeEntry, "id" | "label" | "description">[];