Skip to content

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

Open
AnandSundar wants to merge 20 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 20 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 and others added 6 commits June 20, 2026 15:24
…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>
…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>
@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 and usage tips.

@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 on lines +2 to +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",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

1. plugin.json author not string 📘 Rule violation § Compliance

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.
Agent Prompt
## 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

Comment on lines +1 to +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>]
*

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

2. Node script in connector plugin 📘 Rule violation ⌂ Architecture

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.
Agent Prompt
## 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

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

Adds the aws-secrets-inspector connector plugin with two modes: inspector mode (default) emits v1 finding-contract documents for four SCF-mapped checks (rotation, customer-managed KMS, public-access resource policy, inactive access), and retrieval mode (--retrieve=<name>) returns a single secret value with strict safety invariants (no cache writes, no value in runs.log, --write-to restricted to ~/.config/claude-grc/secrets/). Previously-flagged issues from round 2 review (sha256/byte_size mismatch, destination using lexical path instead of realpath, missing --region in retrieval CLI call, and tags in error-fallback path) are all addressed in this version.

  • Inspector mode evaluates each secret against four SCF controls (CRY-09 × 2, IAC-21, IAC-15.3), with per-secret error fallback producing inconclusive evaluations rather than dropping secrets from the report.
  • Retrieval mode implements a documented region-resolution fallback chain (--region → first of --regionsconfig.defaults.regions[0] → env vars → us-east-1), but the code skips the args.regions[0] step entirely — a single-entry --regions=<region> is silently ignored and the config default is used instead, contradicting the published documentation.
  • Test coverage is thorough: fixture-based schema conformance, retrieval stdout/runs.log invariants, path-traversal and symlink-escape hardening, 0600 file mode, region resolution precedence, and wide-mode dir rejection are all exercised.

Confidence Score: 4/5

Safe to merge with one code fix: the --regions single-entry case in retrieval mode must either be honored (as documented) or explicitly rejected, to avoid silently using the wrong region.

The retrieval region-resolution loop documents args.regions[0] as a fallback but the implementation never reads it. A user who types --regions=eu-west-1 in retrieval mode has that value silently dropped; the call lands on the config default region, with only a generic "inferred from config" warning (suppressed under --quiet). This is a present behavioral defect on the retrieval path. Everything else — the path-traversal guard, symlink resistance, permission modes, sha256 audit trail, and cache isolation — is correct and well-tested.

plugins/connectors/aws-secrets-inspector/scripts/collect.js (region-resolution logic in retrieveSecret) and plugins/connectors/aws-secrets-inspector/commands/retrieve.md (documents the step the code omits).

Important Files Changed

