@nodatachat/guard 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/LICENSE.md +28 -0
  2. package/README.md +120 -0
  3. package/dist/activation.d.ts +8 -0
  4. package/dist/activation.js +110 -0
  5. package/dist/cli.d.ts +2 -0
  6. package/dist/cli.js +458 -0
  7. package/dist/code-scanner.d.ts +14 -0
  8. package/dist/code-scanner.js +309 -0
  9. package/dist/db-scanner.d.ts +7 -0
  10. package/dist/db-scanner.js +185 -0
  11. package/dist/fixers/fix-csrf.d.ts +9 -0
  12. package/dist/fixers/fix-csrf.js +113 -0
  13. package/dist/fixers/fix-gitignore.d.ts +9 -0
  14. package/dist/fixers/fix-gitignore.js +71 -0
  15. package/dist/fixers/fix-headers.d.ts +9 -0
  16. package/dist/fixers/fix-headers.js +118 -0
  17. package/dist/fixers/fix-pii-encrypt.d.ts +9 -0
  18. package/dist/fixers/fix-pii-encrypt.js +298 -0
  19. package/dist/fixers/fix-rate-limit.d.ts +9 -0
  20. package/dist/fixers/fix-rate-limit.js +102 -0
  21. package/dist/fixers/fix-rls.d.ts +9 -0
  22. package/dist/fixers/fix-rls.js +243 -0
  23. package/dist/fixers/fix-routes-auth.d.ts +9 -0
  24. package/dist/fixers/fix-routes-auth.js +82 -0
  25. package/dist/fixers/fix-secrets.d.ts +9 -0
  26. package/dist/fixers/fix-secrets.js +132 -0
  27. package/dist/fixers/index.d.ts +11 -0
  28. package/dist/fixers/index.js +37 -0
  29. package/dist/fixers/registry.d.ts +25 -0
  30. package/dist/fixers/registry.js +249 -0
  31. package/dist/fixers/scheduler.d.ts +9 -0
  32. package/dist/fixers/scheduler.js +254 -0
  33. package/dist/fixers/types.d.ts +160 -0
  34. package/dist/fixers/types.js +11 -0
  35. package/dist/reporter.d.ts +28 -0
  36. package/dist/reporter.js +185 -0
  37. package/dist/types.d.ts +154 -0
  38. package/dist/types.js +5 -0
  39. package/package.json +61 -0
