cerber-core 1.1.6 → 1.1.8

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 (40) hide show
  1. package/CHANGELOG.md +111 -0
  2. package/README.md +157 -1
  3. package/bin/cerber +14 -1
  4. package/dev/templates/BACKEND_SCHEMA.ts.tpl +87 -50
  5. package/dev/templates/cerber-guardian.mjs.tpl +255 -148
  6. package/dev/templates/cerber.yml.tpl +96 -53
  7. package/dev/templates/pre-commit.tpl +4 -4
  8. package/dist/cli/contract-parser.d.ts.map +1 -1
  9. package/dist/cli/contract-parser.js +47 -20
  10. package/dist/cli/contract-parser.js.map +1 -1
  11. package/dist/cli/doctor.d.ts +16 -0
  12. package/dist/cli/doctor.d.ts.map +1 -0
  13. package/dist/cli/doctor.js +140 -0
  14. package/dist/cli/doctor.js.map +1 -0
  15. package/dist/cli/init.d.ts.map +1 -1
  16. package/dist/cli/init.js +98 -8
  17. package/dist/cli/init.js.map +1 -1
  18. package/dist/cli/override-validator.d.ts +27 -0
  19. package/dist/cli/override-validator.d.ts.map +1 -0
  20. package/dist/cli/override-validator.js +100 -0
  21. package/dist/cli/override-validator.js.map +1 -0
  22. package/dist/cli/template-generator.d.ts.map +1 -1
  23. package/dist/cli/template-generator.js +71 -16
  24. package/dist/cli/template-generator.js.map +1 -1
  25. package/dist/cli/types.d.ts +7 -0
  26. package/dist/cli/types.d.ts.map +1 -1
  27. package/dist/index.d.ts +3 -3
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +3 -3
  30. package/dist/index.js.map +1 -1
  31. package/package.json +117 -117
  32. package/solo/templates/BACKEND_SCHEMA.ts.tpl +87 -50
  33. package/solo/templates/cerber-guardian.mjs.tpl +255 -148
  34. package/solo/templates/cerber.yml.tpl +96 -53
  35. package/solo/templates/pre-commit.tpl +4 -4
  36. package/team/templates/BACKEND_SCHEMA.ts.tpl +87 -50
  37. package/team/templates/CODEOWNERS.tpl +18 -6
  38. package/team/templates/cerber-guardian.mjs.tpl +255 -148
  39. package/team/templates/cerber.yml.tpl +96 -53
  40. package/team/templates/pre-commit.tpl +4 -4
