securenow 7.5.1 → 7.6.1
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 +205 -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 +68 -104
- package/cli/firewall.js +29 -14
- package/cli/init.js +211 -212
- package/cli/monitor.js +107 -43
- package/cli/security.js +24 -12
- package/cli/utils.js +2 -1
- package/cli.js +72 -40
- 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 +22 -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 +48 -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
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# SecureNow Environments
|
|
2
|
+
|
|
3
|
+
SecureNow uses one app id for one application, then separates data by deployment environment.
|
|
4
|
+
|
|
5
|
+
## Recommended Model
|
|
6
|
+
|
|
7
|
+
- Use the same `app.key` for local, preview, staging, and production.
|
|
8
|
+
- Set `config.runtime.deploymentEnvironment` in `.securenow/credentials.json`.
|
|
9
|
+
- Default local setup writes `local`.
|
|
10
|
+
- Production runtime credentials should write `production`.
|
|
11
|
+
- The SDK sends this value as the OpenTelemetry `deployment.environment` resource attribute.
|
|
12
|
+
- The firewall sync sends the same environment to SecureNow so app firewall settings can differ per environment.
|
|
13
|
+
|
|
14
|
+
Example local file:
|
|
15
|
+
|
|
16
|
+
```json
|
|
17
|
+
{
|
|
18
|
+
"app": {
|
|
19
|
+
"key": "00000000-0000-0000-0000-000000000000",
|
|
20
|
+
"name": "my-app",
|
|
21
|
+
"instance": "https://freetrial.securenow.ai:4318"
|
|
22
|
+
},
|
|
23
|
+
"config": {
|
|
24
|
+
"runtime": {
|
|
25
|
+
"deploymentEnvironment": "local"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Example production flow:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npx securenow credentials runtime --env production
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
This writes `.securenow/credentials.production.json`. Deploy the generated JSON as a secret file and mount or copy it to:
|
|
38
|
+
|
|
39
|
+
```text
|
|
40
|
+
<app-root>/.securenow/credentials.json
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Investigation Defaults
|
|
44
|
+
|
|
45
|
+
Forensics, firewall status, and security investigation tools default to `production`. Use `--env local`, `--env staging`, or `--env all` when you explicitly want another scope.
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
npx securenow traces --app <app-key> --env production
|
|
49
|
+
npx securenow logs --app <app-key> --env local
|
|
50
|
+
npx securenow forensics "show suspicious IPs in the last hour" --app <app-key> --env production
|
|
51
|
+
npx securenow firewall disable --app <app-key> --env local
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Firewall Defaults
|
|
55
|
+
|
|
56
|
+
- Production defaults to firewall on.
|
|
57
|
+
- Local, preview, staging, and test default to firewall off until explicitly enabled.
|
|
58
|
+
- Blocklists and allowlists are still app-scoped, but the app firewall toggle and threshold are environment-scoped.
|
|
59
|
+
|
|
60
|
+
This keeps local development friction low while preserving production as the primary security boundary.
|
|
@@ -717,3 +717,6 @@ NODE_ENV=production
|
|
|
717
717
|
---
|
|
718
718
|
|
|
719
719
|
**Your Express app is now fully observable!** 🎉
|
|
720
|
+
# Current setup note
|
|
721
|
+
|
|
722
|
+
Use `.securenow/credentials.json` for local and production. Run `npx securenow login`, `npx securenow init`, and for production generate `npx securenow credentials runtime --env production`; mount/copy that file as `.securenow/credentials.json`. Env-var examples in this older guide are legacy fallback snippets.
|
package/docs/FIREWALL-GUIDE.md
CHANGED
|
@@ -434,3 +434,6 @@ npx securenow firewall test-ip 1.2.3.4
|
|
|
434
434
|
- [Environment Variables Reference](./ENVIRONMENT-VARIABLES.md) — All configuration options
|
|
435
435
|
- [All Frameworks Quick Start](./ALL-FRAMEWORKS-QUICKSTART.md) — Framework setup guides
|
|
436
436
|
- [Automatic IP Capture](./AUTOMATIC-IP-CAPTURE.md) — How client IPs are resolved
|
|
437
|
+
# Current setup note
|
|
438
|
+
|
|
439
|
+
Use `.securenow/credentials.json` for local and production. `npx securenow login` writes the firewall key locally; `npx securenow credentials runtime --env production` creates the tokenless production file to mount/copy as `.securenow/credentials.json`. Env-var examples in this older guide are legacy fallback snippets.
|
package/docs/INDEX.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# SecureNow Documentation
|
|
2
2
|
|
|
3
|
-
Complete documentation for SecureNow - OpenTelemetry instrumentation for Node.js, Next.js, and Nuxt with tracing and
|
|
3
|
+
Complete documentation for SecureNow - OpenTelemetry instrumentation for Node.js, Next.js, and Nuxt with tracing, logging, body capture, and firewall protection.
|
|
4
|
+
|
|
5
|
+
**Current setup model:** local and production configuration live in `.securenow/credentials.json`. Run `npx securenow login`, then `npx securenow init`. For production, generate a tokenless runtime file with `npx securenow credentials runtime --env production` and mount/copy it to `.securenow/credentials.json`. Older guides may mention env vars as legacy fallbacks; new integrations should use the credentials file.
|
|
4
6
|
|
|
5
7
|
## 📚 Table of Contents
|
|
6
8
|
|
|
@@ -65,12 +67,8 @@ Complete documentation for SecureNow - OpenTelemetry instrumentation for Node.js
|
|
|
65
67
|
|
|
66
68
|
### ⚙️ Configuration
|
|
67
69
|
|
|
68
|
-
- **[
|
|
69
|
-
|
|
70
|
-
- Optional variables
|
|
71
|
-
- Configuration examples
|
|
72
|
-
- Best practices
|
|
73
|
-
- Priority and overrides
|
|
70
|
+
- **[Credentials Reference](ENVIRONMENT-VARIABLES.md)** - `.securenow/credentials.json` fields, secure defaults, and legacy fallbacks
|
|
71
|
+
- **[Environment Separation](ENVIRONMENTS.md)** - One app id across local, preview, staging, and production
|
|
74
72
|
|
|
75
73
|
### 🔐 Next.js Body Capture
|
|
76
74
|
|
|
@@ -141,7 +139,7 @@ Complete documentation for SecureNow - OpenTelemetry instrumentation for Node.js
|
|
|
141
139
|
### "I want to block malicious IPs automatically"
|
|
142
140
|
1. Start with [Firewall Guide](FIREWALL-GUIDE.md)
|
|
143
141
|
2. Create an API key: [API Keys Guide](API-KEYS-GUIDE.md)
|
|
144
|
-
3.
|
|
142
|
+
3. Review credentials fields: [Credentials Reference](ENVIRONMENT-VARIABLES.md)
|
|
145
143
|
|
|
146
144
|
### "I need to capture IP addresses and headers"
|
|
147
145
|
1. Read [Automatic IP Capture](AUTOMATIC-IP-CAPTURE.md)
|
package/docs/LOGGING-GUIDE.md
CHANGED
|
@@ -699,3 +699,6 @@ logger.emit({
|
|
|
699
699
|
- **Issues**: [GitHub Issues](https://github.com/your-repo/securenow-npm)
|
|
700
700
|
- **Documentation**: [Full Docs](./INDEX.md)
|
|
701
701
|
- **Website**: [securenow.ai](http://securenow.ai/)
|
|
702
|
+
# Current setup note
|
|
703
|
+
|
|
704
|
+
Use `.securenow/credentials.json` for local and production. Run `npx securenow login`, `npx securenow init`, and for production generate `npx securenow credentials runtime --env production`; mount/copy that file as `.securenow/credentials.json`. Env-var examples in this older guide are legacy fallback snippets.
|
package/docs/MCP-GUIDE.md
CHANGED
|
@@ -20,6 +20,14 @@ npx -p securenow securenow-mcp
|
|
|
20
20
|
The local MCP server reads the same project-local `.securenow/credentials.json`
|
|
21
21
|
as the CLI and SDK. No production deployment is required.
|
|
22
22
|
|
|
23
|
+
## Environment Scope
|
|
24
|
+
|
|
25
|
+
MCP tools use the same environment model as the CLI and UI: one app key across
|
|
26
|
+
local, preview, staging, and production, separated by
|
|
27
|
+
`config.runtime.deploymentEnvironment`. Investigation tools default to
|
|
28
|
+
`production`. Pass `environment: "local"`, `"staging"`, `"preview"`, or
|
|
29
|
+
`"all"` only when that scope is intentional.
|
|
30
|
+
|
|
23
31
|
## Hosted MCP
|
|
24
32
|
|
|
25
33
|
For hosted clients, expose the secured API endpoint:
|
package/docs/NEXTJS-GUIDE.md
CHANGED
|
@@ -386,4 +386,7 @@ ISC
|
|
|
386
386
|
---
|
|
387
387
|
|
|
388
388
|
**Made with ❤️ for the Next.js and SecureNow community**
|
|
389
|
+
# Current setup note
|
|
390
|
+
|
|
391
|
+
Use `.securenow/credentials.json` for local and production. Run `npx securenow login`, `npx securenow init`, and for production generate `npx securenow credentials runtime --env production`; mount/copy that file as `.securenow/credentials.json`. Env-var examples in this older guide are legacy fallback snippets.
|
|
389
392
|
|
|
@@ -9,7 +9,7 @@ npm install securenow
|
|
|
9
9
|
# 2. Pick (or create) your app in the browser — writes .securenow/ locally
|
|
10
10
|
npx securenow login
|
|
11
11
|
|
|
12
|
-
# 3. Scaffold instrumentation.ts and
|
|
12
|
+
# 3. Scaffold instrumentation.ts and update next.config.js
|
|
13
13
|
npx securenow init
|
|
14
14
|
|
|
15
15
|
# 4. Run
|
|
@@ -25,23 +25,27 @@ No `.env.local` edits. No API key copy-paste. The app you picked in step 2 is wh
|
|
|
25
25
|
**`instrumentation.ts`** (or `.js`, auto-detected):
|
|
26
26
|
|
|
27
27
|
```typescript
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
export async function register() {
|
|
29
|
+
if (process.env.NEXT_RUNTIME !== 'nodejs') return;
|
|
30
|
+
|
|
31
|
+
const securenowNext = await import(/* webpackIgnore: true */ 'securenow/nextjs');
|
|
32
|
+
const registerSecureNow = securenowNext.registerSecureNow || securenowNext.default?.registerSecureNow;
|
|
33
|
+
registerSecureNow({ captureBody: true });
|
|
34
|
+
await import(/* webpackIgnore: true */ 'securenow/nextjs-auto-capture');
|
|
31
35
|
}
|
|
32
36
|
```
|
|
33
37
|
|
|
34
|
-
|
|
38
|
+
For Next.js 15+, `init` adds `securenow` to `serverExternalPackages` when it can safely patch the file:
|
|
35
39
|
|
|
36
40
|
```javascript
|
|
37
|
-
const
|
|
41
|
+
const nextConfig = {
|
|
42
|
+
serverExternalPackages: ['securenow'],
|
|
43
|
+
};
|
|
38
44
|
|
|
39
|
-
|
|
40
|
-
// your existing config
|
|
41
|
-
});
|
|
45
|
+
export default nextConfig;
|
|
42
46
|
```
|
|
43
47
|
|
|
44
|
-
|
|
48
|
+
For older Next.js, use `experimental.serverComponentsExternalPackages`. If you already have custom `instrumentation.*` or a complex `next.config.*`, `init` prints a Codex/Claude-ready prompt with the exact edits to merge instead of guessing.
|
|
45
49
|
|
|
46
50
|
---
|
|
47
51
|
|
|
@@ -57,21 +61,23 @@ Start your app. In the console you should see:
|
|
|
57
61
|
Then:
|
|
58
62
|
|
|
59
63
|
```bash
|
|
60
|
-
npx securenow test-span #
|
|
61
|
-
npx securenow traces
|
|
64
|
+
npx securenow test-span # emits with config.runtime.deploymentEnvironment
|
|
65
|
+
npx securenow traces --env local
|
|
66
|
+
npx securenow status --env local
|
|
62
67
|
```
|
|
63
68
|
|
|
64
69
|
If `traces` shows your span under the app name you picked, you're done.
|
|
65
70
|
|
|
66
71
|
---
|
|
67
72
|
|
|
68
|
-
##
|
|
73
|
+
## Production / Docker / Vercel
|
|
69
74
|
|
|
70
|
-
|
|
75
|
+
Production uses the same credentials shape. Generate a tokenless runtime file:
|
|
71
76
|
|
|
72
77
|
```bash
|
|
73
|
-
|
|
74
|
-
SECURENOW_INSTANCE=https://freetrial.securenow.ai:4318
|
|
78
|
+
npx securenow credentials runtime --env production
|
|
75
79
|
```
|
|
76
80
|
|
|
81
|
+
Deploy `.securenow/credentials.production.json` as a secret file and mount or copy it to `<app-root>/.securenow/credentials.json`. It contains `app`, `apiKey`, `config`, and explanations, but no CLI OAuth token.
|
|
82
|
+
|
|
77
83
|
See [NEXTJS-GUIDE.md](./NEXTJS-GUIDE.md) for Vercel, standalone builds, and edge runtime details.
|
package/docs/NUXT-GUIDE.md
CHANGED
|
@@ -168,3 +168,6 @@ export default defineNuxtConfig({
|
|
|
168
168
|
},
|
|
169
169
|
});
|
|
170
170
|
```
|
|
171
|
+
# Current setup note
|
|
172
|
+
|
|
173
|
+
Use `.securenow/credentials.json` for local and production. Run `npx securenow login`, `npx securenow init`, and for production generate `npx securenow credentials runtime --env production`; mount/copy that file as `.securenow/credentials.json`. Env-var examples in this older guide are legacy fallback snippets.
|
|
@@ -288,3 +288,6 @@ export const POST = withSecureNow(handler);
|
|
|
288
288
|
|
|
289
289
|
|
|
290
290
|
|
|
291
|
+
# Current setup note
|
|
292
|
+
|
|
293
|
+
Use `.securenow/credentials.json` for local and production. Body capture defaults live under `config.capture.*`; run `npx securenow init` to create secure defaults. Env-var examples in this older guide are legacy fallback snippets.
|
|
@@ -581,4 +581,7 @@ SECURENOW_SENSITIVE_FIELDS="" # Don't do this!
|
|
|
581
581
|
---
|
|
582
582
|
|
|
583
583
|
**Made with security in mind** 🔒
|
|
584
|
+
# Current setup note
|
|
585
|
+
|
|
586
|
+
Use `.securenow/credentials.json` for local and production. Body capture defaults live under `config.capture.*`; run `npx securenow init` to create secure defaults. Env-var examples in this older guide are legacy fallback snippets.
|
|
584
587
|
|
package/firewall-cloud.js
CHANGED
|
@@ -21,10 +21,10 @@ const MIN_PUSH_INTERVAL_MS = 30000;
|
|
|
21
21
|
|
|
22
22
|
// ────── Cloudflare ──────
|
|
23
23
|
|
|
24
|
-
async function cloudflareSync(ips) {
|
|
25
|
-
const token =
|
|
26
|
-
const accountId =
|
|
27
|
-
if (!token || !accountId) throw new Error('Missing CLOUDFLARE_API_TOKEN or CLOUDFLARE_ACCOUNT_ID');
|
|
24
|
+
async function cloudflareSync(ips) {
|
|
25
|
+
const token = _options?.cloudflare?.apiToken;
|
|
26
|
+
const accountId = _options?.cloudflare?.accountId;
|
|
27
|
+
if (!token || !accountId) throw new Error('Missing CLOUDFLARE_API_TOKEN or CLOUDFLARE_ACCOUNT_ID');
|
|
28
28
|
|
|
29
29
|
const listName = 'securenow-blocklist';
|
|
30
30
|
|
|
@@ -82,9 +82,9 @@ async function awsSync(ips) {
|
|
|
82
82
|
throw new Error('AWS WAF requires @aws-sdk/client-wafv2 — install it: npm i @aws-sdk/client-wafv2');
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
-
const ipSetId =
|
|
86
|
-
const ipSetName =
|
|
87
|
-
const scope =
|
|
85
|
+
const ipSetId = _options?.aws?.wafIpSetId;
|
|
86
|
+
const ipSetName = _options?.aws?.wafIpSetName || 'securenow-blocklist';
|
|
87
|
+
const scope = _options?.aws?.wafScope || 'REGIONAL';
|
|
88
88
|
if (!ipSetId) throw new Error('Missing AWS_WAF_IP_SET_ID');
|
|
89
89
|
|
|
90
90
|
const client = new WAFV2.WAFV2Client({});
|
|
@@ -117,8 +117,8 @@ async function gcpSync(ips) {
|
|
|
117
117
|
throw new Error('GCP Cloud Armor requires @google-cloud/compute — install it: npm i @google-cloud/compute');
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
-
const project =
|
|
121
|
-
const policyName =
|
|
120
|
+
const project = _options?.gcp?.projectId;
|
|
121
|
+
const policyName = _options?.gcp?.securityPolicy;
|
|
122
122
|
if (!project || !policyName) throw new Error('Missing GCP_PROJECT_ID or GCP_SECURITY_POLICY');
|
|
123
123
|
|
|
124
124
|
const client = new compute.SecurityPoliciesClient();
|
|
@@ -191,7 +191,7 @@ async function sync(ips) {
|
|
|
191
191
|
if (now - _lastPushTime < MIN_PUSH_INTERVAL_MS) return;
|
|
192
192
|
_lastPushTime = now;
|
|
193
193
|
|
|
194
|
-
const dryRun =
|
|
194
|
+
const dryRun = _options?.cloudDryRun === true;
|
|
195
195
|
if (dryRun) {
|
|
196
196
|
if (_options.log) console.log('[securenow] Firewall cloud: dry-run — would push %d IPs to %s', ips.length, _provider);
|
|
197
197
|
return;
|
package/firewall-only.js
CHANGED
|
@@ -7,36 +7,38 @@
|
|
|
7
7
|
* node -r securenow/firewall-only app.js
|
|
8
8
|
* NODE_OPTIONS='-r securenow/firewall-only' next start
|
|
9
9
|
*
|
|
10
|
-
* Reads .
|
|
11
|
-
*
|
|
10
|
+
* Reads .securenow/credentials.json first, with legacy env vars supported only
|
|
11
|
+
* as fallbacks. Initialises the HTTP-level firewall when a snk_live_ API key
|
|
12
|
+
* is resolvable.
|
|
12
13
|
*/
|
|
13
14
|
|
|
14
15
|
try { require('dotenv').config(); } catch (_) {}
|
|
15
16
|
|
|
16
|
-
const
|
|
17
|
-
|
|
17
|
+
const appConfig = require('./app-config');
|
|
18
|
+
const firewallOptions = appConfig.resolveFirewallOptions();
|
|
18
19
|
|
|
19
|
-
|
|
20
|
-
const firewallApiKey = resolveApiKey();
|
|
21
|
-
const appKey = resolveAppKey();
|
|
22
|
-
|
|
23
|
-
// SECURENOW_FIREWALL_ENABLED=0 is a hard local override (ops escape hatch).
|
|
20
|
+
// config.firewall.enabled=false disables the local SDK firewall.
|
|
24
21
|
// In all other cases the toggle lives in the dashboard; default ON when an
|
|
25
22
|
// API key is present.
|
|
26
|
-
if (
|
|
27
|
-
require('./firewall').init({
|
|
28
|
-
apiKey:
|
|
29
|
-
appKey: appKey
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
23
|
+
if (firewallOptions.apiKey && firewallOptions.enabled) {
|
|
24
|
+
require('./firewall').init({
|
|
25
|
+
apiKey: firewallOptions.apiKey,
|
|
26
|
+
appKey: firewallOptions.appKey,
|
|
27
|
+
environment: firewallOptions.environment,
|
|
28
|
+
apiUrl: firewallOptions.apiUrl,
|
|
29
|
+
versionCheckInterval: firewallOptions.versionCheckInterval,
|
|
30
|
+
syncInterval: firewallOptions.syncInterval,
|
|
31
|
+
failMode: firewallOptions.failMode,
|
|
32
|
+
statusCode: firewallOptions.statusCode,
|
|
33
|
+
log: firewallOptions.log,
|
|
34
|
+
tcp: firewallOptions.tcp,
|
|
35
|
+
iptables: firewallOptions.iptables,
|
|
36
|
+
cloud: firewallOptions.cloud,
|
|
37
|
+
cloudDryRun: firewallOptions.cloudDryRun,
|
|
38
|
+
cloudflare: firewallOptions.cloudflare,
|
|
39
|
+
aws: firewallOptions.aws,
|
|
40
|
+
gcp: firewallOptions.gcp,
|
|
41
|
+
});
|
|
40
42
|
|
|
41
43
|
const shutdown = () => { try { require('./firewall').shutdown(); } catch (_) {} };
|
|
42
44
|
process.on('SIGINT', shutdown);
|
package/firewall.js
CHANGED
|
@@ -98,9 +98,17 @@ function handleRetryAfter(res) {
|
|
|
98
98
|
|
|
99
99
|
// ────── HTTP helpers ──────
|
|
100
100
|
|
|
101
|
-
function buildUrl(apiUrl, path) {
|
|
102
|
-
return apiUrl.replace(/\/+$/, '') + '/api/v1' + path;
|
|
103
|
-
}
|
|
101
|
+
function buildUrl(apiUrl, path) {
|
|
102
|
+
return apiUrl.replace(/\/+$/, '') + '/api/v1' + path;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function buildFirewallUrl(path) {
|
|
106
|
+
const query = new URLSearchParams();
|
|
107
|
+
if (_options && _options.appKey) query.set('app', _options.appKey);
|
|
108
|
+
if (_options && _options.environment) query.set('env', _options.environment);
|
|
109
|
+
const suffix = query.toString() ? `?${query.toString()}` : '';
|
|
110
|
+
return buildUrl(_options.apiUrl, path) + suffix;
|
|
111
|
+
}
|
|
104
112
|
|
|
105
113
|
function jitter(baseMs) {
|
|
106
114
|
return baseMs * (0.8 + Math.random() * 0.4);
|
|
@@ -139,10 +147,14 @@ function httpGet(url, extraHeaders, timeout, callback) {
|
|
|
139
147
|
|
|
140
148
|
// ────── Unified Sync (v2 — single request for everything) ──────
|
|
141
149
|
|
|
142
|
-
function doUnifiedSync(callback) {
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
150
|
+
function doUnifiedSync(callback) {
|
|
151
|
+
const query = new URLSearchParams();
|
|
152
|
+
if (_options.appKey) query.set('app', _options.appKey);
|
|
153
|
+
if (_options.environment) query.set('env', _options.environment);
|
|
154
|
+
const suffix = query.toString() ? `?${query.toString()}` : '';
|
|
155
|
+
const url = buildUrl(_options.apiUrl, '/firewall/sync') + suffix;
|
|
156
|
+
const headers = {};
|
|
157
|
+
if (_options.environment) headers['X-SecureNow-Environment'] = _options.environment;
|
|
146
158
|
if (_lastVersion) headers['X-Blocklist-Version'] = _lastVersion;
|
|
147
159
|
if (_lastAllowlistVersion) headers['X-Allowlist-Version'] = _lastAllowlistVersion;
|
|
148
160
|
|
|
@@ -229,9 +241,10 @@ function doUnifiedSync(callback) {
|
|
|
229
241
|
|
|
230
242
|
// ────── Legacy Sync (v1 — separate endpoints, kept for backward compat) ──────
|
|
231
243
|
|
|
232
|
-
function legacyBlocklistSync(callback) {
|
|
233
|
-
const url =
|
|
234
|
-
const headers = {};
|
|
244
|
+
function legacyBlocklistSync(callback) {
|
|
245
|
+
const url = buildFirewallUrl('/firewall/blocklist');
|
|
246
|
+
const headers = {};
|
|
247
|
+
if (_options.environment) headers['X-SecureNow-Environment'] = _options.environment;
|
|
235
248
|
if (_lastSyncEtag) headers['If-None-Match'] = _lastSyncEtag;
|
|
236
249
|
else if (_lastModified) headers['If-Modified-Since'] = _lastModified;
|
|
237
250
|
|
|
@@ -257,9 +270,10 @@ function legacyBlocklistSync(callback) {
|
|
|
257
270
|
});
|
|
258
271
|
}
|
|
259
272
|
|
|
260
|
-
function legacyAllowlistSync(callback) {
|
|
261
|
-
const url =
|
|
262
|
-
const headers = {};
|
|
273
|
+
function legacyAllowlistSync(callback) {
|
|
274
|
+
const url = buildFirewallUrl('/firewall/allowlist');
|
|
275
|
+
const headers = {};
|
|
276
|
+
if (_options.environment) headers['X-SecureNow-Environment'] = _options.environment;
|
|
263
277
|
if (_lastAllowlistSyncEtag) headers['If-None-Match'] = _lastAllowlistSyncEtag;
|
|
264
278
|
else if (_lastAllowlistModified) headers['If-Modified-Since'] = _lastAllowlistModified;
|
|
265
279
|
|
|
@@ -283,9 +297,10 @@ function legacyAllowlistSync(callback) {
|
|
|
283
297
|
});
|
|
284
298
|
}
|
|
285
299
|
|
|
286
|
-
function legacyVersionCheck(callback) {
|
|
287
|
-
const url =
|
|
288
|
-
const headers = {};
|
|
300
|
+
function legacyVersionCheck(callback) {
|
|
301
|
+
const url = buildFirewallUrl('/firewall/blocklist/version');
|
|
302
|
+
const headers = {};
|
|
303
|
+
if (_options.environment) headers['X-SecureNow-Environment'] = _options.environment;
|
|
289
304
|
if (_lastVersion) headers['If-None-Match'] = _lastVersion;
|
|
290
305
|
|
|
291
306
|
httpGet(url, headers, 5000, (err, res, data) => {
|
|
@@ -305,9 +320,10 @@ function legacyVersionCheck(callback) {
|
|
|
305
320
|
});
|
|
306
321
|
}
|
|
307
322
|
|
|
308
|
-
function legacyAllowlistVersionCheck(callback) {
|
|
309
|
-
const url =
|
|
310
|
-
const headers = {};
|
|
323
|
+
function legacyAllowlistVersionCheck(callback) {
|
|
324
|
+
const url = buildFirewallUrl('/firewall/allowlist/version');
|
|
325
|
+
const headers = {};
|
|
326
|
+
if (_options.environment) headers['X-SecureNow-Environment'] = _options.environment;
|
|
311
327
|
if (_lastAllowlistVersion) headers['If-None-Match'] = _lastAllowlistVersion;
|
|
312
328
|
|
|
313
329
|
httpGet(url, headers, 5000, (err, res, data) => {
|
|
@@ -655,9 +671,10 @@ function patchHttpLayer() {
|
|
|
655
671
|
// ────── Init ──────
|
|
656
672
|
|
|
657
673
|
function init(options) {
|
|
658
|
-
_options = options;
|
|
659
|
-
|
|
660
|
-
if (_options.log) console.log('[securenow] Firewall: ENABLED');
|
|
674
|
+
_options = options;
|
|
675
|
+
|
|
676
|
+
if (_options.log) console.log('[securenow] Firewall: ENABLED');
|
|
677
|
+
if (_options.log && _options.environment) console.log('[securenow] Firewall: environment=%s', _options.environment);
|
|
661
678
|
|
|
662
679
|
patchHttpLayer();
|
|
663
680
|
if (_options.log) console.log('[securenow] Firewall: Layer 1 (HTTP 403) active');
|
|
@@ -672,7 +689,7 @@ function init(options) {
|
|
|
672
689
|
if (_options.log) console.warn('[securenow] Firewall: Layer 2 (TCP drop) failed:', e.message);
|
|
673
690
|
}
|
|
674
691
|
} else {
|
|
675
|
-
if (_options.log) console.log('[securenow] Firewall: Layer 2 (TCP drop) disabled (set
|
|
692
|
+
if (_options.log) console.log('[securenow] Firewall: Layer 2 (TCP drop) disabled (set config.firewall.tcp=true)');
|
|
676
693
|
}
|
|
677
694
|
|
|
678
695
|
if (_options.iptables) {
|
|
@@ -685,7 +702,7 @@ function init(options) {
|
|
|
685
702
|
if (_options.log) console.warn('[securenow] Firewall: Layer 3 (iptables) failed:', e.message);
|
|
686
703
|
}
|
|
687
704
|
} else {
|
|
688
|
-
if (_options.log) console.log('[securenow] Firewall: Layer 3 (iptables) disabled (set
|
|
705
|
+
if (_options.log) console.log('[securenow] Firewall: Layer 3 (iptables) disabled (set config.firewall.iptables=true)');
|
|
689
706
|
}
|
|
690
707
|
|
|
691
708
|
if (_options.cloud) {
|
|
@@ -698,7 +715,7 @@ function init(options) {
|
|
|
698
715
|
if (_options.log) console.warn('[securenow] Firewall: Layer 4 (Cloud WAF) failed:', e.message);
|
|
699
716
|
}
|
|
700
717
|
} else {
|
|
701
|
-
if (_options.log) console.log('[securenow] Firewall: Layer 4 (Cloud WAF) disabled (set
|
|
718
|
+
if (_options.log) console.log('[securenow] Firewall: Layer 4 (Cloud WAF) disabled (set config.firewall.cloud=cloudflare|aws|gcp)');
|
|
702
719
|
}
|
|
703
720
|
|
|
704
721
|
startSyncLoop();
|
|
@@ -742,10 +759,11 @@ function getStats() {
|
|
|
742
759
|
circuitState: _circuitState,
|
|
743
760
|
consecutiveErrors: _consecutiveErrors,
|
|
744
761
|
unifiedSync: _useUnifiedSync,
|
|
745
|
-
remoteEnabled: _remoteEnabled,
|
|
746
|
-
appKey: _options ? _options.appKey || null : null,
|
|
747
|
-
|
|
748
|
-
}
|
|
762
|
+
remoteEnabled: _remoteEnabled,
|
|
763
|
+
appKey: _options ? _options.appKey || null : null,
|
|
764
|
+
environment: _options ? _options.environment || null : null,
|
|
765
|
+
};
|
|
766
|
+
}
|
|
749
767
|
|
|
750
768
|
// Layers (TCP / iptables / cloud) read the matcher to populate kernel-level
|
|
751
769
|
// rules. When the remote toggle is off, return null so they treat the policy
|
package/free-trial-banner.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Free Trial Banner — auto-injects a visible "Testing Environment" banner
|
|
5
5
|
* into HTML pages served by apps using the SecureNow free trial instance.
|
|
6
6
|
*
|
|
7
|
-
* Opt-out: set
|
|
7
|
+
* Opt-out: set config.runtime.hideBanner=true in .securenow/credentials.json
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
const FREETRIAL_HOST = 'freetrial.securenow.ai';
|