Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 27 additions & 2 deletions cdk/src/constructs/jira-integration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
import * as iam from 'aws-cdk-lib/aws-iam';
import { Runtime, Architecture } from 'aws-cdk-lib/aws-lambda';
import * as lambda from 'aws-cdk-lib/aws-lambda-nodejs';
import * as s3 from 'aws-cdk-lib/aws-s3';
import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager';
import { NagSuppressions } from 'cdk-nag';
import { Construct } from 'constructs';
Expand All @@ -35,8 +36,14 @@ import { JiraWorkspaceRegistryTable } from './jira-workspace-registry-table';
/** Default task-record retention used for TTL computation (days). */
const DEFAULT_TASK_RETENTION_DAYS = 90;

/** Webhook-processor Lambda timeout (seconds). */
const WEBHOOK_PROCESSOR_TIMEOUT_SECONDS = 30;
/**
* Webhook-processor Lambda timeout (seconds). The processor is invoked
* asynchronously (not behind the API Gateway 30s deadline), so it can run
* longer than the receiver. #577 added serial authenticated download +
* Bedrock screening of up to 10 Jira attachments, so 60s leaves headroom over
* the per-attachment fetch (10s) + screening-retry budget.
*/
const WEBHOOK_PROCESSOR_TIMEOUT_SECONDS = 60;

/**
* Marker key embedded in the auto-generated stack-wide webhook-secret
Expand Down Expand Up @@ -87,6 +94,14 @@ export interface JiraIntegrationProps {
/** Bedrock Guardrail version for input screening. */
readonly guardrailVersion?: string;

/**
* S3 bucket for task attachment storage. Required for the webhook processor
* to fetch, screen, and store Jira `media` file attachments at task-admission
* time (issue #577). When omitted, issues carrying supported file attachments
* are rejected with a Jira comment rather than silently dropping them.
*/
readonly attachmentsBucket?: s3.IBucket;

/** Task retention in days for TTL computation. */
readonly taskRetentionDays?: number;

Expand Down Expand Up @@ -206,6 +221,9 @@ export class JiraIntegration extends Construct {
createTaskEnv.GUARDRAIL_ID = props.guardrailId;
createTaskEnv.GUARDRAIL_VERSION = props.guardrailVersion;
}
if (props.attachmentsBucket) {
createTaskEnv.ATTACHMENTS_BUCKET_NAME = props.attachmentsBucket.bucketName;
}

// --- Cognito Authorizer (for /jira/link) ---
const cognitoAuthorizer = new apigw.CognitoUserPoolsAuthorizer(this, 'JiraCognitoAuthorizer', {
Expand Down Expand Up @@ -280,6 +298,13 @@ export class JiraIntegration extends Construct {
],
}));
}
// The processor downloads Jira `media` attachments, screens them, and
// writes the cleaned bytes to the attachments bucket before creating the
// task (#577). ReadWrite mirrors the confirm-uploads path (Put + Get for
// multipart/versioned writes).
if (props.attachmentsBucket) {
props.attachmentsBucket.grantReadWrite(webhookProcessorFn);
}

// --- Webhook receiver (verifies HMAC, dedups, invokes processor) ---
const webhookFn = new lambda.NodejsFunction(this, 'WebhookFn', {
Expand Down
24 changes: 24 additions & 0 deletions cdk/src/constructs/task-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,18 @@ export class TaskApi extends Construct {
textTransformations: [{ priority: 0, type: 'NONE' }],
},
},
{
// Jira issue payloads include the full issue field set.
// Attachment metadata can push them over 8 KB. The
// receiver HMAC-verifies the raw body and the priority-4
// rule below still rate-limits this route.
byteMatchStatement: {
fieldToMatch: { uriPath: {} },
positionalConstraint: 'EXACTLY',
searchString: '/v1/jira/webhook',
textTransformations: [{ priority: 0, type: 'NONE' }],
},
},
{
// GitHub deployment_status webhook (preview-deploy
// screenshot pipeline). The full payload (workflow run
Expand Down Expand Up @@ -376,6 +388,18 @@ export class TaskApi extends Construct {
},
},
},
{
notStatement: {
statement: {
byteMatchStatement: {
fieldToMatch: { uriPath: {} },
positionalConstraint: 'EXACTLY',
searchString: '/v1/jira/webhook',
textTransformations: [{ priority: 0, type: 'NONE' }],
},
},
},
},
{
notStatement: {
statement: {
Expand Down
Loading
Loading