@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.
Files changed (2) hide show
  1. package/dist/cli.js +209 -1
  2. 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.4.0";
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.4.0",
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",