@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.
- package/LICENSE +201 -0
- package/README.md +31 -0
- package/dist/commands/app/index.d.ts +0 -8
- package/dist/commands/app/index.js +8 -18
- package/dist/commands/assurance/index.d.ts +35 -0
- package/dist/commands/assurance/index.js +154 -0
- package/dist/commands/auth/bind.d.ts +4 -0
- package/dist/commands/auth/bind.js +97 -0
- package/dist/commands/auth/index.js +21 -2
- package/dist/commands/auth/init.d.ts +2 -0
- package/dist/commands/auth/init.js +248 -96
- package/dist/commands/auth/login.d.ts +2 -0
- package/dist/commands/auth/login.js +136 -18
- package/dist/commands/auth/logout.js +19 -7
- package/dist/commands/auth/sessions.js +7 -4
- package/dist/commands/auth/status.js +33 -12
- package/dist/commands/auth/token.d.ts +4 -0
- package/dist/commands/auth/token.js +73 -0
- package/dist/commands/check/index.d.ts +2 -0
- package/dist/commands/check/index.js +463 -0
- package/dist/commands/credits/index.js +2 -3
- package/dist/commands/gate/index.d.ts +2 -0
- package/dist/commands/gate/index.js +171 -0
- package/dist/commands/inspect/index.d.ts +2 -0
- package/dist/commands/inspect/index.js +136 -0
- package/dist/commands/mcp/index.js +113 -14
- package/dist/commands/open/index.d.ts +0 -8
- package/dist/commands/open/index.js +0 -11
- package/dist/commands/run/index.d.ts +2 -0
- package/dist/commands/run/index.js +206 -0
- package/dist/commands/scan/index.js +3 -8
- package/dist/commands/share/index.d.ts +14 -0
- package/dist/commands/share/index.js +100 -0
- package/dist/commands/sync/index.d.ts +2 -0
- package/dist/commands/sync/index.js +193 -0
- package/dist/commands/target/index.js +0 -3
- package/dist/commands/test/index.d.ts +2 -0
- package/dist/commands/test/index.js +198 -0
- package/dist/commands/threatmodel/index.d.ts +2 -0
- package/dist/commands/threatmodel/index.js +167 -0
- package/dist/index.js +28 -3
- package/dist/internal/application-binding.d.ts +8 -0
- package/dist/internal/application-binding.js +167 -0
- package/dist/internal/binding-token-mint.d.ts +33 -0
- package/dist/internal/binding-token-mint.js +174 -0
- package/dist/internal/change-recommendation.d.ts +6 -0
- package/dist/internal/change-recommendation.js +113 -0
- package/dist/internal/cli-output.d.ts +16 -0
- package/dist/internal/cli-output.js +39 -0
- package/dist/internal/devx-run.d.ts +129 -0
- package/dist/internal/devx-run.js +39 -0
- package/dist/internal/extractor/adapters/go/index.d.ts +0 -4
- package/dist/internal/extractor/adapters/go/index.js +0 -6
- package/dist/internal/extractor/adapters/go/manifest.d.ts +0 -17
- package/dist/internal/extractor/adapters/go/manifest.js +19 -25
- package/dist/internal/extractor/adapters/go/parser.d.ts +0 -4
- package/dist/internal/extractor/adapters/go/parser.js +0 -5
- package/dist/internal/extractor/adapters/go/scanner.d.ts +0 -20
- package/dist/internal/extractor/adapters/go/scanner.js +0 -43
- package/dist/internal/extractor/adapters/python/index.d.ts +0 -3
- package/dist/internal/extractor/adapters/python/index.js +0 -4
- package/dist/internal/extractor/adapters/python/manifest.d.ts +0 -8
- package/dist/internal/extractor/adapters/python/manifest.js +21 -18
- package/dist/internal/extractor/adapters/python/parser.d.ts +0 -3
- package/dist/internal/extractor/adapters/python/parser.js +0 -4
- package/dist/internal/extractor/adapters/python/scanner.d.ts +0 -18
- package/dist/internal/extractor/adapters/python/scanner.js +1 -54
- package/dist/internal/extractor/adapters/typescript/index.d.ts +0 -7
- package/dist/internal/extractor/adapters/typescript/index.js +1 -21
- package/dist/internal/extractor/adapters/typescript/manifest.d.ts +0 -14
- package/dist/internal/extractor/adapters/typescript/manifest.js +26 -17
- package/dist/internal/extractor/adapters/typescript/parser.d.ts +0 -8
- package/dist/internal/extractor/adapters/typescript/parser.js +0 -9
- package/dist/internal/extractor/adapters/typescript/scanner.d.ts +0 -24
- package/dist/internal/extractor/adapters/typescript/scanner.js +0 -43
- package/dist/internal/extractor/graph-builder.d.ts +10 -0
- package/dist/internal/extractor/graph-builder.js +245 -0
- package/dist/internal/extractor/graph-types.d.ts +89 -0
- package/dist/internal/extractor/graph-types.js +1 -0
- package/dist/internal/extractor/index.d.ts +0 -14
- package/dist/internal/extractor/index.js +0 -13
- package/dist/internal/extractor/types.d.ts +0 -45
- package/dist/internal/extractor/types.js +0 -27
- package/dist/internal/extractor/walker.d.ts +0 -18
- package/dist/internal/extractor/walker.js +0 -18
- package/dist/internal/gate-evaluate.d.ts +50 -0
- package/dist/internal/gate-evaluate.js +111 -0
- package/dist/internal/gate-policy.d.ts +38 -0
- package/dist/internal/gate-policy.js +167 -0
- package/dist/internal/git-metadata.d.ts +8 -0
- package/dist/internal/git-metadata.js +38 -0
- package/dist/internal/git.d.ts +9 -0
- package/dist/internal/git.js +45 -0
- package/dist/internal/github-linkage-prompt.d.ts +7 -0
- package/dist/internal/github-linkage-prompt.js +95 -0
- package/dist/internal/ide-setup.d.ts +17 -7
- package/dist/internal/ide-setup.js +379 -46
- package/dist/internal/inventory-sync.d.ts +74 -0
- package/dist/internal/inventory-sync.js +189 -0
- package/dist/internal/org-picker.d.ts +11 -0
- package/dist/internal/org-picker.js +34 -0
- package/dist/internal/orgs.js +0 -1
- package/dist/internal/packs/cache.d.ts +8 -0
- package/dist/internal/packs/cache.js +60 -0
- package/dist/internal/packs/index.d.ts +10 -0
- package/dist/internal/packs/index.js +8 -0
- package/dist/internal/packs/integrity.d.ts +9 -0
- package/dist/internal/packs/integrity.js +24 -0
- package/dist/internal/packs/jcs.d.ts +2 -0
- package/dist/internal/packs/jcs.js +57 -0
- package/dist/internal/packs/local-paths.d.ts +4 -0
- package/dist/internal/packs/local-paths.js +26 -0
- package/dist/internal/packs/npm-registry-client.d.ts +14 -0
- package/dist/internal/packs/npm-registry-client.js +87 -0
- package/dist/internal/packs/registry-client.d.ts +9 -0
- package/dist/internal/packs/registry-client.js +25 -0
- package/dist/internal/packs/resolve.d.ts +8 -0
- package/dist/internal/packs/resolve.js +89 -0
- package/dist/internal/packs/semver.d.ts +9 -0
- package/dist/internal/packs/semver.js +57 -0
- package/dist/internal/packs/signing.d.ts +5 -0
- package/dist/internal/packs/signing.js +56 -0
- package/dist/internal/packs/types.d.ts +77 -0
- package/dist/internal/packs/types.js +20 -0
- package/dist/internal/project-binding.d.ts +1 -0
- package/dist/internal/project-binding.js +16 -11
- package/dist/internal/resolve-init-session.d.ts +8 -0
- package/dist/internal/resolve-init-session.js +36 -0
- package/dist/internal/run-workdir.d.ts +2 -0
- package/dist/internal/run-workdir.js +19 -0
- package/dist/internal/security-context.d.ts +63 -0
- package/dist/internal/security-context.js +50 -0
- package/dist/internal/sync-outbox.d.ts +40 -0
- package/dist/internal/sync-outbox.js +66 -0
- package/dist/internal/target-auth/api-config.d.ts +0 -17
- package/dist/internal/target-auth/api-config.js +0 -18
- package/dist/internal/target-auth/config.d.ts +0 -12
- package/dist/internal/target-auth/config.js +0 -13
- package/dist/internal/target-auth/index.d.ts +0 -18
- package/dist/internal/target-auth/index.js +0 -19
- package/dist/internal/target-auth/keychain.d.ts +0 -18
- package/dist/internal/target-auth/keychain.js +3 -23
- package/dist/internal/target-auth/types.d.ts +0 -26
- package/dist/internal/target-auth/types.js +0 -10
- package/dist/internal/target-auth/workos.d.ts +0 -15
- package/dist/internal/target-auth/workos.js +9 -18
- package/dist/internal/test-scope.d.ts +99 -0
- package/dist/internal/test-scope.js +101 -0
- package/dist/internal/threatmodel-run-state.d.ts +15 -0
- package/dist/internal/threatmodel-run-state.js +43 -0
- package/dist/internal/threatmodel-run.d.ts +85 -0
- package/dist/internal/threatmodel-run.js +159 -0
- package/dist/internal/trust-room-link.d.ts +10 -0
- package/dist/internal/trust-room-link.js +15 -0
- package/dist/internal/zivis-local-state.d.ts +1 -0
- package/dist/internal/zivis-local-state.js +22 -0
- package/package.json +21 -5
- package/dist/commands/app/index.js.map +0 -1
- package/dist/commands/auth/index.js.map +0 -1
- package/dist/commands/auth/init.js.map +0 -1
- package/dist/commands/auth/login.js.map +0 -1
- package/dist/commands/auth/logout.js.map +0 -1
- package/dist/commands/auth/sessions.js.map +0 -1
- package/dist/commands/auth/status.js.map +0 -1
- package/dist/commands/credits/index.js.map +0 -1
- package/dist/commands/mcp/index.js.map +0 -1
- package/dist/commands/open/index.js.map +0 -1
- package/dist/commands/scan/index.js.map +0 -1
- package/dist/commands/target/index.js.map +0 -1
- package/dist/commands/tm/index.d.ts +0 -2
- package/dist/commands/tm/index.js +0 -266
- package/dist/commands/tm/index.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/internal/extractor/adapters/go/index.js.map +0 -1
- package/dist/internal/extractor/adapters/go/manifest.js.map +0 -1
- package/dist/internal/extractor/adapters/go/parser.js.map +0 -1
- package/dist/internal/extractor/adapters/go/scanner.js.map +0 -1
- package/dist/internal/extractor/adapters/python/index.js.map +0 -1
- package/dist/internal/extractor/adapters/python/manifest.js.map +0 -1
- package/dist/internal/extractor/adapters/python/parser.js.map +0 -1
- package/dist/internal/extractor/adapters/python/scanner.js.map +0 -1
- package/dist/internal/extractor/adapters/typescript/index.js.map +0 -1
- package/dist/internal/extractor/adapters/typescript/manifest.js.map +0 -1
- package/dist/internal/extractor/adapters/typescript/parser.js.map +0 -1
- package/dist/internal/extractor/adapters/typescript/scanner.js.map +0 -1
- package/dist/internal/extractor/index.js.map +0 -1
- package/dist/internal/extractor/types.js.map +0 -1
- package/dist/internal/extractor/walker.js.map +0 -1
- package/dist/internal/ide-setup.js.map +0 -1
- package/dist/internal/orgs.js.map +0 -1
- package/dist/internal/project-binding.js.map +0 -1
- package/dist/internal/target-auth/api-config.js.map +0 -1
- package/dist/internal/target-auth/config.js.map +0 -1
- package/dist/internal/target-auth/index.js.map +0 -1
- package/dist/internal/target-auth/keychain.js.map +0 -1
- package/dist/internal/target-auth/types.js.map +0 -1
- package/dist/internal/target-auth/workos.js.map +0 -1
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import * as os from "node:os";
|
|
2
|
+
import * as crypto from "node:crypto";
|
|
3
|
+
import * as child_process from "node:child_process";
|
|
4
|
+
import { getSessionAccount, keychainClearSession, keychainRead, keychainStore, } from "@zivis/mcp/auth";
|
|
5
|
+
import { decodeJwtPayload } from "@zivis/mcp/auth/jwt";
|
|
6
|
+
import { DEFAULT_CONFIG, credentialStorageFromConfig } from "@zivis/mcp/types";
|
|
7
|
+
import { detectProjectBinding, resolveConfigFromBinding } from "@zivis/mcp/project-binding";
|
|
8
|
+
function getRepoFingerprint(cwd) {
|
|
9
|
+
try {
|
|
10
|
+
const remote = child_process
|
|
11
|
+
.execSync("git remote get-url origin 2>/dev/null", { cwd, encoding: "utf8" })
|
|
12
|
+
.trim();
|
|
13
|
+
if (remote) {
|
|
14
|
+
return crypto.createHash("sha256").update(remote).digest("hex").slice(0, 12);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
}
|
|
19
|
+
return crypto.createHash("sha256").update(cwd).digest("hex").slice(0, 12);
|
|
20
|
+
}
|
|
21
|
+
export async function mintLocalProjectBindingToken(params) {
|
|
22
|
+
const { config, session, accessToken, cwd } = params;
|
|
23
|
+
const credentialStorage = credentialStorageFromConfig(config);
|
|
24
|
+
const repoFingerprint = getRepoFingerprint(cwd);
|
|
25
|
+
const hostname = os.hostname();
|
|
26
|
+
let bindResult;
|
|
27
|
+
try {
|
|
28
|
+
const response = await fetch(`${config.apiBaseUrl}/api/auth/bind`, {
|
|
29
|
+
method: "POST",
|
|
30
|
+
headers: {
|
|
31
|
+
Authorization: `Bearer ${accessToken}`,
|
|
32
|
+
"Content-Type": "application/json",
|
|
33
|
+
"User-Agent": "@zivis/cli",
|
|
34
|
+
},
|
|
35
|
+
body: JSON.stringify({ repoFingerprint, hostname }),
|
|
36
|
+
});
|
|
37
|
+
if (!response.ok) {
|
|
38
|
+
const body = (await response.json().catch(() => ({})));
|
|
39
|
+
return {
|
|
40
|
+
ok: false,
|
|
41
|
+
error: body.message ?? body.error ?? `HTTP ${response.status}`,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
bindResult = (await response.json());
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
return {
|
|
48
|
+
ok: false,
|
|
49
|
+
error: err instanceof Error ? err.message : String(err),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
const apiKeyAccount = `apikey.${session}`;
|
|
53
|
+
try {
|
|
54
|
+
await keychainStore(apiKeyAccount, bindResult.key, credentialStorage);
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
return {
|
|
58
|
+
ok: false,
|
|
59
|
+
error: err instanceof Error ? err.message : "Failed to store key in OS keychain",
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
ok: true,
|
|
64
|
+
keyName: bindResult.keyName,
|
|
65
|
+
key: bindResult.key,
|
|
66
|
+
orgName: bindResult.orgName,
|
|
67
|
+
workosOrgId: bindResult.workosOrgId,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
export async function tryAutoMintBindingTokenAfterLogin(opts) {
|
|
71
|
+
if (opts.skip) {
|
|
72
|
+
return { ran: false, ok: true };
|
|
73
|
+
}
|
|
74
|
+
const fullConfig = { ...DEFAULT_CONFIG, ...resolveConfigFromBinding(opts.cwd) };
|
|
75
|
+
const claims = decodeJwtPayload(opts.accessToken);
|
|
76
|
+
const jwtOrgId = claims?.org_id;
|
|
77
|
+
if (!jwtOrgId) {
|
|
78
|
+
return {
|
|
79
|
+
ran: false,
|
|
80
|
+
ok: true,
|
|
81
|
+
message: "JWT has no org_id — skipped MCP binding-key mint.",
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
const detected = detectProjectBinding(opts.cwd);
|
|
85
|
+
const bindingOrg = detected?.binding.workosOrgId;
|
|
86
|
+
const bindingSession = detected?.binding.session;
|
|
87
|
+
if (!bindingOrg || !bindingSession) {
|
|
88
|
+
return {
|
|
89
|
+
ran: false,
|
|
90
|
+
ok: true,
|
|
91
|
+
message: "No project binding with workosOrgId — skipped MCP binding-key mint.",
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
if (bindingOrg !== jwtOrgId) {
|
|
95
|
+
const msg = `JWT org does not match project binding — skipped MCP binding-key mint.`;
|
|
96
|
+
if (opts.warnOnFailure) {
|
|
97
|
+
console.log("");
|
|
98
|
+
console.log(` WARNING: ${msg}`);
|
|
99
|
+
}
|
|
100
|
+
return { ran: false, ok: false, message: msg };
|
|
101
|
+
}
|
|
102
|
+
if (bindingSession !== opts.sessionName) {
|
|
103
|
+
const msg = `Session "${opts.sessionName}" does not match binding session "${bindingSession}" — skipped MCP binding-key mint.`;
|
|
104
|
+
if (opts.warnOnFailure) {
|
|
105
|
+
console.log("");
|
|
106
|
+
console.log(` WARNING: ${msg}`);
|
|
107
|
+
}
|
|
108
|
+
return { ran: false, ok: false, message: msg };
|
|
109
|
+
}
|
|
110
|
+
const st = credentialStorageFromConfig(fullConfig);
|
|
111
|
+
const existing = await keychainRead(`apikey.${bindingSession}`, st).catch(() => null);
|
|
112
|
+
if (existing) {
|
|
113
|
+
console.log("");
|
|
114
|
+
console.log(`✓ MCP API key already present for session "${bindingSession}" — skipped mint.`);
|
|
115
|
+
return { ran: false, ok: true };
|
|
116
|
+
}
|
|
117
|
+
console.log("");
|
|
118
|
+
console.log("Minting Local Project Binding Token for MCP (one-time per machine/session)…");
|
|
119
|
+
const result = await mintLocalProjectBindingToken({
|
|
120
|
+
config: fullConfig,
|
|
121
|
+
session: bindingSession,
|
|
122
|
+
accessToken: opts.accessToken,
|
|
123
|
+
cwd: opts.cwd,
|
|
124
|
+
});
|
|
125
|
+
if (!result.ok) {
|
|
126
|
+
const msg = result.error;
|
|
127
|
+
if (opts.warnOnFailure) {
|
|
128
|
+
printMintFailureBanner(msg);
|
|
129
|
+
}
|
|
130
|
+
return { ran: true, ok: false, message: msg };
|
|
131
|
+
}
|
|
132
|
+
console.log(`✓ MCP API key stored (${result.keyName}; org: ${result.orgName})`);
|
|
133
|
+
return { ran: true, ok: true };
|
|
134
|
+
}
|
|
135
|
+
export async function discardOauthAfterLogin(params) {
|
|
136
|
+
const storage = credentialStorageFromConfig(params.config);
|
|
137
|
+
try {
|
|
138
|
+
await keychainClearSession(params.session, storage);
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
}
|
|
142
|
+
const leftover = await keychainRead(getSessionAccount(params.session), storage).catch(() => null);
|
|
143
|
+
if (leftover) {
|
|
144
|
+
const account = getSessionAccount(params.session);
|
|
145
|
+
const service = `${storage.keychainPrefix}.${account}`;
|
|
146
|
+
console.error("");
|
|
147
|
+
console.error(` WARNING: OAuth tokens are still present in the keychain (${service}).`);
|
|
148
|
+
console.error(" Possible cause: keychain access was denied, or older builds left");
|
|
149
|
+
console.error(" duplicate entries that the OS doesn't surface in a single delete.");
|
|
150
|
+
console.error(" Force-clean with:");
|
|
151
|
+
console.error(` while security delete-generic-password -a zivis-mcp -s '${service}' 2>/dev/null; do :; done`);
|
|
152
|
+
console.error("");
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
function printMintFailureBanner(message) {
|
|
156
|
+
const bar = "═".repeat(70);
|
|
157
|
+
console.error("");
|
|
158
|
+
console.error(bar);
|
|
159
|
+
console.error(" ACTION REQUIRED — MCP API KEY WAS NOT MINTED");
|
|
160
|
+
console.error(bar);
|
|
161
|
+
console.error("");
|
|
162
|
+
console.error(` Reason: ${message}`);
|
|
163
|
+
console.error("");
|
|
164
|
+
console.error(" Why this matters:");
|
|
165
|
+
console.error(" The MCP server in your IDE authenticates with an org-scoped");
|
|
166
|
+
console.error(" API key. Without it, every zivis_* tool call will fail with");
|
|
167
|
+
console.error(" \"Not authenticated\" until you mint one.");
|
|
168
|
+
console.error("");
|
|
169
|
+
console.error(" To fix, run from this repo:");
|
|
170
|
+
console.error(" zivis auth bind");
|
|
171
|
+
console.error("");
|
|
172
|
+
console.error(bar);
|
|
173
|
+
console.error("");
|
|
174
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { PackScopeEntry } from "./packs/types.js";
|
|
2
|
+
export interface ChangeRecommendation {
|
|
3
|
+
scopeIds: string[];
|
|
4
|
+
testIds: string[];
|
|
5
|
+
}
|
|
6
|
+
export declare function buildChangeRecommendation(changedFiles: string[], resolvedScopes: Pick<PackScopeEntry, "id" | "label" | "tests">[]): ChangeRecommendation | null;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
function globToRegex(glob) {
|
|
2
|
+
const escaped = glob.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
3
|
+
let pattern = escaped;
|
|
4
|
+
const leadingDoubleStarSlash = pattern.startsWith("**/");
|
|
5
|
+
if (leadingDoubleStarSlash)
|
|
6
|
+
pattern = pattern.slice(3);
|
|
7
|
+
const DOUBLE_STAR_TOKEN = "@@DOUBLESTAR@@";
|
|
8
|
+
pattern = pattern
|
|
9
|
+
.replace(/\*\*/g, DOUBLE_STAR_TOKEN)
|
|
10
|
+
.replace(/\*/g, "[^/]*")
|
|
11
|
+
.replace(new RegExp(DOUBLE_STAR_TOKEN, "g"), ".*");
|
|
12
|
+
if (leadingDoubleStarSlash)
|
|
13
|
+
pattern = `(?:.*/)?${pattern}`;
|
|
14
|
+
return new RegExp(`^${pattern}$`, "i");
|
|
15
|
+
}
|
|
16
|
+
const REGEX_CACHE = new Map();
|
|
17
|
+
function compiledGlob(glob) {
|
|
18
|
+
let rx = REGEX_CACHE.get(glob);
|
|
19
|
+
if (!rx) {
|
|
20
|
+
rx = globToRegex(glob);
|
|
21
|
+
REGEX_CACHE.set(glob, rx);
|
|
22
|
+
}
|
|
23
|
+
return rx;
|
|
24
|
+
}
|
|
25
|
+
function matchesAnyGlob(filePath, globs) {
|
|
26
|
+
const normalized = filePath.replace(/\\/g, "/");
|
|
27
|
+
return globs.some((glob) => compiledGlob(glob).test(normalized));
|
|
28
|
+
}
|
|
29
|
+
const CHANGE_SCOPE_HEURISTICS = [
|
|
30
|
+
{ scopeId: "auth", globs: ["**/*password*", "**/*passwd*", "**/*bcrypt*", "**/*argon2*", "**/*scrypt*", "**/*hash*"], testIds: ["AUTH-001"] },
|
|
31
|
+
{ scopeId: "auth", globs: ["**/*login*", "**/*signin*", "**/*sign-in*", "**/*credential*"], testIds: ["AUTH-003"] },
|
|
32
|
+
{ scopeId: "auth", globs: ["**/*mfa*", "**/*otp*", "**/*two-factor*", "**/*2fa*", "**/*step-up*"], testIds: ["AUTH-004"] },
|
|
33
|
+
{ scopeId: "auth", globs: ["**/*reset-password*", "**/*forgot-password*", "**/*password-reset*", "**/*account-recovery*"], testIds: ["AUTH-005"] },
|
|
34
|
+
{ scopeId: "auth", globs: ["**/*rate-limit*", "**/*throttle*", "**/*lockout*"], testIds: ["AUTH-006"] },
|
|
35
|
+
{ scopeId: "auth", globs: ["**/auth/**", "**/authentication/**", "**/*oauth*", "**/*sso*", "**/*saml*"] },
|
|
36
|
+
{ scopeId: "authorization", globs: ["**/*rbac*", "**/*permission*", "**/*acl*"], testIds: ["AUTHZ-001", "AUTHZ-002"] },
|
|
37
|
+
{ scopeId: "authorization", globs: ["**/*tenant*", "**/*multi-tenant*", "**/*org-scope*"], testIds: ["AUTHZ-003"] },
|
|
38
|
+
{ scopeId: "authorization", globs: ["**/authz/**", "**/authorization/**", "**/*entitlement*", "**/*role*"] },
|
|
39
|
+
{ scopeId: "session", globs: ["**/*jwt*"], testIds: ["SESSION-006"] },
|
|
40
|
+
{ scopeId: "session", globs: ["**/*csrf*"], testIds: ["SESSION-005"] },
|
|
41
|
+
{ scopeId: "session", globs: ["**/*cookie*"], testIds: ["SESSION-004"] },
|
|
42
|
+
{ scopeId: "session", globs: ["**/session/**", "**/*sessions*", "**/*token*"] },
|
|
43
|
+
{ scopeId: "api", globs: ["**/routes/**", "**/route/**", "**/controllers/**", "**/api/**", "**/*.graphql", "**/openapi*.*", "**/swagger*.*"] },
|
|
44
|
+
{ scopeId: "input", globs: ["**/*sql*", "**/*query-builder*", "**/*raw-query*"], testIds: ["INPUT-001"] },
|
|
45
|
+
{ scopeId: "input", globs: ["**/*template*"], testIds: ["INPUT-003"] },
|
|
46
|
+
{ scopeId: "input", globs: ["**/*path-traversal*", "**/*path-join*"], testIds: ["INPUT-004"] },
|
|
47
|
+
{ scopeId: "input", globs: ["**/*deserializ*", "**/*unmarshal*", "**/*pickle*"], testIds: ["INPUT-005"] },
|
|
48
|
+
{ scopeId: "input", globs: ["**/*sanitiz*", "**/*validat*", "**/*escape*"] },
|
|
49
|
+
{ scopeId: "data", globs: ["**/*encrypt*", "**/*crypto*", "**/*cipher*"], testIds: ["DATA-003", "DATA-005"] },
|
|
50
|
+
{ scopeId: "data", globs: ["**/models/**", "**/entities/**", "**/schema.prisma", "**/*.sql"] },
|
|
51
|
+
{ scopeId: "secrets", globs: ["**/*.env*", "**/*secret*", "**/*credentials*"], testIds: ["SECRETS-001"] },
|
|
52
|
+
{ scopeId: "secrets", globs: ["**/.github/workflows/**"], testIds: ["SECRETS-006"] },
|
|
53
|
+
{
|
|
54
|
+
scopeId: "dependencies",
|
|
55
|
+
globs: [
|
|
56
|
+
"**/package.json",
|
|
57
|
+
"**/package-lock.json",
|
|
58
|
+
"**/pnpm-lock.yaml",
|
|
59
|
+
"**/yarn.lock",
|
|
60
|
+
"**/go.mod",
|
|
61
|
+
"**/go.sum",
|
|
62
|
+
"**/requirements*.txt",
|
|
63
|
+
"**/Pipfile*",
|
|
64
|
+
"**/Gemfile*",
|
|
65
|
+
"**/*.csproj",
|
|
66
|
+
],
|
|
67
|
+
},
|
|
68
|
+
{ scopeId: "ssrf", globs: ["**/*webhook*"], testIds: ["SSRF-001"] },
|
|
69
|
+
{ scopeId: "ssrf", globs: ["**/*fetch*", "**/*http-client*", "**/*outbound*", "**/*proxy*"] },
|
|
70
|
+
{ scopeId: "uploads", globs: ["**/*content-type*", "**/*mime-type*", "**/*filetype*"], testIds: ["UPLOAD-001"] },
|
|
71
|
+
{ scopeId: "uploads", globs: ["**/*blob-storage*", "**/*s3*", "**/*azurite*", "**/*storage-service*"], testIds: ["UPLOAD-002"] },
|
|
72
|
+
{ scopeId: "uploads", globs: ["**/*filename*"], testIds: ["UPLOAD-003"] },
|
|
73
|
+
{ scopeId: "uploads", globs: ["**/*svg*"], testIds: ["UPLOAD-006"] },
|
|
74
|
+
{ scopeId: "uploads", globs: ["**/*upload*", "**/*multipart*", "**/*attachment*"] },
|
|
75
|
+
{ scopeId: "frontend", globs: ["**/*.tsx", "**/*.jsx", "**/components/**", "**/*.vue", "**/*.svelte"] },
|
|
76
|
+
{ scopeId: "infra", globs: ["**/*.tf", "**/Dockerfile*", "**/docker-compose*.yml", "**/docker-compose*.yaml", "**/k8s/**", "**/helm/**", "**/.github/workflows/**"] },
|
|
77
|
+
{ scopeId: "ai", globs: ["**/*llm*", "**/*prompt*", "**/*agent*", "**/*anthropic*", "**/*openai*", "**/*rag*", "**/*embedding*"] },
|
|
78
|
+
];
|
|
79
|
+
export function buildChangeRecommendation(changedFiles, resolvedScopes) {
|
|
80
|
+
const scopesById = new Map(resolvedScopes.map((s) => [s.id, s]));
|
|
81
|
+
const matchedScopeIds = new Set();
|
|
82
|
+
const matchedTestIdsByScope = new Map();
|
|
83
|
+
for (const rule of CHANGE_SCOPE_HEURISTICS) {
|
|
84
|
+
if (!scopesById.has(rule.scopeId))
|
|
85
|
+
continue;
|
|
86
|
+
if (!changedFiles.some((file) => matchesAnyGlob(file, rule.globs)))
|
|
87
|
+
continue;
|
|
88
|
+
matchedScopeIds.add(rule.scopeId);
|
|
89
|
+
if (rule.testIds?.length) {
|
|
90
|
+
const set = matchedTestIdsByScope.get(rule.scopeId) ?? new Set();
|
|
91
|
+
for (const id of rule.testIds)
|
|
92
|
+
set.add(id);
|
|
93
|
+
matchedTestIdsByScope.set(rule.scopeId, set);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (matchedScopeIds.size === 0)
|
|
97
|
+
return null;
|
|
98
|
+
const scopeIds = resolvedScopes.filter((s) => matchedScopeIds.has(s.id)).map((s) => s.id);
|
|
99
|
+
const testIds = [];
|
|
100
|
+
for (const scopeId of scopeIds) {
|
|
101
|
+
const candidateIds = matchedTestIdsByScope.get(scopeId);
|
|
102
|
+
if (!candidateIds)
|
|
103
|
+
continue;
|
|
104
|
+
const tests = scopesById.get(scopeId)?.tests ?? [];
|
|
105
|
+
for (const test of tests) {
|
|
106
|
+
if (test.deprecated)
|
|
107
|
+
continue;
|
|
108
|
+
if (candidateIds.has(test.id))
|
|
109
|
+
testIds.push(test.id);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return { scopeIds, testIds };
|
|
113
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export declare const SCHEMA_VERSION = "1";
|
|
2
|
+
export declare const EXIT: {
|
|
3
|
+
readonly OK: 0;
|
|
4
|
+
readonly POLICY_FAIL: 1;
|
|
5
|
+
readonly INVALID_OR_INDETERMINATE: 2;
|
|
6
|
+
readonly AUTH_FAILURE: 3;
|
|
7
|
+
readonly CLOUD_UNAVAILABLE: 4;
|
|
8
|
+
};
|
|
9
|
+
export declare function jsonEnvelope<T extends Record<string, unknown>>(command: string, data: T): {
|
|
10
|
+
schema_version: string;
|
|
11
|
+
command: string;
|
|
12
|
+
} & T;
|
|
13
|
+
export declare function printJson(value: unknown): void;
|
|
14
|
+
export declare function exitCodeForError(err: unknown): number;
|
|
15
|
+
export declare function errorMessage(err: unknown): string;
|
|
16
|
+
export declare function failCommand(command: string, err: unknown, json: boolean): never;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { ApiError } from "@zivis/mcp/api-client";
|
|
2
|
+
export const SCHEMA_VERSION = "1";
|
|
3
|
+
export const EXIT = {
|
|
4
|
+
OK: 0,
|
|
5
|
+
POLICY_FAIL: 1,
|
|
6
|
+
INVALID_OR_INDETERMINATE: 2,
|
|
7
|
+
AUTH_FAILURE: 3,
|
|
8
|
+
CLOUD_UNAVAILABLE: 4,
|
|
9
|
+
};
|
|
10
|
+
export function jsonEnvelope(command, data) {
|
|
11
|
+
return { schema_version: SCHEMA_VERSION, command, ...data };
|
|
12
|
+
}
|
|
13
|
+
export function printJson(value) {
|
|
14
|
+
process.stdout.write(JSON.stringify(value, null, 2) + "\n");
|
|
15
|
+
}
|
|
16
|
+
export function exitCodeForError(err) {
|
|
17
|
+
if (err instanceof ApiError) {
|
|
18
|
+
if (err.code === "NETWORK_ERROR")
|
|
19
|
+
return EXIT.CLOUD_UNAVAILABLE;
|
|
20
|
+
if (err.code === "AUTH_REQUIRED" || err.status === 401 || err.status === 403)
|
|
21
|
+
return EXIT.AUTH_FAILURE;
|
|
22
|
+
return EXIT.INVALID_OR_INDETERMINATE;
|
|
23
|
+
}
|
|
24
|
+
return EXIT.INVALID_OR_INDETERMINATE;
|
|
25
|
+
}
|
|
26
|
+
export function errorMessage(err) {
|
|
27
|
+
return err instanceof Error ? err.message : String(err);
|
|
28
|
+
}
|
|
29
|
+
export function failCommand(command, err, json) {
|
|
30
|
+
const code = exitCodeForError(err);
|
|
31
|
+
const message = errorMessage(err);
|
|
32
|
+
if (json) {
|
|
33
|
+
printJson(jsonEnvelope(command, { result: "error", error: message }));
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
console.error(`zivis ${command}: ${message}`);
|
|
37
|
+
}
|
|
38
|
+
process.exit(code);
|
|
39
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
export declare const DEVX_RUN_CONTRACT_VERSION = "1";
|
|
2
|
+
export interface DevXRunApi {
|
|
3
|
+
post<T>(path: string, body: unknown): Promise<T>;
|
|
4
|
+
}
|
|
5
|
+
export interface DevXRunPackProvenance {
|
|
6
|
+
packId: string;
|
|
7
|
+
packType: string;
|
|
8
|
+
version: string;
|
|
9
|
+
}
|
|
10
|
+
export interface StartDevXRunParams {
|
|
11
|
+
applicationId: string;
|
|
12
|
+
runType: string;
|
|
13
|
+
requestedIntent?: unknown;
|
|
14
|
+
pack?: DevXRunPackProvenance;
|
|
15
|
+
cwd?: string;
|
|
16
|
+
}
|
|
17
|
+
export interface StartedDevXRun {
|
|
18
|
+
runId: string;
|
|
19
|
+
contractVersion: string;
|
|
20
|
+
applicationId: string;
|
|
21
|
+
startedAt: string;
|
|
22
|
+
}
|
|
23
|
+
export declare function startDevXRun(client: DevXRunApi, params: StartDevXRunParams): Promise<StartedDevXRun>;
|
|
24
|
+
export interface DevXRunWire {
|
|
25
|
+
id: string;
|
|
26
|
+
applicationId: string;
|
|
27
|
+
status: "pending" | "completed" | "cancelled";
|
|
28
|
+
contractVersion: string;
|
|
29
|
+
startedAt: string;
|
|
30
|
+
completedAt: string | null;
|
|
31
|
+
cancelledAt: string | null;
|
|
32
|
+
cancelReason: string | null;
|
|
33
|
+
}
|
|
34
|
+
export interface DevXRunReconciliationSummary {
|
|
35
|
+
findings: Array<{
|
|
36
|
+
id: string;
|
|
37
|
+
action: "created" | "updated" | "unchanged";
|
|
38
|
+
fingerprint: string;
|
|
39
|
+
}>;
|
|
40
|
+
retests: Array<{
|
|
41
|
+
findingId: string;
|
|
42
|
+
outcome: "resolved" | "still_present";
|
|
43
|
+
action: "disposition_updated" | "no_change";
|
|
44
|
+
}>;
|
|
45
|
+
evidence: Array<{
|
|
46
|
+
id: string;
|
|
47
|
+
action: "created" | "existing";
|
|
48
|
+
}>;
|
|
49
|
+
coverage: Array<{
|
|
50
|
+
scopeId: string;
|
|
51
|
+
applicability: string;
|
|
52
|
+
}>;
|
|
53
|
+
}
|
|
54
|
+
export type TrustRoomLinkSource = string;
|
|
55
|
+
export interface DevXRunOutcome {
|
|
56
|
+
outcome: "completed" | "cancelled" | "idempotent-replay";
|
|
57
|
+
run: DevXRunWire;
|
|
58
|
+
reconciliation?: DevXRunReconciliationSummary;
|
|
59
|
+
trustRoomUrl?: string | null;
|
|
60
|
+
trustRoomSource?: TrustRoomLinkSource | null;
|
|
61
|
+
}
|
|
62
|
+
export declare function isRetryableDevXRunError(err: unknown): boolean;
|
|
63
|
+
export declare function completeDevXRunRemote(client: DevXRunApi, params: {
|
|
64
|
+
runId: string;
|
|
65
|
+
contractVersion: string;
|
|
66
|
+
result: unknown;
|
|
67
|
+
}): Promise<DevXRunOutcome>;
|
|
68
|
+
export declare function cancelDevXRunRemote(client: DevXRunApi, params: {
|
|
69
|
+
runId: string;
|
|
70
|
+
reason?: string | null;
|
|
71
|
+
}): Promise<DevXRunOutcome>;
|
|
72
|
+
export interface DevXRunItemsPayload {
|
|
73
|
+
findings?: unknown[];
|
|
74
|
+
evidence?: unknown[];
|
|
75
|
+
observations?: unknown[];
|
|
76
|
+
}
|
|
77
|
+
export interface DevXRunItemFindingWire {
|
|
78
|
+
index: number;
|
|
79
|
+
action: "created" | "updated" | "matched_existing";
|
|
80
|
+
findingId: string;
|
|
81
|
+
match: {
|
|
82
|
+
basis: "fingerprint";
|
|
83
|
+
fingerprint: string;
|
|
84
|
+
};
|
|
85
|
+
retestRecommendation: {
|
|
86
|
+
recommended: true;
|
|
87
|
+
reason: string;
|
|
88
|
+
instruction: string;
|
|
89
|
+
} | null;
|
|
90
|
+
dispositionHistory: Array<Record<string, unknown>>;
|
|
91
|
+
}
|
|
92
|
+
export interface DevXRunItemEvidenceWire {
|
|
93
|
+
index: number;
|
|
94
|
+
action: "created" | "matched_existing";
|
|
95
|
+
evidenceId: string;
|
|
96
|
+
match: {
|
|
97
|
+
basis: "content_hash";
|
|
98
|
+
contentHash: string;
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
export interface DevXRunItemObservationWire {
|
|
102
|
+
testId: string;
|
|
103
|
+
action: "created" | "matched_existing";
|
|
104
|
+
observationId: string;
|
|
105
|
+
scopeId: string;
|
|
106
|
+
result: string;
|
|
107
|
+
}
|
|
108
|
+
export interface DevXRunItemsOutcome {
|
|
109
|
+
runId: string;
|
|
110
|
+
outcome: "applied";
|
|
111
|
+
items: {
|
|
112
|
+
findings: DevXRunItemFindingWire[];
|
|
113
|
+
evidence: DevXRunItemEvidenceWire[];
|
|
114
|
+
observations: DevXRunItemObservationWire[];
|
|
115
|
+
};
|
|
116
|
+
rejectedObservations?: Array<{
|
|
117
|
+
test_id: string;
|
|
118
|
+
reason: string;
|
|
119
|
+
}>;
|
|
120
|
+
runTotals: {
|
|
121
|
+
findings: number;
|
|
122
|
+
evidence: number;
|
|
123
|
+
observations: number;
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
export declare function reportDevXRunItems(client: DevXRunApi, params: {
|
|
127
|
+
runId: string;
|
|
128
|
+
items: DevXRunItemsPayload;
|
|
129
|
+
}): Promise<DevXRunItemsOutcome>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { ApiError } from "@zivis/mcp/api-client";
|
|
2
|
+
import { readGitMetadata } from "./git.js";
|
|
3
|
+
export const DEVX_RUN_CONTRACT_VERSION = "1";
|
|
4
|
+
export async function startDevXRun(client, params) {
|
|
5
|
+
const git = await readGitMetadata(params.cwd ?? process.cwd());
|
|
6
|
+
const response = await client.post(`/api/rt/applications/${params.applicationId}/runs`, {
|
|
7
|
+
runType: params.runType,
|
|
8
|
+
contractVersion: DEVX_RUN_CONTRACT_VERSION,
|
|
9
|
+
gitSha: git.commitSha ?? null,
|
|
10
|
+
gitDirty: git.dirty ?? null,
|
|
11
|
+
packId: params.pack?.packId ?? null,
|
|
12
|
+
packType: params.pack?.packType ?? null,
|
|
13
|
+
packVersion: params.pack?.version ?? null,
|
|
14
|
+
requestedIntent: params.requestedIntent,
|
|
15
|
+
});
|
|
16
|
+
return {
|
|
17
|
+
runId: response.id,
|
|
18
|
+
contractVersion: response.contractVersion,
|
|
19
|
+
applicationId: response.applicationId,
|
|
20
|
+
startedAt: response.startedAt,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
export function isRetryableDevXRunError(err) {
|
|
24
|
+
return err instanceof ApiError && (err.code === "NETWORK_ERROR" || err.status >= 500);
|
|
25
|
+
}
|
|
26
|
+
export async function completeDevXRunRemote(client, params) {
|
|
27
|
+
return client.post(`/api/devx-runs/${params.runId}/complete`, {
|
|
28
|
+
contractVersion: params.contractVersion,
|
|
29
|
+
result: params.result,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
export async function cancelDevXRunRemote(client, params) {
|
|
33
|
+
return client.post(`/api/devx-runs/${params.runId}/cancel`, {
|
|
34
|
+
reason: params.reason ?? null,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
export async function reportDevXRunItems(client, params) {
|
|
38
|
+
return client.post(`/api/devx-runs/${params.runId}/items`, params.items);
|
|
39
|
+
}
|
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Go language adapter. Same shape as the TypeScript adapter — manifest
|
|
3
|
-
* pass + AST pass — different parser + framework table.
|
|
4
|
-
*/
|
|
5
1
|
import type { LanguageAdapter } from "../../types.js";
|
|
6
2
|
export declare const goAdapter: LanguageAdapter;
|
|
7
3
|
export declare const _stdlibFrameworkSet: Set<string>;
|
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Go language adapter. Same shape as the TypeScript adapter — manifest
|
|
3
|
-
* pass + AST pass — different parser + framework table.
|
|
4
|
-
*/
|
|
5
1
|
import * as path from "node:path";
|
|
6
2
|
import { readManifests, summarizeManifests, findExternalServices, findDataStores, detectFrameworkFromManifest, STDLIB_FRAMEWORKS, } from "./manifest.js";
|
|
7
3
|
import { scanFile, aggregate } from "./scanner.js";
|
|
@@ -66,6 +62,4 @@ function frameworkFromOwningManifest(sourceFile, manifests) {
|
|
|
66
62
|
.sort((a, b) => b.dir.length - a.dir.length);
|
|
67
63
|
return candidates[0] ? detectFrameworkFromManifest(candidates[0].m) : undefined;
|
|
68
64
|
}
|
|
69
|
-
// Used by orchestrator-side typing to widen the language union if needed.
|
|
70
65
|
export const _stdlibFrameworkSet = STDLIB_FRAMEWORKS;
|
|
71
|
-
//# sourceMappingURL=index.js.map
|
|
@@ -1,14 +1,5 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Go manifest analysis.
|
|
3
|
-
*
|
|
4
|
-
* Parses go.mod (line-based, no real grammar needed) to build the
|
|
5
|
-
* dependency map. Doesn't read go.sum — that's a hash list, not version
|
|
6
|
-
* authority. Direct vs indirect deps are both in the require block;
|
|
7
|
-
* we keep the // indirect annotation as a hint but don't filter on it.
|
|
8
|
-
*/
|
|
9
1
|
import type { ExtractedExternalService, ExtractedDataStore, ManifestSummary } from "../../types.js";
|
|
10
2
|
export declare const WEB_FRAMEWORK_MODULES: Record<string, string>;
|
|
11
|
-
/** stdlib router lives at "net/http"; not in go.mod, only detected via imports. */
|
|
12
3
|
export declare const STDLIB_FRAMEWORKS: Set<string>;
|
|
13
4
|
export declare const DATA_STORE_MODULES: Record<string, {
|
|
14
5
|
kind: ExtractedDataStore["kind"];
|
|
@@ -19,17 +10,9 @@ export interface ParsedGoMod {
|
|
|
19
10
|
filePath: string;
|
|
20
11
|
module?: string;
|
|
21
12
|
goVersion?: string;
|
|
22
|
-
/** module path → version. */
|
|
23
13
|
deps: Record<string, string>;
|
|
24
14
|
}
|
|
25
15
|
export declare function readManifests(rootDir: string, manifestFiles: string[]): Promise<ParsedGoMod[]>;
|
|
26
|
-
/**
|
|
27
|
-
* Minimal go.mod parser. Supports the two block forms:
|
|
28
|
-
* require modulePath v1.2.3
|
|
29
|
-
* require ( modulePath v1.2.3 \n other v0.0.1 )
|
|
30
|
-
* Strips trailing "// indirect" markers. Does NOT handle replace/exclude
|
|
31
|
-
* directives (out of scope for inventory).
|
|
32
|
-
*/
|
|
33
16
|
export declare function parseGoMod(filePath: string, rootDir: string, raw: string): ParsedGoMod;
|
|
34
17
|
export declare function summarizeManifests(parsed: ParsedGoMod[]): ManifestSummary[];
|
|
35
18
|
export declare function findExternalServices(parsed: ParsedGoMod[]): ExtractedExternalService[];
|
|
@@ -1,11 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Go manifest analysis.
|
|
3
|
-
*
|
|
4
|
-
* Parses go.mod (line-based, no real grammar needed) to build the
|
|
5
|
-
* dependency map. Doesn't read go.sum — that's a hash list, not version
|
|
6
|
-
* authority. Direct vs indirect deps are both in the require block;
|
|
7
|
-
* we keep the // indirect annotation as a hint but don't filter on it.
|
|
8
|
-
*/
|
|
9
1
|
import * as fs from "node:fs/promises";
|
|
10
2
|
import * as path from "node:path";
|
|
11
3
|
export const WEB_FRAMEWORK_MODULES = {
|
|
@@ -18,14 +10,13 @@ export const WEB_FRAMEWORK_MODULES = {
|
|
|
18
10
|
"github.com/gofiber/fiber/v2": "fiber",
|
|
19
11
|
"github.com/gorilla/mux": "gorilla-mux",
|
|
20
12
|
};
|
|
21
|
-
/** stdlib router lives at "net/http"; not in go.mod, only detected via imports. */
|
|
22
13
|
export const STDLIB_FRAMEWORKS = new Set(["net/http"]);
|
|
23
14
|
export const DATA_STORE_MODULES = {
|
|
24
15
|
"github.com/jackc/pgx": { kind: "postgres", client: "pgx" },
|
|
25
16
|
"github.com/jackc/pgx/v5": { kind: "postgres", client: "pgx" },
|
|
26
17
|
"github.com/jackc/pgx/v4": { kind: "postgres", client: "pgx" },
|
|
27
18
|
"github.com/lib/pq": { kind: "postgres", client: "pq" },
|
|
28
|
-
"gorm.io/gorm": { kind: "postgres", client: "gorm" },
|
|
19
|
+
"gorm.io/gorm": { kind: "postgres", client: "gorm" },
|
|
29
20
|
"go.mongodb.org/mongo-driver": { kind: "mongo", client: "mongo-driver" },
|
|
30
21
|
"github.com/redis/go-redis": { kind: "redis", client: "go-redis" },
|
|
31
22
|
"github.com/redis/go-redis/v9": { kind: "redis", client: "go-redis" },
|
|
@@ -48,18 +39,10 @@ export async function readManifests(rootDir, manifestFiles) {
|
|
|
48
39
|
out.push(parseGoMod(file, rootDir, raw));
|
|
49
40
|
}
|
|
50
41
|
catch {
|
|
51
|
-
// skip unreadable
|
|
52
42
|
}
|
|
53
43
|
}
|
|
54
44
|
return out;
|
|
55
45
|
}
|
|
56
|
-
/**
|
|
57
|
-
* Minimal go.mod parser. Supports the two block forms:
|
|
58
|
-
* require modulePath v1.2.3
|
|
59
|
-
* require ( modulePath v1.2.3 \n other v0.0.1 )
|
|
60
|
-
* Strips trailing "// indirect" markers. Does NOT handle replace/exclude
|
|
61
|
-
* directives (out of scope for inventory).
|
|
62
|
-
*/
|
|
63
46
|
export function parseGoMod(filePath, rootDir, raw) {
|
|
64
47
|
const lines = raw.split("\n");
|
|
65
48
|
const result = {
|
|
@@ -92,7 +75,6 @@ export function parseGoMod(filePath, rootDir, raw) {
|
|
|
92
75
|
body = body.slice("require ".length).trim();
|
|
93
76
|
else if (!inBlock)
|
|
94
77
|
continue;
|
|
95
|
-
// Strip "// indirect" / other line comments
|
|
96
78
|
const commentAt = body.indexOf("//");
|
|
97
79
|
if (commentAt >= 0)
|
|
98
80
|
body = body.slice(0, commentAt).trim();
|
|
@@ -132,6 +114,24 @@ export function findExternalServices(parsed) {
|
|
|
132
114
|
callSites: [{ file: m.filePath, line: 1 }],
|
|
133
115
|
});
|
|
134
116
|
}
|
|
117
|
+
for (const [mod, vendor] of Object.entries(WEB_FRAMEWORK_MODULES)) {
|
|
118
|
+
const ver = matchModuleVersion(m.deps, mod);
|
|
119
|
+
if (!ver)
|
|
120
|
+
continue;
|
|
121
|
+
const key = `web_framework::${mod}`;
|
|
122
|
+
const existing = seen.get(key);
|
|
123
|
+
if (existing) {
|
|
124
|
+
existing.callSites.push({ file: m.filePath, line: 1 });
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
seen.set(key, {
|
|
128
|
+
name: vendor,
|
|
129
|
+
kind: "web_framework",
|
|
130
|
+
package: mod,
|
|
131
|
+
version: ver,
|
|
132
|
+
callSites: [{ file: m.filePath, line: 1 }],
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
135
|
}
|
|
136
136
|
return [...seen.values()];
|
|
137
137
|
}
|
|
@@ -166,14 +166,9 @@ export function detectFrameworkFromManifest(m) {
|
|
|
166
166
|
}
|
|
167
167
|
return undefined;
|
|
168
168
|
}
|
|
169
|
-
/**
|
|
170
|
-
* Match a module path including its versioned variants (mod, mod/v2, etc.).
|
|
171
|
-
* Returns the version of the first match.
|
|
172
|
-
*/
|
|
173
169
|
function matchModuleVersion(deps, modulePath) {
|
|
174
170
|
if (deps[modulePath])
|
|
175
171
|
return deps[modulePath];
|
|
176
|
-
// Try versioned variants
|
|
177
172
|
for (const [k, v] of Object.entries(deps)) {
|
|
178
173
|
if (k === modulePath)
|
|
179
174
|
return v;
|
|
@@ -196,4 +191,3 @@ function relPosix(rootDir, abs) {
|
|
|
196
191
|
const rel = path.relative(rootDir, abs);
|
|
197
192
|
return rel.split(path.sep).join("/");
|
|
198
193
|
}
|
|
199
|
-
//# sourceMappingURL=manifest.js.map
|