@@ -1,50 +1,87 @@
1
- /**
2
- * ⚠️ CERBER TEMPLATE (NOT SOURCE OF TRUTH)
3
- *
4
- * This file is a starting point. Edit it to match YOUR architecture.
5
- * The source of truth is CERBER.md.
6
- *
7
- * Generated by: npx cerber init
8
- * Project: {{PROJECT_NAME}}
9
- *
10
- * See: https://github.com/Agaslez/cerber-core#guardian-configuration
11
- */
12
-
13
- export const BACKEND_SCHEMA = {
14
- version: "1.0.0",
15
-
16
- // Your architecture rules (customize)
17
- rules: [],
18
-
19
- // Patterns that should never appear in your code
20
- forbiddenPatterns: [
21
- // Uncomment and customize based on your tech stack:
22
- // {
23
- // pattern: "password\\s*=\\s*['\"][^'\"]+['\"]",
24
- // flags: "i",
25
- // name: "Hardcoded passwords",
26
- // severity: "error"
27
- // },
28
- // {
29
- // pattern: "api[_-]?key\\s*=\\s*['\"][^'\"]+['\"]",
30
- // flags: "i",
31
- // name: "Hardcoded API keys",
32
- // severity: "error"
33
- // }
34
- ]
35
- };
36
-
37
- export default BACKEND_SCHEMA;
38
-
39
- /**
40
- * 💡 Next Steps:
41
- *
42
- * 1. Define your project structure in CERBER.md (single source of truth)
43
- * 2. Add forbiddenPatterns for your tech stack
44
- * 3. Add requiredImports rules for your architecture layers
45
- * 4. Test with: npx cerber-guardian
46
- *
47
- * Full examples:
48
- * - node_modules/cerber-core/examples/backend-schema.ts
49
- * - node_modules/cerber-core/examples/frontend-schema.ts
50
- */
1
+ /**
2
+ * ⚠️ CERBER TEMPLATE (NOT SOURCE OF TRUTH)
3
+ *
4
+ * This file is a starting point. Edit it to match YOUR architecture.
5
+ * The source of truth is CERBER.md.
6
+ *
7
+ * Generated by: npx cerber init
8
+ * Project: {{PROJECT_NAME}}
9
+ *
10
+ * See: https://github.com/Agaslez/cerber-core#guardian-configuration
11
+ */
12
+
13
+ export const BACKEND_SCHEMA = {
14
+ version: "1.0.0",
15
+
16
+ // Your architecture rules (customize)
17
+ rules: [],
18
+
19
+ // Patterns that should never appear in your code
20
+ forbiddenPatterns: [
21
+ // ──────────────────────────────────────────────────────────────────────
22
+ // 🔐 SECURITY BASELINE (Uncomment and adapt to your CERBER.md contract)
23
+ // ──────────────────────────────────────────────────────────────────────
24
+
25
+ // Hardcoded secrets (common examples - adapt to your stack):
26
+ // {
27
+ // pattern: "password\\s*=\\s*['\"][^'\"]+['\"]" ,
28
+ // flags: "i",
29
+ // name: "Hardcoded passwords (e.g., password='admin123')",
30
+ // severity: "error"
31
+ // },
32
+ // {
33
+ // pattern: "api[_-]?key\\s*=\\s*['\"][^'\"]+['\"]" ,
34
+ // flags: "i",
35
+ // name: "Hardcoded API keys (e.g., API_KEY='xyz')",
36
+ // severity: "error"
37
+ // },
38
+ // {
39
+ // pattern: "JWT_SECRET\\s*=\\s*['\"][^'\"]+['\"]" ,
40
+ // flags: "i",
41
+ // name: "Hardcoded JWT secrets",
42
+ // severity: "error"
43
+ // },
44
+
45
+ // Development artifacts (should not reach production):
46
+ // {
47
+ // pattern: "console\\.log\\(",
48
+ // flags: "g",
49
+ // name: "console.log (use proper logging)",
50
+ // severity: "warning"
51
+ // },
52
+ // {
53
+ // pattern: "debugger;",
54
+ // flags: "g",
55
+ // name: "debugger statement",
56
+ // severity: "warning"
57
+ // },
58
+
59
+ // TODOs that should be resolved before commit:
60
+ // {
61
+ // pattern: "TODO_REMOVE|FIXME_REMOVE",
62
+ // flags: "i",
63
+ // name: "Unresolved critical TODOs",
64
+ // severity: "error"
65
+ // },
66
+
67
+ // ──────────────────────────────────────────────────────────────────────
68
+ // ⚠️ IMPORTANT: Uncomment patterns that match rules in your CERBER.md
69
+ // Never invent rules. Only translate what's documented in your contract.
70
+ // ──────────────────────────────────────────────────────────────────────
71
+ ]
72
+ };
73
+
74
+ export default BACKEND_SCHEMA;
75
+
76
+ /**
77
+ * 💡 Next Steps:
78
+ *
79
+ * 1. Define your project structure in CERBER.md (single source of truth)
80
+ * 2. Add forbiddenPatterns for your tech stack
81
+ * 3. Add requiredImports rules for your architecture layers
82
+ * 4. Test with: npx cerber-guardian
83
+ *
84
+ * Full examples:
85
+ * - node_modules/cerber-core/examples/backend-schema.ts
86
+ * - node_modules/cerber-core/examples/frontend-schema.ts
87
+ */
@@ -1,6 +1,18 @@
1
- # Generated by Cerber init
2
-
3
- /CERBER.md {{OWNERS}}
4
- /{{SCHEMA_FILE}} {{OWNERS}}
5
- /.github/workflows/cerber.yml {{OWNERS}}
6
- /scripts/cerber-guardian.mjs {{OWNERS}}
1
+ # Generated by Cerber init
2
+ # Protected Cerber assets - require owner approval for changes
3
+
4
+ # Core contract and schema
5
+ /CERBER.md {{OWNERS}}
6
+ /{{SCHEMA_FILE}} {{OWNERS}}
7
+ /BACKEND_SCHEMA.* {{OWNERS}}
8
+
9
+ # CI/CD enforcement
10
+ /.github/workflows/cerber.yml {{OWNERS}}
11
+
12
+ # Pre-commit guardian
13
+ /scripts/cerber-guardian.mjs {{OWNERS}}
14
+ /.husky/pre-commit {{OWNERS}}
15
+
16
+ # Ownership meta-file (self-protection)
17
+ /.github/CODEOWNERS {{OWNERS}}
18
+
@@ -1,148 +1,255 @@
1
- #!/usr/bin/env node
2
- // Generated by Cerber init - DO NOT EDIT MANUALLY
3
- // To regenerate: npx cerber init --force
4
-
5
- import { execSync } from 'child_process';
6
- import fs from 'fs';
7
- import { join } from 'path';
8
-
9
- const SCHEMA_FILE = '{{SCHEMA_FILE}}';
10
- const APPROVALS_TAG = '{{APPROVALS_TAG}}';
11
-
12
- async function main() {
13
- console.log('🛡️ Cerber Guardian: Validating staged files...');
14
-
15
- if (!fs.existsSync(SCHEMA_FILE)) {
16
- console.error(`❌ Schema file not found: ${SCHEMA_FILE}`);
17
- console.error('Create your schema file to enable validation.');
18
- console.error(`Example: npx cerber init --print-schema-template > ${SCHEMA_FILE}`);
19
- process.exit(1);
20
- }
21
-
22
- // Import schema
23
- let schema;
24
- try {
25
- const schemaPath = join(process.cwd(), SCHEMA_FILE);
26
- const schemaModule = await import(`file://${schemaPath}`);
27
- schema = schemaModule.BACKEND_SCHEMA || schemaModule.default || schemaModule;
28
- } catch (err) {
29
- console.error(`❌ Failed to load schema from ${SCHEMA_FILE}:`, err.message);
30
- process.exit(1);
31
- }
32
-
33
- // Get staged files
34
- let stagedFiles;
35
- try {
36
- stagedFiles = execSync('git diff --cached --name-only', { encoding: 'utf-8' })
37
- .trim()
38
- .split('\n')
39
- .filter(Boolean);
40
- } catch (err) {
41
- console.error('❌ Failed to get staged files');
42
- process.exit(1);
43
- }
44
-
45
- if (stagedFiles.length === 0) {
46
- console.log('✅ No files staged for commit');
47
- return;
48
- }
49
-
50
- console.log(`Checking ${stagedFiles.length} file(s)...`);
51
-
52
- const violations = [];
53
-
54
- // Validate each staged file
55
- for (const file of stagedFiles) {
56
- if (!fs.existsSync(file)) continue;
57
-
58
- const content = fs.readFileSync(file, 'utf-8');
59
-
60
- // Check forbidden patterns
61
- if (schema.forbiddenPatterns && Array.isArray(schema.forbiddenPatterns)) {
62
- for (const rule of schema.forbiddenPatterns) {
63
- if (!rule.pattern) continue;
64
-
65
- const pattern = typeof rule.pattern === 'string'
66
- ? new RegExp(rule.pattern, rule.flags || 'i')
67
- : rule.pattern;
68
-
69
- // Check if file is in exceptions
70
- if (rule.exceptions && rule.exceptions.some(ex => file.includes(ex))) {
71
- continue;
72
- }
73
-
74
- if (pattern.test(content)) {
75
- // Check for architect approval
76
- const hasApproval = content.includes(APPROVALS_TAG);
77
-
78
- if (!hasApproval) {
79
- violations.push({
80
- file,
81
- rule: rule.name || 'Unnamed rule',
82
- severity: rule.severity || 'error'
83
- });
84
- }
85
- }
86
- }
87
- }
88
-
89
- // Check required imports (rules)
90
- if (schema.rules && Array.isArray(schema.rules)) {
91
- for (const rule of schema.rules) {
92
- if (!rule.pattern || !rule.requiredImports) continue;
93
-
94
- const filePattern = typeof rule.pattern === 'string' ? new RegExp(rule.pattern) : rule.pattern;
95
-
96
- if (!filePattern.test(file)) continue;
97
-
98
- // Check if file is in exceptions
99
- if (rule.exceptions && rule.exceptions.some(ex => file.includes(ex))) {
100
- continue;
101
- }
102
-
103
- // Check each required import
104
- for (const requiredImport of rule.requiredImports) {
105
- const importPattern = new RegExp(`import.*${requiredImport}`, 'i');
106
-
107
- if (!importPattern.test(content)) {
108
- // Check for architect approval
109
- const hasApproval = content.includes(APPROVALS_TAG);
110
-
111
- if (!hasApproval) {
112
- violations.push({
113
- file,
114
- rule: `${rule.name || 'Unnamed rule'}: missing import '${requiredImport}'`,
115
- severity: rule.severity || 'error'
116
- });
117
- }
118
- }
119
- }
120
- }
121
- }
122
- }
123
-
124
- // Report violations
125
- if (violations.length > 0) {
126
- console.error('\n❌ Architecture violations detected:\n');
127
-
128
- for (const v of violations) {
129
- const icon = v.severity === 'error' ? '🔴' : '⚠️';
130
- console.error(`${icon} [${v.severity.toUpperCase()}] ${v.file}`);
131
- console.error(` ${v.rule}`);
132
- console.error(` Add ${APPROVALS_TAG} comment to override\n`);
133
- }
134
-
135
- const errorCount = violations.filter(v => v.severity === 'error').length;
136
- if (errorCount > 0) {
137
- console.error(`\n❌ Commit blocked: ${errorCount} error(s) found`);
138
- process.exit(1);
139
- }
140
- }
141
-
142
- console.log('✅ All checks passed');
143
- }
144
-
145
- main().catch(err => {
146
- console.error('❌ Guardian check failed:', err?.message || err);
147
- process.exit(1);
148
- });
1
+ #!/usr/bin/env node
2
+ // Generated by Cerber init - DO NOT EDIT MANUALLY
3
+ // To regenerate: npx cerber init --force
4
+
5
+ import { execSync } from 'child_process';
6
+ import fs from 'fs';
7
+ import { join } from 'path';
8
+
9
+ const APPROVALS_TAG = '{{APPROVALS_TAG}}';
10
+
11
+ function readYamlValue(text, key) {
12
+ const re = new RegExp(`^\\s*${key}\\s*:\\s*(.*)\\s*$`, "m");
13
+ const m = text.match(re);
14
+ if (!m) return "";
15
+ let v = (m[1] ?? "").trim();
16
+
17
+ // Remove quotes
18
+ v = v.replace(/^["']/,"").replace(/["']$/,"");
19
+ return v.trim();
20
+ }
21
+
22
+ function parseOverride(cerberContent) {
23
+ const enabledRaw = readYamlValue(cerberContent, "enabled");
24
+ const enabled = enabledRaw.toLowerCase() === "true";
25
+
26
+ const reason = readYamlValue(cerberContent, "reason");
27
+ const expiresRaw = readYamlValue(cerberContent, "expires");
28
+ const approvedBy = readYamlValue(cerberContent, "approvedBy");
29
+
30
+ if (!enabled) return { state: "DISABLED", reason, expires: expiresRaw, approvedBy };
31
+
32
+ if (!expiresRaw) return { state: "ACTIVE", reason, expires: "", approvedBy };
33
+
34
+ const expiresDate = new Date(expiresRaw);
35
+ if (Number.isNaN(expiresDate.getTime())) {
36
+ return { state: "INVALID", reason, expires: expiresRaw, approvedBy };
37
+ }
38
+
39
+ if (expiresDate.getTime() < Date.now()) {
40
+ return { state: "EXPIRED", reason, expires: expiresRaw, approvedBy };
41
+ }
42
+
43
+ return { state: "ACTIVE", reason, expires: expiresRaw, approvedBy };
44
+ }
45
+
46
+ async function main() {
47
+ console.log('🛡️ Cerber Guardian: Validating staged files...');
48
+
49
+ // Check for emergency override
50
+ const cerberMdPath = join(process.cwd(), 'CERBER.md');
51
+ if (fs.existsSync(cerberMdPath)) {
52
+ const cerberContent = fs.readFileSync(cerberMdPath, 'utf-8');
53
+ const overrideMatch = cerberContent.match(/CERBER_OVERRIDE:\s*\n\s*enabled:\s*(true|false)/);
54
+
55
+ if (overrideMatch && overrideMatch[1] === 'true') {
56
+ const override = parseOverride(cerberContent);
57
+ const { state, reason, expires, approvedBy } = override;
58
+
59
+ if (state === 'ACTIVE') {
60
+ // Override ACTIVE - allow commit with warning
61
+ console.log('');
62
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
63
+ console.log('⚠️ CERBER EMERGENCY OVERRIDE ACTIVE');
64
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
65
+ console.log('');
66
+ console.log(`Status: ACTIVE`);
67
+ console.log(`Reason: ${reason}`);
68
+ console.log(`Expires: ${expires}`);
69
+ console.log(`Approved By: ${approvedBy}`);
70
+ console.log('');
71
+ console.log('Guardian checks: BYPASSED WITH WARNING');
72
+ console.log('Self-protection: STILL ACTIVE (cerber-integrity runs)');
73
+ console.log('');
74
+ console.log('⚠️ Create follow-up PR to fix properly + disable override');
75
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
76
+ console.log('');
77
+ process.exit(0); // Allow commit
78
+ } else if (state === 'EXPIRED') {
79
+ console.log('⚠️ Override expired - proceeding with normal validation');
80
+ } else {
81
+ console.log('⚠️ Override invalid (missing required fields) - proceeding with normal validation');
82
+ }
83
+ }
84
+ }
85
+
86
+ // Load schema from CERBER.md
87
+ if (!fs.existsSync(cerberMdPath)) {
88
+ console.error('❌ CERBER.md not found');
89
+ console.error('Run: npx cerber init');
90
+ process.exit(5);
91
+ }
92
+
93
+ const cerberContent = fs.readFileSync(cerberMdPath, 'utf-8');
94
+
95
+ // Parse SCHEMA section
96
+ const schemaMatch = cerberContent.match(/SCHEMA:\s*\n\s*mode:\s*(\w+)/);
97
+ if (!schemaMatch) {
98
+ console.error('❌ SCHEMA section not found in CERBER.md');
99
+ console.error('Add SCHEMA section with mode: required or mode: disabled');
100
+ process.exit(5);
101
+ }
102
+
103
+ const schemaMode = schemaMatch[1];
104
+ if (schemaMode === 'disabled') {
105
+ console.log('⚠️ Schema validation disabled (SCHEMA.mode: disabled)');
106
+ console.log('✅ Bypassing validation');
107
+ return;
108
+ }
109
+
110
+ // Extract forbidden patterns
111
+ const forbiddenPatterns = [];
112
+ const patternsMatch = cerberContent.match(/forbiddenPatterns:\s*\n([\s\S]*?)(?=\n\w|\nSCHEMA:|$)/);
113
+ if (patternsMatch) {
114
+ const patternLines = patternsMatch[1].match(/- "([^"]+)"/g);
115
+ if (patternLines) {
116
+ patternLines.forEach(line => {
117
+ const pattern = line.match(/- "([^"]+)"/)?.[1];
118
+ if (pattern) {
119
+ forbiddenPatterns.push({
120
+ pattern: pattern,
121
+ name: `Forbidden: ${pattern}`,
122
+ severity: 'error'
123
+ });
124
+ }
125
+ });
126
+ }
127
+ }
128
+
129
+ if (schemaMode === 'required' && forbiddenPatterns.length === 0) {
130
+ console.error('❌ SCHEMA.mode: required but no rules defined');
131
+ console.error('Add forbiddenPatterns to SCHEMA section in CERBER.md');
132
+ process.exit(5);
133
+ }
134
+
135
+ const schema = {
136
+ forbiddenPatterns: forbiddenPatterns,
137
+ rules: []
138
+ };
139
+
140
+ // Get staged files
141
+ let stagedFiles;
142
+ try {
143
+ stagedFiles = execSync('git diff --cached --name-only', { encoding: 'utf-8' })
144
+ .trim()
145
+ .split('\n')
146
+ .filter(Boolean);
147
+ } catch (err) {
148
+ console.error('❌ Failed to get staged files');
149
+ process.exit(1);
150
+ }
151
+
152
+ if (stagedFiles.length === 0) {
153
+ console.log('✅ No files staged for commit');
154
+ return;
155
+ }
156
+
157
+ console.log(`Checking ${stagedFiles.length} file(s)...`);
158
+
159
+ const violations = [];
160
+
161
+ // Validate each staged file
162
+ for (const file of stagedFiles) {
163
+ if (!fs.existsSync(file)) continue;
164
+
165
+ const content = fs.readFileSync(file, 'utf-8');
166
+
167
+ // Check forbidden patterns
168
+ if (schema.forbiddenPatterns && Array.isArray(schema.forbiddenPatterns)) {
169
+ for (const rule of schema.forbiddenPatterns) {
170
+ if (!rule.pattern) continue;
171
+
172
+ const pattern = typeof rule.pattern === 'string'
173
+ ? new RegExp(rule.pattern, rule.flags || 'i')
174
+ : rule.pattern;
175
+
176
+ // Check if file is in exceptions
177
+ if (rule.exceptions && rule.exceptions.some(ex => file.includes(ex))) {
178
+ continue;
179
+ }
180
+
181
+ if (pattern.test(content)) {
182
+ // Check for architect approval
183
+ const hasApproval = content.includes(APPROVALS_TAG);
184
+
185
+ if (!hasApproval) {
186
+ violations.push({
187
+ file,
188
+ rule: rule.name || 'Unnamed rule',
189
+ severity: rule.severity || 'error'
190
+ });
191
+ }
192
+ }
193
+ }
194
+ }
195
+
196
+ // Check required imports (rules)
197
+ if (schema.rules && Array.isArray(schema.rules)) {
198
+ for (const rule of schema.rules) {
199
+ if (!rule.pattern || !rule.requiredImports) continue;
200
+
201
+ const filePattern = typeof rule.pattern === 'string' ? new RegExp(rule.pattern) : rule.pattern;
202
+
203
+ if (!filePattern.test(file)) continue;
204
+
205
+ // Check if file is in exceptions
206
+ if (rule.exceptions && rule.exceptions.some(ex => file.includes(ex))) {
207
+ continue;
208
+ }
209
+
210
+ // Check each required import
211
+ for (const requiredImport of rule.requiredImports) {
212
+ const importPattern = new RegExp(`import.*${requiredImport}`, 'i');
213
+
214
+ if (!importPattern.test(content)) {
215
+ // Check for architect approval
216
+ const hasApproval = content.includes(APPROVALS_TAG);
217
+
218
+ if (!hasApproval) {
219
+ violations.push({
220
+ file,
221
+ rule: `${rule.name || 'Unnamed rule'}: missing import '${requiredImport}'`,
222
+ severity: rule.severity || 'error'
223
+ });
224
+ }
225
+ }
226
+ }
227
+ }
228
+ }
229
+ }
230
+
231
+ // Report violations
232
+ if (violations.length > 0) {
233
+ console.error('\n❌ Architecture violations detected:\n');
234
+
235
+ for (const v of violations) {
236
+ const icon = v.severity === 'error' ? '🔴' : '⚠️';
237
+ console.error(`${icon} [${v.severity.toUpperCase()}] ${v.file}`);
238
+ console.error(` ${v.rule}`);
239
+ console.error(` Add ${APPROVALS_TAG} comment to override\n`);
240
+ }
241
+
242
+ const errorCount = violations.filter(v => v.severity === 'error').length;
243
+ if (errorCount > 0) {
244
+ console.error(`\n❌ Commit blocked: ${errorCount} error(s) found`);
245
+ process.exit(1);
246
+ }
247
+ }
248
+
249
+ console.log('✅ All checks passed');
250
+ }
251
+
252
+ main().catch(err => {
253
+ console.error('❌ Guardian check failed:', err?.message || err);
254
+ process.exit(1);
255
+ });