flecto 2.1.0 → 3.0.1
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 +464 -1
- package/README.md +348 -304
- package/index.js +586 -61
- 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 +173 -22
- 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,47 @@
|
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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.
|
|
32
|
+
| Reviewing config without Flecto | With Flecto |
|
|
33
|
+
|---|---|
|
|
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 |
|
|
46
37
|
|
|
47
|
-
|
|
38
|
+
The same engine reads whatever your change actually lives in:
|
|
48
39
|
|
|
49
|
-
|
|
|
40
|
+
| You are reviewing | Flecto reads |
|
|
50
41
|
|---|---|
|
|
51
|
-
|
|
|
52
|
-
|
|
|
53
|
-
|
|
|
42
|
+
| App config — YAML, JSON, TOML, INI, dotenv | the files directly |
|
|
43
|
+
| A Terraform change | `terraform show -json` output, via `flecto plan` |
|
|
44
|
+
| A Kubernetes change | rendered manifests from `helm`, `kustomize`, or anything else |
|
|
45
|
+
| A SOPS-encrypted file | its structure and recipients — **never its plaintext** |
|
|
46
|
+
|
|
47
|
+
It never invokes `terraform`, `helm`, `kustomize`, `sops`, or `age`, so nothing
|
|
48
|
+
extra has to exist on the CI runner.
|
|
54
49
|
|
|
55
50
|
---
|
|
56
51
|
|
|
@@ -60,358 +55,408 @@ upcoming v2.1 behavior changes.
|
|
|
60
55
|
npm install -g flecto
|
|
61
56
|
```
|
|
62
57
|
|
|
63
|
-
Requires Node.js 20.19.0
|
|
58
|
+
Requires **Node.js 20.19.0+**. Verify:
|
|
64
59
|
|
|
65
60
|
```bash
|
|
66
61
|
flecto --version
|
|
67
62
|
flecto doctor
|
|
68
63
|
```
|
|
69
64
|
|
|
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.
|
|
65
|
+
Prefer not to install globally? Every example below works with
|
|
66
|
+
`npx --yes flecto@3` instead of `flecto`.
|
|
83
67
|
|
|
84
68
|
---
|
|
85
69
|
|
|
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
|
-
---
|
|
70
|
+
## Quick start
|
|
97
71
|
|
|
98
|
-
|
|
72
|
+
A complete walkthrough, start to finish. Copy-paste it anywhere.
|
|
99
73
|
|
|
100
|
-
|
|
74
|
+
**1. Create a config file to track.**
|
|
101
75
|
|
|
102
76
|
```bash
|
|
103
|
-
flecto
|
|
77
|
+
mkdir flecto-demo && cd flecto-demo && mkdir config
|
|
78
|
+
cat > config/prod.yaml <<'EOF'
|
|
79
|
+
database:
|
|
80
|
+
host: db.internal
|
|
81
|
+
pool_size: 5
|
|
82
|
+
ssl: true
|
|
83
|
+
logging:
|
|
84
|
+
level: info
|
|
85
|
+
debug: false
|
|
86
|
+
EOF
|
|
104
87
|
```
|
|
105
88
|
|
|
106
|
-
|
|
89
|
+
**2. Save it as your baseline.**
|
|
107
90
|
|
|
108
91
|
```bash
|
|
109
|
-
flecto watch config/prod.yaml --
|
|
92
|
+
flecto watch config/prod.yaml --snapshot
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
✓ Snapshot saved: /path/to/flecto-demo/.flecto-snapshots/4b8cbbd70d1832a2.json
|
|
110
97
|
```
|
|
111
98
|
|
|
112
|
-
|
|
99
|
+
**3. Make the kind of edit that causes incidents.**
|
|
113
100
|
|
|
114
101
|
```bash
|
|
115
|
-
|
|
102
|
+
cat > config/prod.yaml <<'EOF'
|
|
103
|
+
database:
|
|
104
|
+
host: db.internal
|
|
105
|
+
pool_size: 20
|
|
106
|
+
ssl: true
|
|
107
|
+
logging:
|
|
108
|
+
level: info
|
|
109
|
+
debug: true
|
|
110
|
+
EOF
|
|
116
111
|
```
|
|
117
112
|
|
|
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
|
|
113
|
+
**4. Ask what changed.**
|
|
126
114
|
|
|
127
115
|
```bash
|
|
128
|
-
flecto watch .
|
|
116
|
+
flecto watch config/prod.yaml --diff
|
|
129
117
|
```
|
|
130
118
|
|
|
131
|
-
|
|
119
|
+
```
|
|
120
|
+
/path/to/flecto-demo/config/prod.yaml — 2 changes from snapshot:
|
|
121
|
+
~ database.pool_size: 5 → 20
|
|
122
|
+
~ logging.debug: false → true
|
|
123
|
+
```
|
|
132
124
|
|
|
133
|
-
|
|
125
|
+
Two sentences instead of a diff you have to interpret. Now let Flecto judge it:
|
|
134
126
|
|
|
135
127
|
```bash
|
|
136
|
-
flecto
|
|
137
|
-
--webhook https://hooks.example.com/notify \
|
|
138
|
-
--webhook-header "Authorization: Bearer TOKEN"
|
|
128
|
+
flecto ci config/prod.yaml --format github-annotations
|
|
139
129
|
```
|
|
140
130
|
|
|
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
|
-
}
|
|
131
|
+
```
|
|
132
|
+
::warning title=flecto changed::database.pool_size
|
|
133
|
+
::warning title=flecto changed::logging.debug
|
|
134
|
+
::warning title=flecto policy pool-size-jump [default]::database.pool_size: Pool size increased from 5 to 20 (>=2x).
|
|
135
|
+
::error title=flecto policy dangerous-toggle-enabled [default]::logging.debug: Potentially dangerous toggle enabled.
|
|
163
136
|
```
|
|
164
137
|
|
|
165
|
-
|
|
138
|
+
Exit code `1`. In CI, that's a failed build — before the change ships.
|
|
166
139
|
|
|
167
|
-
|
|
140
|
+
**5. Watch it live.** Leave this running and edit the file in another window:
|
|
168
141
|
|
|
169
142
|
```bash
|
|
170
|
-
flecto
|
|
143
|
+
flecto watch config/prod.yaml
|
|
171
144
|
```
|
|
172
145
|
|
|
173
|
-
```
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
"severityRemap": { "pool-size-jump": "error" },
|
|
183
|
-
"maskSecrets": true
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
}
|
|
146
|
+
```
|
|
147
|
+
flecto watching /path/to/flecto-demo/config/prod.yaml
|
|
148
|
+
Press Ctrl+C to stop.
|
|
149
|
+
|
|
150
|
+
[18:24:48] /path/to/flecto-demo/config/prod.yaml — 2 changes
|
|
151
|
+
~ database.pool_size: 5 → 20
|
|
152
|
+
~ logging.debug: false → true
|
|
153
|
+
! policy(warn) [default] database.pool_size: Pool size increased from 5 to 20 (>=2x).
|
|
154
|
+
! policy(error) [default] logging.debug: Potentially dangerous toggle enabled.
|
|
187
155
|
```
|
|
188
156
|
|
|
189
|
-
|
|
190
|
-
Custom packs: `policies/<id>.json`. Plugins: local ESM exporting `evaluate(changes, ctx)`.
|
|
157
|
+
That's the whole product. Everything below is depth.
|
|
191
158
|
|
|
192
|
-
|
|
159
|
+
---
|
|
193
160
|
|
|
194
|
-
|
|
195
|
-
`match.path`, `afterEquals`, and `numericJump`, packs can use:
|
|
161
|
+
## What you can do with it
|
|
196
162
|
|
|
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.
|
|
163
|
+
### Catch risky changes before they merge
|
|
205
164
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
165
|
+
Add one step to your workflow and risky config edits show up as annotations on
|
|
166
|
+
the pull request:
|
|
167
|
+
|
|
168
|
+
```yaml
|
|
169
|
+
permissions:
|
|
170
|
+
contents: read
|
|
171
|
+
|
|
172
|
+
steps:
|
|
173
|
+
- uses: actions/checkout@v7
|
|
174
|
+
with:
|
|
175
|
+
fetch-depth: 2
|
|
176
|
+
- uses: myselfsiddharth/Flecto/.github/actions/flecto-ci@main
|
|
177
|
+
with:
|
|
178
|
+
targets: config/**/*.{yaml,yml,json,toml,ini}
|
|
179
|
+
snapshot-ref: HEAD~1
|
|
219
180
|
```
|
|
220
181
|
|
|
221
|
-
|
|
222
|
-
|
|
182
|
+
Prefer a summary nobody can miss? `--format pr-comment` renders the changes and
|
|
183
|
+
policy findings as markdown and, when you opt in with `--pr-comment-post` inside
|
|
184
|
+
a GitHub PR run, keeps **one** sticky comment up to date instead of adding a new
|
|
185
|
+
one per push. Without that flag it just prints the markdown, so it can't post
|
|
186
|
+
from your laptop.
|
|
223
187
|
|
|
224
|
-
|
|
188
|
+
The `flecto-pr-risk` Action is that, packaged — the whole adoption is one
|
|
189
|
+
`uses:`, with the baseline resolved from the pull request rather than `HEAD~1`:
|
|
225
190
|
|
|
226
|
-
```
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
}
|
|
191
|
+
```yaml
|
|
192
|
+
permissions:
|
|
193
|
+
contents: read
|
|
194
|
+
pull-requests: write
|
|
195
|
+
|
|
196
|
+
steps:
|
|
197
|
+
- uses: actions/checkout@v7
|
|
198
|
+
with:
|
|
199
|
+
fetch-depth: 0
|
|
200
|
+
- uses: myselfsiddharth/Flecto/.github/actions/flecto-pr-risk@main
|
|
237
201
|
```
|
|
238
202
|
|
|
239
|
-
|
|
203
|
+
A fork's pull request gets a read-only token, so the comment is skipped with a
|
|
204
|
+
warning there — the check itself still runs and still fails on risky changes.
|
|
205
|
+
|
|
206
|
+
Works on any CI runner — it's a plain CLI with meaningful exit codes.
|
|
207
|
+
→ **[CI guide](docs/ci.md)**
|
|
240
208
|
|
|
241
|
-
|
|
209
|
+
### Trigger automation on change
|
|
242
210
|
|
|
243
|
-
|
|
211
|
+
Restart a service, reload a process, or notify an endpoint whenever config moves:
|
|
244
212
|
|
|
245
213
|
```bash
|
|
246
|
-
flecto
|
|
247
|
-
|
|
214
|
+
flecto watch .env --command "docker-compose restart app"
|
|
215
|
+
|
|
216
|
+
flecto watch config/prod.yaml \
|
|
217
|
+
--webhook https://hooks.example.com/notify \
|
|
218
|
+
--delivery-mode at-least-once
|
|
248
219
|
```
|
|
249
220
|
|
|
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.
|
|
221
|
+
Changes arrive as a versioned JSON envelope, and `at-least-once` persists and
|
|
222
|
+
retries failed deliveries.
|
|
255
223
|
|
|
256
|
-
|
|
224
|
+
Posting straight to chat needs no receiver of your own — `--webhook-format`
|
|
225
|
+
shapes the body for Slack, Discord, or Teams, colored by the highest policy
|
|
226
|
+
severity:
|
|
257
227
|
|
|
258
228
|
```bash
|
|
259
|
-
flecto watch config/
|
|
229
|
+
flecto watch config/prod.yaml \
|
|
230
|
+
--webhook "https://hooks.slack.com/services/T000/B000/XXXX" \
|
|
231
|
+
--webhook-format slack
|
|
260
232
|
```
|
|
261
233
|
|
|
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:
|
|
234
|
+
→ **[Webhooks and commands](docs/webhooks.md)**
|
|
265
235
|
|
|
266
|
-
|
|
267
|
-
flecto watch config/services.yaml --array-id-key serviceKey
|
|
268
|
-
```
|
|
236
|
+
### Compare two environments
|
|
269
237
|
|
|
270
|
-
|
|
238
|
+
"Works in staging, fails in prod" is usually one key apart:
|
|
271
239
|
|
|
272
240
|
```bash
|
|
273
|
-
flecto
|
|
241
|
+
flecto compare config/prod.yaml config/staging.yaml
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
```
|
|
245
|
+
"+" exists only in the compared file, "-" only in the baseline, "~" differs
|
|
246
|
+
/path/to/config/staging.yaml — 2 changes from /path/to/config/prod.yaml:
|
|
247
|
+
- only_in_prod: true
|
|
248
|
+
~ database.pool_size: 5 → 20
|
|
249
|
+
! policy(warn) [default] database.pool_size: Pool size increased from 5 to 20 (>=2x).
|
|
274
250
|
```
|
|
275
251
|
|
|
276
|
-
|
|
277
|
-
|
|
252
|
+
The first file is the baseline, the files don't have to share a format, and
|
|
253
|
+
`--format json` gives you the same output `flecto ci` produces.
|
|
254
|
+
→ **[CLI reference](docs/cli-reference.md#flecto-compare-filea-fileb)**
|
|
278
255
|
|
|
279
|
-
###
|
|
256
|
+
### Track drift over time
|
|
280
257
|
|
|
281
258
|
```bash
|
|
282
|
-
flecto
|
|
283
|
-
--command "make reload" \
|
|
284
|
-
--webhook https://hooks.example.com/notify
|
|
259
|
+
flecto history config/prod.yaml --limit 10
|
|
285
260
|
```
|
|
286
261
|
|
|
287
|
-
|
|
262
|
+
Snapshots stay on your machine in `.flecto-snapshots/`. Nothing is uploaded and
|
|
263
|
+
no account is required. → **[CLI reference](docs/cli-reference.md#flecto-history-files)**
|
|
264
|
+
|
|
265
|
+
### Share what changed before the incident
|
|
288
266
|
|
|
289
267
|
```bash
|
|
290
|
-
flecto
|
|
291
|
-
--webhook https://hooks.example.com/notify \
|
|
292
|
-
--delivery-mode at-least-once \
|
|
293
|
-
--on-alert-failure retry
|
|
268
|
+
flecto report --limit 20 --mask-secrets --output drift.html
|
|
294
269
|
```
|
|
295
270
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
271
|
+
One HTML file from that same local history: a timeline per file, every change
|
|
272
|
+
with its UTC timestamp, and policy findings grouped by severity. Fully
|
|
273
|
+
self-contained — inline styles, no fonts, no CDN scripts, no analytics — so you
|
|
274
|
+
can attach it to an incident thread and it renders offline. No server and no
|
|
275
|
+
account, same as everything else here.
|
|
276
|
+
→ **[CLI reference](docs/cli-reference.md#flecto-report-files)**
|
|
300
277
|
|
|
301
|
-
|
|
278
|
+
### Review a Kubernetes change before it reaches a cluster
|
|
302
279
|
|
|
303
|
-
|
|
280
|
+
ArgoCD, Flux, and `helm diff` compare the cluster to the repo — which needs a
|
|
281
|
+
cluster, and an apply that already happened. Flecto compares the manifests *this
|
|
282
|
+
pull request would produce* against the ones `main` produces:
|
|
304
283
|
|
|
305
284
|
```bash
|
|
306
|
-
|
|
307
|
-
flecto
|
|
308
|
-
flecto history config/prod.yaml --limit 10
|
|
285
|
+
helm template api ./charts/api -f values/prod.yaml > /tmp/head.yaml
|
|
286
|
+
flecto compare /tmp/base.yaml /tmp/head.yaml --policies kubernetes --fail-on error
|
|
309
287
|
```
|
|
310
288
|
|
|
311
|
-
|
|
289
|
+
```
|
|
290
|
+
~ Service/prod/api.spec.type: "ClusterIP" → "LoadBalancer"
|
|
291
|
+
! policy(error) [kubernetes] Service type is LoadBalancer, which exposes the
|
|
292
|
+
workload outside the cluster. Confirm the exposure is intended.
|
|
293
|
+
```
|
|
312
294
|
|
|
313
|
-
|
|
295
|
+
Multi-document YAML is keyed by `kind/namespace/name`, so findings name the
|
|
296
|
+
resource. Flecto never runs `helm` or `kustomize` — you render, it diffs, so any
|
|
297
|
+
renderer works and no binary is needed in CI.
|
|
298
|
+
→ **[Kubernetes](docs/kubernetes.md)**
|
|
314
299
|
|
|
315
|
-
|
|
300
|
+
### Read a Terraform plan in plain English
|
|
316
301
|
|
|
317
|
-
|
|
302
|
+
`terraform plan` output is precise and long. Flecto turns it into the handful of
|
|
303
|
+
lines a reviewer actually needs to argue about:
|
|
318
304
|
|
|
319
305
|
```bash
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
--format github-annotations \
|
|
323
|
-
--fail-on "changed,policy,error"
|
|
306
|
+
terraform show -json plan.tfplan > plan.json
|
|
307
|
+
flecto plan plan.json --fail-on error
|
|
324
308
|
```
|
|
325
309
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
310
|
+
```
|
|
311
|
+
plan.json — plan format 1.2
|
|
312
|
+
Plan: 0 to add, 1 to change, 0 to destroy, 1 to replace.
|
|
313
|
+
~ aws_security_group.web.ingress[0].cidr_blocks[0]: "10.0.0.0/8" → "0.0.0.0/0"
|
|
314
|
+
- aws_db_instance.main.#action: "replace" [terraform will destroy and recreate aws_db_instance.main]
|
|
315
|
+
~ aws_db_instance.main.password: "(sensitive value)" → "(sensitive value)" [sensitive]
|
|
316
|
+
! policy(error) [terraform] …cidr_blocks[0]: Security group ingress will accept
|
|
317
|
+
traffic from the whole internet (0.0.0.0/0). Restrict the source to a known CIDR…
|
|
318
|
+
! policy(error) [terraform] …#action: Terraform will destroy a stateful resource.
|
|
319
|
+
Its data does not survive. Take a final snapshot, or add a prevent_destroy…
|
|
320
|
+
```
|
|
330
321
|
|
|
331
|
-
|
|
322
|
+
A **replace reads as a removal**, not a benign update — a recreated database
|
|
323
|
+
should never look like a config tweak. Values Terraform marks sensitive are
|
|
324
|
+
redacted during parsing, before the policy engine or any formatter sees them, and
|
|
325
|
+
`after_unknown` renders as `(known after apply)` rather than `null`.
|
|
332
326
|
|
|
333
|
-
|
|
327
|
+
**Flecto never runs `terraform`** — you produce the JSON, it reads it, so nothing
|
|
328
|
+
extra has to exist on the CI runner.
|
|
329
|
+
→ **[Terraform plans](docs/terraform.md)**
|
|
334
330
|
|
|
335
|
-
|
|
336
|
-
permissions:
|
|
337
|
-
contents: read
|
|
331
|
+
### Encode your own rules
|
|
338
332
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
333
|
+
Beyond the built-in packs, write rules as declarative JSON or YAML — no code:
|
|
334
|
+
|
|
335
|
+
```json
|
|
336
|
+
{
|
|
337
|
+
"id": "risky-feature-enable",
|
|
338
|
+
"severity": "error",
|
|
339
|
+
"allOf": [
|
|
340
|
+
{ "match": { "pathPrefix": "features." } },
|
|
341
|
+
{ "afterTruthy": true }
|
|
342
|
+
]
|
|
343
|
+
}
|
|
347
344
|
```
|
|
348
345
|
|
|
349
|
-
|
|
346
|
+
For anything a predicate can't express, a local ESM plugin exporting
|
|
347
|
+
`evaluate(changes, ctx)` gets the full change set.
|
|
348
|
+
→ **[Writing policy packs](docs/policy-packs.md)** · **[Plugins](docs/plugins.md)**
|
|
350
349
|
|
|
351
|
-
|
|
352
|
-
|---|---|---|
|
|
353
|
-
| `targets` | `config/**/*.{yaml,yml,json,toml,ini}` | Whitespace-separated paths or glob patterns to check |
|
|
354
|
-
| `fail-on` | `policy,error` | Comma-separated events that fail the job |
|
|
355
|
-
| `policies` | _(empty)_ | Comma-separated policy pack IDs; omit to use `.flectorc` / Flecto defaults |
|
|
356
|
-
| `profile` | _(empty)_ | Optional `.flectorc` profile |
|
|
357
|
-
| `format` | `github-annotations` | Flecto output format |
|
|
358
|
-
| `snapshot-ref` | `HEAD~1` | Git ref or snapshot file used as the baseline |
|
|
359
|
-
| `node-version` | `20` | Node.js version used to run Flecto |
|
|
350
|
+
### Cut the noise
|
|
360
351
|
|
|
361
|
-
|
|
352
|
+
```bash
|
|
353
|
+
flecto watch config/prod.yaml --ignore "updated_at,**.meta.timestamp"
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
Arrays of objects are matched by `id` or `name`, so reordering a list of named
|
|
357
|
+
services doesn't read as a wall of changes.
|
|
358
|
+
→ **[Configuration](docs/configuration.md)**
|
|
362
359
|
|
|
363
360
|
---
|
|
364
361
|
|
|
365
|
-
## Built-in policy
|
|
362
|
+
## Built-in policy packs
|
|
366
363
|
|
|
367
|
-
|
|
364
|
+
| Pack | Catches |
|
|
365
|
+
|---|---|
|
|
366
|
+
| `default` | Secret-like keys added or changed, secret-shaped *values* under any key, dangerous toggles, pool-size jumps |
|
|
367
|
+
| `strict-prod` | The same ground, with production-grade severities and matching |
|
|
368
|
+
| `compose` | Privileged services, host networking, Docker socket mounts, sensitive bind mounts |
|
|
369
|
+
| `kubernetes` | Privileged containers, host namespaces, weakened `runAsNonRoot`, added `SYS_ADMIN`, unpinned images, replica jumps, dropped limits, `LoadBalancer`/`NodePort` exposure |
|
|
370
|
+
| `node-runtime` | Dropped engine requirements, TLS verification bypasses, debug/inspector flags |
|
|
371
|
+
| `terraform` | Replaced and destroyed stateful resources, ingress opened to `0.0.0.0/0`, IAM wildcards, public S3, capacity jumps |
|
|
372
|
+
| `sops` | Decryption recipients added or removed, a MAC that moved on its own, a file that stopped being encrypted |
|
|
368
373
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
374
|
+
```bash
|
|
375
|
+
flecto policies list # see what resolves here, built-in and local
|
|
376
|
+
flecto ci "config/**/*.yaml" --policies "default,strict-prod" --fail-on policy
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
Pass files or glob patterns, quoted so your shell doesn't expand them first — a
|
|
380
|
+
bare directory is not a valid target.
|
|
381
|
+
|
|
382
|
+
A local `policies/<id>.json` overrides the built-in pack of the same id, and
|
|
383
|
+
`severityRemap` raises or silences individual rules per profile without forking
|
|
384
|
+
anything. → **[Policy packs](docs/policy-packs.md)**
|
|
385
|
+
|
|
386
|
+
Community packs ship on npm as `flecto-pack-<id>` packages — a package name and
|
|
387
|
+
one declarative JSON file, nothing else:
|
|
373
388
|
|
|
374
|
-
|
|
389
|
+
```bash
|
|
390
|
+
npm install --save-dev flecto-pack-deployment-safety
|
|
391
|
+
flecto policies add deployment-safety
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
`policies add` validates the pack, writes it to `policies/deployment-safety.json`,
|
|
395
|
+
and runs no code from the package. →
|
|
396
|
+
**[Installing community packs](docs/policy-packs.md#installing-a-community-pack)**
|
|
375
397
|
|
|
376
398
|
---
|
|
377
399
|
|
|
378
|
-
##
|
|
400
|
+
## Supported formats
|
|
379
401
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
402
|
+
| Format | Extensions |
|
|
403
|
+
|---|---|
|
|
404
|
+
| JSON | `.json` |
|
|
405
|
+
| YAML | `.yaml`, `.yml` |
|
|
406
|
+
| TOML | `.toml` |
|
|
407
|
+
| INI | `.ini` |
|
|
408
|
+
| dotenv | `.env`, `.env.*`, `*.env` |
|
|
409
|
+
| age (armored) | `.age`, or any file whose contents are one armored blob |
|
|
410
|
+
|
|
411
|
+
Terraform plan JSON (`terraform show -json`) is read by **`flecto plan`**, which
|
|
412
|
+
applies Terraform's own sensitivity marking. Point `plan` at it rather than `ci`
|
|
413
|
+
or `watch` — those treat it as ordinary JSON and will print values Terraform
|
|
414
|
+
marks sensitive ([#113](https://github.com/myselfsiddharth/Flecto/issues/113)).
|
|
415
|
+
|
|
416
|
+
Multi-document YAML (`---`-separated, the usual shape of a Kubernetes manifest)
|
|
417
|
+
is supported. Each document is diffed under its own key — `kind/name` for
|
|
418
|
+
Kubernetes-shaped documents, so a document inserted at the top of the file does
|
|
419
|
+
not renumber every other path. →
|
|
420
|
+
**[Multi-document YAML](docs/configuration.md#multi-document-yaml)**
|
|
384
421
|
|
|
385
422
|
---
|
|
386
423
|
|
|
387
|
-
##
|
|
424
|
+
## Encrypted files
|
|
388
425
|
|
|
389
|
-
|
|
390
|
-
|
|
426
|
+
A `sops`- or age-encrypted file is detected from its **contents**, and diffed
|
|
427
|
+
structurally:
|
|
428
|
+
|
|
429
|
+
```
|
|
430
|
+
+ cache: {"ttl_seconds":300}
|
|
431
|
+
~ database.password: <encrypted value changed>
|
|
432
|
+
+ sops.age.age1exampleexample…: {"recipient":"age1exampleexample…","enc":"<encrypted value>"}
|
|
391
433
|
```
|
|
392
434
|
|
|
393
|
-
|
|
435
|
+
You get keys added and removed, which encrypted values moved, and — the useful
|
|
436
|
+
part — who can decrypt the file. A recipient added is a genuine security event
|
|
437
|
+
and the `sops` pack raises it as one.
|
|
438
|
+
|
|
439
|
+
**Flecto never decrypts.** It never shells out to `sops` or `age`, never reads a
|
|
440
|
+
key file or agent socket, and never prints ciphertext — not even without
|
|
441
|
+
`--mask-secrets`. Ciphertext is replaced with an opaque sentinel in the parser,
|
|
442
|
+
so no diff, snapshot, webhook, or report can carry it. →
|
|
443
|
+
**[Encrypted files](docs/encrypted-files.md)**
|
|
394
444
|
|
|
395
445
|
---
|
|
396
446
|
|
|
397
|
-
##
|
|
447
|
+
## Configuration
|
|
448
|
+
|
|
449
|
+
Most teams commit a `.flectorc` so local runs and CI agree:
|
|
398
450
|
|
|
399
451
|
```bash
|
|
400
452
|
flecto init
|
|
401
453
|
```
|
|
402
454
|
|
|
403
|
-
Looks for `.flectorc`, `.flectorc.json`, `.flectorc.yaml`, or `.flectorc.yml`.
|
|
404
|
-
|
|
405
455
|
```json
|
|
406
456
|
{
|
|
407
457
|
"defaults": {
|
|
408
|
-
"mode": "compact",
|
|
409
|
-
"interval": 100,
|
|
410
|
-
"ignore": ["**.updated_at"],
|
|
411
|
-
"deliveryMode": "best-effort",
|
|
412
|
-
"onAlertFailure": "warn",
|
|
413
458
|
"policies": ["default"],
|
|
414
|
-
"
|
|
459
|
+
"ignore": ["**.updated_at"]
|
|
415
460
|
},
|
|
416
461
|
"profiles": {
|
|
417
462
|
"dev": { "mode": "verbose" },
|
|
@@ -422,81 +467,80 @@ Looks for `.flectorc`, `.flectorc.json`, `.flectorc.yaml`, or `.flectorc.yml`.
|
|
|
422
467
|
"maskSecrets": true
|
|
423
468
|
}
|
|
424
469
|
},
|
|
425
|
-
"files": ["config/**/*.{yaml,yml,json,toml,ini}", ".env"
|
|
426
|
-
"exclude": ["**/node_modules/**"]
|
|
470
|
+
"files": ["config/**/*.{yaml,yml,json,toml,ini}", ".env"]
|
|
427
471
|
}
|
|
428
472
|
```
|
|
429
473
|
|
|
430
474
|
```bash
|
|
431
475
|
flecto watch --profile dev
|
|
432
476
|
flecto ci --profile ci
|
|
433
|
-
flecto doctor
|
|
434
477
|
```
|
|
435
478
|
|
|
436
|
-
CLI flags
|
|
479
|
+
Explicit CLI flags win over profiles, which win over `defaults`.
|
|
480
|
+
→ **[Full configuration reference](docs/configuration.md)**
|
|
437
481
|
|
|
438
482
|
---
|
|
439
483
|
|
|
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`)
|
|
484
|
+
## Commands
|
|
452
485
|
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
486
|
+
| Command | What it does |
|
|
487
|
+
|---|---|
|
|
488
|
+
| `flecto watch [files...]` | Watch for changes and print them as they happen |
|
|
489
|
+
| `flecto watch --snapshot` | Save the current state as a baseline |
|
|
490
|
+
| `flecto watch --diff` | Compare against the baseline and exit |
|
|
491
|
+
| `flecto ci [files...]` | One-shot check with a gate-able exit code |
|
|
492
|
+
| `flecto compare <fileA> <fileB>` | Diff two files against each other (`fileA` is the baseline) |
|
|
493
|
+
| `flecto plan <planFiles...>` | Review `terraform show -json` output and gate on it |
|
|
494
|
+
| `flecto history [files...]` | Summarize drift across local snapshots |
|
|
495
|
+
| `flecto report [files...]` | Render that history as a self-contained HTML file |
|
|
496
|
+
| `flecto policies add <name>` | Install a pack from an `flecto-pack-*` npm package |
|
|
497
|
+
| `flecto policies list` | List available policy packs |
|
|
498
|
+
| `flecto policies test <dir>` | Assert pack and plugin findings from fixtures |
|
|
499
|
+
| `flecto init` | Create a `.flectorc` from detected stack signals |
|
|
500
|
+
| `flecto doctor` | Check setup, config, and environment |
|
|
501
|
+
|
|
502
|
+
→ **[Every flag, every command](docs/cli-reference.md)**
|
|
459
503
|
|
|
460
504
|
---
|
|
461
505
|
|
|
462
|
-
##
|
|
506
|
+
## Documentation
|
|
463
507
|
|
|
464
|
-
|
|
|
508
|
+
| Guide | Covers |
|
|
465
509
|
|---|---|
|
|
466
|
-
|
|
|
467
|
-
|
|
|
468
|
-
|
|
|
469
|
-
|
|
|
470
|
-
|
|
|
510
|
+
| **[CLI reference](docs/cli-reference.md)** | Every command, flag, and exit code |
|
|
511
|
+
| **[Configuration](docs/configuration.md)** | `.flectorc`, profiles, ignore patterns, array identity, masking |
|
|
512
|
+
| **[Encrypted files](docs/encrypted-files.md)** | SOPS and age: what is detected, what is reported, why nothing is decrypted |
|
|
513
|
+
| **[CI](docs/ci.md)** | Baselines, fail triggers, output formats, the bundled GitHub Actions |
|
|
514
|
+
| **[Kubernetes](docs/kubernetes.md)** | Diffing rendered Helm/Kustomize manifests before they reach a cluster |
|
|
515
|
+
| **[Terraform plans](docs/terraform.md)** | Reviewing `terraform show -json` output and the `terraform` pack |
|
|
516
|
+
| **[Webhooks and commands](docs/webhooks.md)** | Envelope shape, delivery modes, command environment |
|
|
517
|
+
| **[Policy packs](docs/policy-packs.md)** | Writing declarative rules |
|
|
518
|
+
| **[Plugins](docs/plugins.md)** · **[Cookbook](docs/plugin-cookbook.md)** | Rules that need real code |
|
|
519
|
+
| **[Troubleshooting](docs/troubleshooting.md)** | When something doesn't behave |
|
|
520
|
+
| **[Changelog](CHANGELOG.md)** | Release history and migration notes |
|
|
471
521
|
|
|
472
522
|
---
|
|
473
523
|
|
|
474
524
|
## How it works
|
|
475
525
|
|
|
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).
|
|
526
|
+
1. **Parse** — format detected by extension or dotenv naming → structured values
|
|
527
|
+
2. **Watch** — [chokidar](https://github.com/paulmillr/chokidar) with debounce
|
|
528
|
+
3. **Diff** — semantic tree comparison with ignore rules and array identity
|
|
529
|
+
4. **Evaluate** — policy packs and plugins → severity-tagged findings
|
|
530
|
+
5. **Emit** — a versioned envelope (`schema_version: "2.0"`)
|
|
531
|
+
6. **Deliver** — terminal output, shell command, webhook, or CI annotations
|
|
489
532
|
|
|
490
|
-
|
|
533
|
+
Flecto runs entirely on your machine. Snapshots are local files, and nothing
|
|
534
|
+
leaves the process unless you configure a webhook or command.
|
|
491
535
|
|
|
492
536
|
---
|
|
493
537
|
|
|
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
|
-
---
|
|
538
|
+
## Project
|
|
499
539
|
|
|
500
|
-
|
|
540
|
+
- **Questions and ideas** — [Discussions](https://github.com/myselfsiddharth/Flecto/discussions)
|
|
541
|
+
- **Bugs and requests** — [Issues](https://github.com/myselfsiddharth/Flecto/issues)
|
|
542
|
+
- **Contributing** — [CONTRIBUTING.md](CONTRIBUTING.md) · [Code of Conduct](CODE_OF_CONDUCT.md)
|
|
543
|
+
- **Security** — [SECURITY.md](SECURITY.md), private disclosure only
|
|
544
|
+
- **Roadmap** — [Milestones](https://github.com/myselfsiddharth/Flecto/milestones)
|
|
501
545
|
|
|
502
|
-
|
|
546
|
+
Released under the [MIT License](LICENSE).
|