Skip to content

Add S3 bucket posture audit helper#79

Open
jamilahmadzai wants to merge 1 commit into
BAWES-Universe:masterfrom
jamilahmadzai:security/s3-bucket-posture-audit
Open

Add S3 bucket posture audit helper#79
jamilahmadzai wants to merge 1 commit into
BAWES-Universe:masterfrom
jamilahmadzai:security/s3-bucket-posture-audit

Conversation

@jamilahmadzai
Copy link
Copy Markdown

@jamilahmadzai jamilahmadzai commented May 15, 2026

/claim #55

Summary

  • add an offline S3 bucket posture audit CLI for maintainer-exported bucket settings JSON
  • flag missing versioning, destructive lifecycle rules, public access gaps, wildcard write CORS, public ACL grants, ownership/logging gaps, and missing owner/service/environment tags
  • emit Markdown or CSV remediation checklists while redacting account IDs and access-key-looking values
  • document the private export shape and add a synthetic regression check

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.mjs
  • node --check tools/audit-s3-bucket-posture.mjs
  • node --check tools/check-s3-bucket-posture-audit.mjs
  • git diff --check

Summary by CodeRabbit

  • New Features

    • Introduced S3 bucket posture audit tool for evaluating bucket configurations against security best practices. Analyzes multiple buckets, generates reports in Markdown or CSV format, and automatically redacts sensitive identifiers in output.
  • Documentation

    • Added comprehensive security documentation for the S3 bucket posture audit feature, including detailed usage instructions, configuration requirements, and sample test cases.

Review Change Stack

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.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 15, 2026

📝 Walkthrough

Walkthrough

This 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.

Changes

S3 Bucket Posture Audit Tool

Layer / File(s) Summary
Documentation and test fixture
README.md, docs/security/s3-bucket-posture-audit.md, tools/fixtures/s3-bucket-posture.sample.json
README gains a link to new security documentation. Security doc describes the audit tool's checks, input/output shapes, usage modes (Markdown report, CSV action plan, pattern filtering), regression invocation, and safety boundary (reads local JSON only, no AWS calls or modifications). Sample fixture defines three S3 buckets with versioning, access blocks, ownership controls, logging, lifecycle rules, CORS policies, and tags.
S3 bucket posture audit script
tools/audit-s3-bucket-posture.mjs
CLI script with argument parsing (format, output path, bucket patterns), filesystem/JSON input handling with normalization across multiple export shapes, core audit engine checking versioning, lifecycle destruction rules, public access block presence and values, CORS wildcard-write combinations, public AWS-group ACL grants, object ownership mode, logging presence, and required tag coverage. Output rendering redacts account IDs and AWS key patterns, formats as Markdown tables or CSV, and sets exit code non-zero for critical/high findings.
Posture audit validation
tools/check-s3-bucket-posture-audit.mjs
Test runner that executes the audit script on the sample fixture, verifies expected Markdown findings are present and sensitive values are redacted, runs CSV mode audit to confirm critical findings appear with correct structure, and validates private identifiers remain hidden across both formats.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A rabbit's audit hops through buckets deep,
Checking if S3 secrets safe do sleep,
No versioning? Tags lost? ACLs astray?
This fluffy script flags risky AWS decay!
With fixtures tested, markdown styled just right,
Bucket posture shines in the audit light.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Add S3 bucket posture audit helper' directly and clearly summarizes the main change: adding a new S3 bucket posture audit tool. It accurately reflects the primary objective of the PR.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
tools/audit-s3-bucket-posture.mjs (2)

123-134: ⚡ Quick win

Handle missing tag keys gracefully.

If a tag object has neither Key nor key property, 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 win

Consider separating current and noncurrent version expiration checks.

Line 221 chains Expiration.Days (current object expiration) with NoncurrentVersionExpiration.NoncurrentDays (noncurrent version expiration). These represent different lifecycle behaviors:

  • Expiration deletes current object versions after N days
  • NoncurrentVersionExpiration deletes noncurrent versions after they become noncurrent

Mixing 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

📥 Commits

Reviewing files that changed from the base of the PR and between 7b023ff and 7c924f4.

📒 Files selected for processing (5)
  • README.md
  • docs/security/s3-bucket-posture-audit.md
  • tools/audit-s3-bucket-posture.mjs
  • tools/check-s3-bucket-posture-audit.mjs
  • tools/fixtures/s3-bucket-posture.sample.json

Comment thread tools/audit-s3-bucket-posture.mjs Outdated
@jamilahmadzai jamilahmadzai force-pushed the security/s3-bucket-posture-audit branch from 7c924f4 to 8bd9642 Compare May 15, 2026 08:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant