Add S3 bucket posture audit helper#79
Conversation
📝 WalkthroughWalkthroughThis PR introduces an S3 bucket posture audit tool that reads S3 configuration JSON exports, checks for security and configuration issues (versioning, lifecycle policies, public access, CORS/ACL risks, object ownership, logging, and required tags), and outputs findings in Markdown or CSV format with sensitive data redaction. ChangesS3 Bucket Posture Audit Tool
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
tools/audit-s3-bucket-posture.mjs (2)
123-134: ⚡ Quick winHandle missing tag keys gracefully.
If a tag object has neither
Keynorkeyproperty,String(tag.Key ?? tag.key)becomes"undefined", creating a spurious tag entry.♻️ Proposed fix to filter invalid tags
function normalizeTagMap(tags) { if (!tags) { return {}; } if (Array.isArray(tags)) { - return Object.fromEntries(tags.map((tag) => [String(tag.Key ?? tag.key), tag.Value ?? tag.value])); + return Object.fromEntries( + tags + .filter((tag) => tag.Key != null || tag.key != null) + .map((tag) => [String(tag.Key ?? tag.key), tag.Value ?? tag.value]) + ); } if (Array.isArray(tags.TagSet)) { - return Object.fromEntries(tags.TagSet.map((tag) => [String(tag.Key ?? tag.key), tag.Value ?? tag.value])); + return Object.fromEntries( + tags.TagSet + .filter((tag) => tag.Key != null || tag.key != null) + .map((tag) => [String(tag.Key ?? tag.key), tag.Value ?? tag.value]) + ); } return Object.fromEntries(Object.entries(tags).map(([key, value]) => [key, String(value)])); }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tools/audit-s3-bucket-posture.mjs` around lines 123 - 134, normalizeTagMap currently converts tag objects with missing Key/key into an "undefined" key; update normalizeTagMap so it skips any tag entries that do not have a defined key (i.e., both tag.Key and tag.key are null/undefined) and optionally skip tags with undefined values; when handling Array.isArray(tags) and Array.isArray(tags.TagSet) use a map+filter pattern that extracts const k = tag.Key ?? tag.key and const v = tag.Value ?? tag.value, filter out entries where k == null (and optionally v == null), then build Object.fromEntries from the remaining [String(k), String(v)] pairs so no "undefined" keys are produced.
219-244: ⚡ Quick winConsider separating current and noncurrent version expiration checks.
Line 221 chains
Expiration.Days(current object expiration) withNoncurrentVersionExpiration.NoncurrentDays(noncurrent version expiration). These represent different lifecycle behaviors:
Expirationdeletes current object versions after N daysNoncurrentVersionExpirationdeletes noncurrent versions after they become noncurrentMixing them could miss destructive rules or misreport severity.
♻️ Proposed refactor to check both expiration types
const lifecycleRules = getLifecycleRules(bucket).filter((rule) => (rule.Status ?? rule.status) === "Enabled"); for (const rule of lifecycleRules) { - const days = Number(rule.Expiration?.Days ?? rule.expiration?.days ?? rule.NoncurrentVersionExpiration?.NoncurrentDays); + const expirationDays = Number(rule.Expiration?.Days ?? rule.expiration?.days); + const noncurrentDays = Number(rule.NoncurrentVersionExpiration?.NoncurrentDays ?? rule.NoncurrentVersionExpiration?.noncurrentDays); + const days = Number.isFinite(expirationDays) ? expirationDays : noncurrentDays; if (!Number.isFinite(days)) { continue; }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tools/audit-s3-bucket-posture.mjs` around lines 219 - 244, The lifecycle check currently mixes current-object expiration and noncurrent-version expiration by reading both into a single "days" value; update the loop over lifecycleRules (from getLifecycleRules) to separately extract and evaluate rule.Expiration.Days (currentDays) and rule.NoncurrentVersionExpiration.NoncurrentDays (noncurrentDays), treat undefined values independently (skip when not finite), and call addFinding for each relevant expiration type (include rule.ID/id and the days in the message) using the existing severity logic (use the tempBucket and threshold rules already applied to "days") so destructive current-object expirations and destructive noncurrent-version expirations are reported separately.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@tools/audit-s3-bucket-posture.mjs`:
- Around line 28-35: The argument parsing reads argv[++index] without checking
bounds, so update the handlers for --format, --output and --bucket-pattern to
first peek argv[index+1] (or test argv.length > index + 1) and if it is
undefined, emit a clear error/usage message and exit (or throw); otherwise
increment index and assign to options.format, options.output or push onto
options.bucketPatterns. Keep the same symbols: argv, index, options.format,
options.output, and options.bucketPatterns when implementing the check.
---
Nitpick comments:
In `@tools/audit-s3-bucket-posture.mjs`:
- Around line 123-134: normalizeTagMap currently converts tag objects with
missing Key/key into an "undefined" key; update normalizeTagMap so it skips any
tag entries that do not have a defined key (i.e., both tag.Key and tag.key are
null/undefined) and optionally skip tags with undefined values; when handling
Array.isArray(tags) and Array.isArray(tags.TagSet) use a map+filter pattern that
extracts const k = tag.Key ?? tag.key and const v = tag.Value ?? tag.value,
filter out entries where k == null (and optionally v == null), then build
Object.fromEntries from the remaining [String(k), String(v)] pairs so no
"undefined" keys are produced.
- Around line 219-244: The lifecycle check currently mixes current-object
expiration and noncurrent-version expiration by reading both into a single
"days" value; update the loop over lifecycleRules (from getLifecycleRules) to
separately extract and evaluate rule.Expiration.Days (currentDays) and
rule.NoncurrentVersionExpiration.NoncurrentDays (noncurrentDays), treat
undefined values independently (skip when not finite), and call addFinding for
each relevant expiration type (include rule.ID/id and the days in the message)
using the existing severity logic (use the tempBucket and threshold rules
already applied to "days") so destructive current-object expirations and
destructive noncurrent-version expirations are reported separately.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 5beb5f3b-868f-4391-b9fd-f20f7f26a4ab
📒 Files selected for processing (5)
README.mddocs/security/s3-bucket-posture-audit.mdtools/audit-s3-bucket-posture.mjstools/check-s3-bucket-posture-audit.mjstools/fixtures/s3-bucket-posture.sample.json
7c924f4 to
8bd9642
Compare
/claim #55
Summary
Safety boundary
No live AWS/IAM calls, bucket policy changes, key rotation, candidate data, account IDs, credentials, raw CloudTrail records, or private S3 exports are accessed or included. The tool only reads local JSON files supplied by maintainers inside their trusted environment.
Verification
node tools/check-s3-bucket-posture-audit.mjsnode --check tools/audit-s3-bucket-posture.mjsnode --check tools/check-s3-bucket-posture-audit.mjsgit diff --checkSummary by CodeRabbit
New Features
Documentation
Payout
PayPal: jamilurrehman722@gmail.com (if a direct payout method is needed).
Payment method: Algora bounty-platform payout to GitHub user @jamilahmadzai for issue #55.