flecto 2.1.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 +431 -1
- package/README.md +305 -309
- package/index.js +574 -56
- package/package.json +3 -2
- package/schemas/flecto-policy-pack-2.0.json +5 -0
- package/src/alerter.js +20 -3
- package/src/config.js +113 -8
- package/src/differ.js +59 -2
- package/src/documents.js +106 -0
- package/src/encrypted.js +573 -0
- package/src/notifiers.js +430 -0
- package/src/packs/default.json +22 -0
- package/src/packs/kubernetes.json +112 -0
- package/src/packs/sops.json +61 -0
- package/src/packs/strict-prod.json +10 -0
- package/src/packs/terraform.json +120 -0
- package/src/parser.js +189 -20
- package/src/policy.js +498 -11
- package/src/pr-comment.js +480 -0
- package/src/renderer.js +70 -16
- package/src/report.js +653 -0
- package/src/secrets.js +316 -0
- package/src/terraform.js +500 -0
- package/src/watcher.js +9 -7
package/README.md
CHANGED
|
@@ -5,52 +5,35 @@
|
|
|
5
5
|
<h1 align="center">Flecto</h1>
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
|
-
<strong>
|
|
9
|
-
Watch · Diff · Policy · CI · Webhooks
|
|
8
|
+
<strong>Know what your config actually changed — and whether it's risky.</strong>
|
|
10
9
|
</p>
|
|
11
10
|
|
|
12
11
|
<p align="center">
|
|
13
12
|
<a href="https://www.npmjs.com/package/flecto"><img alt="npm" src="https://img.shields.io/npm/v/flecto?style=flat-square&color=34d399&labelColor=0b1220"/></a>
|
|
14
13
|
<a href="https://github.com/myselfsiddharth/Flecto/actions/workflows/ci.yml"><img alt="CI" src="https://img.shields.io/github/actions/workflow/status/myselfsiddharth/Flecto/ci.yml?branch=main&style=flat-square&label=CI&labelColor=0b1220"/></a>
|
|
15
14
|
<a href="LICENSE"><img alt="MIT" src="https://img.shields.io/badge/license-MIT-8fa3bf?style=flat-square&labelColor=0b1220"/></a>
|
|
16
|
-
<a href="
|
|
15
|
+
<a href="#documentation"><img alt="Docs" src="https://img.shields.io/badge/docs-read-34d399?style=flat-square&labelColor=0b1220"/></a>
|
|
17
16
|
</p>
|
|
18
17
|
|
|
19
18
|
<p align="center">
|
|
20
|
-
<
|
|
21
|
-
</p>
|
|
22
|
-
|
|
23
|
-
<p align="center">
|
|
24
|
-
<img src="docs/assets/flecto-demo.png" alt="Flecto watch demo in the terminal" width="920"/>
|
|
25
|
-
</p>
|
|
26
|
-
|
|
27
|
-
<p align="center">
|
|
28
|
-
<img src="docs/assets/demo-watch.svg" alt="Animated Flecto watch output" width="920"/>
|
|
19
|
+
<img src="docs/assets/demo-watch.svg" alt="Flecto reporting semantic config changes in the terminal" width="920"/>
|
|
29
20
|
</p>
|
|
30
21
|
|
|
31
22
|
---
|
|
32
23
|
|
|
33
|
-
|
|
24
|
+
Config drives the parts of a system that break loudest: connection pools, feature
|
|
25
|
+
flags, TLS, retries, secrets. But we still review it as text — so a reordered key
|
|
26
|
+
looks identical to a doubled pool size, and `debug: true` slips through in a
|
|
27
|
+
40-line formatting diff.
|
|
34
28
|
|
|
35
|
-
|
|
29
|
+
Flecto reads config as structure, not lines. It tells you what changed in plain
|
|
30
|
+
English, flags what looks risky, and gives you an exit code to gate on.
|
|
36
31
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
- What changed (`pool_size: 5 → 20`)
|
|
40
|
-
- What was added or removed
|
|
41
|
-
- What looks risky (secrets, dangerous toggles, pool jumps)
|
|
42
|
-
- What to do next (CI gate, webhook, shell command)
|
|
43
|
-
|
|
44
|
-
See the [changelog and migration notes](CHANGELOG.md) for release history and
|
|
45
|
-
upcoming v2.1 behavior changes.
|
|
46
|
-
|
|
47
|
-
> Diff tools compare trees. **Flecto watches, scores risk, and alerts.**
|
|
48
|
-
|
|
49
|
-
| Without Flecto | With Flecto |
|
|
32
|
+
| Reviewing config without Flecto | With Flecto |
|
|
50
33
|
|---|---|
|
|
51
|
-
| `+
|
|
52
|
-
| Hope someone notices `debug: true` | Policy finding →
|
|
53
|
-
|
|
|
34
|
+
| `+ 40 lines of YAML noise` | `~ database.pool_size: 5 → 20` |
|
|
35
|
+
| Hope someone notices `debug: true` | Policy finding → build fails |
|
|
36
|
+
| "Something in `.env` changed" | The exact keys, with secrets masked |
|
|
54
37
|
|
|
55
38
|
---
|
|
56
39
|
|
|
@@ -60,358 +43,372 @@ upcoming v2.1 behavior changes.
|
|
|
60
43
|
npm install -g flecto
|
|
61
44
|
```
|
|
62
45
|
|
|
63
|
-
Requires Node.js 20.19.0
|
|
46
|
+
Requires **Node.js 20.19.0+**. Verify:
|
|
64
47
|
|
|
65
48
|
```bash
|
|
66
49
|
flecto --version
|
|
67
50
|
flecto doctor
|
|
68
51
|
```
|
|
69
52
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
## Quick start
|
|
73
|
-
|
|
74
|
-
```bash
|
|
75
|
-
flecto watch config/prod.yaml
|
|
76
|
-
flecto watch .env
|
|
77
|
-
flecto watch settings.json
|
|
78
|
-
flecto watch pyproject.toml
|
|
79
|
-
flecto watch app.ini
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
That’s it — Flecto prints a clear summary on every meaningful change.
|
|
53
|
+
Prefer not to install globally? Every example below works with
|
|
54
|
+
`npx --yes flecto@3` instead of `flecto`.
|
|
83
55
|
|
|
84
56
|
---
|
|
85
57
|
|
|
86
|
-
##
|
|
87
|
-
|
|
88
|
-
- **Semantic diffs** for JSON, YAML, TOML, INI, and dotenv (`.env`, `.env.*`, `*.env`)
|
|
89
|
-
- **Live watch** with optional command + webhook delivery
|
|
90
|
-
- **Policy packs** (`default`, `strict-prod`, `compose`, `node-runtime`) + custom `policies/*.json` + local ESM plugins
|
|
91
|
-
- **CI mode** with JSON / NDJSON / GitHub annotations and fail rules
|
|
92
|
-
- **Snapshots & diffs** for deploy scripts and pre-commit hooks
|
|
93
|
-
- **Profiles** via `--profile` or `FLECTO_PROFILE`
|
|
94
|
-
- **Default array identity** (`id` / `name`, or `--array-id-key`) and secret masking
|
|
95
|
-
|
|
96
|
-
---
|
|
58
|
+
## Quick start
|
|
97
59
|
|
|
98
|
-
|
|
60
|
+
A complete walkthrough, start to finish. Copy-paste it anywhere.
|
|
99
61
|
|
|
100
|
-
|
|
62
|
+
**1. Create a config file to track.**
|
|
101
63
|
|
|
102
64
|
```bash
|
|
103
|
-
flecto
|
|
65
|
+
mkdir flecto-demo && cd flecto-demo && mkdir config
|
|
66
|
+
cat > config/prod.yaml <<'EOF'
|
|
67
|
+
database:
|
|
68
|
+
host: db.internal
|
|
69
|
+
pool_size: 5
|
|
70
|
+
ssl: true
|
|
71
|
+
logging:
|
|
72
|
+
level: info
|
|
73
|
+
debug: false
|
|
74
|
+
EOF
|
|
104
75
|
```
|
|
105
76
|
|
|
106
|
-
|
|
77
|
+
**2. Save it as your baseline.**
|
|
107
78
|
|
|
108
79
|
```bash
|
|
109
|
-
flecto watch config/prod.yaml --
|
|
80
|
+
flecto watch config/prod.yaml --snapshot
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
✓ Snapshot saved: /path/to/flecto-demo/.flecto-snapshots/4b8cbbd70d1832a2.json
|
|
110
85
|
```
|
|
111
86
|
|
|
112
|
-
|
|
87
|
+
**3. Make the kind of edit that causes incidents.**
|
|
113
88
|
|
|
114
89
|
```bash
|
|
115
|
-
|
|
90
|
+
cat > config/prod.yaml <<'EOF'
|
|
91
|
+
database:
|
|
92
|
+
host: db.internal
|
|
93
|
+
pool_size: 20
|
|
94
|
+
ssl: true
|
|
95
|
+
logging:
|
|
96
|
+
level: info
|
|
97
|
+
debug: true
|
|
98
|
+
EOF
|
|
116
99
|
```
|
|
117
100
|
|
|
118
|
-
|
|
119
|
-
|---|---|
|
|
120
|
-
| `meta.timestamp` | That exact key |
|
|
121
|
-
| `meta` | Everything under `meta.*` |
|
|
122
|
-
| `servers[*].meta.timestamp` | That key inside any array item |
|
|
123
|
-
| `**.updated_at` | Any key named `updated_at`, anywhere |
|
|
124
|
-
|
|
125
|
-
### Run a command on change
|
|
101
|
+
**4. Ask what changed.**
|
|
126
102
|
|
|
127
103
|
```bash
|
|
128
|
-
flecto watch .
|
|
104
|
+
flecto watch config/prod.yaml --diff
|
|
129
105
|
```
|
|
130
106
|
|
|
131
|
-
|
|
107
|
+
```
|
|
108
|
+
/path/to/flecto-demo/config/prod.yaml — 2 changes from snapshot:
|
|
109
|
+
~ database.pool_size: 5 → 20
|
|
110
|
+
~ logging.debug: false → true
|
|
111
|
+
```
|
|
132
112
|
|
|
133
|
-
|
|
113
|
+
Two sentences instead of a diff you have to interpret. Now let Flecto judge it:
|
|
134
114
|
|
|
135
115
|
```bash
|
|
136
|
-
flecto
|
|
137
|
-
--webhook https://hooks.example.com/notify \
|
|
138
|
-
--webhook-header "Authorization: Bearer TOKEN"
|
|
116
|
+
flecto ci config/prod.yaml --format github-annotations
|
|
139
117
|
```
|
|
140
118
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
"event_id": "uuid",
|
|
147
|
-
"event_type": "changes",
|
|
148
|
-
"emitted_at": "2026-04-14T10:42:31.000Z",
|
|
149
|
-
"file": "/absolute/path/to/config/prod.yaml",
|
|
150
|
-
"changes": [
|
|
151
|
-
{ "type": "changed", "path": "database.pool_size", "before": 5, "after": 20 }
|
|
152
|
-
],
|
|
153
|
-
"policies": [
|
|
154
|
-
{
|
|
155
|
-
"id": "pool-size-jump",
|
|
156
|
-
"severity": "warn",
|
|
157
|
-
"path": "database.pool_size",
|
|
158
|
-
"message": "Pool size increased from 5 to 20 (>=2x).",
|
|
159
|
-
"pack": "default"
|
|
160
|
-
}
|
|
161
|
-
]
|
|
162
|
-
}
|
|
119
|
+
```
|
|
120
|
+
::warning title=flecto changed::database.pool_size
|
|
121
|
+
::warning title=flecto changed::logging.debug
|
|
122
|
+
::warning title=flecto policy pool-size-jump [default]::database.pool_size: Pool size increased from 5 to 20 (>=2x).
|
|
123
|
+
::error title=flecto policy dangerous-toggle-enabled [default]::logging.debug: Potentially dangerous toggle enabled.
|
|
163
124
|
```
|
|
164
125
|
|
|
165
|
-
|
|
126
|
+
Exit code `1`. In CI, that's a failed build — before the change ships.
|
|
166
127
|
|
|
167
|
-
|
|
128
|
+
**5. Watch it live.** Leave this running and edit the file in another window:
|
|
168
129
|
|
|
169
130
|
```bash
|
|
170
|
-
flecto
|
|
131
|
+
flecto watch config/prod.yaml
|
|
171
132
|
```
|
|
172
133
|
|
|
173
|
-
```
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
"severityRemap": { "pool-size-jump": "error" },
|
|
183
|
-
"maskSecrets": true
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
}
|
|
134
|
+
```
|
|
135
|
+
flecto watching /path/to/flecto-demo/config/prod.yaml
|
|
136
|
+
Press Ctrl+C to stop.
|
|
137
|
+
|
|
138
|
+
[18:24:48] /path/to/flecto-demo/config/prod.yaml — 2 changes
|
|
139
|
+
~ database.pool_size: 5 → 20
|
|
140
|
+
~ logging.debug: false → true
|
|
141
|
+
! policy(warn) [default] database.pool_size: Pool size increased from 5 to 20 (>=2x).
|
|
142
|
+
! policy(error) [default] logging.debug: Potentially dangerous toggle enabled.
|
|
187
143
|
```
|
|
188
144
|
|
|
189
|
-
|
|
190
|
-
Custom packs: `policies/<id>.json`. Plugins: local ESM exporting `evaluate(changes, ctx)`.
|
|
145
|
+
That's the whole product. Everything below is depth.
|
|
191
146
|
|
|
192
|
-
|
|
147
|
+
---
|
|
193
148
|
|
|
194
|
-
|
|
195
|
-
`match.path`, `afterEquals`, and `numericJump`, packs can use:
|
|
149
|
+
## What you can do with it
|
|
196
150
|
|
|
197
|
-
|
|
198
|
-
- `beforeTruthy: true` and `afterTruthy: true` to require a truthy before/after value.
|
|
199
|
-
- `afterMatches` to require a string after value that matches a regular expression.
|
|
200
|
-
- `numericDelta: { "min": 10 }` to match an absolute numeric change of at least 10.
|
|
201
|
-
- `match.pathEquals` and `match.pathPrefix` for exact or prefix path matching without regex.
|
|
202
|
-
- `allOf` and `anyOf` arrays of simple match clauses. Every `allOf` clause and at least
|
|
203
|
-
one `anyOf` clause must match. Clauses support the same value, truthiness, numeric, and
|
|
204
|
-
`match` predicates, but cannot nest composition.
|
|
151
|
+
### Catch risky changes before they merge
|
|
205
152
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
153
|
+
Add one step to your workflow and risky config edits show up as annotations on
|
|
154
|
+
the pull request:
|
|
155
|
+
|
|
156
|
+
```yaml
|
|
157
|
+
permissions:
|
|
158
|
+
contents: read
|
|
159
|
+
|
|
160
|
+
steps:
|
|
161
|
+
- uses: actions/checkout@v7
|
|
162
|
+
with:
|
|
163
|
+
fetch-depth: 2
|
|
164
|
+
- uses: myselfsiddharth/Flecto/.github/actions/flecto-ci@main
|
|
165
|
+
with:
|
|
166
|
+
targets: config/**/*.{yaml,yml,json,toml,ini}
|
|
167
|
+
snapshot-ref: HEAD~1
|
|
219
168
|
```
|
|
220
169
|
|
|
221
|
-
|
|
222
|
-
|
|
170
|
+
Prefer a summary nobody can miss? `--format pr-comment` renders the changes and
|
|
171
|
+
policy findings as markdown and, when you opt in with `--pr-comment-post` inside
|
|
172
|
+
a GitHub PR run, keeps **one** sticky comment up to date instead of adding a new
|
|
173
|
+
one per push. Without that flag it just prints the markdown, so it can't post
|
|
174
|
+
from your laptop.
|
|
223
175
|
|
|
224
|
-
|
|
176
|
+
The `flecto-pr-risk` Action is that, packaged — the whole adoption is one
|
|
177
|
+
`uses:`, with the baseline resolved from the pull request rather than `HEAD~1`:
|
|
225
178
|
|
|
226
|
-
```
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
}
|
|
179
|
+
```yaml
|
|
180
|
+
permissions:
|
|
181
|
+
contents: read
|
|
182
|
+
pull-requests: write
|
|
183
|
+
|
|
184
|
+
steps:
|
|
185
|
+
- uses: actions/checkout@v7
|
|
186
|
+
with:
|
|
187
|
+
fetch-depth: 0
|
|
188
|
+
- uses: myselfsiddharth/Flecto/.github/actions/flecto-pr-risk@main
|
|
237
189
|
```
|
|
238
190
|
|
|
239
|
-
|
|
191
|
+
A fork's pull request gets a read-only token, so the comment is skipped with a
|
|
192
|
+
warning there — the check itself still runs and still fails on risky changes.
|
|
193
|
+
|
|
194
|
+
Works on any CI runner — it's a plain CLI with meaningful exit codes.
|
|
195
|
+
→ **[CI guide](docs/ci.md)**
|
|
240
196
|
|
|
241
|
-
|
|
197
|
+
### Trigger automation on change
|
|
242
198
|
|
|
243
|
-
|
|
199
|
+
Restart a service, reload a process, or notify an endpoint whenever config moves:
|
|
244
200
|
|
|
245
201
|
```bash
|
|
246
|
-
flecto
|
|
247
|
-
|
|
202
|
+
flecto watch .env --command "docker-compose restart app"
|
|
203
|
+
|
|
204
|
+
flecto watch config/prod.yaml \
|
|
205
|
+
--webhook https://hooks.example.com/notify \
|
|
206
|
+
--delivery-mode at-least-once
|
|
248
207
|
```
|
|
249
208
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
id, Flecto resolves local files before bundled packs in this order:
|
|
253
|
-
`policies/<id>.json`, `policies/<id>.yaml`, `policies/<id>.yml`, then the
|
|
254
|
-
built-in pack. A local pack with the same id overrides its built-in counterpart.
|
|
209
|
+
Changes arrive as a versioned JSON envelope, and `at-least-once` persists and
|
|
210
|
+
retries failed deliveries.
|
|
255
211
|
|
|
256
|
-
|
|
212
|
+
Posting straight to chat needs no receiver of your own — `--webhook-format`
|
|
213
|
+
shapes the body for Slack, Discord, or Teams, colored by the highest policy
|
|
214
|
+
severity:
|
|
257
215
|
|
|
258
216
|
```bash
|
|
259
|
-
flecto watch config/
|
|
217
|
+
flecto watch config/prod.yaml \
|
|
218
|
+
--webhook "https://hooks.slack.com/services/T000/B000/XXXX" \
|
|
219
|
+
--webhook-format slack
|
|
260
220
|
```
|
|
261
221
|
|
|
262
|
-
|
|
263
|
-
to `name` when `id` is unavailable. This avoids false changes when named items
|
|
264
|
-
are reordered. Use a custom identity field when needed:
|
|
222
|
+
→ **[Webhooks and commands](docs/webhooks.md)**
|
|
265
223
|
|
|
266
|
-
|
|
267
|
-
flecto watch config/services.yaml --array-id-key serviceKey
|
|
268
|
-
```
|
|
224
|
+
### Compare two environments
|
|
269
225
|
|
|
270
|
-
|
|
226
|
+
"Works in staging, fails in prod" is usually one key apart:
|
|
271
227
|
|
|
272
228
|
```bash
|
|
273
|
-
flecto
|
|
229
|
+
flecto compare config/prod.yaml config/staging.yaml
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
```
|
|
233
|
+
"+" exists only in the compared file, "-" only in the baseline, "~" differs
|
|
234
|
+
/path/to/config/staging.yaml — 2 changes from /path/to/config/prod.yaml:
|
|
235
|
+
- only_in_prod: true
|
|
236
|
+
~ database.pool_size: 5 → 20
|
|
237
|
+
! policy(warn) [default] database.pool_size: Pool size increased from 5 to 20 (>=2x).
|
|
274
238
|
```
|
|
275
239
|
|
|
276
|
-
|
|
277
|
-
|
|
240
|
+
The first file is the baseline, the files don't have to share a format, and
|
|
241
|
+
`--format json` gives you the same output `flecto ci` produces.
|
|
242
|
+
→ **[CLI reference](docs/cli-reference.md#flecto-compare-filea-fileb)**
|
|
278
243
|
|
|
279
|
-
###
|
|
244
|
+
### Track drift over time
|
|
280
245
|
|
|
281
246
|
```bash
|
|
282
|
-
flecto
|
|
283
|
-
--command "make reload" \
|
|
284
|
-
--webhook https://hooks.example.com/notify
|
|
247
|
+
flecto history config/prod.yaml --limit 10
|
|
285
248
|
```
|
|
286
249
|
|
|
287
|
-
|
|
250
|
+
Snapshots stay on your machine in `.flecto-snapshots/`. Nothing is uploaded and
|
|
251
|
+
no account is required. → **[CLI reference](docs/cli-reference.md#flecto-history-files)**
|
|
252
|
+
|
|
253
|
+
### Share what changed before the incident
|
|
288
254
|
|
|
289
255
|
```bash
|
|
290
|
-
flecto
|
|
291
|
-
--webhook https://hooks.example.com/notify \
|
|
292
|
-
--delivery-mode at-least-once \
|
|
293
|
-
--on-alert-failure retry
|
|
256
|
+
flecto report --limit 20 --mask-secrets --output drift.html
|
|
294
257
|
```
|
|
295
258
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
259
|
+
One HTML file from that same local history: a timeline per file, every change
|
|
260
|
+
with its UTC timestamp, and policy findings grouped by severity. Fully
|
|
261
|
+
self-contained — inline styles, no fonts, no CDN scripts, no analytics — so you
|
|
262
|
+
can attach it to an incident thread and it renders offline. No server and no
|
|
263
|
+
account, same as everything else here.
|
|
264
|
+
→ **[CLI reference](docs/cli-reference.md#flecto-report-files)**
|
|
300
265
|
|
|
301
|
-
|
|
266
|
+
### Review a Kubernetes change before it reaches a cluster
|
|
302
267
|
|
|
303
|
-
|
|
268
|
+
ArgoCD, Flux, and `helm diff` compare the cluster to the repo — which needs a
|
|
269
|
+
cluster, and an apply that already happened. Flecto compares the manifests *this
|
|
270
|
+
pull request would produce* against the ones `main` produces:
|
|
304
271
|
|
|
305
272
|
```bash
|
|
306
|
-
|
|
307
|
-
flecto
|
|
308
|
-
flecto history config/prod.yaml --limit 10
|
|
273
|
+
helm template api ./charts/api -f values/prod.yaml > /tmp/head.yaml
|
|
274
|
+
flecto compare /tmp/base.yaml /tmp/head.yaml --policies kubernetes --fail-on error
|
|
309
275
|
```
|
|
310
276
|
|
|
311
|
-
|
|
277
|
+
```
|
|
278
|
+
~ Service/prod/api.spec.type: "ClusterIP" → "LoadBalancer"
|
|
279
|
+
! policy(error) [kubernetes] Service type is LoadBalancer, which exposes the
|
|
280
|
+
workload outside the cluster. Confirm the exposure is intended.
|
|
281
|
+
```
|
|
312
282
|
|
|
313
|
-
|
|
283
|
+
Multi-document YAML is keyed by `kind/namespace/name`, so findings name the
|
|
284
|
+
resource. Flecto never runs `helm` or `kustomize` — you render, it diffs, so any
|
|
285
|
+
renderer works and no binary is needed in CI.
|
|
286
|
+
→ **[Kubernetes](docs/kubernetes.md)**
|
|
314
287
|
|
|
315
|
-
|
|
288
|
+
### Encode your own rules
|
|
316
289
|
|
|
317
|
-
|
|
290
|
+
Beyond the built-in packs, write rules as declarative JSON or YAML — no code:
|
|
318
291
|
|
|
319
|
-
```
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
292
|
+
```json
|
|
293
|
+
{
|
|
294
|
+
"id": "risky-feature-enable",
|
|
295
|
+
"severity": "error",
|
|
296
|
+
"allOf": [
|
|
297
|
+
{ "match": { "pathPrefix": "features." } },
|
|
298
|
+
{ "afterTruthy": true }
|
|
299
|
+
]
|
|
300
|
+
}
|
|
324
301
|
```
|
|
325
302
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
If every target is missing or unsupported, `flecto ci` and `flecto watch --snapshot` exit non-zero (pass `--allow-empty` to opt out).
|
|
303
|
+
For anything a predicate can't express, a local ESM plugin exporting
|
|
304
|
+
`evaluate(changes, ctx)` gets the full change set.
|
|
305
|
+
→ **[Writing policy packs](docs/policy-packs.md)** · **[Plugins](docs/plugins.md)**
|
|
330
306
|
|
|
331
|
-
###
|
|
307
|
+
### Cut the noise
|
|
332
308
|
|
|
333
|
-
|
|
309
|
+
```bash
|
|
310
|
+
flecto watch config/prod.yaml --ignore "updated_at,**.meta.timestamp"
|
|
311
|
+
```
|
|
334
312
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
313
|
+
Arrays of objects are matched by `id` or `name`, so reordering a list of named
|
|
314
|
+
services doesn't read as a wall of changes.
|
|
315
|
+
→ **[Configuration](docs/configuration.md)**
|
|
338
316
|
|
|
339
|
-
|
|
340
|
-
- uses: actions/checkout@v7
|
|
341
|
-
with:
|
|
342
|
-
fetch-depth: 2
|
|
343
|
-
- uses: myselfsiddharth/Flecto/.github/actions/flecto-ci@main
|
|
344
|
-
with:
|
|
345
|
-
targets: config/**/*.{yaml,yml,json,toml,ini}
|
|
346
|
-
snapshot-ref: HEAD~1
|
|
347
|
-
```
|
|
317
|
+
---
|
|
348
318
|
|
|
349
|
-
|
|
319
|
+
## Built-in policy packs
|
|
350
320
|
|
|
351
|
-
|
|
|
352
|
-
|
|
353
|
-
| `
|
|
354
|
-
| `
|
|
355
|
-
| `
|
|
356
|
-
| `
|
|
357
|
-
| `
|
|
358
|
-
| `
|
|
359
|
-
| `
|
|
321
|
+
| Pack | Catches |
|
|
322
|
+
|---|---|
|
|
323
|
+
| `default` | Secret-like keys added or changed, secret-shaped *values* under any key, dangerous toggles, pool-size jumps |
|
|
324
|
+
| `strict-prod` | The same ground, with production-grade severities and matching |
|
|
325
|
+
| `compose` | Privileged services, host networking, Docker socket mounts, sensitive bind mounts |
|
|
326
|
+
| `kubernetes` | Privileged containers, host namespaces, weakened `runAsNonRoot`, added `SYS_ADMIN`, unpinned images, replica jumps, dropped limits, `LoadBalancer`/`NodePort` exposure |
|
|
327
|
+
| `node-runtime` | Dropped engine requirements, TLS verification bypasses, debug/inspector flags |
|
|
328
|
+
| `terraform` | Replaced and destroyed stateful resources, ingress opened to `0.0.0.0/0`, IAM wildcards, public S3, capacity jumps |
|
|
329
|
+
| `sops` | Decryption recipients added or removed, a MAC that moved on its own, a file that stopped being encrypted |
|
|
360
330
|
|
|
361
|
-
|
|
331
|
+
```bash
|
|
332
|
+
flecto policies list # see what resolves here, built-in and local
|
|
333
|
+
flecto ci "config/**/*.yaml" --policies "default,strict-prod" --fail-on policy
|
|
334
|
+
```
|
|
362
335
|
|
|
363
|
-
|
|
336
|
+
Pass files or glob patterns, quoted so your shell doesn't expand them first — a
|
|
337
|
+
bare directory is not a valid target.
|
|
364
338
|
|
|
365
|
-
|
|
339
|
+
A local `policies/<id>.json` overrides the built-in pack of the same id, and
|
|
340
|
+
`severityRemap` raises or silences individual rules per profile without forking
|
|
341
|
+
anything. → **[Policy packs](docs/policy-packs.md)**
|
|
366
342
|
|
|
367
|
-
|
|
343
|
+
Community packs ship on npm as `flecto-pack-<id>` packages — a package name and
|
|
344
|
+
one declarative JSON file, nothing else:
|
|
368
345
|
|
|
369
|
-
|
|
370
|
-
-
|
|
371
|
-
|
|
372
|
-
|
|
346
|
+
```bash
|
|
347
|
+
npm install --save-dev flecto-pack-deployment-safety
|
|
348
|
+
flecto policies add deployment-safety
|
|
349
|
+
```
|
|
373
350
|
|
|
374
|
-
|
|
351
|
+
`policies add` validates the pack, writes it to `policies/deployment-safety.json`,
|
|
352
|
+
and runs no code from the package. →
|
|
353
|
+
**[Installing community packs](docs/policy-packs.md#installing-a-community-pack)**
|
|
375
354
|
|
|
376
355
|
---
|
|
377
356
|
|
|
378
|
-
##
|
|
357
|
+
## Supported formats
|
|
379
358
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
359
|
+
| Format | Extensions |
|
|
360
|
+
|---|---|
|
|
361
|
+
| JSON | `.json` |
|
|
362
|
+
| YAML | `.yaml`, `.yml` |
|
|
363
|
+
| TOML | `.toml` |
|
|
364
|
+
| INI | `.ini` |
|
|
365
|
+
| dotenv | `.env`, `.env.*`, `*.env` |
|
|
366
|
+
| age (armored) | `.age`, or any file whose contents are one armored blob |
|
|
367
|
+
|
|
368
|
+
Multi-document YAML (`---`-separated, the usual shape of a Kubernetes manifest)
|
|
369
|
+
is supported. Each document is diffed under its own key — `kind/name` for
|
|
370
|
+
Kubernetes-shaped documents, so a document inserted at the top of the file does
|
|
371
|
+
not renumber every other path. →
|
|
372
|
+
**[Multi-document YAML](docs/configuration.md#multi-document-yaml)**
|
|
384
373
|
|
|
385
374
|
---
|
|
386
375
|
|
|
387
|
-
##
|
|
376
|
+
## Encrypted files
|
|
377
|
+
|
|
378
|
+
A `sops`- or age-encrypted file is detected from its **contents**, and diffed
|
|
379
|
+
structurally:
|
|
388
380
|
|
|
389
|
-
```bash
|
|
390
|
-
flecto watch config/prod.yaml --polling --interval 500
|
|
391
381
|
```
|
|
382
|
+
+ cache: {"ttl_seconds":300}
|
|
383
|
+
~ database.password: <encrypted value changed>
|
|
384
|
+
+ sops.age.age1exampleexample…: {"recipient":"age1exampleexample…","enc":"<encrypted value>"}
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
You get keys added and removed, which encrypted values moved, and — the useful
|
|
388
|
+
part — who can decrypt the file. A recipient added is a genuine security event
|
|
389
|
+
and the `sops` pack raises it as one.
|
|
392
390
|
|
|
393
|
-
|
|
391
|
+
**Flecto never decrypts.** It never shells out to `sops` or `age`, never reads a
|
|
392
|
+
key file or agent socket, and never prints ciphertext — not even without
|
|
393
|
+
`--mask-secrets`. Ciphertext is replaced with an opaque sentinel in the parser,
|
|
394
|
+
so no diff, snapshot, webhook, or report can carry it. →
|
|
395
|
+
**[Encrypted files](docs/encrypted-files.md)**
|
|
394
396
|
|
|
395
397
|
---
|
|
396
398
|
|
|
397
|
-
##
|
|
399
|
+
## Configuration
|
|
400
|
+
|
|
401
|
+
Most teams commit a `.flectorc` so local runs and CI agree:
|
|
398
402
|
|
|
399
403
|
```bash
|
|
400
404
|
flecto init
|
|
401
405
|
```
|
|
402
406
|
|
|
403
|
-
Looks for `.flectorc`, `.flectorc.json`, `.flectorc.yaml`, or `.flectorc.yml`.
|
|
404
|
-
|
|
405
407
|
```json
|
|
406
408
|
{
|
|
407
409
|
"defaults": {
|
|
408
|
-
"mode": "compact",
|
|
409
|
-
"interval": 100,
|
|
410
|
-
"ignore": ["**.updated_at"],
|
|
411
|
-
"deliveryMode": "best-effort",
|
|
412
|
-
"onAlertFailure": "warn",
|
|
413
410
|
"policies": ["default"],
|
|
414
|
-
"
|
|
411
|
+
"ignore": ["**.updated_at"]
|
|
415
412
|
},
|
|
416
413
|
"profiles": {
|
|
417
414
|
"dev": { "mode": "verbose" },
|
|
@@ -422,81 +419,80 @@ Looks for `.flectorc`, `.flectorc.json`, `.flectorc.yaml`, or `.flectorc.yml`.
|
|
|
422
419
|
"maskSecrets": true
|
|
423
420
|
}
|
|
424
421
|
},
|
|
425
|
-
"files": ["config/**/*.{yaml,yml,json,toml,ini}", ".env"
|
|
426
|
-
"exclude": ["**/node_modules/**"]
|
|
422
|
+
"files": ["config/**/*.{yaml,yml,json,toml,ini}", ".env"]
|
|
427
423
|
}
|
|
428
424
|
```
|
|
429
425
|
|
|
430
426
|
```bash
|
|
431
427
|
flecto watch --profile dev
|
|
432
428
|
flecto ci --profile ci
|
|
433
|
-
flecto doctor
|
|
434
429
|
```
|
|
435
430
|
|
|
436
|
-
CLI flags
|
|
431
|
+
Explicit CLI flags win over profiles, which win over `defaults`.
|
|
432
|
+
→ **[Full configuration reference](docs/configuration.md)**
|
|
437
433
|
|
|
438
434
|
---
|
|
439
435
|
|
|
440
|
-
##
|
|
441
|
-
|
|
442
|
-
### Compact (default)
|
|
443
|
-
|
|
444
|
-
```
|
|
445
|
-
[HH:MM:SS] <filepath> — N changes
|
|
446
|
-
~ path: before → after (yellow — value changed)
|
|
447
|
-
+ path: value (green — key added)
|
|
448
|
-
- path: value (red — key removed)
|
|
449
|
-
```
|
|
450
|
-
|
|
451
|
-
### Verbose (`--mode verbose`)
|
|
436
|
+
## Commands
|
|
452
437
|
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
438
|
+
| Command | What it does |
|
|
439
|
+
|---|---|
|
|
440
|
+
| `flecto watch [files...]` | Watch for changes and print them as they happen |
|
|
441
|
+
| `flecto watch --snapshot` | Save the current state as a baseline |
|
|
442
|
+
| `flecto watch --diff` | Compare against the baseline and exit |
|
|
443
|
+
| `flecto ci [files...]` | One-shot check with a gate-able exit code |
|
|
444
|
+
| `flecto compare <fileA> <fileB>` | Diff two files against each other (`fileA` is the baseline) |
|
|
445
|
+
| `flecto plan <planFiles...>` | Review `terraform show -json` output and gate on it |
|
|
446
|
+
| `flecto history [files...]` | Summarize drift across local snapshots |
|
|
447
|
+
| `flecto report [files...]` | Render that history as a self-contained HTML file |
|
|
448
|
+
| `flecto policies add <name>` | Install a pack from an `flecto-pack-*` npm package |
|
|
449
|
+
| `flecto policies list` | List available policy packs |
|
|
450
|
+
| `flecto policies test <dir>` | Assert pack and plugin findings from fixtures |
|
|
451
|
+
| `flecto init` | Create a `.flectorc` from detected stack signals |
|
|
452
|
+
| `flecto doctor` | Check setup, config, and environment |
|
|
453
|
+
|
|
454
|
+
→ **[Every flag, every command](docs/cli-reference.md)**
|
|
459
455
|
|
|
460
456
|
---
|
|
461
457
|
|
|
462
|
-
##
|
|
458
|
+
## Documentation
|
|
463
459
|
|
|
464
|
-
|
|
|
460
|
+
| Guide | Covers |
|
|
465
461
|
|---|---|
|
|
466
|
-
|
|
|
467
|
-
|
|
|
468
|
-
|
|
|
469
|
-
|
|
|
470
|
-
|
|
|
462
|
+
| **[CLI reference](docs/cli-reference.md)** | Every command, flag, and exit code |
|
|
463
|
+
| **[Configuration](docs/configuration.md)** | `.flectorc`, profiles, ignore patterns, array identity, masking |
|
|
464
|
+
| **[Encrypted files](docs/encrypted-files.md)** | SOPS and age: what is detected, what is reported, why nothing is decrypted |
|
|
465
|
+
| **[CI](docs/ci.md)** | Baselines, fail triggers, output formats, the bundled GitHub Actions |
|
|
466
|
+
| **[Kubernetes](docs/kubernetes.md)** | Diffing rendered Helm/Kustomize manifests before they reach a cluster |
|
|
467
|
+
| **[Terraform plans](docs/terraform.md)** | Reviewing `terraform show -json` output and the `terraform` pack |
|
|
468
|
+
| **[Webhooks and commands](docs/webhooks.md)** | Envelope shape, delivery modes, command environment |
|
|
469
|
+
| **[Policy packs](docs/policy-packs.md)** | Writing declarative rules |
|
|
470
|
+
| **[Plugins](docs/plugins.md)** · **[Cookbook](docs/plugin-cookbook.md)** | Rules that need real code |
|
|
471
|
+
| **[Troubleshooting](docs/troubleshooting.md)** | When something doesn't behave |
|
|
472
|
+
| **[Changelog](CHANGELOG.md)** | Release history and migration notes |
|
|
471
473
|
|
|
472
474
|
---
|
|
473
475
|
|
|
474
476
|
## How it works
|
|
475
477
|
|
|
476
|
-
1. **
|
|
477
|
-
2. **
|
|
478
|
-
3. **
|
|
479
|
-
4. **
|
|
480
|
-
5. **
|
|
481
|
-
6. **
|
|
482
|
-
|
|
483
|
-
---
|
|
484
|
-
|
|
485
|
-
## Contributing
|
|
486
|
-
|
|
487
|
-
Flecto is open source. See [CONTRIBUTING.md](CONTRIBUTING.md) for setup, PR rules, and review expectations.
|
|
488
|
-
Please read the [Code of Conduct](CODE_OF_CONDUCT.md) and [Security policy](SECURITY.md).
|
|
478
|
+
1. **Parse** — format detected by extension or dotenv naming → structured values
|
|
479
|
+
2. **Watch** — [chokidar](https://github.com/paulmillr/chokidar) with debounce
|
|
480
|
+
3. **Diff** — semantic tree comparison with ignore rules and array identity
|
|
481
|
+
4. **Evaluate** — policy packs and plugins → severity-tagged findings
|
|
482
|
+
5. **Emit** — a versioned envelope (`schema_version: "2.0"`)
|
|
483
|
+
6. **Deliver** — terminal output, shell command, webhook, or CI annotations
|
|
489
484
|
|
|
490
|
-
|
|
485
|
+
Flecto runs entirely on your machine. Snapshots are local files, and nothing
|
|
486
|
+
leaves the process unless you configure a webhook or command.
|
|
491
487
|
|
|
492
488
|
---
|
|
493
489
|
|
|
494
|
-
##
|
|
495
|
-
|
|
496
|
-
If Flecto helps your team catch a risky config change — **[star the repo](https://github.com/myselfsiddharth/Flecto)** so more people can find it.
|
|
497
|
-
|
|
498
|
-
---
|
|
490
|
+
## Project
|
|
499
491
|
|
|
500
|
-
|
|
492
|
+
- **Questions and ideas** — [Discussions](https://github.com/myselfsiddharth/Flecto/discussions)
|
|
493
|
+
- **Bugs and requests** — [Issues](https://github.com/myselfsiddharth/Flecto/issues)
|
|
494
|
+
- **Contributing** — [CONTRIBUTING.md](CONTRIBUTING.md) · [Code of Conduct](CODE_OF_CONDUCT.md)
|
|
495
|
+
- **Security** — [SECURITY.md](SECURITY.md), private disclosure only
|
|
496
|
+
- **Roadmap** — [Milestones](https://github.com/myselfsiddharth/Flecto/milestones)
|
|
501
497
|
|
|
502
|
-
|
|
498
|
+
Released under the [MIT License](LICENSE).
|