@stratusagent/gateway 0.13.6 → 0.14.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.
@@ -0,0 +1,141 @@
1
+ import { type JsonObject } from '@stratusagent/core';
2
+ import { type EventFilter, type InboundEvent } from '@stratusagent/agents';
3
+ import type { EventSourceConfig } from '@stratusagent/state';
4
+ import { type EventSourceActivity, type SqliteEventStore } from './events.ts';
5
+ export type { EventSourceConfig };
6
+ /** A trigger as matching sees it: the schedule's id, its agent, and its event cadence's source and filter. */
7
+ export interface EventTrigger {
8
+ triggerId: string;
9
+ agentId: string;
10
+ source: string;
11
+ filter: EventFilter;
12
+ }
13
+ export interface EventIntakeRequest {
14
+ /** The `<source>` in `/events/<source>`. */
15
+ sourceId: string;
16
+ /** As node:http gives them: lowercase names. */
17
+ headers: Readonly<Record<string, string | string[] | undefined>>;
18
+ /** The raw body, read no further than one byte past `maxBodyBytes`. */
19
+ body: Buffer;
20
+ }
21
+ export interface EventIntakeResponse {
22
+ status: number;
23
+ body: JsonObject;
24
+ }
25
+ export interface EventSourceCounters {
26
+ tooLarge: number;
27
+ badSignature: number;
28
+ rateLimited: number;
29
+ notReady: number;
30
+ lastRefusal?: {
31
+ at: string;
32
+ reason: string;
33
+ };
34
+ }
35
+ export interface EventIntakeCounters {
36
+ /** Requests to a path no source is configured for, all paths together. */
37
+ unknownSource: number;
38
+ /** Per configured source: requests refused before they could leave a row. */
39
+ sources: Record<string, EventSourceCounters>;
40
+ }
41
+ /** One configured source in the events summary (`stratus events`, `/health`). */
42
+ export interface EventSourceSummary extends Omit<EventSourceActivity, 'lastRefused'> {
43
+ /** Requests refused before they could leave a row, since this daemon started. */
44
+ refusedBeforeRow: Omit<EventSourceCounters, 'lastRefusal'>;
45
+ /** The newest refusal of any kind: a refused row, or one this daemon refused before a row. */
46
+ lastRefusal?: {
47
+ at: string;
48
+ reason: string;
49
+ };
50
+ }
51
+ export interface EventsSummary {
52
+ /** Where the row counts start: 24 hours before the summary. */
53
+ since: string;
54
+ /** Every configured source, with or without activity. */
55
+ sources: Record<string, EventSourceSummary>;
56
+ /** Requests to a path no source is configured for, since this daemon started. */
57
+ unknownSource: number;
58
+ }
59
+ /**
60
+ * The rows' view (it survives restarts) and the listener's counters (they
61
+ * hold what never got a row) as one answer per configured source.
62
+ */
63
+ export declare const summarizeEvents: (input: {
64
+ sourceIds: readonly string[];
65
+ since: string;
66
+ activity: Readonly<Record<string, EventSourceActivity>>;
67
+ counters: EventIntakeCounters;
68
+ }) => EventsSummary;
69
+ export interface EventIntakeOptions {
70
+ store: Pick<SqliteEventStore, 'accept' | 'refuse'>;
71
+ /** The configured sources, read per request. */
72
+ sources(): Readonly<Record<string, EventSourceConfig>>;
73
+ /**
74
+ * A stored credential's value by name, or undefined when it is not set.
75
+ * Asked per request, so `stratus credential set` takes effect on the next
76
+ * delivery, with no restart.
77
+ */
78
+ secret(name: string): Promise<string | undefined>;
79
+ /** The event triggers on a source, read per delivery. Empty until triggers exist. */
80
+ triggers(sourceId: string): readonly EventTrigger[];
81
+ maxBodyBytes?: number;
82
+ now?(): Date;
83
+ warn(line: string): void;
84
+ }
85
+ export interface EventIntake {
86
+ receive(request: EventIntakeRequest): Promise<EventIntakeResponse>;
87
+ counters(): EventIntakeCounters;
88
+ }
89
+ export declare const DEFAULT_EVENT_MAX_BODY_BYTES = 1048576;
90
+ /** Signed requests a source may make in any minute before it hears 429. */
91
+ export declare const EVENT_SOURCE_REQUESTS_PER_MINUTE = 120;
92
+ /**
93
+ * Standard Webhooks 1.0 symmetric verification: `v1,<base64 HMAC-SHA256 of
94
+ * "id.timestamp.body">` keyed with the base64 secret (the `whsec_` prefix is
95
+ * optional). The header may carry several space-separated signatures, and any
96
+ * one valid `v1` under any one secret passes, which is how a sender rotates
97
+ * keys. Does not check the timestamp's age; the intake does.
98
+ */
99
+ export declare const verifyStandardWebhookSignature: (input: {
100
+ id: string;
101
+ timestamp: string;
102
+ body: Buffer;
103
+ signature: string | undefined;
104
+ }, secrets: readonly string[]) => boolean;
105
+ /** GitHub's `X-Hub-Signature-256: sha256=<hex HMAC-SHA256 of the raw body>`, keyed with the secret as text. */
106
+ export declare const verifyGithubSignature: (input: {
107
+ body: Buffer;
108
+ signature: string | undefined;
109
+ }, secrets: readonly string[]) => boolean;
110
+ /**
111
+ * A Standard Webhooks body as an event. A structured CloudEvent (it has
112
+ * `specversion`) keeps its attributes, and its `id` must be the `webhook-id`
113
+ * it was signed under, or one signed delivery could be replayed as another
114
+ * event. A plain `{ type, timestamp?, data? }` payload takes its id from
115
+ * `webhook-id` and its source from the configured source. Extension
116
+ * attributes keep only valid names with scalar values, as strings; anything
117
+ * else is dropped, not refused. An empty `webhook-id` and `data_base64` are
118
+ * refused: one would make every such delivery share a dedup key, the other
119
+ * would arrive with its payload gone.
120
+ */
121
+ export declare const parseStandardWebhookEvent: (input: {
122
+ sourceId: string;
123
+ webhookId: string;
124
+ body: Buffer | string;
125
+ }) => InboundEvent;
126
+ /**
127
+ * A GitHub webhook delivery as a CloudEvent. The subject is `pull/<n>` for
128
+ * anything on a PR, an issue comment on a PR included, so a review and a
129
+ * comment on one PR share a session; `issues/<n>` for an issue. `actor` is
130
+ * who acted, `author` who owns the PR or issue: what a trigger names to wake
131
+ * only the PR's owner. GitHub sends no event time, so `time` is when it
132
+ * arrived.
133
+ */
134
+ export declare const mapGithubDelivery: (input: {
135
+ event: string;
136
+ delivery: string;
137
+ payload: unknown;
138
+ receivedAt: string;
139
+ }) => InboundEvent;
140
+ export declare const createEventIntake: (options: EventIntakeOptions) => EventIntake;
141
+ //# sourceMappingURL=event-intake.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-intake.d.ts","sourceRoot":"","sources":["../src/event-intake.ts"],"names":[],"mappings":"AAEA,OAAO,EAA2B,KAAK,UAAU,EAAkB,MAAM,oBAAoB,CAAC;AAC9F,OAAO,EAAsB,KAAK,WAAW,EAAE,KAAK,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAC/F,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAE7D,OAAO,EAKL,KAAK,mBAAmB,EAExB,KAAK,gBAAgB,EACtB,MAAM,aAAa,CAAC;AAYrB,YAAY,EAAE,iBAAiB,EAAE,CAAC;AAElC,8GAA8G;AAC9G,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,WAAW,CAAC;CACrB;AAED,MAAM,WAAW,kBAAkB;IACjC,4CAA4C;IAC5C,QAAQ,EAAE,MAAM,CAAC;IACjB,gDAAgD;IAChD,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC,CAAC;IACjE,uEAAuE;IACvE,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,UAAU,CAAC;CAClB;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;CAC9C;AAED,MAAM,WAAW,mBAAmB;IAClC,0EAA0E;IAC1E,aAAa,EAAE,MAAM,CAAC;IACtB,6EAA6E;IAC7E,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;CAC9C;AAED,iFAAiF;AACjF,MAAM,WAAW,kBAAmB,SAAQ,IAAI,CAAC,mBAAmB,EAAE,aAAa,CAAC;IAClF,iFAAiF;IACjF,gBAAgB,EAAE,IAAI,CAAC,mBAAmB,EAAE,aAAa,CAAC,CAAC;IAC3D,8FAA8F;IAC9F,WAAW,CAAC,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;CAC9C;AAED,MAAM,WAAW,aAAa;IAC5B,+DAA+D;IAC/D,KAAK,EAAE,MAAM,CAAC;IACd,yDAAyD;IACzD,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;IAC5C,iFAAiF;IACjF,aAAa,EAAE,MAAM,CAAC;CACvB;AAED;;;GAGG;AACH,eAAO,MAAM,eAAe,GAAI,OAAO;IACrC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC,CAAC;IACxD,QAAQ,EAAE,mBAAmB,CAAC;CAC/B,KAAG,aAWF,CAAC;AAEH,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,IAAI,CAAC,gBAAgB,EAAE,QAAQ,GAAG,QAAQ,CAAC,CAAC;IACnD,gDAAgD;IAChD,OAAO,IAAI,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC,CAAC;IACvD;;;;OAIG;IACH,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IAClD,qFAAqF;IACrF,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,YAAY,EAAE,CAAC;IACpD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,GAAG,CAAC,IAAI,IAAI,CAAC;IACb,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC;IACnE,QAAQ,IAAI,mBAAmB,CAAC;CACjC;AAED,eAAO,MAAM,4BAA4B,UAAY,CAAC;AAEtD,2EAA2E;AAC3E,eAAO,MAAM,gCAAgC,MAAM,CAAC;AAepD;;;;;;GAMG;AACH,eAAO,MAAM,8BAA8B,GACzC,OAAO;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,EACrF,SAAS,SAAS,MAAM,EAAE,KACzB,OAiBF,CAAC;AAEF,+GAA+G;AAC/G,eAAO,MAAM,qBAAqB,GAChC,OAAO;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,EACtD,SAAS,SAAS,MAAM,EAAE,KACzB,OAQF,CAAC;AA4BF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,yBAAyB,GACpC,OAAO;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAA;CAAE,KACpE,YAiDF,CAAC;AAwBF;;;;;;;GAOG;AACH,eAAO,MAAM,iBAAiB,GAC5B,OAAO;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,KAC/E,YA0BF,CAAC;AAKF,eAAO,MAAM,iBAAiB,GAAI,SAAS,kBAAkB,KAAG,WA4K/D,CAAC"}
@@ -0,0 +1,369 @@
1
+ import { createHmac, timingSafeEqual } from 'node:crypto';
2
+ import { escapeControlCharacters } from '@stratusagent/core';
3
+ import { matchesEventFilter } from '@stratusagent/agents';
4
+ import { eventCoalescingKey, eventDispatchKey, eventSessionId, renderEventBlock, } from "./events.js";
5
+ /**
6
+ * The rows' view (it survives restarts) and the listener's counters (they
7
+ * hold what never got a row) as one answer per configured source.
8
+ */
9
+ export const summarizeEvents = (input) => ({
10
+ since: input.since,
11
+ unknownSource: input.counters.unknownSource,
12
+ sources: Object.fromEntries(input.sourceIds.map((sourceId) => {
13
+ const { lastRefused, ...rows } = input.activity[sourceId] ?? { deliveries: {}, firings: {} };
14
+ const { lastRefusal: counted, ...refusedBeforeRow } = input.counters.sources[sourceId]
15
+ ?? { tooLarge: 0, badSignature: 0, rateLimited: 0, notReady: 0 };
16
+ // ISO instants compare as strings.
17
+ const lastRefusal = lastRefused !== undefined && (counted === undefined || lastRefused.at > counted.at) ? lastRefused : counted;
18
+ return [sourceId, { ...rows, refusedBeforeRow, ...(lastRefusal !== undefined ? { lastRefusal: { ...lastRefusal } } : {}) }];
19
+ })),
20
+ });
21
+ export const DEFAULT_EVENT_MAX_BODY_BYTES = 1_048_576;
22
+ /** Signed requests a source may make in any minute before it hears 429. */
23
+ export const EVENT_SOURCE_REQUESTS_PER_MINUTE = 120;
24
+ /** How far a Standard Webhooks timestamp may be from our clock: the replay window. */
25
+ const TIMESTAMP_TOLERANCE_MS = 5 * 60 * 1_000;
26
+ /** A refusal is warned in the daemon log at most this often per source. */
27
+ const REFUSAL_WARN_INTERVAL_MS = 10 * 60 * 1_000;
28
+ const headerOf = (headers, name) => {
29
+ const value = headers[name];
30
+ return Array.isArray(value) ? value.join(', ') : value;
31
+ };
32
+ const constantTimeEqual = (a, b) => a.length === b.length && timingSafeEqual(a, b);
33
+ /**
34
+ * Standard Webhooks 1.0 symmetric verification: `v1,<base64 HMAC-SHA256 of
35
+ * "id.timestamp.body">` keyed with the base64 secret (the `whsec_` prefix is
36
+ * optional). The header may carry several space-separated signatures, and any
37
+ * one valid `v1` under any one secret passes, which is how a sender rotates
38
+ * keys. Does not check the timestamp's age; the intake does.
39
+ */
40
+ export const verifyStandardWebhookSignature = (input, secrets) => {
41
+ const offered = (input.signature ?? '')
42
+ .split(' ')
43
+ .filter((entry) => entry.startsWith('v1,'))
44
+ .map((entry) => Buffer.from(entry.slice(3), 'base64'));
45
+ if (offered.length === 0) {
46
+ return false;
47
+ }
48
+ const signed = Buffer.concat([Buffer.from(`${input.id}.${input.timestamp}.`), input.body]);
49
+ return secrets.some((secret) => {
50
+ const key = Buffer.from(secret.startsWith('whsec_') ? secret.slice(6) : secret, 'base64');
51
+ if (key.length === 0) {
52
+ return false;
53
+ }
54
+ const expected = createHmac('sha256', key).update(signed).digest();
55
+ return offered.some((candidate) => constantTimeEqual(candidate, expected));
56
+ });
57
+ };
58
+ /** GitHub's `X-Hub-Signature-256: sha256=<hex HMAC-SHA256 of the raw body>`, keyed with the secret as text. */
59
+ export const verifyGithubSignature = (input, secrets) => {
60
+ const signature = input.signature ?? '';
61
+ if (!signature.startsWith('sha256=')) {
62
+ return false;
63
+ }
64
+ const offered = Buffer.from(signature.slice('sha256='.length), 'utf8');
65
+ return secrets.some((secret) => secret.length > 0
66
+ && constantTimeEqual(offered, Buffer.from(createHmac('sha256', secret).update(input.body).digest('hex'), 'utf8')));
67
+ };
68
+ /** A body that parsed but isn't an event; its message is the 400's. */
69
+ class MalformedEventError extends Error {
70
+ constructor(message) {
71
+ super(message);
72
+ this.name = 'MalformedEventError';
73
+ }
74
+ }
75
+ const isPlainObject = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
76
+ const parseJsonObject = (body) => {
77
+ let parsed;
78
+ try {
79
+ parsed = JSON.parse(typeof body === 'string' ? body : body.toString('utf8'));
80
+ }
81
+ catch {
82
+ throw new MalformedEventError('The body is not JSON.');
83
+ }
84
+ if (!isPlainObject(parsed)) {
85
+ throw new MalformedEventError('The body must be a JSON object.');
86
+ }
87
+ return parsed;
88
+ };
89
+ const ATTRIBUTE_NAME = /^[a-z0-9]+$/;
90
+ /**
91
+ * A Standard Webhooks body as an event. A structured CloudEvent (it has
92
+ * `specversion`) keeps its attributes, and its `id` must be the `webhook-id`
93
+ * it was signed under, or one signed delivery could be replayed as another
94
+ * event. A plain `{ type, timestamp?, data? }` payload takes its id from
95
+ * `webhook-id` and its source from the configured source. Extension
96
+ * attributes keep only valid names with scalar values, as strings; anything
97
+ * else is dropped, not refused. An empty `webhook-id` and `data_base64` are
98
+ * refused: one would make every such delivery share a dedup key, the other
99
+ * would arrive with its payload gone.
100
+ */
101
+ export const parseStandardWebhookEvent = (input) => {
102
+ if (input.webhookId.length === 0) {
103
+ throw new MalformedEventError('The webhook-id header is empty; every delivery needs its own id.');
104
+ }
105
+ const object = parseJsonObject(input.body);
106
+ if (object.specversion === undefined) {
107
+ if (typeof object.type !== 'string' || object.type.length === 0) {
108
+ throw new MalformedEventError('A Standard Webhooks payload needs a string "type".');
109
+ }
110
+ return {
111
+ attributes: {
112
+ specversion: '1.0',
113
+ id: input.webhookId,
114
+ source: `stratus:source/${input.sourceId}`,
115
+ type: object.type,
116
+ ...(typeof object.timestamp === 'string' ? { time: object.timestamp } : {}),
117
+ },
118
+ ...(object.data !== undefined ? { data: object.data } : {}),
119
+ };
120
+ }
121
+ if (object.specversion !== '1.0') {
122
+ throw new MalformedEventError(`CloudEvents specversion "${String(object.specversion)}" is not supported; send "1.0".`);
123
+ }
124
+ for (const required of ['id', 'source', 'type']) {
125
+ const value = object[required];
126
+ if (typeof value !== 'string' || value.length === 0) {
127
+ throw new MalformedEventError(`A CloudEvent needs a non-empty string "${required}".`);
128
+ }
129
+ }
130
+ if (object.id !== input.webhookId) {
131
+ throw new MalformedEventError('The CloudEvent id must equal the webhook-id header it was signed under.');
132
+ }
133
+ // Refused, not dropped: accepting it would hand triggers an event whose
134
+ // payload silently vanished. Events here are JSON.
135
+ if (object.data_base64 !== undefined) {
136
+ throw new MalformedEventError('data_base64 is not supported; send the payload as JSON in "data".');
137
+ }
138
+ const attributes = {};
139
+ for (const [name, value] of Object.entries(object)) {
140
+ if (name === 'data' || !ATTRIBUTE_NAME.test(name)) {
141
+ continue;
142
+ }
143
+ if (typeof value === 'string') {
144
+ attributes[name] = value;
145
+ }
146
+ else if (typeof value === 'number' || typeof value === 'boolean') {
147
+ attributes[name] = String(value);
148
+ }
149
+ }
150
+ return { attributes, ...(object.data !== undefined ? { data: object.data } : {}) };
151
+ };
152
+ const stringAt = (value, ...path) => {
153
+ let cursor = value;
154
+ for (const key of path) {
155
+ if (!isPlainObject(cursor)) {
156
+ return undefined;
157
+ }
158
+ cursor = cursor[key];
159
+ }
160
+ return typeof cursor === 'string' && cursor.length > 0 ? cursor : undefined;
161
+ };
162
+ const numberAt = (value, ...path) => {
163
+ let cursor = value;
164
+ for (const key of path) {
165
+ if (!isPlainObject(cursor)) {
166
+ return undefined;
167
+ }
168
+ cursor = cursor[key];
169
+ }
170
+ return typeof cursor === 'number' && Number.isSafeInteger(cursor) ? cursor : undefined;
171
+ };
172
+ /**
173
+ * A GitHub webhook delivery as a CloudEvent. The subject is `pull/<n>` for
174
+ * anything on a PR, an issue comment on a PR included, so a review and a
175
+ * comment on one PR share a session; `issues/<n>` for an issue. `actor` is
176
+ * who acted, `author` who owns the PR or issue: what a trigger names to wake
177
+ * only the PR's owner. GitHub sends no event time, so `time` is when it
178
+ * arrived.
179
+ */
180
+ export const mapGithubDelivery = (input) => {
181
+ const payload = (isPlainObject(input.payload) ? input.payload : {});
182
+ const action = stringAt(payload, 'action');
183
+ const repository = stringAt(payload, 'repository', 'full_name');
184
+ const organization = stringAt(payload, 'organization', 'login');
185
+ const pullNumber = numberAt(payload, 'pull_request', 'number');
186
+ const issueNumber = numberAt(payload, 'issue', 'number');
187
+ const issueIsPull = isPlainObject(payload.issue) && payload.issue.pull_request !== undefined;
188
+ const subject = pullNumber !== undefined
189
+ ? `pull/${pullNumber}`
190
+ : issueNumber !== undefined ? `${issueIsPull ? 'pull' : 'issues'}/${issueNumber}` : undefined;
191
+ const actor = stringAt(payload, 'sender', 'login');
192
+ const author = stringAt(payload, 'pull_request', 'user', 'login') ?? stringAt(payload, 'issue', 'user', 'login');
193
+ return {
194
+ attributes: {
195
+ specversion: '1.0',
196
+ id: input.delivery,
197
+ source: `https://github.com${repository ? `/${repository}` : organization ? `/${organization}` : ''}`,
198
+ type: `com.github.${input.event}${action ? `.${action}` : ''}`,
199
+ ...(subject !== undefined ? { subject } : {}),
200
+ time: input.receivedAt,
201
+ ...(actor !== undefined ? { actor } : {}),
202
+ ...(author !== undefined ? { author } : {}),
203
+ },
204
+ data: payload,
205
+ };
206
+ };
207
+ const mediaType = (contentType) => (contentType ?? '').split(';')[0].trim().toLowerCase();
208
+ export const createEventIntake = (options) => {
209
+ const maxBodyBytes = options.maxBodyBytes ?? DEFAULT_EVENT_MAX_BODY_BYTES;
210
+ const now = options.now ?? (() => new Date());
211
+ let unknownSource = 0;
212
+ const counters = new Map();
213
+ const signedRequests = new Map();
214
+ const lastWarned = new Map();
215
+ const countersFor = (sourceId) => {
216
+ let entry = counters.get(sourceId);
217
+ if (!entry) {
218
+ entry = { tooLarge: 0, badSignature: 0, rateLimited: 0, notReady: 0 };
219
+ counters.set(sourceId, entry);
220
+ }
221
+ return entry;
222
+ };
223
+ const refusal = (sourceId, reason) => {
224
+ const at = now();
225
+ countersFor(sourceId).lastRefusal = { at: at.toISOString(), reason };
226
+ const last = lastWarned.get(sourceId);
227
+ if (last === undefined || at.getTime() - last >= REFUSAL_WARN_INTERVAL_MS) {
228
+ lastWarned.set(sourceId, at.getTime());
229
+ // Escaped: a reason can quote a sender's content type or field, and a
230
+ // raw control character there would forge or recolor a log line.
231
+ options.warn(`events: refused a request to source "${sourceId}": ${escapeControlCharacters(reason)}`);
232
+ }
233
+ };
234
+ const answer = (status, body) => ({ status, body });
235
+ /** Count a signed request against its source's minute; false when the minute is full. */
236
+ const admitSigned = (sourceId) => {
237
+ const at = now().getTime();
238
+ const recent = (signedRequests.get(sourceId) ?? []).filter((time) => at - time < 60_000);
239
+ if (recent.length >= EVENT_SOURCE_REQUESTS_PER_MINUTE) {
240
+ signedRequests.set(sourceId, recent);
241
+ return false;
242
+ }
243
+ recent.push(at);
244
+ signedRequests.set(sourceId, recent);
245
+ return true;
246
+ };
247
+ const receive = async (request) => {
248
+ const { sourceId, headers, body } = request;
249
+ const sources = options.sources();
250
+ const source = Object.hasOwn(sources, sourceId) ? sources[sourceId] : undefined;
251
+ if (!source) {
252
+ unknownSource += 1;
253
+ return answer(404, { error: 'unknown_source', message: 'No event source is configured at this path.' });
254
+ }
255
+ const count = countersFor(sourceId);
256
+ if (body.length > maxBodyBytes) {
257
+ count.tooLarge += 1;
258
+ refusal(sourceId, `body over ${maxBodyBytes} bytes`);
259
+ return answer(413, { error: 'too_large', message: `The body is over ${maxBodyBytes} bytes.` });
260
+ }
261
+ const secrets = (await Promise.all(source.secrets.map((name) => options.secret(name))))
262
+ .filter((value) => typeof value === 'string' && value.length > 0);
263
+ if (secrets.length === 0) {
264
+ count.notReady += 1;
265
+ const reason = `No secret is set for event source "${sourceId}". Run \`stratus credential set ${source.secrets[0] ?? `events.${sourceId}`}\` on the daemon's host.`;
266
+ refusal(sourceId, reason);
267
+ return answer(503, { error: 'source_not_ready', message: reason });
268
+ }
269
+ const receivedAt = now().toISOString();
270
+ let eventId;
271
+ let signatureProblem;
272
+ if (source.verify === 'github') {
273
+ eventId = headerOf(headers, 'x-github-delivery');
274
+ if (!verifyGithubSignature({ body, signature: headerOf(headers, 'x-hub-signature-256') }, secrets)) {
275
+ signatureProblem = 'missing or wrong X-Hub-Signature-256 signature';
276
+ }
277
+ }
278
+ else {
279
+ eventId = headerOf(headers, 'webhook-id');
280
+ const timestamp = headerOf(headers, 'webhook-timestamp');
281
+ if (eventId === undefined || timestamp === undefined
282
+ || !verifyStandardWebhookSignature({ id: eventId, timestamp, body, signature: headerOf(headers, 'webhook-signature') }, secrets)) {
283
+ signatureProblem = 'missing or wrong webhook-signature (Standard Webhooks v1)';
284
+ }
285
+ else if (!/^\d+$/.test(timestamp) || Math.abs(now().getTime() - Number(timestamp) * 1_000) > TIMESTAMP_TOLERANCE_MS) {
286
+ signatureProblem = 'webhook-timestamp is more than 5 minutes from this host\'s clock';
287
+ }
288
+ }
289
+ if (signatureProblem !== undefined) {
290
+ count.badSignature += 1;
291
+ refusal(sourceId, signatureProblem);
292
+ return answer(401, { error: 'bad_signature', message: `Refused: ${signatureProblem}.` });
293
+ }
294
+ if (!admitSigned(sourceId)) {
295
+ count.rateLimited += 1;
296
+ refusal(sourceId, `over ${EVENT_SOURCE_REQUESTS_PER_MINUTE} signed requests a minute`);
297
+ return answer(429, { error: 'rate_limited', message: `Over ${EVENT_SOURCE_REQUESTS_PER_MINUTE} requests a minute for this source.` });
298
+ }
299
+ // Signed from here on: refusals leave a row, without a tombstone, so the
300
+ // sender can fix the request and retry under the same id.
301
+ const refuse = (status, error, reason, type) => {
302
+ options.store.refuse({ source: sourceId, eventId: eventId ?? '', ...(type !== undefined ? { type } : {}), reason, receivedAt });
303
+ refusal(sourceId, reason);
304
+ return answer(status, { error, message: reason });
305
+ };
306
+ const type = mediaType(headerOf(headers, 'content-type'));
307
+ let event;
308
+ try {
309
+ if (source.verify === 'github') {
310
+ const githubEvent = headerOf(headers, 'x-github-event');
311
+ if (type !== 'application/json') {
312
+ return refuse(415, 'unsupported_content_type', `Content type "${type || 'none'}" is not supported. In the GitHub webhook's settings, set Content type to application/json.`, githubEvent !== undefined ? `com.github.${githubEvent}` : undefined);
313
+ }
314
+ if (githubEvent === undefined || githubEvent.length === 0 || eventId === undefined || eventId.length === 0) {
315
+ throw new MalformedEventError('A GitHub delivery needs X-GitHub-Event and X-GitHub-Delivery headers.');
316
+ }
317
+ event = mapGithubDelivery({ event: githubEvent, delivery: eventId, payload: parseJsonObject(body), receivedAt });
318
+ }
319
+ else {
320
+ if (type !== 'application/json' && type !== 'application/cloudevents+json') {
321
+ return refuse(415, 'unsupported_content_type', `Content type "${type || 'none'}" is not supported. Send application/cloudevents+json or application/json.`);
322
+ }
323
+ event = parseStandardWebhookEvent({ sourceId, webhookId: eventId, body });
324
+ }
325
+ }
326
+ catch (error) {
327
+ if (error instanceof MalformedEventError) {
328
+ return refuse(400, 'malformed', error.message);
329
+ }
330
+ throw error;
331
+ }
332
+ // The rule filter, before anything that could cost a model call.
333
+ const matched = options.triggers(sourceId)
334
+ .filter((trigger) => trigger.source === sourceId && matchesEventFilter(trigger.filter, event));
335
+ const coalescingKey = eventCoalescingKey(sourceId, event);
336
+ const block = matched.length > 0 ? renderEventBlock(sourceId, event) : '';
337
+ const firings = matched.map((trigger) => ({
338
+ triggerId: trigger.triggerId,
339
+ agentId: trigger.agentId,
340
+ coalescingKey,
341
+ sessionId: eventSessionId(trigger.triggerId, coalescingKey),
342
+ dispatchKey: eventDispatchKey(trigger.triggerId, sourceId, event.attributes.id ?? ''),
343
+ block,
344
+ }));
345
+ const accepted = options.store.accept({
346
+ source: sourceId,
347
+ eventId: event.attributes.id ?? '',
348
+ type: event.attributes.type ?? '',
349
+ ...(event.attributes.subject !== undefined ? { subject: event.attributes.subject } : {}),
350
+ receivedAt,
351
+ firings,
352
+ });
353
+ return answer(200, { status: accepted.status });
354
+ };
355
+ return {
356
+ receive,
357
+ counters: () => ({
358
+ unknownSource,
359
+ sources: Object.fromEntries(Object.keys(options.sources()).map((sourceId) => {
360
+ const entry = countersFor(sourceId);
361
+ return [sourceId, {
362
+ ...entry,
363
+ ...(entry.lastRefusal ? { lastRefusal: { ...entry.lastRefusal } } : {}),
364
+ }];
365
+ })),
366
+ }),
367
+ };
368
+ };
369
+ //# sourceMappingURL=event-intake.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-intake.js","sourceRoot":"","sources":["../src/event-intake.ts"],"names":[],"mappings":"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"}