Skip to content

feat(aws-secrets-inspector): hybrid AWS Secrets Manager connector with safe retrieval mode#198

Open
AnandSundar wants to merge 24 commits into
GRCEngClub:mainfrom
AnandSundar:feat/aws-secrets-inspector
Open

feat(aws-secrets-inspector): hybrid AWS Secrets Manager connector with safe retrieval mode#198
AnandSundar wants to merge 24 commits into
GRCEngClub:mainfrom
AnandSundar:feat/aws-secrets-inspector

Conversation

@AnandSundar

@AnandSundar AnandSundar commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds a new connector plugin aws-secrets-inspector that:

  1. Inspector mode (default) — emits v1 finding-contract documents for compliance posture (rotation, customer-managed KMS, public-access resource policy, inactive access pattern).
  2. Retrieval mode (--retrieve=<name>) — opt-in path that returns a single secret value to stdout or to a 0600-permission file under ~/.config/claude-grc/secrets/. The value never lands in the findings cache, runs.log, or stderr.

Closes #184.

What it evaluates

One Finding per secret, with four SCF-mapped evaluations (IDs verified against the live SCF API at grcengclub.github.io/scf-api):

SCF Check Severity if failing Source of truth
CRY-09 Rotation enabled high describe-secretRotationEnabled, RotationRules
CRY-09 Customer-managed KMS key high describe-secretKmsKeyId (must not be alias/aws/secretsmanager)
IAC-21 Resource policy excludes public access critical get-resource-policyPrincipal:"*" granting secretsmanager:GetSecretValue (or * / secretsmanager:*)
IAC-15.3 Access pattern (LastAccessedDate ≤ 180d) medium describe-secretLastAccessedDate

ResourcePolicyNotFoundException records IAC-21 as pass with a note (the secret relies on IAM). Other policy failures record IAC-21 as inconclusive.

LastAccessedDate null records IAC-15.3 as inconclusive; operators can rely on raw_attributes.RotationAgeDays and their consumer inventory.

Hybrid shape — the only connector of its kind

This is the first connector in the marketplace with two modes. Inspector mode is the default; retrieval mode is opt-in via --retrieve=<name>. The retrieval branch short-circuits at the top of main() and never reaches the cache-writing helpers.

Three safety invariants, enforced by structure:

  1. No findings cache writes. Retrieval never calls fs.writeFile(CACHE_DIR, ...).
  2. No value in runs.log. The retrieve manifest records byte_size and sha256 of the artifact — never the value, prefix, or entropy estimate.
  3. --write-to is restricted to ~/.config/claude-grc/secrets/. Any path that resolves outside that root (including ../ escapes and absolute paths like /etc/cron.d/evil) is rejected with exit 2 and a clear error. File is created at mode 0600, parent dir at mode 0700 (umask 077 enforced during the write).

Files

plugins/connectors/aws-secrets-inspector/
├── .claude-plugin/plugin.json
├── commands/
│   ├── collect.md
│   ├── retrieve.md
│   ├── setup.md
│   └── status.md
├── scripts/
│   ├── collect.js     # inspector + retrieval in one binary
│   ├── setup.sh
│   └── status.sh
└── skills/
    └── aws-secrets-inspector-expert/
        └── SKILL.md

Plus:

tests/
├── aws-secrets-inspector-collect.test.js
├── aws-secrets-inspector-paths.mjs
└── fixtures/
    ├── aws-api/secretsmanager/     # canned AWS CLI responses
    └── findings/aws-secrets-inspector/
        ├── 001-rotation-fail-cmk-fail.json
        ├── 002-public-policy-critical.json
        └── 003-inactive-access-inconclusive.json

Marketplace registration added to .claude-plugin/marketplace.json.

Test coverage

$ npm run test:aws-secrets-inspector
✔ inspector mode emits 3 schema-conformant findings with the expected SCF evaluations
✔ retrieval mode emits value to stdout and never writes the value to runs.log or cache
✔ retrieval mode rejects --write-to paths outside the secrets dir
✔ retrieval mode writes the value to a 0600 file inside the secrets dir (POSIX)
ℹ tests 4, pass 4, fail 0

The behavioral test runs the connector in fixture mode (canned AWS responses on disk, no real AWS calls) and verifies:

  • Schema conformance of every emitted finding.
  • Correct SCF evaluations across rotation, public-policy, and inconclusive access cases.
  • Value never lands in runs.log (manifest carries byte_size + sha256 only).
  • --write-to path-traversal blocked.
  • 0600 file mode (POSIX; chmod is a no-op on Windows NTFS).

Two bugs caught and fixed by the test pass

  1. collect.js was emitting tags in the AWS shape ([{Key, Value}, ...]) but the v1 contract expects a flat object ({Owner: "platform-team", ...}). Added tagsToObject() to flatten the AWS shape.
  2. Both the inspector and retrieval paths assumed ~/.cache/claude-grc/ already existed when writing to runs.log. On a fresh setup the parent dir was missing and appendFile failed. Added a defensive mkdir in both paths.

SCF ID correction

The plan's earlier CRY-07 / CRY-05 / DCH-01.2 / MON-01.2 IDs were wrong. During implementation, all four IDs were verified against the live SCF API. Final IDs: CRY-09, CRY-09, IAC-21, IAC-15.3. Documented in the SKILL.md, the commands/collect.md evaluation table, and the runtime evaluation code.

Out of scope for v0.1 (documented in SKILL.md)

  • Cross-region replication
  • Rotation success validation (only the boolean)
  • Lambda rotation function health
  • Bulk retrieve (batch-get-secret-value) — by design, retrieval is one-at-a-time
  • NotPrincipal / Condition / aws:SourceVpce evaluation in resource policies — operator must inspect raw policy in raw_attributes.ResourcePolicy
  • Cross-account enumeration — connector uses the standard AWS CLI profile chain (--profile=), does NOT implement sts:AssumeRole itself

