@vigilbase/cli 0.2.1 → 0.2.2
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/CLOUDFLARE.md +8 -8
- package/COMMANDS.md +56 -13
- package/LOCAL.md +6 -3
- package/ONE.md +4 -3
- package/README.md +30 -20
- package/SECURITY.md +4 -2
- package/dist/cli.js +6 -3
- package/dist/commands/account.js +1 -1
- package/dist/commands/auth.js +2 -1
- package/dist/commands/cloudflare.d.ts +1 -1
- package/dist/commands/cloudflare.js +8 -3
- package/dist/commands/legacy-validation.d.ts +7 -1
- package/dist/commands/legacy-validation.js +15 -2
- package/dist/commands/local.js +2 -1
- package/dist/commands/one.js +33 -6
- package/dist/commands/public-tools.js +11 -11
- package/dist/commands/runs.js +1 -1
- package/dist/commands/security.d.ts +1 -1
- package/dist/commands/security.js +34 -22
- package/dist/lib/client.d.ts +5 -0
- package/dist/lib/client.js +7 -2
- package/dist/lib/cloudflare-client.js +1 -1
- package/dist/lib/help.d.ts +3 -0
- package/dist/lib/help.js +43 -5
- package/dist/lib/one-client.d.ts +2 -0
- package/dist/lib/one-client.js +6 -2
- package/dist/lib/one-error-hints.d.ts +19 -0
- package/dist/lib/one-error-hints.js +47 -0
- package/dist/local/cloudflare-checkup/errors.d.ts +2 -0
- package/dist/local/cloudflare-checkup/errors.js +2 -0
- package/dist/local/cloudflare-checkup/snapshot-file.js +5 -3
- package/package.json +1 -1
package/dist/commands/one.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { executeOneOperation, formatOnePayload } from '../lib/one-operations.js';
|
|
3
3
|
import { assertCommandFlags, ok, outputJson, requireOneClient, withSpinner } from './helpers.js';
|
|
4
|
+
import { missingArguments } from './legacy-validation.js';
|
|
4
5
|
const regions = ['ams', 'nyc', 'lon', 'sgp', 'iad', 'sfo', 'fra', 'dub', 'tok', 'syd', 'cpt', 'gru', 'dxb', 'ewr', 'lhr', 'jnb', 'atl', 'blr', 'bom', 'cdg', 'del', 'dfw', 'hnl', 'icn', 'itm', 'lax', 'mad', 'man', 'mel', 'mex', 'mia', 'mxp', 'nrt', 'ord', 'sao', 'scl', 'sea', 'sjc', 'sto', 'tlv', 'waw', 'yto'];
|
|
5
6
|
function flag(ctx, name, max = 2048) {
|
|
6
7
|
const value = ctx.values[name];
|
|
@@ -26,8 +27,28 @@ function identifier(value) {
|
|
|
26
27
|
throw new Error('An ID containing only letters, numbers, underscores, or hyphens is required.');
|
|
27
28
|
return result.data;
|
|
28
29
|
}
|
|
30
|
+
const customerSubcommands = ['show', 'summary', 'services', 'endpoints', 'contacts', 'organization', 'watchdog-targets', 'watchdog-scans', 'reports', 'investigations', 'contracts', 'billing', 'invoices', 'subscriptions', 'projects'];
|
|
31
|
+
/** Positional placeholders of each subcommand (as in ONE_HELP), so a missing argument is named. */
|
|
32
|
+
const POSITIONALS = {
|
|
33
|
+
customers: {
|
|
34
|
+
...Object.fromEntries(customerSubcommands.map((command) => [command, '<customer-id>'])),
|
|
35
|
+
report: '<customer-id> <report-id>',
|
|
36
|
+
investigation: '<customer-id> <investigation-id>',
|
|
37
|
+
},
|
|
38
|
+
orgs: { show: '<organization-id>' },
|
|
39
|
+
monitoring: { metrics: '<target-id>...', targets: '<customer-id>', status: '<customer-id>', history: '<customer-id>' },
|
|
40
|
+
};
|
|
41
|
+
function positionalUsage(ctx) {
|
|
42
|
+
const [command] = ctx.rest;
|
|
43
|
+
const placeholders = POSITIONALS[ctx.command];
|
|
44
|
+
if (command === undefined || !placeholders || !Object.hasOwn(placeholders, command))
|
|
45
|
+
return undefined;
|
|
46
|
+
return `vigilbase ${ctx.command} ${command} ${placeholders[command]}`;
|
|
47
|
+
}
|
|
29
48
|
function check(ctx, arity, flags = []) {
|
|
30
|
-
if (ctx.rest.length
|
|
49
|
+
if (ctx.rest.length < arity)
|
|
50
|
+
throw new Error(missingArguments(ctx.command, arity - ctx.rest.length, positionalUsage(ctx)));
|
|
51
|
+
if (ctx.rest.length > arity)
|
|
31
52
|
throw new Error(`Unexpected arguments. Run \`vigilbase ${ctx.command} --help\` for usage.`);
|
|
32
53
|
assertCommandFlags(ctx, flags);
|
|
33
54
|
}
|
|
@@ -114,8 +135,10 @@ export function buildOneOperation(ctx) {
|
|
|
114
135
|
return operation(fleetRoutes[command]);
|
|
115
136
|
}
|
|
116
137
|
if (command === 'metrics') {
|
|
117
|
-
check(ctx, ctx.rest.length);
|
|
118
|
-
const ids =
|
|
138
|
+
check(ctx, Math.max(ctx.rest.length, 2));
|
|
139
|
+
const ids = ctx.rest.slice(1).map(uuid);
|
|
140
|
+
if (ids.length > 50)
|
|
141
|
+
throw new Error('monitoring metrics accepts at most 50 target IDs.');
|
|
119
142
|
return operation('/api/monitoring/metrics', { target_ids: [...new Set(ids)].join(',') });
|
|
120
143
|
}
|
|
121
144
|
if (command === 'targets' || command === 'status' || command === 'history') {
|
|
@@ -133,8 +156,11 @@ export function buildOneOperation(ctx) {
|
|
|
133
156
|
}
|
|
134
157
|
}
|
|
135
158
|
if (ctx.command === 'support' && command === 'tickets') {
|
|
136
|
-
check(ctx, ctx.rest.length === 0 ? 0 : 1, ['isAll', 'limit']);
|
|
137
|
-
return operation('/api/support/tickets', {
|
|
159
|
+
check(ctx, ctx.rest.length === 0 ? 0 : 1, ['isAll', 'limit', 'customer-id']);
|
|
160
|
+
return operation('/api/support/tickets', {
|
|
161
|
+
includeAll: ctx.values.isAll ? true : undefined, limit: integer(ctx, 'limit', 1, 100),
|
|
162
|
+
customerId: ctx.values['customer-id'] === undefined ? undefined : uuid(ctx.values['customer-id']),
|
|
163
|
+
});
|
|
138
164
|
}
|
|
139
165
|
throw new Error(`Unknown ${ctx.command} command: ${command}. Run \`vigilbase ${ctx.command} --help\` for usage.`);
|
|
140
166
|
}
|
|
@@ -192,8 +218,9 @@ These commands read organization context; they do not switch the active organiza
|
|
|
192
218
|
history <customer-id> --range 24h|7d|30d --region CODE --target-id UUID
|
|
193
219
|
All operations require read:monitoring and the API's tenant access checks.
|
|
194
220
|
History defaults to 7d. Health metadata preserves unknown, partial, and unavailable states.`,
|
|
195
|
-
support: `Usage: vigilbase support tickets [--all] [--limit 1..100] [--json]
|
|
221
|
+
support: `Usage: vigilbase support tickets [--all] [--limit 1..100] [--customer-id <uuid>] [--json]
|
|
196
222
|
Lists current organization's open tickets; --all includes closed tickets.
|
|
223
|
+
Vigilbase staff must choose the customer with --customer-id.
|
|
197
224
|
The API defaults to 50 results. Requires support:access and a linked Pylon account.
|
|
198
225
|
The response retains hasMore; the API does not expose cursor pagination.`,
|
|
199
226
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { toolNameSchema, validateArguments } from './legacy-validation.js';
|
|
3
3
|
import { NetworkError } from '../lib/network.js';
|
|
4
|
-
import { VigilbaseApiError } from '../lib/client.js';
|
|
4
|
+
import { PUBLIC_TOOLS_PATH, VigilbaseApiError } from '../lib/client.js';
|
|
5
5
|
import { getOneAccessToken, getOptionalOneAccessToken, hasUsableSignIn, readCredentials } from '../auth/index.js';
|
|
6
6
|
import { SignInUnsupportedError } from '../auth/store.js';
|
|
7
7
|
import { formatExecuteResult, formatToolDescription, formatToolsList, } from '../lib/format.js';
|
|
@@ -58,7 +58,10 @@ export const handleList = async (ctx) => {
|
|
|
58
58
|
return ok();
|
|
59
59
|
};
|
|
60
60
|
export const handleRun = async (ctx) => {
|
|
61
|
-
|
|
61
|
+
const usage = ctx.command === 'headers' ? 'vigilbase headers <url>'
|
|
62
|
+
: ctx.command === 'email' ? 'vigilbase email <domain>'
|
|
63
|
+
: 'vigilbase run <tool> <target>';
|
|
64
|
+
validateArguments(ctx, ctx.command === 'run' ? 2 : 1, ['via', 'isPublic'], usage);
|
|
62
65
|
let toolName;
|
|
63
66
|
let target;
|
|
64
67
|
if (ctx.command === 'headers') {
|
|
@@ -73,11 +76,8 @@ export const handleRun = async (ctx) => {
|
|
|
73
76
|
toolName = ctx.rest[0] ?? '';
|
|
74
77
|
target = ctx.rest[1] ?? '';
|
|
75
78
|
}
|
|
76
|
-
if (!toolName || !target)
|
|
77
|
-
throw new Error(
|
|
78
|
-
? 'Usage: vigilbase run <tool> <target>'
|
|
79
|
-
: `Usage: vigilbase ${ctx.command} <target>`);
|
|
80
|
-
}
|
|
79
|
+
if (!toolName || !target)
|
|
80
|
+
throw new Error(`Usage: ${usage}`);
|
|
81
81
|
toolName = toolNameSchema.parse(toolName);
|
|
82
82
|
target = z.string().trim().min(1).max(2048).refine((value) => !/[\u0000-\u001f\u007f]/.test(value), 'Target must not contain control characters.').parse(target);
|
|
83
83
|
const via = ctx.values.via;
|
|
@@ -116,7 +116,7 @@ export const handleRun = async (ctx) => {
|
|
|
116
116
|
return result.success ? ok() : EXIT.ERROR;
|
|
117
117
|
};
|
|
118
118
|
export const handleDescribe = async (ctx) => {
|
|
119
|
-
validateArguments(ctx, 1, ['isNoSkill']);
|
|
119
|
+
validateArguments(ctx, 1, ['isNoSkill'], 'vigilbase describe <tool>');
|
|
120
120
|
toolNameSchema.parse(ctx.rest[0]);
|
|
121
121
|
const toolName = ctx.rest[0];
|
|
122
122
|
if (!toolName)
|
|
@@ -139,7 +139,7 @@ export const handleDescribe = async (ctx) => {
|
|
|
139
139
|
return ok();
|
|
140
140
|
};
|
|
141
141
|
export const handleSkill = async (ctx) => {
|
|
142
|
-
validateArguments(ctx, 1);
|
|
142
|
+
validateArguments(ctx, 1, [], 'vigilbase skill <tool>');
|
|
143
143
|
toolNameSchema.parse(ctx.rest[0]);
|
|
144
144
|
const toolName = ctx.rest[0];
|
|
145
145
|
if (!toolName)
|
|
@@ -163,7 +163,7 @@ export const handleSkill = async (ctx) => {
|
|
|
163
163
|
return ok();
|
|
164
164
|
};
|
|
165
165
|
export const handleBadge = async (ctx) => {
|
|
166
|
-
validateArguments(ctx, 1, ['style']);
|
|
166
|
+
validateArguments(ctx, 1, ['style'], 'vigilbase badge <domain>');
|
|
167
167
|
const domain = ctx.rest[0];
|
|
168
168
|
if (!domain)
|
|
169
169
|
throw new Error('Usage: vigilbase badge <domain>');
|
|
@@ -230,7 +230,7 @@ export const handleDoctor = async (ctx) => {
|
|
|
230
230
|
};
|
|
231
231
|
};
|
|
232
232
|
const checks = {
|
|
233
|
-
publicApi: checkResult(publicResult, `${ctx.config.publicBaseUrl}
|
|
233
|
+
publicApi: checkResult(publicResult, `${ctx.config.publicBaseUrl}${PUBLIC_TOOLS_PATH}`),
|
|
234
234
|
one: checkResult(oneResult, oneCheckUrl),
|
|
235
235
|
signIn: checkResult(signInResult),
|
|
236
236
|
};
|
package/dist/commands/runs.js
CHANGED
|
@@ -10,7 +10,7 @@ export const handleRuns = async (ctx) => {
|
|
|
10
10
|
toolName = toolNameSchema.optional().parse(ctx.values.tool);
|
|
11
11
|
}
|
|
12
12
|
else if (subcommand === 'get' || subcommand === 'show') {
|
|
13
|
-
validateArguments(ctx, 2);
|
|
13
|
+
validateArguments(ctx, 2, [], `vigilbase runs ${subcommand} <UUID>`);
|
|
14
14
|
z.string().uuid().parse(ctx.rest[1]);
|
|
15
15
|
}
|
|
16
16
|
else if (z.string().uuid().safeParse(subcommand).success)
|
|
@@ -2,4 +2,4 @@ import { type SecurityRequest } from '../lib/security-client.js';
|
|
|
2
2
|
import { type CommandContext, type CommandHandler } from './helpers.js';
|
|
3
3
|
export declare function buildSecurityRequest(ctx: CommandContext): SecurityRequest;
|
|
4
4
|
export declare const handleSecurity: CommandHandler;
|
|
5
|
-
export declare const SECURITY_HELP = "vigilbase security \u2014 managed security in Vigilbase One\n\n [overview]\n incidents [list] --status --severity --attack-category\n --order-by --order-direction --limit --offset\n incidents show <id>\n incidents timeline <id> --limit --before\n incidents fabric <id> --dataset --min-weight --limit\n incidents share-links <id>\n incidents update <id> <status> --confirm [--note]\n alerts [list] --limit --asset-id --start\n alerts show <id>\n logs --limit --asset-id --provider --dataset --search\n --start --end --sort --frs-min --cursor\n audit --limit --cursor --action-prefix --outcome\n assets\n assets <allowlist|blocklist|remediation|autonomy|datasets> <asset-id>\n assets graph <asset-id> --dataset --hours --min-weight --limit\n fabric default-asset\n detections
|
|
5
|
+
export declare const SECURITY_HELP = "vigilbase security \u2014 managed security in Vigilbase One\n\n [overview]\n incidents [list] --status --severity --attack-category\n --order-by --order-direction --limit --offset\n incidents show <id>\n incidents timeline <id> --limit --before\n incidents fabric <id> --dataset --min-weight --limit\n incidents share-links <id>\n incidents update <id> <status> --confirm [--note]\n alerts [list] --limit --asset-id --start\n alerts show <id>\n logs --limit --asset-id --provider --dataset --search\n --start --end --sort --frs-min --cursor\n audit --limit --cursor --action-prefix --outcome\n assets\n assets <allowlist|blocklist|remediation|autonomy|datasets> <asset-id>\n assets graph <asset-id> --dataset --hours --min-weight --limit\n fabric default-asset\n detections --asset-id <asset-id>\n providers\n connections [list]\n connections show <id>\n connections <available-assets|remediation> <id>\n connections <logpush-status|worker-stats> <connection-id> <job-id>\n analytics [overview] --metrics + time-window flags\n analytics <security-analysis|bot-activity|top-attacker-ips|bot-asns|dns-security|page-shield>\n analytics provider-events --provider --dataset --start --end\n [--asset-id --search --limit]\n zero-trust <device-health|app-tests|access|sessions|device-posture>\n zero-trust gateway <dns|http|network>\n cloudflare-account [inventory]\n cloudflare-account <audit|ingestion|posture>\n notifications [list] --limit\n notifications <settings|preferences|slack-channels>\n reports [list] --limit --offset\n reports show <id>\n reports settings\n intel quota\n intel <ip|asn|cidr|ja3|ja4> <query>\n settings <lifecycle-emails|broadcast-emails|applications>\n\nTime-window flags: --hours (1\u2013720, default 24) or --start/--end ISO timestamps;\noptional --asset-id. Apply to analytics (except provider-events), zero-trust,\nand cloudflare-account audit/ingestion/posture. Inventory has no window flags.\nLimits: list incidents/reports/audit 1\u2013100 (default 50); alerts/notifications\n1\u2013100 (default 100); timeline 1\u2013200 (default 100); logs 1\u20131000 (default 100);\nprovider-events 1\u2013500 (default 200); graph 1\u20131000 (default 200).\nOffsets: 0\u20131000000. Graph min-weight: 1\u20131000000 (default 1).\nAll: --json, --timeout <ms>, --customer-id <One customer UUID> (internal View-as).\nAll operations read data except confirmed incident status updates.\nUpdate status: acknowledged, mitigating, resolved, false_positive.\nSee SECURITY.md for every endpoint, supported value, alias, and pagination contract.\n";
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { SecurityClient } from '../lib/security-client.js';
|
|
3
3
|
import { assertCommandFlags, asArray, asRecord, ok, outputJson, requireOneClient, withSpinner } from './helpers.js';
|
|
4
|
+
import { missingArguments } from './legacy-validation.js';
|
|
4
5
|
const statuses = ['open', 'active', 'acknowledged', 'investigating', 'mitigating', 'mitigated', 'resolved', 'false_positive'];
|
|
5
6
|
const updateStatuses = ['acknowledged', 'mitigating', 'resolved', 'false_positive'];
|
|
6
7
|
function textFlag(ctx, flag) {
|
|
@@ -20,8 +21,11 @@ function integer(ctx, flag, max, fallback) {
|
|
|
20
21
|
function allowed(ctx, flags) {
|
|
21
22
|
assertCommandFlags(ctx, [...flags, 'customer-id']);
|
|
22
23
|
}
|
|
23
|
-
|
|
24
|
-
|
|
24
|
+
/** `usage` follows `vigilbase security` and ends with the positional placeholders, so a missing one is named. */
|
|
25
|
+
function arity(ctx, length, usage) {
|
|
26
|
+
if (ctx.rest.length < length)
|
|
27
|
+
throw new Error(missingArguments('security', length - ctx.rest.length, usage && `vigilbase security ${usage}`));
|
|
28
|
+
if (ctx.rest.length > length)
|
|
25
29
|
throw new Error('Unexpected security arguments. Run `vigilbase security --help` for usage.');
|
|
26
30
|
}
|
|
27
31
|
function uuid(value) {
|
|
@@ -92,7 +96,7 @@ function extendedSecurityRequest(ctx, scope) {
|
|
|
92
96
|
const views = { 'device-health': 'deviceHealth', 'app-tests': 'appTests', gateway: 'gateway', access: 'access', sessions: 'sessions', 'device-posture': 'devicePosture' };
|
|
93
97
|
if (!sub || !views[sub])
|
|
94
98
|
throw new Error('Choose a zero-trust command: device-health, app-tests, gateway, access, sessions, device-posture.');
|
|
95
|
-
arity(ctx, sub === 'gateway' ? 3 : 2);
|
|
99
|
+
arity(ctx, sub === 'gateway' ? 3 : 2, 'zero-trust gateway <dns|http|network>');
|
|
96
100
|
const query = window();
|
|
97
101
|
if (sub === 'gateway')
|
|
98
102
|
query.surface = z.enum(['dns', 'http', 'network']).parse(id);
|
|
@@ -128,28 +132,28 @@ function extendedSecurityRequest(ctx, scope) {
|
|
|
128
132
|
}
|
|
129
133
|
if (group === 'assets' && sub !== undefined) {
|
|
130
134
|
const routes = {
|
|
131
|
-
allowlist: [`/domains/${
|
|
132
|
-
remediation: [`/assets/${
|
|
133
|
-
datasets: [`/fabric/assets/${
|
|
135
|
+
allowlist: (assetId) => [`/domains/${assetId}/allowlist`, 'ipList'], blocklist: (assetId) => [`/domains/${assetId}/blocklist`, 'ipList'],
|
|
136
|
+
remediation: (assetId) => [`/assets/${assetId}/remediation-settings`, 'assetRemediation'], autonomy: (assetId) => [`/assets/${assetId}/vigil-autonomy`, 'autonomy'],
|
|
137
|
+
datasets: (assetId) => [`/fabric/assets/${assetId}/datasets`, 'datasets'], graph: (assetId) => [`/fabric/assets/${assetId}/graph`, 'graph'],
|
|
134
138
|
};
|
|
135
|
-
const route = routes[sub];
|
|
139
|
+
const route = Object.hasOwn(routes, sub) ? routes[sub] : undefined;
|
|
136
140
|
if (!route)
|
|
137
141
|
throw new Error(`Unknown assets command: ${sub}`);
|
|
138
|
-
arity(ctx, 3);
|
|
142
|
+
arity(ctx, 3, `assets ${sub} <asset-id>`);
|
|
139
143
|
if (sub !== 'graph')
|
|
140
144
|
allowed(ctx, []);
|
|
141
|
-
return request(...route, sub === 'graph' ? graph(false) : {});
|
|
145
|
+
return request(...route(uuid(id)), sub === 'graph' ? graph(false) : {});
|
|
142
146
|
}
|
|
143
147
|
if (group === 'fabric') {
|
|
144
|
-
arity(ctx, 2);
|
|
145
|
-
allowed(ctx, []);
|
|
146
148
|
if (sub !== 'default-asset')
|
|
147
149
|
throw new Error('Usage: vigilbase security fabric default-asset');
|
|
150
|
+
arity(ctx, 2);
|
|
151
|
+
allowed(ctx, []);
|
|
148
152
|
return request('/fabric/default-asset', 'defaultAsset');
|
|
149
153
|
}
|
|
150
154
|
if (group === 'connections' && sub && ['available-assets', 'remediation', 'logpush-status', 'worker-stats'].includes(sub)) {
|
|
151
155
|
const isJob = sub === 'logpush-status' || sub === 'worker-stats';
|
|
152
|
-
arity(ctx, isJob ? 4 : 3);
|
|
156
|
+
arity(ctx, isJob ? 4 : 3, isJob ? `connections ${sub} <connection-id> <job-id>` : `connections ${sub} <id>`);
|
|
153
157
|
allowed(ctx, []);
|
|
154
158
|
const connectionId = uuid(id);
|
|
155
159
|
if (sub === 'available-assets')
|
|
@@ -165,7 +169,7 @@ function extendedSecurityRequest(ctx, scope) {
|
|
|
165
169
|
}
|
|
166
170
|
if (group === 'alerts') {
|
|
167
171
|
if (sub === 'show' || sub === 'get') {
|
|
168
|
-
arity(ctx, 3);
|
|
172
|
+
arity(ctx, 3, `alerts ${sub} <id>`);
|
|
169
173
|
allowed(ctx, []);
|
|
170
174
|
return request(`/alerts/${uuid(id)}`, 'alert');
|
|
171
175
|
}
|
|
@@ -179,7 +183,7 @@ function extendedSecurityRequest(ctx, scope) {
|
|
|
179
183
|
return request('/alerts', 'alerts', { limit: integer(ctx, 'limit', 100, 100), asset_id: ctx.values['asset-id'] !== undefined ? uuid(ctx.values['asset-id']) : undefined, since });
|
|
180
184
|
}
|
|
181
185
|
if (group === 'incidents' && (sub === 'fabric' || sub === 'share-links')) {
|
|
182
|
-
arity(ctx, 3);
|
|
186
|
+
arity(ctx, 3, `incidents ${sub} <id>`);
|
|
183
187
|
if (sub === 'share-links')
|
|
184
188
|
allowed(ctx, []);
|
|
185
189
|
return request(`/incidents/${uuid(id)}/${sub}`, sub === 'fabric' ? 'graph' : 'shareLinks', sub === 'fabric' ? graph(true) : {});
|
|
@@ -211,17 +215,17 @@ export function buildSecurityRequest(ctx) {
|
|
|
211
215
|
}
|
|
212
216
|
if (['incidents', 'connections', 'reports'].includes(group)) {
|
|
213
217
|
if (command === 'show' || command === 'get') {
|
|
214
|
-
arity(ctx, 3);
|
|
218
|
+
arity(ctx, 3, `${group} ${command} <id>`);
|
|
215
219
|
allowed(ctx, []);
|
|
216
220
|
return request(`/${group}/${uuid(ctx.rest[2])}`, group === 'incidents' ? 'incident' : group === 'connections' ? 'connection' : 'report');
|
|
217
221
|
}
|
|
218
222
|
if (group === 'incidents' && command === 'timeline') {
|
|
219
|
-
arity(ctx, 3);
|
|
223
|
+
arity(ctx, 3, 'incidents timeline <id>');
|
|
220
224
|
allowed(ctx, ['before', 'limit']);
|
|
221
225
|
return request(`/incidents/${uuid(ctx.rest[2])}/timeline`, 'timeline', { limit: integer(ctx, 'limit', 200, 100), before: textFlag(ctx, 'before') });
|
|
222
226
|
}
|
|
223
227
|
if (group === 'incidents' && command === 'update') {
|
|
224
|
-
arity(ctx, 4);
|
|
228
|
+
arity(ctx, 4, 'incidents update <id> <status>');
|
|
225
229
|
allowed(ctx, ['note', 'isConfirm']);
|
|
226
230
|
const id = uuid(ctx.rest[2]);
|
|
227
231
|
const status = z.enum(updateStatuses).parse(ctx.rest[3]);
|
|
@@ -250,10 +254,18 @@ export function buildSecurityRequest(ctx) {
|
|
|
250
254
|
order_direction: z.enum(['asc', 'desc']).optional().parse(textFlag(ctx, 'order-direction')),
|
|
251
255
|
});
|
|
252
256
|
}
|
|
253
|
-
if (group === 'assets'
|
|
257
|
+
if (group === 'assets') {
|
|
258
|
+
arity(ctx, 1);
|
|
259
|
+
allowed(ctx, []);
|
|
260
|
+
return request('/domains', 'assets');
|
|
261
|
+
}
|
|
262
|
+
if (group === 'detections') {
|
|
263
|
+
// Detections are configured per asset; the API has no tenant-wide list.
|
|
254
264
|
arity(ctx, 1);
|
|
255
|
-
allowed(ctx,
|
|
256
|
-
|
|
265
|
+
allowed(ctx, ['asset-id']);
|
|
266
|
+
if (ctx.values['asset-id'] === undefined)
|
|
267
|
+
throw new Error('security detections requires --asset-id <uuid>. List asset IDs with `vigilbase security assets`.');
|
|
268
|
+
return request('/detections', 'detections', { asset_id: uuid(ctx.values['asset-id']) });
|
|
257
269
|
}
|
|
258
270
|
if (group === 'logs') {
|
|
259
271
|
arity(ctx, 1);
|
|
@@ -289,7 +301,7 @@ export function buildSecurityRequest(ctx) {
|
|
|
289
301
|
arity(ctx, 2);
|
|
290
302
|
return request('/intel/quota', 'quota');
|
|
291
303
|
}
|
|
292
|
-
arity(ctx, 3);
|
|
304
|
+
arity(ctx, 3, 'intel <ip|asn|cidr|ja3|ja4> <query>');
|
|
293
305
|
const type = z.enum(['ip', 'asn', 'cidr', 'ja3', 'ja4']).parse(command);
|
|
294
306
|
const q = z.string().trim().min(1).max(512).parse(ctx.rest[2]);
|
|
295
307
|
return request('/intel/lookup', 'intel', { q, type });
|
|
@@ -387,4 +399,4 @@ export const handleSecurity = async (ctx) => {
|
|
|
387
399
|
display(ctx, request, payload);
|
|
388
400
|
return ok();
|
|
389
401
|
};
|
|
390
|
-
export const SECURITY_HELP = "vigilbase security \u2014 managed security in Vigilbase One\n\n [overview]\n incidents [list] --status --severity --attack-category\n --order-by --order-direction --limit --offset\n incidents show <id>\n incidents timeline <id> --limit --before\n incidents fabric <id> --dataset --min-weight --limit\n incidents share-links <id>\n incidents update <id> <status> --confirm [--note]\n alerts [list] --limit --asset-id --start\n alerts show <id>\n logs --limit --asset-id --provider --dataset --search\n --start --end --sort --frs-min --cursor\n audit --limit --cursor --action-prefix --outcome\n assets\n assets <allowlist|blocklist|remediation|autonomy|datasets> <asset-id>\n assets graph <asset-id> --dataset --hours --min-weight --limit\n fabric default-asset\n detections
|
|
402
|
+
export const SECURITY_HELP = "vigilbase security \u2014 managed security in Vigilbase One\n\n [overview]\n incidents [list] --status --severity --attack-category\n --order-by --order-direction --limit --offset\n incidents show <id>\n incidents timeline <id> --limit --before\n incidents fabric <id> --dataset --min-weight --limit\n incidents share-links <id>\n incidents update <id> <status> --confirm [--note]\n alerts [list] --limit --asset-id --start\n alerts show <id>\n logs --limit --asset-id --provider --dataset --search\n --start --end --sort --frs-min --cursor\n audit --limit --cursor --action-prefix --outcome\n assets\n assets <allowlist|blocklist|remediation|autonomy|datasets> <asset-id>\n assets graph <asset-id> --dataset --hours --min-weight --limit\n fabric default-asset\n detections --asset-id <asset-id>\n providers\n connections [list]\n connections show <id>\n connections <available-assets|remediation> <id>\n connections <logpush-status|worker-stats> <connection-id> <job-id>\n analytics [overview] --metrics + time-window flags\n analytics <security-analysis|bot-activity|top-attacker-ips|bot-asns|dns-security|page-shield>\n analytics provider-events --provider --dataset --start --end\n [--asset-id --search --limit]\n zero-trust <device-health|app-tests|access|sessions|device-posture>\n zero-trust gateway <dns|http|network>\n cloudflare-account [inventory]\n cloudflare-account <audit|ingestion|posture>\n notifications [list] --limit\n notifications <settings|preferences|slack-channels>\n reports [list] --limit --offset\n reports show <id>\n reports settings\n intel quota\n intel <ip|asn|cidr|ja3|ja4> <query>\n settings <lifecycle-emails|broadcast-emails|applications>\n\nTime-window flags: --hours (1\u2013720, default 24) or --start/--end ISO timestamps;\noptional --asset-id. Apply to analytics (except provider-events), zero-trust,\nand cloudflare-account audit/ingestion/posture. Inventory has no window flags.\nLimits: list incidents/reports/audit 1\u2013100 (default 50); alerts/notifications\n1\u2013100 (default 100); timeline 1\u2013200 (default 100); logs 1\u20131000 (default 100);\nprovider-events 1\u2013500 (default 200); graph 1\u20131000 (default 200).\nOffsets: 0\u20131000000. Graph min-weight: 1\u20131000000 (default 1).\nAll: --json, --timeout <ms>, --customer-id <One customer UUID> (internal View-as).\nAll operations read data except confirmed incident status updates.\nUpdate status: acknowledged, mitigating, resolved, false_positive.\nSee SECURITY.md for every endpoint, supported value, alias, and pagination contract.\n";
|
package/dist/lib/client.d.ts
CHANGED
|
@@ -7,15 +7,20 @@ export declare class VigilbaseApiError extends Error {
|
|
|
7
7
|
readonly code?: string;
|
|
8
8
|
/** The server's sanitised explanation, set only for known vigilbase.com problem errors. */
|
|
9
9
|
readonly reason?: string;
|
|
10
|
+
/** CLI-authored remediation, never server text (see one-error-hints.ts). */
|
|
11
|
+
readonly hint?: string;
|
|
10
12
|
constructor(message: string, options: {
|
|
11
13
|
status: number;
|
|
12
14
|
body?: unknown;
|
|
13
15
|
retryAfterSec?: number;
|
|
14
16
|
code?: string;
|
|
15
17
|
reason?: string;
|
|
18
|
+
hint?: string;
|
|
16
19
|
});
|
|
17
20
|
}
|
|
18
21
|
export declare function retryAfterSeconds(response: Response, body: unknown): number | undefined;
|
|
22
|
+
/** The versioned public tools API. The unversioned /api/agent/tools aliases sunset on 2027-09-01. */
|
|
23
|
+
export declare const PUBLIC_TOOLS_PATH = "/api/v1/agent/tools";
|
|
19
24
|
/**
|
|
20
25
|
* Public (unauthenticated) vigilbase.com tools client.
|
|
21
26
|
* Vigilbase One authenticated calls will use a separate client + device-auth tokens.
|
package/dist/lib/client.js
CHANGED
|
@@ -10,6 +10,8 @@ export class VigilbaseApiError extends Error {
|
|
|
10
10
|
code;
|
|
11
11
|
/** The server's sanitised explanation, set only for known vigilbase.com problem errors. */
|
|
12
12
|
reason;
|
|
13
|
+
/** CLI-authored remediation, never server text (see one-error-hints.ts). */
|
|
14
|
+
hint;
|
|
13
15
|
constructor(message, options) {
|
|
14
16
|
super(message);
|
|
15
17
|
this.name = 'VigilbaseApiError';
|
|
@@ -18,6 +20,7 @@ export class VigilbaseApiError extends Error {
|
|
|
18
20
|
this.retryAfterSec = options.retryAfterSec;
|
|
19
21
|
this.code = options.code;
|
|
20
22
|
this.reason = options.reason;
|
|
23
|
+
this.hint = options.hint;
|
|
21
24
|
}
|
|
22
25
|
}
|
|
23
26
|
export function retryAfterSeconds(response, body) {
|
|
@@ -68,6 +71,8 @@ function publicApiError(fallback, response, body) {
|
|
|
68
71
|
reason: reason?.message,
|
|
69
72
|
});
|
|
70
73
|
}
|
|
74
|
+
/** The versioned public tools API. The unversioned /api/agent/tools aliases sunset on 2027-09-01. */
|
|
75
|
+
export const PUBLIC_TOOLS_PATH = '/api/v1/agent/tools';
|
|
71
76
|
/**
|
|
72
77
|
* Public (unauthenticated) vigilbase.com tools client.
|
|
73
78
|
* Vigilbase One authenticated calls will use a separate client + device-auth tokens.
|
|
@@ -93,7 +98,7 @@ export class VigilbaseClient {
|
|
|
93
98
|
return { response, body: parseJsonResponse(response, text) };
|
|
94
99
|
}
|
|
95
100
|
async listTools() {
|
|
96
|
-
const { response, body } = await this.request(
|
|
101
|
+
const { response, body } = await this.request(PUBLIC_TOOLS_PATH);
|
|
97
102
|
if (!response.ok) {
|
|
98
103
|
throw publicApiError(`Failed to list tools (HTTP ${response.status})`, response, body);
|
|
99
104
|
}
|
|
@@ -110,7 +115,7 @@ export class VigilbaseClient {
|
|
|
110
115
|
return body;
|
|
111
116
|
}
|
|
112
117
|
async executeTool(toolName, target) {
|
|
113
|
-
const { response, body } = await this.request(
|
|
118
|
+
const { response, body } = await this.request(`${PUBLIC_TOOLS_PATH}/${encodeURIComponent(toolName)}/execute`, {
|
|
114
119
|
method: 'POST',
|
|
115
120
|
headers: { 'Content-Type': 'application/json' },
|
|
116
121
|
body: JSON.stringify({ target }),
|
|
@@ -61,7 +61,7 @@ export class CloudflareClient {
|
|
|
61
61
|
method: 'POST',
|
|
62
62
|
headers: { 'Content-Type': 'application/json' },
|
|
63
63
|
body: JSON.stringify({ customerId: scope.customerId, accountId: scope.accountId }),
|
|
64
|
-
});
|
|
64
|
+
}, { hasCustomerScope: Boolean(scope.customerId) });
|
|
65
65
|
}
|
|
66
66
|
const params = new URLSearchParams();
|
|
67
67
|
if (scope.customerId)
|
package/dist/lib/help.d.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
1
|
export declare const HELP_TAGLINE = "one \u00B7 security \u00B7 tools";
|
|
2
|
+
/** A tool from the catalog the CLI knows offline: it runs as `run <tool> <target>`, not as a command. */
|
|
3
|
+
export declare function isCatalogTool(name: string): boolean;
|
|
2
4
|
export declare const HELP_TEXT: string;
|
|
5
|
+
export declare const HELP_USAGE = "Usage: vigilbase help [<command> | <tool>]";
|
|
3
6
|
export declare function helpForCommand(command: string): string | null;
|
package/dist/lib/help.js
CHANGED
|
@@ -2,7 +2,21 @@ import { CLOUDFLARE_HELP } from '../commands/cloudflare.js';
|
|
|
2
2
|
import { LOCAL_HELP } from '../commands/local.js';
|
|
3
3
|
import { ONE_HELP } from '../commands/one.js';
|
|
4
4
|
import { SECURITY_HELP } from '../commands/security.js';
|
|
5
|
+
import { isOneAuthRequiredTool, ONE_AUTH_REQUIRED_TOOLS } from './one-client.js';
|
|
5
6
|
export const HELP_TAGLINE = 'one · security · tools';
|
|
7
|
+
/** vigilbase.com's public catalog, with each tool's shortcut command. `help <tool>` answers offline. */
|
|
8
|
+
const PUBLIC_TOOL_SHORTCUTS = {
|
|
9
|
+
'security-headers': 'headers <url>',
|
|
10
|
+
'email-security': 'email <domain>',
|
|
11
|
+
};
|
|
12
|
+
const PUBLIC_TOOLS = Object.keys(PUBLIC_TOOL_SHORTCUTS);
|
|
13
|
+
function isPublicTool(name) {
|
|
14
|
+
return Object.hasOwn(PUBLIC_TOOL_SHORTCUTS, name);
|
|
15
|
+
}
|
|
16
|
+
/** A tool from the catalog the CLI knows offline: it runs as `run <tool> <target>`, not as a command. */
|
|
17
|
+
export function isCatalogTool(name) {
|
|
18
|
+
return isPublicTool(name) || isOneAuthRequiredTool(name);
|
|
19
|
+
}
|
|
6
20
|
/** Column where descriptions start in the main help: two-space indent, term, at least two spaces. */
|
|
7
21
|
const DESCRIPTION_COLUMN = 31;
|
|
8
22
|
/** Two-column help rows whose descriptions all start at DESCRIPTION_COLUMN. */
|
|
@@ -32,7 +46,7 @@ Public tools (no login)
|
|
|
32
46
|
badge <domain> · doctor Badge URL · connectivity
|
|
33
47
|
|
|
34
48
|
Auth-required tools (after login)
|
|
35
|
-
|
|
49
|
+
${ONE_AUTH_REQUIRED_TOOLS.map((tool) => ` run ${tool} <target>`).join('\n')}
|
|
36
50
|
|
|
37
51
|
On this machine (no login)
|
|
38
52
|
cloudflare-checkup Run the Cloudflare Checkup on this machine in one
|
|
@@ -57,7 +71,7 @@ Account (login required)
|
|
|
57
71
|
Options
|
|
58
72
|
${GLOBAL_OPTIONS}
|
|
59
73
|
|
|
60
|
-
Use vigilbase <command> --help for
|
|
74
|
+
Use vigilbase <command> --help or vigilbase help <tool> for usage.
|
|
61
75
|
|
|
62
76
|
Examples
|
|
63
77
|
vigilbase headers https://vigilbase.com
|
|
@@ -68,16 +82,40 @@ Examples
|
|
|
68
82
|
vigilbase cf zones
|
|
69
83
|
vigilbase security incidents --severity high
|
|
70
84
|
`;
|
|
85
|
+
/** How to run a catalog tool: `help <tool>` and `<tool> --help`, since tools run as `run <tool> <target>`. */
|
|
86
|
+
function helpForTool(tool) {
|
|
87
|
+
if (isPublicTool(tool)) {
|
|
88
|
+
return `Usage: vigilbase run ${tool} <target> [--json] [--via public|one] [--public]
|
|
89
|
+
|
|
90
|
+
Public tool, no login needed. Shortcut: vigilbase ${PUBLIC_TOOL_SHORTCUTS[tool]}
|
|
91
|
+
Details: vigilbase describe ${tool}`;
|
|
92
|
+
}
|
|
93
|
+
if (isOneAuthRequiredTool(tool)) {
|
|
94
|
+
return `Usage: vigilbase run ${tool} <target> [--json]
|
|
95
|
+
|
|
96
|
+
Runs through Vigilbase One: run \`vigilbase login\` first.
|
|
97
|
+
Details: vigilbase describe ${tool}`;
|
|
98
|
+
}
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
export const HELP_USAGE = 'Usage: vigilbase help [<command> | <tool>]';
|
|
71
102
|
export function helpForCommand(command) {
|
|
72
103
|
switch (command) {
|
|
104
|
+
case 'help':
|
|
105
|
+
return `${HELP_USAGE}
|
|
106
|
+
|
|
107
|
+
Shows the usage of a command or of a tool, for example vigilbase help run or
|
|
108
|
+
vigilbase help website-security. Without one, shows every command.`;
|
|
73
109
|
case 'tools':
|
|
74
110
|
case 'list':
|
|
75
111
|
return `Usage: vigilbase list [--json]`;
|
|
76
112
|
case 'run':
|
|
77
113
|
return `Usage: vigilbase run <tool> <target> [--json] [--via public|one] [--public]
|
|
78
114
|
|
|
79
|
-
Public:
|
|
80
|
-
Login:
|
|
115
|
+
Public: ${PUBLIC_TOOLS.join(', ')}
|
|
116
|
+
Login: ${ONE_AUTH_REQUIRED_TOOLS.join(', ')}
|
|
117
|
+
|
|
118
|
+
Run vigilbase help <tool> for a tool's usage.`;
|
|
81
119
|
case 'describe':
|
|
82
120
|
return `Usage: vigilbase describe <tool> [--json] [--no-skill]`;
|
|
83
121
|
case 'skill':
|
|
@@ -131,6 +169,6 @@ Prefer --token-stdin with piped input to keep tokens out of command arguments.`;
|
|
|
131
169
|
case 'email':
|
|
132
170
|
return `Usage: vigilbase email <domain> [--json] [--via public|one] [--public]`;
|
|
133
171
|
default:
|
|
134
|
-
return
|
|
172
|
+
return helpForTool(command);
|
|
135
173
|
}
|
|
136
174
|
}
|
package/dist/lib/one-client.d.ts
CHANGED
|
@@ -54,6 +54,8 @@ export declare class VigilbaseOneClient {
|
|
|
54
54
|
/** Authenticated JSON transport shared by the One command clients. */
|
|
55
55
|
requestJson<T extends ApiErrorBody = Record<string, unknown>>(path: string, init?: RequestInit, options?: {
|
|
56
56
|
allowStatuses?: number[];
|
|
57
|
+
/** Whether the request names a customer outside the query string (a JSON body). */
|
|
58
|
+
hasCustomerScope?: boolean;
|
|
57
59
|
}): Promise<T>;
|
|
58
60
|
getSession(): Promise<OneSessionResponse>;
|
|
59
61
|
getAccount(): Promise<Record<string, unknown>>;
|
package/dist/lib/one-client.js
CHANGED
|
@@ -6,6 +6,7 @@ import { z } from 'zod';
|
|
|
6
6
|
import { validateOneOrigin } from './config.js';
|
|
7
7
|
import { validateStripePortalUrl } from './open-url.js';
|
|
8
8
|
import { parseJsonResponse, requestText } from './network.js';
|
|
9
|
+
import { oneErrorHint } from './one-error-hints.js';
|
|
9
10
|
import { retryAfterSeconds, VigilbaseApiError } from './client.js';
|
|
10
11
|
/**
|
|
11
12
|
* Tools that require Vigilbase One authentication (auth-required scanners).
|
|
@@ -129,9 +130,12 @@ export class VigilbaseOneClient {
|
|
|
129
130
|
if (errorDetails.code === 'FLAREHAWK_VIEW_AS_REQUIRED') {
|
|
130
131
|
message = 'Select a customer with --customer-id <customer UUID> for security commands.';
|
|
131
132
|
}
|
|
133
|
+
const hint = response.ok ? undefined : oneErrorHint({
|
|
134
|
+
method: init?.method ?? 'GET', path, status: response.status, code: errorDetails.code, hasCustomerScope: options?.hasCustomerScope,
|
|
135
|
+
});
|
|
132
136
|
if (response.status === 401 || response.status === 403) {
|
|
133
137
|
throw new VigilbaseApiError(message ||
|
|
134
|
-
'Authentication required. Run `vigilbase login`.', { status: response.status, body: parsed, code: errorDetails.code });
|
|
138
|
+
'Authentication required. Run `vigilbase login`.', { status: response.status, body: parsed, code: errorDetails.code, hint });
|
|
135
139
|
}
|
|
136
140
|
if (response.status === 429) {
|
|
137
141
|
throw new VigilbaseApiError(message || 'Rate limit exceeded.', {
|
|
@@ -144,7 +148,7 @@ export class VigilbaseOneClient {
|
|
|
144
148
|
const allowed = options?.allowStatuses ?? [];
|
|
145
149
|
if (!response.ok && !allowed.includes(response.status)) {
|
|
146
150
|
throw new VigilbaseApiError(message ||
|
|
147
|
-
`Request failed (HTTP ${response.status})`, { status: response.status, body: parsed, code: errorDetails.code });
|
|
151
|
+
`Request failed (HTTP ${response.status})`, { status: response.status, body: parsed, code: errorDetails.code, hint });
|
|
148
152
|
}
|
|
149
153
|
if (!z.record(z.string(), z.unknown()).safeParse(body).success) {
|
|
150
154
|
throw new VigilbaseApiError('Invalid One API response. Expected a JSON object.', { status: response.status });
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI-authored hints for One errors the CLI recognises. They are chosen only
|
|
3
|
+
* from the request (method, route, whether a customer was selected), the HTTP
|
|
4
|
+
* status and One's own error codes. One's free-text messages are never shown:
|
|
5
|
+
* they can describe its configuration.
|
|
6
|
+
*/
|
|
7
|
+
export declare const STAFF_CUSTOMER_SCOPE_HINT = "Vigilbase staff: add --customer-id <uuid>.";
|
|
8
|
+
export declare const PERSONAL_SETTINGS_HINT = "Personal settings belong to each user, so View-as (--customer-id) cannot read them.";
|
|
9
|
+
export declare const BROWSER_SESSION_HINT = "Invitations need a One browser session: manage them at https://one.vigilbase.com.";
|
|
10
|
+
export type OneErrorContext = {
|
|
11
|
+
method: string;
|
|
12
|
+
/** The One path the CLI requested, with its query string. */
|
|
13
|
+
path: string;
|
|
14
|
+
status: number;
|
|
15
|
+
code?: string;
|
|
16
|
+
/** Whether the request named a customer; defaults to a `customerId` query parameter. */
|
|
17
|
+
hasCustomerScope?: boolean;
|
|
18
|
+
};
|
|
19
|
+
export declare function oneErrorHint(context: OneErrorContext): string | undefined;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI-authored hints for One errors the CLI recognises. They are chosen only
|
|
3
|
+
* from the request (method, route, whether a customer was selected), the HTTP
|
|
4
|
+
* status and One's own error codes. One's free-text messages are never shown:
|
|
5
|
+
* they can describe its configuration.
|
|
6
|
+
*/
|
|
7
|
+
export const STAFF_CUSTOMER_SCOPE_HINT = 'Vigilbase staff: add --customer-id <uuid>.';
|
|
8
|
+
export const PERSONAL_SETTINGS_HINT = 'Personal settings belong to each user, so View-as (--customer-id) cannot read them.';
|
|
9
|
+
export const BROWSER_SESSION_HINT = 'Invitations need a One browser session: manage them at https://one.vigilbase.com.';
|
|
10
|
+
/** One's security boundary codes that already explain a 403 (apps/one/src/lib/flarehawk/access.ts). */
|
|
11
|
+
const SECURITY_ACCESS_CODES = new Set([
|
|
12
|
+
'FLAREHAWK_CUSTOMER_ACCESS_DENIED',
|
|
13
|
+
'FLAREHAWK_ROLE_UNSUPPORTED',
|
|
14
|
+
'FLAREHAWK_ROLE_INSUFFICIENT',
|
|
15
|
+
'FLAREHAWK_ORGANIZATION_REQUIRED',
|
|
16
|
+
'FLAREHAWK_VIEW_AS_REQUIRED',
|
|
17
|
+
]);
|
|
18
|
+
const RULES = [
|
|
19
|
+
// Staff sessions must name the customer; One answers 400 without one.
|
|
20
|
+
{ method: 'GET', route: /^\/api\/support\/tickets$/u, status: 400, hasCustomerScope: false, hint: STAFF_CUSTOMER_SCOPE_HINT },
|
|
21
|
+
{ method: 'GET', route: /^\/api\/services\/cloudflare\/checkups$/u, status: 400, hasCustomerScope: false, hint: STAFF_CUSTOMER_SCOPE_HINT },
|
|
22
|
+
{ method: 'POST', route: /^\/api\/services\/cloudflare\/checkups\/start$/u, status: 400, hasCustomerScope: false, hint: STAFF_CUSTOMER_SCOPE_HINT },
|
|
23
|
+
{ method: 'GET', route: /^\/api\/services\/cloudflare\/workspace(?:\/[a-z-]+)?$/u, status: 400, hasCustomerScope: false, hint: STAFF_CUSTOMER_SCOPE_HINT },
|
|
24
|
+
// Personal security settings are per user; a View-as read is refused.
|
|
25
|
+
{
|
|
26
|
+
method: 'GET',
|
|
27
|
+
route: /^\/api\/flarehawk\/(?:settings\/(?:lifecycle-emails|broadcast-emails)|integrations\/grants|notifications\/preferences)$/u,
|
|
28
|
+
status: 403,
|
|
29
|
+
hasCustomerScope: true,
|
|
30
|
+
hint: PERSONAL_SETTINGS_HINT,
|
|
31
|
+
excludedCodes: SECURITY_ACCESS_CODES,
|
|
32
|
+
},
|
|
33
|
+
// Identity's management API accepts only One browser sessions, so CLI tokens can't read or send invitations.
|
|
34
|
+
{ method: 'GET', route: /^\/api\/auth\/orgs\/invitations$/u, status: 403, hasCustomerScope: false, hint: BROWSER_SESSION_HINT, code: 'browser_session_required' },
|
|
35
|
+
{ method: 'POST', route: /^\/api\/auth\/orgs\/invite$/u, status: 403, hasCustomerScope: false, hint: BROWSER_SESSION_HINT, code: 'browser_session_required' },
|
|
36
|
+
];
|
|
37
|
+
export function oneErrorHint(context) {
|
|
38
|
+
const [pathname = '', query = ''] = context.path.split('?', 2);
|
|
39
|
+
const hasCustomerScope = context.hasCustomerScope ?? new URLSearchParams(query).has('customerId');
|
|
40
|
+
const method = context.method.toUpperCase();
|
|
41
|
+
return RULES.find((rule) => rule.method === method &&
|
|
42
|
+
rule.status === context.status &&
|
|
43
|
+
rule.hasCustomerScope === hasCustomerScope &&
|
|
44
|
+
rule.route.test(pathname) &&
|
|
45
|
+
(rule.code === undefined || rule.code === context.code) &&
|
|
46
|
+
!(context.code !== undefined && rule.excludedCodes?.has(context.code)))?.hint;
|
|
47
|
+
}
|
|
@@ -10,6 +10,8 @@ export declare const LOCAL_ERROR_HINTS: Record<LocalCheckupErrorCode, string>;
|
|
|
10
10
|
* hint names both ways out.
|
|
11
11
|
*/
|
|
12
12
|
export declare const ENV_TOKEN_AUTH_HINT = "Set CLOUDFLARE_API_TOKEN in this shell to a valid read-only Cloudflare API token (read -rs CLOUDFLARE_API_TOKEN && export CLOUDFLARE_API_TOKEN) and run the same command again, or unset it (or use --cf-auth profile) to sign in with your browser instead.";
|
|
13
|
+
/** SNAPSHOT_INVALID for an `inspect` or `submit` path with no file: nothing to collect again, so how to make one. */
|
|
14
|
+
export declare const SNAPSHOT_MISSING_HINT = "Check the file path. To collect a snapshot into a file, run `npx @vigilbase/cli cloudflare-checkup collect --out <file>`.";
|
|
13
15
|
/** CF_PERMISSION_DENIED in env mode: the token lacks a read permission; the browser sign-in is the other way out. */
|
|
14
16
|
export declare const ENV_TOKEN_PERMISSION_HINT = "The API token in CLOUDFLARE_API_TOKEN cannot read everything the checkup needs. Add the read permissions to it, or unset it (or use --cf-auth profile) to sign in with your browser instead, then run the same command again.";
|
|
15
17
|
export declare class LocalCheckupError extends Error {
|