securenow 7.5.0 → 7.6.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/CONSUMING-APPS-GUIDE.md +2 -0
- package/NPM_README.md +201 -237
- package/README.md +73 -26
- package/SKILL-API.md +209 -205
- package/SKILL-CLI.md +71 -64
- package/app-config.js +479 -83
- package/cli/apiKey.js +1 -1
- package/cli/apps.js +1 -1
- package/cli/config.js +31 -12
- package/cli/credentials.js +88 -0
- package/cli/diagnostics.js +81 -98
- package/cli/firewall.js +29 -14
- package/cli/init.js +246 -201
- package/cli/monitor.js +107 -43
- package/cli/security.js +24 -12
- package/cli/ui.js +22 -12
- package/cli/utils.js +2 -1
- package/cli.js +71 -39
- package/console-instrumentation.js +1 -1
- package/docs/ENVIRONMENT-VARIABLES.md +137 -863
- package/docs/ENVIRONMENTS.md +60 -0
- package/docs/EXPRESS-SETUP-GUIDE.md +3 -0
- package/docs/FIREWALL-GUIDE.md +3 -0
- package/docs/INDEX.md +6 -8
- package/docs/LOGGING-GUIDE.md +3 -0
- package/docs/MCP-GUIDE.md +8 -0
- package/docs/NEXTJS-GUIDE.md +3 -0
- package/docs/NEXTJS-QUICKSTART.md +24 -16
- package/docs/NUXT-GUIDE.md +3 -0
- package/docs/QUICKSTART-BODY-CAPTURE.md +3 -0
- package/docs/REQUEST-BODY-CAPTURE.md +3 -0
- package/firewall-cloud.js +10 -10
- package/firewall-only.js +25 -23
- package/firewall.js +47 -29
- package/free-trial-banner.js +1 -1
- package/mcp/catalog.js +104 -17
- package/nextjs-auto-capture.d.ts +7 -4
- package/nextjs-auto-capture.js +7 -7
- package/nextjs-middleware.js +4 -3
- package/nextjs-wrapper.js +6 -6
- package/nextjs.d.ts +36 -25
- package/nextjs.js +47 -55
- package/nuxt-server-plugin.mjs +35 -51
- package/nuxt.d.ts +29 -23
- package/package.json +1 -1
- package/postinstall.js +27 -61
- package/register.d.ts +19 -33
- package/register.js +8 -8
- package/resolve-ip.js +4 -5
- package/tracing.d.ts +21 -19
- package/tracing.js +34 -42
package/cli/init.js
CHANGED
|
@@ -1,201 +1,246 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const ui = require('./ui');
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
if (allDeps.
|
|
31
|
-
if (allDeps.
|
|
32
|
-
if (allDeps.
|
|
33
|
-
if (allDeps.
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const
|
|
101
|
-
if (
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
ui.
|
|
120
|
-
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
const
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const ui = require('./ui');
|
|
6
|
+
const config = require('./config');
|
|
7
|
+
|
|
8
|
+
const INSTRUMENTATION = `import { createRequire } from 'node:module';
|
|
9
|
+
|
|
10
|
+
const require = createRequire(import.meta.url);
|
|
11
|
+
|
|
12
|
+
export async function register() {
|
|
13
|
+
if (process.env.NEXT_RUNTIME !== 'nodejs') return;
|
|
14
|
+
|
|
15
|
+
const { registerSecureNow } = require('securenow/nextjs');
|
|
16
|
+
registerSecureNow({ captureBody: true });
|
|
17
|
+
require('securenow/nextjs-auto-capture');
|
|
18
|
+
}
|
|
19
|
+
`;
|
|
20
|
+
|
|
21
|
+
function detectProject(dir) {
|
|
22
|
+
const pkgPath = path.join(dir, 'package.json');
|
|
23
|
+
if (!fs.existsSync(pkgPath)) return { framework: 'unknown' };
|
|
24
|
+
|
|
25
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8').replace(/^\uFEFF/, ''));
|
|
26
|
+
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
27
|
+
|
|
28
|
+
if (allDeps.next) return { framework: 'nextjs', pkg, pkgPath, nextVersion: allDeps.next };
|
|
29
|
+
if (allDeps.nuxt) return { framework: 'nuxt', pkg, pkgPath };
|
|
30
|
+
if (allDeps.express) return { framework: 'express', pkg, pkgPath };
|
|
31
|
+
if (allDeps.fastify) return { framework: 'fastify', pkg, pkgPath };
|
|
32
|
+
if (allDeps.koa) return { framework: 'koa', pkg, pkgPath };
|
|
33
|
+
if (allDeps.hapi || allDeps['@hapi/hapi']) return { framework: 'hapi', pkg, pkgPath };
|
|
34
|
+
return { framework: 'node', pkg, pkgPath };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function findInstrumentationFile(dir) {
|
|
38
|
+
for (const name of ['instrumentation.ts', 'instrumentation.js', 'src/instrumentation.ts', 'src/instrumentation.js']) {
|
|
39
|
+
const p = path.join(dir, name);
|
|
40
|
+
if (fs.existsSync(p)) return p;
|
|
41
|
+
}
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function findNextConfig(dir) {
|
|
46
|
+
for (const name of ['next.config.js', 'next.config.mjs', 'next.config.ts']) {
|
|
47
|
+
const p = path.join(dir, name);
|
|
48
|
+
if (fs.existsSync(p)) return p;
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function hasTypeScript(dir) {
|
|
54
|
+
return fs.existsSync(path.join(dir, 'tsconfig.json'));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function nextMajor(project) {
|
|
58
|
+
const raw = String(project.nextVersion || '').replace(/^[^\d]*/, '');
|
|
59
|
+
const major = parseInt(raw, 10);
|
|
60
|
+
return Number.isFinite(major) ? major : 15;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function init(_args, flags) {
|
|
64
|
+
const dir = process.cwd();
|
|
65
|
+
const project = detectProject(dir);
|
|
66
|
+
|
|
67
|
+
ui.header('SecureNow Project Setup');
|
|
68
|
+
|
|
69
|
+
if (project.framework === 'unknown') {
|
|
70
|
+
ui.error('No package.json found. Run this command in your project root.');
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
ui.info(`Detected framework: ${ui.bold(project.framework)}`);
|
|
75
|
+
initCredentials(flags);
|
|
76
|
+
|
|
77
|
+
if (project.framework === 'nextjs') {
|
|
78
|
+
await initNextJs(dir, project, flags);
|
|
79
|
+
} else if (project.framework === 'nuxt') {
|
|
80
|
+
initNuxt(dir);
|
|
81
|
+
} else {
|
|
82
|
+
initNode(project);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
console.log('');
|
|
86
|
+
ui.success('Setup complete.');
|
|
87
|
+
ui.info('Run `npx securenow login` if this project is not linked to an app yet.');
|
|
88
|
+
ui.info('Then verify with `npx securenow test-span` and `npx securenow status`.');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function initCredentials(flags) {
|
|
92
|
+
config.ensureCredentialDefaults({ local: true });
|
|
93
|
+
config.ensureLocalGitignore();
|
|
94
|
+
const creds = config.loadCredentials();
|
|
95
|
+
creds.config = creds.config || {};
|
|
96
|
+
creds.config.runtime = creds.config.runtime || {};
|
|
97
|
+
creds.config.runtime.deploymentEnvironment = flags.env || flags.environment || 'local';
|
|
98
|
+
config.saveCredentials(creds, { local: true });
|
|
99
|
+
|
|
100
|
+
const explicitApiKey = flags.key || flags['api-key'] || '';
|
|
101
|
+
if (explicitApiKey) {
|
|
102
|
+
if (!String(explicitApiKey).startsWith('snk_live_')) {
|
|
103
|
+
ui.error('--key must start with snk_live_');
|
|
104
|
+
process.exit(1);
|
|
105
|
+
}
|
|
106
|
+
config.setApiKey(explicitApiKey, { local: true });
|
|
107
|
+
ui.success('Stored firewall API key in .securenow/credentials.json');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
ui.success('Ensured .securenow/credentials.json has secure defaults and explanations');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function initNextJs(dir, project, flags) {
|
|
114
|
+
const useTs = flags.javascript ? false : flags.typescript ? true : hasTypeScript(dir);
|
|
115
|
+
const ext = useTs ? 'ts' : 'js';
|
|
116
|
+
|
|
117
|
+
const existing = findInstrumentationFile(dir);
|
|
118
|
+
if (existing) {
|
|
119
|
+
ui.info(`instrumentation file already exists: ${path.relative(dir, existing)}`);
|
|
120
|
+
printAgentPrompt('instrumentation', path.basename(existing), nextMajor(project));
|
|
121
|
+
} else {
|
|
122
|
+
const filePath = path.join(dir, `instrumentation.${ext}`);
|
|
123
|
+
fs.writeFileSync(filePath, INSTRUMENTATION, 'utf8');
|
|
124
|
+
ui.success(`Created instrumentation.${ext}`);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const configPath = findNextConfig(dir);
|
|
128
|
+
if (configPath) {
|
|
129
|
+
const patched = patchNextConfig(configPath, nextMajor(project));
|
|
130
|
+
if (patched === 'already') {
|
|
131
|
+
ui.info(`${path.basename(configPath)} already externalizes securenow`);
|
|
132
|
+
} else if (patched === 'patched') {
|
|
133
|
+
ui.success(`Updated ${path.basename(configPath)} with SecureNow server externalization`);
|
|
134
|
+
} else {
|
|
135
|
+
ui.warn(`Could not safely edit ${path.basename(configPath)} automatically.`);
|
|
136
|
+
printAgentPrompt('next-config', path.basename(configPath), nextMajor(project));
|
|
137
|
+
}
|
|
138
|
+
} else {
|
|
139
|
+
const newConfigPath = path.join(dir, 'next.config.mjs');
|
|
140
|
+
fs.writeFileSync(newConfigPath, `/** @type {import('next').NextConfig} */
|
|
141
|
+
const nextConfig = {
|
|
142
|
+
serverExternalPackages: ['securenow'],
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
export default nextConfig;
|
|
146
|
+
`, 'utf8');
|
|
147
|
+
ui.success('Created next.config.mjs');
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function patchNextConfig(configPath, major) {
|
|
152
|
+
const content = fs.readFileSync(configPath, 'utf8');
|
|
153
|
+
const serverExternalWithSecureNow = /serverExternalPackages\s*:\s*\[[\s\S]*?['"]securenow['"][\s\S]*?\]/m.test(content);
|
|
154
|
+
const serverComponentsWithSecureNow = /serverComponentsExternalPackages\s*:\s*\[[\s\S]*?['"]securenow['"][\s\S]*?\]/m.test(content);
|
|
155
|
+
if (serverExternalWithSecureNow || serverComponentsWithSecureNow || content.includes('withSecureNow(')) {
|
|
156
|
+
return 'already';
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (major < 15) return 'manual';
|
|
160
|
+
|
|
161
|
+
const existingServerExternal = content.match(/serverExternalPackages\s*:\s*\[([\s\S]*?)\]/m);
|
|
162
|
+
if (existingServerExternal) {
|
|
163
|
+
const current = existingServerExternal[1].trim().replace(/,\s*$/, '');
|
|
164
|
+
const replacement = `serverExternalPackages: [${current ? `${current}, ` : ''}'securenow']`;
|
|
165
|
+
fs.writeFileSync(configPath, content.replace(existingServerExternal[0], replacement), 'utf8');
|
|
166
|
+
return 'patched';
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const insert = ` serverExternalPackages: ['securenow'],\n`;
|
|
170
|
+
const patterns = [
|
|
171
|
+
/(const\s+nextConfig\s*=\s*{\s*\r?\n)/,
|
|
172
|
+
/(export\s+default\s+{\s*\r?\n)/,
|
|
173
|
+
/(module\.exports\s*=\s*{\s*\r?\n)/,
|
|
174
|
+
];
|
|
175
|
+
|
|
176
|
+
for (const pattern of patterns) {
|
|
177
|
+
if (pattern.test(content)) {
|
|
178
|
+
fs.writeFileSync(configPath, content.replace(pattern, `$1${insert}`), 'utf8');
|
|
179
|
+
return 'patched';
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return 'manual';
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function printAgentPrompt(kind, filename, major) {
|
|
187
|
+
console.log('');
|
|
188
|
+
ui.heading('Codex/Claude prompt');
|
|
189
|
+
console.log([
|
|
190
|
+
'Set up SecureNow in this existing Next.js project without using .env files.',
|
|
191
|
+
'Use .securenow/credentials.json for local and production configuration; do not add .env files.',
|
|
192
|
+
kind === 'instrumentation'
|
|
193
|
+
? `Merge this into ${filename}: in register(), return unless process.env.NEXT_RUNTIME === "nodejs"; use createRequire(import.meta.url); require("securenow/nextjs").registerSecureNow({ captureBody: true }); then require("securenow/nextjs-auto-capture"). Preserve all existing instrumentation.`
|
|
194
|
+
: null,
|
|
195
|
+
kind === 'next-config' && major >= 15
|
|
196
|
+
? `Update ${filename} while preserving existing config: add securenow to serverExternalPackages, e.g. serverExternalPackages: [...(existing || []), "securenow"].`
|
|
197
|
+
: null,
|
|
198
|
+
kind === 'next-config' && major < 15
|
|
199
|
+
? `Update ${filename} while preserving existing config: enable experimental.instrumentationHook and add securenow to experimental.serverComponentsExternalPackages.`
|
|
200
|
+
: null,
|
|
201
|
+
'Verify with: npx securenow env, npx securenow test-span, npx securenow status, and the project build command.',
|
|
202
|
+
].filter(Boolean).join('\n'));
|
|
203
|
+
console.log('');
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function initNuxt(dir) {
|
|
207
|
+
const configPath = path.join(dir, 'nuxt.config.ts');
|
|
208
|
+
if (fs.existsSync(configPath)) {
|
|
209
|
+
const content = fs.readFileSync(configPath, 'utf8');
|
|
210
|
+
if (content.includes('securenow/nuxt')) {
|
|
211
|
+
ui.info('nuxt.config already references securenow/nuxt');
|
|
212
|
+
} else {
|
|
213
|
+
ui.warn('Add securenow/nuxt to your nuxt.config modules array.');
|
|
214
|
+
}
|
|
215
|
+
} else {
|
|
216
|
+
ui.warn('Add securenow/nuxt to your nuxt.config modules array.');
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function initNode(project) {
|
|
221
|
+
const scripts = project.pkg.scripts || {};
|
|
222
|
+
const startScript = scripts.start || '';
|
|
223
|
+
if (startScript.includes('securenow/register')) {
|
|
224
|
+
ui.info('start script already uses securenow/register');
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (startScript) {
|
|
229
|
+
ui.warn('Update your start script to include the securenow preload:');
|
|
230
|
+
console.log('');
|
|
231
|
+
if (startScript.includes('node ')) {
|
|
232
|
+
const updated = startScript.replace('node ', 'node -r securenow/register ');
|
|
233
|
+
console.log(` "start": "${updated}"`);
|
|
234
|
+
} else {
|
|
235
|
+
console.log(` "start": "node -r securenow/register ${startScript.replace(/^node\s+/, '')}"`);
|
|
236
|
+
}
|
|
237
|
+
console.log('');
|
|
238
|
+
} else {
|
|
239
|
+
ui.warn('Add a start script with the securenow preload:');
|
|
240
|
+
console.log('');
|
|
241
|
+
console.log(' "start": "node -r securenow/register src/index.js"');
|
|
242
|
+
console.log('');
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
module.exports = { init };
|