securenow 5.17.1 → 6.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +15 -0
- package/README.md +40 -243
- package/cli.js +455 -425
- package/console-instrumentation.js +136 -147
- package/docs/ALL-FRAMEWORKS-QUICKSTART.md +455 -1339
- package/docs/ARCHITECTURE.md +3 -3
- package/docs/AUTO-BODY-CAPTURE.md +1 -1
- package/docs/AUTO-SETUP.md +4 -4
- package/docs/AUTOMATIC-IP-CAPTURE.md +5 -5
- package/docs/BODY-CAPTURE-QUICKSTART.md +2 -2
- package/docs/CHANGELOG-NEXTJS.md +1 -1
- package/docs/CUSTOMER-GUIDE.md +16 -16
- package/docs/EASIEST-SETUP.md +5 -5
- package/docs/ENVIRONMENT-VARIABLES.md +652 -880
- package/docs/EXPRESS-BODY-CAPTURE.md +12 -13
- package/docs/EXPRESS-SETUP-GUIDE.md +720 -719
- package/docs/INDEX.md +4 -22
- package/docs/LOGGING-GUIDE.md +708 -701
- package/docs/LOGGING-QUICKSTART.md +239 -234
- package/docs/NEXTJS-BODY-CAPTURE.md +2 -2
- package/docs/NEXTJS-GUIDE.md +14 -14
- package/docs/NEXTJS-QUICKSTART.md +1 -1
- package/docs/NEXTJS-WRAPPER-APPROACH.md +1 -1
- package/docs/QUICKSTART-BODY-CAPTURE.md +2 -2
- package/docs/REDACTION-EXAMPLES.md +1 -1
- package/docs/REQUEST-BODY-CAPTURE.md +10 -19
- package/docs/VERCEL-OTEL-MIGRATION.md +3 -3
- package/examples/README.md +6 -6
- package/examples/instrumentation-with-auto-capture.ts +1 -1
- package/examples/nextjs-env-example.txt +2 -2
- package/examples/nextjs-instrumentation.js +1 -1
- package/examples/nextjs-instrumentation.ts +1 -1
- package/examples/nextjs-with-logging-example.md +6 -6
- package/examples/nextjs-with-options.ts +1 -1
- package/examples/test-nextjs-setup.js +1 -1
- package/nextjs-auto-capture.js +207 -199
- package/nextjs-middleware.js +181 -186
- package/nextjs-webpack-config.js +53 -88
- package/nextjs-wrapper.js +158 -158
- package/nextjs.d.ts +1 -1
- package/nextjs.js +135 -190
- package/package.json +45 -67
- package/postinstall.js +6 -6
- package/register.d.ts +1 -1
- package/register.js +4 -39
- package/tracing.d.ts +1 -2
- package/tracing.js +22 -287
- package/web-vite.mjs +156 -239
- package/CONSUMING-APPS-GUIDE.md +0 -455
- package/NPM_README.md +0 -1958
- package/SKILL-API.md +0 -600
- package/SKILL-CLI.md +0 -419
- package/cidr.js +0 -83
- package/cli/apps.js +0 -585
- package/cli/auth.js +0 -280
- package/cli/client.js +0 -115
- package/cli/config.js +0 -173
- package/cli/firewall.js +0 -100
- package/cli/fp.js +0 -638
- package/cli/init.js +0 -201
- package/cli/monitor.js +0 -545
- package/cli/run.js +0 -133
- package/cli/security.js +0 -1064
- package/cli/ui.js +0 -386
- package/docs/API-KEYS-GUIDE.md +0 -233
- package/docs/AUTO-SETUP-SUMMARY.md +0 -331
- package/docs/BODY-CAPTURE-FIX.md +0 -261
- package/docs/COMPLETION-REPORT.md +0 -408
- package/docs/FINAL-SOLUTION.md +0 -335
- package/docs/FIREWALL-GUIDE.md +0 -426
- package/docs/IMPLEMENTATION-SUMMARY.md +0 -410
- package/docs/NEXTJS-BODY-CAPTURE-COMPARISON.md +0 -323
- package/docs/NEXTJS-SETUP-COMPLETE.md +0 -795
- package/docs/NUXT-GUIDE.md +0 -166
- package/docs/SOLUTION-SUMMARY.md +0 -312
- package/firewall-cloud.js +0 -212
- package/firewall-iptables.js +0 -139
- package/firewall-only.js +0 -38
- package/firewall-tcp.js +0 -74
- package/firewall.js +0 -720
- package/free-trial-banner.js +0 -174
- package/nuxt-server-plugin.mjs +0 -423
- package/nuxt.d.ts +0 -60
- package/nuxt.mjs +0 -75
- package/resolve-ip.js +0 -77
package/cli/ui.js
DELETED
|
@@ -1,386 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const readline = require('readline');
|
|
4
|
-
|
|
5
|
-
const NO_COLOR = !!process.env.NO_COLOR || !process.stdout.isTTY;
|
|
6
|
-
|
|
7
|
-
const codes = {
|
|
8
|
-
reset: '\x1b[0m',
|
|
9
|
-
bold: '\x1b[1m',
|
|
10
|
-
dim: '\x1b[2m',
|
|
11
|
-
italic: '\x1b[3m',
|
|
12
|
-
underline: '\x1b[4m',
|
|
13
|
-
red: '\x1b[31m',
|
|
14
|
-
green: '\x1b[32m',
|
|
15
|
-
yellow: '\x1b[33m',
|
|
16
|
-
blue: '\x1b[34m',
|
|
17
|
-
magenta: '\x1b[35m',
|
|
18
|
-
cyan: '\x1b[36m',
|
|
19
|
-
white: '\x1b[37m',
|
|
20
|
-
gray: '\x1b[90m',
|
|
21
|
-
bgRed: '\x1b[41m',
|
|
22
|
-
bgGreen: '\x1b[42m',
|
|
23
|
-
bgYellow: '\x1b[43m',
|
|
24
|
-
bgBlue: '\x1b[44m',
|
|
25
|
-
bgCyan: '\x1b[46m',
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
function style(code, text) {
|
|
29
|
-
if (NO_COLOR) return text;
|
|
30
|
-
return `${code}${text}${codes.reset}`;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const c = {
|
|
34
|
-
bold: (s) => style(codes.bold, s),
|
|
35
|
-
dim: (s) => style(codes.dim, s),
|
|
36
|
-
italic: (s) => style(codes.italic, s),
|
|
37
|
-
underline: (s) => style(codes.underline, s),
|
|
38
|
-
red: (s) => style(codes.red, s),
|
|
39
|
-
green: (s) => style(codes.green, s),
|
|
40
|
-
yellow: (s) => style(codes.yellow, s),
|
|
41
|
-
blue: (s) => style(codes.blue, s),
|
|
42
|
-
magenta: (s) => style(codes.magenta, s),
|
|
43
|
-
cyan: (s) => style(codes.cyan, s),
|
|
44
|
-
white: (s) => style(codes.white, s),
|
|
45
|
-
gray: (s) => style(codes.gray, s),
|
|
46
|
-
success: (s) => style(codes.green, s),
|
|
47
|
-
error: (s) => style(codes.red, s),
|
|
48
|
-
warn: (s) => style(codes.yellow, s),
|
|
49
|
-
info: (s) => style(codes.cyan, s),
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
function stripAnsi(str) {
|
|
53
|
-
return str.replace(/\x1b\[[0-9;]*m/g, '');
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function visibleLength(str) {
|
|
57
|
-
return stripAnsi(str).length;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function padEnd(str, len) {
|
|
61
|
-
const visible = visibleLength(str);
|
|
62
|
-
if (visible >= len) return str;
|
|
63
|
-
return str + ' '.repeat(len - visible);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function table(headers, rows, opts = {}) {
|
|
67
|
-
if (!rows.length) {
|
|
68
|
-
console.log(c.dim(' No results found.'));
|
|
69
|
-
return;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const maxWidth = process.stdout.columns || 120;
|
|
73
|
-
const colWidths = headers.map((h, i) => {
|
|
74
|
-
const headerLen = visibleLength(String(h));
|
|
75
|
-
const maxCell = rows.reduce((max, row) => {
|
|
76
|
-
const cell = String(row[i] ?? '');
|
|
77
|
-
return Math.max(max, visibleLength(cell));
|
|
78
|
-
}, 0);
|
|
79
|
-
return Math.min(Math.max(headerLen, maxCell), opts.maxColWidth || 60);
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
const totalWidth = colWidths.reduce((a, b) => a + b, 0) + (colWidths.length - 1) * 3;
|
|
83
|
-
if (totalWidth > maxWidth && colWidths.length > 1) {
|
|
84
|
-
const lastIdx = colWidths.length - 1;
|
|
85
|
-
const excess = totalWidth - maxWidth;
|
|
86
|
-
colWidths[lastIdx] = Math.max(10, colWidths[lastIdx] - excess);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
const headerLine = headers.map((h, i) => padEnd(c.bold(String(h)), colWidths[i])).join(' ');
|
|
90
|
-
const separator = colWidths.map(w => c.dim('─'.repeat(w))).join('───');
|
|
91
|
-
|
|
92
|
-
console.log(` ${headerLine}`);
|
|
93
|
-
console.log(` ${separator}`);
|
|
94
|
-
|
|
95
|
-
for (const row of rows) {
|
|
96
|
-
const line = row.map((cell, i) => {
|
|
97
|
-
let s = String(cell ?? '');
|
|
98
|
-
if (visibleLength(s) > colWidths[i]) {
|
|
99
|
-
s = stripAnsi(s).slice(0, colWidths[i] - 1) + '…';
|
|
100
|
-
}
|
|
101
|
-
return padEnd(s, colWidths[i]);
|
|
102
|
-
}).join(' ');
|
|
103
|
-
console.log(` ${line}`);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function keyValue(pairs) {
|
|
108
|
-
const maxKey = pairs.reduce((max, [k]) => Math.max(max, k.length), 0);
|
|
109
|
-
for (const [key, value] of pairs) {
|
|
110
|
-
console.log(` ${c.bold(key.padEnd(maxKey))} ${value ?? c.dim('—')}`);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
function heading(text) {
|
|
115
|
-
console.log(`\n${c.bold(c.cyan(text))}`);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
function subheading(text) {
|
|
119
|
-
console.log(`\n ${c.bold(text)}`);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
function success(msg) {
|
|
123
|
-
console.log(`${c.green('✓')} ${msg}`);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
function error(msg) {
|
|
127
|
-
console.error(`${c.red('✗')} ${msg}`);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
function warn(msg) {
|
|
131
|
-
console.log(`${c.yellow('!')} ${msg}`);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
function info(msg) {
|
|
135
|
-
console.log(`${c.cyan('ℹ')} ${msg}`);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
function spinner(message) {
|
|
139
|
-
if (!process.stderr.isTTY) {
|
|
140
|
-
process.stderr.write(` ${message}...\n`);
|
|
141
|
-
return {
|
|
142
|
-
update() {},
|
|
143
|
-
stop(msg) { if (msg) process.stderr.write(` ${msg}\n`); },
|
|
144
|
-
fail(msg) { if (msg) process.stderr.write(` ${msg}\n`); },
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
149
|
-
let i = 0;
|
|
150
|
-
let currentMsg = message;
|
|
151
|
-
const interval = setInterval(() => {
|
|
152
|
-
const frame = c.cyan(frames[i++ % frames.length]);
|
|
153
|
-
process.stderr.write(`\r ${frame} ${currentMsg}`);
|
|
154
|
-
}, 80);
|
|
155
|
-
|
|
156
|
-
return {
|
|
157
|
-
update(msg) { currentMsg = msg; },
|
|
158
|
-
stop(finalMsg) {
|
|
159
|
-
clearInterval(interval);
|
|
160
|
-
process.stderr.write(`\r ${c.green('✓')} ${finalMsg || currentMsg}\n`);
|
|
161
|
-
},
|
|
162
|
-
fail(finalMsg) {
|
|
163
|
-
clearInterval(interval);
|
|
164
|
-
process.stderr.write(`\r ${c.red('✗')} ${finalMsg || currentMsg}\n`);
|
|
165
|
-
},
|
|
166
|
-
};
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
function prompt(question, opts = {}) {
|
|
170
|
-
const rl = readline.createInterface({
|
|
171
|
-
input: process.stdin,
|
|
172
|
-
output: process.stderr,
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
return new Promise((resolve) => {
|
|
176
|
-
const q = opts.secret ? question : ` ${c.cyan('?')} ${question} `;
|
|
177
|
-
rl.question(q, (answer) => {
|
|
178
|
-
rl.close();
|
|
179
|
-
resolve(answer.trim());
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
if (opts.secret) {
|
|
183
|
-
const onData = (char) => {
|
|
184
|
-
const code = char.toString();
|
|
185
|
-
if (code === '\n' || code === '\r' || code === '\u0004') return;
|
|
186
|
-
process.stderr.write('*');
|
|
187
|
-
};
|
|
188
|
-
process.stdin.on('data', onData);
|
|
189
|
-
rl.once('close', () => process.stdin.removeListener('data', onData));
|
|
190
|
-
}
|
|
191
|
-
});
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
async function confirm(question, defaultYes = false) {
|
|
195
|
-
const hint = defaultYes ? 'Y/n' : 'y/N';
|
|
196
|
-
const answer = await prompt(`${question} ${c.dim(`(${hint})`)}`);
|
|
197
|
-
if (!answer) return defaultYes;
|
|
198
|
-
return answer.toLowerCase().startsWith('y');
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
async function select(question, choices) {
|
|
202
|
-
console.log(`\n ${c.cyan('?')} ${question}\n`);
|
|
203
|
-
choices.forEach((choice, i) => {
|
|
204
|
-
console.log(` ${c.bold(String(i + 1))} ${choice.label || choice}`);
|
|
205
|
-
});
|
|
206
|
-
console.log('');
|
|
207
|
-
const answer = await prompt('Enter number');
|
|
208
|
-
const idx = parseInt(answer, 10) - 1;
|
|
209
|
-
if (idx < 0 || idx >= choices.length) {
|
|
210
|
-
error('Invalid selection');
|
|
211
|
-
process.exit(1);
|
|
212
|
-
}
|
|
213
|
-
return choices[idx].value ?? choices[idx];
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
async function multiSelect(question, choices) {
|
|
217
|
-
console.log(`\n ${c.cyan('?')} ${question}\n`);
|
|
218
|
-
choices.forEach((choice, i) => {
|
|
219
|
-
const label = choice.label || choice;
|
|
220
|
-
const detail = choice.detail ? c.dim(` — ${choice.detail}`) : '';
|
|
221
|
-
console.log(` ${c.bold(String(i + 1).padStart(3))} ${label}${detail}`);
|
|
222
|
-
});
|
|
223
|
-
console.log('');
|
|
224
|
-
console.log(c.dim(` Enter numbers separated by commas, a range (e.g. 1-5), or "all"`));
|
|
225
|
-
const answer = await prompt('Selection');
|
|
226
|
-
|
|
227
|
-
if (!answer) return [];
|
|
228
|
-
|
|
229
|
-
if (answer.toLowerCase() === 'all') {
|
|
230
|
-
return choices.map((ch) => ch.value ?? ch);
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
const indices = new Set();
|
|
234
|
-
for (const part of answer.split(',')) {
|
|
235
|
-
const trimmed = part.trim();
|
|
236
|
-
if (trimmed.includes('-')) {
|
|
237
|
-
const [startStr, endStr] = trimmed.split('-');
|
|
238
|
-
const start = parseInt(startStr, 10);
|
|
239
|
-
const end = parseInt(endStr, 10);
|
|
240
|
-
if (!isNaN(start) && !isNaN(end)) {
|
|
241
|
-
for (let i = Math.min(start, end); i <= Math.max(start, end); i++) {
|
|
242
|
-
indices.add(i);
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
} else {
|
|
246
|
-
const num = parseInt(trimmed, 10);
|
|
247
|
-
if (!isNaN(num)) indices.add(num);
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
const selected = [];
|
|
252
|
-
for (const idx of indices) {
|
|
253
|
-
if (idx >= 1 && idx <= choices.length) {
|
|
254
|
-
selected.push(choices[idx - 1].value ?? choices[idx - 1]);
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
return selected;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
function json(data) {
|
|
261
|
-
console.log(JSON.stringify(data, null, 2));
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
function hr() {
|
|
265
|
-
const width = Math.min(process.stdout.columns || 80, 80);
|
|
266
|
-
console.log(c.dim('─'.repeat(width)));
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
// Parses timestamps the backend returns in multiple formats:
|
|
270
|
-
// - UInt64 nanoseconds as a string (signoz logs) → "1775856777891000000"
|
|
271
|
-
// - ClickHouse DateTime64 string (signoz traces) → "2026-04-10 21:34:51.133000000" (UTC, no Z)
|
|
272
|
-
// - ISO 8601 → "2026-04-10T21:34:51.133Z"
|
|
273
|
-
// - number (ms) / Date → passthrough
|
|
274
|
-
function parseTimestamp(ts) {
|
|
275
|
-
if (ts == null || ts === '') return null;
|
|
276
|
-
if (ts instanceof Date) return isNaN(ts.getTime()) ? null : ts;
|
|
277
|
-
if (typeof ts === 'number') {
|
|
278
|
-
const d = new Date(ts);
|
|
279
|
-
return isNaN(d.getTime()) ? null : d;
|
|
280
|
-
}
|
|
281
|
-
const s = String(ts).trim();
|
|
282
|
-
if (/^\d{16,}$/.test(s)) {
|
|
283
|
-
const d = new Date(Number(s) / 1e6);
|
|
284
|
-
return isNaN(d.getTime()) ? null : d;
|
|
285
|
-
}
|
|
286
|
-
if (/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/.test(s) && !/[zZ]|[+-]\d{2}:?\d{2}$/.test(s)) {
|
|
287
|
-
const isoish = s.replace(' ', 'T').replace(/\.(\d{3})\d*$/, '.$1') + 'Z';
|
|
288
|
-
const d = new Date(isoish);
|
|
289
|
-
if (!isNaN(d.getTime())) return d;
|
|
290
|
-
}
|
|
291
|
-
const d = new Date(s);
|
|
292
|
-
return isNaN(d.getTime()) ? null : d;
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
function timeAgo(dateStr) {
|
|
296
|
-
const date = parseTimestamp(dateStr);
|
|
297
|
-
if (!date) return '—';
|
|
298
|
-
const diff = Date.now() - date.getTime();
|
|
299
|
-
const seconds = Math.floor(diff / 1000);
|
|
300
|
-
if (seconds < 60) return 'just now';
|
|
301
|
-
const minutes = Math.floor(seconds / 60);
|
|
302
|
-
if (minutes < 60) return `${minutes}m ago`;
|
|
303
|
-
const hours = Math.floor(minutes / 60);
|
|
304
|
-
if (hours < 24) return `${hours}h ago`;
|
|
305
|
-
const days = Math.floor(hours / 24);
|
|
306
|
-
if (days < 30) return `${days}d ago`;
|
|
307
|
-
return date.toLocaleDateString();
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
function truncate(str, len = 50) {
|
|
311
|
-
if (!str) return '';
|
|
312
|
-
str = String(str);
|
|
313
|
-
if (str.length <= len) return str;
|
|
314
|
-
return str.slice(0, len - 1) + '…';
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
function statusBadge(status) {
|
|
318
|
-
const map = {
|
|
319
|
-
active: c.green('● active'),
|
|
320
|
-
inactive: c.dim('○ inactive'),
|
|
321
|
-
healthy: c.green('● healthy'),
|
|
322
|
-
unhealthy: c.red('● unhealthy'),
|
|
323
|
-
warning: c.yellow('● warning'),
|
|
324
|
-
open: c.red('● open'),
|
|
325
|
-
resolved: c.green('● resolved'),
|
|
326
|
-
closed: c.dim('● closed'),
|
|
327
|
-
acknowledged: c.yellow('● ack'),
|
|
328
|
-
blocked: c.red('■ blocked'),
|
|
329
|
-
trusted: c.green('■ trusted'),
|
|
330
|
-
enabled: c.green('● enabled'),
|
|
331
|
-
disabled: c.dim('○ disabled'),
|
|
332
|
-
paused: c.yellow('◆ paused'),
|
|
333
|
-
critical: c.red('▲ critical'),
|
|
334
|
-
high: c.red('▲ high'),
|
|
335
|
-
medium: c.yellow('▲ medium'),
|
|
336
|
-
low: c.blue('▲ low'),
|
|
337
|
-
info: c.cyan('▲ info'),
|
|
338
|
-
read: c.dim('○ read'),
|
|
339
|
-
unread: c.cyan('● unread'),
|
|
340
|
-
};
|
|
341
|
-
return map[(status || '').toLowerCase()] || status;
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
function httpStatusColor(code) {
|
|
345
|
-
code = parseInt(code, 10);
|
|
346
|
-
if (code < 300) return c.green(code);
|
|
347
|
-
if (code < 400) return c.cyan(code);
|
|
348
|
-
if (code < 500) return c.yellow(code);
|
|
349
|
-
return c.red(code);
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
function durationColor(ms) {
|
|
353
|
-
ms = parseFloat(ms);
|
|
354
|
-
if (isNaN(ms)) return '—';
|
|
355
|
-
if (ms < 100) return c.green(`${ms.toFixed(0)}ms`);
|
|
356
|
-
if (ms < 500) return c.yellow(`${ms.toFixed(0)}ms`);
|
|
357
|
-
if (ms < 1000) return c.red(`${ms.toFixed(0)}ms`);
|
|
358
|
-
return c.red(`${(ms / 1000).toFixed(2)}s`);
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
module.exports = {
|
|
362
|
-
c,
|
|
363
|
-
NO_COLOR,
|
|
364
|
-
stripAnsi,
|
|
365
|
-
table,
|
|
366
|
-
keyValue,
|
|
367
|
-
heading,
|
|
368
|
-
subheading,
|
|
369
|
-
success,
|
|
370
|
-
error,
|
|
371
|
-
warn,
|
|
372
|
-
info,
|
|
373
|
-
spinner,
|
|
374
|
-
prompt,
|
|
375
|
-
confirm,
|
|
376
|
-
select,
|
|
377
|
-
multiSelect,
|
|
378
|
-
json,
|
|
379
|
-
hr,
|
|
380
|
-
timeAgo,
|
|
381
|
-
parseTimestamp,
|
|
382
|
-
truncate,
|
|
383
|
-
statusBadge,
|
|
384
|
-
httpStatusColor,
|
|
385
|
-
durationColor,
|
|
386
|
-
};
|
package/docs/API-KEYS-GUIDE.md
DELETED
|
@@ -1,233 +0,0 @@
|
|
|
1
|
-
# SecureNow API Keys
|
|
2
|
-
|
|
3
|
-
API keys provide programmatic access to the SecureNow platform. They support granular feature-level permissions, application scoping, IP allowlisting, and secure one-time-copy generation.
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## Creating an API Key
|
|
8
|
-
|
|
9
|
-
### From the Dashboard
|
|
10
|
-
|
|
11
|
-
1. Go to **Settings → API Keys**
|
|
12
|
-
2. Click **Create API Key**
|
|
13
|
-
3. Enter a name (e.g., "Production Firewall", "CI/CD Pipeline")
|
|
14
|
-
4. Select the scopes (permissions) you need
|
|
15
|
-
5. Optionally restrict to specific applications and IP addresses
|
|
16
|
-
6. Click **Create**
|
|
17
|
-
7. **Copy the key immediately** — it will only be shown once
|
|
18
|
-
|
|
19
|
-
### From the CLI
|
|
20
|
-
|
|
21
|
-
```bash
|
|
22
|
-
npx securenow login
|
|
23
|
-
npx securenow firewall status
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
The `securenow init` and `securenow login` commands can automatically provision a firewall API key for you.
|
|
27
|
-
|
|
28
|
-
---
|
|
29
|
-
|
|
30
|
-
## Key Format
|
|
31
|
-
|
|
32
|
-
All API keys use the format:
|
|
33
|
-
|
|
34
|
-
```
|
|
35
|
-
snk_live_<64 hex characters>
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
Example: `snk_live_a1b2c3d4e5f6...`
|
|
39
|
-
|
|
40
|
-
The `snk_live_` prefix makes it easy to identify SecureNow keys in your codebase and credential scanners.
|
|
41
|
-
|
|
42
|
-
---
|
|
43
|
-
|
|
44
|
-
## Scopes (Permissions)
|
|
45
|
-
|
|
46
|
-
Each API key has a set of scopes that control what it can access. Scopes follow the `resource:action` pattern.
|
|
47
|
-
|
|
48
|
-
| Scope | Description |
|
|
49
|
-
|-------|-------------|
|
|
50
|
-
| `firewall:read` | Read the blocklist (used by the firewall SDK) |
|
|
51
|
-
| `blocklist:read` | List and check blocked IPs |
|
|
52
|
-
| `blocklist:write` | Add and remove blocked IPs |
|
|
53
|
-
| `applications:read` | List and view applications |
|
|
54
|
-
| `applications:write` | Create and delete applications |
|
|
55
|
-
| `traces:read` | Query traces |
|
|
56
|
-
| `logs:read` | Query logs |
|
|
57
|
-
| `issues:read` | List and view security issues |
|
|
58
|
-
| `issues:write` | Resolve and manage issues |
|
|
59
|
-
| `alerts:read` | View alert rules, channels, and history |
|
|
60
|
-
| `alerts:write` | Create and manage alert rules |
|
|
61
|
-
| `analytics:read` | View analytics data |
|
|
62
|
-
| `forensics:read` | Run forensic queries |
|
|
63
|
-
| `ip:read` | IP intelligence lookups |
|
|
64
|
-
| `trusted:read` | List trusted IPs |
|
|
65
|
-
| `trusted:write` | Manage trusted IPs |
|
|
66
|
-
| `notifications:read` | List notifications |
|
|
67
|
-
| `notifications:write` | Mark notifications as read |
|
|
68
|
-
| `api-map:read` | View API map |
|
|
69
|
-
| `instances:read` | List instances |
|
|
70
|
-
| `false-positives:read` | List false positive rules |
|
|
71
|
-
| `false-positives:write` | Create and manage false positive rules |
|
|
72
|
-
|
|
73
|
-
### Principle of Least Privilege
|
|
74
|
-
|
|
75
|
-
Only grant the scopes your use case requires:
|
|
76
|
-
|
|
77
|
-
- **Firewall SDK:** `firewall:read`
|
|
78
|
-
- **CI/CD monitoring:** `issues:read`, `traces:read`
|
|
79
|
-
- **Automated remediation:** `blocklist:read`, `blocklist:write`
|
|
80
|
-
- **Dashboard integration:** all `*:read` scopes
|
|
81
|
-
|
|
82
|
-
---
|
|
83
|
-
|
|
84
|
-
## Application Scoping
|
|
85
|
-
|
|
86
|
-
Restrict an API key to specific applications. When set, the key can only access data for those applications.
|
|
87
|
-
|
|
88
|
-
Leave empty to allow access to all applications on your account.
|
|
89
|
-
|
|
90
|
-
**Alert rules:** Keys that are scoped to specific applications **cannot** create or update alert rules with **`applicationsAll: true`** (“all applications”). Use explicit app keys on each rule instead. Unscoped keys may use all-apps mode.
|
|
91
|
-
|
|
92
|
-
---
|
|
93
|
-
|
|
94
|
-
## IP Allowlisting
|
|
95
|
-
|
|
96
|
-
Restrict an API key to specific client IPs or CIDR ranges. When set, requests from other IPs are rejected with 403.
|
|
97
|
-
|
|
98
|
-
```
|
|
99
|
-
34.56.78.90
|
|
100
|
-
10.0.0.0/24
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
Leave empty to allow from any IP.
|
|
104
|
-
|
|
105
|
-
---
|
|
106
|
-
|
|
107
|
-
## Using API Keys
|
|
108
|
-
|
|
109
|
-
### In HTTP Requests
|
|
110
|
-
|
|
111
|
-
Pass the API key in the `Authorization` header:
|
|
112
|
-
|
|
113
|
-
```bash
|
|
114
|
-
curl -s https://api.securenow.ai/api/v1/blocklist \
|
|
115
|
-
-H "Authorization: Bearer snk_live_abc123..."
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
### In the Firewall SDK
|
|
119
|
-
|
|
120
|
-
Set the `SECURENOW_API_KEY` environment variable:
|
|
121
|
-
|
|
122
|
-
```bash
|
|
123
|
-
SECURENOW_API_KEY=snk_live_abc123...
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
The firewall SDK reads this automatically on startup.
|
|
127
|
-
|
|
128
|
-
### In CI/CD
|
|
129
|
-
|
|
130
|
-
```yaml
|
|
131
|
-
# GitHub Actions example — SDK firewall key
|
|
132
|
-
env:
|
|
133
|
-
SECURENOW_API_KEY: ${{ secrets.SECURENOW_API_KEY }}
|
|
134
|
-
|
|
135
|
-
steps:
|
|
136
|
-
- run: |
|
|
137
|
-
ISSUES=$(curl -s https://api.securenow.ai/api/v1/issues \
|
|
138
|
-
-H "Authorization: Bearer $SECURENOW_API_KEY")
|
|
139
|
-
echo "$ISSUES" | jq '.issues | length'
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
### CLI Authentication in CI/CD
|
|
143
|
-
|
|
144
|
-
For CLI commands in CI, use the `SECURENOW_TOKEN` env var to skip file-based login:
|
|
145
|
-
|
|
146
|
-
```yaml
|
|
147
|
-
# GitHub Actions example — CLI auth via env var
|
|
148
|
-
env:
|
|
149
|
-
SECURENOW_TOKEN: ${{ secrets.SECURENOW_CLI_TOKEN }}
|
|
150
|
-
|
|
151
|
-
steps:
|
|
152
|
-
- run: npx securenow issues --json --status open
|
|
153
|
-
- run: npx securenow forensics "critical attacks in last 24h" --json
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
The `SECURENOW_TOKEN` env var takes priority over any stored credentials.
|
|
157
|
-
|
|
158
|
-
---
|
|
159
|
-
|
|
160
|
-
## Key Management
|
|
161
|
-
|
|
162
|
-
### Viewing Keys
|
|
163
|
-
|
|
164
|
-
```bash
|
|
165
|
-
# From the CLI
|
|
166
|
-
npx securenow api-keys list
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
Or go to **Settings → API Keys** in the dashboard. You'll see the key name, last 4 characters, status, scopes, and last used timestamp.
|
|
170
|
-
|
|
171
|
-
### Revoking a Key
|
|
172
|
-
|
|
173
|
-
```bash
|
|
174
|
-
npx securenow api-keys revoke <key-id>
|
|
175
|
-
```
|
|
176
|
-
|
|
177
|
-
Or click **Revoke** in the dashboard. Revoked keys immediately stop working. This cannot be undone.
|
|
178
|
-
|
|
179
|
-
### Regenerating a Key
|
|
180
|
-
|
|
181
|
-
Regeneration creates a new key with the same name, scopes, and settings. The old key is automatically revoked.
|
|
182
|
-
|
|
183
|
-
---
|
|
184
|
-
|
|
185
|
-
## Rate Limits
|
|
186
|
-
|
|
187
|
-
API keys are subject to rate limiting:
|
|
188
|
-
|
|
189
|
-
| Endpoint | Limit |
|
|
190
|
-
|----------|-------|
|
|
191
|
-
| General API (`/api/*`) | 600 requests/minute |
|
|
192
|
-
| Firewall sync (`/api/firewall/*`) | 120 requests/minute |
|
|
193
|
-
|
|
194
|
-
Rate limit headers are included in every response:
|
|
195
|
-
|
|
196
|
-
```
|
|
197
|
-
X-RateLimit-Limit: 600
|
|
198
|
-
X-RateLimit-Remaining: 597
|
|
199
|
-
X-RateLimit-Reset: 1712534400
|
|
200
|
-
```
|
|
201
|
-
|
|
202
|
-
---
|
|
203
|
-
|
|
204
|
-
## Security Best Practices
|
|
205
|
-
|
|
206
|
-
1. **Never commit API keys to source control.** Use `.env` files or secret managers.
|
|
207
|
-
2. **Use the minimum scopes required.** A firewall key only needs `firewall:read`.
|
|
208
|
-
3. **Restrict by IP when possible.** Server keys should be locked to your server IPs.
|
|
209
|
-
4. **Rotate keys periodically.** Use the regenerate feature to rotate without downtime.
|
|
210
|
-
5. **Monitor usage.** Check the "last used" timestamp and known IPs in the dashboard.
|
|
211
|
-
6. **Revoke unused keys.** Delete keys that are no longer in use.
|
|
212
|
-
|
|
213
|
-
---
|
|
214
|
-
|
|
215
|
-
## API Versioning
|
|
216
|
-
|
|
217
|
-
All API endpoints are available at both `/api/` and `/api/v1/`. We recommend using the versioned path for stability:
|
|
218
|
-
|
|
219
|
-
```bash
|
|
220
|
-
# Recommended
|
|
221
|
-
https://api.securenow.ai/api/v1/blocklist
|
|
222
|
-
|
|
223
|
-
# Also works (unversioned)
|
|
224
|
-
https://api.securenow.ai/api/blocklist
|
|
225
|
-
```
|
|
226
|
-
|
|
227
|
-
---
|
|
228
|
-
|
|
229
|
-
## Related Documentation
|
|
230
|
-
|
|
231
|
-
- [Firewall Guide](./FIREWALL-GUIDE.md) — Automatic IP blocking setup
|
|
232
|
-
- [Environment Variables Reference](./ENVIRONMENT-VARIABLES.md) — All configuration options
|
|
233
|
-
- [All Frameworks Quick Start](./ALL-FRAMEWORKS-QUICKSTART.md) — Framework setup guides
|