Repository navigation
Releases: gofastercloud/terraform-secure-upload
Release list
v0.7.2
Fixed
- Scan summary hardcoded NO_THREATS_FOUND for all quarantine paths —
_build_scan_summary()always reported GuardDuty asNO_THREATS_FOUNDand downstream scanners asclean, even when GuardDuty found threats or VT/PI triggered quarantine. The function now accepts the actual scan results: GuardDuty status is derived from the routing reason, VT results are passed through, PI score is included when it triggered quarantine, and scanners that didn't run shownot_checkedinstead ofclean.
v0.7.1
Fixed
- Discord and ServiceNow webhooks missing scan results — The webhook forwarder Lambda was not extracting the
scansfield from quarantine SNS messages, so Discord embeds and ServiceNow incidents only showed threat names without GuardDuty status, VirusTotal details (positives/total/SHA-256), or prompt injection findings. Now both integrations include full scan results.
v0.7.0 — Parallel VirusTotal Scanning
Changed
- VirusTotal scanning runs in parallel with GuardDuty — The VirusTotal hash lookup now triggers immediately when a file lands in the ingress bucket via an EventBridge rule on S3 Object Created events, running concurrently with GuardDuty's deeper scan. Previously, VT ran sequentially inside the file router after GuardDuty completed. VT results are stored as S3 object tags (
vt-status,vt-positives,vt-total,vt-sha256) which the file router reads when making its routing decision. If VT hasn't finished by the time the file router runs (unlikely but possible), it falls back to synchronous invocation. - Richer notification messages — Quarantine alerts and egress notifications now include detailed scan results from all scanners (GuardDuty status, VirusTotal positives/total/SHA-256, prompt injection score) in a structured
scansfield.
Added
- S3 EventBridge notifications on ingress bucket —
aws_s3_bucket_notificationresource enables EventBridge delivery for Object Created events. - EventBridge rule for VT scanner — new rule + target + permission (conditional on
enable_virustotal_scanning) triggers VT scanner Lambda on S3 uploads. - S3 object tagging by VT scanner — VT scanner writes results as object tags after scanning, enabling the file router to read results without a synchronous invoke.
- Test file generator —
test-files/generate.py(uv-runnable) produces clean, EICAR, and prompt injection test files in.txt,.pdf, and.docxformats for pipeline validation.
v0.6.0 — VirusTotal, Audit Trail & Egress Notifications
Added
- VirusTotal hash lookup scanning — optional SHA-256 hash check against the VirusTotal API. Files that pass GuardDuty are checked against VT; those with detections at or above
virustotal_thresholdare quarantined. API key stored as SSM SecureString. - File processing audit trail — optional DynamoDB table records every file at each pipeline stage (received, GuardDuty, VirusTotal, prompt injection, routed) with configurable TTL retention via
audit_trail_retention_days. - Egress notifications — optional SNS topic fires when clean files reach the egress bucket, enabling downstream automation.
- CloudWatch dashboard metric filters — expanded dashboard with VirusTotal scan metrics, prompt injection metrics, and egress notification tracking.
- New variables —
enable_virustotal_scanning,virustotal_api_key,virustotal_threshold,enable_egress_notifications,egress_notification_emails,enable_audit_trail,audit_trail_retention_days,enable_cloudwatch_dashboard. - New outputs —
virustotal_scanner_function_arn,egress_sns_topic_arn,audit_trail_table_arn,audit_trail_table_name,cloudwatch_dashboard_arn.
v0.5.1
Fixes
- Scope chatbot IAM policy: CloudWatch Logs access now restricted to this module's log groups (
/aws/lambda/${name_prefix}-*) instead of account-wideResource: "*" - Fix inconsistent tagging: All integration resources now use
local.default_tags(adds missingModuleandManagedBytags) - Isolate webhook integration failures: Discord and ServiceNow calls are now independent — one failing no longer blocks the other
v0.5.0 — Prompt Injection Scanning
What's New
Prompt Injection Scanning
Optional AI-powered scanning of uploaded documents for prompt injection attacks. When enabled, files that pass GuardDuty malware scanning are additionally scanned using the protectai/deberta-v3-base-prompt-injection-v2 ONNX model before reaching the egress bucket.
Supported formats: PDF, DOCX, PPTX, and plain text (TXT, CSV, MD, JSON, XML, HTML, YAML, LOG). Non-text files pass through with score 0.
How it works: The file router invokes a container-image Lambda synchronously. Text is extracted, tokenized into overlapping 512-token chunks, and scored by the model. Files scoring above the configurable threshold (default 80) are quarantined with an SNS alert.
Usage
module "secure_upload" {
source = "gofastercloud/secure-upload/aws"
name_prefix = "myapp"
enable_prompt_injection_scanning = true
# Optional tuning
prompt_injection_threshold = 80 # 0-100, default 80
prompt_injection_memory_size = 3008 # more memory = more CPU = faster cold starts
}New Variables
| Variable | Default | Description |
|---|---|---|
enable_prompt_injection_scanning |
false |
Enable the scanning pipeline |
prompt_injection_threshold |
80 |
Score (0-100) above which files are quarantined |
prompt_injection_memory_size |
3008 |
Scanner Lambda memory in MB |
prompt_injection_timeout |
120 |
Scanner Lambda timeout in seconds |
prompt_injection_reserved_concurrency |
5 |
Scanner Lambda concurrency |
prompt_injection_image_uri |
null |
BYO pre-built container image |
Other Changes
- File router timeout auto-adjusts to
scanner_timeout + 30swhen scanning is enabled - ECR repository with KMS encryption and lifecycle policy for scanner images
- 7 new validation tests (49 total)
- Documentation updated (README, ARCHITECTURE.md, CHANGELOG.md)
Full Changelog: v0.4.0...v0.5.0
v0.4.0 — Chat & Incident Integrations
Added
- Slack integration — AWS Chatbot channel configuration for malware alerts
- Microsoft Teams integration — AWS Chatbot channel configuration for malware alerts
- PagerDuty integration — SNS HTTPS subscription to PagerDuty Events API
- VictorOps / Splunk On-Call integration — SNS HTTPS subscription to VictorOps REST endpoint
- Discord integration — Lambda webhook forwarder with rich embeds; webhook URL stored in SSM Parameter Store (SecureString, KMS-encrypted)
- ServiceNow integration — Lambda webhook forwarder that creates incidents via REST API; credentials fetched from Secrets Manager at runtime
- Integration documentation — new integrations.md with detailed setup instructions for all six integrations
- 11 new validation tests — integration variable validation coverage (42 total tests)
All integrations are optional and disabled by default. See integrations.md for setup instructions.
v0.3.0 — End-to-end tested
What's Changed
Three bugs discovered and fixed during end-to-end smoke testing:
Fixed
-
S3 bucket policy
StringNotEqualsIfExistsincorrect for Deny statements — The v0.2.1 fix was wrong:StringNotEqualsIfExistson a Deny evaluates totruewhen the condition key is absent, which still blocks uploads without explicit encryption headers. Replaced withStringNotEquals+Nullcondition guard across all three buckets (6 statements). The Deny now only fires when the header is explicitly present with a non-KMS value; absent headers fall through to bucket default SSE-KMS. -
SFTP user IAM trust policy used wrong ARN type — Transfer Family passes a user ARN (
arn:aws:transfer:region:account:user/server-id/username), not a server ARN, when assuming the data access role. Changedaws:SourceArnfromserver/*touser/server-id/*. -
SFTP
ListBucketcondition too restrictive — Thes3:prefixcondition only alloweduploads/dave/*but Transfer Family also needs to stat the home directory prefix itself (uploads/davewithout trailing slash). Added the bare prefix as a second allowed value.
Verified
End-to-end pipeline tested:
| Test | GuardDuty Result | Lambda Route | SNS Alert |
|---|---|---|---|
| Clean text file (SFTP upload) | NO_THREATS_FOUND | → egress bucket | No |
| EICAR test file (SFTP upload) | THREATS_FOUND | → quarantine bucket | Yes (email delivered) |
| SFTP egress download | — | File downloaded successfully | — |
Full Changelog: v0.2.2...v0.3.0
v0.2.2
What's Changed
Fixed
-
KMS key policy missing CloudWatch Logs service principal — CloudWatch Logs uses a regional service principal (
logs.<region>.amazonaws.com) with akms:EncryptionContextcondition. The Lambda log group failed to create withAccessDeniedException. Added a dedicatedAllowCloudWatchLogsstatement to the KMS key policy. -
GuardDuty Malware Protection Plan creation failed — The GuardDuty IAM role was missing S3 bucket notification permissions (
s3:GetBucketNotification,s3:PutBucketNotification) and EventBridge managed rule permissions (events:PutRule,events:PutTargets,events:DeleteRule,events:RemoveTargets,events:DescribeRule). -
Transfer Family user creation failed with trailing slash — Home directory mapping targets included a trailing
/which Transfer Family rejects. Fixed withtrimsuffix().
Changed
lambda_reserved_concurrencynow accepts-1— Uses unreserved account concurrency, necessary for accounts where reserving even 10 would drop below the 50-unreserved minimum.
Full Changelog: v0.2.1...v0.2.2
v0.2.1
What's Changed
Fixed
- S3 bucket policy blocks AWS service uploads — Changed
StringNotEqualstoStringNotEqualsIfExistsinDenyNonKMSEncryptionandDenyWrongKMSKeypolicy statements across all three buckets (ingress, egress, quarantine). Services that rely on bucket default encryption and omitx-amz-server-side-encryptionheaders (e.g. AWS Transfer Family, GuardDuty, Lambda) were gettingAccessDenied. TheIfExistsvariant only evaluates the condition when the header is present, allowing headless uploads to fall through to bucket default SSE-KMS.
Changed
-
Bucket names include hashed account ID for global uniqueness — S3 bucket names changed from
<prefix>-<type>to<prefix>-<hash>-<type>where<hash>is the first 8 characters ofsha256(account_id). This prevents name collisions across AWS accounts without exposing the raw account ID. Breaking change: existing buckets will be recreated. -
IAM wildcards replaced with explicit actions —
kms:GenerateDataKey*,kms:ReEncrypt*,transfer:*, andec2:*replaced with enumerated action lists in the KMS key policy and README SCP recommendations.
Upgrading
This release contains two breaking changes:
-
Bucket names changed — All four buckets (ingress, egress, quarantine, logs) now include an account hash in the name. Terraform will plan to destroy and recreate them. Back up your data before upgrading or use
terraform state mvto migrate. -
KMS key policy updated — The
AllowServiceUsagestatement now uses explicit actions instead of wildcards. This is a non-destructive policy update.
Full Changelog: v0.2.0...v0.2.1