@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,249 @@
1
+ "use strict";
2
+ // ═══════════════════════════════════════════════════════════
3
+ // Guard Capsule — Fixer Registry
4
+ //
5
+ // Maps finding types to fixer modules.
6
+ // Orchestrates: analyze → plan → preview → apply → verify
7
+ // ═══════════════════════════════════════════════════════════
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.getFixer = getFixer;
10
+ exports.getAllFixers = getAllFixers;
11
+ exports.getFixersByPriority = getFixersByPriority;
12
+ exports.runCapsule = runCapsule;
13
+ exports.sendNotifications = sendNotifications;
14
+ const crypto_1 = require("crypto");
15
+ // Import fixers (will be implemented one by one)
16
+ const fix_pii_encrypt_1 = require("./fix-pii-encrypt");
17
+ const fix_rls_1 = require("./fix-rls");
18
+ const fix_secrets_1 = require("./fix-secrets");
19
+ const fix_routes_auth_1 = require("./fix-routes-auth");
20
+ const fix_headers_1 = require("./fix-headers");
21
+ const fix_csrf_1 = require("./fix-csrf");
22
+ const fix_rate_limit_1 = require("./fix-rate-limit");
23
+ const fix_gitignore_1 = require("./fix-gitignore");
24
+ // ── Registry ──
25
+ const ALL_FIXERS = [
26
+ new fix_pii_encrypt_1.PiiEncryptFixer(),
27
+ new fix_rls_1.RlsFixer(),
28
+ new fix_secrets_1.SecretsFixer(),
29
+ new fix_routes_auth_1.RoutesAuthFixer(),
30
+ new fix_headers_1.HeadersFixer(),
31
+ new fix_csrf_1.CsrfFixer(),
32
+ new fix_rate_limit_1.RateLimitFixer(),
33
+ new fix_gitignore_1.GitignoreFixer(),
34
+ ];
35
+ function getFixer(category) {
36
+ return ALL_FIXERS.find(f => f.category === category);
37
+ }
38
+ function getAllFixers() {
39
+ return [...ALL_FIXERS];
40
+ }
41
+ function getFixersByPriority() {
42
+ // Critical fixers first, then high, then medium
43
+ const priority = [
44
+ "secrets", // Critical — hardcoded secrets are immediate risk
45
+ "pii-encrypt", // Critical — exposed PII
46
+ "rls", // High — DB access control
47
+ "routes-auth", // High — API exposure
48
+ "headers", // High — browser security
49
+ "csrf", // High — request forgery
50
+ "rate-limit", // Medium — abuse prevention
51
+ "gitignore", // Medium — prevention
52
+ ];
53
+ return priority.map(c => getFixer(c)).filter(Boolean);
54
+ }
55
+ async function runCapsule(context, options, onProgress) {
56
+ const startTime = Date.now();
57
+ const fixers = options.fixers
58
+ ? options.fixers.map(c => getFixer(c)).filter(Boolean)
59
+ : getFixersByPriority();
60
+ // Filter out skipped fixers from config
61
+ const skipList = context.config.fix?.skipFixers || [];
62
+ const activeFixer = fixers.filter(f => !skipList.includes(f.category));
63
+ const plans = [];
64
+ const results = [];
65
+ // ── Phase 1: Analyze (generate plans) ──
66
+ onProgress?.("Analyzing findings...");
67
+ for (const fixer of activeFixer) {
68
+ onProgress?.(` Analyzing ${fixer.name}...`);
69
+ try {
70
+ const plan = await fixer.analyze(context);
71
+ if (plan.actions.length > 0) {
72
+ plans.push(plan);
73
+ onProgress?.(` ${fixer.name}: ${plan.totalActions} actions (${plan.autoFixable} auto, ${plan.manualRequired} manual)`);
74
+ }
75
+ else {
76
+ onProgress?.(` ${fixer.name}: no issues found`);
77
+ }
78
+ }
79
+ catch (err) {
80
+ onProgress?.(` ${fixer.name}: analysis failed — ${err instanceof Error ? err.message : err}`);
81
+ }
82
+ }
83
+ if (options.mode === "plan" || options.dryRun) {
84
+ return {
85
+ plans,
86
+ results: [],
87
+ totalActions: plans.reduce((s, p) => s + p.totalActions, 0),
88
+ applied: 0,
89
+ failed: 0,
90
+ skipped: 0,
91
+ manualPending: plans.reduce((s, p) => s + p.manualRequired, 0),
92
+ estimatedScoreImpact: plans.reduce((s, p) => s + p.estimatedScoreImpact, 0),
93
+ proofHash: hashPlans(plans),
94
+ duration: Date.now() - startTime,
95
+ };
96
+ }
97
+ // ── Phase 2: Apply ──
98
+ onProgress?.("Applying fixes...");
99
+ for (const plan of plans) {
100
+ const fixer = getFixer(plan.fixer);
101
+ if (!fixer)
102
+ continue;
103
+ // Check if this fixer needs approval
104
+ const needsApproval = context.config.fix?.requireApproval?.includes(plan.fixer);
105
+ if (needsApproval && !options.interactive) {
106
+ onProgress?.(` ${fixer.name}: skipped (requires approval)`);
107
+ continue;
108
+ }
109
+ onProgress?.(` Applying ${fixer.name} (${plan.autoFixable} actions)...`);
110
+ try {
111
+ const result = await fixer.apply(plan, options.actionIds);
112
+ results.push(result);
113
+ onProgress?.(` ${fixer.name}: ${result.applied} applied, ${result.failed} failed, ${result.skipped} skipped`);
114
+ }
115
+ catch (err) {
116
+ onProgress?.(` ${fixer.name}: apply failed — ${err instanceof Error ? err.message : err}`);
117
+ }
118
+ }
119
+ // ── Phase 3: Verify (optional) ──
120
+ if (options.mode === "verify") {
121
+ onProgress?.("Verifying fixes...");
122
+ for (const result of results) {
123
+ if (result.applied === 0)
124
+ continue;
125
+ const fixer = getFixer(result.fixer);
126
+ if (!fixer)
127
+ continue;
128
+ const verified = await fixer.verify(result);
129
+ onProgress?.(` ${fixer.name}: ${verified ? "VERIFIED" : "VERIFICATION FAILED"}`);
130
+ }
131
+ }
132
+ const totalApplied = results.reduce((s, r) => s + r.applied, 0);
133
+ const totalFailed = results.reduce((s, r) => s + r.failed, 0);
134
+ const totalSkipped = results.reduce((s, r) => s + r.skipped, 0);
135
+ const totalManual = results.reduce((s, r) => s + r.manualPending, 0);
136
+ return {
137
+ plans,
138
+ results,
139
+ totalActions: plans.reduce((s, p) => s + p.totalActions, 0),
140
+ applied: totalApplied,
141
+ failed: totalFailed,
142
+ skipped: totalSkipped,
143
+ manualPending: totalManual,
144
+ estimatedScoreImpact: plans.reduce((s, p) => s + p.estimatedScoreImpact, 0),
145
+ proofHash: hashResults(results),
146
+ duration: Date.now() - startTime,
147
+ };
148
+ }
149
+ // ── Notification Dispatcher ──
150
+ async function sendNotifications(config, event, payload, onLog) {
151
+ if (!config.notify)
152
+ return;
153
+ if (!config.notify.onEvents.includes(event))
154
+ return;
155
+ // Email (via NoData API — we relay, never see content)
156
+ if (config.notify.email?.length) {
157
+ for (const email of config.notify.email) {
158
+ try {
159
+ await fetch("https://nodatachat.com/api/guard/notify", {
160
+ method: "POST",
161
+ headers: { "Content-Type": "application/json" },
162
+ body: JSON.stringify({ channel: "email", to: email, payload }),
163
+ signal: AbortSignal.timeout(5000),
164
+ });
165
+ onLog?.(` Notified: ${email}`);
166
+ }
167
+ catch {
168
+ onLog?.(` Email notification failed: ${email}`);
169
+ }
170
+ }
171
+ }
172
+ // Webhook (direct — customer's own endpoint)
173
+ if (config.notify.webhook?.length) {
174
+ for (const url of config.notify.webhook) {
175
+ try {
176
+ await fetch(url, {
177
+ method: "POST",
178
+ headers: { "Content-Type": "application/json", "X-NoData-Event": event },
179
+ body: JSON.stringify(payload),
180
+ signal: AbortSignal.timeout(5000),
181
+ });
182
+ onLog?.(` Webhook sent: ${url}`);
183
+ }
184
+ catch {
185
+ onLog?.(` Webhook failed: ${url}`);
186
+ }
187
+ }
188
+ }
189
+ // Slack
190
+ if (config.notify.slack) {
191
+ const { webhookUrl, channel, mentionOn } = config.notify.slack;
192
+ const shouldMention = mentionOn === "critical" && payload.critical > 0
193
+ || mentionOn === "drop" && payload.delta < 0;
194
+ const emoji = payload.delta > 0 ? ":arrow_up:" : payload.delta < 0 ? ":arrow_down:" : ":white_check_mark:";
195
+ const mention = shouldMention ? "<!channel> " : "";
196
+ const text = [
197
+ `${mention}${emoji} *NoData Guard* — ${event.replace(/-/g, " ")}`,
198
+ `Score: *${payload.score}%*${payload.previousScore != null ? ` (was ${payload.previousScore}%)` : ""}`,
199
+ payload.critical > 0 ? `:red_circle: ${payload.critical} critical` : null,
200
+ payload.high > 0 ? `:orange_circle: ${payload.high} high` : null,
201
+ payload.fixesApplied ? `:wrench: ${payload.fixesApplied} fixes applied` : null,
202
+ payload.dashboardUrl ? `<${payload.dashboardUrl}|View Dashboard>` : null,
203
+ ].filter(Boolean).join("\n");
204
+ try {
205
+ await fetch(webhookUrl, {
206
+ method: "POST",
207
+ headers: { "Content-Type": "application/json" },
208
+ body: JSON.stringify({ text, channel }),
209
+ signal: AbortSignal.timeout(5000),
210
+ });
211
+ onLog?.(" Slack notified");
212
+ }
213
+ catch {
214
+ onLog?.(" Slack notification failed");
215
+ }
216
+ }
217
+ // Telegram
218
+ if (config.notify.telegram) {
219
+ const { botToken, chatId } = config.notify.telegram;
220
+ const emoji = payload.delta > 0 ? "+" : payload.delta < 0 ? "" : "";
221
+ const text = [
222
+ `NoData Guard — ${event.replace(/-/g, " ")}`,
223
+ `Score: ${payload.score}%${payload.previousScore != null ? ` (${emoji}${payload.delta})` : ""}`,
224
+ payload.critical > 0 ? `Critical: ${payload.critical}` : null,
225
+ payload.fixesApplied ? `Fixes: ${payload.fixesApplied} applied` : null,
226
+ ].filter(Boolean).join("\n");
227
+ try {
228
+ await fetch(`https://api.telegram.org/bot${botToken}/sendMessage`, {
229
+ method: "POST",
230
+ headers: { "Content-Type": "application/json" },
231
+ body: JSON.stringify({ chat_id: chatId, text, parse_mode: "Markdown" }),
232
+ signal: AbortSignal.timeout(5000),
233
+ });
234
+ onLog?.(" Telegram notified");
235
+ }
236
+ catch {
237
+ onLog?.(" Telegram notification failed");
238
+ }
239
+ }
240
+ }
241
+ // ── Helpers ──
242
+ function hashPlans(plans) {
243
+ const data = plans.map(p => `${p.fixer}:${p.totalActions}:${p.actions.map(a => a.id).join(",")}`).join("|");
244
+ return (0, crypto_1.createHash)("sha256").update(data).digest("hex");
245
+ }
246
+ function hashResults(results) {
247
+ const data = results.map(r => `${r.fixer}:${r.applied}:${r.proofHash}`).join("|");
248
+ return (0, crypto_1.createHash)("sha256").update(data).digest("hex");
249
+ }
@@ -0,0 +1,9 @@
1
+ import type { CapsuleConfig, CITemplate } from "./types";
2
+ export declare function generateGitHubActions(config: CapsuleConfig): CITemplate;
3
+ export declare function generateGitLabCI(config: CapsuleConfig): CITemplate;
4
+ export declare function generateBitbucket(config: CapsuleConfig): CITemplate;
5
+ export declare function installSchedule(projectDir: string, config: CapsuleConfig, provider?: "github-actions" | "gitlab-ci" | "bitbucket" | "auto"): {
6
+ files: string[];
7
+ messages: string[];
8
+ };
9
+ export declare function generateDefaultConfig(overrides?: Partial<CapsuleConfig>): CapsuleConfig;
@@ -0,0 +1,254 @@
1
+ "use strict";
2
+ // ═══════════════════════════════════════════════════════════
3
+ // Guard Capsule — Scheduler
4
+ //
5
+ // Generates CI/CD workflow files and cron configurations.
6
+ // Supports: GitHub Actions, GitLab CI, Bitbucket Pipelines,
7
+ // local cron (node-cron), and webhook triggers.
8
+ // ═══════════════════════════════════════════════════════════
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.generateGitHubActions = generateGitHubActions;
11
+ exports.generateGitLabCI = generateGitLabCI;
12
+ exports.generateBitbucket = generateBitbucket;
13
+ exports.installSchedule = installSchedule;
14
+ exports.generateDefaultConfig = generateDefaultConfig;
15
+ const fs_1 = require("fs");
16
+ const path_1 = require("path");
17
+ const PRESETS = {
18
+ daily: "0 3 * * *", // Every day at 3am
19
+ weekly: "0 3 * * 1", // Every Monday at 3am
20
+ monthly: "0 3 1 * *", // First of month at 3am
21
+ };
22
+ // ── GitHub Actions Workflow ──
23
+ function generateGitHubActions(config) {
24
+ const cron = config.schedule?.cron || PRESETS[config.schedule?.preset || "weekly"];
25
+ const failOn = config.scan?.failOn || "critical";
26
+ const hasDb = !!config.scan?.dbUrl;
27
+ const content = `# NoData Guard — Automated Security Scan
28
+ # Generated by @nodatachat/guard
29
+ # Runs: ${config.schedule?.preset || "weekly"} (${cron})
30
+
31
+ name: NoData Guard Security Scan
32
+
33
+ on:
34
+ schedule:
35
+ - cron: '${cron}'
36
+ push:
37
+ branches: [main, master]
38
+ pull_request:
39
+ branches: [main, master]
40
+ workflow_dispatch: # Manual trigger
41
+
42
+ jobs:
43
+ security-scan:
44
+ name: Guard Security Scan
45
+ runs-on: ubuntu-latest
46
+ timeout-minutes: 10
47
+
48
+ steps:
49
+ - uses: actions/checkout@v4
50
+
51
+ - uses: actions/setup-node@v4
52
+ with:
53
+ node-version: '20'
54
+
55
+ - name: Install Guard
56
+ run: npm install -g @nodatachat/guard
57
+
58
+ - name: Run Security Scan
59
+ run: |
60
+ nodata-guard \\
61
+ --license-key \${{ secrets.NDC_LICENSE }} \\${hasDb ? `\n --db \${{ secrets.DATABASE_URL }} \\` : ""}
62
+ --ci \\
63
+ --fail-on ${failOn} \\
64
+ --output ./guard-reports
65
+ env:
66
+ NDC_LICENSE: \${{ secrets.NDC_LICENSE }}${hasDb ? "\n DATABASE_URL: ${{ secrets.DATABASE_URL }}" : ""}
67
+
68
+ - name: Upload Report
69
+ if: always()
70
+ uses: actions/upload-artifact@v4
71
+ with:
72
+ name: guard-report-\${{ github.sha }}
73
+ path: ./guard-reports/
74
+ retention-days: 90
75
+
76
+ - name: Comment PR with Score
77
+ if: github.event_name == 'pull_request'
78
+ uses: actions/github-script@v7
79
+ with:
80
+ script: |
81
+ const fs = require('fs');
82
+ const reports = fs.readdirSync('./guard-reports').filter(f => f.endsWith('.json') && f.includes('metadata'));
83
+ if (reports.length === 0) return;
84
+ const meta = JSON.parse(fs.readFileSync('./guard-reports/' + reports[0], 'utf-8'));
85
+ const score = meta.scores?.overall || '?';
86
+ const critical = meta.issues?.critical || 0;
87
+ const high = meta.issues?.high || 0;
88
+ const emoji = score >= 80 ? ':white_check_mark:' : score >= 60 ? ':warning:' : ':x:';
89
+ github.rest.issues.createComment({
90
+ issue_number: context.issue.number,
91
+ owner: context.repo.owner,
92
+ repo: context.repo.repo,
93
+ body: \`## \${emoji} NoData Guard — Score: \${score}%\\n\\n| Critical | High | Coverage |\\n|---|---|---|\\n| \${critical} | \${high} | \${meta.code_summary?.encryption_coverage_percent || '?'}% |\\n\\n> Your code never left the runner. [View full report in artifacts.]\`
94
+ });
95
+ `;
96
+ return {
97
+ provider: "github-actions",
98
+ filename: ".github/workflows/nodata-guard.yml",
99
+ content,
100
+ };
101
+ }
102
+ // ── GitLab CI ──
103
+ function generateGitLabCI(config) {
104
+ const cron = config.schedule?.cron || PRESETS[config.schedule?.preset || "weekly"];
105
+ const failOn = config.scan?.failOn || "critical";
106
+ const hasDb = !!config.scan?.dbUrl;
107
+ const content = `# NoData Guard — Automated Security Scan
108
+ # Generated by @nodatachat/guard
109
+
110
+ stages:
111
+ - security
112
+
113
+ nodata-guard:
114
+ stage: security
115
+ image: node:20
116
+ rules:
117
+ - if: $CI_PIPELINE_SOURCE == "schedule"
118
+ - if: $CI_PIPELINE_SOURCE == "merge_request_event"
119
+ - if: $CI_COMMIT_BRANCH == "main"
120
+ script:
121
+ - npm install -g @nodatachat/guard
122
+ - nodata-guard --license-key $NDC_LICENSE${hasDb ? " --db $DATABASE_URL" : ""} --ci --fail-on ${failOn} --output ./guard-reports
123
+ artifacts:
124
+ paths:
125
+ - guard-reports/
126
+ expire_in: 90 days
127
+ when: always
128
+ variables:
129
+ NDC_LICENSE: $NDC_LICENSE${hasDb ? "\n DATABASE_URL: $DATABASE_URL" : ""}
130
+
131
+ # Add to CI/CD > Schedules: cron "${cron}"
132
+ `;
133
+ return {
134
+ provider: "gitlab-ci",
135
+ filename: ".gitlab-ci-guard.yml",
136
+ content,
137
+ };
138
+ }
139
+ // ── Bitbucket Pipelines ──
140
+ function generateBitbucket(config) {
141
+ const failOn = config.scan?.failOn || "critical";
142
+ const hasDb = !!config.scan?.dbUrl;
143
+ const content = `# NoData Guard — Automated Security Scan
144
+ # Generated by @nodatachat/guard
145
+
146
+ pipelines:
147
+ default:
148
+ - step:
149
+ name: NoData Guard Security Scan
150
+ image: node:20
151
+ script:
152
+ - npm install -g @nodatachat/guard
153
+ - nodata-guard --license-key $NDC_LICENSE${hasDb ? " --db $DATABASE_URL" : ""} --ci --fail-on ${failOn}
154
+ artifacts:
155
+ - nodata-full-report.json
156
+ - nodata-metadata-only.json
157
+
158
+ custom:
159
+ nodata-guard:
160
+ - step:
161
+ name: Manual Security Scan
162
+ image: node:20
163
+ script:
164
+ - npm install -g @nodatachat/guard
165
+ - nodata-guard --license-key $NDC_LICENSE${hasDb ? " --db $DATABASE_URL" : ""} --ci --fail-on ${failOn}
166
+ `;
167
+ return {
168
+ provider: "bitbucket",
169
+ filename: "bitbucket-pipelines-guard.yml",
170
+ content,
171
+ };
172
+ }
173
+ // ── Write CI config to project ──
174
+ function installSchedule(projectDir, config, provider) {
175
+ const files = [];
176
+ const messages = [];
177
+ // Auto-detect provider
178
+ let detected = provider;
179
+ if (!detected || detected === "auto") {
180
+ if ((0, fs_1.existsSync)((0, path_1.join)(projectDir, ".github")))
181
+ detected = "github-actions";
182
+ else if ((0, fs_1.existsSync)((0, path_1.join)(projectDir, ".gitlab-ci.yml")))
183
+ detected = "gitlab-ci";
184
+ else if ((0, fs_1.existsSync)((0, path_1.join)(projectDir, "bitbucket-pipelines.yml")))
185
+ detected = "bitbucket";
186
+ else
187
+ detected = "github-actions"; // Default
188
+ }
189
+ let template;
190
+ switch (detected) {
191
+ case "github-actions":
192
+ template = generateGitHubActions(config);
193
+ break;
194
+ case "gitlab-ci":
195
+ template = generateGitLabCI(config);
196
+ break;
197
+ case "bitbucket":
198
+ template = generateBitbucket(config);
199
+ break;
200
+ default:
201
+ template = generateGitHubActions(config);
202
+ }
203
+ // Write the file
204
+ const filePath = (0, path_1.join)(projectDir, template.filename);
205
+ const dir = (0, path_1.join)(projectDir, ...template.filename.split("/").slice(0, -1));
206
+ if (!(0, fs_1.existsSync)(dir))
207
+ (0, fs_1.mkdirSync)(dir, { recursive: true });
208
+ (0, fs_1.writeFileSync)(filePath, template.content, "utf-8");
209
+ files.push(filePath);
210
+ messages.push(`Created ${template.filename}`);
211
+ // Write .nodata-guard.json config
212
+ const configPath = (0, path_1.join)(projectDir, ".nodata-guard.json");
213
+ (0, fs_1.writeFileSync)(configPath, JSON.stringify(config, null, 2), "utf-8");
214
+ files.push(configPath);
215
+ messages.push("Created .nodata-guard.json");
216
+ // Remind about secrets
217
+ messages.push("");
218
+ messages.push("Required secrets:");
219
+ messages.push(" NDC_LICENSE — Your NoData Guard license key");
220
+ if (config.scan?.dbUrl) {
221
+ messages.push(" DATABASE_URL — Database connection string");
222
+ }
223
+ messages.push("");
224
+ messages.push(`Schedule: ${config.schedule?.preset || "weekly"} (${config.schedule?.cron || PRESETS[config.schedule?.preset || "weekly"]})`);
225
+ return { files, messages };
226
+ }
227
+ // ── Generate default config ──
228
+ function generateDefaultConfig(overrides) {
229
+ return {
230
+ version: "1.0",
231
+ scan: {
232
+ failOn: "critical",
233
+ ...overrides?.scan,
234
+ },
235
+ schedule: {
236
+ enabled: true,
237
+ preset: "weekly",
238
+ cron: PRESETS.weekly,
239
+ timezone: "UTC",
240
+ ...overrides?.schedule,
241
+ },
242
+ notify: {
243
+ onEvents: ["scan-complete", "score-drop", "critical-found", "fix-failed"],
244
+ ...overrides?.notify,
245
+ },
246
+ fix: {
247
+ autoApply: false,
248
+ dryRunFirst: true,
249
+ requireApproval: ["pii-encrypt", "rls", "secrets"],
250
+ ...overrides?.fix,
251
+ },
252
+ ...overrides,
253
+ };
254
+ }
@@ -0,0 +1,160 @@
1
+ export type FixerCategory = "pii-encrypt" | "rls" | "secrets" | "routes-auth" | "headers" | "csrf" | "rate-limit" | "gitignore";
2
+ export type FixActionType = "sql-migration" | "file-patch" | "file-create" | "file-append" | "env-add" | "config-update" | "command" | "manual";
3
+ export type FixSeverity = "critical" | "high" | "medium" | "low";
4
+ export type FixStatus = "planned" | "previewed" | "applying" | "applied" | "verified" | "failed" | "skipped" | "manual-required";
5
+ export interface FixAction {
6
+ id: string;
7
+ type: FixActionType;
8
+ description: string;
9
+ descriptionHe: string;
10
+ severity: FixSeverity;
11
+ target: string;
12
+ detail: string;
13
+ content: string;
14
+ rollback?: string;
15
+ status: FixStatus;
16
+ appliedAt?: string;
17
+ error?: string;
18
+ beforeHash?: string;
19
+ afterHash?: string;
20
+ dependsOn?: string[];
21
+ blockedBy?: string[];
22
+ }
23
+ export interface FixPlan {
24
+ fixer: FixerCategory;
25
+ name: string;
26
+ nameHe: string;
27
+ description: string;
28
+ descriptionHe: string;
29
+ actions: FixAction[];
30
+ totalActions: number;
31
+ autoFixable: number;
32
+ manualRequired: number;
33
+ estimatedScoreImpact: number;
34
+ affectedControls: string[];
35
+ prerequisites: string[];
36
+ }
37
+ export interface FixerResult {
38
+ fixer: FixerCategory;
39
+ plan: FixPlan;
40
+ startedAt: string;
41
+ completedAt: string;
42
+ durationMs: number;
43
+ applied: number;
44
+ failed: number;
45
+ skipped: number;
46
+ manualPending: number;
47
+ proofHash: string;
48
+ proofFile?: string;
49
+ scoreBefore: number;
50
+ scoreAfter: number;
51
+ }
52
+ export interface CapsuleConfig {
53
+ version: "1.0";
54
+ licenseKey?: string;
55
+ projectDir?: string;
56
+ scan: {
57
+ dbUrl?: string;
58
+ skipDirs?: string[];
59
+ failOn?: "critical" | "high" | "medium";
60
+ };
61
+ schedule?: {
62
+ enabled: boolean;
63
+ cron?: string;
64
+ preset?: "daily" | "weekly" | "monthly";
65
+ timezone?: string;
66
+ };
67
+ notify?: {
68
+ email?: string[];
69
+ webhook?: string[];
70
+ slack?: {
71
+ webhookUrl: string;
72
+ channel?: string;
73
+ mentionOn?: "critical" | "drop";
74
+ };
75
+ telegram?: {
76
+ botToken: string;
77
+ chatId: string;
78
+ };
79
+ onEvents: NotifyEvent[];
80
+ };
81
+ fix?: {
82
+ autoApply?: boolean;
83
+ dryRunFirst?: boolean;
84
+ skipFixers?: FixerCategory[];
85
+ requireApproval?: FixerCategory[];
86
+ };
87
+ }
88
+ export type NotifyEvent = "scan-complete" | "score-drop" | "score-improve" | "critical-found" | "fix-applied" | "fix-failed" | "schedule-missed";
89
+ export interface NotifyPayload {
90
+ event: NotifyEvent;
91
+ timestamp: string;
92
+ projectName: string;
93
+ projectHash: string;
94
+ score: number;
95
+ previousScore: number | null;
96
+ delta: number;
97
+ critical: number;
98
+ high: number;
99
+ medium: number;
100
+ fixesApplied?: number;
101
+ fixesFailed?: number;
102
+ dashboardUrl?: string;
103
+ scanId: string;
104
+ proofHash: string;
105
+ }
106
+ export interface Fixer {
107
+ category: FixerCategory;
108
+ name: string;
109
+ nameHe: string;
110
+ /** Analyze scan results and generate a fix plan */
111
+ analyze(context: FixerContext): Promise<FixPlan>;
112
+ /** Apply the fix plan (or subset of actions) */
113
+ apply(plan: FixPlan, actionIds?: string[]): Promise<FixerResult>;
114
+ /** Verify fixes were applied correctly (re-scan specific controls) */
115
+ verify(result: FixerResult): Promise<boolean>;
116
+ }
117
+ export interface FixerContext {
118
+ projectDir: string;
119
+ dbUrl?: string;
120
+ config: CapsuleConfig;
121
+ scanResults: {
122
+ piiFields: Array<{
123
+ table: string;
124
+ column: string;
125
+ pii_type: string;
126
+ encrypted: boolean;
127
+ encryption_pattern: string | null;
128
+ }>;
129
+ routes: Array<{
130
+ path: string;
131
+ has_auth: boolean;
132
+ auth_type: string | null;
133
+ }>;
134
+ secrets: Array<{
135
+ file: string;
136
+ line: number;
137
+ type: string;
138
+ severity: string;
139
+ is_env_interpolated: boolean;
140
+ }>;
141
+ rls: Array<{
142
+ table: string;
143
+ rls_enabled: boolean;
144
+ policy_count: number;
145
+ }>;
146
+ framework: string;
147
+ database: string;
148
+ };
149
+ stack: {
150
+ framework: string;
151
+ database: string;
152
+ language: string;
153
+ hosting: string;
154
+ };
155
+ }
156
+ export interface CITemplate {
157
+ provider: "github-actions" | "gitlab-ci" | "bitbucket";
158
+ filename: string;
159
+ content: string;
160
+ }
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ // ═══════════════════════════════════════════════════════════
3
+ // Guard Capsule — Fixer Types
4
+ //
5
+ // Every fixer follows the same pattern:
6
+ // analyze → plan → preview → apply → verify
7
+ //
8
+ // Fixers NEVER send data externally.
9
+ // Fixers ALWAYS produce a proof of what changed.
10
+ // ═══════════════════════════════════════════════════════════
11
+ Object.defineProperty(exports, "__esModule", { value: true });