@nodatachat/guard 2.4.0 → 2.5.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 +209 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -26,7 +26,7 @@ const reporter_1 = require("./reporter");
|
|
|
26
26
|
const scheduler_1 = require("./fixers/scheduler");
|
|
27
27
|
const vault_crypto_1 = require("./vault-crypto");
|
|
28
28
|
const capsule_dir_1 = require("./capsule-dir");
|
|
29
|
-
const VERSION = "2.
|
|
29
|
+
const VERSION = "2.5.1";
|
|
30
30
|
async function main() {
|
|
31
31
|
const args = process.argv.slice(2);
|
|
32
32
|
// ── Subcommand routing ──
|
|
@@ -114,6 +114,77 @@ async function main() {
|
|
|
114
114
|
licenseKey = process.env.NDC_LICENSE || process.env.NODATA_LICENSE_KEY || process.env.NODATA_API_KEY || process.env.NDC_API_KEY;
|
|
115
115
|
if (!dbUrl)
|
|
116
116
|
dbUrl = process.env.DATABASE_URL;
|
|
117
|
+
// ── Auto-detect from .env files ──
|
|
118
|
+
// Guard reads .env files to find DATABASE_URL and license keys.
|
|
119
|
+
// ALL values stay local — never sent anywhere.
|
|
120
|
+
// If DB URL is found, Guard asks for explicit consent before connecting.
|
|
121
|
+
if (!dbUrl || !licenseKey) {
|
|
122
|
+
const envResult = readEnvFiles(projectDir);
|
|
123
|
+
if (!licenseKey && envResult.licenseKey) {
|
|
124
|
+
licenseKey = envResult.licenseKey;
|
|
125
|
+
if (!ciMode)
|
|
126
|
+
console.log(`\n \x1b[32m✓\x1b[0m Found license key in ${envResult.licenseSource}`);
|
|
127
|
+
}
|
|
128
|
+
// Try direct DB URL first
|
|
129
|
+
let detectedDbUrl = envResult.dbUrl;
|
|
130
|
+
let detectedDbSource = envResult.dbSource;
|
|
131
|
+
// If no direct DB URL found, try to construct from Supabase URL
|
|
132
|
+
// Supabase projects have a predictable connection string format
|
|
133
|
+
if (!detectedDbUrl && envResult.supabaseUrl && !ciMode) {
|
|
134
|
+
const match = envResult.supabaseUrl.match(/https:\/\/([a-z0-9]+)\.supabase\.co/);
|
|
135
|
+
if (match) {
|
|
136
|
+
const projectRef = match[1];
|
|
137
|
+
console.log("");
|
|
138
|
+
console.log(" ╔══════════════════════════════════════════╗");
|
|
139
|
+
console.log(" ║ \x1b[33mSupabase project detected\x1b[0m ║");
|
|
140
|
+
console.log(" ╚══════════════════════════════════════════╝");
|
|
141
|
+
console.log(` Project: ${projectRef}.supabase.co`);
|
|
142
|
+
console.log("");
|
|
143
|
+
console.log(" \x1b[2mTo verify DB encryption, Guard needs a direct connection.\x1b[0m");
|
|
144
|
+
console.log(" \x1b[2mFind it in: Supabase Dashboard → Settings → Database → URI\x1b[0m");
|
|
145
|
+
console.log("");
|
|
146
|
+
const password = await askConsent(" Paste your database password (or press Enter to skip): ", true);
|
|
147
|
+
if (password && typeof password === "string" && password.trim()) {
|
|
148
|
+
detectedDbUrl = `postgresql://postgres.${projectRef}:${password.trim()}@aws-0-eu-central-1.pooler.supabase.com:6543/postgres`;
|
|
149
|
+
detectedDbSource = "Supabase (auto-constructed)";
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
console.log(" \x1b[33m→\x1b[0m No password — skipping database scan\n");
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
if (!dbUrl && detectedDbUrl) {
|
|
157
|
+
// Mask the connection string for display (show host only)
|
|
158
|
+
const masked = maskDbUrl(detectedDbUrl);
|
|
159
|
+
if (ciMode) {
|
|
160
|
+
// CI mode: auto-consent (operator set up the env)
|
|
161
|
+
dbUrl = detectedDbUrl;
|
|
162
|
+
console.log(`[nodata-guard] Using database from ${detectedDbSource}`);
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
// Interactive: ask for consent
|
|
166
|
+
console.log("");
|
|
167
|
+
console.log(" ╔══════════════════════════════════════════╗");
|
|
168
|
+
console.log(" ║ \x1b[33mDatabase connection found\x1b[0m ║");
|
|
169
|
+
console.log(" ╚══════════════════════════════════════════╝");
|
|
170
|
+
console.log(` Source: ${detectedDbSource}`);
|
|
171
|
+
console.log(` URL: ${masked}`);
|
|
172
|
+
console.log("");
|
|
173
|
+
console.log(" \x1b[2mGuard will connect to your database to verify encryption.\x1b[0m");
|
|
174
|
+
console.log(" \x1b[2mOnly checks if values START WITH encryption prefixes.\x1b[0m");
|
|
175
|
+
console.log(" \x1b[2mNo data values are read, copied, or sent anywhere.\x1b[0m");
|
|
176
|
+
console.log("");
|
|
177
|
+
const consent = await askConsent(" Connect to database? (y/n): ");
|
|
178
|
+
if (consent) {
|
|
179
|
+
dbUrl = detectedDbUrl;
|
|
180
|
+
console.log(` \x1b[32m✓\x1b[0m Database scan enabled\n`);
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
console.log(" \x1b[33m→\x1b[0m Skipping database scan — code-only mode\n");
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
117
188
|
// Shared Protect config fallback (~/.nodata/config.json)
|
|
118
189
|
if (!licenseKey) {
|
|
119
190
|
try {
|
|
@@ -583,6 +654,135 @@ function loadOrCreateConfig(projectDir, overrides) {
|
|
|
583
654
|
},
|
|
584
655
|
});
|
|
585
656
|
}
|
|
657
|
+
// ═══════════════════════════════════════════════════════════
|
|
658
|
+
// Consent & display helpers
|
|
659
|
+
// ═══════════════════════════════════════════════════════════
|
|
660
|
+
function maskDbUrl(url) {
|
|
661
|
+
try {
|
|
662
|
+
const parsed = new URL(url);
|
|
663
|
+
const host = parsed.hostname;
|
|
664
|
+
const port = parsed.port || (url.includes("6543") ? "6543" : "5432");
|
|
665
|
+
const db = parsed.pathname.replace("/", "") || "postgres";
|
|
666
|
+
const user = parsed.username ? parsed.username.slice(0, 8) + "..." : "***";
|
|
667
|
+
return `${user}@${host}:${port}/${db}`;
|
|
668
|
+
}
|
|
669
|
+
catch {
|
|
670
|
+
// Fallback: show first 20 chars + mask
|
|
671
|
+
return url.slice(0, 20) + "...****";
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
function askConsent(prompt, returnRaw) {
|
|
675
|
+
return new Promise((resolve) => {
|
|
676
|
+
const readline = require("readline");
|
|
677
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
678
|
+
rl.question(prompt, (answer) => {
|
|
679
|
+
rl.close();
|
|
680
|
+
if (returnRaw) {
|
|
681
|
+
resolve(answer);
|
|
682
|
+
}
|
|
683
|
+
else {
|
|
684
|
+
const a = answer.trim().toLowerCase();
|
|
685
|
+
resolve(a === "y" || a === "yes" || a === "כ" || a === "כן");
|
|
686
|
+
}
|
|
687
|
+
});
|
|
688
|
+
});
|
|
689
|
+
}
|
|
690
|
+
function readEnvFiles(projectDir) {
|
|
691
|
+
const result = {
|
|
692
|
+
dbUrl: null, dbSource: "",
|
|
693
|
+
licenseKey: null, licenseSource: "",
|
|
694
|
+
supabaseUrl: null, supabaseKey: null,
|
|
695
|
+
};
|
|
696
|
+
// Priority order — more specific files override general ones
|
|
697
|
+
const envFiles = [
|
|
698
|
+
".env",
|
|
699
|
+
".env.local",
|
|
700
|
+
".env.development",
|
|
701
|
+
".env.development.local",
|
|
702
|
+
".env.production",
|
|
703
|
+
".env.production.local",
|
|
704
|
+
];
|
|
705
|
+
// Keys that contain database connection strings
|
|
706
|
+
const DB_KEYS = [
|
|
707
|
+
"DATABASE_URL",
|
|
708
|
+
"DIRECT_URL",
|
|
709
|
+
"POSTGRES_URL",
|
|
710
|
+
"POSTGRES_PRISMA_URL",
|
|
711
|
+
"POSTGRES_URL_NON_POOLING",
|
|
712
|
+
"PG_CONNECTION_STRING",
|
|
713
|
+
"DB_URL",
|
|
714
|
+
"SUPABASE_DB_URL",
|
|
715
|
+
"DATABASE_CONNECTION_STRING",
|
|
716
|
+
];
|
|
717
|
+
// Keys that contain license/API keys
|
|
718
|
+
const LICENSE_KEYS = [
|
|
719
|
+
"NDC_LICENSE",
|
|
720
|
+
"NODATA_LICENSE_KEY",
|
|
721
|
+
"NODATA_API_KEY",
|
|
722
|
+
"NDC_API_KEY",
|
|
723
|
+
"NDC_PROTECT_KEY",
|
|
724
|
+
"CAPSULE_LICENSE_KEY",
|
|
725
|
+
"CAPSULE_API_KEY",
|
|
726
|
+
];
|
|
727
|
+
const SUPABASE_URL_KEYS = [
|
|
728
|
+
"NEXT_PUBLIC_SUPABASE_URL",
|
|
729
|
+
"SUPABASE_URL",
|
|
730
|
+
"VITE_SUPABASE_URL",
|
|
731
|
+
"REACT_APP_SUPABASE_URL",
|
|
732
|
+
"NUXT_PUBLIC_SUPABASE_URL",
|
|
733
|
+
];
|
|
734
|
+
const SUPABASE_KEY_KEYS = [
|
|
735
|
+
"SUPABASE_SERVICE_ROLE_KEY",
|
|
736
|
+
"SUPABASE_SERVICE_KEY",
|
|
737
|
+
];
|
|
738
|
+
for (const envFile of envFiles) {
|
|
739
|
+
const filePath = (0, path_1.resolve)(projectDir, envFile);
|
|
740
|
+
if (!(0, fs_1.existsSync)(filePath))
|
|
741
|
+
continue;
|
|
742
|
+
try {
|
|
743
|
+
const content = (0, fs_1.readFileSync)(filePath, "utf-8");
|
|
744
|
+
const lines = content.split("\n");
|
|
745
|
+
for (const line of lines) {
|
|
746
|
+
const trimmed = line.trim();
|
|
747
|
+
if (!trimmed || trimmed.startsWith("#"))
|
|
748
|
+
continue;
|
|
749
|
+
const eqIdx = trimmed.indexOf("=");
|
|
750
|
+
if (eqIdx < 1)
|
|
751
|
+
continue;
|
|
752
|
+
const key = trimmed.slice(0, eqIdx).trim();
|
|
753
|
+
let val = trimmed.slice(eqIdx + 1).trim();
|
|
754
|
+
// Strip quotes
|
|
755
|
+
if ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith("'") && val.endsWith("'"))) {
|
|
756
|
+
val = val.slice(1, -1);
|
|
757
|
+
}
|
|
758
|
+
if (!val)
|
|
759
|
+
continue;
|
|
760
|
+
// Check DB keys
|
|
761
|
+
if (!result.dbUrl && DB_KEYS.includes(key)) {
|
|
762
|
+
if (val.startsWith("postgres") || val.startsWith("pg://") || val.includes("5432") || val.includes("6543")) {
|
|
763
|
+
result.dbUrl = val;
|
|
764
|
+
result.dbSource = envFile;
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
// Check license keys
|
|
768
|
+
if (!result.licenseKey && LICENSE_KEYS.includes(key)) {
|
|
769
|
+
result.licenseKey = val;
|
|
770
|
+
result.licenseSource = envFile;
|
|
771
|
+
}
|
|
772
|
+
// Check Supabase URL (for fallback DB construction)
|
|
773
|
+
if (!result.supabaseUrl && SUPABASE_URL_KEYS.includes(key)) {
|
|
774
|
+
result.supabaseUrl = val;
|
|
775
|
+
}
|
|
776
|
+
// Check Supabase service role key
|
|
777
|
+
if (!result.supabaseKey && SUPABASE_KEY_KEYS.includes(key)) {
|
|
778
|
+
result.supabaseKey = val;
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
catch { /* skip unreadable files */ }
|
|
783
|
+
}
|
|
784
|
+
return result;
|
|
785
|
+
}
|
|
586
786
|
function handleAttest(args) {
|
|
587
787
|
let licenseKey;
|
|
588
788
|
let projectDir = process.cwd();
|
|
@@ -715,9 +915,17 @@ function printHelp() {
|
|
|
715
915
|
✓ Prove — cryptographic proof chain of scan results
|
|
716
916
|
✓ Monitor — track score over time via dashboard
|
|
717
917
|
|
|
918
|
+
Auto-detection:
|
|
919
|
+
Guard automatically reads .env files to find DATABASE_URL
|
|
920
|
+
and license keys. If a database is found, Guard asks for
|
|
921
|
+
your explicit consent before connecting. All values stay
|
|
922
|
+
local — never sent anywhere. In CI mode (--ci), consent
|
|
923
|
+
is automatic (operator configured the environment).
|
|
924
|
+
|
|
718
925
|
What we NEVER do:
|
|
719
926
|
✗ Modify your code, database, or configuration
|
|
720
927
|
✗ Receive data values, source code, or credentials
|
|
928
|
+
✗ Read actual data from your database (only checks prefixes)
|
|
721
929
|
|
|
722
930
|
Important:
|
|
723
931
|
Recommendations require understanding of YOUR system —
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nodatachat/guard",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.5.1",
|
|
4
4
|
"description": "NoData Guard — continuous security scanner. Runs locally, reports only metadata. Your data never leaves your machine.",
|
|
5
5
|
"main": "./dist/cli.js",
|
|
6
6
|
"types": "./dist/cli.d.ts",
|