flecto 3.0.0 → 3.0.2
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 +534 -1
- package/README.md +59 -1
- package/index.js +427 -54
- package/package.json +4 -1
- package/schemas/flecto-policy-pack-2.0.json +2 -0
- package/src/baseline.js +193 -0
- package/src/config.js +463 -19
- package/src/encrypted.js +16 -13
- package/src/packs/github-actions.json +92 -0
- package/src/parser.js +212 -22
- package/src/policy-test.js +5 -1
- package/src/policy.js +96 -23
- package/src/pr-comment.js +53 -87
- package/src/pr-providers.js +261 -0
- package/src/renderer.js +7 -7
- package/src/report.js +39 -1
- package/src/sarif.js +144 -0
- package/src/secrets.js +41 -7
- package/src/suppressions.js +431 -0
- package/src/terraform.js +28 -0
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,506 @@ The format is based on [Keep a Changelog], and this project adheres to
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [3.0.2] - 2026-09-06
|
|
11
|
+
|
|
12
|
+
### Security
|
|
13
|
+
|
|
14
|
+
- **Assessed 2.x against GHSA-wq8m-fc3q-8m5x and corrected the advisory range**
|
|
15
|
+
([#125]). The advisory's own proof-of-concept was run against a clean install
|
|
16
|
+
of every released version: 2.0.0, 2.1.0, and 3.0.0 execute an rc-declared
|
|
17
|
+
plugin; 1.0.x predate the `plugins` option, and 3.0.1 is fixed. So the true
|
|
18
|
+
affected range is `>= 2.0.0, <= 3.0.0`, not the `<= 3.0.0` the draft advisory
|
|
19
|
+
recorded — which wrongly swept in 1.x. The 2.x backport is merged on
|
|
20
|
+
`release/2.x` (2.1.1) and blocks both the exploit and its path-traversal
|
|
21
|
+
variant, but **2.1.1 was never published**, so the highest installable 2.x is
|
|
22
|
+
the still-vulnerable 2.1.0. `SECURITY.md` now says so, and the full matrix and
|
|
23
|
+
publish recommendation are in
|
|
24
|
+
[`docs/ghsa-wq8m-fc3q-8m5x-2x.md`](docs/ghsa-wq8m-fc3q-8m5x-2x.md).
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
|
|
28
|
+
- **Inline suppressions in JSON** ([#158]). `.json` and `.jsonc` are parsed as
|
|
29
|
+
JSONC, so they carry comments — but `flecto-ignore-next-line` was still skipped
|
|
30
|
+
there, and skipped silently: the directive parsed as an ordinary comment, the
|
|
31
|
+
finding fired anyway, and nothing told the author their suppression had been
|
|
32
|
+
ignored. That is the failure mode inline suppressions exist to avoid, pointed
|
|
33
|
+
the wrong way.
|
|
34
|
+
|
|
35
|
+
The JSON resolver reuses the parser's comment stripper rather than recognising
|
|
36
|
+
`//` and block comments a second time — comments are blanked in place, so line
|
|
37
|
+
numbers still line up — and then walks the brace/bracket depth and enclosing
|
|
38
|
+
key stack to the same dotted path the differ reports.
|
|
39
|
+
|
|
40
|
+
Anything inside an **array** is refused, as it already is in YAML: an array
|
|
41
|
+
element's diff path is its index or its `--array-id-key` identity depending on
|
|
42
|
+
how the run is configured, so resolving one would suppress the wrong finding
|
|
43
|
+
under the other. Over-suppression is the dangerous direction for a security
|
|
44
|
+
tool, and the refusal is covered per array mode rather than by one happy path.
|
|
45
|
+
|
|
46
|
+
- **Coverage measurement in CI, focused on the modules where a gap is a security
|
|
47
|
+
question** ([#149]). CI ran `npm test` and `npm run pack:check` and nothing
|
|
48
|
+
else, so "is the plugin-loading path from GHSA-wq8m-fc3q-8m5x covered, and is
|
|
49
|
+
every branch of it covered?" was answered by reading `test/security.test.js`
|
|
50
|
+
and hoping.
|
|
51
|
+
|
|
52
|
+
`npm run coverage` runs the suite under `node --test
|
|
53
|
+
--experimental-test-coverage` — a flag, not a dependency — and prints a report
|
|
54
|
+
for `config.js` (plugin resolution), `policy.js` (pack loading), `secrets.js`,
|
|
55
|
+
`encrypted.js`, `pr-comment.js`, and `pr-providers.js` (token handling), worst
|
|
56
|
+
branch coverage first, with the count of branches that never executed.
|
|
57
|
+
Reporting those separately is the point: one repo-wide average is the number
|
|
58
|
+
that hides them.
|
|
59
|
+
|
|
60
|
+
**No threshold gates the job.** A number chosen before anyone has read the
|
|
61
|
+
report is arbitrary, and the usual outcome is tests written to satisfy the gate
|
|
62
|
+
rather than to find defects. The report prints in the job log, so reading it
|
|
63
|
+
needs no artifact download. The one thing that *does* fail the job is a focused
|
|
64
|
+
module missing from the report — a renamed module would otherwise drop out of
|
|
65
|
+
the table silently, leaving a report that covers less than it claims to.
|
|
66
|
+
|
|
67
|
+
No linter. The style argument is the weak one, the project is consistent
|
|
68
|
+
without it, and a rule set worth having is a separate decision from this one.
|
|
69
|
+
See [security review](docs/security-review.md#knowing-what-has-been-exercised).
|
|
70
|
+
|
|
71
|
+
- `afterAnyMatches`, a policy matcher that applies a regular expression to the
|
|
72
|
+
elements of an array value. `afterMatches` requires a string, so the edit that
|
|
73
|
+
widens a scalar into a list — `runs-on: ubuntu-latest` →
|
|
74
|
+
`runs-on: [self-hosted, linux]` — was invisible to every value predicate: the
|
|
75
|
+
differ reports it as one `changed` event whose `after` is an array and does not
|
|
76
|
+
descend into a type change, so no per-element leaf exists to match either. The
|
|
77
|
+
scan is flat and array-only: a non-array value never matches, non-string
|
|
78
|
+
elements are skipped, and it does not recurse into nested arrays or objects, so
|
|
79
|
+
what a rule matches stays readable from the rule text. `afterMatches` keeps its
|
|
80
|
+
exact meaning, so no existing pack changes behavior. The `github-actions` pack's
|
|
81
|
+
`github-actions-self-hosted-runner` rule now pairs the two in an `anyOf` and
|
|
82
|
+
covers the fourth `runs-on` shape it previously documented as a limitation.
|
|
83
|
+
([#159])
|
|
84
|
+
|
|
85
|
+
- **Merge request comments on GitLab and Bitbucket.** The sticky review comment
|
|
86
|
+
was GitHub-only: `src/pr-comment.js` and both composite actions spoke
|
|
87
|
+
`GITHUB_TOKEN`, `GITHUB_EVENT_PATH`, and the issue-comments API directly, so
|
|
88
|
+
the flagship review experience was unavailable to every team not on GitHub.
|
|
89
|
+
|
|
90
|
+
Delivery is now an adapter (`src/pr-providers.js`); everything upstream of it —
|
|
91
|
+
the differ, the policy engine, the envelope, and the rendered markdown body —
|
|
92
|
+
was already provider-agnostic. The host is detected from CI variables and
|
|
93
|
+
`--pr-provider github|gitlab|bitbucket` forces one. All three upsert a single
|
|
94
|
+
sticky comment by marker, skip the write when the body is unchanged, redact
|
|
95
|
+
the token from error text, and leave the exit code to the diff and policy
|
|
96
|
+
result.
|
|
97
|
+
|
|
98
|
+
**GitLab's `CI_JOB_TOKEN` cannot post merge request notes.** Flecto does not
|
|
99
|
+
attempt it, because the resulting 401 reads like a broken setup rather than a
|
|
100
|
+
missing permission; it names `FLECTO_GITLAB_TOKEN` and the `api` scope
|
|
101
|
+
instead. See [CI](docs/ci.md#providers). ([#138])
|
|
102
|
+
|
|
103
|
+
- Inline suppressions: `# flecto-ignore-next-line <rule> — <reason>` on the line
|
|
104
|
+
above a deliberate finding accepts that one finding in place, the companion to
|
|
105
|
+
the baseline's bulk acceptance. **A reason is mandatory** — a directive without
|
|
106
|
+
one is refused with a pointer to the file and line, never silently applied or
|
|
107
|
+
dropped — so a repo does not accumulate unexplained suppressions. It is scoped
|
|
108
|
+
to the next line and the named rule, and resolves to that line's full key path
|
|
109
|
+
(nesting for YAML, section/table for INI/TOML, flat for dotenv), so a
|
|
110
|
+
suppression on one `pool_size` cannot hide an uncommented `pool_size` elsewhere
|
|
111
|
+
in the file. Works in every commented format Flecto parses — YAML, TOML, INI,
|
|
112
|
+
dotenv, and (since [#158]) JSON. Suppressed findings
|
|
113
|
+
are still surfaced — a count by default, the full list with `--show-suppressed`
|
|
114
|
+
— so the gate stays legible. ([#119])
|
|
115
|
+
|
|
116
|
+
- Adoption baseline for `flecto ci`: `--baseline <file>` gates only on findings
|
|
117
|
+
not already recorded, and `--update-baseline` rewrites the file from the
|
|
118
|
+
current findings. This is how a repo with years of pre-existing config turns on
|
|
119
|
+
enforcement without first fixing everything or silencing rules it still wants
|
|
120
|
+
on new config. A finding is keyed on `(rule id, file, path)` — not its value —
|
|
121
|
+
so an accepted `pool-size-jump` stays accepted as the number drifts, and the
|
|
122
|
+
file does not churn. Recorded findings are suppressed from the gate and the
|
|
123
|
+
output; new ones still fail. The file is diff-friendly (one sorted entry per
|
|
124
|
+
finding, with severity, message, `acceptedAt`, and an optional hand-written
|
|
125
|
+
`reason` that updates preserve). Stale entries — recorded findings that no
|
|
126
|
+
longer occur — are reported so the file shrinks; updating is always explicit,
|
|
127
|
+
never automatic, so a run cannot launder new risk into the accepted set.
|
|
128
|
+
Change-based `--fail-on` triggers still fire, since the baseline accepts policy
|
|
129
|
+
findings, not the diff. ([#118])
|
|
130
|
+
|
|
131
|
+
- `flecto ci --format sarif` emits SARIF 2.1.0 for upload to GitHub code
|
|
132
|
+
scanning (`github/codeql-action/upload-sarif`). Policy findings render on the
|
|
133
|
+
pull request diff and in the Security tab, with GitHub handling dedup,
|
|
134
|
+
new-vs-existing, and fixed-finding tracking. Each pack rule maps to a
|
|
135
|
+
`reportingDescriptor` (id, short description, pack, level); `severity` maps to
|
|
136
|
+
SARIF `level` (`error`/`warning`/`note`). `--mask-secrets` applies, since a
|
|
137
|
+
SARIF file is uploaded and retained. Results are **file-level** for now —
|
|
138
|
+
Flecto reports a semantic path, not a source line, so each result anchors at
|
|
139
|
+
the top of the file and preserves the full path as a SARIF logical location;
|
|
140
|
+
GitHub still renders and tracks the alert. Recipe and required
|
|
141
|
+
`security-events: write` permission are in [docs/ci.md](docs/ci.md). ([#120])
|
|
142
|
+
|
|
143
|
+
- `flecto init` now detects Kubernetes manifests and SOPS usage — the two file
|
|
144
|
+
shapes 3.0 was built around — and enables the `kubernetes` and `sops` packs
|
|
145
|
+
accordingly. Detection is content-based: a YAML document must actually carry
|
|
146
|
+
`apiVersion` + `kind` to count as a manifest (a config with a bare `kind:`
|
|
147
|
+
field does not), and SOPS is recognized from a top-level metadata block or a
|
|
148
|
+
`.sops.yaml` creation-rules file. Sniffing is bounded — the repo root plus the
|
|
149
|
+
conventional `k8s/` / `kubernetes/` / `manifests/` / `deploy/` directories, a
|
|
150
|
+
cap on files read, and files over 256 KB skipped — so `init` never turns into
|
|
151
|
+
a full-tree scan. The "detected nothing" generic fallback is unchanged. ([#123])
|
|
152
|
+
|
|
153
|
+
- **JSON with comments and trailing commas is parsed** ([#152]). `.json` was
|
|
154
|
+
read with bare `JSON.parse`, so a single `//` failed the whole file — and a
|
|
155
|
+
config watcher installed into a JavaScript repository could not read the
|
|
156
|
+
`tsconfig.json`, `.vscode/settings.json`, `jsconfig.json`, or
|
|
157
|
+
`devcontainer.json` sitting next to it. Worse, it failed with a *parse error*
|
|
158
|
+
rather than an unsupported-format skip, so it looked broken rather than out of
|
|
159
|
+
scope.
|
|
160
|
+
|
|
161
|
+
Both comment styles and trailing commas are now accepted, and `.jsonc` is a
|
|
162
|
+
recognised extension. No new dependency: comments are blanked in place, one
|
|
163
|
+
space per stripped character, with newlines kept — so byte offsets and line
|
|
164
|
+
numbers in a genuine syntax error still point at the line in your file.
|
|
165
|
+
|
|
166
|
+
The strip tracks string state, because the naive version corrupts exactly the
|
|
167
|
+
values config files carry: `{"url": "https://example.com"}` is a URL, not a
|
|
168
|
+
comment. Comments are not preserved on the parsed value; Flecto never rewrites
|
|
169
|
+
config, and a comment-only edit is not a semantic change. See
|
|
170
|
+
[JSON with comments](docs/configuration.md#json-with-comments).
|
|
171
|
+
|
|
172
|
+
Inline suppressions followed, in [#158] — a directive written in a file that
|
|
173
|
+
visibly supports comments no longer does nothing.
|
|
174
|
+
- **`ci --changed-only`** ([#151]). `ci --format json` emitted an envelope for
|
|
175
|
+
every **scanned** file, not every **changed** one, so the output grew with the
|
|
176
|
+
size of the repository rather than the size of the change. Each envelope
|
|
177
|
+
carries `schema_version`, two UUIDs, an ISO timestamp, and an absolute path —
|
|
178
|
+
on 250 service configs with one file edited, roughly 88% of the output
|
|
179
|
+
described files that did not change.
|
|
180
|
+
|
|
181
|
+
For a human that is invisible, since the terminal renderer already prints only
|
|
182
|
+
what changed. It is the machine consumers that pay: webhook sinks, NDJSON
|
|
183
|
+
readers, and any agent handed the JSON.
|
|
184
|
+
|
|
185
|
+
| change (250 configs) | default | `--changed-only` | reduction |
|
|
186
|
+
|---|---|---|---|
|
|
187
|
+
| nothing changed | 112.8 KB | 13.3 KB | 88% |
|
|
188
|
+
| one file changed | 113.4 KB | 14.3 KB | 87% |
|
|
189
|
+
| every 10th file changed | 126.8 KB | 37.3 KB | 71% |
|
|
190
|
+
|
|
191
|
+
**The evidence that Flecto looked is kept.** An envelope for a scanned but
|
|
192
|
+
unchanged file tells a consumer diffing two runs that a file was *checked and
|
|
193
|
+
clean* rather than *not checked at all*, and dropping it would quietly weaken
|
|
194
|
+
a gate someone relies on. Those files collapse into a single `lifecycle`
|
|
195
|
+
envelope carrying the list of paths, so what is removed is the per-file
|
|
196
|
+
overhead rather than the signal.
|
|
197
|
+
|
|
198
|
+
**Off by default**, so `schema_version` stays `2.0` and existing consumers see
|
|
199
|
+
byte-for-byte identical output. A file with policy findings but no changes is
|
|
200
|
+
never collapsed. Settable as `changedOnly` in `.flectorc`. See
|
|
201
|
+
[CI usage](docs/ci.md#--changed-only).
|
|
202
|
+
|
|
203
|
+
- **`github-actions` policy pack** ([#139]). Workflow YAML is the one config
|
|
204
|
+
file in most repositories where a bad change is a security incident rather
|
|
205
|
+
than an outage, and Flecto already parses it. Eleven declarative rules over
|
|
206
|
+
the CI-takeover shapes: `pull_request_target` added, a new scheduled, manual,
|
|
207
|
+
or reusable-workflow trigger, the `permissions` block removed or widened to
|
|
208
|
+
`write-all` or to `write` on one scope, an action referenced by mutable tag
|
|
209
|
+
instead of a commit SHA, a checkout of the pull-request head, `secrets.*`
|
|
210
|
+
interpolated into `run:`, and a job moved to a self-hosted runner. Enabled by
|
|
211
|
+
`flecto init` when `.github/workflows/` exists. No engine change — the pack
|
|
212
|
+
auto-registers from `src/packs/`.
|
|
213
|
+
|
|
214
|
+
It reports **what the pull request changed**, not what the workflow already
|
|
215
|
+
contained; `actionlint` and `zizmor` already lint the state well. Two limits
|
|
216
|
+
are documented rather than papered over: severity cannot depend on the
|
|
217
|
+
trigger, because a rule sees one change event and cannot consult the rest of
|
|
218
|
+
the document, and `runs-on` changing from a string to a list produces one
|
|
219
|
+
event whose value is an array, which no matcher inspects. Every rule carries
|
|
220
|
+
its reasoning in [policy packs](docs/policy-packs.md#github-actions-workflows),
|
|
221
|
+
and four fixtures pin the boundary — including one asserting **zero** findings
|
|
222
|
+
for changes that only look risky.
|
|
223
|
+
|
|
224
|
+
- **Context-savings measurement in the benchmark harness.** Section 5 of
|
|
225
|
+
`npm run bench` reports the size of the semantic diff against the size of the
|
|
226
|
+
config it describes, in bytes, at three mutation rates plus a single-file
|
|
227
|
+
crossover table. Published in [performance](docs/performance.md#context-savings).
|
|
228
|
+
|
|
229
|
+
The result is more qualified than the claim it was written to check. A sparse
|
|
230
|
+
change in a large file is 50x to 1270x cheaper to read as a diff than as the
|
|
231
|
+
file, and the advantage compounds because a change event plus its envelope
|
|
232
|
+
costs a fixed ~600 bytes while the file grows. But a *dense* change is not
|
|
233
|
+
cheaper at all — at roughly a quarter of a file's keys the payload runs about
|
|
234
|
+
3x the size of the files it covers — and `ci --format json` currently emits an
|
|
235
|
+
envelope for every **scanned** file rather than every changed one, so with one
|
|
236
|
+
file changed out of 250 roughly 98% of the output is boilerplate for files that
|
|
237
|
+
did not change. ([#137])
|
|
238
|
+
|
|
239
|
+
### Changed
|
|
240
|
+
- The 3.0 integrations were verified against the real tools they integrate with,
|
|
241
|
+
not only fixtures ([#122]). The HTML report was opened in a real browser — both
|
|
242
|
+
themes render with no JS errors, and the filter, expand/collapse, and
|
|
243
|
+
disclosure triangle work. The encrypted-file path is now tested against output
|
|
244
|
+
from the real `age` binary (`test/fixtures/encrypted-real/`), confirming a real
|
|
245
|
+
age file is detected and never leaks ciphertext through a diff. The
|
|
246
|
+
`flecto-pr-risk` Action was statically reviewed (no runner here to execute a
|
|
247
|
+
live PR) and its flagged mechanics are correct. What was verified, and what
|
|
248
|
+
still needs a real runner / `terraform` / `sops`, is recorded in
|
|
249
|
+
[docs/integration-verification.md](docs/integration-verification.md) — which
|
|
250
|
+
also notes that `flecto report` has no `--mask-secrets` yet, so it renders
|
|
251
|
+
secret values in the clear (a follow-up). ([#122])
|
|
252
|
+
|
|
253
|
+
- **CI runs on Windows and macOS** ([#148]). The matrix varied the Node version
|
|
254
|
+
and nothing else, so every job ran on `ubuntu-latest` — for a tool whose
|
|
255
|
+
primary local mode is watching files by glob, the two platforms where that
|
|
256
|
+
behavior differs had never been tested. Linux keeps the full Node matrix;
|
|
257
|
+
Windows and macOS run one version each, since what they add is the operating
|
|
258
|
+
system rather than the runtime.
|
|
259
|
+
|
|
260
|
+
- **Fuzzing for the boundary an untrusted pull request controls** ([#150]).
|
|
261
|
+
`flecto ci` runs on a pull request, and everything it reads there is
|
|
262
|
+
attacker-supplied: the config files, their names, `.flectorc`, and the regexes
|
|
263
|
+
inside a policy pack the same pull request can add. GHSA-wq8m-fc3q-8m5x came
|
|
264
|
+
out of that surface, and the two DoS vectors fixed after it were found by hand
|
|
265
|
+
— which finds what someone thought to look for.
|
|
266
|
+
|
|
267
|
+
`npm run fuzz` runs eleven structure-aware targets over it: `parseContent` per
|
|
268
|
+
format, `diffTrees`, `expandChangeSubtrees`, Flecto's own regexes in
|
|
269
|
+
`secrets.js` and `encrypted.js`, and pack loading and evaluation. The shared
|
|
270
|
+
invariant is that each either succeeds or throws a clean `Error` — never hangs,
|
|
271
|
+
never exhausts memory, never returns a prototype-polluted object.
|
|
272
|
+
|
|
273
|
+
**No fuzzing dependency.** The inputs are config text, trees, and regex sources
|
|
274
|
+
rather than binary protocols, so the generators are hand-written over a seeded
|
|
275
|
+
PRNG in `test/fuzz/`. That is also what makes a case `(target, seed, index)`
|
|
276
|
+
and nothing else, so `--case N` replays one case without walking the N-1 before
|
|
277
|
+
it.
|
|
278
|
+
|
|
279
|
+
**The time budget is enforced from outside the process.** A hang cannot be
|
|
280
|
+
observed from inside the process that hung, so cases run in a child that writes
|
|
281
|
+
its case index before running the case, and the driver kills the child when the
|
|
282
|
+
heartbeat stops. A failing input is then shrunk — each candidate in its own
|
|
283
|
+
child, so a candidate that hangs shrinks like any other failure.
|
|
284
|
+
|
|
285
|
+
**A finding becomes a regression test by moving one file.** The minimized input
|
|
286
|
+
lands in `test/fuzz/findings/`; moving it to `test/fixtures/fuzz/` is the whole
|
|
287
|
+
procedure, because `test/fuzz-regressions.test.js` replays everything there as
|
|
288
|
+
part of `npm test`. The corpus ships seeded with the already-fixed vectors from
|
|
289
|
+
the security review record.
|
|
290
|
+
|
|
291
|
+
Scheduled nightly, never on a pull request — a fuzz run is a wall-clock budget
|
|
292
|
+
against a random seed, and gating a merge on one is a flaky merge gate — and it
|
|
293
|
+
files nothing automatically, because a finding on this boundary may be
|
|
294
|
+
exploitable rather than merely a hang and those go private per `SECURITY.md`.
|
|
295
|
+
|
|
296
|
+
### Fixed
|
|
297
|
+
|
|
298
|
+
- **"No snapshot history" no longer renders as "no drift"** ([#141]).
|
|
299
|
+
`.flecto-snapshots/` lives in the working directory and is not committed, so on
|
|
300
|
+
an ephemeral CI runner it is empty on every run — and the drift commands read
|
|
301
|
+
that emptiness as an all-clear. For a tool whose job is making risk visible,
|
|
302
|
+
rendering a clean result from a missing input is the worst failure available.
|
|
303
|
+
|
|
304
|
+
- `flecto watch --diff` exited **0** when no target had a snapshot: nothing
|
|
305
|
+
was compared, and the caller was told the files match their baseline. It now
|
|
306
|
+
errors, and a run where only *some* targets lack a snapshot reports how many
|
|
307
|
+
were skipped instead of quietly diffing the rest.
|
|
308
|
+
- `flecto history` printed `0 changes` for the first snapshot of a file — a
|
|
309
|
+
result that was never computed. First snapshots now read as
|
|
310
|
+
`baseline (no earlier snapshot to compare against)`, and a listing with no
|
|
311
|
+
comparisons in it says so.
|
|
312
|
+
- `flecto report` said "No semantic changes from the previous snapshot" on
|
|
313
|
+
cards that had no previous snapshot. Those now name themselves as first
|
|
314
|
+
snapshots, the summary gains a **Comparisons** tile beside **Changes**, and a
|
|
315
|
+
report in which nothing was compared carries a banner saying so above the
|
|
316
|
+
fold.
|
|
317
|
+
- `flecto ci` already failed closed on a missing baseline, but did it with a
|
|
318
|
+
raw `ENOENT` on a hashed filename. The error now names both ways out —
|
|
319
|
+
save a snapshot, or pass `--snapshot-ref <git-ref>`.
|
|
320
|
+
|
|
321
|
+
The shared snapshot store the issue also asks for is not part of this change;
|
|
322
|
+
what is fixed here is every consumer's answer when the history is empty.
|
|
323
|
+
|
|
324
|
+
- **Symlinked targets could read files from outside the repository** ([#121]).
|
|
325
|
+
A pull request adding a config file that is a symlink out of the checkout had
|
|
326
|
+
that file parsed and its **values** emitted — into the job log, the JSON
|
|
327
|
+
envelope, and the `--format pr-comment` markdown that `--pr-comment-post`
|
|
328
|
+
writes to a comment on the pull request. The attacker never controls the
|
|
329
|
+
linked-to file, which is what makes it worth reading: on a CI runner that
|
|
330
|
+
includes `~/.npmrc`, `~/.docker/config.json`, and `~/.aws/credentials` — which
|
|
331
|
+
is INI, and parses perfectly. Opening a pull request is the whole attack.
|
|
332
|
+
|
|
333
|
+
Every resolved target, and `.flecto-snapshots/` before a snapshot is written,
|
|
334
|
+
is now checked for escape rather than for location, so the legitimate cases are
|
|
335
|
+
untouched: a link that stays inside the project still resolves, and a path
|
|
336
|
+
*named* from outside the project (`flecto compare /a.yaml /b.yaml`) is operator
|
|
337
|
+
intent. Only a path inside the project that resolves out of it is refused —
|
|
338
|
+
loudly, naming `FLECTO_ALLOW_SYMLINK_TARGETS=1` for a checkout that links
|
|
339
|
+
config in from a sibling directory on purpose.
|
|
340
|
+
|
|
341
|
+
- **Prototype pollution in the INI parser** ([#121]). A `.ini` file containing a
|
|
342
|
+
`[__proto__]` section wrote every key in that section onto `Object.prototype`
|
|
343
|
+
for the rest of the process: `parseIni` looked the section up as
|
|
344
|
+
`out[section]`, which resolves to `Object.prototype` for that name — and
|
|
345
|
+
`Object.prototype` passes `isPlainObject`, because its own prototype is
|
|
346
|
+
`null`, so the existing guard did not catch it.
|
|
347
|
+
|
|
348
|
+
The blast radius went past the attacker's own file. `severityRemap[rule.id]`
|
|
349
|
+
is a plain-object lookup, so `dangerous-toggle-enabled=off` under
|
|
350
|
+
`[__proto__]` silenced that rule for **every file in the same run**, turning a
|
|
351
|
+
failing `flecto ci --fail-on error` green. Config file contents are
|
|
352
|
+
attacker-controlled on a pull request, which is the case `flecto ci` exists
|
|
353
|
+
to run in.
|
|
354
|
+
|
|
355
|
+
Sections are now read with `Object.hasOwn` and every key written with
|
|
356
|
+
`Object.defineProperty`, so a reserved name is an ordinary own key holding
|
|
357
|
+
ordinary data — and stays *visible* in the diff, rather than being dropped.
|
|
358
|
+
Two same-class sites were hardened alongside it, neither exploitable: the
|
|
359
|
+
masking walk in `src/renderer.js` and the copy loops in `src/encrypted.js`
|
|
360
|
+
moved a `__proto__` subtree onto the result's prototype, dropping the key from
|
|
361
|
+
the output instead of rendering it. Both now rebuild with
|
|
362
|
+
`Object.fromEntries`.
|
|
363
|
+
|
|
364
|
+
Found by the fuzz harness added in [#150] on its first full-length run.
|
|
365
|
+
|
|
366
|
+
- **A `flecto-ignore-next-line` that resolves to nothing now says so** ([#158]).
|
|
367
|
+
A directive on an array element, in a multi-document YAML file, or in a file
|
|
368
|
+
type with no comment syntax at all was accepted, resolved to no path, matched
|
|
369
|
+
nothing, and produced no output — the operator believed a finding was accepted
|
|
370
|
+
and had no way to learn otherwise. Every such directive now warns on stderr,
|
|
371
|
+
naming the file, the line, and `--baseline` as the way to accept the finding.
|
|
372
|
+
|
|
373
|
+
A warning rather than an error, deliberately: the case already fails closed,
|
|
374
|
+
because the finding the directive meant to accept still fires and still gates
|
|
375
|
+
the build. Failing it a second time adds nothing the first failure did not
|
|
376
|
+
already say. What was missing was the signal, not the gate. (The
|
|
377
|
+
mandatory-reason check stays a hard error — there, a suppression *would* have
|
|
378
|
+
hidden a finding, with no justification recorded.)
|
|
379
|
+
|
|
380
|
+
- Adding a second YAML document beside an existing one no longer re-paths the
|
|
381
|
+
whole file. A lone Kubernetes-shaped document (`apiVersion` + `kind` +
|
|
382
|
+
`metadata.name`) is now keyed by identity — `kind/namespace/name` — exactly as
|
|
383
|
+
it is inside a multi-document file, so a `Service` added next to a `Deployment`
|
|
384
|
+
reads as one addition instead of reporting the untouched Deployment as removed
|
|
385
|
+
and re-added. Ordinary single-document YAML (anything without both
|
|
386
|
+
`apiVersion` and `kind`) is unchanged. ([#124])
|
|
387
|
+
|
|
388
|
+
**Migration:** paths for a *single*-document manifest change from bare
|
|
389
|
+
(`spec.replicas`) to identity-prefixed (`Deployment/prod/api.spec.replicas`).
|
|
390
|
+
Snapshots and CI baselines taken of a single manifest before this release will
|
|
391
|
+
show one-time churn on the next diff; `--ignore` entries and custom pack path
|
|
392
|
+
regexes written against the bare paths need the prefix. Multi-document files
|
|
393
|
+
and non-manifest config are unaffected.
|
|
394
|
+
|
|
395
|
+
- `flecto policies test` now resolves packs installed by `flecto policies add`.
|
|
396
|
+
The harness searched only the fixture directory's `policies/`, while
|
|
397
|
+
`policies add` writes to the invoking project's — so the two commands added in
|
|
398
|
+
the same release did not compose. A fixture's own `policies/` still wins, so
|
|
399
|
+
self-contained fixtures are unaffected; the project is a fallback. The
|
|
400
|
+
"unknown pack" error now names every directory it searched instead of
|
|
401
|
+
suggesting a path that already existed. ([#114])
|
|
402
|
+
|
|
403
|
+
- **`--snapshot-ref <git-ref>` no longer fails on Windows** ([#148]). The
|
|
404
|
+
repository-relative path is derived by comparing `git rev-parse
|
|
405
|
+
--show-toplevel` against the file's own path, and Windows spells one directory
|
|
406
|
+
two ways: git reports the long form, while `os.tmpdir()` and many shells hand
|
|
407
|
+
Flecto the 8.3 short form (`C:\Users\RUNNER~1\...`). Node's JS `realpathSync`
|
|
408
|
+
reconciles neither, so the two compared as different directories and the
|
|
409
|
+
computed relative path climbed out of the repository — `git show` then failed
|
|
410
|
+
on a file that was plainly tracked. Canonicalization now prefers
|
|
411
|
+
`realpathSync.native`, which asks the OS for the final path and so resolves
|
|
412
|
+
short names and normalizes case. Linux and macOS are unaffected: the two calls
|
|
413
|
+
agree for any path that exists. Found by the new Windows runner.
|
|
414
|
+
|
|
415
|
+
- **Glob patterns written with Windows separators now match** ([#148]).
|
|
416
|
+
`resolveFiles` passed user patterns straight to `fast-glob`, which requires
|
|
417
|
+
POSIX separators and reads `\\` as an escape character — so on Windows
|
|
418
|
+
`config\\*.yaml` asked for a file literally named `config*.yaml`, matched
|
|
419
|
+
nothing, and reported `No files matched`, blaming the user for a platform bug.
|
|
420
|
+
Since PowerShell and cmd tab-completion produce backslash paths, that was the
|
|
421
|
+
default way a Windows user would invoke Flecto.
|
|
422
|
+
|
|
423
|
+
Patterns are now rewritten to POSIX separators **on Windows only** — on Linux
|
|
424
|
+
and macOS a backslash is a legal filename character and a meaningful glob
|
|
425
|
+
escape, so rewriting there would break patterns that work today. `exclude`
|
|
426
|
+
patterns get the same rewrite, since an exclude that silently stops excluding
|
|
427
|
+
widens what Flecto reports on. Resolved paths stay native.
|
|
428
|
+
- **`ci --format json` no longer truncates at 64 KB through a pipe** ([#155]).
|
|
429
|
+
Output was printed with `console.log` and followed immediately by
|
|
430
|
+
`process.exit()`, which does not flush a pending write — and Node writes to a
|
|
431
|
+
pipe asynchronously. Everything past the 64 KB pipe buffer was dropped, and
|
|
432
|
+
the command still exited with its normal status.
|
|
433
|
+
|
|
434
|
+
Redirecting to a file hid it, because Node writes to a file descriptor
|
|
435
|
+
synchronously. It appeared only through a pipe — which is how every consumer
|
|
436
|
+
that matters reads it: `| jq`, `$(...)` capture, and any CI harness collecting
|
|
437
|
+
stdout.
|
|
438
|
+
|
|
439
|
+
A truncated envelope stream that exits normally is the worst shape for a
|
|
440
|
+
consumer: it reads as a clean run over fewer files rather than as a failure.
|
|
441
|
+
With `ndjson` it is quieter still, since every line before the cut is valid
|
|
442
|
+
JSON, so a line-by-line reader consumes a clean prefix and never learns the
|
|
443
|
+
rest existed.
|
|
444
|
+
|
|
445
|
+
Affected `ci`, `plan`, and `diff`/`compare` on `--format json`, `ndjson`,
|
|
446
|
+
`sarif`, and `github-annotations`. A truncated SARIF document is rejected
|
|
447
|
+
outright by `upload-sarif`, but only after the gate has already reported
|
|
448
|
+
success. `--format pr-comment` was never affected — its body is capped at
|
|
449
|
+
60,000 characters to fit GitHub's comment limit, which lands under one pipe
|
|
450
|
+
buffer.
|
|
451
|
+
|
|
452
|
+
### Security
|
|
453
|
+
|
|
454
|
+
- **Two denial-of-service vectors fixed, found while resuming the 3.0 security
|
|
455
|
+
review** ([#121]). (1) Secret detection (`src/secrets.js`), which runs on every
|
|
456
|
+
changed string value under the `default` pack, had two `O(n²)` regexes — the
|
|
457
|
+
PEM private-key and URL-credential patterns — so a single ~500 KB value in a
|
|
458
|
+
pull request could hang the CI job. Both are now linear; 1 MB scans in under a
|
|
459
|
+
second, and detection of real (including unterminated) keys is unchanged. (2) A
|
|
460
|
+
YAML alias bomb ("billion laughs") — a few hundred bytes of nested aliases that
|
|
461
|
+
`normalizeParsedValue` expanded into an exponentially large tree — now fails
|
|
462
|
+
fast against a node budget instead of exhausting memory. Regression tests for
|
|
463
|
+
both in `test/security.test.js`. The review's findings and its "checked, solid"
|
|
464
|
+
list are recorded in [docs/security-review.md](docs/security-review.md); a
|
|
465
|
+
residual limitation (attacker-supplied regexes in custom packs, which Node
|
|
466
|
+
cannot time out) is noted in [SECURITY.md](SECURITY.md).
|
|
467
|
+
|
|
468
|
+
- **Terraform plan JSON is refused by every command except `flecto plan`.**
|
|
469
|
+
Terraform's `before_sensitive` / `after_sensitive` redaction is applied only by
|
|
470
|
+
`flecto plan`; a plan file is ordinary JSON, so `ci`, `watch`, `compare`,
|
|
471
|
+
`report`, and snapshot writes read it as a plain config tree and printed the
|
|
472
|
+
values Terraform itself refuses to print. `--mask-secrets` was not a backstop —
|
|
473
|
+
it fires on the attribute *name*, and `user_data` does not match. Realistic
|
|
474
|
+
ways to hit it: `flecto ci "**/*.json"`, a committed `tfplan.json`, or
|
|
475
|
+
`.flectorc` `files` patterns that sweep JSON. Those commands now fail with a
|
|
476
|
+
pointer to `flecto plan`, mirroring the guard `flecto plan` already had in the
|
|
477
|
+
other direction. ([#113])
|
|
478
|
+
|
|
479
|
+
## [3.0.1] - 2026-08-07
|
|
480
|
+
|
|
481
|
+
### Security
|
|
482
|
+
|
|
483
|
+
- **Policy plugins declared in `.flectorc` are no longer loaded**
|
|
484
|
+
([GHSA-wq8m-fc3q-8m5x], critical). A pull request that added a `.flectorc`
|
|
485
|
+
with a `plugins` entry achieved **arbitrary code execution on the CI runner** —
|
|
486
|
+
`flecto ci` is what teams run on pull requests, and it honoured the attacker's
|
|
487
|
+
config with no opt-in, no allowlist, and no path containment. The attacker's
|
|
488
|
+
code ran with whatever the workflow exposed, including `GITHUB_TOKEN`, and the
|
|
489
|
+
path was not contained, so `../../../../tmp/x.mjs` loaded a module from
|
|
490
|
+
anywhere on disk.
|
|
491
|
+
|
|
492
|
+
Plugins now load only from an explicit `--plugins` flag. If a config file is
|
|
493
|
+
genuinely trusted, set `FLECTO_ALLOW_RC_PLUGINS=1`; even then an rc-declared
|
|
494
|
+
plugin must live inside the working directory. Flecto **fails loudly** rather
|
|
495
|
+
than skipping the plugin silently, because a policy plugin that stopped running
|
|
496
|
+
without saying so would quietly weaken a gate the operator believes is
|
|
497
|
+
enforced.
|
|
498
|
+
|
|
499
|
+
Policy *packs* are declarative and were never affected. `--plugins` is
|
|
500
|
+
unchanged, including paths outside the project, since the flag is operator
|
|
501
|
+
intent rather than attacker input.
|
|
502
|
+
|
|
503
|
+
**If you run Flecto on untrusted pull requests, upgrade.** If you rely on
|
|
504
|
+
`plugins` in `.flectorc`, move it to `--plugins` or set the opt-in.
|
|
505
|
+
|
|
506
|
+
The trust boundary is now documented in [plugin authoring](docs/plugins.md);
|
|
507
|
+
it previously was not stated anywhere.
|
|
508
|
+
|
|
509
|
+
|
|
10
510
|
## [3.0.0] - 2026-08-06
|
|
11
511
|
|
|
12
512
|
### Migration notes
|
|
@@ -472,7 +972,9 @@ fixed — those runs were never actually gated — but the failure is new.
|
|
|
472
972
|
- Misconfigured policy packs/plugins cause `watch` to exit non-zero instead of
|
|
473
973
|
continuing with no policies.
|
|
474
974
|
|
|
475
|
-
[Unreleased]: https://github.com/myselfsiddharth/Flecto/compare/v3.0.
|
|
975
|
+
[Unreleased]: https://github.com/myselfsiddharth/Flecto/compare/v3.0.2...HEAD
|
|
976
|
+
[3.0.2]: https://github.com/myselfsiddharth/Flecto/compare/v3.0.1...v3.0.2
|
|
977
|
+
[3.0.1]: https://github.com/myselfsiddharth/Flecto/compare/v3.0.0...v3.0.1
|
|
476
978
|
[3.0.0]: https://github.com/myselfsiddharth/Flecto/compare/v2.1.0...v3.0.0
|
|
477
979
|
[2.1.0]: https://github.com/myselfsiddharth/Flecto/compare/v2.0.0...v2.1.0
|
|
478
980
|
[#6]: https://github.com/myselfsiddharth/Flecto/issues/6
|
|
@@ -529,5 +1031,36 @@ fixed — those runs were never actually gated — but the failure is new.
|
|
|
529
1031
|
[#108]: https://github.com/myselfsiddharth/Flecto/pull/108
|
|
530
1032
|
[#109]: https://github.com/myselfsiddharth/Flecto/issues/109
|
|
531
1033
|
[#110]: https://github.com/myselfsiddharth/Flecto/issues/110
|
|
1034
|
+
[#122]: https://github.com/myselfsiddharth/Flecto/issues/122
|
|
1035
|
+
|
|
1036
|
+
[#121]: https://github.com/myselfsiddharth/Flecto/issues/121
|
|
1037
|
+
|
|
1038
|
+
[#119]: https://github.com/myselfsiddharth/Flecto/issues/119
|
|
1039
|
+
|
|
1040
|
+
[#118]: https://github.com/myselfsiddharth/Flecto/issues/118
|
|
1041
|
+
|
|
1042
|
+
[#120]: https://github.com/myselfsiddharth/Flecto/issues/120
|
|
1043
|
+
|
|
1044
|
+
[#123]: https://github.com/myselfsiddharth/Flecto/issues/123
|
|
1045
|
+
|
|
1046
|
+
[#124]: https://github.com/myselfsiddharth/Flecto/issues/124
|
|
1047
|
+
|
|
1048
|
+
[#113]: https://github.com/myselfsiddharth/Flecto/issues/113
|
|
1049
|
+
|
|
1050
|
+
[#114]: https://github.com/myselfsiddharth/Flecto/issues/114
|
|
1051
|
+
[#148]: https://github.com/myselfsiddharth/Flecto/issues/148
|
|
1052
|
+
[#152]: https://github.com/myselfsiddharth/Flecto/issues/152
|
|
1053
|
+
[#151]: https://github.com/myselfsiddharth/Flecto/issues/151
|
|
1054
|
+
[#155]: https://github.com/myselfsiddharth/Flecto/issues/155
|
|
1055
|
+
[#139]: https://github.com/myselfsiddharth/Flecto/issues/139
|
|
1056
|
+
[#137]: https://github.com/myselfsiddharth/Flecto/issues/137
|
|
1057
|
+
[#149]: https://github.com/myselfsiddharth/Flecto/issues/149
|
|
1058
|
+
[#158]: https://github.com/myselfsiddharth/Flecto/issues/158
|
|
1059
|
+
[#159]: https://github.com/myselfsiddharth/Flecto/issues/159
|
|
1060
|
+
[#150]: https://github.com/myselfsiddharth/Flecto/issues/150
|
|
1061
|
+
[#125]: https://github.com/myselfsiddharth/Flecto/issues/125
|
|
1062
|
+
[#141]: https://github.com/myselfsiddharth/Flecto/issues/141
|
|
532
1063
|
[Keep a Changelog]: https://keepachangelog.com/en/1.1.0/
|
|
1064
|
+
[#138]: https://github.com/myselfsiddharth/Flecto/issues/138
|
|
533
1065
|
[Semantic Versioning]: https://semver.org/spec/v2.0.0.html
|
|
1066
|
+
[GHSA-wq8m-fc3q-8m5x]: https://github.com/myselfsiddharth/Flecto/security/advisories/GHSA-wq8m-fc3q-8m5x
|
package/README.md
CHANGED
|
@@ -35,6 +35,18 @@ English, flags what looks risky, and gives you an exit code to gate on.
|
|
|
35
35
|
| Hope someone notices `debug: true` | Policy finding → build fails |
|
|
36
36
|
| "Something in `.env` changed" | The exact keys, with secrets masked |
|
|
37
37
|
|
|
38
|
+
The same engine reads whatever your change actually lives in:
|
|
39
|
+
|
|
40
|
+
| You are reviewing | Flecto reads |
|
|
41
|
+
|---|---|
|
|
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.
|
|
49
|
+
|
|
38
50
|
---
|
|
39
51
|
|
|
40
52
|
## Install
|
|
@@ -188,6 +200,9 @@ steps:
|
|
|
188
200
|
- uses: myselfsiddharth/Flecto/.github/actions/flecto-pr-risk@main
|
|
189
201
|
```
|
|
190
202
|
|
|
203
|
+
GitLab and Bitbucket work the same way — Flecto detects the host from CI
|
|
204
|
+
variables, or `--pr-provider` forces one. See [CI](docs/ci.md#providers).
|
|
205
|
+
|
|
191
206
|
A fork's pull request gets a read-only token, so the comment is skipped with a
|
|
192
207
|
warning there — the check itself still runs and still fails on risky changes.
|
|
193
208
|
|
|
@@ -285,6 +300,37 @@ resource. Flecto never runs `helm` or `kustomize` — you render, it diffs, so a
|
|
|
285
300
|
renderer works and no binary is needed in CI.
|
|
286
301
|
→ **[Kubernetes](docs/kubernetes.md)**
|
|
287
302
|
|
|
303
|
+
### Read a Terraform plan in plain English
|
|
304
|
+
|
|
305
|
+
`terraform plan` output is precise and long. Flecto turns it into the handful of
|
|
306
|
+
lines a reviewer actually needs to argue about:
|
|
307
|
+
|
|
308
|
+
```bash
|
|
309
|
+
terraform show -json plan.tfplan > plan.json
|
|
310
|
+
flecto plan plan.json --fail-on error
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
```
|
|
314
|
+
plan.json — plan format 1.2
|
|
315
|
+
Plan: 0 to add, 1 to change, 0 to destroy, 1 to replace.
|
|
316
|
+
~ aws_security_group.web.ingress[0].cidr_blocks[0]: "10.0.0.0/8" → "0.0.0.0/0"
|
|
317
|
+
- aws_db_instance.main.#action: "replace" [terraform will destroy and recreate aws_db_instance.main]
|
|
318
|
+
~ aws_db_instance.main.password: "(sensitive value)" → "(sensitive value)" [sensitive]
|
|
319
|
+
! policy(error) [terraform] …cidr_blocks[0]: Security group ingress will accept
|
|
320
|
+
traffic from the whole internet (0.0.0.0/0). Restrict the source to a known CIDR…
|
|
321
|
+
! policy(error) [terraform] …#action: Terraform will destroy a stateful resource.
|
|
322
|
+
Its data does not survive. Take a final snapshot, or add a prevent_destroy…
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
A **replace reads as a removal**, not a benign update — a recreated database
|
|
326
|
+
should never look like a config tweak. Values Terraform marks sensitive are
|
|
327
|
+
redacted during parsing, before the policy engine or any formatter sees them, and
|
|
328
|
+
`after_unknown` renders as `(known after apply)` rather than `null`.
|
|
329
|
+
|
|
330
|
+
**Flecto never runs `terraform`** — you produce the JSON, it reads it, so nothing
|
|
331
|
+
extra has to exist on the CI runner.
|
|
332
|
+
→ **[Terraform plans](docs/terraform.md)**
|
|
333
|
+
|
|
288
334
|
### Encode your own rules
|
|
289
335
|
|
|
290
336
|
Beyond the built-in packs, write rules as declarative JSON or YAML — no code:
|
|
@@ -327,6 +373,7 @@ services doesn't read as a wall of changes.
|
|
|
327
373
|
| `node-runtime` | Dropped engine requirements, TLS verification bypasses, debug/inspector flags |
|
|
328
374
|
| `terraform` | Replaced and destroyed stateful resources, ingress opened to `0.0.0.0/0`, IAM wildcards, public S3, capacity jumps |
|
|
329
375
|
| `sops` | Decryption recipients added or removed, a MAC that moved on its own, a file that stopped being encrypted |
|
|
376
|
+
| `github-actions` | Changed workflow triggers, widened permissions, self-hosted runners, unpinned actions, pull-request head checkout, and secrets interpolated into `run` |
|
|
330
377
|
|
|
331
378
|
```bash
|
|
332
379
|
flecto policies list # see what resolves here, built-in and local
|
|
@@ -358,13 +405,23 @@ and runs no code from the package. →
|
|
|
358
405
|
|
|
359
406
|
| Format | Extensions |
|
|
360
407
|
|---|---|
|
|
361
|
-
| JSON | `.json` |
|
|
408
|
+
| JSON / JSONC | `.json`, `.jsonc` |
|
|
362
409
|
| YAML | `.yaml`, `.yml` |
|
|
363
410
|
| TOML | `.toml` |
|
|
364
411
|
| INI | `.ini` |
|
|
365
412
|
| dotenv | `.env`, `.env.*`, `*.env` |
|
|
366
413
|
| age (armored) | `.age`, or any file whose contents are one armored blob |
|
|
367
414
|
|
|
415
|
+
`.json` accepts comments and trailing commas, so `tsconfig.json`,
|
|
416
|
+
`.vscode/settings.json`, `jsconfig.json`, and `devcontainer.json` are read as
|
|
417
|
+
written. →
|
|
418
|
+
**[JSON with comments](docs/configuration.md#json-with-comments)**
|
|
419
|
+
|
|
420
|
+
Terraform plan JSON (`terraform show -json`) is read by **`flecto plan`**, which
|
|
421
|
+
applies Terraform's own sensitivity marking. Point `plan` at it rather than `ci`
|
|
422
|
+
or `watch` — those treat it as ordinary JSON and will print values Terraform
|
|
423
|
+
marks sensitive ([#113](https://github.com/myselfsiddharth/Flecto/issues/113)).
|
|
424
|
+
|
|
368
425
|
Multi-document YAML (`---`-separated, the usual shape of a Kubernetes manifest)
|
|
369
426
|
is supported. Each document is diffed under its own key — `kind/name` for
|
|
370
427
|
Kubernetes-shaped documents, so a document inserted at the top of the file does
|
|
@@ -463,6 +520,7 @@ Explicit CLI flags win over profiles, which win over `defaults`.
|
|
|
463
520
|
| **[Configuration](docs/configuration.md)** | `.flectorc`, profiles, ignore patterns, array identity, masking |
|
|
464
521
|
| **[Encrypted files](docs/encrypted-files.md)** | SOPS and age: what is detected, what is reported, why nothing is decrypted |
|
|
465
522
|
| **[CI](docs/ci.md)** | Baselines, fail triggers, output formats, the bundled GitHub Actions |
|
|
523
|
+
| **[Performance](docs/performance.md)** | Where time goes at scale, and how much smaller a diff is than the config |
|
|
466
524
|
| **[Kubernetes](docs/kubernetes.md)** | Diffing rendered Helm/Kustomize manifests before they reach a cluster |
|
|
467
525
|
| **[Terraform plans](docs/terraform.md)** | Reviewing `terraform show -json` output and the `terraform` pack |
|
|
468
526
|
| **[Webhooks and commands](docs/webhooks.md)** | Envelope shape, delivery modes, command environment |
|