@sagentix-inc/evidence-collector 0.3.1 → 0.3.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.
@@ -21,5 +21,209 @@
21
21
  "url": "https://github.com/prowler-cloud/prowler"
22
22
  },
23
23
  "provider": "okta",
24
- "checks": []
24
+ "checks": [
25
+ {
26
+ "id": "prowler.okta.apitoken_not_super_admin",
27
+ "title": "Okta API tokens are not owned by Super Admin users",
28
+ "scope": "Prowler 5.47.0 check apitoken_not_super_admin (Okta apitoken, resource type NotDefined): one result per resource Prowler evaluates; shows one setting, not the whole control.",
29
+ "evidenceType": "Configuration",
30
+ "severity": "medium"
31
+ },
32
+ {
33
+ "id": "prowler.okta.apitoken_restricted_to_network_zone",
34
+ "title": "Okta API tokens are restricted to known Network Zones",
35
+ "scope": "Prowler 5.47.0 check apitoken_restricted_to_network_zone (Okta apitoken, resource type NotDefined): one result per resource Prowler evaluates; shows one setting, not the whole control.",
36
+ "evidenceType": "Configuration",
37
+ "severity": "medium"
38
+ },
39
+ {
40
+ "id": "prowler.okta.application_admin_console_mfa_required",
41
+ "title": "Okta Admin Console authentication policy enforces multifactor authentication",
42
+ "scope": "Prowler 5.47.0 check application_admin_console_mfa_required (Okta application, resource type NotDefined): one result per resource Prowler evaluates; shows one setting, not the whole control.",
43
+ "evidenceType": "Configuration",
44
+ "severity": "high"
45
+ },
46
+ {
47
+ "id": "prowler.okta.application_admin_console_phishing_resistant_authentication",
48
+ "title": "Okta Admin Console authentication policy enforces phishing-resistant factors",
49
+ "scope": "Prowler 5.47.0 check application_admin_console_phishing_resistant_authentication (Okta application, resource type NotDefined): one result per resource Prowler evaluates; shows one setting, not the whole control.",
50
+ "evidenceType": "Configuration",
51
+ "severity": "medium"
52
+ },
53
+ {
54
+ "id": "prowler.okta.application_admin_console_session_idle_timeout_15min",
55
+ "title": "Okta Admin Console app session idle timeout is 15 minutes or less",
56
+ "scope": "Prowler 5.47.0 check application_admin_console_session_idle_timeout_15min (Okta application, resource type NotDefined): one result per resource Prowler evaluates; shows one setting, not the whole control.",
57
+ "evidenceType": "Configuration",
58
+ "severity": "medium"
59
+ },
60
+ {
61
+ "id": "prowler.okta.application_authentication_policy_network_zone_enforced",
62
+ "title": "Okta application authentication policies enforce Network Zones",
63
+ "scope": "Prowler 5.47.0 check application_authentication_policy_network_zone_enforced (Okta application, resource type NotDefined): one result per resource Prowler evaluates; shows one setting, not the whole control.",
64
+ "evidenceType": "Configuration",
65
+ "severity": "medium"
66
+ },
67
+ {
68
+ "id": "prowler.okta.application_dashboard_mfa_required",
69
+ "title": "Okta Dashboard authentication policy enforces multifactor authentication",
70
+ "scope": "Prowler 5.47.0 check application_dashboard_mfa_required (Okta application, resource type NotDefined): one result per resource Prowler evaluates; shows one setting, not the whole control.",
71
+ "evidenceType": "Configuration",
72
+ "severity": "high"
73
+ },
74
+ {
75
+ "id": "prowler.okta.application_dashboard_phishing_resistant_authentication",
76
+ "title": "Okta Dashboard authentication policy enforces phishing-resistant factors",
77
+ "scope": "Prowler 5.47.0 check application_dashboard_phishing_resistant_authentication (Okta application, resource type NotDefined): one result per resource Prowler evaluates; shows one setting, not the whole control.",
78
+ "evidenceType": "Configuration",
79
+ "severity": "medium"
80
+ },
81
+ {
82
+ "id": "prowler.okta.authenticator_okta_verify_fips_compliant",
83
+ "title": "Okta Verify authenticator is active and restricts enrollment to FIPS-compliant devices",
84
+ "scope": "Prowler 5.47.0 check authenticator_okta_verify_fips_compliant (Okta authenticator, resource type NotDefined): one result per resource Prowler evaluates; shows one setting, not the whole control.",
85
+ "evidenceType": "Configuration",
86
+ "severity": "medium"
87
+ },
88
+ {
89
+ "id": "prowler.okta.authenticator_password_common_password_check",
90
+ "title": "Every active Okta Password Policy rejects common passwords",
91
+ "scope": "Prowler 5.47.0 check authenticator_password_common_password_check (Okta authenticator, resource type NotDefined): one result per resource Prowler evaluates; shows one setting, not the whole control.",
92
+ "evidenceType": "Configuration",
93
+ "severity": "medium"
94
+ },
95
+ {
96
+ "id": "prowler.okta.authenticator_password_complexity_lowercase",
97
+ "title": "Every active Okta Password Policy requires at least one lowercase character",
98
+ "scope": "Prowler 5.47.0 check authenticator_password_complexity_lowercase (Okta authenticator, resource type NotDefined): one result per resource Prowler evaluates; shows one setting, not the whole control.",
99
+ "evidenceType": "Configuration",
100
+ "severity": "low"
101
+ },
102
+ {
103
+ "id": "prowler.okta.authenticator_password_complexity_number",
104
+ "title": "Every active Okta Password Policy requires at least one numeric character",
105
+ "scope": "Prowler 5.47.0 check authenticator_password_complexity_number (Okta authenticator, resource type NotDefined): one result per resource Prowler evaluates; shows one setting, not the whole control.",
106
+ "evidenceType": "Configuration",
107
+ "severity": "low"
108
+ },
109
+ {
110
+ "id": "prowler.okta.authenticator_password_complexity_symbol",
111
+ "title": "Every active Okta Password Policy requires at least one symbol character",
112
+ "scope": "Prowler 5.47.0 check authenticator_password_complexity_symbol (Okta authenticator, resource type NotDefined): one result per resource Prowler evaluates; shows one setting, not the whole control.",
113
+ "evidenceType": "Configuration",
114
+ "severity": "low"
115
+ },
116
+ {
117
+ "id": "prowler.okta.authenticator_password_complexity_uppercase",
118
+ "title": "Every active Okta Password Policy requires at least one uppercase character",
119
+ "scope": "Prowler 5.47.0 check authenticator_password_complexity_uppercase (Okta authenticator, resource type NotDefined): one result per resource Prowler evaluates; shows one setting, not the whole control.",
120
+ "evidenceType": "Configuration",
121
+ "severity": "low"
122
+ },
123
+ {
124
+ "id": "prowler.okta.authenticator_password_history_5",
125
+ "title": "Every active Okta Password Policy remembers at least 5 previous passwords",
126
+ "scope": "Prowler 5.47.0 check authenticator_password_history_5 (Okta authenticator, resource type NotDefined): one result per resource Prowler evaluates; shows one setting, not the whole control.",
127
+ "evidenceType": "Configuration",
128
+ "severity": "low"
129
+ },
130
+ {
131
+ "id": "prowler.okta.authenticator_password_lockout_threshold_3",
132
+ "title": "Every active Okta Password Policy locks accounts after 3 or fewer failed attempts",
133
+ "scope": "Prowler 5.47.0 check authenticator_password_lockout_threshold_3 (Okta authenticator, resource type NotDefined): one result per resource Prowler evaluates; shows one setting, not the whole control.",
134
+ "evidenceType": "Configuration",
135
+ "severity": "medium"
136
+ },
137
+ {
138
+ "id": "prowler.okta.authenticator_password_maximum_age_60d",
139
+ "title": "Every active Okta Password Policy enforces a maximum password age of 60 days or less",
140
+ "scope": "Prowler 5.47.0 check authenticator_password_maximum_age_60d (Okta authenticator, resource type NotDefined): one result per resource Prowler evaluates; shows one setting, not the whole control.",
141
+ "evidenceType": "Configuration",
142
+ "severity": "low"
143
+ },
144
+ {
145
+ "id": "prowler.okta.authenticator_password_minimum_age_24h",
146
+ "title": "Every active Okta Password Policy enforces a minimum password age of 24 hours",
147
+ "scope": "Prowler 5.47.0 check authenticator_password_minimum_age_24h (Okta authenticator, resource type NotDefined): one result per resource Prowler evaluates; shows one setting, not the whole control.",
148
+ "evidenceType": "Configuration",
149
+ "severity": "low"
150
+ },
151
+ {
152
+ "id": "prowler.okta.authenticator_password_minimum_length_15",
153
+ "title": "Every active Okta Password Policy enforces a minimum length of 15 characters",
154
+ "scope": "Prowler 5.47.0 check authenticator_password_minimum_length_15 (Okta authenticator, resource type NotDefined): one result per resource Prowler evaluates; shows one setting, not the whole control.",
155
+ "evidenceType": "Configuration",
156
+ "severity": "medium"
157
+ },
158
+ {
159
+ "id": "prowler.okta.authenticator_smart_card_active",
160
+ "title": "Okta Smart Card IdP authenticator is configured and active",
161
+ "scope": "Prowler 5.47.0 check authenticator_smart_card_active (Okta authenticator, resource type NotDefined): one result per resource Prowler evaluates; shows one setting, not the whole control.",
162
+ "evidenceType": "Configuration",
163
+ "severity": "medium"
164
+ },
165
+ {
166
+ "id": "prowler.okta.idp_smart_card_dod_approved_ca",
167
+ "title": "Okta Smart Card (X509) Identity Provider uses a DOD-approved certificate authority",
168
+ "scope": "Prowler 5.47.0 check idp_smart_card_dod_approved_ca (Okta idp, resource type NotDefined): one result per resource Prowler evaluates; shows one setting, not the whole control.",
169
+ "evidenceType": "Configuration",
170
+ "severity": "medium"
171
+ },
172
+ {
173
+ "id": "prowler.okta.network_zone_block_anonymized_proxies",
174
+ "title": "Okta uses active Network Zone blocklists for anonymized proxy sources",
175
+ "scope": "Prowler 5.47.0 check network_zone_block_anonymized_proxies (Okta network, resource type NotDefined): one result per resource Prowler evaluates; shows one setting, not the whole control.",
176
+ "evidenceType": "Configuration",
177
+ "severity": "medium"
178
+ },
179
+ {
180
+ "id": "prowler.okta.signon_dod_warning_banner_configured",
181
+ "title": "Okta sign-in page displays the Standard Mandatory DOD Notice and Consent Banner",
182
+ "scope": "Prowler 5.47.0 check signon_dod_warning_banner_configured (Okta signon, resource type NotDefined): one result per resource Prowler evaluates; shows one setting, not the whole control.",
183
+ "evidenceType": "Configuration",
184
+ "severity": "informational"
185
+ },
186
+ {
187
+ "id": "prowler.okta.signon_global_session_cookies_not_persistent",
188
+ "title": "Default Global Session Policy has a Priority 1 non-default rule disabling persistent global session cookies",
189
+ "scope": "Prowler 5.47.0 check signon_global_session_cookies_not_persistent (Okta signon, resource type NotDefined): one result per resource Prowler evaluates; shows one setting, not the whole control.",
190
+ "evidenceType": "Configuration",
191
+ "severity": "medium"
192
+ },
193
+ {
194
+ "id": "prowler.okta.signon_global_session_idle_timeout_15min",
195
+ "title": "Default Global Session Policy has a Priority 1 non-default rule enforcing 15-minute idle timeout",
196
+ "scope": "Prowler 5.47.0 check signon_global_session_idle_timeout_15min (Okta signon, resource type NotDefined): one result per resource Prowler evaluates; shows one setting, not the whole control.",
197
+ "evidenceType": "Configuration",
198
+ "severity": "medium"
199
+ },
200
+ {
201
+ "id": "prowler.okta.signon_global_session_lifetime_18h",
202
+ "title": "Default Global Session Policy has a Priority 1 non-default rule limiting session lifetime to 18 hours",
203
+ "scope": "Prowler 5.47.0 check signon_global_session_lifetime_18h (Okta signon, resource type NotDefined): one result per resource Prowler evaluates; shows one setting, not the whole control.",
204
+ "evidenceType": "Configuration",
205
+ "severity": "medium"
206
+ },
207
+ {
208
+ "id": "prowler.okta.signon_global_session_policy_network_zone_enforced",
209
+ "title": "Default Global Session Policy applies a Network Zone condition aligned with the Access Control Policy",
210
+ "scope": "Prowler 5.47.0 check signon_global_session_policy_network_zone_enforced (Okta signon, resource type NotDefined): one result per resource Prowler evaluates; shows one setting, not the whole control.",
211
+ "evidenceType": "Configuration",
212
+ "severity": "medium"
213
+ },
214
+ {
215
+ "id": "prowler.okta.systemlog_streaming_enabled",
216
+ "title": "Okta off-loads audit records to a central log server via Log Streaming",
217
+ "scope": "Prowler 5.47.0 check systemlog_streaming_enabled (Okta systemlog, resource type NotDefined): one result per resource Prowler evaluates; shows one setting, not the whole control.",
218
+ "evidenceType": "Configuration",
219
+ "severity": "high"
220
+ },
221
+ {
222
+ "id": "prowler.okta.user_inactivity_automation_35d_enabled",
223
+ "title": "Okta automation suspends or deactivates users after 35 days of inactivity",
224
+ "scope": "Prowler 5.47.0 check user_inactivity_automation_35d_enabled (Okta user, resource type NotDefined): one result per resource Prowler evaluates; shows one setting, not the whole control.",
225
+ "evidenceType": "Configuration",
226
+ "severity": "medium"
227
+ }
228
+ ]
25
229
  }
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "@sagentix-inc/evidence-collector",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "type": "module"
5
5
  }
@@ -31,6 +31,7 @@ export const REFUSED_ARGS = [
31
31
  ];
32
32
  /** Environment variables whose values never leave the machine, even if Prowler echoes them in a detail. */
33
33
  const SECRET_ENVS = [
34
+ "GITHUB_TOKEN", "CLOUDFLARE_API_TOKEN_READONLY", "ENTRA_CLIENT_SECRET", "M365_CERTIFICATE_CONTENT",
34
35
  "AWS_SECRET_ACCESS_KEY", "AWS_SESSION_TOKEN", "AZURE_CLIENT_SECRET", "GITHUB_PERSONAL_ACCESS_TOKEN", "GITHUB_OAUTH_APP_TOKEN",
35
36
  "GITHUB_APP_KEY", "OKTA_PRIVATE_KEY", "CLOUDFLARE_API_TOKEN", "CLOUDFLARE_API_KEY", "GOOGLEWORKSPACE_CREDENTIALS_CONTENT",
36
37
  ];
@@ -43,7 +44,12 @@ function catalogIds(dir = DEFAULT_CATALOG_DIR) {
43
44
  return [];
44
45
  }
45
46
  }
46
- const one = (v) => (typeof v === "string" && v.trim() ? v.trim() : undefined);
47
+ const one = (v) => {
48
+ const x = Array.isArray(v) ? v[0] : v;
49
+ return typeof x === "string" && x.trim() ? x.trim() : undefined;
50
+ };
51
+ /** --prowler-provider aws,azure or repeated, in order, without repeats. */
52
+ const providersOf = (v) => [...new Set((Array.isArray(v) ? v : typeof v === "string" ? [v] : []).flatMap((x) => String(x).split(",")).map((x) => x.trim()).filter(Boolean))];
47
53
  /** Splits --prowler-args like a shell would for plain words and '…' or "…" quoting (no expansion). */
