@intentius/chant 0.49.0 → 0.51.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/dist/audit/catalog.d.ts +13 -3
- package/dist/audit/catalog.d.ts.map +1 -1
- package/dist/audit/core.d.ts +9 -0
- package/dist/audit/core.d.ts.map +1 -1
- package/dist/audit/discover.d.ts +6 -0
- package/dist/audit/discover.d.ts.map +1 -1
- package/dist/audit/fetch.d.ts.map +1 -1
- package/dist/audit/report-html.d.ts.map +1 -1
- package/dist/audit/report-model.d.ts +6 -0
- package/dist/audit/report-model.d.ts.map +1 -1
- package/dist/audit/report.d.ts.map +1 -1
- package/dist/audit/rules-doc.d.ts.map +1 -1
- package/dist/audit/secrets.d.ts +95 -0
- package/dist/audit/secrets.d.ts.map +1 -0
- package/dist/audit/wrangler.d.ts +33 -0
- package/dist/audit/wrangler.d.ts.map +1 -0
- package/dist/build.d.ts.map +1 -1
- package/dist/cli/commands/audit.d.ts +7 -0
- package/dist/cli/commands/audit.d.ts.map +1 -1
- package/dist/cli/commands/build.d.ts +23 -0
- package/dist/cli/commands/build.d.ts.map +1 -1
- package/dist/cli/handlers/build.d.ts.map +1 -1
- package/dist/cli/handlers/components.d.ts +31 -0
- package/dist/cli/handlers/components.d.ts.map +1 -1
- package/dist/cli/handlers/lifecycle.d.ts +11 -0
- package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
- package/dist/cli/handlers/op-progress.d.ts +57 -0
- package/dist/cli/handlers/op-progress.d.ts.map +1 -0
- package/dist/cli/handlers/operator.d.ts +32 -0
- package/dist/cli/handlers/operator.d.ts.map +1 -0
- package/dist/cli/handlers/run-client.d.ts +21 -1
- package/dist/cli/handlers/run-client.d.ts.map +1 -1
- package/dist/cli/handlers/run-report.d.ts.map +1 -1
- package/dist/cli/handlers/run.d.ts.map +1 -1
- package/dist/cli/handlers/scenario.d.ts +39 -0
- package/dist/cli/handlers/scenario.d.ts.map +1 -0
- package/dist/cli/handlers/search.d.ts +22 -0
- package/dist/cli/handlers/search.d.ts.map +1 -1
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/mcp/op-tools.d.ts.map +1 -1
- package/dist/cli/mcp/resource-handlers.d.ts.map +1 -1
- package/dist/cli/mcp/server.d.ts +35 -2
- package/dist/cli/mcp/server.d.ts.map +1 -1
- package/dist/cli/mcp/types.d.ts +29 -1
- package/dist/cli/mcp/types.d.ts.map +1 -1
- package/dist/cli/registry.d.ts +47 -3
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/codegen/docs-rule-scanning.d.ts.map +1 -1
- package/dist/components/capability.d.ts +17 -2
- package/dist/components/capability.d.ts.map +1 -1
- package/dist/components/cli-support.d.ts +7 -0
- package/dist/components/cli-support.d.ts.map +1 -1
- package/dist/components/component.d.ts +15 -0
- package/dist/components/component.d.ts.map +1 -1
- package/dist/components/driver.d.ts.map +1 -1
- package/dist/components/run-progress.d.ts +7 -5
- package/dist/components/run-progress.d.ts.map +1 -1
- package/dist/components/verbs/index.d.ts +6 -1
- package/dist/components/verbs/index.d.ts.map +1 -1
- package/dist/components/verbs/run-agent.d.ts +499 -0
- package/dist/components/verbs/run-agent.d.ts.map +1 -0
- package/dist/components/verbs/sign.d.ts +30 -0
- package/dist/components/verbs/sign.d.ts.map +1 -1
- package/dist/composite.d.ts +6 -1
- package/dist/composite.d.ts.map +1 -1
- package/dist/discovery/collect.d.ts.map +1 -1
- package/dist/discovery/fold-import.d.ts +15 -1
- package/dist/discovery/fold-import.d.ts.map +1 -1
- package/dist/discovery/fold-rank.d.ts +66 -0
- package/dist/discovery/fold-rank.d.ts.map +1 -0
- package/dist/discovery/index.d.ts +15 -0
- package/dist/discovery/index.d.ts.map +1 -1
- package/dist/discovery/param-deps.d.ts +17 -0
- package/dist/discovery/param-deps.d.ts.map +1 -0
- package/dist/fold/fold.d.ts +55 -2
- package/dist/fold/fold.d.ts.map +1 -1
- package/dist/fold/subset.d.ts +21 -14
- package/dist/fold/subset.d.ts.map +1 -1
- package/dist/lexicon-schema.d.ts +2 -0
- package/dist/lexicon-schema.d.ts.map +1 -1
- package/dist/lexicon.d.ts +134 -0
- package/dist/lexicon.d.ts.map +1 -1
- package/dist/lifecycle/assert-live.d.ts +77 -0
- package/dist/lifecycle/assert-live.d.ts.map +1 -0
- package/dist/lifecycle/change-set.d.ts +17 -0
- package/dist/lifecycle/change-set.d.ts.map +1 -1
- package/dist/lifecycle/converge-ledger.d.ts +90 -0
- package/dist/lifecycle/converge-ledger.d.ts.map +1 -0
- package/dist/lifecycle/deep-diff.d.ts +18 -0
- package/dist/lifecycle/deep-diff.d.ts.map +1 -1
- package/dist/lifecycle/deep-observe.d.ts +9 -1
- package/dist/lifecycle/deep-observe.d.ts.map +1 -1
- package/dist/lifecycle/disruption.d.ts +96 -0
- package/dist/lifecycle/disruption.d.ts.map +1 -0
- package/dist/lifecycle/gate-ledger.d.ts +33 -0
- package/dist/lifecycle/gate-ledger.d.ts.map +1 -0
- package/dist/lifecycle/git.d.ts +145 -21
- package/dist/lifecycle/git.d.ts.map +1 -1
- package/dist/lifecycle/index.d.ts +6 -0
- package/dist/lifecycle/index.d.ts.map +1 -1
- package/dist/lifecycle/lease.d.ts +113 -0
- package/dist/lifecycle/lease.d.ts.map +1 -0
- package/dist/lifecycle/replay.d.ts +2 -0
- package/dist/lifecycle/replay.d.ts.map +1 -1
- package/dist/lifecycle/scenario-eval.d.ts +42 -0
- package/dist/lifecycle/scenario-eval.d.ts.map +1 -0
- package/dist/lifecycle/scenario.d.ts +163 -0
- package/dist/lifecycle/scenario.d.ts.map +1 -0
- package/dist/lifecycle/symptoms.d.ts +63 -0
- package/dist/lifecycle/symptoms.d.ts.map +1 -0
- package/dist/lint/output-docs.d.ts +94 -0
- package/dist/lint/output-docs.d.ts.map +1 -0
- package/dist/lint/post-synth.d.ts +29 -0
- package/dist/lint/post-synth.d.ts.map +1 -1
- package/dist/lint/rules/__fixtures__/comp/comp003/pass/agent-turn.component.d.ts +11 -0
- package/dist/lint/rules/__fixtures__/comp/comp003/pass/agent-turn.component.d.ts.map +1 -0
- package/dist/lsp/lexicon-providers.d.ts +7 -0
- package/dist/lsp/lexicon-providers.d.ts.map +1 -1
- package/dist/op/activity-contract.d.ts +139 -0
- package/dist/op/activity-contract.d.ts.map +1 -0
- package/dist/op/builders.d.ts +42 -2
- package/dist/op/builders.d.ts.map +1 -1
- package/dist/op/converge-rule.d.ts +161 -0
- package/dist/op/converge-rule.d.ts.map +1 -0
- package/dist/op/generate-pipeline.d.ts +39 -0
- package/dist/op/generate-pipeline.d.ts.map +1 -0
- package/dist/op/index.d.ts +14 -0
- package/dist/op/index.d.ts.map +1 -1
- package/dist/op/local-executor.d.ts +7 -1
- package/dist/op/local-executor.d.ts.map +1 -1
- package/dist/op/op-verb-class.d.ts +42 -0
- package/dist/op/op-verb-class.d.ts.map +1 -0
- package/dist/op/operator.d.ts +128 -0
- package/dist/op/operator.d.ts.map +1 -0
- package/dist/op/step-output-ref.d.ts +187 -0
- package/dist/op/step-output-ref.d.ts.map +1 -0
- package/dist/op/types.d.ts +18 -1
- package/dist/op/types.d.ts.map +1 -1
- package/dist/provenance.d.ts +73 -3
- package/dist/provenance.d.ts.map +1 -1
- package/dist/runtime-adapter.d.ts +7 -1
- package/dist/runtime-adapter.d.ts.map +1 -1
- package/dist/serializer.d.ts +18 -0
- package/dist/serializer.d.ts.map +1 -1
- package/dist/testing.d.ts +23 -2
- package/dist/testing.d.ts.map +1 -1
- package/dist/toml.d.ts +40 -5
- package/dist/toml.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/audit/catalog.test.ts +1 -1
- package/src/audit/catalog.ts +75 -3
- package/src/audit/core.ts +9 -0
- package/src/audit/discover.ts +29 -2
- package/src/audit/fetch.test.ts +216 -3
- package/src/audit/fetch.ts +270 -59
- package/src/audit/report-html.ts +5 -2
- package/src/audit/report-model.ts +9 -0
- package/src/audit/report.test.ts +22 -0
- package/src/audit/report.ts +3 -2
- package/src/audit/rules-doc.ts +2 -0
- package/src/audit/secrets.test.ts +303 -0
- package/src/audit/secrets.ts +406 -0
- package/src/audit/wrangler.test.ts +230 -0
- package/src/audit/wrangler.ts +290 -0
- package/src/build.ts +8 -3
- package/src/cli/command-group.ts +1 -1
- package/src/cli/commands/__fixtures__/schemas/sarif-2.1.0.schema.json +2882 -0
- package/src/cli/commands/audit.test.ts +215 -1
- package/src/cli/commands/audit.ts +86 -17
- package/src/cli/commands/build.test.ts +167 -2
- package/src/cli/commands/build.ts +114 -23
- package/src/cli/handlers/build.ts +2 -0
- package/src/cli/handlers/components.test.ts +199 -1
- package/src/cli/handlers/components.ts +160 -3
- package/src/cli/handlers/graph.test.ts +20 -0
- package/src/cli/handlers/graph.ts +10 -1
- package/src/cli/handlers/lifecycle.test.ts +90 -0
- package/src/cli/handlers/lifecycle.ts +30 -5
- package/src/cli/handlers/op-progress.test.ts +202 -0
- package/src/cli/handlers/op-progress.ts +192 -0
- package/src/cli/handlers/operator.test.ts +255 -0
- package/src/cli/handlers/operator.ts +240 -0
- package/src/cli/handlers/run-client.test.ts +82 -0
- package/src/cli/handlers/run-client.ts +85 -2
- package/src/cli/handlers/run-report.test.ts +62 -0
- package/src/cli/handlers/run-report.ts +20 -58
- package/src/cli/handlers/run.test.ts +144 -0
- package/src/cli/handlers/run.ts +40 -18
- package/src/cli/handlers/scenario.test.ts +456 -0
- package/src/cli/handlers/scenario.ts +330 -0
- package/src/cli/handlers/search-drift.test.ts +263 -0
- package/src/cli/handlers/search.ts +150 -1
- package/src/cli/main.test.ts +23 -0
- package/src/cli/main.ts +81 -1
- package/src/cli/mcp/op-tools.ts +17 -6
- package/src/cli/mcp/resource-handlers.ts +13 -5
- package/src/cli/mcp/server.test.ts +265 -2
- package/src/cli/mcp/server.ts +84 -7
- package/src/cli/mcp/types.ts +27 -1
- package/src/cli/registry.ts +47 -3
- package/src/codegen/docs-rule-scanning.test.ts +42 -0
- package/src/codegen/docs-rule-scanning.ts +25 -2
- package/src/components/README.md +7 -0
- package/src/components/capability.ts +17 -2
- package/src/components/cli-support.test.ts +17 -0
- package/src/components/cli-support.ts +13 -1
- package/src/components/component-schema.test.ts +32 -0
- package/src/components/component.schema.json +6 -0
- package/src/components/component.test.ts +21 -0
- package/src/components/component.ts +15 -0
- package/src/components/driver.ts +12 -4
- package/src/components/run-progress.ts +9 -5
- package/src/components/verbs/index.ts +6 -1
- package/src/components/verbs/run-agent.test.ts +683 -0
- package/src/components/verbs/run-agent.ts +786 -0
- package/src/components/verbs/sign.test.ts +19 -0
- package/src/components/verbs/sign.ts +34 -2
- package/src/composite.ts +31 -2
- package/src/discovery/collect.ts +11 -2
- package/src/discovery/fold-import.test.ts +54 -0
- package/src/discovery/fold-import.ts +178 -38
- package/src/discovery/fold-rank.test.ts +197 -0
- package/src/discovery/fold-rank.ts +346 -0
- package/src/discovery/index.ts +16 -1
- package/src/discovery/param-deps.test.ts +118 -0
- package/src/discovery/param-deps.ts +170 -0
- package/src/fold/fold.test.ts +6 -2
- package/src/fold/fold.ts +184 -3
- package/src/fold/subset.test.ts +82 -19
- package/src/fold/subset.ts +79 -41
- package/src/lexicon-schema.ts +3 -0
- package/src/lexicon.ts +154 -2
- package/src/lifecycle/assert-live.test.ts +125 -0
- package/src/lifecycle/assert-live.ts +154 -0
- package/src/lifecycle/change-set.ts +35 -3
- package/src/lifecycle/converge-ledger.test.ts +199 -0
- package/src/lifecycle/converge-ledger.ts +179 -0
- package/src/lifecycle/deep-diff.test.ts +79 -1
- package/src/lifecycle/deep-diff.ts +23 -0
- package/src/lifecycle/deep-observe.ts +13 -2
- package/src/lifecycle/disruption.test.ts +186 -0
- package/src/lifecycle/disruption.ts +224 -0
- package/src/lifecycle/gate-ledger.test.ts +103 -0
- package/src/lifecycle/gate-ledger.ts +140 -0
- package/src/lifecycle/git.test.ts +430 -0
- package/src/lifecycle/git.ts +446 -84
- package/src/lifecycle/index.ts +6 -0
- package/src/lifecycle/lease.test.ts +343 -0
- package/src/lifecycle/lease.ts +270 -0
- package/src/lifecycle/replay.test.ts +25 -0
- package/src/lifecycle/replay.ts +11 -3
- package/src/lifecycle/scenario-eval.test.ts +199 -0
- package/src/lifecycle/scenario-eval.ts +158 -0
- package/src/lifecycle/scenario.test.ts +195 -0
- package/src/lifecycle/scenario.ts +321 -0
- package/src/lifecycle/symptoms.test.ts +116 -0
- package/src/lifecycle/symptoms.ts +126 -0
- package/src/lint/output-docs.test.ts +220 -0
- package/src/lint/output-docs.ts +204 -0
- package/src/lint/post-synth.test.ts +97 -0
- package/src/lint/post-synth.ts +45 -0
- package/src/lint/rules/__fixtures__/comp/comp003/pass/agent-turn.component.ts +26 -0
- package/src/lint/rules/comp/comp.test.ts +49 -1
- package/src/lint/rules/evl001-non-literal-expression.test.ts +8 -3
- package/src/lint/rules/evl001-non-literal-expression.ts +6 -6
- package/src/lsp/lexicon-providers.test.ts +44 -0
- package/src/lsp/lexicon-providers.ts +11 -1
- package/src/op/activity-contract.test.ts +180 -0
- package/src/op/activity-contract.ts +278 -0
- package/src/op/builders-exports.test.ts +17 -1
- package/src/op/builders.ts +59 -5
- package/src/op/converge-rule.test.ts +179 -0
- package/src/op/converge-rule.ts +311 -0
- package/src/op/generate-pipeline.test.ts +53 -0
- package/src/op/generate-pipeline.ts +99 -0
- package/src/op/index.ts +30 -0
- package/src/op/local-executor.test.ts +92 -0
- package/src/op/local-executor.ts +52 -10
- package/src/op/local-output.ts +1 -1
- package/src/op/op-verb-class.test.ts +126 -0
- package/src/op/op-verb-class.ts +115 -0
- package/src/op/operator.test.ts +346 -0
- package/src/op/operator.ts +213 -0
- package/src/op/step-output-ref.test.ts +334 -0
- package/src/op/step-output-ref.ts +453 -0
- package/src/op/types.ts +18 -1
- package/src/provenance.test.ts +151 -4
- package/src/provenance.ts +118 -4
- package/src/runtime-adapter.ts +31 -10
- package/src/serializer.ts +18 -0
- package/src/testing.test.ts +89 -2
- package/src/testing.ts +63 -3
- package/src/toml.test.ts +157 -384
- package/src/toml.ts +371 -5
package/src/audit/fetch.ts
CHANGED
|
@@ -133,6 +133,27 @@ async function getJsonAt(
|
|
|
133
133
|
return { status: res.status, body: await res.json() };
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
+
/**
|
|
137
|
+
* Like `getJsonAt`, but for search endpoints specifically: a 404 here means
|
|
138
|
+
* "this endpoint doesn't exist" (an unsupported/misconfigured search API — the
|
|
139
|
+
* real-world case for Forgejo, whose code-search availability is undocumented,
|
|
140
|
+
* #520), not "zero results" the way a missing *file* would be. Zero results are
|
|
141
|
+
* a 200 with an empty array/list, so treating 404 as an error (rather than
|
|
142
|
+
* `getJsonAt`'s "return null" leniency) lets a genuinely unsupported search API
|
|
143
|
+
* fall back to the walk instead of silently reporting no matches.
|
|
144
|
+
*/
|
|
145
|
+
async function getSearchJsonAt(url: string, headers: Record<string, string>, doFetch: typeof fetch, timeoutMs: number): Promise<unknown> {
|
|
146
|
+
let res: Response;
|
|
147
|
+
try {
|
|
148
|
+
res = await doFetch(url, { headers, redirect: "manual", signal: timeoutSignal(timeoutMs) });
|
|
149
|
+
} catch (err) {
|
|
150
|
+
throw new FetchError(`Request failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
151
|
+
}
|
|
152
|
+
if (res.status >= 300 && res.status < 400) throw new FetchError(`Refusing to follow redirect from ${url}`);
|
|
153
|
+
if (!res.ok) throw new FetchError(`${url} returned ${res.status}`);
|
|
154
|
+
return res.json();
|
|
155
|
+
}
|
|
156
|
+
|
|
136
157
|
function projectId(owner: string, repo: string): string {
|
|
137
158
|
return encodeURIComponent(`${owner}/${repo}`);
|
|
138
159
|
}
|
|
@@ -154,73 +175,263 @@ interface TreeEntry {
|
|
|
154
175
|
size?: number;
|
|
155
176
|
}
|
|
156
177
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
178
|
+
// ── Search-first discovery for large repos (#520) ───────────────────────────
|
|
179
|
+
//
|
|
180
|
+
// A full tree walk + extension filter over-fetches badly on a large monorepo
|
|
181
|
+
// (thousands of `.yml`/`.json` candidates, almost all irrelevant) and still
|
|
182
|
+
// misses IaC/CI content living at non-canonical paths. Above a size threshold,
|
|
183
|
+
// switch from "walk everything `isCandidatePath` allows" to "search for each
|
|
184
|
+
// content-detected lexicon's characteristic signature, download only hits":
|
|
185
|
+
//
|
|
186
|
+
// - Known-path lexicons (github, forgejo, and gitlab's canonical file) never
|
|
187
|
+
// need a search query — their location is fixed, so a plain path/tree
|
|
188
|
+
// lookup already finds them for any repo size. GitHub/Forgejo Actions
|
|
189
|
+
// *must* live under `.github`/`.forgejo` workflows (the platform enforces
|
|
190
|
+
// it), so no search term is listed for them at all. GitLab's `include:`
|
|
191
|
+
// can pull in CI files from anywhere, so its canonical file is still
|
|
192
|
+
// backed by a `stages:` search term to catch those satellites (#518).
|
|
193
|
+
// - Content-detected lexicons (k8s, aws, azure, gcp, docker, helm) have no
|
|
194
|
+
// fixed path, so at scale the only cheap way to find them is a search for
|
|
195
|
+
// the grep-equivalent of their `detectTemplate` signature.
|
|
196
|
+
// - Search is additive, never load-bearing: any error, rate limit, or
|
|
197
|
+
// unsupported/unavailable API degrades to the walk rather than failing
|
|
198
|
+
// the audit.
|
|
199
|
+
|
|
200
|
+
/** Repos above this many tree entries switch from the walk to search-first
|
|
201
|
+
* discovery. Cheap to evaluate — it's the size of a call already being made
|
|
202
|
+
* (GitHub/Forgejo's one-shot recursive tree; GitLab's first root-tree page,
|
|
203
|
+
* see below) — and keeps small repos on the walk, which needs no search API
|
|
204
|
+
* at all and so works even where search is unavailable (e.g. a Forgejo
|
|
205
|
+
* instance with no code indexer). */
|
|
206
|
+
const LARGE_REPO_TREE_ENTRIES = 500;
|
|
207
|
+
|
|
208
|
+
/** How many result pages to pull per search term. GitLab's basic (non-
|
|
209
|
+
* Elasticsearch) blob search returns at most ~20 results per query regardless
|
|
210
|
+
* of `per_page` and doesn't paginate past them; GitHub code search is rate-
|
|
211
|
+
* limited (~10 req/min unauthenticated) and caps at 1000 results/query. Either
|
|
212
|
+
* way, a short page (`< per_page`) already stops the loop early — this is just
|
|
213
|
+
* a hard ceiling on round trips per term. */
|
|
214
|
+
const MAX_SEARCH_PAGES = 3;
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Content-detected lexicons: no canonical path, so search-first discovery
|
|
218
|
+
* needs one characteristic content term per lexicon, taken from that lexicon's
|
|
219
|
+
* own `detectTemplate` signature. One row per lexicon — docker gets two
|
|
220
|
+
* (Dockerfile and Compose share nothing in content) — so adding a new
|
|
221
|
+
* content-detected lexicon here is the only step needed to search for it.
|
|
222
|
+
* Ordered most → least selective; the caller de-duplicates by path, so a
|
|
223
|
+
* broader later term (`apiVersion`) adds nothing for files the more specific
|
|
224
|
+
* earlier term (`apiVersion: v2`) already found.
|
|
225
|
+
*/
|
|
226
|
+
const CONTENT_SEARCH_TERMS: Array<{ lexicon: AuditLexicon; term: string }> = [
|
|
227
|
+
{ lexicon: "aws", term: "AWSTemplateFormatVersion" }, // CloudFormation
|
|
228
|
+
{ lexicon: "azure", term: "deploymentTemplate" }, // ARM $schema substring
|
|
229
|
+
{ lexicon: "gcp", term: "cnrm.cloud.google.com" }, // GCP Config Connector
|
|
230
|
+
{ lexicon: "helm", term: "apiVersion: v2" }, // Chart.yaml
|
|
231
|
+
{ lexicon: "docker", term: "FROM " }, // Dockerfiles (space avoids false hits)
|
|
232
|
+
{ lexicon: "docker", term: "services:" }, // Docker Compose
|
|
233
|
+
{ lexicon: "k8s", term: "apiVersion" }, // any k8s resource (broad; runs last)
|
|
234
|
+
];
|
|
235
|
+
|
|
236
|
+
/** GitLab-only: `include:` can pull a CI file in from anywhere, so the
|
|
237
|
+
* canonical-path lexicon still gets a search term (#518, #520). GitHub and
|
|
238
|
+
* Forgejo Actions have no such mechanism, so they need none. */
|
|
239
|
+
const GITLAB_CI_TERM = "stages:";
|
|
240
|
+
|
|
241
|
+
/** One page of GitLab's blob search (`scope=blobs`) for a single term. */
|
|
242
|
+
async function gitlabSearchPage(host: HostConfig, owner: string, repo: string, ref: string, term: string, page: number, doFetch: typeof fetch, headers: Record<string, string>, ms: number): Promise<string[]> {
|
|
243
|
+
const url = `${host.api}/projects/${projectId(owner, repo)}/search?scope=blobs&search=${encodeURIComponent(term)}&per_page=100&page=${page}&ref=${encodeURIComponent(ref)}`;
|
|
244
|
+
const body = await getSearchJsonAt(url, headers, doFetch, ms);
|
|
245
|
+
if (!Array.isArray(body)) return [];
|
|
246
|
+
return (body as Array<{ path?: string }>).map((e) => e.path).filter((p): p is string => typeof p === "string");
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* GitLab content search across every term (CI + content-detected lexicons),
|
|
251
|
+
* de-duplicated by path. Throws on the first request error (a 401 with a bad
|
|
252
|
+
* or absent token, a rate limit, a transient failure) — the caller catches
|
|
253
|
+
* that and falls back to the BFS walk (#520).
|
|
254
|
+
*/
|
|
255
|
+
async function gitlabSearch(host: HostConfig, owner: string, repo: string, ref: string, doFetch: typeof fetch, headers: Record<string, string>, ms: number): Promise<TreeEntry[]> {
|
|
256
|
+
const out: TreeEntry[] = [];
|
|
257
|
+
const seen = new Set<string>();
|
|
258
|
+
const terms = [GITLAB_CI_TERM, ...CONTENT_SEARCH_TERMS.map((t) => t.term)];
|
|
259
|
+
for (const term of terms) {
|
|
260
|
+
for (let page = 1; page <= MAX_SEARCH_PAGES; page++) {
|
|
261
|
+
const hits = await gitlabSearchPage(host, owner, repo, ref, term, page, doFetch, headers, ms);
|
|
262
|
+
if (hits.length === 0) break;
|
|
263
|
+
for (const p of hits) if (!seen.has(p)) { seen.add(p); out.push({ path: p, type: "blob" }); }
|
|
264
|
+
if (hits.length < 100) break;
|
|
192
265
|
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
266
|
+
}
|
|
267
|
+
return out;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Non-recursive BFS over a bounded directory frontier. GitLab's recursive tree
|
|
272
|
+
* API lists ALL directories before any blobs for large repos (#518), so this
|
|
273
|
+
* walks directories breadth-first to ensure root blobs appear on the first
|
|
274
|
+
* request regardless of how many subdirectories follow. `seedRootPage1`, when
|
|
275
|
+
* given, is the root's page-1 entries already fetched by the size probe below
|
|
276
|
+
* — reused here so the small-repo path never double-fetches it.
|
|
277
|
+
*/
|
|
278
|
+
async function gitlabBfsWalk(host: HostConfig, owner: string, repo: string, ref: string, doFetch: typeof fetch, headers: Record<string, string>, ms: number, seedRootPage1?: Array<{ path: string; type: string }>): Promise<TreeEntry[]> {
|
|
279
|
+
const out: TreeEntry[] = [];
|
|
280
|
+
const queue: string[] = [""]; // "" = repo root
|
|
281
|
+
const MAX_DIRS = 30;
|
|
282
|
+
const MAX_BLOBS = 200;
|
|
283
|
+
let dirs = 0;
|
|
284
|
+
while (queue.length > 0 && dirs < MAX_DIRS && out.length < MAX_BLOBS) {
|
|
285
|
+
const dir = queue.shift()!;
|
|
286
|
+
dirs++;
|
|
287
|
+
const pathParam = dir ? `&path=${encodeURIComponent(dir)}` : "";
|
|
288
|
+
for (let page = 1; page <= 5; page++) {
|
|
289
|
+
let body: unknown;
|
|
290
|
+
if (dir === "" && page === 1 && seedRootPage1) {
|
|
291
|
+
body = seedRootPage1;
|
|
292
|
+
} else {
|
|
206
293
|
const url = `${host.api}/projects/${projectId(owner, repo)}/repository/tree?per_page=100&page=${page}&ref=${encodeURIComponent(ref)}${pathParam}`;
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
if (body.length < 100) break;
|
|
294
|
+
({ body } = await getJsonAt(url, headers, doFetch, ms));
|
|
295
|
+
}
|
|
296
|
+
if (!Array.isArray(body) || body.length === 0) break;
|
|
297
|
+
for (const e of body as Array<{ path: string; type: string }>) {
|
|
298
|
+
if (e.type === "blob") out.push({ path: e.path, type: "blob" });
|
|
299
|
+
else if (e.type === "tree") queue.push(e.path);
|
|
214
300
|
}
|
|
301
|
+
if (body.length < 100) break;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
return out;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* GitLab tree discovery: a cheap size probe (the root's first tree page, which
|
|
309
|
+
* every path below needs anyway) decides walk vs. search. GitLab's own
|
|
310
|
+
* recursive-tree endpoint can't be used to size the repo up front — that's the
|
|
311
|
+
* exact pagination trap #518 fixed (directories dominate the page cap before
|
|
312
|
+
* any blob appears) — so "is the root itself large" (a full 100-entry page)
|
|
313
|
+
* stands in for "is the repo large".
|
|
314
|
+
*/
|
|
315
|
+
async function listTreeGitLab(host: HostConfig, owner: string, repo: string, ref: string, doFetch: typeof fetch, headers: Record<string, string>, ms: number): Promise<TreeEntry[]> {
|
|
316
|
+
const rootUrl = `${host.api}/projects/${projectId(owner, repo)}/repository/tree?per_page=100&page=1&ref=${encodeURIComponent(ref)}`;
|
|
317
|
+
const { body: rootBody } = await getJsonAt(rootUrl, headers, doFetch, ms);
|
|
318
|
+
const rootPage1 = Array.isArray(rootBody) ? (rootBody as Array<{ path: string; type: string }>) : [];
|
|
319
|
+
const isLarge = rootPage1.length >= 100;
|
|
320
|
+
|
|
321
|
+
if (isLarge && "PRIVATE-TOKEN" in headers) {
|
|
322
|
+
// GitLab's search API requires authentication even for public projects, so
|
|
323
|
+
// this branch only ever runs with a token. Any failure — no search access,
|
|
324
|
+
// a rate limit, a transient error — falls back to the walk (#520) rather
|
|
325
|
+
// than failing the audit.
|
|
326
|
+
try {
|
|
327
|
+
return await gitlabSearch(host, owner, repo, ref, doFetch, headers, ms);
|
|
328
|
+
} catch {
|
|
329
|
+
// fall through to the walk below
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
return gitlabBfsWalk(host, owner, repo, ref, doFetch, headers, ms, rootPage1);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/** One page of GitHub's code search (`GET /search/code`) for a single term. */
|
|
336
|
+
async function githubSearchPage(host: HostConfig, owner: string, repo: string, term: string, page: number, doFetch: typeof fetch, headers: Record<string, string>, ms: number): Promise<string[]> {
|
|
337
|
+
const q = encodeURIComponent(`${term} repo:${owner}/${repo}`);
|
|
338
|
+
const url = `${host.api}/search/code?q=${q}&per_page=100&page=${page}`;
|
|
339
|
+
const body = await getSearchJsonAt(url, { ...headers, Accept: "application/vnd.github+json" }, doFetch, ms);
|
|
340
|
+
const items = (body as { items?: Array<{ path?: string }> } | null)?.items;
|
|
341
|
+
if (!Array.isArray(items)) return [];
|
|
342
|
+
return items.map((e) => e.path).filter((p): p is string => typeof p === "string");
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* GitHub code search across the content-detected lexicons. Rate-limited to
|
|
347
|
+
* ~10 req/min unauthenticated (#520) — a 403/422/429 throws (via `getSearchJsonAt`)
|
|
348
|
+
* and the caller falls back to the already-fetched full tree.
|
|
349
|
+
*/
|
|
350
|
+
async function githubCodeSearch(host: HostConfig, owner: string, repo: string, doFetch: typeof fetch, headers: Record<string, string>, ms: number): Promise<TreeEntry[]> {
|
|
351
|
+
const out: TreeEntry[] = [];
|
|
352
|
+
const seen = new Set<string>();
|
|
353
|
+
for (const { term } of CONTENT_SEARCH_TERMS) {
|
|
354
|
+
for (let page = 1; page <= MAX_SEARCH_PAGES; page++) {
|
|
355
|
+
const hits = await githubSearchPage(host, owner, repo, term, page, doFetch, headers, ms);
|
|
356
|
+
if (hits.length === 0) break;
|
|
357
|
+
for (const p of hits) if (!seen.has(p)) { seen.add(p); out.push({ path: p, type: "blob" }); }
|
|
358
|
+
if (hits.length < 100) break;
|
|
215
359
|
}
|
|
216
|
-
return out;
|
|
217
360
|
}
|
|
218
|
-
|
|
361
|
+
return out;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* One page of a Forgejo/Gitea repo code search. The shape here mirrors Gitea's
|
|
366
|
+
* `{ok, data}` search envelope (also used by `GET /repos/search`), tolerating
|
|
367
|
+
* a bare array too — Forgejo's code-search REST surface (and whether an
|
|
368
|
+
* instance even runs a code indexer) isn't consistently documented across
|
|
369
|
+
* versions (#520), so this is best-effort: any unexpected shape/status throws
|
|
370
|
+
* and the caller falls back to the full tree, never breaking the audit.
|
|
371
|
+
*/
|
|
372
|
+
async function forgejoSearchPage(host: HostConfig, owner: string, repo: string, term: string, page: number, doFetch: typeof fetch, headers: Record<string, string>, ms: number): Promise<string[]> {
|
|
373
|
+
const url = `${host.api}/repos/${owner}/${repo}/search?q=${encodeURIComponent(term)}&page=${page}&limit=100`;
|
|
374
|
+
const body = await getSearchJsonAt(url, headers, doFetch, ms);
|
|
375
|
+
const data = Array.isArray(body) ? body : (body as { data?: unknown } | null)?.data;
|
|
376
|
+
if (!Array.isArray(data)) return [];
|
|
377
|
+
return (data as Array<{ path?: string }>).map((e) => e.path).filter((p): p is string => typeof p === "string");
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/** Forgejo/Gitea code search across the content-detected lexicons. See `forgejoSearchPage`. */
|
|
381
|
+
async function forgejoCodeSearch(host: HostConfig, owner: string, repo: string, doFetch: typeof fetch, headers: Record<string, string>, ms: number): Promise<TreeEntry[]> {
|
|
382
|
+
const out: TreeEntry[] = [];
|
|
383
|
+
const seen = new Set<string>();
|
|
384
|
+
for (const { term } of CONTENT_SEARCH_TERMS) {
|
|
385
|
+
for (let page = 1; page <= MAX_SEARCH_PAGES; page++) {
|
|
386
|
+
const hits = await forgejoSearchPage(host, owner, repo, term, page, doFetch, headers, ms);
|
|
387
|
+
if (hits.length === 0) break;
|
|
388
|
+
for (const p of hits) if (!seen.has(p)) { seen.add(p); out.push({ path: p, type: "blob" }); }
|
|
389
|
+
if (hits.length < 100) break;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
return out;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* GitHub/Forgejo tree discovery: their `git/trees?recursive=1` API returns the
|
|
397
|
+
* whole tree in one call (no GitLab-style ordering trap), so the entry count
|
|
398
|
+
* from that single call is a free large-repo signal. Below the threshold,
|
|
399
|
+
* behavior is unchanged — the full blob list, later filtered by
|
|
400
|
+
* `isCandidatePath`. Above it, known-path lexicons (CI) are kept straight from
|
|
401
|
+
* the tree we already have — free, no search needed — and the content-detected
|
|
402
|
+
* lexicons are found by search instead of downloaded wholesale.
|
|
403
|
+
*/
|
|
404
|
+
async function listTreeGitHubLike(host: HostConfig, owner: string, repo: string, ref: string, doFetch: typeof fetch, headers: Record<string, string>, ms: number): Promise<TreeEntry[]> {
|
|
219
405
|
const url = `${host.api}/repos/${owner}/${repo}/git/trees/${encodeURIComponent(ref)}?recursive=1`;
|
|
220
406
|
const { body } = await getJsonAt(url, headers, doFetch, ms);
|
|
221
|
-
const
|
|
407
|
+
const treeBody = body as { tree?: TreeEntry[]; truncated?: boolean } | null;
|
|
408
|
+
const tree = treeBody?.tree;
|
|
222
409
|
if (!Array.isArray(tree)) return [];
|
|
223
|
-
|
|
410
|
+
const blobs = tree.filter((e) => e.type === "blob").map((e) => ({ path: e.path, type: "blob", size: e.size }));
|
|
411
|
+
|
|
412
|
+
const isLarge = blobs.length > LARGE_REPO_TREE_ENTRIES || treeBody?.truncated === true;
|
|
413
|
+
if (!isLarge) return blobs;
|
|
414
|
+
|
|
415
|
+
const { ciLexiconForPath } = await import("./discover");
|
|
416
|
+
const known = blobs.filter((e) => ciLexiconForPath(e.path));
|
|
417
|
+
try {
|
|
418
|
+
const hits = host.kind === "github"
|
|
419
|
+
? await githubCodeSearch(host, owner, repo, doFetch, headers, ms)
|
|
420
|
+
: await forgejoCodeSearch(host, owner, repo, doFetch, headers, ms);
|
|
421
|
+
const knownPaths = new Set(known.map((e) => e.path));
|
|
422
|
+
return [...known, ...hits.filter((h) => !knownPaths.has(h.path))];
|
|
423
|
+
} catch {
|
|
424
|
+
// Search unavailable/errored/rate-limited — degrade to the full tree we
|
|
425
|
+
// already have, filtered by isCandidatePath downstream, same as a small
|
|
426
|
+
// repo (#520: search is additive, never load-bearing).
|
|
427
|
+
return blobs;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/** List blob paths worth considering for download (all lexicons), choosing walk vs. search-first per host (#520). */
|
|
432
|
+
async function listTree(host: HostConfig, owner: string, repo: string, ref: string, doFetch: typeof fetch, headers: Record<string, string>, ms: number): Promise<TreeEntry[]> {
|
|
433
|
+
if (host.kind === "gitlab") return listTreeGitLab(host, owner, repo, ref, doFetch, headers, ms);
|
|
434
|
+
return listTreeGitHubLike(host, owner, repo, ref, doFetch, headers, ms);
|
|
224
435
|
}
|
|
225
436
|
|
|
226
437
|
/**
|
package/src/audit/report-html.ts
CHANGED
|
@@ -87,7 +87,10 @@ function renderNeedsReview(clusters: GuidanceCluster[], n: number): string {
|
|
|
87
87
|
const rules = c.rules
|
|
88
88
|
.map(({ meta, findings }) => {
|
|
89
89
|
const locs = findings
|
|
90
|
-
.map((f) =>
|
|
90
|
+
.map((f) => {
|
|
91
|
+
const loc = f.line ? `${f.file}:${f.line}` : f.file;
|
|
92
|
+
return `<li><code>${esc(loc)}</code>${f.entity ? ` (<code>${esc(f.entity)}</code>)` : ""} — ${esc(f.message)}</li>`;
|
|
93
|
+
})
|
|
91
94
|
.join("");
|
|
92
95
|
return `<div class="rule"><div><span class="sev ${findings[0].severity}"></span><strong>${ruleLink(meta.id)}</strong> — ${esc(meta.title)}. <span class="muted">${esc(meta.remediation)}</span>${authorityLinks(meta)}</div><ul>${locs}</ul></div>`;
|
|
93
96
|
})
|
|
@@ -108,7 +111,7 @@ function renderReportOnly(findings: EnrichedFinding[], n: number): string {
|
|
|
108
111
|
function renderHeader(counts: ReportCounts, snapshot: AuditSnapshot | undefined, notes: string[]): string {
|
|
109
112
|
const sev = `<span class="sev error"></span>${counts.errors} error <span class="sev warning"></span>${counts.warnings} warning <span class="sev info"></span>${counts.infos} info`;
|
|
110
113
|
const tiers = `<span class="chip">${counts.quickWin} quick-win</span> <span class="chip">${counts.needsReview} needs-review</span> <span class="chip">${counts.reportOnly} hygiene</span>`;
|
|
111
|
-
const cats = `<span class="chip">${counts.security} security</span> <span class="chip">${counts.correctness} correctness</span> <span class="chip">${counts.bestPractice} best-practice</span>`;
|
|
114
|
+
const cats = `<span class="chip">${counts.security} security</span> <span class="chip">${counts.correctness} correctness</span> <span class="chip">${counts.bestPractice} best-practice</span> <span class="chip">${counts.efficiency} efficiency</span>`;
|
|
112
115
|
const meta: string[] = [];
|
|
113
116
|
if (snapshot) {
|
|
114
117
|
if (snapshot.host) meta.push(esc(snapshot.host));
|
|
@@ -62,6 +62,8 @@ export interface ReportCounts {
|
|
|
62
62
|
security: number;
|
|
63
63
|
correctness: number;
|
|
64
64
|
bestPractice: number;
|
|
65
|
+
/** Waste findings (#444) — excluded from `security`/`correctness` by construction (disjoint categories). */
|
|
66
|
+
efficiency: number;
|
|
65
67
|
}
|
|
66
68
|
|
|
67
69
|
export interface ReportModel {
|
|
@@ -92,6 +94,10 @@ export interface SerializedFinding {
|
|
|
92
94
|
message: string;
|
|
93
95
|
file: string;
|
|
94
96
|
entity?: string;
|
|
97
|
+
/** 1-based line within `file`, when the finding can pin one (e.g. secrets detection, #443). */
|
|
98
|
+
line?: number;
|
|
99
|
+
/** Redaction-safe fingerprint of a flagged value, when applicable (see `secrets.ts`). */
|
|
100
|
+
fingerprint?: string;
|
|
95
101
|
lexicon: string;
|
|
96
102
|
tier: Tier;
|
|
97
103
|
fixKind: FixKind;
|
|
@@ -265,6 +271,7 @@ export function buildReportModel(findings: AuditFinding[], opts: BuildModelOptio
|
|
|
265
271
|
security: shown.filter((f) => f.meta.category === "security").length,
|
|
266
272
|
correctness: shown.filter((f) => f.meta.category === "correctness").length,
|
|
267
273
|
bestPractice: shown.filter((f) => f.meta.category === "best-practice").length,
|
|
274
|
+
efficiency: shown.filter((f) => f.meta.category === "efficiency").length,
|
|
268
275
|
};
|
|
269
276
|
|
|
270
277
|
return {
|
|
@@ -295,6 +302,8 @@ export function buildReportJson(
|
|
|
295
302
|
message: f.message,
|
|
296
303
|
file: f.file,
|
|
297
304
|
entity: f.entity,
|
|
305
|
+
line: f.line,
|
|
306
|
+
fingerprint: f.fingerprint,
|
|
298
307
|
lexicon: f.lexicon,
|
|
299
308
|
tier: f.meta.tier,
|
|
300
309
|
fixKind: f.meta.fixKind,
|
package/src/audit/report.test.ts
CHANGED
|
@@ -127,6 +127,28 @@ describe("renderMarkdown — reworked structure", () => {
|
|
|
127
127
|
});
|
|
128
128
|
});
|
|
129
129
|
|
|
130
|
+
describe("efficiency dimension (#444)", () => {
|
|
131
|
+
test("buildReportModel tallies efficiency separately, excluded from security/correctness", async () => {
|
|
132
|
+
const { buildReportModel } = await import("./report-model");
|
|
133
|
+
const findings: AuditFinding[] = [
|
|
134
|
+
{ checkId: "GHA063", severity: "info", message: "no cache.", file: ".github/workflows/ci.yml", lexicon: "github", entity: "build" }, // efficiency
|
|
135
|
+
{ checkId: "GHA021", severity: "warning", message: "unpinned checkout.", file: ".github/workflows/ci.yml", lexicon: "github", entity: "build" }, // security
|
|
136
|
+
{ checkId: "GHA028", severity: "error", message: "no triggers.", file: ".github/workflows/ci.yml", lexicon: "github" }, // correctness
|
|
137
|
+
];
|
|
138
|
+
const { counts } = buildReportModel(findings, { catalog: CATALOG });
|
|
139
|
+
expect(counts.efficiency).toBe(1);
|
|
140
|
+
expect(counts.security).toBe(1);
|
|
141
|
+
expect(counts.correctness).toBe(1);
|
|
142
|
+
// the efficiency finding must not have leaked into either tally
|
|
143
|
+
expect(counts.security + counts.correctness + counts.bestPractice + counts.efficiency).toBe(counts.total);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test("markdown report surfaces the efficiency tally in the By category line", () => {
|
|
147
|
+
const out = md([{ checkId: "GHA063", severity: "info", message: "no cache.", file: ".github/workflows/ci.yml", lexicon: "github", entity: "build" }]);
|
|
148
|
+
expect(out).toContain("By category: 0 security, 0 correctness, 0 best-practice, 1 efficiency.");
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
130
152
|
describe("catalog threading (#687)", () => {
|
|
131
153
|
test("buildReportModel uses a passed-in catalog over core's static one", async () => {
|
|
132
154
|
const { buildReportModel } = await import("./report-model");
|
package/src/audit/report.ts
CHANGED
|
@@ -72,7 +72,8 @@ function renderNeedsReview(clusters: GuidanceCluster[]): string[] {
|
|
|
72
72
|
for (const { meta, findings } of cluster.rules) {
|
|
73
73
|
lines.push(`- **${ruleLink(meta.id)}** — ${meta.title} (${findings[0].severity}). ${meta.remediation}${authorityLinks(meta)}`);
|
|
74
74
|
for (const f of findings) {
|
|
75
|
-
const
|
|
75
|
+
const loc = f.line ? `${f.file}:${f.line}` : f.file;
|
|
76
|
+
const where = f.entity ? `\`${loc}\` (\`${f.entity}\`)` : `\`${loc}\``;
|
|
76
77
|
lines.push(` - ${where} — ${escapeCell(f.message)}`);
|
|
77
78
|
}
|
|
78
79
|
}
|
|
@@ -110,7 +111,7 @@ export function renderMarkdown(findings: AuditFinding[], opts: RenderOptions = {
|
|
|
110
111
|
`${counts.quickWin} quick-win, ${counts.needsReview} needs-review, ${counts.reportOnly} report-only ` +
|
|
111
112
|
`(${counts.errors} error, ${counts.warnings} warning, ${counts.infos} info).`,
|
|
112
113
|
"",
|
|
113
|
-
`By category: ${counts.security} security, ${counts.correctness} correctness, ${counts.bestPractice} best-practice.`,
|
|
114
|
+
`By category: ${counts.security} security, ${counts.correctness} correctness, ${counts.bestPractice} best-practice, ${counts.efficiency} efficiency.`,
|
|
114
115
|
"",
|
|
115
116
|
);
|
|
116
117
|
|
package/src/audit/rules-doc.ts
CHANGED
|
@@ -21,6 +21,8 @@ const GROUPS: Array<{ heading: string; prefixes: string[]; blurb: string }> = [
|
|
|
21
21
|
{ heading: "GCP Config Connector (WGC)", prefixes: ["WGC"], blurb: "Run against Config Connector (cnrm.cloud.google.com) manifests." },
|
|
22
22
|
{ heading: "Helm (WHM)", prefixes: ["WHM"], blurb: "Run against Helm charts (Chart.yaml + templates)." },
|
|
23
23
|
{ heading: "fountain (FTN)", prefixes: ["FTN"], blurb: "Run against fountain manifests (`apiVersion: fountain.dev/v1`) — standalone `fountain apply` YAML is parsed back into the entity graph, so the same rules fire on `chant build` and `chant audit`." },
|
|
24
|
+
{ heading: "Secrets & credentials (SEC)", prefixes: ["SEC"], blurb: "Lexicon-independent — scans the raw text of every scanned file for likely credentials, regardless of which audit lexicons are installed. Matched values are always redacted; see [suppressing false positives](/chant/cli/audit/#suppressing-a-secrets-finding)." },
|
|
25
|
+
{ heading: "Wrangler config (WRG)", prefixes: ["WRG"], blurb: "Lexicon-independent, audit-only (#446) — scans `wrangler.toml`, Cloudflare Workers' native deploy config, which the engine cannot otherwise parse (it is not YAML/JSON). No authoring surface: chant does not write Wrangler config, it only reads it for these checks." },
|
|
24
26
|
];
|
|
25
27
|
|
|
26
28
|
/**
|