@@ -0,0 +1,28 @@
1
+ import type { FullReport, MetadataReport, PIIFieldResult, RouteResult, SecretResult, RLSResult, InfraResult } from "./types";
2
+ interface ReportInput {
3
+ scanId: string;
4
+ projectName: string;
5
+ projectHash: string;
6
+ scanType: "code" | "db" | "full";
7
+ scanDurationMs: number;
8
+ previousScore: number | null;
9
+ code?: {
10
+ filesScanned: number;
11
+ framework: string;
12
+ database: string;
13
+ piiFields: PIIFieldResult[];
14
+ routes: RouteResult[];
15
+ secrets: SecretResult[];
16
+ };
17
+ db?: {
18
+ tables: number;
19
+ piiFields: PIIFieldResult[];
20
+ rls: RLSResult[];
21
+ infra: InfraResult;
22
+ };
23
+ }
24
+ export declare function generateReports(input: ReportInput): {
25
+ full: FullReport;
26
+ metadata: MetadataReport;
27
+ };
28
+ export {};
@@ -0,0 +1,185 @@
1
+ "use strict";
2
+ // ═══════════════════════════════════════════════════════════
3
+ // @nodatachat/guard — Dual Report Generator
4
+ //
5
+ // Generates TWO reports from the same scan:
6
+ //
7
+ // 1. full-report.json — STAYS LOCAL — customer sees everything
8
+ // Contains: actual field names, values context, full details
9
+ //
10
+ // 2. metadata-only.json — SENT TO NODATA — no data values
11
+ // Contains: table.column names, encrypted yes/no, counts
12
+ //
13
+ // The customer can diff the two files to see exactly what was redacted.
14
+ // ═══════════════════════════════════════════════════════════
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.generateReports = generateReports;
17
+ const crypto_1 = require("crypto");
18
+ const GUARD_VERSION = "1.0.0";
19
+ function generateReports(input) {
20
+ const now = new Date().toISOString();
21
+ // Merge PII fields from code + DB
22
+ const allPII = [];
23
+ const seenFields = new Set();
24
+ if (input.code) {
25
+ for (const f of input.code.piiFields) {
26
+ const key = `${f.table}.${f.column}`;
27
+ if (!seenFields.has(key)) {
28
+ seenFields.add(key);
29
+ allPII.push(f);
30
+ }
31
+ }
32
+ }
33
+ if (input.db) {
34
+ for (const f of input.db.piiFields) {
35
+ const key = `${f.table}.${f.column}`;
36
+ if (seenFields.has(key)) {
37
+ // DB result overrides code result (DB is ground truth)
38
+ const idx = allPII.findIndex(p => `${p.table}.${p.column}` === key);
39
+ if (idx >= 0)
40
+ allPII[idx] = { ...allPII[idx], ...f, encrypted: f.encrypted || allPII[idx].encrypted };
41
+ }
42
+ else {
43
+ seenFields.add(key);
44
+ allPII.push(f);
45
+ }
46
+ }
47
+ }
48
+ // Calculate scores
49
+ const totalPII = allPII.length;
50
+ const encryptedPII = allPII.filter(f => f.encrypted).length;
51
+ const plaintextPII = totalPII - encryptedPII;
52
+ const encCoverage = totalPII > 0 ? Math.round((encryptedPII / totalPII) * 100) : 100;
53
+ const totalRoutes = input.code?.routes.length || 0;
54
+ const protectedRoutes = input.code?.routes.filter(r => r.has_auth).length || 0;
55
+ const routeProtection = totalRoutes > 0 ? Math.round((protectedRoutes / totalRoutes) * 100) : 100;
56
+ const criticalSecrets = input.code?.secrets.filter(s => s.severity === "critical" && !s.is_env_interpolated).length || 0;
57
+ const highSecrets = input.code?.secrets.filter(s => s.severity === "high" && !s.is_env_interpolated).length || 0;
58
+ const medSecrets = input.code?.secrets.filter(s => s.severity === "medium" && !s.is_env_interpolated).length || 0;
59
+ // Overall score: weighted average
60
+ const encWeight = 0.5; // encryption is most important
61
+ const routeWeight = 0.25;
62
+ const secretWeight = 0.25;
63
+ const secretScore = Math.max(0, 100 - (criticalSecrets * 25 + highSecrets * 10 + medSecrets * 3));
64
+ const overallScore = Math.round(encCoverage * encWeight + routeProtection * routeWeight + secretScore * secretWeight);
65
+ const improved = input.previousScore !== null && overallScore > input.previousScore;
66
+ // Proof hash
67
+ const proofData = allPII.map(f => `${f.table}.${f.column}:${f.encrypted}`).join("|") + `|${overallScore}|${now}`;
68
+ const proofHash = (0, crypto_1.createHash)("sha256").update(proofData).digest("hex");
69
+ // ── Build metadata report (what we send) ──
70
+ const metadata = {
71
+ version: "1.0",
72
+ scan_id: input.scanId,
73
+ generated_at: now,
74
+ project_hash: input.projectHash,
75
+ scan_type: input.scanType,
76
+ scan_duration_ms: input.scanDurationMs,
77
+ guard_version: GUARD_VERSION,
78
+ scores: {
79
+ overall: overallScore,
80
+ previous: input.previousScore,
81
+ improved,
82
+ delta: input.previousScore !== null ? overallScore - input.previousScore : 0,
83
+ },
84
+ code_summary: input.code ? {
85
+ files_scanned: input.code.filesScanned,
86
+ framework: input.code.framework,
87
+ database: input.code.database,
88
+ pii_fields_total: input.code.piiFields.length,
89
+ pii_fields_encrypted: input.code.piiFields.filter(f => f.encrypted).length,
90
+ encryption_coverage_percent: encCoverage,
91
+ routes_total: totalRoutes,
92
+ routes_protected: protectedRoutes,
93
+ route_protection_percent: routeProtection,
94
+ secrets_found: (input.code.secrets.filter(s => !s.is_env_interpolated)).length,
95
+ secrets_critical: criticalSecrets,
96
+ // Per-field summary — NO VALUES, just table.column + status
97
+ fields: input.code.piiFields.map(f => ({
98
+ table: f.table,
99
+ column: f.column,
100
+ pii_type: f.pii_type,
101
+ encrypted: f.encrypted,
102
+ pattern: f.encryption_pattern,
103
+ })),
104
+ } : null,
105
+ db_summary: input.db ? {
106
+ tables: input.db.tables,
107
+ pii_fields_total: input.db.piiFields.length,
108
+ pii_fields_encrypted: input.db.piiFields.filter(f => f.encrypted).length,
109
+ encryption_coverage_percent: input.db.piiFields.length > 0
110
+ ? Math.round((input.db.piiFields.filter(f => f.encrypted).length / input.db.piiFields.length) * 100)
111
+ : 100,
112
+ rls_tables_enabled: input.db.rls.filter(r => r.rls_enabled).length,
113
+ rls_tables_total: input.db.rls.length,
114
+ rls_coverage_percent: input.db.rls.length > 0
115
+ ? Math.round((input.db.rls.filter(r => r.rls_enabled).length / input.db.rls.length) * 100)
116
+ : 0,
117
+ db_version: input.db.infra.db_version,
118
+ ssl: input.db.infra.ssl,
119
+ has_encrypt_functions: input.db.infra.encrypt_functions,
120
+ trigger_count: input.db.infra.trigger_count,
121
+ } : null,
122
+ issues: {
123
+ critical: criticalSecrets,
124
+ high: highSecrets + plaintextPII,
125
+ medium: medSecrets,
126
+ fixes_available: plaintextPII, // each plaintext field has a migration fix
127
+ },
128
+ proof_hash: proofHash,
129
+ privacy: {
130
+ contains_data_values: false,
131
+ contains_connection_strings: false,
132
+ contains_credentials: false,
133
+ contains_source_code: false,
134
+ contains_file_contents: false,
135
+ customer_can_verify: true,
136
+ },
137
+ };
138
+ // ── Build full report (stays local) ──
139
+ const full = {
140
+ version: "1.0",
141
+ scan_id: input.scanId,
142
+ generated_at: now,
143
+ project_name: input.projectName,
144
+ scan_type: input.scanType,
145
+ scan_duration_ms: input.scanDurationMs,
146
+ overall_score: overallScore,
147
+ previous_score: input.previousScore,
148
+ improved,
149
+ code: input.code ? {
150
+ files_scanned: input.code.filesScanned,
151
+ framework: input.code.framework,
152
+ database: input.code.database,
153
+ pii_fields: input.code.piiFields,
154
+ routes: input.code.routes,
155
+ secrets: input.code.secrets,
156
+ encryption_coverage_percent: encCoverage,
157
+ route_protection_percent: routeProtection,
158
+ } : null,
159
+ db: input.db ? {
160
+ tables: input.db.tables,
161
+ pii_fields: input.db.piiFields,
162
+ rls: input.db.rls,
163
+ infra: input.db.infra,
164
+ encryption_coverage_percent: input.db.piiFields.length > 0
165
+ ? Math.round((input.db.piiFields.filter(f => f.encrypted).length / input.db.piiFields.length) * 100)
166
+ : 100,
167
+ rls_coverage_percent: input.db.rls.length > 0
168
+ ? Math.round((input.db.rls.filter(r => r.rls_enabled).length / input.db.rls.length) * 100)
169
+ : 0,
170
+ } : null,
171
+ summary: {
172
+ total_pii_fields: totalPII,
173
+ encrypted_fields: encryptedPII,
174
+ plaintext_fields: plaintextPII,
175
+ coverage_percent: encCoverage,
176
+ critical_issues: criticalSecrets,
177
+ high_issues: highSecrets + plaintextPII,
178
+ medium_issues: medSecrets,
179
+ fixes_available: plaintextPII,
180
+ },
181
+ proof_hash: proofHash,
182
+ metadata_preview: metadata,
183
+ };
184
+ return { full, metadata };
185
+ }
@@ -0,0 +1,154 @@
1
+ export interface ActivationRequest {
2
+ license_key: string;
3
+ project_hash: string;
4
+ guard_version: string;
5
+ scan_type: "code" | "db" | "full";
6
+ }
7
+ export interface ActivationResponse {
8
+ success: boolean;
9
+ activation_key: string;
10
+ tier: string;
11
+ features: string[];
12
+ scan_id: string;
13
+ expires_at: string;
14
+ error?: string;
15
+ }
16
+ export interface PIIFieldResult {
17
+ table: string;
18
+ column: string;
19
+ pii_type: string;
20
+ encrypted: boolean;
21
+ encryption_pattern: string | null;
22
+ has_companion_column: boolean;
23
+ row_count: number;
24
+ encrypted_count: number;
25
+ }
26
+ export interface RouteResult {
27
+ path: string;
28
+ has_auth: boolean;
29
+ auth_type: string | null;
30
+ }
31
+ export interface SecretResult {
32
+ file: string;
33
+ line: number;
34
+ type: string;
35
+ severity: "critical" | "high" | "medium";
36
+ is_env_interpolated: boolean;
37
+ }
38
+ export interface RLSResult {
39
+ table: string;
40
+ rls_enabled: boolean;
41
+ policy_count: number;
42
+ }
43
+ export interface InfraResult {
44
+ db_type: string;
45
+ db_version: string;
46
+ ssl: boolean;
47
+ has_pgcrypto: boolean;
48
+ encrypt_functions: boolean;
49
+ trigger_count: number;
50
+ }
51
+ export interface FullReport {
52
+ version: "1.0";
53
+ scan_id: string;
54
+ generated_at: string;
55
+ project_name: string;
56
+ scan_type: "code" | "db" | "full";
57
+ scan_duration_ms: number;
58
+ overall_score: number;
59
+ previous_score: number | null;
60
+ improved: boolean;
61
+ code: {
62
+ files_scanned: number;
63
+ framework: string;
64
+ database: string;
65
+ pii_fields: PIIFieldResult[];
66
+ routes: RouteResult[];
67
+ secrets: SecretResult[];
68
+ encryption_coverage_percent: number;
69
+ route_protection_percent: number;
70
+ } | null;
71
+ db: {
72
+ tables: number;
73
+ pii_fields: PIIFieldResult[];
74
+ rls: RLSResult[];
75
+ infra: InfraResult;
76
+ encryption_coverage_percent: number;
77
+ rls_coverage_percent: number;
78
+ } | null;
79
+ summary: {
80
+ total_pii_fields: number;
81
+ encrypted_fields: number;
82
+ plaintext_fields: number;
83
+ coverage_percent: number;
84
+ critical_issues: number;
85
+ high_issues: number;
86
+ medium_issues: number;
87
+ fixes_available: number;
88
+ };
89
+ proof_hash: string;
90
+ metadata_preview: MetadataReport;
91
+ }
92
+ export interface MetadataReport {
93
+ version: "1.0";
94
+ scan_id: string;
95
+ generated_at: string;
96
+ project_hash: string;
97
+ scan_type: "code" | "db" | "full";
98
+ scan_duration_ms: number;
99
+ guard_version: string;
100
+ scores: {
101
+ overall: number;
102
+ previous: number | null;
103
+ improved: boolean;
104
+ delta: number;
105
+ };
106
+ code_summary: {
107
+ files_scanned: number;
108
+ framework: string;
109
+ database: string;
110
+ pii_fields_total: number;
111
+ pii_fields_encrypted: number;
112
+ encryption_coverage_percent: number;
113
+ routes_total: number;
114
+ routes_protected: number;
115
+ route_protection_percent: number;
116
+ secrets_found: number;
117
+ secrets_critical: number;
118
+ fields: Array<{
119
+ table: string;
120
+ column: string;
121
+ pii_type: string;
122
+ encrypted: boolean;
123
+ pattern: string | null;
124
+ }>;
125
+ } | null;
126
+ db_summary: {
127
+ tables: number;
128
+ pii_fields_total: number;
129
+ pii_fields_encrypted: number;
130
+ encryption_coverage_percent: number;
131
+ rls_tables_enabled: number;
132
+ rls_tables_total: number;
133
+ rls_coverage_percent: number;
134
+ db_version: string;
135
+ ssl: boolean;
136
+ has_encrypt_functions: boolean;
137
+ trigger_count: number;
138
+ } | null;
139
+ issues: {
140
+ critical: number;
141
+ high: number;
142
+ medium: number;
143
+ fixes_available: number;
144
+ };
145
+ proof_hash: string;
146
+ privacy: {
147
+ contains_data_values: false;
148
+ contains_connection_strings: false;
149
+ contains_credentials: false;
150
+ contains_source_code: false;
151
+ contains_file_contents: false;
152
+ customer_can_verify: true;
153
+ };
154
+ }
package/dist/types.js ADDED
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ // ═══════════════════════════════════════════════════════════
3
+ // @nodatachat/guard — Type Definitions
4
+ // ═══════════════════════════════════════════════════════════
5
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "@nodatachat/guard",
3
+ "version": "2.0.0",
4
+ "description": "NoData Guard — continuous security scanner. Runs locally, reports only metadata. Your data never leaves your machine.",
5
+ "main": "./dist/cli.js",
6
+ "types": "./dist/cli.d.ts",
7
+ "bin": {
8
+ "nodata-guard": "./dist/cli.js"
9
+ },
10
+ "files": [
11
+ "dist",
12
+ "README.md",
13
+ "LICENSE.md"
14
+ ],
15
+ "scripts": {
16
+ "build": "tsc",
17
+ "dev": "npx tsx src/cli.ts",
18
+ "scan": "npx tsx src/cli.ts --license-key NDC-DEV --skip-send",
19
+ "scan:plan": "npx tsx src/cli.ts --license-key NDC-DEV --skip-send --fix-plan",
20
+ "scan:fix": "npx tsx src/cli.ts --license-key NDC-DEV --skip-send --fix",
21
+ "prepublishOnly": "npm run build",
22
+ "start": "node dist/cli.js"
23
+ },
24
+ "engines": {
25
+ "node": ">=18.0.0"
26
+ },
27
+ "dependencies": {
28
+ "pg": "^8.13.0"
29
+ },
30
+ "devDependencies": {
31
+ "typescript": "^5.7.0",
32
+ "@types/pg": "^8.11.0",
33
+ "@types/node": "^22.0.0"
34
+ },
35
+ "keywords": [
36
+ "security",
37
+ "soc2",
38
+ "soc1",
39
+ "compliance",
40
+ "encryption",
41
+ "audit",
42
+ "pii",
43
+ "scanner",
44
+ "guard",
45
+ "nodata",
46
+ "privacy",
47
+ "gdpr",
48
+ "cicd",
49
+ "devsecops"
50
+ ],
51
+ "license": "SEE LICENSE IN LICENSE.md",
52
+ "homepage": "https://nodatachat.com/guard",
53
+ "repository": {
54
+ "type": "git",
55
+ "url": "https://github.com/nodatachat/guard"
56
+ },
57
+ "bugs": {
58
+ "url": "https://github.com/nodatachat/guard/issues"
59
+ },
60
+ "author": "NoDataChat <support@nodatachat.com>"
61
+ }