flecto 2.0.0 → 3.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.
- package/CHANGELOG.md +533 -0
- package/README.md +345 -211
- package/index.js +826 -77
- package/package.json +9 -7
- package/schemas/flecto-policy-pack-2.0.json +129 -0
- package/src/alerter.js +24 -6
- package/src/config.js +135 -9
- package/src/differ.js +154 -47
- package/src/documents.js +106 -0
- package/src/encrypted.js +573 -0
- package/src/notifiers.js +430 -0
- package/src/packs/compose.json +45 -0
- package/src/packs/default.json +23 -1
- package/src/packs/kubernetes.json +112 -0
- package/src/packs/node-runtime.json +44 -0
- package/src/packs/sops.json +61 -0
- package/src/packs/strict-prod.json +11 -1
- package/src/packs/terraform.json +120 -0
- package/src/parser.js +189 -20
- package/src/policy-test.js +124 -0
- package/src/policy.js +815 -30
- package/src/pr-comment.js +480 -0
- package/src/renderer.js +75 -18
- package/src/report.js +653 -0
- package/src/secrets.js +316 -0
- package/src/terraform.js +500 -0
- package/src/watcher.js +27 -15
package/package.json
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
"access": "public",
|
|
5
5
|
"provenance": true
|
|
6
6
|
},
|
|
7
|
-
"version": "
|
|
8
|
-
"description": "Flecto
|
|
7
|
+
"version": "3.0.0",
|
|
8
|
+
"description": "Flecto \u2014 semantic config watcher that reports meaningful changes in plain English",
|
|
9
9
|
"license": "MIT",
|
|
10
10
|
"keywords": [
|
|
11
11
|
"flecto",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"url": "git+https://github.com/myselfsiddharth/Flecto.git"
|
|
26
26
|
},
|
|
27
27
|
"engines": {
|
|
28
|
-
"node": ">=
|
|
28
|
+
"node": ">=20.19.0"
|
|
29
29
|
},
|
|
30
30
|
"type": "module",
|
|
31
31
|
"main": "index.js",
|
|
@@ -37,20 +37,22 @@
|
|
|
37
37
|
"src/**/*",
|
|
38
38
|
"schemas/**/*",
|
|
39
39
|
"README.md",
|
|
40
|
-
"LICENSE"
|
|
40
|
+
"LICENSE",
|
|
41
|
+
"CHANGELOG.md"
|
|
41
42
|
],
|
|
42
43
|
"scripts": {
|
|
43
44
|
"test": "node --test test/*.test.js",
|
|
44
45
|
"test:watch": "node --test --watch test/*.test.js",
|
|
46
|
+
"bench": "node bench/run.js",
|
|
45
47
|
"pack:check": "npm pack --dry-run"
|
|
46
48
|
},
|
|
47
49
|
"dependencies": {
|
|
48
50
|
"@iarna/toml": "^2.2.5",
|
|
49
51
|
"chalk": "^5.3.0",
|
|
50
|
-
"chokidar": "^
|
|
52
|
+
"chokidar": "^5.0.0",
|
|
51
53
|
"commander": "^12.1.0",
|
|
52
|
-
"dotenv": "^
|
|
54
|
+
"dotenv": "^17.4.2",
|
|
53
55
|
"fast-glob": "^3.3.3",
|
|
54
|
-
"js-yaml": "^4.
|
|
56
|
+
"js-yaml": "^4.3.0"
|
|
55
57
|
}
|
|
56
58
|
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://github.com/myselfsiddharth/Flecto/schemas/flecto-policy-pack-2.0.json",
|
|
4
|
+
"title": "FlectoPolicyPack",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"additionalProperties": false,
|
|
7
|
+
"required": ["rules"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"id": { "type": "string", "minLength": 1 },
|
|
10
|
+
"expandSubtrees": { "type": "boolean" },
|
|
11
|
+
"rules": {
|
|
12
|
+
"type": "array",
|
|
13
|
+
"items": { "$ref": "#/$defs/rule" }
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"$defs": {
|
|
17
|
+
"rule": {
|
|
18
|
+
"type": "object",
|
|
19
|
+
"additionalProperties": false,
|
|
20
|
+
"required": ["id", "severity"],
|
|
21
|
+
"properties": {
|
|
22
|
+
"id": { "type": "string", "minLength": 1 },
|
|
23
|
+
"severity": { "enum": ["info", "warn", "error"] },
|
|
24
|
+
"when": {
|
|
25
|
+
"type": "array",
|
|
26
|
+
"minItems": 1,
|
|
27
|
+
"items": { "enum": ["added", "removed", "changed"] }
|
|
28
|
+
},
|
|
29
|
+
"match": {
|
|
30
|
+
"type": "object",
|
|
31
|
+
"additionalProperties": false,
|
|
32
|
+
"properties": {
|
|
33
|
+
"path": { "type": "string" },
|
|
34
|
+
"pathFlags": { "type": "string" },
|
|
35
|
+
"pathEquals": { "type": "string" },
|
|
36
|
+
"pathPrefix": { "type": "string" }
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"beforeEquals": true,
|
|
40
|
+
"afterEquals": true,
|
|
41
|
+
"beforeIn": { "type": "array" },
|
|
42
|
+
"afterIn": { "type": "array" },
|
|
43
|
+
"beforeTruthy": { "const": true },
|
|
44
|
+
"afterTruthy": { "const": true },
|
|
45
|
+
"beforeLooksSecret": { "const": true },
|
|
46
|
+
"afterLooksSecret": { "const": true },
|
|
47
|
+
"afterMatches": { "type": "string" },
|
|
48
|
+
"numericJump": {
|
|
49
|
+
"type": "object",
|
|
50
|
+
"additionalProperties": false,
|
|
51
|
+
"required": ["minMultiple"],
|
|
52
|
+
"properties": {
|
|
53
|
+
"minMultiple": {
|
|
54
|
+
"type": "number",
|
|
55
|
+
"exclusiveMinimum": 0
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
"numericDelta": {
|
|
60
|
+
"type": "object",
|
|
61
|
+
"additionalProperties": false,
|
|
62
|
+
"required": ["min"],
|
|
63
|
+
"properties": {
|
|
64
|
+
"min": {
|
|
65
|
+
"type": "number",
|
|
66
|
+
"minimum": 0
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
"allOf": {
|
|
71
|
+
"type": "array",
|
|
72
|
+
"minItems": 1,
|
|
73
|
+
"items": { "$ref": "#/$defs/clause" }
|
|
74
|
+
},
|
|
75
|
+
"anyOf": {
|
|
76
|
+
"type": "array",
|
|
77
|
+
"minItems": 1,
|
|
78
|
+
"items": { "$ref": "#/$defs/clause" }
|
|
79
|
+
},
|
|
80
|
+
"message": { "type": "string" },
|
|
81
|
+
"messageTemplate": { "type": "string" }
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
"clause": {
|
|
85
|
+
"type": "object",
|
|
86
|
+
"additionalProperties": false,
|
|
87
|
+
"properties": {
|
|
88
|
+
"match": { "$ref": "#/$defs/match" },
|
|
89
|
+
"beforeEquals": true,
|
|
90
|
+
"afterEquals": true,
|
|
91
|
+
"beforeIn": { "type": "array" },
|
|
92
|
+
"afterIn": { "type": "array" },
|
|
93
|
+
"beforeTruthy": { "const": true },
|
|
94
|
+
"afterTruthy": { "const": true },
|
|
95
|
+
"beforeLooksSecret": { "const": true },
|
|
96
|
+
"afterLooksSecret": { "const": true },
|
|
97
|
+
"afterMatches": { "type": "string" },
|
|
98
|
+
"numericJump": { "$ref": "#/$defs/numericJump" },
|
|
99
|
+
"numericDelta": { "$ref": "#/$defs/numericDelta" }
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
"match": {
|
|
103
|
+
"type": "object",
|
|
104
|
+
"additionalProperties": false,
|
|
105
|
+
"properties": {
|
|
106
|
+
"path": { "type": "string" },
|
|
107
|
+
"pathFlags": { "type": "string" },
|
|
108
|
+
"pathEquals": { "type": "string" },
|
|
109
|
+
"pathPrefix": { "type": "string" }
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
"numericJump": {
|
|
113
|
+
"type": "object",
|
|
114
|
+
"additionalProperties": false,
|
|
115
|
+
"required": ["minMultiple"],
|
|
116
|
+
"properties": {
|
|
117
|
+
"minMultiple": { "type": "number", "exclusiveMinimum": 0 }
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
"numericDelta": {
|
|
121
|
+
"type": "object",
|
|
122
|
+
"additionalProperties": false,
|
|
123
|
+
"required": ["min"],
|
|
124
|
+
"properties": {
|
|
125
|
+
"min": { "type": "number", "minimum": 0 }
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
package/src/alerter.js
CHANGED
|
@@ -2,6 +2,7 @@ import { spawn } from 'child_process';
|
|
|
2
2
|
import { mkdirSync, writeFileSync, readFileSync, readdirSync, unlinkSync } from 'fs';
|
|
3
3
|
import { resolve } from 'path';
|
|
4
4
|
import { renderWarn } from './renderer.js';
|
|
5
|
+
import { formatWebhookPayload } from './notifiers.js';
|
|
5
6
|
|
|
6
7
|
const ALERT_TMP_DIR = '.flecto-tmp';
|
|
7
8
|
const ALERT_QUEUE_DIR = '.flecto-queue';
|
|
@@ -11,12 +12,13 @@ const MAX_ENV_CHANGES_CHARS = 16_000;
|
|
|
11
12
|
let alertQueue = Promise.resolve();
|
|
12
13
|
|
|
13
14
|
function enqueue(fn) {
|
|
14
|
-
|
|
15
|
-
.then(
|
|
15
|
+
const result = alertQueue
|
|
16
|
+
.then(() => fn());
|
|
17
|
+
alertQueue = result
|
|
16
18
|
.catch((err) => {
|
|
17
19
|
renderWarn(`Alert pipeline error: ${err?.message ?? String(err)}`);
|
|
18
20
|
});
|
|
19
|
-
return
|
|
21
|
+
return result;
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
/**
|
|
@@ -134,11 +136,19 @@ async function flushPersistentQueue(deliver) {
|
|
|
134
136
|
/**
|
|
135
137
|
* @param {string} url
|
|
136
138
|
* @param {import('./envelope.js').FlectoEnvelope} envelope
|
|
137
|
-
* @param {{
|
|
139
|
+
* @param {{
|
|
140
|
+
* headers?: Record<string, string>,
|
|
141
|
+
* timeoutMs?: number,
|
|
142
|
+
* retries?: number,
|
|
143
|
+
* format?: import('./notifiers.js').WebhookFormat
|
|
144
|
+
* }} [options]
|
|
138
145
|
* @returns {Promise<boolean>}
|
|
139
146
|
*/
|
|
140
147
|
export async function postWebhook(url, envelope, options = {}) {
|
|
141
|
-
|
|
148
|
+
// `flecto` (the default) returns the envelope untouched, so the body is
|
|
149
|
+
// byte-identical to what has always been posted. Chat formats reshape only
|
|
150
|
+
// the body: headers, retries, and delivery modes are unchanged.
|
|
151
|
+
const body = JSON.stringify(formatWebhookPayload(envelope, options.format ?? 'flecto'));
|
|
142
152
|
const timeoutMs = options.timeoutMs ?? 5_000;
|
|
143
153
|
const retries = options.retries ?? 2;
|
|
144
154
|
const headers = {
|
|
@@ -185,7 +195,13 @@ export async function postWebhook(url, envelope, options = {}) {
|
|
|
185
195
|
}
|
|
186
196
|
|
|
187
197
|
/**
|
|
188
|
-
* @param {{
|
|
198
|
+
* @param {{
|
|
199
|
+
* webhook?: string,
|
|
200
|
+
* webhookHeaders?: Record<string, string>,
|
|
201
|
+
* webhookTimeoutMs?: number,
|
|
202
|
+
* webhookRetries?: number,
|
|
203
|
+
* webhookFormat?: import('./notifiers.js').WebhookFormat
|
|
204
|
+
* }} options
|
|
189
205
|
* @param {import('./envelope.js').FlectoEnvelope} envelope
|
|
190
206
|
*/
|
|
191
207
|
async function deliverWebhook(options, envelope) {
|
|
@@ -194,6 +210,7 @@ async function deliverWebhook(options, envelope) {
|
|
|
194
210
|
headers: options.webhookHeaders,
|
|
195
211
|
timeoutMs: options.webhookTimeoutMs,
|
|
196
212
|
retries: options.webhookRetries,
|
|
213
|
+
format: options.webhookFormat,
|
|
197
214
|
});
|
|
198
215
|
}
|
|
199
216
|
|
|
@@ -216,6 +233,7 @@ function applyFailurePolicy(options, ok) {
|
|
|
216
233
|
* webhookHeaders?: Record<string, string>,
|
|
217
234
|
* webhookTimeoutMs?: number,
|
|
218
235
|
* webhookRetries?: number,
|
|
236
|
+
* webhookFormat?: import('./notifiers.js').WebhookFormat,
|
|
219
237
|
* deliveryMode?: 'best-effort' | 'at-least-once',
|
|
220
238
|
* onAlertFailure?: 'warn' | 'exit' | 'retry'
|
|
221
239
|
* }} options
|
package/src/config.js
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
|
-
import { existsSync, readFileSync, writeFileSync } from 'fs';
|
|
1
|
+
import { existsSync, readFileSync, readdirSync, writeFileSync } from 'fs';
|
|
2
2
|
import { resolve } from 'path';
|
|
3
3
|
import fg from 'fast-glob';
|
|
4
4
|
import yaml from 'js-yaml';
|
|
5
|
+
import { isEnvFilename } from './parser.js';
|
|
5
6
|
|
|
6
7
|
const RC_CANDIDATES = ['.flectorc', '.flectorc.json', '.flectorc.yaml', '.flectorc.yml'];
|
|
8
|
+
const COMPOSE_FILENAMES = ['docker-compose.yml', 'docker-compose.yaml', 'compose.yml', 'compose.yaml'];
|
|
9
|
+
const CONFIG_DIR_PATTERN = 'config/**/*.{yaml,yml,json,toml,ini}';
|
|
10
|
+
const ENV_FILE_PATTERNS = ['.env', '.env.*', '*.env'];
|
|
11
|
+
const GENERIC_FILE_PATTERNS = [CONFIG_DIR_PATTERN, ...ENV_FILE_PATTERNS];
|
|
7
12
|
|
|
8
13
|
/**
|
|
9
14
|
* @typedef {{
|
|
@@ -13,6 +18,15 @@ const RC_CANDIDATES = ['.flectorc', '.flectorc.json', '.flectorc.yaml', '.flecto
|
|
|
13
18
|
* include?: string[],
|
|
14
19
|
* exclude?: string[]
|
|
15
20
|
* }} FlectoRc
|
|
21
|
+
*
|
|
22
|
+
* @typedef {{
|
|
23
|
+
* id: string,
|
|
24
|
+
* evidence: string[],
|
|
25
|
+
* pack: string | null,
|
|
26
|
+
* summary: string
|
|
27
|
+
* }} StackSignal
|
|
28
|
+
*
|
|
29
|
+
* @typedef {{ signals: StackSignal[], packs: string[], files: string[] }} StackDetection
|
|
16
30
|
*/
|
|
17
31
|
|
|
18
32
|
/**
|
|
@@ -73,6 +87,7 @@ export function resolveEffectiveOptions(config, profile, cliOverrides = {}) {
|
|
|
73
87
|
export function resolvePolicyOptions(effective) {
|
|
74
88
|
const policiesRaw = effective.policies;
|
|
75
89
|
const pluginsRaw = effective.plugins;
|
|
90
|
+
const severityRemapRaw = effective.severityRemap;
|
|
76
91
|
const policies = Array.isArray(policiesRaw)
|
|
77
92
|
? policiesRaw.map(String)
|
|
78
93
|
: typeof policiesRaw === 'string'
|
|
@@ -83,7 +98,22 @@ export function resolvePolicyOptions(effective) {
|
|
|
83
98
|
: typeof pluginsRaw === 'string'
|
|
84
99
|
? String(pluginsRaw).split(',').map((s) => s.trim()).filter(Boolean)
|
|
85
100
|
: [];
|
|
86
|
-
|
|
101
|
+
if (
|
|
102
|
+
severityRemapRaw !== undefined
|
|
103
|
+
&& (severityRemapRaw === null || Array.isArray(severityRemapRaw) || typeof severityRemapRaw !== 'object')
|
|
104
|
+
) {
|
|
105
|
+
throw new Error('severityRemap must be an object mapping rule ids to info, warn, error, or off');
|
|
106
|
+
}
|
|
107
|
+
const severityRemap = {};
|
|
108
|
+
for (const [ruleId, severity] of Object.entries(severityRemapRaw ?? {})) {
|
|
109
|
+
if (!['info', 'warn', 'error', 'off'].includes(severity)) {
|
|
110
|
+
throw new Error(
|
|
111
|
+
`severityRemap for "${ruleId}" must be one of: info, warn, error, off`,
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
severityRemap[ruleId] = severity;
|
|
115
|
+
}
|
|
116
|
+
return { policies, plugins, severityRemap };
|
|
87
117
|
}
|
|
88
118
|
|
|
89
119
|
/**
|
|
@@ -110,13 +140,104 @@ export async function resolveFiles(input) {
|
|
|
110
140
|
}
|
|
111
141
|
|
|
112
142
|
/**
|
|
113
|
-
*
|
|
143
|
+
* Detect stack signals in a directory and map them to policy packs and file
|
|
144
|
+
* patterns. Only built-in pack ids and patterns Flecto can actually parse are
|
|
145
|
+
* ever returned, so a config built from this stays loadable by every command.
|
|
146
|
+
* Terraform files are reported as context only: `.tf` is not a supported parse
|
|
147
|
+
* format, and the `terraform` pack reads plan JSON rather than `.tf` sources,
|
|
148
|
+
* so neither belongs in a config built from a directory listing.
|
|
114
149
|
* @param {string} cwd
|
|
115
|
-
* @returns {
|
|
150
|
+
* @returns {StackDetection}
|
|
151
|
+
*/
|
|
152
|
+
export function detectStack(cwd = process.cwd()) {
|
|
153
|
+
/** @type {StackSignal[]} */
|
|
154
|
+
const signals = [];
|
|
155
|
+
const packs = ['default'];
|
|
156
|
+
/** @type {string[]} */
|
|
157
|
+
const files = [];
|
|
158
|
+
|
|
159
|
+
let entries = [];
|
|
160
|
+
try {
|
|
161
|
+
entries = readdirSync(cwd, { withFileTypes: true });
|
|
162
|
+
} catch {
|
|
163
|
+
return { signals, packs, files };
|
|
164
|
+
}
|
|
165
|
+
const fileNames = entries.filter((entry) => entry.isFile()).map((entry) => entry.name);
|
|
166
|
+
const dirNames = new Set(entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name));
|
|
167
|
+
const fileNameSet = new Set(fileNames);
|
|
168
|
+
|
|
169
|
+
const composeFiles = COMPOSE_FILENAMES.filter((name) => fileNameSet.has(name));
|
|
170
|
+
if (composeFiles.length > 0) {
|
|
171
|
+
packs.push('compose');
|
|
172
|
+
files.push(...composeFiles);
|
|
173
|
+
signals.push({
|
|
174
|
+
id: 'compose',
|
|
175
|
+
evidence: composeFiles,
|
|
176
|
+
pack: 'compose',
|
|
177
|
+
summary: `Detected ${composeFiles.join(', ')} → enabled the \`compose\` policy pack and watched ${composeFiles.length === 1 ? 'it' : 'them'}`,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (fileNameSet.has('package.json')) {
|
|
182
|
+
packs.push('node-runtime');
|
|
183
|
+
files.push('package.json');
|
|
184
|
+
signals.push({
|
|
185
|
+
id: 'node',
|
|
186
|
+
evidence: ['package.json'],
|
|
187
|
+
pack: 'node-runtime',
|
|
188
|
+
summary: 'Detected package.json → enabled the `node-runtime` policy pack and watched it',
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const terraformFiles = fileNames.filter((name) => name.toLowerCase().endsWith('.tf')).sort();
|
|
193
|
+
if (terraformFiles.length > 0) {
|
|
194
|
+
signals.push({
|
|
195
|
+
id: 'terraform',
|
|
196
|
+
evidence: terraformFiles,
|
|
197
|
+
pack: null,
|
|
198
|
+
summary: `Detected Terraform files (${terraformFiles.join(', ')}) → .tf is not a parseable format, so nothing was enabled; run "flecto plan" on "terraform show -json" output to use the terraform pack`,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (dirNames.has('config')) {
|
|
203
|
+
files.push(CONFIG_DIR_PATTERN);
|
|
204
|
+
signals.push({
|
|
205
|
+
id: 'config-dir',
|
|
206
|
+
evidence: ['config/'],
|
|
207
|
+
pack: null,
|
|
208
|
+
summary: `Detected config/ → watched ${CONFIG_DIR_PATTERN}`,
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const envFiles = fileNames.filter((name) => isEnvFilename(name)).sort();
|
|
213
|
+
if (envFiles.length > 0) {
|
|
214
|
+
files.push(...ENV_FILE_PATTERNS);
|
|
215
|
+
signals.push({
|
|
216
|
+
id: 'dotenv',
|
|
217
|
+
evidence: envFiles,
|
|
218
|
+
pack: null,
|
|
219
|
+
summary: `Detected ${envFiles.join(', ')} → watched ${ENV_FILE_PATTERNS.join(', ')}`,
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return { signals, packs, files: [...new Set(files)] };
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Scaffold a starter rc file if missing, pre-selecting policy packs and file
|
|
228
|
+
* patterns from the stack signals found in `cwd`. Never overwrites an existing
|
|
229
|
+
* config: any of the four `.flectorc` candidates is reported back untouched.
|
|
230
|
+
* @param {string} cwd
|
|
231
|
+
* @returns {{ path: string, created: boolean, detection: StackDetection }}
|
|
116
232
|
*/
|
|
117
233
|
export function initRcFile(cwd = process.cwd()) {
|
|
234
|
+
const detection = detectStack(cwd);
|
|
235
|
+
const existingPath = RC_CANDIDATES
|
|
236
|
+
.map((candidate) => resolve(cwd, candidate))
|
|
237
|
+
.find((candidate) => existsSync(candidate));
|
|
238
|
+
if (existingPath) return { path: existingPath, created: false, detection };
|
|
239
|
+
|
|
118
240
|
const path = resolve(cwd, '.flectorc.json');
|
|
119
|
-
if (existsSync(path)) return path;
|
|
120
241
|
const starter = {
|
|
121
242
|
defaults: {
|
|
122
243
|
mode: 'compact',
|
|
@@ -124,20 +245,25 @@ export function initRcFile(cwd = process.cwd()) {
|
|
|
124
245
|
ignore: ['**.updated_at'],
|
|
125
246
|
deliveryMode: 'best-effort',
|
|
126
247
|
onAlertFailure: 'warn',
|
|
127
|
-
policies:
|
|
248
|
+
policies: detection.packs,
|
|
128
249
|
plugins: [],
|
|
129
250
|
arrayIdKey: null,
|
|
251
|
+
arrayId: true,
|
|
130
252
|
arrayIgnoreOrder: false,
|
|
131
253
|
maskSecrets: false,
|
|
132
254
|
},
|
|
133
255
|
profiles: {
|
|
134
256
|
dev: { mode: 'verbose' },
|
|
135
257
|
ci: { failOn: 'policy,error' },
|
|
136
|
-
prod: {
|
|
258
|
+
prod: {
|
|
259
|
+
policies: [...detection.packs, 'strict-prod'],
|
|
260
|
+
severityRemap: { 'pool-size-jump': 'error' },
|
|
261
|
+
maskSecrets: true,
|
|
262
|
+
},
|
|
137
263
|
},
|
|
138
|
-
files:
|
|
264
|
+
files: detection.files.length > 0 ? detection.files : [...GENERIC_FILE_PATTERNS],
|
|
139
265
|
exclude: ['**/node_modules/**'],
|
|
140
266
|
};
|
|
141
267
|
writeFileSync(path, JSON.stringify(starter, null, 2), 'utf8');
|
|
142
|
-
return path;
|
|
268
|
+
return { path, created: true, detection };
|
|
143
269
|
}
|