@node9/proxy 1.25.0 → 1.26.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/dist/cli.js +111 -18
- package/dist/cli.mjs +111 -18
- package/dist/dashboard.mjs +44 -8
- package/dist/index.js +44 -8
- package/dist/index.mjs +44 -8
- package/dist/scan-ink.mjs +43 -7
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -2273,6 +2273,42 @@ var init_dist = __esm({
|
|
|
2273
2273
|
regex: /\bAGE-SECRET-KEY-1[QPZRY9X8GF2TVDW0S3JNLH]{58}\b/,
|
|
2274
2274
|
severity: "block",
|
|
2275
2275
|
keywords: ["age-secret-key-"]
|
|
2276
|
+
},
|
|
2277
|
+
// ── Database connection strings ───────────────────────────────────────────
|
|
2278
|
+
// Universal <scheme>://[user]:<password>@<host> shape. Covers the gap
|
|
2279
|
+
// vendor-prefix patterns (AWS / GitHub / Stripe / …) leave open. Matches
|
|
2280
|
+
// the whole URL so maskSecret produces `<scheme>...:****@...<host>` —
|
|
2281
|
+
// the password value never appears in the redacted sample.
|
|
2282
|
+
//
|
|
2283
|
+
// Schemes covered: redis, rediss (TLS), postgres, postgresql,
|
|
2284
|
+
// mongodb, mongodb+srv, mysql, mariadb, amqp, amqps, kafka,
|
|
2285
|
+
// clickhouse, cassandra. HTTP(S) / FTP / SSH are intentionally
|
|
2286
|
+
// excluded — they're not database URLs and adding them would
|
|
2287
|
+
// create false positives on every basic-auth URL in the wild.
|
|
2288
|
+
//
|
|
2289
|
+
// Requires `:password@` (4+ char password) so user-only URLs like
|
|
2290
|
+
// `redis://user@host` don't match. Stopwords ('your', '${', '<your',
|
|
2291
|
+
// 'placeholder', 'changeme', etc.) keep doc/README scans clean.
|
|
2292
|
+
{
|
|
2293
|
+
name: "Database Connection String",
|
|
2294
|
+
regex: /\b(redis|rediss|postgres|postgresql|mongodb|mongodb\+srv|mysql|mariadb|amqp|amqps|kafka|clickhouse|cassandra):\/\/[^:/\s@]*:[^@\s]{4,}@[^\s/]+/,
|
|
2295
|
+
severity: "block",
|
|
2296
|
+
keywords: [
|
|
2297
|
+
"redis://",
|
|
2298
|
+
"rediss://",
|
|
2299
|
+
"postgres://",
|
|
2300
|
+
"postgresql://",
|
|
2301
|
+
"mongodb://",
|
|
2302
|
+
"mongodb+srv://",
|
|
2303
|
+
"mysql://",
|
|
2304
|
+
"mariadb://",
|
|
2305
|
+
"amqp://",
|
|
2306
|
+
"amqps://",
|
|
2307
|
+
"kafka://",
|
|
2308
|
+
"clickhouse://",
|
|
2309
|
+
"cassandra://"
|
|
2310
|
+
],
|
|
2311
|
+
minEntropy: 3
|
|
2276
2312
|
}
|
|
2277
2313
|
];
|
|
2278
2314
|
DLP_PATTERNS_GLOBAL = DLP_PATTERNS.map(
|
|
@@ -2375,7 +2411,7 @@ var init_dist = __esm({
|
|
|
2375
2411
|
},
|
|
2376
2412
|
{
|
|
2377
2413
|
// Mirrors the JSON shield's `.env` pattern (project-jail.json's
|
|
2378
|
-
//
|
|
2414
|
+
// block-read-env-any-tool) so the AST FS-op path catches the
|
|
2379
2415
|
// same set the regex shield does — including Next.js / Vite's
|
|
2380
2416
|
// `.env.<env>.local` double-suffix overrides which are commonly
|
|
2381
2417
|
// gitignored AND commonly contain real secrets.
|
|
@@ -3121,7 +3157,7 @@ var init_dist = __esm({
|
|
|
3121
3157
|
{
|
|
3122
3158
|
field: "command",
|
|
3123
3159
|
op: "matches",
|
|
3124
|
-
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type)\\s+.*?\\.ssh[\\/\\\\]",
|
|
3160
|
+
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|sort|uniq|tac|nl|dd)\\s+.*?\\.ssh[\\/\\\\]",
|
|
3125
3161
|
flags: "i"
|
|
3126
3162
|
}
|
|
3127
3163
|
],
|
|
@@ -3135,7 +3171,7 @@ var init_dist = __esm({
|
|
|
3135
3171
|
{
|
|
3136
3172
|
field: "command",
|
|
3137
3173
|
op: "matches",
|
|
3138
|
-
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type)\\s+.*?\\.aws[\\/\\\\]",
|
|
3174
|
+
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|sort|uniq|tac|nl|dd)\\s+.*?\\.aws[\\/\\\\]",
|
|
3139
3175
|
flags: "i"
|
|
3140
3176
|
}
|
|
3141
3177
|
],
|
|
@@ -3149,7 +3185,7 @@ var init_dist = __esm({
|
|
|
3149
3185
|
{
|
|
3150
3186
|
field: "command",
|
|
3151
3187
|
op: "matches",
|
|
3152
|
-
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type)\\s
|
|
3188
|
+
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|sort|uniq|tac|nl|dd)\\s+.*?\\.env(\\.(local|production|staging|development|production\\.local|staging\\.local|development\\.local))?(?=\\s|$|[;&|>)<])",
|
|
3153
3189
|
flags: "i"
|
|
3154
3190
|
}
|
|
3155
3191
|
],
|
|
@@ -3163,7 +3199,7 @@ var init_dist = __esm({
|
|
|
3163
3199
|
{
|
|
3164
3200
|
field: "command",
|
|
3165
3201
|
op: "matches",
|
|
3166
|
-
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type)\\s+.*(credentials\\.json|\\.netrc|\\.npmrc|\\.docker[\\/\\\\]config\\.json|gcloud[\\/\\\\]credentials)",
|
|
3202
|
+
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|sort|uniq|tac|nl|dd)\\s+.*(credentials\\.json|\\.netrc|\\.npmrc|\\.docker[\\/\\\\]config\\.json|gcloud[\\/\\\\]credentials)",
|
|
3167
3203
|
flags: "i"
|
|
3168
3204
|
}
|
|
3169
3205
|
],
|
|
@@ -3199,7 +3235,7 @@ var init_dist = __esm({
|
|
|
3199
3235
|
reason: "Reading AWS credentials is blocked by project-jail shield"
|
|
3200
3236
|
},
|
|
3201
3237
|
{
|
|
3202
|
-
name: "shield:project-jail:
|
|
3238
|
+
name: "shield:project-jail:block-read-env-any-tool",
|
|
3203
3239
|
tool: "*",
|
|
3204
3240
|
conditions: [
|
|
3205
3241
|
{
|
|
@@ -3209,8 +3245,8 @@ var init_dist = __esm({
|
|
|
3209
3245
|
flags: "i"
|
|
3210
3246
|
}
|
|
3211
3247
|
],
|
|
3212
|
-
verdict: "
|
|
3213
|
-
reason: "Reading .env files
|
|
3248
|
+
verdict: "block",
|
|
3249
|
+
reason: "Reading .env files is blocked by project-jail shield"
|
|
3214
3250
|
},
|
|
3215
3251
|
{
|
|
3216
3252
|
name: "shield:project-jail:review-read-credentials-any-tool",
|
|
@@ -3471,6 +3507,30 @@ function writeShieldOverride(shieldName, ruleName, verdict) {
|
|
|
3471
3507
|
overrides[shieldName] = { ...overrides[shieldName] ?? {}, [ruleName]: verdict };
|
|
3472
3508
|
writeShieldsFile({ ...current, overrides });
|
|
3473
3509
|
}
|
|
3510
|
+
function migrateRenamedRuleKeys() {
|
|
3511
|
+
const current = readShieldsFile();
|
|
3512
|
+
if (!current.overrides || Object.keys(current.overrides).length === 0) return [];
|
|
3513
|
+
const renameMap = new Map(RULE_KEY_MIGRATIONS);
|
|
3514
|
+
const migrated = [];
|
|
3515
|
+
const nextOverrides = {};
|
|
3516
|
+
let anyChange = false;
|
|
3517
|
+
for (const [shield, rules] of Object.entries(current.overrides)) {
|
|
3518
|
+
const nextRules = {};
|
|
3519
|
+
for (const [key, verdict] of Object.entries(rules)) {
|
|
3520
|
+
const newKey = renameMap.get(key);
|
|
3521
|
+
if (newKey) {
|
|
3522
|
+
if (!(newKey in nextRules)) nextRules[newKey] = verdict;
|
|
3523
|
+
migrated.push({ shield, oldKey: key, newKey });
|
|
3524
|
+
anyChange = true;
|
|
3525
|
+
} else {
|
|
3526
|
+
nextRules[key] = verdict;
|
|
3527
|
+
}
|
|
3528
|
+
}
|
|
3529
|
+
if (Object.keys(nextRules).length > 0) nextOverrides[shield] = nextRules;
|
|
3530
|
+
}
|
|
3531
|
+
if (anyChange) writeShieldsFile({ ...current, overrides: nextOverrides });
|
|
3532
|
+
return migrated;
|
|
3533
|
+
}
|
|
3474
3534
|
function clearShieldOverride(shieldName, ruleName) {
|
|
3475
3535
|
const current = readShieldsFile();
|
|
3476
3536
|
if (!current.overrides?.[shieldName]?.[ruleName]) return;
|
|
@@ -3519,7 +3579,7 @@ function installShield(name, shieldJson) {
|
|
|
3519
3579
|
import_fs2.default.writeFileSync(tmp, JSON.stringify(shieldJson, null, 2), { mode: 384 });
|
|
3520
3580
|
import_fs2.default.renameSync(tmp, filePath);
|
|
3521
3581
|
}
|
|
3522
|
-
var import_fs2, import_path2, import_os2, import_crypto3, USER_SHIELDS_DIR, SHIELDS, SHIELDS_STATE_FILE;
|
|
3582
|
+
var import_fs2, import_path2, import_os2, import_crypto3, USER_SHIELDS_DIR, SHIELDS, SHIELDS_STATE_FILE, RULE_KEY_MIGRATIONS;
|
|
3523
3583
|
var init_shields = __esm({
|
|
3524
3584
|
"src/shields.ts"() {
|
|
3525
3585
|
"use strict";
|
|
@@ -3532,6 +3592,14 @@ var init_shields = __esm({
|
|
|
3532
3592
|
USER_SHIELDS_DIR = import_path2.default.join(import_os2.default.homedir(), ".node9", "shields");
|
|
3533
3593
|
SHIELDS = buildSHIELDS();
|
|
3534
3594
|
SHIELDS_STATE_FILE = import_path2.default.join(import_os2.default.homedir(), ".node9", "shields.json");
|
|
3595
|
+
RULE_KEY_MIGRATIONS = [
|
|
3596
|
+
// 2026-05-21 — project-jail .env reads promoted review → block. Renamed
|
|
3597
|
+
// so the key honestly describes the verdict. Credential-file rules
|
|
3598
|
+
// (.netrc, .npmrc, .docker, .kube, gcloud) deliberately stay at `review`
|
|
3599
|
+
// — see the rationale in packages/policy-engine/src/shell/index.ts
|
|
3600
|
+
// around the review-read-credentials rule.
|
|
3601
|
+
["shield:project-jail:review-read-env-any-tool", "shield:project-jail:block-read-env-any-tool"]
|
|
3602
|
+
];
|
|
3535
3603
|
}
|
|
3536
3604
|
});
|
|
3537
3605
|
|
|
@@ -7035,6 +7103,18 @@ function claudeDesktopConfigPath(homeDir2 = import_os12.default.homedir()) {
|
|
|
7035
7103
|
}
|
|
7036
7104
|
return null;
|
|
7037
7105
|
}
|
|
7106
|
+
function binaryInPath(binary) {
|
|
7107
|
+
const pathEnv = process.env.PATH ?? "";
|
|
7108
|
+
for (const dir of pathEnv.split(import_path15.default.delimiter)) {
|
|
7109
|
+
if (!dir) continue;
|
|
7110
|
+
try {
|
|
7111
|
+
import_fs13.default.accessSync(import_path15.default.join(dir, binary), import_fs13.default.constants.X_OK);
|
|
7112
|
+
return true;
|
|
7113
|
+
} catch {
|
|
7114
|
+
}
|
|
7115
|
+
}
|
|
7116
|
+
return false;
|
|
7117
|
+
}
|
|
7038
7118
|
function detectAgents(homeDir2 = import_os12.default.homedir()) {
|
|
7039
7119
|
const exists = (p) => {
|
|
7040
7120
|
try {
|
|
@@ -7057,7 +7137,9 @@ function detectAgents(homeDir2 = import_os12.default.homedir()) {
|
|
|
7057
7137
|
windsurf: exists(import_path15.default.join(homeDir2, ".codeium", "windsurf")),
|
|
7058
7138
|
vscode: exists(import_path15.default.join(homeDir2, ".vscode")),
|
|
7059
7139
|
claudeDesktop: desktopPath !== null && exists(import_path15.default.dirname(desktopPath)),
|
|
7060
|
-
|
|
7140
|
+
// Opencode creates ~/.config/opencode lazily on first launch — fall back
|
|
7141
|
+
// to a PATH lookup so installed-but-never-launched CLIs are still wired.
|
|
7142
|
+
opencode: exists(import_path15.default.join(homeDir2, ".config", "opencode")) || binaryInPath("opencode")
|
|
7061
7143
|
};
|
|
7062
7144
|
}
|
|
7063
7145
|
async function setupCursor() {
|
|
@@ -18744,14 +18826,18 @@ init_setup();
|
|
|
18744
18826
|
init_shields();
|
|
18745
18827
|
init_service();
|
|
18746
18828
|
var DEFAULT_SHIELDS = ["bash-safe", "filesystem", "project-jail"];
|
|
18747
|
-
function
|
|
18829
|
+
function buildTelemetryPayload(agents, firstInstall) {
|
|
18830
|
+
return {
|
|
18831
|
+
event: "init_completed",
|
|
18832
|
+
agents_detected: agents,
|
|
18833
|
+
os: process.platform,
|
|
18834
|
+
node9_version: node9Version(),
|
|
18835
|
+
first_install: firstInstall
|
|
18836
|
+
};
|
|
18837
|
+
}
|
|
18838
|
+
function fireTelemetryPing(agents, firstInstall) {
|
|
18748
18839
|
try {
|
|
18749
|
-
const body = JSON.stringify(
|
|
18750
|
-
event: "init_completed",
|
|
18751
|
-
agents_detected: agents,
|
|
18752
|
-
os: process.platform,
|
|
18753
|
-
node9_version: process.env.npm_package_version ?? "unknown"
|
|
18754
|
-
});
|
|
18840
|
+
const body = JSON.stringify(buildTelemetryPayload(agents, firstInstall));
|
|
18755
18841
|
const req = import_https4.default.request(
|
|
18756
18842
|
{
|
|
18757
18843
|
hostname: "api.node9.ai",
|
|
@@ -18784,6 +18870,12 @@ function registerInitCommand(program2) {
|
|
|
18784
18870
|
).action(
|
|
18785
18871
|
async (options) => {
|
|
18786
18872
|
console.log(import_chalk16.default.cyan.bold("\n\u{1F6E1}\uFE0F Node9 Init\n"));
|
|
18873
|
+
{
|
|
18874
|
+
const migrated = migrateRenamedRuleKeys();
|
|
18875
|
+
for (const m of migrated) {
|
|
18876
|
+
console.log(import_chalk16.default.dim(` \u{1F527} Rule renamed: ${m.oldKey} \u2192 ${m.newKey}`));
|
|
18877
|
+
}
|
|
18878
|
+
}
|
|
18787
18879
|
let chosenMode = options.mode.toLowerCase();
|
|
18788
18880
|
if (!["standard", "strict", "audit", "observe"].includes(chosenMode)) {
|
|
18789
18881
|
chosenMode = DEFAULT_CONFIG.settings.mode;
|
|
@@ -18818,6 +18910,7 @@ function registerInitCommand(program2) {
|
|
|
18818
18910
|
console.log("");
|
|
18819
18911
|
}
|
|
18820
18912
|
const configPath = import_path39.default.join(import_os34.default.homedir(), ".node9", "config.json");
|
|
18913
|
+
const isFirstInstall = !import_fs38.default.existsSync(configPath);
|
|
18821
18914
|
if (import_fs38.default.existsSync(configPath) && !options.force) {
|
|
18822
18915
|
try {
|
|
18823
18916
|
const existing = JSON.parse(import_fs38.default.readFileSync(configPath, "utf-8"));
|
|
@@ -18921,7 +19014,7 @@ function registerInitCommand(program2) {
|
|
|
18921
19014
|
message: "Send anonymous usage stats to help improve node9? (no code, no args)",
|
|
18922
19015
|
default: true
|
|
18923
19016
|
});
|
|
18924
|
-
if (sendTelemetry) fireTelemetryPing(found);
|
|
19017
|
+
if (sendTelemetry) fireTelemetryPing(found, isFirstInstall);
|
|
18925
19018
|
console.log("");
|
|
18926
19019
|
}
|
|
18927
19020
|
const agentList = found.join(", ");
|
package/dist/cli.mjs
CHANGED
|
@@ -2251,6 +2251,42 @@ var init_dist = __esm({
|
|
|
2251
2251
|
regex: /\bAGE-SECRET-KEY-1[QPZRY9X8GF2TVDW0S3JNLH]{58}\b/,
|
|
2252
2252
|
severity: "block",
|
|
2253
2253
|
keywords: ["age-secret-key-"]
|
|
2254
|
+
},
|
|
2255
|
+
// ── Database connection strings ───────────────────────────────────────────
|
|
2256
|
+
// Universal <scheme>://[user]:<password>@<host> shape. Covers the gap
|
|
2257
|
+
// vendor-prefix patterns (AWS / GitHub / Stripe / …) leave open. Matches
|
|
2258
|
+
// the whole URL so maskSecret produces `<scheme>...:****@...<host>` —
|
|
2259
|
+
// the password value never appears in the redacted sample.
|
|
2260
|
+
//
|
|
2261
|
+
// Schemes covered: redis, rediss (TLS), postgres, postgresql,
|
|
2262
|
+
// mongodb, mongodb+srv, mysql, mariadb, amqp, amqps, kafka,
|
|
2263
|
+
// clickhouse, cassandra. HTTP(S) / FTP / SSH are intentionally
|
|
2264
|
+
// excluded — they're not database URLs and adding them would
|
|
2265
|
+
// create false positives on every basic-auth URL in the wild.
|
|
2266
|
+
//
|
|
2267
|
+
// Requires `:password@` (4+ char password) so user-only URLs like
|
|
2268
|
+
// `redis://user@host` don't match. Stopwords ('your', '${', '<your',
|
|
2269
|
+
// 'placeholder', 'changeme', etc.) keep doc/README scans clean.
|
|
2270
|
+
{
|
|
2271
|
+
name: "Database Connection String",
|
|
2272
|
+
regex: /\b(redis|rediss|postgres|postgresql|mongodb|mongodb\+srv|mysql|mariadb|amqp|amqps|kafka|clickhouse|cassandra):\/\/[^:/\s@]*:[^@\s]{4,}@[^\s/]+/,
|
|
2273
|
+
severity: "block",
|
|
2274
|
+
keywords: [
|
|
2275
|
+
"redis://",
|
|
2276
|
+
"rediss://",
|
|
2277
|
+
"postgres://",
|
|
2278
|
+
"postgresql://",
|
|
2279
|
+
"mongodb://",
|
|
2280
|
+
"mongodb+srv://",
|
|
2281
|
+
"mysql://",
|
|
2282
|
+
"mariadb://",
|
|
2283
|
+
"amqp://",
|
|
2284
|
+
"amqps://",
|
|
2285
|
+
"kafka://",
|
|
2286
|
+
"clickhouse://",
|
|
2287
|
+
"cassandra://"
|
|
2288
|
+
],
|
|
2289
|
+
minEntropy: 3
|
|
2254
2290
|
}
|
|
2255
2291
|
];
|
|
2256
2292
|
DLP_PATTERNS_GLOBAL = DLP_PATTERNS.map(
|
|
@@ -2353,7 +2389,7 @@ var init_dist = __esm({
|
|
|
2353
2389
|
},
|
|
2354
2390
|
{
|
|
2355
2391
|
// Mirrors the JSON shield's `.env` pattern (project-jail.json's
|
|
2356
|
-
//
|
|
2392
|
+
// block-read-env-any-tool) so the AST FS-op path catches the
|
|
2357
2393
|
// same set the regex shield does — including Next.js / Vite's
|
|
2358
2394
|
// `.env.<env>.local` double-suffix overrides which are commonly
|
|
2359
2395
|
// gitignored AND commonly contain real secrets.
|
|
@@ -3099,7 +3135,7 @@ var init_dist = __esm({
|
|
|
3099
3135
|
{
|
|
3100
3136
|
field: "command",
|
|
3101
3137
|
op: "matches",
|
|
3102
|
-
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type)\\s+.*?\\.ssh[\\/\\\\]",
|
|
3138
|
+
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|sort|uniq|tac|nl|dd)\\s+.*?\\.ssh[\\/\\\\]",
|
|
3103
3139
|
flags: "i"
|
|
3104
3140
|
}
|
|
3105
3141
|
],
|
|
@@ -3113,7 +3149,7 @@ var init_dist = __esm({
|
|
|
3113
3149
|
{
|
|
3114
3150
|
field: "command",
|
|
3115
3151
|
op: "matches",
|
|
3116
|
-
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type)\\s+.*?\\.aws[\\/\\\\]",
|
|
3152
|
+
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|sort|uniq|tac|nl|dd)\\s+.*?\\.aws[\\/\\\\]",
|
|
3117
3153
|
flags: "i"
|
|
3118
3154
|
}
|
|
3119
3155
|
],
|
|
@@ -3127,7 +3163,7 @@ var init_dist = __esm({
|
|
|
3127
3163
|
{
|
|
3128
3164
|
field: "command",
|
|
3129
3165
|
op: "matches",
|
|
3130
|
-
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type)\\s
|
|
3166
|
+
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|sort|uniq|tac|nl|dd)\\s+.*?\\.env(\\.(local|production|staging|development|production\\.local|staging\\.local|development\\.local))?(?=\\s|$|[;&|>)<])",
|
|
3131
3167
|
flags: "i"
|
|
3132
3168
|
}
|
|
3133
3169
|
],
|
|
@@ -3141,7 +3177,7 @@ var init_dist = __esm({
|
|
|
3141
3177
|
{
|
|
3142
3178
|
field: "command",
|
|
3143
3179
|
op: "matches",
|
|
3144
|
-
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type)\\s+.*(credentials\\.json|\\.netrc|\\.npmrc|\\.docker[\\/\\\\]config\\.json|gcloud[\\/\\\\]credentials)",
|
|
3180
|
+
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|sort|uniq|tac|nl|dd)\\s+.*(credentials\\.json|\\.netrc|\\.npmrc|\\.docker[\\/\\\\]config\\.json|gcloud[\\/\\\\]credentials)",
|
|
3145
3181
|
flags: "i"
|
|
3146
3182
|
}
|
|
3147
3183
|
],
|
|
@@ -3177,7 +3213,7 @@ var init_dist = __esm({
|
|
|
3177
3213
|
reason: "Reading AWS credentials is blocked by project-jail shield"
|
|
3178
3214
|
},
|
|
3179
3215
|
{
|
|
3180
|
-
name: "shield:project-jail:
|
|
3216
|
+
name: "shield:project-jail:block-read-env-any-tool",
|
|
3181
3217
|
tool: "*",
|
|
3182
3218
|
conditions: [
|
|
3183
3219
|
{
|
|
@@ -3187,8 +3223,8 @@ var init_dist = __esm({
|
|
|
3187
3223
|
flags: "i"
|
|
3188
3224
|
}
|
|
3189
3225
|
],
|
|
3190
|
-
verdict: "
|
|
3191
|
-
reason: "Reading .env files
|
|
3226
|
+
verdict: "block",
|
|
3227
|
+
reason: "Reading .env files is blocked by project-jail shield"
|
|
3192
3228
|
},
|
|
3193
3229
|
{
|
|
3194
3230
|
name: "shield:project-jail:review-read-credentials-any-tool",
|
|
@@ -3453,6 +3489,30 @@ function writeShieldOverride(shieldName, ruleName, verdict) {
|
|
|
3453
3489
|
overrides[shieldName] = { ...overrides[shieldName] ?? {}, [ruleName]: verdict };
|
|
3454
3490
|
writeShieldsFile({ ...current, overrides });
|
|
3455
3491
|
}
|
|
3492
|
+
function migrateRenamedRuleKeys() {
|
|
3493
|
+
const current = readShieldsFile();
|
|
3494
|
+
if (!current.overrides || Object.keys(current.overrides).length === 0) return [];
|
|
3495
|
+
const renameMap = new Map(RULE_KEY_MIGRATIONS);
|
|
3496
|
+
const migrated = [];
|
|
3497
|
+
const nextOverrides = {};
|
|
3498
|
+
let anyChange = false;
|
|
3499
|
+
for (const [shield, rules] of Object.entries(current.overrides)) {
|
|
3500
|
+
const nextRules = {};
|
|
3501
|
+
for (const [key, verdict] of Object.entries(rules)) {
|
|
3502
|
+
const newKey = renameMap.get(key);
|
|
3503
|
+
if (newKey) {
|
|
3504
|
+
if (!(newKey in nextRules)) nextRules[newKey] = verdict;
|
|
3505
|
+
migrated.push({ shield, oldKey: key, newKey });
|
|
3506
|
+
anyChange = true;
|
|
3507
|
+
} else {
|
|
3508
|
+
nextRules[key] = verdict;
|
|
3509
|
+
}
|
|
3510
|
+
}
|
|
3511
|
+
if (Object.keys(nextRules).length > 0) nextOverrides[shield] = nextRules;
|
|
3512
|
+
}
|
|
3513
|
+
if (anyChange) writeShieldsFile({ ...current, overrides: nextOverrides });
|
|
3514
|
+
return migrated;
|
|
3515
|
+
}
|
|
3456
3516
|
function clearShieldOverride(shieldName, ruleName) {
|
|
3457
3517
|
const current = readShieldsFile();
|
|
3458
3518
|
if (!current.overrides?.[shieldName]?.[ruleName]) return;
|
|
@@ -3501,7 +3561,7 @@ function installShield(name, shieldJson) {
|
|
|
3501
3561
|
fs2.writeFileSync(tmp, JSON.stringify(shieldJson, null, 2), { mode: 384 });
|
|
3502
3562
|
fs2.renameSync(tmp, filePath);
|
|
3503
3563
|
}
|
|
3504
|
-
var USER_SHIELDS_DIR, SHIELDS, SHIELDS_STATE_FILE;
|
|
3564
|
+
var USER_SHIELDS_DIR, SHIELDS, SHIELDS_STATE_FILE, RULE_KEY_MIGRATIONS;
|
|
3505
3565
|
var init_shields = __esm({
|
|
3506
3566
|
"src/shields.ts"() {
|
|
3507
3567
|
"use strict";
|
|
@@ -3510,6 +3570,14 @@ var init_shields = __esm({
|
|
|
3510
3570
|
USER_SHIELDS_DIR = path2.join(os2.homedir(), ".node9", "shields");
|
|
3511
3571
|
SHIELDS = buildSHIELDS();
|
|
3512
3572
|
SHIELDS_STATE_FILE = path2.join(os2.homedir(), ".node9", "shields.json");
|
|
3573
|
+
RULE_KEY_MIGRATIONS = [
|
|
3574
|
+
// 2026-05-21 — project-jail .env reads promoted review → block. Renamed
|
|
3575
|
+
// so the key honestly describes the verdict. Credential-file rules
|
|
3576
|
+
// (.netrc, .npmrc, .docker, .kube, gcloud) deliberately stay at `review`
|
|
3577
|
+
// — see the rationale in packages/policy-engine/src/shell/index.ts
|
|
3578
|
+
// around the review-read-credentials rule.
|
|
3579
|
+
["shield:project-jail:review-read-env-any-tool", "shield:project-jail:block-read-env-any-tool"]
|
|
3580
|
+
];
|
|
3513
3581
|
}
|
|
3514
3582
|
});
|
|
3515
3583
|
|
|
@@ -7016,6 +7084,18 @@ function claudeDesktopConfigPath(homeDir2 = os12.homedir()) {
|
|
|
7016
7084
|
}
|
|
7017
7085
|
return null;
|
|
7018
7086
|
}
|
|
7087
|
+
function binaryInPath(binary) {
|
|
7088
|
+
const pathEnv = process.env.PATH ?? "";
|
|
7089
|
+
for (const dir of pathEnv.split(path15.delimiter)) {
|
|
7090
|
+
if (!dir) continue;
|
|
7091
|
+
try {
|
|
7092
|
+
fs13.accessSync(path15.join(dir, binary), fs13.constants.X_OK);
|
|
7093
|
+
return true;
|
|
7094
|
+
} catch {
|
|
7095
|
+
}
|
|
7096
|
+
}
|
|
7097
|
+
return false;
|
|
7098
|
+
}
|
|
7019
7099
|
function detectAgents(homeDir2 = os12.homedir()) {
|
|
7020
7100
|
const exists = (p) => {
|
|
7021
7101
|
try {
|
|
@@ -7038,7 +7118,9 @@ function detectAgents(homeDir2 = os12.homedir()) {
|
|
|
7038
7118
|
windsurf: exists(path15.join(homeDir2, ".codeium", "windsurf")),
|
|
7039
7119
|
vscode: exists(path15.join(homeDir2, ".vscode")),
|
|
7040
7120
|
claudeDesktop: desktopPath !== null && exists(path15.dirname(desktopPath)),
|
|
7041
|
-
|
|
7121
|
+
// Opencode creates ~/.config/opencode lazily on first launch — fall back
|
|
7122
|
+
// to a PATH lookup so installed-but-never-launched CLIs are still wired.
|
|
7123
|
+
opencode: exists(path15.join(homeDir2, ".config", "opencode")) || binaryInPath("opencode")
|
|
7042
7124
|
};
|
|
7043
7125
|
}
|
|
7044
7126
|
async function setupCursor() {
|
|
@@ -18716,14 +18798,18 @@ import path39 from "path";
|
|
|
18716
18798
|
import os34 from "os";
|
|
18717
18799
|
import https4 from "https";
|
|
18718
18800
|
var DEFAULT_SHIELDS = ["bash-safe", "filesystem", "project-jail"];
|
|
18719
|
-
function
|
|
18801
|
+
function buildTelemetryPayload(agents, firstInstall) {
|
|
18802
|
+
return {
|
|
18803
|
+
event: "init_completed",
|
|
18804
|
+
agents_detected: agents,
|
|
18805
|
+
os: process.platform,
|
|
18806
|
+
node9_version: node9Version(),
|
|
18807
|
+
first_install: firstInstall
|
|
18808
|
+
};
|
|
18809
|
+
}
|
|
18810
|
+
function fireTelemetryPing(agents, firstInstall) {
|
|
18720
18811
|
try {
|
|
18721
|
-
const body = JSON.stringify(
|
|
18722
|
-
event: "init_completed",
|
|
18723
|
-
agents_detected: agents,
|
|
18724
|
-
os: process.platform,
|
|
18725
|
-
node9_version: process.env.npm_package_version ?? "unknown"
|
|
18726
|
-
});
|
|
18812
|
+
const body = JSON.stringify(buildTelemetryPayload(agents, firstInstall));
|
|
18727
18813
|
const req = https4.request(
|
|
18728
18814
|
{
|
|
18729
18815
|
hostname: "api.node9.ai",
|
|
@@ -18756,6 +18842,12 @@ function registerInitCommand(program2) {
|
|
|
18756
18842
|
).action(
|
|
18757
18843
|
async (options) => {
|
|
18758
18844
|
console.log(chalk16.cyan.bold("\n\u{1F6E1}\uFE0F Node9 Init\n"));
|
|
18845
|
+
{
|
|
18846
|
+
const migrated = migrateRenamedRuleKeys();
|
|
18847
|
+
for (const m of migrated) {
|
|
18848
|
+
console.log(chalk16.dim(` \u{1F527} Rule renamed: ${m.oldKey} \u2192 ${m.newKey}`));
|
|
18849
|
+
}
|
|
18850
|
+
}
|
|
18759
18851
|
let chosenMode = options.mode.toLowerCase();
|
|
18760
18852
|
if (!["standard", "strict", "audit", "observe"].includes(chosenMode)) {
|
|
18761
18853
|
chosenMode = DEFAULT_CONFIG.settings.mode;
|
|
@@ -18790,6 +18882,7 @@ function registerInitCommand(program2) {
|
|
|
18790
18882
|
console.log("");
|
|
18791
18883
|
}
|
|
18792
18884
|
const configPath = path39.join(os34.homedir(), ".node9", "config.json");
|
|
18885
|
+
const isFirstInstall = !fs38.existsSync(configPath);
|
|
18793
18886
|
if (fs38.existsSync(configPath) && !options.force) {
|
|
18794
18887
|
try {
|
|
18795
18888
|
const existing = JSON.parse(fs38.readFileSync(configPath, "utf-8"));
|
|
@@ -18893,7 +18986,7 @@ function registerInitCommand(program2) {
|
|
|
18893
18986
|
message: "Send anonymous usage stats to help improve node9? (no code, no args)",
|
|
18894
18987
|
default: true
|
|
18895
18988
|
});
|
|
18896
|
-
if (sendTelemetry) fireTelemetryPing(found);
|
|
18989
|
+
if (sendTelemetry) fireTelemetryPing(found, isFirstInstall);
|
|
18897
18990
|
console.log("");
|
|
18898
18991
|
}
|
|
18899
18992
|
const agentList = found.join(", ");
|
package/dist/dashboard.mjs
CHANGED
|
@@ -1030,6 +1030,42 @@ var init_dist = __esm({
|
|
|
1030
1030
|
regex: /\bAGE-SECRET-KEY-1[QPZRY9X8GF2TVDW0S3JNLH]{58}\b/,
|
|
1031
1031
|
severity: "block",
|
|
1032
1032
|
keywords: ["age-secret-key-"]
|
|
1033
|
+
},
|
|
1034
|
+
// ── Database connection strings ───────────────────────────────────────────
|
|
1035
|
+
// Universal <scheme>://[user]:<password>@<host> shape. Covers the gap
|
|
1036
|
+
// vendor-prefix patterns (AWS / GitHub / Stripe / …) leave open. Matches
|
|
1037
|
+
// the whole URL so maskSecret produces `<scheme>...:****@...<host>` —
|
|
1038
|
+
// the password value never appears in the redacted sample.
|
|
1039
|
+
//
|
|
1040
|
+
// Schemes covered: redis, rediss (TLS), postgres, postgresql,
|
|
1041
|
+
// mongodb, mongodb+srv, mysql, mariadb, amqp, amqps, kafka,
|
|
1042
|
+
// clickhouse, cassandra. HTTP(S) / FTP / SSH are intentionally
|
|
1043
|
+
// excluded — they're not database URLs and adding them would
|
|
1044
|
+
// create false positives on every basic-auth URL in the wild.
|
|
1045
|
+
//
|
|
1046
|
+
// Requires `:password@` (4+ char password) so user-only URLs like
|
|
1047
|
+
// `redis://user@host` don't match. Stopwords ('your', '${', '<your',
|
|
1048
|
+
// 'placeholder', 'changeme', etc.) keep doc/README scans clean.
|
|
1049
|
+
{
|
|
1050
|
+
name: "Database Connection String",
|
|
1051
|
+
regex: /\b(redis|rediss|postgres|postgresql|mongodb|mongodb\+srv|mysql|mariadb|amqp|amqps|kafka|clickhouse|cassandra):\/\/[^:/\s@]*:[^@\s]{4,}@[^\s/]+/,
|
|
1052
|
+
severity: "block",
|
|
1053
|
+
keywords: [
|
|
1054
|
+
"redis://",
|
|
1055
|
+
"rediss://",
|
|
1056
|
+
"postgres://",
|
|
1057
|
+
"postgresql://",
|
|
1058
|
+
"mongodb://",
|
|
1059
|
+
"mongodb+srv://",
|
|
1060
|
+
"mysql://",
|
|
1061
|
+
"mariadb://",
|
|
1062
|
+
"amqp://",
|
|
1063
|
+
"amqps://",
|
|
1064
|
+
"kafka://",
|
|
1065
|
+
"clickhouse://",
|
|
1066
|
+
"cassandra://"
|
|
1067
|
+
],
|
|
1068
|
+
minEntropy: 3
|
|
1033
1069
|
}
|
|
1034
1070
|
];
|
|
1035
1071
|
DLP_PATTERNS_GLOBAL = DLP_PATTERNS.map(
|
|
@@ -1132,7 +1168,7 @@ var init_dist = __esm({
|
|
|
1132
1168
|
},
|
|
1133
1169
|
{
|
|
1134
1170
|
// Mirrors the JSON shield's `.env` pattern (project-jail.json's
|
|
1135
|
-
//
|
|
1171
|
+
// block-read-env-any-tool) so the AST FS-op path catches the
|
|
1136
1172
|
// same set the regex shield does — including Next.js / Vite's
|
|
1137
1173
|
// `.env.<env>.local` double-suffix overrides which are commonly
|
|
1138
1174
|
// gitignored AND commonly contain real secrets.
|
|
@@ -1742,7 +1778,7 @@ var init_dist = __esm({
|
|
|
1742
1778
|
{
|
|
1743
1779
|
field: "command",
|
|
1744
1780
|
op: "matches",
|
|
1745
|
-
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type)\\s+.*?\\.ssh[\\/\\\\]",
|
|
1781
|
+
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|sort|uniq|tac|nl|dd)\\s+.*?\\.ssh[\\/\\\\]",
|
|
1746
1782
|
flags: "i"
|
|
1747
1783
|
}
|
|
1748
1784
|
],
|
|
@@ -1756,7 +1792,7 @@ var init_dist = __esm({
|
|
|
1756
1792
|
{
|
|
1757
1793
|
field: "command",
|
|
1758
1794
|
op: "matches",
|
|
1759
|
-
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type)\\s+.*?\\.aws[\\/\\\\]",
|
|
1795
|
+
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|sort|uniq|tac|nl|dd)\\s+.*?\\.aws[\\/\\\\]",
|
|
1760
1796
|
flags: "i"
|
|
1761
1797
|
}
|
|
1762
1798
|
],
|
|
@@ -1770,7 +1806,7 @@ var init_dist = __esm({
|
|
|
1770
1806
|
{
|
|
1771
1807
|
field: "command",
|
|
1772
1808
|
op: "matches",
|
|
1773
|
-
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type)\\s
|
|
1809
|
+
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|sort|uniq|tac|nl|dd)\\s+.*?\\.env(\\.(local|production|staging|development|production\\.local|staging\\.local|development\\.local))?(?=\\s|$|[;&|>)<])",
|
|
1774
1810
|
flags: "i"
|
|
1775
1811
|
}
|
|
1776
1812
|
],
|
|
@@ -1784,7 +1820,7 @@ var init_dist = __esm({
|
|
|
1784
1820
|
{
|
|
1785
1821
|
field: "command",
|
|
1786
1822
|
op: "matches",
|
|
1787
|
-
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type)\\s+.*(credentials\\.json|\\.netrc|\\.npmrc|\\.docker[\\/\\\\]config\\.json|gcloud[\\/\\\\]credentials)",
|
|
1823
|
+
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|sort|uniq|tac|nl|dd)\\s+.*(credentials\\.json|\\.netrc|\\.npmrc|\\.docker[\\/\\\\]config\\.json|gcloud[\\/\\\\]credentials)",
|
|
1788
1824
|
flags: "i"
|
|
1789
1825
|
}
|
|
1790
1826
|
],
|
|
@@ -1820,7 +1856,7 @@ var init_dist = __esm({
|
|
|
1820
1856
|
reason: "Reading AWS credentials is blocked by project-jail shield"
|
|
1821
1857
|
},
|
|
1822
1858
|
{
|
|
1823
|
-
name: "shield:project-jail:
|
|
1859
|
+
name: "shield:project-jail:block-read-env-any-tool",
|
|
1824
1860
|
tool: "*",
|
|
1825
1861
|
conditions: [
|
|
1826
1862
|
{
|
|
@@ -1830,8 +1866,8 @@ var init_dist = __esm({
|
|
|
1830
1866
|
flags: "i"
|
|
1831
1867
|
}
|
|
1832
1868
|
],
|
|
1833
|
-
verdict: "
|
|
1834
|
-
reason: "Reading .env files
|
|
1869
|
+
verdict: "block",
|
|
1870
|
+
reason: "Reading .env files is blocked by project-jail shield"
|
|
1835
1871
|
},
|
|
1836
1872
|
{
|
|
1837
1873
|
name: "shield:project-jail:review-read-credentials-any-tool",
|
package/dist/index.js
CHANGED
|
@@ -739,6 +739,42 @@ var DLP_PATTERNS = [
|
|
|
739
739
|
regex: /\bAGE-SECRET-KEY-1[QPZRY9X8GF2TVDW0S3JNLH]{58}\b/,
|
|
740
740
|
severity: "block",
|
|
741
741
|
keywords: ["age-secret-key-"]
|
|
742
|
+
},
|
|
743
|
+
// ── Database connection strings ───────────────────────────────────────────
|
|
744
|
+
// Universal <scheme>://[user]:<password>@<host> shape. Covers the gap
|
|
745
|
+
// vendor-prefix patterns (AWS / GitHub / Stripe / …) leave open. Matches
|
|
746
|
+
// the whole URL so maskSecret produces `<scheme>...:****@...<host>` —
|
|
747
|
+
// the password value never appears in the redacted sample.
|
|
748
|
+
//
|
|
749
|
+
// Schemes covered: redis, rediss (TLS), postgres, postgresql,
|
|
750
|
+
// mongodb, mongodb+srv, mysql, mariadb, amqp, amqps, kafka,
|
|
751
|
+
// clickhouse, cassandra. HTTP(S) / FTP / SSH are intentionally
|
|
752
|
+
// excluded — they're not database URLs and adding them would
|
|
753
|
+
// create false positives on every basic-auth URL in the wild.
|
|
754
|
+
//
|
|
755
|
+
// Requires `:password@` (4+ char password) so user-only URLs like
|
|
756
|
+
// `redis://user@host` don't match. Stopwords ('your', '${', '<your',
|
|
757
|
+
// 'placeholder', 'changeme', etc.) keep doc/README scans clean.
|
|
758
|
+
{
|
|
759
|
+
name: "Database Connection String",
|
|
760
|
+
regex: /\b(redis|rediss|postgres|postgresql|mongodb|mongodb\+srv|mysql|mariadb|amqp|amqps|kafka|clickhouse|cassandra):\/\/[^:/\s@]*:[^@\s]{4,}@[^\s/]+/,
|
|
761
|
+
severity: "block",
|
|
762
|
+
keywords: [
|
|
763
|
+
"redis://",
|
|
764
|
+
"rediss://",
|
|
765
|
+
"postgres://",
|
|
766
|
+
"postgresql://",
|
|
767
|
+
"mongodb://",
|
|
768
|
+
"mongodb+srv://",
|
|
769
|
+
"mysql://",
|
|
770
|
+
"mariadb://",
|
|
771
|
+
"amqp://",
|
|
772
|
+
"amqps://",
|
|
773
|
+
"kafka://",
|
|
774
|
+
"clickhouse://",
|
|
775
|
+
"cassandra://"
|
|
776
|
+
],
|
|
777
|
+
minEntropy: 3
|
|
742
778
|
}
|
|
743
779
|
];
|
|
744
780
|
var DLP_PATTERNS_GLOBAL = DLP_PATTERNS.map(
|
|
@@ -1092,7 +1128,7 @@ var SENSITIVE_PATH_RULES = [
|
|
|
1092
1128
|
},
|
|
1093
1129
|
{
|
|
1094
1130
|
// Mirrors the JSON shield's `.env` pattern (project-jail.json's
|
|
1095
|
-
//
|
|
1131
|
+
// block-read-env-any-tool) so the AST FS-op path catches the
|
|
1096
1132
|
// same set the regex shield does — including Next.js / Vite's
|
|
1097
1133
|
// `.env.<env>.local` double-suffix overrides which are commonly
|
|
1098
1134
|
// gitignored AND commonly contain real secrets.
|
|
@@ -2554,7 +2590,7 @@ var project_jail_default = {
|
|
|
2554
2590
|
{
|
|
2555
2591
|
field: "command",
|
|
2556
2592
|
op: "matches",
|
|
2557
|
-
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type)\\s+.*?\\.ssh[\\/\\\\]",
|
|
2593
|
+
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|sort|uniq|tac|nl|dd)\\s+.*?\\.ssh[\\/\\\\]",
|
|
2558
2594
|
flags: "i"
|
|
2559
2595
|
}
|
|
2560
2596
|
],
|
|
@@ -2568,7 +2604,7 @@ var project_jail_default = {
|
|
|
2568
2604
|
{
|
|
2569
2605
|
field: "command",
|
|
2570
2606
|
op: "matches",
|
|
2571
|
-
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type)\\s+.*?\\.aws[\\/\\\\]",
|
|
2607
|
+
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|sort|uniq|tac|nl|dd)\\s+.*?\\.aws[\\/\\\\]",
|
|
2572
2608
|
flags: "i"
|
|
2573
2609
|
}
|
|
2574
2610
|
],
|
|
@@ -2582,7 +2618,7 @@ var project_jail_default = {
|
|
|
2582
2618
|
{
|
|
2583
2619
|
field: "command",
|
|
2584
2620
|
op: "matches",
|
|
2585
|
-
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type)\\s
|
|
2621
|
+
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|sort|uniq|tac|nl|dd)\\s+.*?\\.env(\\.(local|production|staging|development|production\\.local|staging\\.local|development\\.local))?(?=\\s|$|[;&|>)<])",
|
|
2586
2622
|
flags: "i"
|
|
2587
2623
|
}
|
|
2588
2624
|
],
|
|
@@ -2596,7 +2632,7 @@ var project_jail_default = {
|
|
|
2596
2632
|
{
|
|
2597
2633
|
field: "command",
|
|
2598
2634
|
op: "matches",
|
|
2599
|
-
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type)\\s+.*(credentials\\.json|\\.netrc|\\.npmrc|\\.docker[\\/\\\\]config\\.json|gcloud[\\/\\\\]credentials)",
|
|
2635
|
+
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|sort|uniq|tac|nl|dd)\\s+.*(credentials\\.json|\\.netrc|\\.npmrc|\\.docker[\\/\\\\]config\\.json|gcloud[\\/\\\\]credentials)",
|
|
2600
2636
|
flags: "i"
|
|
2601
2637
|
}
|
|
2602
2638
|
],
|
|
@@ -2632,7 +2668,7 @@ var project_jail_default = {
|
|
|
2632
2668
|
reason: "Reading AWS credentials is blocked by project-jail shield"
|
|
2633
2669
|
},
|
|
2634
2670
|
{
|
|
2635
|
-
name: "shield:project-jail:
|
|
2671
|
+
name: "shield:project-jail:block-read-env-any-tool",
|
|
2636
2672
|
tool: "*",
|
|
2637
2673
|
conditions: [
|
|
2638
2674
|
{
|
|
@@ -2642,8 +2678,8 @@ var project_jail_default = {
|
|
|
2642
2678
|
flags: "i"
|
|
2643
2679
|
}
|
|
2644
2680
|
],
|
|
2645
|
-
verdict: "
|
|
2646
|
-
reason: "Reading .env files
|
|
2681
|
+
verdict: "block",
|
|
2682
|
+
reason: "Reading .env files is blocked by project-jail shield"
|
|
2647
2683
|
},
|
|
2648
2684
|
{
|
|
2649
2685
|
name: "shield:project-jail:review-read-credentials-any-tool",
|
package/dist/index.mjs
CHANGED
|
@@ -709,6 +709,42 @@ var DLP_PATTERNS = [
|
|
|
709
709
|
regex: /\bAGE-SECRET-KEY-1[QPZRY9X8GF2TVDW0S3JNLH]{58}\b/,
|
|
710
710
|
severity: "block",
|
|
711
711
|
keywords: ["age-secret-key-"]
|
|
712
|
+
},
|
|
713
|
+
// ── Database connection strings ───────────────────────────────────────────
|
|
714
|
+
// Universal <scheme>://[user]:<password>@<host> shape. Covers the gap
|
|
715
|
+
// vendor-prefix patterns (AWS / GitHub / Stripe / …) leave open. Matches
|
|
716
|
+
// the whole URL so maskSecret produces `<scheme>...:****@...<host>` —
|
|
717
|
+
// the password value never appears in the redacted sample.
|
|
718
|
+
//
|
|
719
|
+
// Schemes covered: redis, rediss (TLS), postgres, postgresql,
|
|
720
|
+
// mongodb, mongodb+srv, mysql, mariadb, amqp, amqps, kafka,
|
|
721
|
+
// clickhouse, cassandra. HTTP(S) / FTP / SSH are intentionally
|
|
722
|
+
// excluded — they're not database URLs and adding them would
|
|
723
|
+
// create false positives on every basic-auth URL in the wild.
|
|
724
|
+
//
|
|
725
|
+
// Requires `:password@` (4+ char password) so user-only URLs like
|
|
726
|
+
// `redis://user@host` don't match. Stopwords ('your', '${', '<your',
|
|
727
|
+
// 'placeholder', 'changeme', etc.) keep doc/README scans clean.
|
|
728
|
+
{
|
|
729
|
+
name: "Database Connection String",
|
|
730
|
+
regex: /\b(redis|rediss|postgres|postgresql|mongodb|mongodb\+srv|mysql|mariadb|amqp|amqps|kafka|clickhouse|cassandra):\/\/[^:/\s@]*:[^@\s]{4,}@[^\s/]+/,
|
|
731
|
+
severity: "block",
|
|
732
|
+
keywords: [
|
|
733
|
+
"redis://",
|
|
734
|
+
"rediss://",
|
|
735
|
+
"postgres://",
|
|
736
|
+
"postgresql://",
|
|
737
|
+
"mongodb://",
|
|
738
|
+
"mongodb+srv://",
|
|
739
|
+
"mysql://",
|
|
740
|
+
"mariadb://",
|
|
741
|
+
"amqp://",
|
|
742
|
+
"amqps://",
|
|
743
|
+
"kafka://",
|
|
744
|
+
"clickhouse://",
|
|
745
|
+
"cassandra://"
|
|
746
|
+
],
|
|
747
|
+
minEntropy: 3
|
|
712
748
|
}
|
|
713
749
|
];
|
|
714
750
|
var DLP_PATTERNS_GLOBAL = DLP_PATTERNS.map(
|
|
@@ -1062,7 +1098,7 @@ var SENSITIVE_PATH_RULES = [
|
|
|
1062
1098
|
},
|
|
1063
1099
|
{
|
|
1064
1100
|
// Mirrors the JSON shield's `.env` pattern (project-jail.json's
|
|
1065
|
-
//
|
|
1101
|
+
// block-read-env-any-tool) so the AST FS-op path catches the
|
|
1066
1102
|
// same set the regex shield does — including Next.js / Vite's
|
|
1067
1103
|
// `.env.<env>.local` double-suffix overrides which are commonly
|
|
1068
1104
|
// gitignored AND commonly contain real secrets.
|
|
@@ -2524,7 +2560,7 @@ var project_jail_default = {
|
|
|
2524
2560
|
{
|
|
2525
2561
|
field: "command",
|
|
2526
2562
|
op: "matches",
|
|
2527
|
-
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type)\\s+.*?\\.ssh[\\/\\\\]",
|
|
2563
|
+
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|sort|uniq|tac|nl|dd)\\s+.*?\\.ssh[\\/\\\\]",
|
|
2528
2564
|
flags: "i"
|
|
2529
2565
|
}
|
|
2530
2566
|
],
|
|
@@ -2538,7 +2574,7 @@ var project_jail_default = {
|
|
|
2538
2574
|
{
|
|
2539
2575
|
field: "command",
|
|
2540
2576
|
op: "matches",
|
|
2541
|
-
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type)\\s+.*?\\.aws[\\/\\\\]",
|
|
2577
|
+
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|sort|uniq|tac|nl|dd)\\s+.*?\\.aws[\\/\\\\]",
|
|
2542
2578
|
flags: "i"
|
|
2543
2579
|
}
|
|
2544
2580
|
],
|
|
@@ -2552,7 +2588,7 @@ var project_jail_default = {
|
|
|
2552
2588
|
{
|
|
2553
2589
|
field: "command",
|
|
2554
2590
|
op: "matches",
|
|
2555
|
-
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type)\\s
|
|
2591
|
+
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|sort|uniq|tac|nl|dd)\\s+.*?\\.env(\\.(local|production|staging|development|production\\.local|staging\\.local|development\\.local))?(?=\\s|$|[;&|>)<])",
|
|
2556
2592
|
flags: "i"
|
|
2557
2593
|
}
|
|
2558
2594
|
],
|
|
@@ -2566,7 +2602,7 @@ var project_jail_default = {
|
|
|
2566
2602
|
{
|
|
2567
2603
|
field: "command",
|
|
2568
2604
|
op: "matches",
|
|
2569
|
-
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type)\\s+.*(credentials\\.json|\\.netrc|\\.npmrc|\\.docker[\\/\\\\]config\\.json|gcloud[\\/\\\\]credentials)",
|
|
2605
|
+
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|sort|uniq|tac|nl|dd)\\s+.*(credentials\\.json|\\.netrc|\\.npmrc|\\.docker[\\/\\\\]config\\.json|gcloud[\\/\\\\]credentials)",
|
|
2570
2606
|
flags: "i"
|
|
2571
2607
|
}
|
|
2572
2608
|
],
|
|
@@ -2602,7 +2638,7 @@ var project_jail_default = {
|
|
|
2602
2638
|
reason: "Reading AWS credentials is blocked by project-jail shield"
|
|
2603
2639
|
},
|
|
2604
2640
|
{
|
|
2605
|
-
name: "shield:project-jail:
|
|
2641
|
+
name: "shield:project-jail:block-read-env-any-tool",
|
|
2606
2642
|
tool: "*",
|
|
2607
2643
|
conditions: [
|
|
2608
2644
|
{
|
|
@@ -2612,8 +2648,8 @@ var project_jail_default = {
|
|
|
2612
2648
|
flags: "i"
|
|
2613
2649
|
}
|
|
2614
2650
|
],
|
|
2615
|
-
verdict: "
|
|
2616
|
-
reason: "Reading .env files
|
|
2651
|
+
verdict: "block",
|
|
2652
|
+
reason: "Reading .env files is blocked by project-jail shield"
|
|
2617
2653
|
},
|
|
2618
2654
|
{
|
|
2619
2655
|
name: "shield:project-jail:review-read-credentials-any-tool",
|
package/dist/scan-ink.mjs
CHANGED
|
@@ -729,6 +729,42 @@ var DLP_PATTERNS = [
|
|
|
729
729
|
regex: /\bAGE-SECRET-KEY-1[QPZRY9X8GF2TVDW0S3JNLH]{58}\b/,
|
|
730
730
|
severity: "block",
|
|
731
731
|
keywords: ["age-secret-key-"]
|
|
732
|
+
},
|
|
733
|
+
// ── Database connection strings ───────────────────────────────────────────
|
|
734
|
+
// Universal <scheme>://[user]:<password>@<host> shape. Covers the gap
|
|
735
|
+
// vendor-prefix patterns (AWS / GitHub / Stripe / …) leave open. Matches
|
|
736
|
+
// the whole URL so maskSecret produces `<scheme>...:****@...<host>` —
|
|
737
|
+
// the password value never appears in the redacted sample.
|
|
738
|
+
//
|
|
739
|
+
// Schemes covered: redis, rediss (TLS), postgres, postgresql,
|
|
740
|
+
// mongodb, mongodb+srv, mysql, mariadb, amqp, amqps, kafka,
|
|
741
|
+
// clickhouse, cassandra. HTTP(S) / FTP / SSH are intentionally
|
|
742
|
+
// excluded — they're not database URLs and adding them would
|
|
743
|
+
// create false positives on every basic-auth URL in the wild.
|
|
744
|
+
//
|
|
745
|
+
// Requires `:password@` (4+ char password) so user-only URLs like
|
|
746
|
+
// `redis://user@host` don't match. Stopwords ('your', '${', '<your',
|
|
747
|
+
// 'placeholder', 'changeme', etc.) keep doc/README scans clean.
|
|
748
|
+
{
|
|
749
|
+
name: "Database Connection String",
|
|
750
|
+
regex: /\b(redis|rediss|postgres|postgresql|mongodb|mongodb\+srv|mysql|mariadb|amqp|amqps|kafka|clickhouse|cassandra):\/\/[^:/\s@]*:[^@\s]{4,}@[^\s/]+/,
|
|
751
|
+
severity: "block",
|
|
752
|
+
keywords: [
|
|
753
|
+
"redis://",
|
|
754
|
+
"rediss://",
|
|
755
|
+
"postgres://",
|
|
756
|
+
"postgresql://",
|
|
757
|
+
"mongodb://",
|
|
758
|
+
"mongodb+srv://",
|
|
759
|
+
"mysql://",
|
|
760
|
+
"mariadb://",
|
|
761
|
+
"amqp://",
|
|
762
|
+
"amqps://",
|
|
763
|
+
"kafka://",
|
|
764
|
+
"clickhouse://",
|
|
765
|
+
"cassandra://"
|
|
766
|
+
],
|
|
767
|
+
minEntropy: 3
|
|
732
768
|
}
|
|
733
769
|
];
|
|
734
770
|
var DLP_PATTERNS_GLOBAL = DLP_PATTERNS.map(
|
|
@@ -1333,7 +1369,7 @@ var project_jail_default = {
|
|
|
1333
1369
|
{
|
|
1334
1370
|
field: "command",
|
|
1335
1371
|
op: "matches",
|
|
1336
|
-
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type)\\s+.*?\\.ssh[\\/\\\\]",
|
|
1372
|
+
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|sort|uniq|tac|nl|dd)\\s+.*?\\.ssh[\\/\\\\]",
|
|
1337
1373
|
flags: "i"
|
|
1338
1374
|
}
|
|
1339
1375
|
],
|
|
@@ -1347,7 +1383,7 @@ var project_jail_default = {
|
|
|
1347
1383
|
{
|
|
1348
1384
|
field: "command",
|
|
1349
1385
|
op: "matches",
|
|
1350
|
-
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type)\\s+.*?\\.aws[\\/\\\\]",
|
|
1386
|
+
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|sort|uniq|tac|nl|dd)\\s+.*?\\.aws[\\/\\\\]",
|
|
1351
1387
|
flags: "i"
|
|
1352
1388
|
}
|
|
1353
1389
|
],
|
|
@@ -1361,7 +1397,7 @@ var project_jail_default = {
|
|
|
1361
1397
|
{
|
|
1362
1398
|
field: "command",
|
|
1363
1399
|
op: "matches",
|
|
1364
|
-
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type)\\s
|
|
1400
|
+
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|sort|uniq|tac|nl|dd)\\s+.*?\\.env(\\.(local|production|staging|development|production\\.local|staging\\.local|development\\.local))?(?=\\s|$|[;&|>)<])",
|
|
1365
1401
|
flags: "i"
|
|
1366
1402
|
}
|
|
1367
1403
|
],
|
|
@@ -1375,7 +1411,7 @@ var project_jail_default = {
|
|
|
1375
1411
|
{
|
|
1376
1412
|
field: "command",
|
|
1377
1413
|
op: "matches",
|
|
1378
|
-
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type)\\s+.*(credentials\\.json|\\.netrc|\\.npmrc|\\.docker[\\/\\\\]config\\.json|gcloud[\\/\\\\]credentials)",
|
|
1414
|
+
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|sort|uniq|tac|nl|dd)\\s+.*(credentials\\.json|\\.netrc|\\.npmrc|\\.docker[\\/\\\\]config\\.json|gcloud[\\/\\\\]credentials)",
|
|
1379
1415
|
flags: "i"
|
|
1380
1416
|
}
|
|
1381
1417
|
],
|
|
@@ -1411,7 +1447,7 @@ var project_jail_default = {
|
|
|
1411
1447
|
reason: "Reading AWS credentials is blocked by project-jail shield"
|
|
1412
1448
|
},
|
|
1413
1449
|
{
|
|
1414
|
-
name: "shield:project-jail:
|
|
1450
|
+
name: "shield:project-jail:block-read-env-any-tool",
|
|
1415
1451
|
tool: "*",
|
|
1416
1452
|
conditions: [
|
|
1417
1453
|
{
|
|
@@ -1421,8 +1457,8 @@ var project_jail_default = {
|
|
|
1421
1457
|
flags: "i"
|
|
1422
1458
|
}
|
|
1423
1459
|
],
|
|
1424
|
-
verdict: "
|
|
1425
|
-
reason: "Reading .env files
|
|
1460
|
+
verdict: "block",
|
|
1461
|
+
reason: "Reading .env files is blocked by project-jail shield"
|
|
1426
1462
|
},
|
|
1427
1463
|
{
|
|
1428
1464
|
name: "shield:project-jail:review-read-credentials-any-tool",
|