1import type * as Effect from "effect/Effect";
2import type * as Stream from "effect/Stream";
3import {
4 consumeBusEvents,
5 type EventRecord,
6 type EventRouteProps,
7} from "../EventBridge/EventSource.ts";
8
9/**
10 * The `detail` payload Amazon Inspector delivers to EventBridge for a
11 * finding — the full finding document. Only the commonly-matched fields are
12 * typed; the schema grows over time.
13 */
14export interface FindingEventDetail {
15 /** The finding's ARN. */
16 findingArn?: string;
17 /** The account the finding was generated in. */
18 awsAccountId?: string;
19 /** The finding severity (`CRITICAL`, `HIGH`, `MEDIUM`, `LOW`, ...). */
20 severity?: string;
21 /** The finding status (`ACTIVE`, `SUPPRESSED`, `CLOSED`). */
22 status?: string;
23 /** The finding type (`PACKAGE_VULNERABILITY`, `NETWORK_REACHABILITY`, `CODE_VULNERABILITY`). */
24 type?: string;
25 /** The finding title, e.g. `CVE-2021-44228 - log4j-core`. */
26 title?: string;
27 /** The finding description. */
28 description?: string;
29 /** The Inspector risk score. */
30 inspectorScore?: number;
31 /** The affected resources. */
32 resources?: Array<Record<string, unknown>>;
33 /** Package vulnerability details (CVE, affected packages, ...). */
34 packageVulnerabilityDetails?: Record<string, unknown>;
35 /** Additional finding fields (the schema grows over time). */
36 [key: string]: unknown;
37}
38
39/** An Amazon Inspector finding EventBridge event delivered to the handler. */
40export type FindingEvent = EventRecord<FindingEventDetail>;
41
42export interface FindingEventSourceProps extends EventRouteProps {
43 /**
44 * Logical id for the backing EventBridge rule.
45 * @default "Inspector2Findings"
46 */
47 id?: string;
48 /**
49 * Restrict to specific severities (matched against `detail.severity`),
50 * e.g. `["CRITICAL", "HIGH"]`.
51 */
52 severities?: readonly string[];
53 /**
54 * Restrict to specific finding statuses (matched against
55 * `detail.status`), e.g. `["ACTIVE"]`.
56 */
57 statuses?: readonly string[];
58}
59
60/**
61 * Event source connecting Amazon Inspector findings to the hosting compute.
62 * Inspector publishes every newly created, updated, or closed finding to
63 * the account's default EventBridge bus (source `aws.inspector2`,
64 * detail-type `Inspector2 Finding`); this subscribes the host Function to
65 * those events so it can triage, notify, or auto-remediate.
66 *
67 * Inspector publishes to EventBridge automatically — no additional resource
68 * is created besides the EventBridge rule targeting the host. Provide the
69 * host-specific implementation layer (e.g. `AWS.Lambda.EventSource`) on the
70 * Function effect.
71 *
72 * ### Consuming Findings
73 * **Example:** Alert on Critical Findings
74 * ```typescript
75 * import * as AWS from "alchemy/AWS";
76 *
77 * export default AlertFunction.make(
78 * { main: import.meta.url },
79 * Effect.gen(function* () {
80 * yield* AWS.Inspector2.consumeFindings(
81 * { severities: ["CRITICAL"], statuses: ["ACTIVE"] },
82 * (events) =>
83 * Stream.runForEach(events, (event) =>
84 * Effect.logError(
85 * `Inspector: ${event.detail.title} on ${event.detail.awsAccountId}`,
86 * ),
87 * ),
88 * );
89 * return {};
90 * }).pipe(Effect.provide(AWS.Lambda.EventSource)),
91 * );
92 * ```
93 */
94export const consumeFindings = <StreamReq = never, Req = never>(
95 props: FindingEventSourceProps,
96 process: (
97 events: Stream.Stream<FindingEvent, never, StreamReq>,
98 ) => Effect.Effect<void, never, Req>,
99) =>
100 consumeBusEvents(
101 props.id ?? "Inspector2Findings",
102 {
103 source: ["aws.inspector2"],
104 "detail-type": ["Inspector2 Finding"],
105 ...(props.severities !== undefined || props.statuses !== undefined
106 ? {
107 detail: {
108 ...(props.severities !== undefined
109 ? { severity: [...props.severities] }
110 : {}),
111 ...(props.statuses !== undefined
112 ? { status: [...props.statuses] }
113 : {}),
114 },
115 }
116 : {}),
117 },
118 { description: props.description, state: props.state },
119 process,
120 );