48
54
  export function splitArgs(s) {
49
55
  const out = [];
@@ -55,11 +61,22 @@ export function splitArgs(s) {
55
61
  }
56
62
  /** The provider's authentication flag, chosen from what the environment holds. Everything else Prowler reads from env itself. */
57
63
  export function authArgs(provider, env) {
64
+ // The generated workflow names what the collector's own source reads, so Prowler reads the same scope
65
+ // (Prowler 5.47.0: aws --region, azure --subscription-ids, gcp --project-ids).
66
+ const scope = (name, flag) => { const v = (env[name] ?? "").split(/\s+/).filter(Boolean); return v.length ? [flag, ...v] : []; };
58
67
  switch (provider) {
59
- case "azure":
68
+ case "aws": return scope("SAGENTIX_PROWLER_AWS_REGIONS", "--region");
69
+ case "gcp": return scope("SAGENTIX_PROWLER_GCP_PROJECTS", "--project-ids");
60
70
  case "m365":
71
+ // A certificate signs in every Microsoft 365 service, Exchange and Teams included; a secret covers Graph only.
72
+ // --init-modules installs the PowerShell modules those services need (an ephemeral runner has none).
73
+ if (env.M365_CERTIFICATE_CONTENT)
74
+ return ["--certificate-auth", "--init-modules"];
75
+ return [...(env.AZURE_CLIENT_SECRET ? ["--sp-env-auth"] : ["--az-cli-auth"]), "--init-modules"];
76
+ case "azure":
61
77
  // A client secret in env: service principal; otherwise the session `azure/login` (OIDC) left for the az CLI.
62
- return env.AZURE_CLIENT_SECRET ? ["--sp-env-auth"] : ["--az-cli-auth"];
78
+ return [...(env.AZURE_CLIENT_SECRET ? ["--sp-env-auth"] : ["--az-cli-auth"]),
79
+ ...(provider === "azure" ? scope("SAGENTIX_PROWLER_AZURE_SUBSCRIPTIONS", "--subscription-ids") : [])];
63
80
  case "kubernetes":
64
81
  return env.KUBECONFIG ? ["--kubeconfig-file", env.KUBECONFIG] : [];
65
82
  default:
@@ -68,6 +85,25 @@ export function authArgs(provider, env) {
68
85
  return [];
69
86
  }
70
87
  }
88
+ /**
89
+ * Prowler reads its own variable names; the collector's native sources read theirs. So the same read-only credential
90
+ * the client already stored serves both ways (2026-11-01: each control collected at least two ways): the collector's
91
+ * name is copied to Prowler's when Prowler's is unset. Names from prowler/providers/<provider> in Prowler 5.47.0.
92
+ */
93
+ export function prowlerEnv(provider, env) {
94
+ const alias = {
95
+ github: { GITHUB_PERSONAL_ACCESS_TOKEN: "GITHUB_TOKEN" },
96
+ cloudflare: { CLOUDFLARE_API_TOKEN: "CLOUDFLARE_API_TOKEN_READONLY" },
97
+ googleworkspace: { GOOGLEWORKSPACE_CREDENTIALS_FILE: "GOOGLE_SA_KEY_FILE", GOOGLEWORKSPACE_DELEGATED_USER: "GOOGLE_ADMIN_EMAIL" },
98
+ m365: { AZURE_TENANT_ID: "ENTRA_TENANT_ID", AZURE_CLIENT_ID: "ENTRA_CLIENT_ID", AZURE_CLIENT_SECRET: "ENTRA_CLIENT_SECRET" },
99
+ okta: { OKTA_ORG_DOMAIN: "OKTA_DOMAIN" },
100
+ };
101
+ const out = { ...env };
102
+ for (const [theirs, ours] of Object.entries(alias[provider] ?? {}))
103
+ if (!out[theirs] && env[ours])
104
+ out[theirs] = env[ours];
105
+ return out;
106
+ }
71
107
  const isObj = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
72
108
  const s = (v) => (typeof v === "string" ? v : "");
73
109
  const EMAIL = /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/g;
@@ -184,44 +220,63 @@ export async function runProwler(provider, o) {
184
220
  export const prowler = {
185
221
  name: "prowler",
186
222
  options: {
187
- "prowler-provider": { type: "string", help: `Prowler provider to run: ${PROWLER_PROVIDERS.join(", ")}` },
223
+ "prowler-provider": { type: "string", multiple: true, help: `Prowler providers to run, comma-separated: ${PROWLER_PROVIDERS.join(", ")} (one failing never stops the others)` },
188
224
  "prowler-bin": { type: "string", help: "the prowler executable (default prowler on PATH)" },
189
225
  "prowler-args": { type: "string", help: "extra Prowler options, written --prowler-args=\"--region us-east-1 ca-central-1\" (never output, send or fix options)" },
190
226
  "prowler-output": { type: "string", help: "read this json-ocsf file instead of running Prowler" },
191
227
  },
192
228
  emits: catalogIds(),
193
229
  describe(opts) {
194
- const p = one(opts["prowler-provider"]) ?? "(no provider)";
230
+ const p = providersOf(opts["prowler-provider"]).join(", ") || "(no provider)";
195
231
  return one(opts["prowler-output"]) ? `Prowler ${p} findings from a json-ocsf file` : `Prowler ${PROWLER_VERSION} ${p} scan`;
196
232
  },
197
233
  async run(ctx) {
198
- const provider = one(ctx.opts["prowler-provider"]);
199
- if (!provider || !PROWLER_PROVIDERS.includes(provider)) {
200
- throw new Error(`--prowler-provider is required: one of ${PROWLER_PROVIDERS.join(", ")}.`);
201
- }
202
- const known = new Set(catalogIds(one(ctx.opts.catalog) ?? DEFAULT_CATALOG_DIR));
203
- if (![...known].some((id) => id.startsWith(`prowler.${provider}.`))) {
204
- ctx.warn(`prowler: sos-app's catalogue lists no ${provider} check (Prowler maps none to a KSI or SP 800-53 control); every finding will be dropped.`);
234
+ const providers = providersOf(ctx.opts["prowler-provider"]);
235
+ const bad = providers.filter((p) => !PROWLER_PROVIDERS.includes(p));
236
+ if (!providers.length || bad.length) {
237
+ throw new Error(`--prowler-provider is required: one or more of ${PROWLER_PROVIDERS.join(", ")}${bad.length ? ` (not: ${bad.join(", ")})` : ""}.`);
205
238
  }
206
239
  const file = one(ctx.opts["prowler-output"]);
207
- let text;
208
- if (file) {
240
+ if (file && providers.length > 1)
241
+ throw new Error("--prowler-output reads one provider's file; name one --prowler-provider with it.");
242
+ const known = new Set(catalogIds(one(ctx.opts.catalog) ?? DEFAULT_CATALOG_DIR));
243
+ const results = [];
244
+ const failures = [];
245
+ for (const provider of providers) {
246
+ if (![...known].some((id) => id.startsWith(`prowler.${provider}.`))) {
247
+ ctx.warn(`prowler: sos-app's catalogue lists no ${provider} check; every finding will be dropped.`);
248
+ }
249
+ let text;
209
250
  try {
210
- text = readFileSync(file, "utf8");
251
+ if (file) {
252
+ try {
253
+ text = readFileSync(file, "utf8");
254
+ }
255
+ catch (e) {
256
+ throw new Error(`cannot read --prowler-output ${file}: ${e.code ?? String(e)}`);
257
+ }
258
+ }
259
+ else {
260
+ const spawner = ctx.clients.prowler ?? spawnProwler;
261
+ const extra = one(ctx.opts["prowler-args"]) ? splitArgs(one(ctx.opts["prowler-args"])) : [];
262
+ text = await runProwler(provider, { bin: one(ctx.opts["prowler-bin"]) ?? "prowler", extra, env: prowlerEnv(provider, ctx.env), spawner });
263
+ }
211
264
  }
212
265
  catch (e) {
213
- throw new Error(`cannot read --prowler-output ${file}: ${e.code ?? String(e)}`);
266
+ // A second way that fails leaves the control to its other ways: say so and go on with the next provider.
267
+ failures.push(e);
268
+ if (providers.length > 1)
269
+ ctx.warn(`prowler: ${provider} did not run (${e.message}); its controls keep their other ways.`);
270
+ continue;
214
271
  }
272
+ const { results: r, summary } = parseOcsf(text, provider, known);
273
+ results.push(...r);
274
+ ctx.warn(`prowler: ${summary.findings} findings; ${summary.kept} results (${summary.merged} merged into the same check and resource, ` +
275
+ `${summary.muted} muted and ${summary.manual} manual sent as skipped); ${summary.dropped} findings dropped from ${summary.droppedChecks.length} checks not in the catalogue` +
276
+ (summary.droppedChecks.length ? `: ${summary.droppedChecks.slice(0, 20).join(", ")}${summary.droppedChecks.length > 20 ? ", …" : ""}` : ""));
215
277
  }
216
- else {
217
- const spawner = ctx.clients.prowler ?? spawnProwler;
218
- const extra = one(ctx.opts["prowler-args"]) ? splitArgs(one(ctx.opts["prowler-args"])) : [];
219
- text = await runProwler(provider, { bin: one(ctx.opts["prowler-bin"]) ?? "prowler", extra, env: ctx.env, spawner });
220
- }
221
- const { results, summary } = parseOcsf(text, provider, known);
222
- ctx.warn(`prowler: ${summary.findings} findings; ${summary.kept} results (${summary.merged} merged into the same check and resource, ` +
223
- `${summary.muted} muted and ${summary.manual} manual sent as skipped); ${summary.dropped} findings dropped from ${summary.droppedChecks.length} checks not in the catalogue` +
224
- (summary.droppedChecks.length ? `: ${summary.droppedChecks.slice(0, 20).join(", ")}${summary.droppedChecks.length > 20 ? ", …" : ""}` : ""));
278
+ if (failures.length === providers.length)
279
+ throw failures[0];
225
280
  return scrub(results, SECRET_ENVS.map((n) => ctx.env[n]));
226
281
  },
227
282
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sagentix-inc/evidence-collector",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "description": "Customer-side evidence collector: reads systems read-only, runs checks, signs the results and pushes them outbound to sos-app.",
5
5
  "type": "module",
6
6
  "engines": {