@@ -25,6 +25,7 @@ import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
2525import * as iam from 'aws-cdk-lib/aws-iam' ;
2626import { Runtime , Architecture } from 'aws-cdk-lib/aws-lambda' ;
2727import * as lambda from 'aws-cdk-lib/aws-lambda-nodejs' ;
28+ import * as s3 from 'aws-cdk-lib/aws-s3' ;
2829import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager' ;
2930import { NagSuppressions } from 'cdk-nag' ;
3031import { Construct } from 'constructs' ;
@@ -35,8 +36,17 @@ import { JiraWorkspaceRegistryTable } from './jira-workspace-registry-table';
3536/** Default task-record retention used for TTL computation (days). */
3637const 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' , {
0 commit comments