Skip to content

Commit f9d0f75

Browse files
authored
Merge branch 'main' into pr/ecs-substrate-hardening
2 parents f5e4bba + beef46a commit f9d0f75

13 files changed

Lines changed: 2010 additions & 130 deletions

File tree

cdk/src/constructs/jira-integration.ts

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
2525
import * as iam from 'aws-cdk-lib/aws-iam';
2626
import { Runtime, Architecture } from 'aws-cdk-lib/aws-lambda';
2727
import * as lambda from 'aws-cdk-lib/aws-lambda-nodejs';
28+
import * as s3 from 'aws-cdk-lib/aws-s3';
2829
import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager';
2930
import { NagSuppressions } from 'cdk-nag';
3031
import { Construct } from 'constructs';
@@ -35,8 +36,17 @@ import { JiraWorkspaceRegistryTable } from './jira-workspace-registry-table';
3536
/** Default task-record retention used for TTL computation (days). */
3637
const DEFAULT_TASK_RETENTION_DAYS = 90;
3738

38-
/** Webhook-processor Lambda timeout (seconds). */
39-
const WEBHOOK_PROCESSOR_TIMEOUT_SECONDS = 30;
39+
/**
40+
* Webhook-processor Lambda timeout (seconds). The processor is invoked
41+
* asynchronously (not behind the API Gateway 30s deadline), so it can run
42+
* longer than the receiver. #577 added serial authenticated download +
43+
* Bedrock screening of up to 10 Jira attachments; the per-attachment fetch
44+
* timeout alone (10s) can sum past 60s across a full batch, and a mid-loop
45+
* kill would orphan objects and force an idempotent retry. 300s covers the
46+
* worst-case serial batch with headroom. (A future optimization could
47+
* parallelize the download/screen loop and lower this again.)
48+
*/
49+
const WEBHOOK_PROCESSOR_TIMEOUT_SECONDS = 300;
4050

4151
/**
4252
* Marker key embedded in the auto-generated stack-wide webhook-secret
@@ -87,6 +97,14 @@ export interface JiraIntegrationProps {
8797
/** Bedrock Guardrail version for input screening. */
8898
readonly guardrailVersion?: string;
8999

100+
/**
101+
* S3 bucket for task attachment storage. Required for the webhook processor
102+
* to fetch, screen, and store Jira `media` file attachments at task-admission
103+
* time (issue #577). When omitted, issues carrying supported file attachments
104+
* are rejected with a Jira comment rather than silently dropping them.
105+
*/
106+
readonly attachmentsBucket?: s3.IBucket;
107+
90108
/** Task retention in days for TTL computation. */
91109
readonly taskRetentionDays?: number;
92110

@@ -206,6 +224,9 @@ export class JiraIntegration extends Construct {
206224
createTaskEnv.GUARDRAIL_ID = props.guardrailId;
207225
createTaskEnv.GUARDRAIL_VERSION = props.guardrailVersion;
208226
}
227+
if (props.attachmentsBucket) {
228+
createTaskEnv.ATTACHMENTS_BUCKET_NAME = props.attachmentsBucket.bucketName;
229+
}
209230

210231
// --- Cognito Authorizer (for /jira/link) ---
211232
const cognitoAuthorizer = new apigw.CognitoUserPoolsAuthorizer(this, 'JiraCognitoAuthorizer', {
@@ -280,6 +301,13 @@ export class JiraIntegration extends Construct {
280301
],
281302
}));
282303
}
304+
// The processor downloads Jira `media` attachments, screens them, and
305+
// writes the cleaned bytes to the attachments bucket before creating the
306+
// task (#577). ReadWrite mirrors the confirm-uploads path (Put + Get for
307+
// multipart/versioned writes).
308+
if (props.attachmentsBucket) {
309+
props.attachmentsBucket.grantReadWrite(webhookProcessorFn);
310+
}
283311

284312
// --- Webhook receiver (verifies HMAC, dedups, invokes processor) ---
285313
const webhookFn = new lambda.NodejsFunction(this, 'WebhookFn', {

cdk/src/constructs/task-api.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,18 @@ export class TaskApi extends Construct {
326326
textTransformations: [{ priority: 0, type: 'NONE' }],
327327
},
328328
},
329+
{
330+
// Jira issue payloads include the full issue field set.
331+
// Attachment metadata can push them over 8 KB. The
332+
// receiver HMAC-verifies the raw body and the priority-4
333+
// rule below still rate-limits this route.
334+
byteMatchStatement: {
335+
fieldToMatch: { uriPath: {} },
336+
positionalConstraint: 'EXACTLY',
337+
searchString: '/v1/jira/webhook',
338+
textTransformations: [{ priority: 0, type: 'NONE' }],
339+
},
340+
},
329341
{
330342
// GitHub deployment_status webhook (preview-deploy
331343
// screenshot pipeline). The full payload (workflow run
@@ -376,6 +388,18 @@ export class TaskApi extends Construct {
376388
},
377389
},
378390
},
391+
{
392+
notStatement: {
393+
statement: {
394+
byteMatchStatement: {
395+
fieldToMatch: { uriPath: {} },
396+
positionalConstraint: 'EXACTLY',
397+
searchString: '/v1/jira/webhook',
398+
textTransformations: [{ priority: 0, type: 'NONE' }],
399+
},
400+
},
401+
},
402+
},
379403
{
380404
notStatement: {
381405
statement: {

0 commit comments

Comments
 (0)