Filename Overview
plugins/connectors/aws-secrets-inspector/scripts/collect.js Core connector implementation (inspector + retrieval modes). The previously-flagged issues (tags in error path, sha256 mismatch, destination realpath, region not passed) are all fixed. One new gap: args.regions[0] is never consulted in the retrieval region-resolution chain despite being documented as a fallback step.
tests/aws-secrets-inspector-collect.test.js Comprehensive fixture-based behavioral tests covering inspector-mode schema conformance, retrieval stdout + runs.log invariants, path-traversal rejection, wide-mode dir rejection, multi-region rejection, 0600 file mode, symlink destination, and region resolution precedence. All paths exercised.
tests/aws-secrets-inspector-paths.mjs Lexical path-traversal unit tests for safeResolveWritePath. Covers absolute-outside, traversal-via-.., and correct inside-secrets-dir acceptance. The local reimplementation of the function stays in sync with the production version for the cases it tests.
tests/aws-secrets-inspector-symlinks.mjs Integration test for symlink-escape resistance in safeResolveWritePath. Covers symlink-inside-secrets-dir-pointing-outside, nested symlinks, /etc/passwd symlink, and brand-new file acceptance. Properly skips when symlinks are unsupported.
plugins/connectors/aws-secrets-inspector/commands/retrieve.md Documents retrieval mode usage, safety contract, and exit codes. The --region fallback chain description includes "first of --regions" as a step, which the code does not implement — this creates a documentation/implementation mismatch.
plugins/connectors/aws-secrets-inspector/commands/collect.md Inspector mode documentation with SCF evaluation table, region precedence, and output format. Accurate to the implementation.
plugins/connectors/aws-secrets-inspector/scripts/setup.sh Idempotent setup script that verifies AWS CLI, resolves credentials, and writes the connector config. Mirrors the existing aws-inspector pattern.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A([node collect.js args]) --> B{--retrieve set?}
    B -- Yes --> R1[retrieveSecret]
    B -- No --> I1[Read config YAML]

    subgraph Retrieval Mode
        R1 --> R2[Read config YAML]
        R2 --> R3{--regions.length > 1?}
        R3 -- Yes --> R4[fail EXIT.USAGE]
        R3 -- No --> R5{--region set?}
        R5 -- Yes --> R6[use --region]
        R5 -- No --> R7{config.defaults.regions 0?}
        R7 -- Yes --> R8[use config.defaults.regions 0]
        R7 -- No --> R9[config/env/default fallback]
        R8 --> R10[aws get-secret-value --region]
        R6 --> R10
        R9 --> R10
        R10 --> R11{--write-to set?}
        R11 -- Yes --> R12[safeResolveWritePath]
        R12 --> R13[mkdir 0700 + chmod]
        R13 --> R14[writeFile 0600]
        R14 --> R15[stdout: confirmation only]
        R11 -- No --> R16[stdout: JSON payload]
        R15 & R16 --> R17[runs.log: byte_size + sha256 only]
    end

    subgraph Inspector Mode
        I1 --> I2[For each region]
        I2 --> I3[list-secrets]
        I3 --> I4[For each secret ARN]
        I4 --> I5[describe-secret]
        I5 --> I6[get-resource-policy]
        I6 --> I7[Evaluate 4 SCF checks]
        I7 --> I8[Emit Finding]
        I8 --> I4
        I4 --> I9[Write cache JSON]
        I9 --> I10[Append runs.log manifest]
    end

    style R4 fill:#ff9999
    style R12 fill:#99ccff
    style R14 fill:#99ccff
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"}}}%%
flowchart TD
    A([node collect.js args]) --> B{--retrieve set?}
    B -- Yes --> R1[retrieveSecret]
    B -- No --> I1[Read config YAML]

    subgraph Retrieval Mode
        R1 --> R2[Read config YAML]
        R2 --> R3{--regions.length > 1?}
        R3 -- Yes --> R4[fail EXIT.USAGE]
        R3 -- No --> R5{--region set?}
        R5 -- Yes --> R6[use --region]
        R5 -- No --> R7{config.defaults.regions 0?}
        R7 -- Yes --> R8[use config.defaults.regions 0]
        R7 -- No --> R9[config/env/default fallback]
        R8 --> R10[aws get-secret-value --region]
        R6 --> R10
        R9 --> R10
        R10 --> R11{--write-to set?}
        R11 -- Yes --> R12[safeResolveWritePath]
        R12 --> R13[mkdir 0700 + chmod]
        R13 --> R14[writeFile 0600]
        R14 --> R15[stdout: confirmation only]
        R11 -- No --> R16[stdout: JSON payload]
        R15 & R16 --> R17[runs.log: byte_size + sha256 only]
    end

    subgraph Inspector Mode
        I1 --> I2[For each region]
        I2 --> I3[list-secrets]
        I3 --> I4[For each secret ARN]
        I4 --> I5[describe-secret]
        I5 --> I6[get-resource-policy]
        I6 --> I7[Evaluate 4 SCF checks]
        I7 --> I8[Emit Finding]
        I8 --> I4
        I4 --> I9[Write cache JSON]
        I9 --> I10[Append runs.log manifest]
    end

    style R4 fill:#ff9999
    style R12 fill:#99ccff
    style R14 fill:#99ccff
Loading

Fix All in Claude Code Fix All in Codex Fix All in Cursor

Reviews (6): Last reviewed commit: "fix(lychee): exclude deleted support.cla..." | 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
Comment thread plugins/connectors/aws-secrets-inspector/scripts/collect.js Outdated
AnandSundar added 2 commits June 20, 2026 19:00
… 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.

@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 and others added 2 commits June 20, 2026 19:15
…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.
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 on lines +598 to +618
if (args.regions.length > 1) {
fail(EXIT.USAGE, `--retrieve does not accept --regions with multiple entries; pass a single --region=<id> instead (got: ${args.regions.join(',')}).`);
}
let region;
let regionSource;
if (args.region) {
region = args.region; regionSource = '--region';
} else if (config.defaults?.regions?.[0]) {
region = config.defaults.regions[0]; regionSource = 'config.defaults.regions[0]';
} else if (config.default_region) {
region = config.default_region; regionSource = 'config.default_region';
} else if (process.env.AWS_DEFAULT_REGION) {
region = process.env.AWS_DEFAULT_REGION; regionSource = 'AWS_DEFAULT_REGION';
} else if (process.env.AWS_REGION) {
region = process.env.AWS_REGION; regionSource = 'AWS_REGION';
} else {
region = 'us-east-1'; regionSource = 'default';
}
if (regionSource !== '--region') {
log(`retrieve: region=${region} inferred from ${regionSource}; pass --region=<id> to make this explicit.`);
}

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 --regions single-entry silently dropped in retrieval mode

commands/retrieve.md documents the fallback chain as: --region → first of --regionsconfig.defaults.regions[0]config.default_region → env vars → us-east-1. The code, however, skips args.regions[0] entirely — after the multi-region guard (lines 598–600 reject only length > 1), the resolution immediately falls through to config.defaults.regions[0]. A user who passes --regions=eu-west-1 expecting a single-entry fallback (as the docs promise) silently uses the config default region instead. The warning on line 617 tells them a region was inferred from config, but never mentions that their --regions value was discarded — especially invisible under --quiet. The fix is to add else if (args.regions[0]) between the args.region and config.defaults?.regions?.[0] branches.

Fix in Claude Code Fix in Codex Fix in Cursor

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

1 participant