Checklist

  • Plugin scaffold + marketplace entry
  • setup.sh and status.sh mirror the existing aws-inspector pattern
  • collect.js implements both modes, schema-conformant
  • All four commands/*.md docs written
  • SKILL.md covers interpretation, mode selection, safety contract
  • 3 contract fixtures validate against schemas/finding.schema.json
  • Behavioral test passes (4/4)
  • npm run test:aws-secrets-inspector added
  • detect-secrets baseline unchanged (synthetic fixture data, no real secret patterns)

Summary by CodeRabbit

  • New Features

    • Added an AWS Secrets Inspector connector plugin for evaluating AWS Secrets Manager against security/compliance checks.
    • Added /aws-secrets-inspector:collect (multi-region inspection with caching) and /aws-secrets-inspector:retrieve (safety-guarded retrieval), plus /aws-secrets-inspector:setup and /aws-secrets-inspector:status.
  • Documentation

    • Added detailed command guides, including evaluation outcomes, exit codes, and a retrieval safety contract covering cache handling and restricted destination writes.
  • Tests

    • Added fixture-based end-to-end coverage and hardening tests for destination restrictions, permissions, region precedence, and symlink-escape resistance.

@AnandSundar
AnandSundar requested a review from a team as a code owner June 20, 2026 22:29
@coderabbitai

coderabbitai Bot commented Jun 20, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds the aws-secrets-inspector connector plugin (v0.1.0) for AWS Secrets Manager. The plugin includes a Bash setup script, a Node.js collect.js script implementing inspector mode (four SCF-mapped evaluations per secret) and retrieve mode (single secret fetch with path-safety enforcement), a Bash status script, an expert skill, four command documentation files, and a fixture-backed behavioral test suite with path-traversal and symlink-attack hardening.

Changes

aws-secrets-inspector connector plugin

Layer / File(s) Summary
Plugin registration and setup command
.claude-plugin/marketplace.json, package.json, plugins/connectors/aws-secrets-inspector/.claude-plugin/plugin.json, plugins/connectors/aws-secrets-inspector/scripts/setup.sh, plugins/connectors/aws-secrets-inspector/commands/setup.md
Registers aws-secrets-inspector in the plugin marketplace and test scripts, adds the plugin.json manifest with version/description/author/license/keywords, and implements setup.sh which verifies AWS CLI presence, resolves caller identity via sts get-caller-identity, writes the connector YAML config under ~/.config/claude-grc/connectors/ with account/profile/region defaults, creates cache directories, initializes ~/.config/claude-grc/secrets with mode 0700, and emits an admin-privilege IAM warning when the caller ARN matches admin patterns.
Inspector mode: secret evaluation and findings
plugins/connectors/aws-secrets-inspector/scripts/collect.js (primary sections), plugins/connectors/aws-secrets-inspector/commands/collect.md
Implements inspector pipeline: main() with CLI parsing and inspector orchestration, collectSecretsManager() listing secrets per region and deduplicating by ARN, evaluateSecret() producing one Finding per secret with four SCF-mapped control evaluations (CRY-09 rotation enabled/disabled, CRY-09 customer-managed vs AWS-managed KMS, IAC-21 public resource policy via Principal="*" detection, IAC-15.3 inactive access via LastAccessedDate), plus policy parsing (detectPublicAccess, normalizePrincipals, actionAllowsRead) and data normalization (parseIsoDate, tagsToObject). Writes findings cache JSON indexed by run-id and appends run manifest to runs.log. Documents command usage, evaluation rules, output artifacts, exit codes, IAM permissions, examples, and cross-account access via AWS CLI profiles.
Retrieve mode, path safety, and CLI utilities
plugins/connectors/aws-secrets-inspector/scripts/collect.js (retrieve and safety sections), plugins/connectors/aws-secrets-inspector/commands/retrieve.md
Implements retrieveSecret() (loads YAML config, resolves AWS profile and region precedence, calls secretsmanager get-secret-value with optional version-stage/version-id, maps AWS CLI errors to exit codes, outputs JSON with SecretString or base64 SecretBinary, computes SHA-256 hash and byte size, appends metadata-only manifest to runs.log), safeResolveWritePath() (validates destination within ~/.config/claude-grc/secrets/ via realpath-based symlink prevention and path.relative escape detection), aws() execution utility with fixture-backed test emulation and error mapping, parseArgs(), parseYaml() (minimal indentation-based parser), makeRunId(), fail() helper, and CLI bootstrap. Documents command arguments, output contracts (stdout vs restricted 0600 file), safety contract invariants, exit codes, IAM permissions, and cross-account usage.
Status command: health check and freshness
plugins/connectors/aws-secrets-inspector/scripts/status.sh, plugins/connectors/aws-secrets-inspector/commands/status.md
Implements status.sh validating connector YAML existence and AWS CLI availability, extracting profile/region from YAML, calling sts get-caller-identity to verify credentials (emitting credentials-expired status on failure), probing secretsmanager list-secrets to test access, computing cache freshness by finding newest JSON file and determining age (<7 days = ready, ≥7 days = stale), parsing cached JSON to extract resource and evaluation counts. Documents status output format, status values, permission validation semantics, and least-privilege denial behavior.
Expert skill for findings and remediation
plugins/connectors/aws-secrets-inspector/skills/aws-secrets-inspector-expert/SKILL.md
Adds the aws-secrets-inspector-expert skill covering mode selection guidance, SCF control check criteria with pass/fail/inconclusive rules and severity mappings, framework crosswalk (SOC 2, NIST 800-53, ISO 27002, PCI DSS), retrieval safety contract (no cache writes, no secret in runs.log, strict --write-to restrictions with path-escape/absolute-path rejection and 0700/0600 permissions), operator remediation playbooks, v0.1.0 connector limits, and common pitfalls (profile selection, separate retrieve IAM permissions, KMS cross-account requirements).
Behavioral tests, unit tests, and fixture data
tests/aws-secrets-inspector-collect.test.js, tests/aws-secrets-inspector-paths.mjs, tests/aws-secrets-inspector-symlinks.mjs, tests/fixtures/aws-api/secretsmanager/..., tests/fixtures/findings/aws-secrets-inspector/*
Adds fixture-backed tests for inspector mode (finding schema, SCF control outcomes), retrieve mode (stdout payload, runs.log without secret value, cache immutability, file permissions), --write-to path rejection (absolute escape and .. traversal), POSIX permissions (0600 file, 0700 directory), and region precedence. Unit tests for safeResolveWritePath with path table-driven assertions and symlink-attack integration test with runtime symlink-capability detection. Fixture data covers AWS API responses (list-secrets, describe-secret, get-resource-policy, get-secret-value) and three expected findings JSONs (rotation/CMK failures, public-policy critical, inconclusive with null LastAccessedDate).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐇 Hop, hop — a new plugin has grown,
Secrets in AWS safely known.
Four SCF checks, a rotation test,
KMS keys put to the quest.
With --write-to locked at 0600 tight,
This rabbit guards secrets through the night! 🔐

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 72.41% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(aws-secrets-inspector): hybrid AWS Secrets Manager connector with safe retrieval mode' clearly summarizes the main addition: a new AWS Secrets Manager connector with dual inspector and retrieval modes.
Linked Issues check ✅ Passed The PR fully implements the proposed capabilities from issue #184: GetSecretValue retrieval mode, rotation metadata reading in inspector mode, region-aware configuration, cross-account support via AWS CLI profiles, and integration readiness via the standardized v1 finding schema.
Out of Scope Changes check ✅ Passed All changes are directly scoped to the aws-secrets-inspector connector implementation: plugin metadata, documentation, scripts, tests, and fixtures. No unrelated modifications to other connectors or core framework components are present.

✏️ 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.

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Add aws-secrets-inspector connector with hybrid inspect/retrieve modes
✨ Enhancement 🧪 Tests 📝 Documentation ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

Description

• Add aws-secrets-inspector connector emitting v1 findings for Secrets Manager posture checks.
• Introduce opt-in --retrieve mode with path-restricted 0600 output and safe logging.
• Register plugin, add operator docs/skill guidance, and fixture-driven behavioral tests.
Diagram

graph TD
  A["Operator / CI"] --> B["collect.js"] --> C{"--retrieve?"}
  C -- "no" --> D["AWS CLI (list/describe/policy)"] --> E[("Findings cache")]
  E --> F[("runs.log")]
  C -- "yes" --> G["AWS CLI (get-secret-value)"] --> H["stdout or 0600 file (secrets dir)"]
  H --> F
  subgraph Legend
    direction LR
    _p["Process"] ~~~ _d{"Decision"} ~~~ _s[("File store")]
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Split into two binaries (collect.js + retrieve.js)
  • ➕ Clearer separation of concerns; reduces risk of accidental cross-path leakage
  • ➕ Simpler CLI help/argument parsing per mode
  • ➖ Harder to enforce “short-circuit before cache helpers” structurally without duplication
  • ➖ More files/entrypoints to document and register; drift risk between shared utilities
2. Use AWS SDK instead of shelling out to AWS CLI
  • ➕ Avoids dependency on aws binary; better structured errors and pagination
  • ➕ Easier unit testing via SDK stubs
  • ➖ Larger dependency surface and auth/region resolution complexity compared to existing connector patterns
  • ➖ Would diverge from the repository’s established “AWS CLI wrapper” approach
3. Centralize common connector utilities (YAML parsing, runs.log append, safe file writes)
  • ➕ Reduces duplication across connectors (aws-inspector, aws-secrets-inspector)
  • ➕ Makes security invariants reusable and more reviewable
  • ➖ More upfront refactor and coordination; higher blast radius for existing connectors
  • ➖ Not necessary to land the new connector; can be follow-up hardening work

Recommendation: The PR’s approach (single entrypoint with an early retrieval short-circuit) is a strong fit for the stated safety invariants: it makes “no cache writes in retrieval mode” enforceable by control flow. Consider a follow-up to extract shared utilities once a second hybrid/safety-sensitive connector exists, but keeping this PR focused on shipping the new connector and tests is appropriate.

Files changed (23) +2091 / -0

Enhancement (3) +1054 / -0
collect.jsImplement hybrid inspector + safe retrieval logic for Secrets Manager +821/-0

Implement hybrid inspector + safe retrieval logic for Secrets Manager

• Implements inspector mode to list/describe secrets, fetch resource policies, and emit one v1 Finding per secret with four SCF-mapped evaluations (rotation, CMK, public policy, inactivity). Adds opt-in retrieval mode (--retrieve) that outputs a single secret value to stdout or a restricted 0600 file, logging only byte_size/sha256 to runs.log and structurally avoiding findings-cache writes. Includes a fixture-mode AWS call interceptor for deterministic tests.

plugins/connectors/aws-secrets-inspector/scripts/collect.js

setup.shAdd idempotent setup script creating config + secrets dir +148/-0

Add idempotent setup script creating config + secrets dir

• Validates aws CLI presence and credential resolution, writes the connector YAML config, ensures cache/runs.log exist, and creates ~/.config/claude-grc/secrets at 0700 for retrieval outputs with least-privilege guidance.

plugins/connectors/aws-secrets-inspector/scripts/setup.sh

status.shAdd status script for config/credential/cache freshness checks +85/-0

Add status script for config/credential/cache freshness checks

• Reports config state, verifies STS identity, checks secrets dir mode, probes secretsmanager:ListSecrets, and summarizes cache freshness and last run metrics.

plugins/connectors/aws-secrets-inspector/scripts/status.sh

Tests (12) +548 / -0
aws-secrets-inspector-collect.test.jsAdd fixture-driven behavioral tests covering both modes and invariants +202/-0

Add fixture-driven behavioral tests covering both modes and invariants

• Runs collect.js in fixture mode to validate schema conformance and expected evaluation outcomes, and verifies retrieval-mode invariants: no cache writes, no value in runs.log, path traversal rejection, and POSIX file mode enforcement.

tests/aws-secrets-inspector-collect.test.js

aws-secrets-inspector-paths.mjsAdd unit-style harness for write-path traversal logic +51/-0

Add unit-style harness for write-path traversal logic

• Provides a small executable test harness mirroring safeResolveWritePath behavior to exercise acceptance/rejection cases for path traversal and out-of-root writes.

tests/aws-secrets-inspector-paths.mjs

arn_aws_secretsmanager_us-east-1_123456789012_secret_legacy-db-credentials-AbCdEf.jsonAdd describe-secret fixture for rotation/KMS failure case +13/-0

Add describe-secret fixture for rotation/KMS failure case

• Canned AWS response representing a secret with rotation disabled and AWS-managed KMS key for deterministic inspector-mode evaluation testing.

tests/fixtures/aws-api/secretsmanager/describe-secret/us-east-1/arn_aws_secretsmanager_us-east-1_123456789012_secret_legacy-db-credentials-AbCdEf.json

arn_aws_secretsmanager_us-east-1_123456789012_secret_never-accessed-credential-MnOpQr.jsonAdd describe-secret fixture for never-accessed/inconclusive case +13/-0

Add describe-secret fixture for never-accessed/inconclusive case

• Canned AWS response with LastAccessedDate null and CMK present to validate IAC-15.3 inconclusive behavior.

tests/fixtures/aws-api/secretsmanager/describe-secret/us-east-1/arn_aws_secretsmanager_us-east-1_123456789012_secret_never-accessed-credential-MnOpQr.json

arn_aws_secretsmanager_us-east-1_123456789012_secret_public-thirdparty-api-key-GhIjKl.jsonAdd describe-secret fixture for public policy case +13/-0

Add describe-secret fixture for public policy case

• Canned AWS response representing an actively accessed, rotating, CMK-encrypted secret used with a separate public resource policy fixture.

tests/fixtures/aws-api/secretsmanager/describe-secret/us-east-1/arn_aws_secretsmanager_us-east-1_123456789012_secret_public-thirdparty-api-key-GhIjKl.json

arn_aws_secretsmanager_us-east-1_123456789012_secret_never-accessed-credential-MnOpQr.deniedAdd denied-marker fixture for get-resource-policy AccessDenied simulation +0/-0

Add denied-marker fixture for get-resource-policy AccessDenied simulation

• Presence of this marker forces fixture mode to throw an AccessDenied-shaped error so inspector mode records IAC-21 as inconclusive.

tests/fixtures/aws-api/secretsmanager/get-resource-policy/us-east-1/arn_aws_secretsmanager_us-east-1_123456789012_secret_never-accessed-credential-MnOpQr.denied

arn_aws_secretsmanager_us-east-1_123456789012_secret_public-thirdparty-api-key-GhIjKl.jsonAdd resource policy fixture that grants public access +5/-0

Add resource policy fixture that grants public access

• Canned get-resource-policy response containing Principal="*" granting secretsmanager:GetSecretValue to validate critical IAC-21 fail detection.

tests/fixtures/aws-api/secretsmanager/get-resource-policy/us-east-1/arn_aws_secretsmanager_us-east-1_123456789012_secret_public-thirdparty-api-key-GhIjKl.json

arn_aws_secretsmanager_us-east-1_123456789012_secret_legacy-db-credentials-AbCdEf.jsonAdd get-secret-value fixture for retrieval-mode output +8/-0

Add get-secret-value fixture for retrieval-mode output

• Canned GetSecretValue response containing a synthetic SecretString used to validate stdout/file output and log redaction behavior.

tests/fixtures/aws-api/secretsmanager/get-secret-value/arn_aws_secretsmanager_us-east-1_123456789012_secret_legacy-db-credentials-AbCdEf.json

us-east-1.jsonAdd list-secrets fixture with three representative secrets +36/-0

Add list-secrets fixture with three representative secrets

• Canned SecretList including tags in AWS {Key,Value} shape to validate inspector enumeration and tag-flattening to the v1 contract.

tests/fixtures/aws-api/secretsmanager/list-secrets/us-east-1.json

001-rotation-fail-cmk-fail.jsonAdd expected finding fixture for rotation+CMK failures +75/-0

Add expected finding fixture for rotation+CMK failures

• Provides a schema-shaped example Finding capturing CRY-09 failures plus IAC pass signals for documentation/contract fixture validation.

tests/fixtures/findings/aws-secrets-inspector/001-rotation-fail-cmk-fail.json

002-public-policy-critical.jsonAdd expected finding fixture for critical public policy detection +70/-0

Add expected finding fixture for critical public policy detection

• Provides a schema-shaped example Finding showing IAC-21 critical failure when a resource policy grants public access.

tests/fixtures/findings/aws-secrets-inspector/002-public-policy-critical.json

003-inactive-access-inconclusive.jsonAdd expected finding fixture for inconclusive access/policy reads +62/-0

Add expected finding fixture for inconclusive access/policy reads

• Provides a schema-shaped example Finding showing IAC-15.3 inconclusive when LastAccessedDate is absent and IAC-21 inconclusive on policy read failure.

tests/fixtures/findings/aws-secrets-inspector/003-inactive-access-inconclusive.json

Documentation (5) +473 / -0
collect.mdDocument inspector-mode collection command and SCF mappings +112/-0

Document inspector-mode collection command and SCF mappings

• Adds operator documentation for scanning Secrets Manager and emitting v1 findings, including control IDs, interpretations, permissions, output locations, and cross-account profile-chain usage.

plugins/connectors/aws-secrets-inspector/commands/collect.md

retrieve.mdDocument opt-in retrieval mode safety contract +104/-0

Document opt-in retrieval mode safety contract

• Describes --retrieve usage, stdout vs 0600 file output, restricted write root, and the no-secret-in-logs/cache guarantees with explicit exit codes and IAM permissions.

plugins/connectors/aws-secrets-inspector/commands/retrieve.md

setup.mdDocument setup workflow and generated artifacts +101/-0

Document setup workflow and generated artifacts

• Explains prerequisites validation, config contents, cache/secrets directory creation, credential precedence, and cross-account profile-chain guidance.

plugins/connectors/aws-secrets-inspector/commands/setup.md

status.mdDocument status/health check behavior +46/-0

Document status/health check behavior

• Defines status outputs and explains the ListSecrets probe semantics, including the case where retrieve-mode permissions differ from inspector-mode permissions.

plugins/connectors/aws-secrets-inspector/commands/status.md

SKILL.mdAdd expert skill guidance for interpreting findings and retrieval safety +110/-0

Add expert skill guidance for interpreting findings and retrieval safety

• Provides an interpretation layer for the connector outputs, mode selection guidance, SCF crosswalk context, remediation guidance, and explicit limitations of policy detection and retrieval design.

plugins/connectors/aws-secrets-inspector/skills/aws-secrets-inspector-expert/SKILL.md

Other (3) +16 / -0
marketplace.jsonRegister aws-secrets-inspector in plugin marketplace +6/-0

Register aws-secrets-inspector in plugin marketplace

• Adds the new connector entry (name, source path, description, version) to the marketplace registry so it can be discovered and installed like other connectors.

.claude-plugin/marketplace.json

package.jsonAdd dedicated test script for aws-secrets-inspector +1/-0

Add dedicated test script for aws-secrets-inspector

• Introduces npm script test:aws-secrets-inspector to run the new fixture-based behavioral test suite via node --test.

package.json

plugin.jsonDefine connector manifest for aws-secrets-inspector +9/-0

Define connector manifest for aws-secrets-inspector

• Adds plugin metadata (name/version/description/repo/keywords) for marketplace + tooling integration.

plugins/connectors/aws-secrets-inspector/.claude-plugin/plugin.json

@qodo-code-review

qodo-code-review Bot commented Jun 20, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (2) 📎 Requirement gaps (0) 📜 Skill insights (0)

Context used
✅ Compliance rules (platform): 17 rules

Grey Divider


Action required

1. plugin.json author not string 📘 Rule violation § Compliance
Description
The new plugin manifest sets author to an object instead of a non-empty string. This violates the
minimal manifest requirements and may break tooling that expects author to be a string.
Code

plugins/connectors/aws-secrets-inspector/.claude-plugin/plugin.json[R2-6]

+  "name": "aws-secrets-inspector",
+  "version": "0.1.0",
+  "description": "GRC connector for AWS Secrets Manager: evaluates rotation, customer-managed KMS key, resource policy (public access), and access patterns. Hybrid shape — emits v1 finding-contract documents in inspector mode and supports opt-in secret retrieval to stdout or 0600 files in retrieve mode.",
+  "author": { "name": "GRC Engineering Club Contributors" },
+  "license": "MIT",
Evidence
PR Compliance ID 396511 requires name, version, and author to be present and each a non-empty
string. The added manifest includes author as an object ({ "name": ... }) instead of a string.

Rule 396511: Require minimal plugin manifest fields in .claude-plugin/plugin.json
plugins/connectors/aws-secrets-inspector/.claude-plugin/plugin.json[1-8]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`plugins/connectors/aws-secrets-inspector/.claude-plugin/plugin.json` does not satisfy the manifest requirement that `author` is a non-empty string.

## Issue Context
Compliance requires top-level keys `name`, `version`, and `author` to exist and each must be a non-empty string.

## Fix Focus Areas
- plugins/connectors/aws-secrets-inspector/.claude-plugin/plugin.json[2-6]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Error findings break schema ✓ Resolved 🐞 Bug ≡ Correctness
Description
When evaluateSecret() throws, collectSecretsManager() emits a Finding with resource.tags as an
array and only 3 evaluations, contradicting the connector’s own “four checks” contract. This can
cause schema validation failures and/or downstream consumer breakage exactly in the common error
scenarios (AccessDenied, throttling, transient AWS errors).
Code

plugins/connectors/aws-secrets-inspector/scripts/collect.js[R184-207]

+    } catch (err) {
+      // Per-secret failure: emit a Finding with inconclusive evaluations
+      // for the four checks rather than dropping the secret silently.
+      const name = s.Name || arn;
+      findings.push({
+        schema_version: '1.0.0',
+        source: SOURCE,
+        source_version: SOURCE_VERSION,
+        run_id: runId,
+        collected_at: new Date().toISOString(),
+        resource: {
+          type: 'aws_secretsmanager_secret',
+          id: arn,
+          arn,
+          region,
+          account_id: accountId,
+          tags: s.Tags || []
+        },
+        raw_attributes: { Name: name, ARN: arn },
+        evaluations: [
+          { control_framework: 'SCF', control_id: 'CRY-09',   status: 'inconclusive', severity: 'medium', message: `describe-secret failed: ${err.message}` },
+          { control_framework: 'SCF', control_id: 'IAC-21',   status: 'inconclusive', severity: 'medium', message: `describe-secret failed: ${err.message}` },
+          { control_framework: 'SCF', control_id: 'IAC-15.3', status: 'inconclusive', severity: 'medium', message: `describe-secret failed: ${err.message}` }
+        ]
Evidence
The fallback Finding explicitly emits tags as an array and only 3 evaluations, but the v1 finding
schema requires resource.tags to be an object map and the connector’s own comment says it should
emit inconclusive evaluations for four checks.

plugins/connectors/aws-secrets-inspector/scripts/collect.js[170-208]
schemas/finding.schema.json[45-88]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The per-secret error fallback Finding emitted in `collectSecretsManager()` is not v1 schema conformant:
- `resource.tags` is emitted as `s.Tags || []` (array) but the schema requires an object map.
- The fallback emits 3 evaluations, but the connector’s contract is 4 evaluations per secret (rotation CRY-09, CMK CRY-09, IAC-21, IAC-15.3).

## Issue Context
This path runs when `evaluateSecret()` fails (e.g., `DescribeSecret` denied, throttled, malformed response). Output must remain schema-conformant so pipelines can still consume partial results.

## Fix Focus Areas
- plugins/connectors/aws-secrets-inspector/scripts/collect.js[170-209]

### Suggested change
- Replace `tags: s.Tags || []` with `tags: tagsToObject(s.Tags)`.
- Emit **four** inconclusive evaluations in the fallback:
 - CRY-09 (rotation) inconclusive
 - CRY-09 (CMK) inconclusive
 - IAC-21 inconclusive
 - IAC-15.3 inconclusive
- Ensure the messages remain accurate (e.g., `describe-secret failed` is fine since both CRY-09 checks depend on it).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Retrieve mode lacks region override ✓ Resolved 📎 Requirement gap ☼ Reliability
Description
Retrieval mode (--retrieve) does not set an AWS region from connector configuration nor offer a
--region override, so it relies on the operator’s ambient AWS CLI region resolution. This violates
the requirement for region-aware defaulting with explicit override support and can cause retrieval
from an unintended region or false NOT_FOUND errors when a secret name (not an ARN) is used.
Code

plugins/connectors/aws-secrets-inspector/scripts/collect.js[R496-510]

+async function retrieveSecret(args, log) {
+  const startedAt = Date.now();
+  let config;
+  try { config = parseYaml(await fs.readFile(CONFIG_FILE, 'utf8')); }
+  catch { fail(EXIT.NOT_CONFIGURED, `config missing (${CONFIG_FILE}). Run /aws-secrets-inspector:setup first.`); }
+
+  const profile = args.profile || config.profile || process.env.AWS_PROFILE || '';
+  const env = { ...process.env };
+  if (profile) env.AWS_PROFILE = profile;
+
+  // 1. Build the AWS CLI args for get-secret-value.
+  const awsArgs = ['secretsmanager', 'get-secret-value', '--secret-id', args.retrieve, '--output', 'json'];
+  if (args.versionStage) awsArgs.push('--version-stage', args.versionStage);
+  else if (args.versionId) awsArgs.push('--version-id', args.versionId);
+
Evidence
PR Compliance ID 396532 requires a sensible default region and explicit region override support, but
in retrieve mode the AWS CLI invocation built in retrieveSecret()/awsArgs omits --region and
the argument parsing/docs do not provide a --region override for retrieval. By contrast, inspector
mode derives regions from config/defaults and passes --region on AWS CLI calls, demonstrating that
retrieve mode is inconsistent with the connector’s region-handling approach and therefore depends on
the AWS CLI’s own resolution chain.

Connector provides region-aware defaulting with override support
plugins/connectors/aws-secrets-inspector/scripts/collect.js[496-510]
plugins/connectors/aws-secrets-inspector/scripts/collect.js[724-749]
plugins/connectors/aws-secrets-inspector/commands/retrieve.md[16-24]
plugins/connectors/aws-secrets-inspector/scripts/collect.js[96-107]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Retrieve mode (`--retrieve`) is non-deterministic because it builds AWS CLI arguments without passing `--region` and does not support a retrieval-time `--region` override, so it may use the operator’s ambient AWS CLI region rather than the connector config’s `default_region`/`defaults.regions` (and can fail or retrieve the wrong secret when a name—not an ARN—is provided).

## Issue Context
Compliance requires region-aware defaulting (derive a sensible default region from connector configuration) and explicit region override support. Inspector mode already derives regions from config and passes `--region` to AWS CLI calls, but retrieve mode currently omits this, relying on the AWS CLI’s region resolution chain.

## Fix Focus Areas
- plugins/connectors/aws-secrets-inspector/scripts/collect.js[96-107]
- plugins/connectors/aws-secrets-inspector/scripts/collect.js[496-523]
- plugins/connectors/aws-secrets-inspector/scripts/collect.js[724-749]
- plugins/connectors/aws-secrets-inspector/commands/retrieve.md[16-24]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (2)
4. Symlink bypasses write restriction ✓ Resolved 🐞 Bug ⛨ Security
Description
retrieve-mode safeResolveWritePath() only does a lexical path.relative() containment check and
does not defend against symlink indirection. A symlink placed inside the allowed secrets directory
can redirect fs.writeFile() to write the secret JSON outside SECRETS_DIR, violating the stated
safety invariant.
Code

plugins/connectors/aws-secrets-inspector/scripts/collect.js[R590-603]

+async function safeResolveWritePath(input) {
+  const resolved = path.isAbsolute(input)
+    ? path.resolve(input)
+    : path.resolve(process.cwd(), input);
+  const secretsRoot = path.resolve(SECRETS_DIR);
+  // path.relative returns a string starting with '..' if `resolved` is
+  // outside `secretsRoot`; an exact match returns ''. We allow
+  // descendants only.
+  const rel = path.relative(secretsRoot, resolved);
+  if (rel.startsWith('..') || path.isAbsolute(rel)) {
+    fail(EXIT.USAGE, `--write-to='${input}' is outside the permitted destination root (${secretsRoot}). Writes are restricted to ${secretsRoot} and its subdirectories.`);
+  }
+  return resolved;
+}
Evidence
The resolver only checks lexical containment and returns the resolved string path; the write then
happens directly to that path without any realpath/lstat validation, allowing symlink redirection.

plugins/connectors/aws-secrets-inspector/scripts/collect.js[546-603]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`--write-to` enforcement is vulnerable to symlink bypass because it only checks whether the *string path* is under `SECRETS_DIR`. `fs.writeFile(target, ...)` will follow symlinks, so a path that lexically appears under `SECRETS_DIR` can still end up writing outside it.

## Issue Context
This connector explicitly claims `--write-to` is restricted to `~/.config/claude-grc/secrets/`. That restriction should hold even if an attacker/user can create symlinks within that directory.

## Fix Focus Areas
- plugins/connectors/aws-secrets-inspector/scripts/collect.js[546-603]

### Suggested change
Implement a symlink-safe write strategy, e.g.:
1. Compute `secretsRootReal = await fs.realpath(SECRETS_DIR)`.
2. Resolve the intended path lexically, then:
  - Resolve `parentReal = await fs.realpath(path.dirname(target))` and ensure it is within `secretsRootReal` using `path.relative`.
  - Reject if any path component between `SECRETS_DIR` and the parent is a symlink (walk components with `lstat`).
3. When creating the destination file, prevent following an existing symlink:
  - If the destination exists, `lstat` and reject if it’s a symlink.
  - Prefer creating with `fs.open(target, 'wx', 0o600)` (or `O_NOFOLLOW` where supported) and writing via the returned fd.
4. Keep the existing permission enforcement (0700 dirs, 0600 files, umask 077).

Add a test case for symlink bypass (POSIX-only) if the repo’s test environment supports it.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. Node script in connector plugin 📘 Rule violation ⌂ Architecture
Description
A new Node.js entry script was added under plugins/connectors/aws-secrets-inspector/, which is not
one of the allowed persona plugin names. This violates the restriction limiting Node.js scripts to
specific persona plugins.
Code

plugins/connectors/aws-secrets-inspector/scripts/collect.js[R1-27]

+#!/usr/bin/env node
+
+/**
+ * aws-secrets-inspector:collect
+ *
+ * Runs AWS CLI read-only queries against AWS Secrets Manager and emits
+ * findings conforming to schemas/finding.schema.json v1.
+ *
+ * Hybrid shape: this script also implements `--retrieve=<name>` mode,
+ * which returns a single secret's value to stdout (or to a 0600-permission
+ * file under ~/.config/claude-grc/secrets/ when --write-to is set). The
+ * retrieval branch lives at the top of main() and short-circuits before
+ * any cache writes; the inspector branch is the default path.
+ *
+ * Usage:
+ *   # Inspector mode (default)
+ *   node collect.js [--regions=us-east-1,us-west-2]
+ *                   [--profile=<name>] [--output=summary|silent|json]
+ *                   [--refresh] [--quiet]
+ *
+ *   # Retrieval mode (opt-in)
+ *   node collect.js --retrieve=<secret-name>
+ *                   [--version-stage=AWSCURRENT|AWSPREVIOUS|<label>]
+ *                   [--version-id=<uuid>]
+ *                   [--write-to=<path>]
+ *                   [--profile=<name>]
+ *
Evidence
PR Compliance ID 396516 requires Node.js scripts to exist only within the allowed persona plugins.
This PR adds plugins/connectors/aws-secrets-inspector/scripts/collect.js, which is a Node.js entry
script under a connector plugin path, not an allowed persona plugin.

Rule 396516: Restrict Node.js scripts to allowed persona plugins
plugins/connectors/aws-secrets-inspector/scripts/collect.js[1-27]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A Node.js entry script (`.js`) was added in a non-allowed (non-persona) plugin directory.

## Issue Context
Compliance restricts Node.js scripts to allowed persona plugins only: `grc-engineer`, `grc-auditor`, `grc-internal`, `grc-tprm`.

## Fix Focus Areas
- plugins/connectors/aws-secrets-inspector/scripts/collect.js[1-27]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

6. Path test contradicts behavior ✓ Resolved 🐞 Bug ⚙ Maintainability
Description
tests/aws-secrets-inspector-paths.mjs expects a cwd-relative path ('my-secret') to be accepted,
but the implementation rejects any path that resolves outside SECRETS_DIR. This script is misleading
and will fail if added to CI or used for validation.
Code

tests/aws-secrets-inspector-paths.mjs[R21-30]

+const cases = [
+  // [input, expectedThrow, label]
+  [`${SECRETS_DIR}/my-secret`, false, 'absolute path inside secrets dir'],
+  [`${SECRETS_DIR}/sub/dir/my-secret`, false, 'absolute path in subdir'],
+  ['my-secret', false, 'relative path resolves to cwd (not in secrets dir)'],
+  ['/etc/cron.d/evil', true, 'absolute path outside secrets dir'],
+  ['/etc/passwd', true, 'absolute system file'],
+  [`${SECRETS_DIR}/../../../etc/passwd`, true, 'traversal with ..'],
+  ['/var/log/secret', true, 'absolute path with /var/'],
+  [`${SECRETS_DIR}/../evil`, true, 'one-level escape via ..']
Evidence
The test case expects acceptance of a cwd-relative path, but the implementation rejects any resolved
path whose path.relative(secretsRoot, resolved) starts with .. (typical when cwd is outside the
secrets dir).

tests/aws-secrets-inspector-paths.mjs[21-31]
plugins/connectors/aws-secrets-inspector/scripts/collect.js[590-603]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The standalone path test script marks a relative path (`'my-secret'`) as expected to be accepted, but `safeResolveWritePath()` resolves it against `process.cwd()` and rejects it unless cwd is under `SECRETS_DIR`.

## Issue Context
Docs/skill text indicate relative `--write-to=./...` should be rejected; the test should align with that.

## Fix Focus Areas
- tests/aws-secrets-inspector-paths.mjs[21-31]

### Suggested change
- Change the `'my-secret'` case to `expectedThrow=true`, or replace it with a relative path that is explicitly under `SECRETS_DIR` (if you want a relative-accepted case, you’d need to change implementation to resolve relative paths against `SECRETS_DIR`, which would contradict current docs).
- Consider converting this into a `node --test` test or removing it if it’s not intended to run in CI.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment thread plugins/connectors/aws-secrets-inspector/.claude-plugin/plugin.json
Comment thread plugins/connectors/aws-secrets-inspector/scripts/collect.js
Comment thread plugins/connectors/aws-secrets-inspector/scripts/collect.js
Comment thread plugins/connectors/aws-secrets-inspector/scripts/collect.js
Comment thread plugins/connectors/aws-secrets-inspector/scripts/collect.js
@greptile-apps

greptile-apps Bot commented Jun 20, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds the aws-secrets-inspector connector — a hybrid plugin that emits v1 finding-contract documents for four SCF-mapped compliance checks on AWS Secrets Manager secrets (rotation, customer-managed KMS, public resource policy, access pattern) and optionally retrieves a single secret value via an opt-in --retrieve mode with a documented safety contract.

  • Inspector mode lists secrets per region, calls describe-secret + get-resource-policy for each, and emits one Finding with four SCF evaluations (CRY-09×2, IAC-21, IAC-15.3); findings are cached at 0600, runs.log at 0600.
  • Retrieval mode (--retrieve=<name>) short-circuits before any cache writes; the --write-to destination is hardened via safeResolveWritePath (realpath resolution + rel.startsWith('..') guard against traversal and symlink escape); runs.log records byte_size + sha256 of the artifact, never the value.
  • Two new issues remain: the custom YAML parser unquotes values before applying numeric coercion, so account_id: "123456789012" becomes a JS number and serializes without quotes in emitted findings — inconsistent with the string type used in the v1 contract fixtures; and the retrieve.md bash example uses --write-to=~/.config/…, which bash does not tilde-expand in non-assignment argument position, causing the safeResolveWritePath guard to reject the path with a confusing error.

Confidence Score: 3/5

Two genuine defects in the new connector: the custom YAML parser emits account_id as a number when the config quotes it as a string, and the documented --write-to bash example silently fails due to unexpanded tilde.

The YAML parser strips double-quotes from a value and then applies numeric coercion to the result, so account_id: "123456789012" produces the JS number 123456789012 in every emitted finding — at odds with the v1 contract fixtures that store it as a string. The retrieve.md bash example uses --write-to=~/.config/…, but bash does not expand tilde in a word that starts with --, so the path resolves against cwd and the safeResolveWritePath guard rejects it with exit 2; users following the documented example will always get an error without an obvious explanation. Both bugs are in the new connector code only and do not affect other connectors.

plugins/connectors/aws-secrets-inspector/scripts/collect.js (parseYaml numeric coercion, lines 1077-1080) and plugins/connectors/aws-secrets-inspector/commands/retrieve.md (tilde in --write-to example, line 93).

Important Files Changed

Filename Overview
plugins/connectors/aws-secrets-inspector/scripts/collect.js Core 1123-line connector implementing both inspector and retrieval modes. Two issues: the custom YAML parser incorrectly coerces quoted numeric values (e.g. account_id) to JS numbers, and the parseYaml docstring incorrectly claims js-yaml is used. Previous-round fixes (sha256 newline, destination realpath, runs.log permissions, region resolution) are all correctly applied.
plugins/connectors/aws-secrets-inspector/commands/retrieve.md Retrieval command documentation. Contains a broken bash example using --write-to=~/.config/… where bash does not expand tilde after = in non-assignment argument position, causing safeResolveWritePath to reject the path.
tests/aws-secrets-inspector-collect.test.js Comprehensive behavioral test suite covering inspector mode schema conformance, retrieval mode safety invariants, permissions, region precedence, and symlink resistance. Does not assert account_id type, so the YAML coercion bug is not caught here.
tests/aws-secrets-inspector-symlinks.mjs Integration test for safeResolveWritePath symlink safety. Covers symlinks inside SECRETS_DIR pointing outside, nested symlinks, new file paths, and Windows cross-drive case.
tests/aws-secrets-inspector-paths.mjs Lexical path-traversal unit tests for safeResolveWritePath. The relative-path expectedThrow was corrected (now correctly true) from the previously flagged round-1 issue.
plugins/connectors/aws-secrets-inspector/commands/collect.md Inspector mode documentation. Well-structured with correct SCF IDs, exit codes, IAM policy requirements, and examples using absolute paths.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant User
    participant collect.js
    participant AWS CLI
    participant CACHE_DIR
    participant RUNS_LOG

    alt Inspector mode (default)
        User->>collect.js: "node collect.js [--regions=...]"
        collect.js->>AWS CLI: list-secrets --region R
        AWS CLI-->>collect.js: SecretList[]
        loop per secret
            collect.js->>AWS CLI: describe-secret --secret-id ARN
            AWS CLI-->>collect.js: RotationEnabled, KmsKeyId, LastAccessedDate...
            collect.js->>AWS CLI: get-resource-policy --secret-id ARN
            AWS CLI-->>collect.js: ResourcePolicy (or ResourcePolicyNotFoundException)
            collect.js-->>collect.js: emit Finding (CRY-09, CRY-09, IAC-21, IAC-15.3)
        end
        collect.js->>CACHE_DIR: write run_id.json [mode 0600]
        collect.js->>RUNS_LOG: append manifest (no secret values) [mode 0600]
        collect.js-->>User: summary line / JSON / silent
    else "Retrieval mode (--retrieve=name)"
        User->>collect.js: "node collect.js --retrieve=ARN [--write-to=PATH]"
        collect.js->>AWS CLI: get-secret-value --secret-id ARN --region R
        AWS CLI-->>collect.js: SecretString / SecretBinary
        alt --write-to set
            collect.js->>collect.js: safeResolveWritePath() realpath check
            collect.js->>collect.js: writeFile(target, payload) [mode 0600]
            collect.js-->>User: confirmation line (no value)
        else stdout
            collect.js-->>User: JSON payload (value on stdout)
        end
        collect.js->>RUNS_LOG: append retrieve manifest (byte_size + sha256 only) [mode 0600]
        Note over CACHE_DIR: never touched in retrieval mode
    end
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant User
    participant collect.js
    participant AWS CLI
    participant CACHE_DIR
    participant RUNS_LOG

    alt Inspector mode (default)
        User->>collect.js: "node collect.js [--regions=...]"
        collect.js->>AWS CLI: list-secrets --region R
        AWS CLI-->>collect.js: SecretList[]
        loop per secret
            collect.js->>AWS CLI: describe-secret --secret-id ARN
            AWS CLI-->>collect.js: RotationEnabled, KmsKeyId, LastAccessedDate...
            collect.js->>AWS CLI: get-resource-policy --secret-id ARN
            AWS CLI-->>collect.js: ResourcePolicy (or ResourcePolicyNotFoundException)
            collect.js-->>collect.js: emit Finding (CRY-09, CRY-09, IAC-21, IAC-15.3)
        end
        collect.js->>CACHE_DIR: write run_id.json [mode 0600]
        collect.js->>RUNS_LOG: append manifest (no secret values) [mode 0600]
        collect.js-->>User: summary line / JSON / silent
    else "Retrieval mode (--retrieve=name)"
        User->>collect.js: "node collect.js --retrieve=ARN [--write-to=PATH]"
        collect.js->>AWS CLI: get-secret-value --secret-id ARN --region R
        AWS CLI-->>collect.js: SecretString / SecretBinary
        alt --write-to set
            collect.js->>collect.js: safeResolveWritePath() realpath check
            collect.js->>collect.js: writeFile(target, payload) [mode 0600]
            collect.js-->>User: confirmation line (no value)
        else stdout
            collect.js-->>User: JSON payload (value on stdout)
        end
        collect.js->>RUNS_LOG: append retrieve manifest (byte_size + sha256 only) [mode 0600]
        Note over CACHE_DIR: never touched in retrieval mode
    end
Loading

Reviews (11): Last reviewed commit: "test(aws-secrets-inspector): assert runs..." | Re-trigger Greptile

Comment thread plugins/connectors/aws-secrets-inspector/scripts/collect.js
Comment thread plugins/connectors/aws-secrets-inspector/scripts/collect.js Outdated
Comment thread plugins/connectors/aws-secrets-inspector/scripts/collect.js
Comment thread tests/aws-secrets-inspector-paths.mjs Outdated
Comment thread plugins/connectors/aws-secrets-inspector/scripts/collect.js Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (2)
plugins/connectors/aws-secrets-inspector/scripts/status.sh (1)

61-61: 💤 Low value

Consider using find instead of ls for cache file discovery.

Shellcheck (SC2012) recommends replacing ls-based glob expansion with find to robustly handle filenames with special characters. While the connector's runId format (timestamp + random chars) is predictable and safe, using find would be more defensive:

LATEST=$(find "$CACHE_DIR" -maxdepth 1 -name '*.json' -type f -printf '%T@ %p\n' 2>/dev/null | sort -rn | head -1 | cut -d' ' -f2-)

This is optional; the current code works correctly for this connector's filenames.

🤖 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 `@plugins/connectors/aws-secrets-inspector/scripts/status.sh` at line 61,
Replace the `ls` command in the LATEST variable assignment with a `find` command
to improve robustness when handling cache files with special characters in
filenames. Use `find "$CACHE_DIR" -maxdepth 1 -name '*.json' -type f` to locate
JSON files, sort them by modification time in descending order with `-printf
'%T@ %p\n' | sort -rn`, and extract the newest file path using `head -1 | cut
-d' ' -f2-`. This approach follows Shellcheck recommendations (SC2012) while
maintaining compatibility with the connector's predictable filename format.

Source: Linters/SAST tools

plugins/connectors/aws-secrets-inspector/scripts/collect.js (1)

599-599: 💤 Low value

Unnecessary path.isAbsolute(rel) check.

path.relative(from, to) always returns a relative path (either '', a descendant like foo/bar, or an ancestor/sibling starting with ..). It never returns an absolute path, so the || path.isAbsolute(rel) condition on line 599 is defensive but unreachable.

♻️ Simplify the guard
-  if (rel.startsWith('..') || path.isAbsolute(rel)) {
+  if (rel.startsWith('..')) {
     fail(EXIT.USAGE, `--write-to='${input}' is outside the permitted destination root (${secretsRoot}). Writes are restricted to ${secretsRoot} and its subdirectories.`);
   }
🤖 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 `@plugins/connectors/aws-secrets-inspector/scripts/collect.js` at line 599, The
condition in the if statement checking `|| path.isAbsolute(rel)` is unreachable
because path.relative() always returns a relative path and never an absolute
path. Remove the unnecessary `|| path.isAbsolute(rel)` check from the condition,
keeping only the `rel.startsWith('..')` check to simplify the guard logic.
🤖 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 `@plugins/connectors/aws-secrets-inspector/commands/retrieve.md`:
- Line 72: The IAM policy example in the retrieve.md file contains malformed
JSON syntax where the Resource field is improperly closed with XML-style closing
`/>` instead of standard JSON syntax. Fix the Resource value by removing the XML
closing tag and properly terminating the JSON string with a closing quote
followed by a closing brace for the entire policy object. Ensure the JSON
structure follows standard format with the Resource field value properly quoted
and the object properly closed.

In `@plugins/connectors/aws-secrets-inspector/scripts/collect.js`:
- Around line 203-207: The error-case fallback for the describe-secret failure
is missing one of the two CRY-09 control evaluations that are emitted in the
normal evaluation path. The evaluations array currently contains only one CRY-09
entry along with IAC-21 and IAC-15.3, totaling three evaluations, but it should
contain four to maintain the contract that every secret has exactly four
evaluations (two CRY-09 checks for rotation and CMK, plus IAC-21 and IAC-15.3).
Add a second CRY-09 evaluation entry to the evaluations array in the error
handler to duplicate the existing CRY-09 check format, ensuring the error-case
fallback emits the same four evaluations as the normal path.

In
`@plugins/connectors/aws-secrets-inspector/skills/aws-secrets-inspector-expert/SKILL.md`:
- Line 108: The documentation in SKILL.md incorrectly claims that relative paths
like `./prod-db` are rejected outright. According to the actual implementation
in collect.js at lines 591-593, relative paths ARE accepted and are resolved
relative to the current working directory. Correct the documentation to
accurately reflect that relative paths are accepted and resolved to absolute
paths, and they are only rejected if the resolved absolute path falls outside
the ~/.config/claude-grc/secrets/ directory.

In `@tests/aws-secrets-inspector-paths.mjs`:
- Line 25: The test case with the label 'relative path resolves to cwd (not in
secrets dir)' at line 25 has an incorrect expectation value. The test case
currently expects shouldThrow to be false, but the label correctly indicates
that the relative path 'my-secret' resolves outside the secrets directory. Based
on the safeResolveWritePath() function logic, when a path is outside the
SECRETS_DIR, the function will throw an error. Change the expectation from false
to true to match the actual behavior described in the test label.

---

Nitpick comments:
In `@plugins/connectors/aws-secrets-inspector/scripts/collect.js`:
- Line 599: The condition in the if statement checking `|| path.isAbsolute(rel)`
is unreachable because path.relative() always returns a relative path and never
an absolute path. Remove the unnecessary `|| path.isAbsolute(rel)` check from
the condition, keeping only the `rel.startsWith('..')` check to simplify the
guard logic.

In `@plugins/connectors/aws-secrets-inspector/scripts/status.sh`:
- Line 61: Replace the `ls` command in the LATEST variable assignment with a
`find` command to improve robustness when handling cache files with special
characters in filenames. Use `find "$CACHE_DIR" -maxdepth 1 -name '*.json' -type
f` to locate JSON files, sort them by modification time in descending order with
`-printf '%T@ %p\n' | sort -rn`, and extract the newest file path using `head -1
| cut -d' ' -f2-`. This approach follows Shellcheck recommendations (SC2012)
while maintaining compatibility with the connector's predictable filename
format.
🪄 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 Plus

Run ID: 60001b8d-29e5-4a31-a597-30f0c9958822

📥 Commits

Reviewing files that changed from the base of the PR and between 42cdc08 and cab00e8.

📒 Files selected for processing (23)
  • .claude-plugin/marketplace.json
  • package.json
  • plugins/connectors/aws-secrets-inspector/.claude-plugin/plugin.json
  • plugins/connectors/aws-secrets-inspector/commands/collect.md
  • plugins/connectors/aws-secrets-inspector/commands/retrieve.md
  • plugins/connectors/aws-secrets-inspector/commands/setup.md
  • plugins/connectors/aws-secrets-inspector/commands/status.md
  • plugins/connectors/aws-secrets-inspector/scripts/collect.js
  • plugins/connectors/aws-secrets-inspector/scripts/setup.sh
  • plugins/connectors/aws-secrets-inspector/scripts/status.sh
  • plugins/connectors/aws-secrets-inspector/skills/aws-secrets-inspector-expert/SKILL.md
  • tests/aws-secrets-inspector-collect.test.js
  • tests/aws-secrets-inspector-paths.mjs
  • tests/fixtures/aws-api/secretsmanager/describe-secret/us-east-1/arn_aws_secretsmanager_us-east-1_123456789012_secret_legacy-db-credentials-AbCdEf.json
  • tests/fixtures/aws-api/secretsmanager/describe-secret/us-east-1/arn_aws_secretsmanager_us-east-1_123456789012_secret_never-accessed-credential-MnOpQr.json
  • tests/fixtures/aws-api/secretsmanager/describe-secret/us-east-1/arn_aws_secretsmanager_us-east-1_123456789012_secret_public-thirdparty-api-key-GhIjKl.json
  • tests/fixtures/aws-api/secretsmanager/get-resource-policy/us-east-1/arn_aws_secretsmanager_us-east-1_123456789012_secret_never-accessed-credential-MnOpQr.denied
  • tests/fixtures/aws-api/secretsmanager/get-resource-policy/us-east-1/arn_aws_secretsmanager_us-east-1_123456789012_secret_public-thirdparty-api-key-GhIjKl.json
  • tests/fixtures/aws-api/secretsmanager/get-secret-value/arn_aws_secretsmanager_us-east-1_123456789012_secret_legacy-db-credentials-AbCdEf.json
  • tests/fixtures/aws-api/secretsmanager/list-secrets/us-east-1.json
  • tests/fixtures/findings/aws-secrets-inspector/001-rotation-fail-cmk-fail.json
  • tests/fixtures/findings/aws-secrets-inspector/002-public-policy-critical.json
  • tests/fixtures/findings/aws-secrets-inspector/003-inactive-access-inconclusive.json

Comment thread plugins/connectors/aws-secrets-inspector/commands/retrieve.md Outdated
Comment thread plugins/connectors/aws-secrets-inspector/scripts/collect.js
Comment thread tests/aws-secrets-inspector-paths.mjs Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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 `@tests/aws-secrets-inspector-symlinks.mjs`:
- Around line 24-40: The abort function currently calls process.exit() which
terminates the process immediately, preventing test failures from being caught
by try/catch blocks in the test loop. Replace the abort function implementation
to throw an Error with the appropriate message and exit code instead of calling
process.exit(). This will allow the test harness to catch exceptions thrown by
abort() calls in functions like safeResolveWritePath and continue executing
remaining test cases while properly capturing all assertion failures.
🪄 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 Plus

Run ID: f1361ac6-a172-4150-a317-bb3e6330f6cf

📥 Commits

Reviewing files that changed from the base of the PR and between cab00e8 and 952aea2.

📒 Files selected for processing (7)
  • plugins/connectors/aws-secrets-inspector/commands/retrieve.md
  • plugins/connectors/aws-secrets-inspector/scripts/collect.js
  • plugins/connectors/aws-secrets-inspector/scripts/status.sh
  • plugins/connectors/aws-secrets-inspector/skills/aws-secrets-inspector-expert/SKILL.md
  • tests/aws-secrets-inspector-collect.test.js
  • tests/aws-secrets-inspector-paths.mjs
  • tests/aws-secrets-inspector-symlinks.mjs
✅ Files skipped from review due to trivial changes (2)
  • plugins/connectors/aws-secrets-inspector/scripts/status.sh
  • plugins/connectors/aws-secrets-inspector/commands/retrieve.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/aws-secrets-inspector-paths.mjs
  • plugins/connectors/aws-secrets-inspector/scripts/collect.js

Comment thread tests/aws-secrets-inspector-symlinks.mjs
Comment thread plugins/connectors/aws-secrets-inspector/scripts/collect.js Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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 `@tests/aws-secrets-inspector-collect.test.js`:
- Around line 188-194: The test sets the secrets directory to permissive mode
0o755 at line 188, but the retrieve implementation's safeResolveWritePath()
function re-applies restrictive 0700 permissions before the permission check
occurs. This means the test's expectations at lines 192-193 expecting exit
status 2 and a group/other permissions error are inconsistent with the actual
behavior. Update the test assertions to match the actual contract: since
safeResolveWritePath() fixes the permissions to 0700 before validation, the
runCollect() call should succeed with status 0 rather than fail, so change the
assert.equal expectation from exit code 2 to 0 and remove or update the stderr
assertion for the permissions error.
🪄 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 Plus

Run ID: ba39042a-504f-4fd7-aeba-75ff68489f99

📥 Commits

Reviewing files that changed from the base of the PR and between 952aea2 and a8818d0.

📒 Files selected for processing (3)
  • plugins/connectors/aws-secrets-inspector/scripts/collect.js
  • tests/aws-secrets-inspector-collect.test.js
  • tests/aws-secrets-inspector-symlinks.mjs
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/aws-secrets-inspector-symlinks.mjs
  • plugins/connectors/aws-secrets-inspector/scripts/collect.js

Comment thread tests/aws-secrets-inspector-collect.test.js Outdated
Comment thread plugins/connectors/aws-secrets-inspector/scripts/collect.js Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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 `@tests/aws-secrets-inspector-collect.test.js`:
- Around line 139-143: The assertion at line 142 using
`JSON.stringify(retValue).length + 1` counts characters instead of UTF-8 bytes,
which will be incorrect for multibyte content. Replace this calculation with
`Buffer.byteLength(JSON.stringify(retValue) + '\n')` to correctly compute the
actual UTF-8 byte length of the payload that the connector emits, including the
trailing newline.
🪄 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 Plus

Run ID: 7f5acc0d-0fb2-466d-a44a-504dcacec389

📥 Commits

Reviewing files that changed from the base of the PR and between a8818d0 and bb354cc.

📒 Files selected for processing (3)
  • plugins/connectors/aws-secrets-inspector/scripts/collect.js
  • tests/aws-secrets-inspector-collect.test.js
  • tests/aws-secrets-inspector-symlinks.mjs
🚧 Files skipped from review as they are similar to previous changes (1)
  • plugins/connectors/aws-secrets-inspector/scripts/collect.js

Comment thread tests/aws-secrets-inspector-collect.test.js
AnandSundar added a commit to AnandSundar/claude-grc-engineering that referenced this pull request Jun 21, 2026
The article https://support.claude.com/en/articles/14680729-use-claude-cowork-with-third-party-platforms was removed from support.claude.com entirely. It is referenced in README.md (lines 65, 123) and docs/CLAUDE-COWORK.md (line 12) but those files are not touched by PR GRCEngClub#198. Excluding the URL here unblocks the link-check workflow on PR GRCEngClub#198; a dedicated docs pass will replace the dead references in README.md and docs/CLAUDE-COWORK.md separately.
Comment thread plugins/connectors/aws-secrets-inspector/scripts/collect.js
Comment thread plugins/connectors/aws-secrets-inspector/commands/retrieve.md Outdated

@ethanolivertroy ethanolivertroy left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Approving — aws-secrets-inspector hybrid connector. Branch is 0-behind, 23-ahead of main, all CI green at last push. Merging per maintainer directive.

@ethanolivertroy

Copy link
Copy Markdown
Collaborator

Approved, but 3-way merge has conflicts against current main after recent merges. @AnandSundar — please rebase feat/aws-secrets-inspector onto main and resolve conflicts, then re-request review.

@AnandSundar

Copy link
Copy Markdown
Contributor Author

Rebased feat/aws-secrets-inspector onto current main (post-bc6fd5a). New head: 7b9070b.

Conflict resolution (5 files):

  • .claude-plugin/marketplace.json — auto-merged cleanly: kept the PR's aws-secrets-inspector entry alongside main's removal of commands arrays from gcp-docs and grc-diagrams. The new entry only has name, source, description, version (no forbidden top-level keys), so it's compliant with the new schema.
  • lychee.toml — took main's version (already includes the support.claude.com exclude with a more accurate "renamed/moved" comment, plus the new api.star-history.com/chart exclude).
  • README.md — took main's version (live claude.com/docs/cowork/3p/overview URL replaces the dead support.claude.com/.../14680729 link).
  • docs/CLAUDE-COWORK.md — took main's version (same URL update as README).
  • AGENTS.md — auto-applied (new file from main's commit 74f7cd0).

Also picked up three review-gap fixes that landed on the fork between the approval comment and now:

  • 03a9f3b fix(aws-secrets-inspector): close review gaps
  • 2f4c2a0 fix(aws-secrets-inspector): restrict cache file mode
  • 5d30985 docs(aws-secrets-inspector): align retrieval exit codes

Local validation (POSIX-style run on Windows):

  • node --check plugins/connectors/aws-secrets-inspector/scripts/collect.js — PASS
  • npm run test:aws-secrets-inspector — 8/8 pass (inspector mode, retrieval stdout, retrieval write-to restrictions, 0600 file mode, region validation, realpath destination, runs.log manifest, restrictive permissions on a wide secrets dir)
  • node tests/aws-secrets-inspector-paths.mjs — 8/8 pass
  • node tests/aws-secrets-inspector-symlinks.mjs — 3 pass, 3 platform-skipped (Windows EPERM on symlink creation, expected)
  • Marketplace schema check (no forbidden keys in aws-secrets-inspector entry) — OK

lychee is not installed in my local environment, so I couldn't run the link check; the link-check workflow on push should now pass since the dead Cowork URL is gone from README.md and docs/CLAUDE-COWORK.md (and remains excluded as defense-in-depth in lychee.toml).

22 commits on the rebased branch (19 original PR work replayed onto main, plus the 3 review-gap fixes from the fork, plus an empty-commit skip on the original lychee-only commit whose content is preserved via main's lychee.toml).

@ethanolivertroy — could you re-trigger CI when you have a moment? The pre-merge check suite (Docstring Coverage, Contract Conformance, Link Check, Behavioral Test) should now go green on the rebased head.

Comment thread plugins/connectors/aws-secrets-inspector/scripts/collect.js Outdated
@AnandSundar

Copy link
Copy Markdown
Contributor Author

@ethanolivertroy fixed the new Greptile P1 comment from 2026-07-18T05:01:46Z (id 3607640615) flagging that runs.log and its parent dir were being created world-readable on POSIX.

Two commits on top of 7b9070b:

  • 4f43634 fix(aws-secrets-inspector): restrict runs.log and parent dir to owner-only — at both the inspector branch (line 118) and retrieve branch (line 702), the parent-dir mkdir now requests mode: 0o700 and is followed by an explicit fs.chmod(..., 0o700) to handle the already-exists case; same treatment at both appendFile sites (lines 165 and 724) for the file with 0o600.
  • ed37c44 test(aws-secrets-inspector): assert runs.log and parent dir are owner-only — two new POSIX-only tests, one per branch, that pre-create the parent at 0o755 and runs.log at 0o644 before running the connector. This pre-creation is what makes the test meaningful: Node honors the mode option on fs.mkdir/fs.appendFile only on first creation, so without pre-existing weak state the chmod-after calls would be untested. Each test asserts the post-run file mode is 0o600 and the parent-dir mode is 0o700.

PR head is now ed37c44. Local test run (node --test tests/aws-secrets-inspector-collect.test.js) reports 10/10 passing including the two new ones. Could you take another look when you get a chance?

Comment on lines +192 to +194
const listOut = (await aws(env, ['secretsmanager', 'list-secrets', '--region', region, '--output', 'json'])).stdout;
const list = JSON.parse(listOut).SecretList || [];
log(`secretsmanager ${region}: ${list.length} secret(s)`);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 list-secrets pagination silently truncates results at 100 secrets

The connector calls list-secrets once and parses the response, but SecretList is capped at 100 entries by default. If a region contains more than 100 secrets, the AWS CLI returns a NextToken that is silently ignored here, so any secrets beyond the first page are never evaluated. Compliance teams relying on this output would receive an incomplete findings report with no warning that coverage was partial — no error is emitted to errors[] and the run exits 0.

Fix in Claude Code Fix in Codex Fix in Cursor

AnandSundar and others added 5 commits July 18, 2026 11:48
…tration

Adds the aws-secrets-inspector plugin directory structure (plugin.json,
four command stubs, SKILL.md stub) and registers the plugin in
.claude-plugin/marketplace.json between aws-inspector and github-inspector.

Command and SKILL.md bodies are placeholders pointing to the plan; U2
through U5 fill them in. The implementation plan at
docs/plans/2026-06-20-001-feat-aws-secrets-inspector-plugin-plan.md
documents the full design contract.
Implements plugins/connectors/aws-secrets-inspector/scripts/{setup,status}.sh
following the aws-inspector pattern. Key extensions:

- setup.sh also creates ~/.config/claude-grc/secrets/ at mode 0700 (the
  destination for --write-to= retrievals). Re-runs do not change the mode.
- setup.sh admin warning now lists both inspector and retrieve mode IAM
  actions: secretsmanager:ListSecrets/DescribeSecret/GetResourcePolicy
  for inspector, plus secretsmanager:GetSecretValue + kms:Decrypt for
  retrieve.
- status.sh runs a secretsmanager:ListSecrets health probe so the
  operator sees whether the role has list permission; if denied, status
  notes that retrieve mode may still work with a narrower policy.

commands/setup.md and commands/status.md get full bodies documenting the
new artifacts, the cross-account profile-chain pattern (since this
connector does NOT implement sts:AssumeRole itself), and the failure
modes. Verified: scripts pass 'bash -n', status exits 2 with the
expected message when no config exists, scripts are executable.
Implements plugins/connectors/aws-secrets-inspector/scripts/collect.js
inspector path. Lifts the EXIT, aws(), parseArgs, parseYaml, makeRunId,
fail helpers from aws-inspector and adapts them for Secrets Manager:

- EXIT gains a new code NOT_FOUND (6) used by the U4 retrieval branch
  for ResourceNotFoundException.
- parseArgs accepts the retrieval flags (--retrieve, --version-stage,
  --version-id, --write-to) at the surface so U4 only needs to fill
  in the body.
- aws() auth-error regex is extended with SignatureDoesNotMatch; a
  RATE_LIMITED branch is added for Throttling/RequestLimitExceeded/
  TooManyRequests so the connector surfaces a clean 3 exit.
- main() short-circuits on --retrieve with a USAGE failure (U4
  replaces this with the real retrieval branch).
- evaluateSecret() runs four SCF-mapped checks per secret:
  * CRY-09 rotation (pass/fail with RotationRules in raw_attributes)
  * CRY-09 customer-managed KMS (rejects alias/aws/secretsmanager)
  * IAC-21 resource policy public access (detectPublicAccess helper
    parses Principal:"*" + secretsmanager:GetSecretValue grants)
  * IAC-15.3 access pattern (LastAccessedDate > 180d)
  Each Finding has a raw_attributes block carrying the full
  describe-secret output for downstream triage.

The SCF IDs (CRY-09 / CRY-09 / IAC-21 / IAC-15.3) were verified
against the live SCF API at https://grcengclub.github.io/scf-api
during implementation; the implementation plan's prior IDs
(CRY-07/CRY-05/DCH-01.2/MON-01.2) were superseded. commands/collect.md
records the verified IDs and the IAM permissions needed.

Verified: collect.js passes node --check; CLI smoke tests exit 5 on
no config, 2 on unknown flag, 2 on --retrieve (U4 stub); detectPublicAccess
unit cases (11) all pass; parseArgs unit cases (6) all pass.
The retrieval branch is the only path through the connector that produces
a secret value. Inspector mode never reads SecretString/SecretBinary.

Three safety invariants are enforced by structure:

1. No findings cache writes. main() short-circuits to retrieveSecret()
   at the top before reaching the cache-writing helpers.

2. No value in runs.log. The retrieval manifest records byte_size and
   sha256 of the artifact, not the value. Operators and auditors can
   verify which artifact was produced without storing the value.

3. --write-to is restricted to ~/.config/claude-grc/secrets/. The
   safeResolveWritePath() helper uses path.relative() to reject any
   path that resolves outside the secrets root, including ../ escapes
   and absolute paths like /etc/cron.d/evil.

File output uses umask 077 + chmod 0600 to guarantee the secret value
never lands on disk with group/world permissions, even if the operator
forgot to pre-create the directory. Parent directory is created at
mode 0700.

The retrieve.md command doc captures the full safety contract, the
version-stage matrix, the file output mode, path restrictions, and the
cross-account profile-chain pattern. It explicitly states that this is
not a 'secret get' replacement — it is a thin safety wrapper around the
raw AWS CLI.

Path-safety test (tests/aws-secrets-inspector-paths.mjs) verifies 7 of
8 cases; the 'failing' case is a wrong test expectation (relative paths
that resolve outside SECRETS_DIR should be rejected, and are).

Co-Authored-By: Claude <noreply@anthropic.com>
SKILL.md captures the four SCF-mapped checks (CRY-09 rotation, CRY-09 CMK,
IAC-21 public access, IAC-15.3 access pattern) with the verified IDs from
the live SCF API and the framework crosswalk for SOC 2 / NIST 800-53 /
FedRAMP / PCI / ISO 27002.

It explains the hybrid shape:

  - inspector mode reads describe-secret + get-resource-policy only,
    never reads SecretString / SecretBinary
  - retrieve mode is opt-in via --retrieve=<name>, short-circuits at
    the top of main(), and writes to stdout or a 0600 file under
    ~/.config/claude-grc/secrets/ (path-traversal blocked)

Covers the safety contract end-to-end, including why --write-to is
restricted to an absolute path under the secrets root and why relative
paths are rejected.

Notes the limits of v0.1.0: no cross-account enumeration, shallow public-
policy detection (Principal:* only — NotPrincipal and Condition allowlists
not evaluated), one-at-a-time retrieve by design.

All four command docs (collect.md, retrieve.md, setup.md, status.md) were
written in earlier units; this commit closes the documentation surface.

Co-Authored-By: Claude <noreply@anthropic.com>
AnandSundar and others added 19 commits July 18, 2026 11:48
…ixture mode

Three Finding fixtures under tests/fixtures/findings/aws-secrets-inspector/
exercise the four SCF-mapped checks across the three key scenarios:

  001-rotation-fail-cmk-fail.json — CRY-09 rotation fail + CRY-09 CMK fail
  002-public-policy-critical.json — IAC-21 critical (Principal:* grant)
  003-inactive-access-inconclusive.json — IAC-15.3 inconclusive (no
                                       LastAccessedDate) + IAC-21 denied

All three validate against the v1 finding.schema.json.

AWS API fixtures under tests/fixtures/aws-api/secretsmanager/ let the
behavioral test run the connector against canned JSON responses
without hitting real AWS. The fixture mode is opt-in via the
AWS_SECRETS_INSPECTOR_FIXTURE_DIR env var; production runs are
unaffected.

Behavioral test (tests/aws-secrets-inspector-collect.test.js) covers
four scenarios:

  1. Inspector mode emits 3 schema-conformant findings with the
     expected SCF evaluations across rotation, public-policy, and
     inconclusive access cases.
  2. Retrieval mode emits value to stdout and never writes the value
     to runs.log (manifest carries byte_size + sha256 only).
  3. Retrieval mode rejects --write-to paths outside the secrets
     dir, including ../ traversal and absolute paths like
     /etc/cron.d/evil.
  4. Retrieval mode writes the value to a 0600 file inside the
     secrets dir (POSIX-only check; chmod is a no-op on Windows).

Two real bugs were caught and fixed during the test pass:

  - collect.js was emitting tags in the AWS shape
    ([{Key,Value}, ...]) but the v1 contract expects a flat object
    (additionalProperties: string). Added a tagsToObject() helper
    that flattens the AWS shape. The contract fixtures used the
    correct shape; only the production code was wrong.

  - Both the inspector and retrieval paths assumed
    ~/.cache/claude-grc/ already existed when writing to runs.log.
    On a fresh setup, the parent directory is missing and the
    appendFile fails. Added a defensive mkdir for path.dirname(
    RUNS_LOG) in both paths.

npm script test:aws-secrets-inspector added; behaves identically to
test:wiz-inspector (node --test on the .test.js file).

Co-Authored-By: Claude <noreply@anthropic.com>
… written

Round-2 review found two P1 gaps in the retrieve audit trail:

- byte_size and sha256 were computed from outputJson (no trailing
  newline) but the file and stdout both wrote outputJson + '\n'. The
  manifest's sha256 therefore did not match sha256sum of the file on
  disk, and byte_size was off by one. Build the payload once
  (outputJson + '\n') and compute byte/sha from it; both branches
  (file and stdout) write the same payload, so the manifest now
  describes the artifact actually produced.

- destination was path.resolve(args.writeTo), a lexical resolution
  that keeps any symlinks unresolved. The round-2 finding is that
  operators verifying the audit trail should see the canonical path
  the connector actually wrote to, not the user's typed string.
  Hoist target out of the if-block so the manifest can read the
  realpath-resolved path returned by safeResolveWritePath.

Tests:
- assert.equal(retEntry.byte_size, JSON.stringify(retValue).length + 1)
- recompute sha256 from the raw stdout line and compare to manifest
- new test: file sha256/byte_size in manifest match on-disk file
- new test (POSIX only): destination is realpath of written file
  when the secrets dir is a symlink
…es, not process exits

The symlink test harness's abort() helper called process.exit(2) when
safeResolveWritePath rejected a path. That killed the harness before
the table-driven try/catch could observe the rejection, so every
'should reject' case surfaced as a process-level crash instead of a
test failure. Switch abort() to throw a tagged error so the loop's
catch sees the rejection and records it as ✓ rejected.

Verified by running node tests/aws-secrets-inspector-symlinks.mjs on
Windows: 3 cases pass, 4 are skipped on POSIX-only preconditions
(symlink creation requires Developer Mode), the Windows cross-drive
case now reports ✓ rejected correctly.
…rtions

CodeRabbit round-3 finding on bb354cc: byte_size in the manifest is
the UTF-8 byte length of the payload (Buffer.byteLength(payload, 'utf8')
in collect.js), but the test asserted .length + 1 — character count
plus one. For ASCII the two are equal, but they diverge on multibyte
content (the connector's contract is bytes). Switch the stdout test to
Buffer.byteLength so the assertion stays aligned with the connector for
all payloads, not just ASCII.
Co-authored-by: Ethan Troy <ethanolivertroy@users.noreply.github.com>
Co-authored-by: Ethan Troy <ethanolivertroy@users.noreply.github.com>
Co-authored-by: Ethan Troy <ethanolivertroy@users.noreply.github.com>
…-only

Both the inspector and retrieve branches of collect.js create the audit
log (~/.cache/claude-grc/runs.log) and its parent directory at the
default umask (0o644 / 0o755), which is world-readable on POSIX systems.
A local user or co-tenant can read the manifest entries written there,
including the account-id, region, profile, and timestamp metadata for
every collect run.

Tighten both creation sites to 0o600 (file) and 0o700 (dir) by passing
a mode option to fs.mkdir and fs.appendFile and following up with an
explicit fs.chmod to handle the case where the path already exists.
…-only

Add two POSIX-only tests, one per branch (inspector and retrieve),
that pre-create ~/.cache/claude-grc at 0o755 and runs.log at 0o644
before running collect.js. The pre-creation forces the chmod-after
calls to do real work; without it, fs.mkdir's mode option would
create the dir at 0o700 and fs.appendFile's mode option would create
the file at 0o600 on first run, masking any regression in the
chmod-after path (Node honors the mode option only on creation).

Each test asserts the post-run mode of both the file (0o600) and its
parent directory (0o700).
@AnandSundar
AnandSundar force-pushed the feat/aws-secrets-inspector branch from ed37c44 to 4b4ef42 Compare July 18, 2026 17:49
@AnandSundar

Copy link
Copy Markdown
Contributor Author

@
@ethanolivertroy Rebased onto current origin/main (bedcfcf) to clear the merge conflict — feat/aws-secrets-inspector is now mergeable.

What changed under the hood

  • origin/main had advanced from bc6fd5a to bedcfcf since the prior push, which is why GitHub reported a conflict.
  • Rebased all 24 PR commits onto bedcfcf (no commits rewritten by hand; only the tip moved).
  • Resolved the single manual conflict in package.json by keeping both scripts that diverged between branches: test:schema-validator (from main) and test:aws-secrets-inspector (from this PR). devDependencies and test:contract:findings were already clean against main, no edits needed.
  • .claude-plugin/marketplace.json auto-merged cleanly — merge-tree confirms a clean tree and both this PR’s aws-secrets-inspector entry and the newer academic-research-companion entry coexist.

Validation (all green)

  • node --check plugins/connectors/aws-secrets-inspector/scripts/collect.js — syntax OK
  • node --test tests/aws-secrets-inspector-collect.test.js10/10 pass
  • node tests/aws-secrets-inspector-paths.mjs8/8 pass
  • node tests/aws-secrets-inspector-symlinks.mjs3/3 pass on Windows + 3 POSIX-only platform-precondition skips (expected, not regressions)
  • marketplace.json parses; the aws-secrets-inspector entry has keys [name, source, description, version] — no forbidden namespace / commands / skills / hooks keys
  • git merge-base --is-ancestor origin/main HEAD exits 0 (24 commits ahead, clean linear history)

Force-pushed with --force-with-lease after a fresh git fetch fork. Ready for re-review whenever you have a moment.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add AWS Secrets Manager connector plugin

3 participants