zeuslock-dlp-cli 0.2.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.
- package/README.md +570 -0
- package/bin/zeuslock.js +9 -0
- package/package.json +33 -0
- package/src/cli.js +78 -0
- package/src/commands/agents.js +211 -0
- package/src/commands/anonymize.js +83 -0
- package/src/commands/auth.js +337 -0
- package/src/commands/deploy.js +515 -0
- package/src/commands/extensions.js +73 -0
- package/src/commands/hook.js +221 -0
- package/src/commands/incidents.js +436 -0
- package/src/commands/keys.js +211 -0
- package/src/commands/mcp.js +322 -0
- package/src/commands/rules.js +432 -0
- package/src/commands/scan.js +178 -0
- package/src/commands/shadow-ai.js +255 -0
- package/src/commands/siem.js +241 -0
- package/src/commands/status.js +170 -0
- package/src/commands/tokens.js +293 -0
- package/src/commands/users.js +255 -0
- package/src/commands/whoami.js +43 -0
- package/src/lib/api-client.js +308 -0
- package/src/lib/api-key-store.js +84 -0
- package/src/lib/auth-store.js +123 -0
- package/src/lib/cli-token.js +22 -0
- package/src/lib/command-token.js +15 -0
- package/src/lib/config.js +27 -0
- package/src/lib/dlp-scan.js +146 -0
- package/src/lib/package-info.js +11 -0
- package/src/lib/prompt.js +55 -0
- package/src/lib/siem-cursors.js +64 -0
- package/src/lib/table.js +30 -0
- package/src/lib/time.js +33 -0
- package/src/lib/version.js +24 -0
package/src/cli.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
|
|
4
|
+
import { createAgentsCommand } from "./commands/agents.js";
|
|
5
|
+
import { createAnonymizeCommand } from "./commands/anonymize.js";
|
|
6
|
+
import { createAuthCommand } from "./commands/auth.js";
|
|
7
|
+
import { createDeployCommand } from "./commands/deploy.js";
|
|
8
|
+
import { createExtensionsCommand } from "./commands/extensions.js";
|
|
9
|
+
import { createHookCommand } from "./commands/hook.js";
|
|
10
|
+
import { createIncidentsCommand } from "./commands/incidents.js";
|
|
11
|
+
import { createKeysCommand } from "./commands/keys.js";
|
|
12
|
+
import { createMcpCommand } from "./commands/mcp.js";
|
|
13
|
+
import { createRulesCommand } from "./commands/rules.js";
|
|
14
|
+
import { createScanCommand } from "./commands/scan.js";
|
|
15
|
+
import { createShadowAiCommand } from "./commands/shadow-ai.js";
|
|
16
|
+
import { createSiemCommand } from "./commands/siem.js";
|
|
17
|
+
import { createStatusCommand } from "./commands/status.js";
|
|
18
|
+
import { createTokensCommand } from "./commands/tokens.js";
|
|
19
|
+
import { createUsersCommand } from "./commands/users.js";
|
|
20
|
+
import { createWhoamiCommand } from "./commands/whoami.js";
|
|
21
|
+
import { assertCliAccessToken } from "./lib/cli-token.js";
|
|
22
|
+
import { setCommandToken } from "./lib/command-token.js";
|
|
23
|
+
|
|
24
|
+
const require = createRequire(import.meta.url);
|
|
25
|
+
const packageJson = require("../package.json");
|
|
26
|
+
|
|
27
|
+
export function buildProgram({
|
|
28
|
+
stdin = process.stdin,
|
|
29
|
+
stdout = process.stdout,
|
|
30
|
+
stderr = process.stderr,
|
|
31
|
+
env = process.env
|
|
32
|
+
} = {}) {
|
|
33
|
+
const program = new Command();
|
|
34
|
+
|
|
35
|
+
program
|
|
36
|
+
.name("zeuslock")
|
|
37
|
+
.description("ZeusLock command line")
|
|
38
|
+
.version(packageJson.version)
|
|
39
|
+
.option("--token <zlu_token>", "personal CLI access token for one command invocation")
|
|
40
|
+
.configureOutput({
|
|
41
|
+
writeOut: (text) => stdout.write(text),
|
|
42
|
+
writeErr: (text) => stderr.write(text),
|
|
43
|
+
outputError: (text, write) => write(text)
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
program.hook("preAction", (thisCommand) => {
|
|
47
|
+
const token = String(thisCommand.opts().token || "").trim();
|
|
48
|
+
if (token) {
|
|
49
|
+
assertCliAccessToken(token, "--token");
|
|
50
|
+
}
|
|
51
|
+
setCommandToken(env, token);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
program.addCommand(createAuthCommand({ stdin, stdout, stderr, env }));
|
|
55
|
+
program.addCommand(createScanCommand({ stdin, stdout, env }));
|
|
56
|
+
program.addCommand(createAnonymizeCommand({ stdout, env }));
|
|
57
|
+
program.addCommand(createHookCommand({ stdout, env }));
|
|
58
|
+
program.addCommand(createDeployCommand({ stdout, env }));
|
|
59
|
+
program.addCommand(createAgentsCommand({ stdin, stdout, env }));
|
|
60
|
+
program.addCommand(createExtensionsCommand({ stdout, env }));
|
|
61
|
+
program.addCommand(createIncidentsCommand({ stdout, env }));
|
|
62
|
+
program.addCommand(createUsersCommand({ stdin, stdout, env }));
|
|
63
|
+
program.addCommand(createKeysCommand({ stdin, stdout, env }));
|
|
64
|
+
program.addCommand(createTokensCommand({ stdin, stdout, env }));
|
|
65
|
+
program.addCommand(createRulesCommand({ stdout, env }));
|
|
66
|
+
program.addCommand(createSiemCommand({ stdout, env }));
|
|
67
|
+
program.addCommand(createShadowAiCommand({ stdout, env }));
|
|
68
|
+
program.addCommand(createMcpCommand({ stdout, env }));
|
|
69
|
+
program.addCommand(createWhoamiCommand({ stdout, stderr, env }));
|
|
70
|
+
program.addCommand(createStatusCommand({ stdout, env }));
|
|
71
|
+
|
|
72
|
+
return program;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export async function run(argv = process.argv) {
|
|
76
|
+
const program = buildProgram();
|
|
77
|
+
await program.parseAsync(argv);
|
|
78
|
+
}
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
|
|
3
|
+
import { requestAuthenticatedJson } from "../lib/api-client.js";
|
|
4
|
+
import { promptText } from "../lib/prompt.js";
|
|
5
|
+
import { dash, formatTable } from "../lib/table.js";
|
|
6
|
+
import { parseApiTimestamp } from "../lib/time.js";
|
|
7
|
+
import { compareVersions } from "../lib/version.js";
|
|
8
|
+
|
|
9
|
+
const AGENT_ONLINE_WINDOW_MS = 5 * 60 * 1000;
|
|
10
|
+
const STATUS_FILTERS = ["all", "online", "offline"];
|
|
11
|
+
|
|
12
|
+
export function createAgentsCommand({
|
|
13
|
+
stdin = process.stdin,
|
|
14
|
+
stdout = process.stdout,
|
|
15
|
+
env = process.env
|
|
16
|
+
} = {}) {
|
|
17
|
+
const command = new Command("agents")
|
|
18
|
+
.description("Review and manage desktop agents");
|
|
19
|
+
|
|
20
|
+
command.addCommand(createAgentsListCommand({ stdout, env }));
|
|
21
|
+
command.addCommand(createAgentsRevokeCommand({ stdin, stdout, env }));
|
|
22
|
+
|
|
23
|
+
return command;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function createAgentsListCommand({ stdout = process.stdout, env = process.env } = {}) {
|
|
27
|
+
return new Command("list")
|
|
28
|
+
.description("List desktop agents")
|
|
29
|
+
.option("--status <status>", "status filter: all, online, or offline", "all")
|
|
30
|
+
.option("--search <text>", "search hostname or platform")
|
|
31
|
+
.option("--json", "print machine-readable output")
|
|
32
|
+
.action(async (options) => {
|
|
33
|
+
const status = parseStatus(options.status);
|
|
34
|
+
const agentsData = await requestAuthenticatedJson("/api/agents", { env });
|
|
35
|
+
const latestVersion = await fetchLatestAgentVersion({ env });
|
|
36
|
+
const agents = normalizeAgents(
|
|
37
|
+
Array.isArray(agentsData) ? agentsData : (agentsData.agents || []),
|
|
38
|
+
latestVersion
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
const filtered = agents.filter((agent) =>
|
|
42
|
+
matchesAgentFilters(agent, {
|
|
43
|
+
status,
|
|
44
|
+
search: options.search
|
|
45
|
+
})
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
const output = {
|
|
49
|
+
total: typeof agentsData?.total === "number" ? agentsData.total : agents.length,
|
|
50
|
+
online_count: typeof agentsData?.online_count === "number"
|
|
51
|
+
? agentsData.online_count
|
|
52
|
+
: agents.filter((agent) => agent.is_online).length,
|
|
53
|
+
offline_count: typeof agentsData?.offline_count === "number"
|
|
54
|
+
? agentsData.offline_count
|
|
55
|
+
: agents.filter((agent) => !agent.is_online).length,
|
|
56
|
+
latest_version: latestVersion || null,
|
|
57
|
+
filters: {
|
|
58
|
+
status,
|
|
59
|
+
search: options.search || null
|
|
60
|
+
},
|
|
61
|
+
agents: filtered
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
if (options.json) {
|
|
65
|
+
stdout.write(`${JSON.stringify(output)}\n`);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
stdout.write(`Agents: ${output.total} total, ${output.online_count} online, ${output.offline_count} offline\n`);
|
|
70
|
+
if (latestVersion) {
|
|
71
|
+
stdout.write(`Latest published version: ${latestVersion}\n`);
|
|
72
|
+
}
|
|
73
|
+
if (!filtered.length) {
|
|
74
|
+
stdout.write("No agents found.\n");
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
stdout.write("\n");
|
|
79
|
+
stdout.write(`${formatTable(filtered, [
|
|
80
|
+
{ header: "AGENT ID", value: (agent) => dash(agent.agent_id) },
|
|
81
|
+
{ header: "HOSTNAME", value: (agent) => dash(agent.hostname) },
|
|
82
|
+
{ header: "PLATFORM", value: (agent) => dash(agent.platform) },
|
|
83
|
+
{ header: "VERSION", value: (agent) => dash(agent.version) },
|
|
84
|
+
{ header: "UPDATE", value: (agent) => agent.update_available ? "yes" : "-" },
|
|
85
|
+
{ header: "STATUS", value: (agent) => agent.status },
|
|
86
|
+
{ header: "USER", value: (agent) => dash(agent.username) },
|
|
87
|
+
{ header: "LAST HEARTBEAT", value: (agent) => dash(agent.last_seen) }
|
|
88
|
+
])}\n`);
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function createAgentsRevokeCommand({
|
|
93
|
+
stdin = process.stdin,
|
|
94
|
+
stdout = process.stdout,
|
|
95
|
+
env = process.env
|
|
96
|
+
} = {}) {
|
|
97
|
+
return new Command("revoke")
|
|
98
|
+
.description("Revoke a desktop agent")
|
|
99
|
+
.argument("<agent_id>", "agent ID from agents list")
|
|
100
|
+
.option("-y, --yes", "skip confirmation prompt")
|
|
101
|
+
.option("--json", "print machine-readable output")
|
|
102
|
+
.action(async (agentId, options) => {
|
|
103
|
+
if (!options.yes) {
|
|
104
|
+
const answer = await promptText({
|
|
105
|
+
message: `Revoke agent ${agentId}? Type "yes" to confirm: `,
|
|
106
|
+
stdin,
|
|
107
|
+
stdout,
|
|
108
|
+
optionName: "--yes"
|
|
109
|
+
});
|
|
110
|
+
if (answer.toLowerCase() !== "yes") {
|
|
111
|
+
stdout.write("Revocation cancelled.\n");
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const data = await requestAuthenticatedJson(`/api/agents/${encodeURIComponent(agentId)}`, {
|
|
117
|
+
env,
|
|
118
|
+
method: "DELETE"
|
|
119
|
+
});
|
|
120
|
+
const output = {
|
|
121
|
+
agent_id: agentId,
|
|
122
|
+
...data
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
if (options.json) {
|
|
126
|
+
stdout.write(`${JSON.stringify(output)}\n`);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
stdout.write(`${output.message || "Agent revoked"}: ${agentId}\n`);
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function parseStatus(value) {
|
|
135
|
+
const status = String(value || "all").trim().toLowerCase();
|
|
136
|
+
if (!STATUS_FILTERS.includes(status)) {
|
|
137
|
+
throw new Error(`Invalid --status. Allowed values: ${STATUS_FILTERS.join(", ")}.`);
|
|
138
|
+
}
|
|
139
|
+
return status;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async function fetchLatestAgentVersion({ env }) {
|
|
143
|
+
try {
|
|
144
|
+
const downloadsData = await requestAuthenticatedJson("/api/v1/agents/downloads", { env });
|
|
145
|
+
return downloadsData?.version || "";
|
|
146
|
+
} catch {
|
|
147
|
+
return "";
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function normalizeAgents(agents, latestVersion = "") {
|
|
152
|
+
return agents.map((agent) => {
|
|
153
|
+
const online = computeOnline(agent);
|
|
154
|
+
const version = agent.version || "";
|
|
155
|
+
return {
|
|
156
|
+
agent_id: agent.agent_id || agent.id || "",
|
|
157
|
+
hostname: agent.hostname || "",
|
|
158
|
+
platform: agent.platform || "",
|
|
159
|
+
version,
|
|
160
|
+
username: agent.username || agent.user || agent.user_email || "",
|
|
161
|
+
last_seen: agent.last_seen || "",
|
|
162
|
+
first_seen: agent.first_seen || "",
|
|
163
|
+
is_online: online,
|
|
164
|
+
status: online ? "online" : "offline",
|
|
165
|
+
update_available: isOutdated(version, latestVersion),
|
|
166
|
+
alerts_today: numberOrZero(agent.alerts_today),
|
|
167
|
+
blocks_today: numberOrZero(agent.blocks_today),
|
|
168
|
+
anonymizations_today: numberOrZero(agent.anonymizations_today)
|
|
169
|
+
};
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function matchesAgentFilters(agent, { status, search = "" }) {
|
|
174
|
+
if (status === "online" && !agent.is_online) {
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
if (status === "offline" && agent.is_online) {
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const query = String(search || "").trim().toLowerCase();
|
|
182
|
+
if (query) {
|
|
183
|
+
return (
|
|
184
|
+
agent.hostname.toLowerCase().includes(query) ||
|
|
185
|
+
agent.platform.toLowerCase().includes(query)
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function computeOnline(agent, now = new Date()) {
|
|
193
|
+
if (typeof agent?.is_online === "boolean") {
|
|
194
|
+
return agent.is_online;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const lastSeen = parseApiTimestamp(agent?.last_seen);
|
|
198
|
+
if (!lastSeen) {
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return now.getTime() - lastSeen.getTime() < AGENT_ONLINE_WINDOW_MS;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function isOutdated(version, latestVersion) {
|
|
206
|
+
return Boolean(version && latestVersion && compareVersions(version, latestVersion) < 0);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function numberOrZero(value) {
|
|
210
|
+
return Number.isFinite(Number(value)) ? Number(value) : 0;
|
|
211
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { Command } from "commander";
|
|
4
|
+
|
|
5
|
+
import { requestApiKeyJson } from "../lib/api-client.js";
|
|
6
|
+
|
|
7
|
+
export function createAnonymizeCommand({
|
|
8
|
+
stdout = process.stdout,
|
|
9
|
+
env = process.env
|
|
10
|
+
} = {}) {
|
|
11
|
+
return new Command("anonymize")
|
|
12
|
+
.description("Anonymize a text file before sending it to an LLM")
|
|
13
|
+
.argument("<file>", "text file to anonymize")
|
|
14
|
+
.option("--api-key <key>", "organization API key; defaults to saved key or ZEUSLOCK_API_KEY")
|
|
15
|
+
.option("--output <path>", "write anonymized text to a file instead of stdout")
|
|
16
|
+
.option("--include-sensitive-map", "include original-to-masked substitutions in JSON output")
|
|
17
|
+
.option("--json", "print machine-readable output")
|
|
18
|
+
.action(async (file, options) => {
|
|
19
|
+
const filePath = path.resolve(file);
|
|
20
|
+
const text = await readFile(filePath, "utf8");
|
|
21
|
+
const result = await requestApiKeyJson("/api/v1/anonymize", {
|
|
22
|
+
env,
|
|
23
|
+
apiKey: options.apiKey,
|
|
24
|
+
method: "POST",
|
|
25
|
+
body: { text }
|
|
26
|
+
});
|
|
27
|
+
const anonymizedText = String(result?.anonymized_text ?? result?.masked ?? "");
|
|
28
|
+
const anonymizationCount = Array.isArray(result?.anonymizations)
|
|
29
|
+
? result.anonymizations.length
|
|
30
|
+
: 0;
|
|
31
|
+
|
|
32
|
+
if (options.output) {
|
|
33
|
+
await writeFile(path.resolve(options.output), anonymizedText, "utf8");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (options.json) {
|
|
37
|
+
stdout.write(`${JSON.stringify(buildJsonOutput(result, {
|
|
38
|
+
file: filePath,
|
|
39
|
+
output: options.output ? path.resolve(options.output) : null,
|
|
40
|
+
includeSensitiveMap: Boolean(options.includeSensitiveMap),
|
|
41
|
+
anonymizedText,
|
|
42
|
+
anonymizationCount
|
|
43
|
+
}))}\n`);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (options.output) {
|
|
48
|
+
stdout.write(`Anonymized ${anonymizationCount} value(s) to ${path.resolve(options.output)}\n`);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
stdout.write(anonymizedText);
|
|
53
|
+
if (!anonymizedText.endsWith("\n")) {
|
|
54
|
+
stdout.write("\n");
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function buildJsonOutput(result, {
|
|
60
|
+
file,
|
|
61
|
+
output,
|
|
62
|
+
includeSensitiveMap,
|
|
63
|
+
anonymizedText,
|
|
64
|
+
anonymizationCount
|
|
65
|
+
}) {
|
|
66
|
+
const base = {
|
|
67
|
+
file,
|
|
68
|
+
output,
|
|
69
|
+
anonymized_text: anonymizedText,
|
|
70
|
+
anonymization_count: anonymizationCount
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
if (includeSensitiveMap) {
|
|
74
|
+
return {
|
|
75
|
+
...base,
|
|
76
|
+
original: result?.original ?? "",
|
|
77
|
+
masked: result?.masked ?? anonymizedText,
|
|
78
|
+
anonymizations: Array.isArray(result?.anonymizations) ? result.anonymizations : []
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return base;
|
|
83
|
+
}
|