Skip to content
Open
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
6133296
feat(aws-secrets-inspector): U1 plugin scaffold and marketplace regis…
AnandSundar Jun 20, 2026
c2d2752
feat(aws-secrets-inspector): U2 setup and status scripts
AnandSundar Jun 20, 2026
82a4570
feat(aws-secrets-inspector): U3 inspector mode
AnandSundar Jun 20, 2026
f887ae4
feat(aws-secrets-inspector): U4 retrieval mode
AnandSundar Jun 20, 2026
47eb646
feat(aws-secrets-inspector): U5 SKILL.md and command documentation
AnandSundar Jun 20, 2026
cab00e8
feat(aws-secrets-inspector): U6 contract fixtures, behavioral test, f…
AnandSundar Jun 20, 2026
ccb34af
fix(retrieve.md): correct malformed JSON in IAM policy example
Jun 20, 2026
4bc1f59
fix(collect.js): restore four-evaluation contract in error fallback
Jun 20, 2026
6ba90e8
fix(path-safety): align docs, test, and code on relative-path behavior
Jun 20, 2026
eac1778
chore(status.sh): replace ls with find for robust filename handling
Jun 20, 2026
83bdddb
docs(collect.js): add JSDoc to functions to clear 80% docstring gate
Jun 20, 2026
0de8ad8
fix(collect.js): flatten tags in error-fallback to v1 contract shape
Jun 21, 2026
0428b82
fix(collect.js): resolve symlinks before --write-to containment check
AnandSundar Jun 21, 2026
a085d5c
fix(collect.js): pass --region to get-secret-value in retrieve mode
AnandSundar Jun 21, 2026
952aea2
refactor(collect.js): remove dead fs.access try/catch in awsFixture
AnandSundar Jun 21, 2026
a8818d0
fix(collect.js): harden retrieve path-safety and region resolution pe…
AnandSundar Jun 21, 2026
52c822a
fix(aws-secrets-inspector): make retrieve manifest match the artifact…
Jun 21, 2026
bb354cc
test(aws-secrets-inspector): report symlink rejections as test failur…
Jun 21, 2026
c65732d
test(aws-secrets-inspector): use Buffer.byteLength for byte_size asse…
Jun 21, 2026
d6fcf39
fix(lychee): exclude deleted support.claude.com Cowork article
AnandSundar Jun 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,12 @@
"description": "GRC connector for AWS: IAM, S3, CloudTrail, EBS compliance checks \u2192 v1 finding contract",
"version": "0.1.0"
},
{
"name": "aws-secrets-inspector",
"source": "./plugins/connectors/aws-secrets-inspector",
"description": "GRC connector for AWS Secrets Manager: rotation, customer-managed KMS, resource policy, and access-pattern checks \u2192 v1 finding contract. Hybrid shape supports opt-in secret retrieval to stdout or 0600-permission files.",
"version": "0.1.0"
},
{
"name": "github-inspector",
"source": "./plugins/connectors/github-inspector",
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"scaffold-framework": "node plugins/grc-engineer/scripts/scaffold-framework.js",
"generate-coverage": "node plugins/grc-engineer/scripts/generate-coverage.js",
"test:wiz-inspector": "node --test tests/wiz-inspector-collect.test.js",
"test:aws-secrets-inspector": "node --test tests/aws-secrets-inspector-collect.test.js",
"test:contract": "bash tests/validate-contract-fixtures.sh",
"test:grc-diagrams": "bash tests/validate-grc-diagram-skills.sh",
"test:plugin-manifests": "bash tests/validate-plugin-manifests.sh",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"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",
Comment on lines +2 to +6

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

"repository": "https://github.com/GRCEngClub/claude-grc-engineering",
"keywords": ["grc", "compliance", "aws", "secrets-manager", "scf", "soc2", "fedramp", "connector"]
}
112 changes: 112 additions & 0 deletions plugins/connectors/aws-secrets-inspector/commands/collect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
---
name: AWS Secrets Inspector Collect
description: Query AWS Secrets Manager for compliance-relevant configuration (rotation, KMS, resource policy, access patterns) and emit findings conforming to the v1 contract.
---

# /aws-secrets-inspector:collect

Scans AWS Secrets Manager for compliance-relevant configuration. Emits one Finding document per secret. Inspector mode is the default path of `aws-secrets-inspector`. For value retrieval, use `/aws-secrets-inspector:retrieve` instead.

## How to run

```bash
node plugins/connectors/aws-secrets-inspector/scripts/collect.js [options]
```

## Arguments

- `--regions=<csv>` — regions to scan (default: the config's `default_region` or `defaults.regions`)
- `--profile=<name>` — override the configured AWS profile
- `--output=<fmt>` — `silent` | `summary` (default) | `json`
- `--refresh` — ignore cache; re-query AWS (always refreshes for now)
- `--quiet` — no stderr progress

## What it evaluates

One Finding per secret, with four SCF-mapped evaluations. The SCF IDs were verified against the live SCF API at `https://grcengclub.github.io/scf-api` during implementation (the implementation plan's prior guesses were superseded).

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

If a `get-resource-policy` call returns `ResourcePolicyNotFoundException`, the secret relies on IAM only and IAC-21 is recorded as `pass` with a note. If the call fails for any other reason, IAC-21 is `inconclusive`.

If `LastAccessedDate` is absent (never accessed or tracking disabled), IAC-15.3 is `inconclusive`; the operator can rely on the rotation-age signal recorded in `raw_attributes.RotationAgeDays` and on their consumer inventory.

## Output

- Writes `~/.cache/claude-grc/findings/aws-secrets-inspector/<run_id>.json` — array of Findings
- Appends a run manifest to `~/.cache/claude-grc/runs.log`
- One-line summary unless `--quiet` or `--output=json`:

```
aws-secrets-inspector: 12 resources, 48 evaluations, 7 failing (1 critical, 4 high, 2 medium).
```

The `raw_attributes` block on each Finding carries everything needed for downstream triage without a re-query: `Name`, `ARN`, `RotationEnabled`, `RotationRules`, `LastRotatedDate`, `LastChangedDate`, `LastAccessedDate`, `KmsKeyId`, `OwningService`, `PrimaryRegion`, `Description`, `InactiveDays`, `RotationAgeDays`. `resource.tags` carries the secret's tag set.

## Exit codes

- `0` success
- `2` credentials invalid or expired; or `--retrieve` not yet implemented (U4 follow-up)
- `3` rate-limited (AWS throttling; retry later)
- `4` partial (some regions inaccessible; report still written)
- `5` config missing — run setup
- `6` retrieval mode: secret not found (U4)

## Permissions

Minimum IAM policy for inspector mode (read-only):

```json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"secretsmanager:ListSecrets",
"secretsmanager:DescribeSecret",
"secretsmanager:GetResourcePolicy"
],
"Resource": "*"
}
]
}
```

For retrieve mode (U4), additionally: `secretsmanager:GetSecretValue`, `kms:Decrypt`.

The AWS-managed `ReadOnlyAccess` policy is a superset that also works.

## Examples

```bash
# Default region, all secrets
/aws-secrets-inspector:collect

# Multi-region scan
/aws-secrets-inspector:collect --regions=us-east-1,us-west-2,eu-west-1

# Alternate profile (e.g., for cross-account via the AWS CLI profile chain)
/aws-secrets-inspector:collect --profile=audit-target

# CI/CD-friendly
node plugins/connectors/aws-secrets-inspector/scripts/collect.js --quiet --output=json
```

## Cross-account access

This connector does NOT implement `sts:AssumeRole` itself. For cross-account access, configure the standard AWS CLI profile chain in `~/.aws/config` and pass `--profile=<name>`:

```ini
[profile audit-target]
role_arn = arn:aws:iam::222233334444:role/SecurityAudit
source_profile = default
mfa_serial = arn:aws:iam::111122223333:mfa/you # optional
```

The AWS SDK performs the `AssumeRole` transparently; this connector sees the resolved credentials. See `commands/setup.md` and `commands/retrieve.md` for the same pattern applied to setup and retrieval.
108 changes: 108 additions & 0 deletions plugins/connectors/aws-secrets-inspector/commands/retrieve.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
---
name: AWS Secrets Inspector Retrieve
description: Retrieve a single AWS Secrets Manager secret value to stdout or a 0600-permission file. Opt-in retrieval mode — never writes to the findings cache.
---

# /aws-secrets-inspector:retrieve

Retrieves a single AWS Secrets Manager secret value and returns it to stdout (default) or to a 0600-permission file under `~/.config/claude-grc/secrets/`. This is the **only** path through the connector that produces a secret value; the inspector mode never reads `SecretString` or `SecretBinary`.

## How to run

```bash
node plugins/connectors/aws-secrets-inspector/scripts/collect.js --retrieve=<secret-name> [options]
```

## Arguments

- `--retrieve=<name>` — required; the secret's name or full ARN
- `--version-stage=<stage>` — `AWSCURRENT` (default), `AWSPREVIOUS`, or a custom stage label
- `--version-id=<uuid>` — alternative to `--version-stage`; explicit version UUID
- `--write-to=<path>` — write the JSON to a 0600-permission file at `<path>` (must resolve under `~/.config/claude-grc/secrets/`); confirmation line is printed to stdout
- `--region=<region>` — region for the `get-secret-value` call; falls back to the first of `--regions`, then `config.defaults.regions[0]`, then `config.default_region`, then `AWS_DEFAULT_REGION` / `AWS_REGION`, then `us-east-1`. Pass this when the secret lives in a region other than the one encoded in your AWS profile.
- `--profile=<name>` — override the configured AWS profile
- `--quiet` — no stderr progress

## Output

**Stdout (default).** A single line of JSON:

```json
{"name":"prod-db","arn":"arn:aws:secretsmanager:us-east-1:123456789012:secret:prod-db-AbCdEf","version_id":"a1b2c3d4-...","version_stages":["AWSCURRENT"],"created_at":"2026-04-13T15:10:00Z","secret_string":"<value>"}
```

For binary secrets, `secret_binary` is set to the base64 string returned by AWS (text-safe on stdout); `secret_string` is absent.

**File (`--write-to`).** The same JSON is written to `<path>`, with the file created at mode 0600 and the parent directory at mode 0700. Stdout emits a confirmation:

```
aws-secrets-inspector:retrieve wrote 247 bytes to ~/.config/claude-grc/secrets/prod-db (sha256=...)
```

The confirmation does **not** include the secret value.

## Safety contract

This connector enforces three invariants for retrieval runs:

1. **No findings cache writes.** The retrieval branch short-circuits at the top of `main()` and never reaches 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/`.** 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. The parent directory is created with mode 0700; the destination file is created with mode 0600 (umask 077 enforced during the write).

The retrieval manifest in `runs.log` looks like:

```json
{"source":"aws-secrets-inspector","run_id":"20260620-...","mode":"retrieve","secret_name":"prod-db","version_id":"a1b2c3d4-...","version_stage":"AWSCURRENT","destination":"~/.config/claude-grc/secrets/prod-db","byte_size":247,"sha256":"...","exit_code":0}
```

## Exit codes

- `0` success
- `2` usage error (unknown flag, `--write-to` outside the permitted root)
- `2` auth failure (credentials invalid or expired)
- `3` rate-limited
- `5` config missing — run setup
- `6` secret not found in this account/region (or denied by the resource policy)

## Permissions

```json
{
"Version": "2012-10-17",
"Statement": [
{ "Effect": "Allow", "Action": ["secretsmanager:GetSecretValue"], "Resource": "<secret-arn-or-*>" },
{ "Effect": "Allow", "Action": ["kms:Decrypt"], "Resource": "<kms-key-arn>" }
]
}
```

For cross-account access, configure the standard AWS CLI profile chain in `~/.aws/config` and pass `--profile=<name>` (this connector does NOT implement `sts:AssumeRole` itself).

## Examples

```bash
# Default: AWSCURRENT to stdout
/aws-secrets-inspector:retrieve --retrieve=prod-db

# Explicit region when the secret lives outside your profile's region
/aws-secrets-inspector:retrieve --retrieve=prod-db --region=eu-west-1

# Previous version, captured into a 0600 file
/aws-secrets-inspector:retrieve --retrieve=prod-db --version-stage=AWSPREVIOUS \
--write-to=~/.config/claude-grc/secrets/prod-db-prev

# Use in a CI step: pipe the JSON into a jq extractor
/aws-secrets-inspector:retrieve --retrieve=prod-db --quiet | jq -r .secret_string

# Cross-account via the profile chain
/aws-secrets-inspector:retrieve --retrieve=prod-db --profile=audit-target
```

## Why this is not a `secret get` replacement

This connector is a thin wrapper around `aws secretsmanager get-secret-value`. It adds two things the raw CLI does not:

- **A safety wrapper** that guarantees no value lands in the findings cache, `runs.log`, or stderr.
- **A restricted `--write-to`** that prevents the operator from accidentally writing the value to a world-readable file.

Operators who prefer the raw `aws` CLI for ad-hoc work can use it directly. Operators who need a CI-friendly secret source should use `/aws-secrets-inspector:retrieve` so the audit trail is consistent.
101 changes: 101 additions & 0 deletions plugins/connectors/aws-secrets-inspector/commands/setup.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
---
name: AWS Secrets Inspector Setup
description: Verify the aws-secrets-inspector connector's prerequisites and write its config. Idempotent.
---

# /aws-secrets-inspector:setup

Prepares the aws-secrets-inspector connector. Confirms `aws` CLI is installed and credentials resolve, writes `~/.config/claude-grc/connectors/aws-secrets-inspector.yaml`, and creates `~/.config/claude-grc/secrets/` (mode 0700) as the destination for `--write-to=` retrievals. Runs a read-only health check.

## How to run

```bash
bash plugins/connectors/aws-secrets-inspector/scripts/setup.sh [--profile=<name>] [--region=<region>]
```

Exits 0 on success, 2 on missing/invalid credentials, 5 on missing `aws` binary.

## Credential precedence

Honors the standard AWS credential chain:

1. `--profile` flag (writes it to config)
2. `AWS_PROFILE` environment variable
3. `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` env vars
4. `~/.aws/credentials` default profile
5. Instance metadata / IRSA / SSO

The setup script runs `aws sts get-caller-identity` to verify credentials resolve *before* writing the config. If none resolve, it fails with actionable remediation.

## What it does

1. Check `aws` CLI is installed (`aws --version`).
2. Resolve credentials (`aws sts get-caller-identity`).
3. Resolve default region (from `--region`, `AWS_REGION`, config, or fall back with a warning).
4. Write config:

```yaml
version: 1
source: aws-secrets-inspector
source_version: "0.1.0"
account_id: "<12-digit>"
caller_arn: "<arn>"
profile: "<if set>"
default_region: "us-east-1"
defaults:
regions: ["us-east-1"]
rotation: true
kms: customer_managed
```

5. Create the cache dir `~/.cache/claude-grc/findings/aws-secrets-inspector/` and ensure `~/.cache/claude-grc/runs.log` exists.
6. Create the secrets dir `~/.config/claude-grc/secrets/` at mode 0700 (operator-only).
7. Warn if the caller ARN has administrative privileges (dogfooding: production scans and retrievals should use a dedicated least-privilege role).

## Typical output

```
aws-secrets-inspector:setup ✓
aws: /opt/homebrew/bin/aws 2.15.1
account: 123456789012
caller: arn:aws:iam::123456789012:user/you
profile: default
default region: us-east-1
config written: /Users/you/.config/claude-grc/connectors/aws-secrets-inspector.yaml
secrets dir: /Users/you/.config/claude-grc/secrets (mode 700)

WARNING: caller ARN appears to have administrative privileges. For production
scans and retrieval, prefer a dedicated least-privilege role. Minimum IAM
actions for the aws-secrets-inspector:
- Inspector mode: secretsmanager:ListSecrets, secretsmanager:DescribeSecret,
secretsmanager:GetResourcePolicy
- Retrieve mode: + secretsmanager:GetSecretValue, kms:Decrypt

Next:
/aws-secrets-inspector:collect
/aws-secrets-inspector:retrieve --retrieve=<secret-name>
```

## Failure modes

- **aws not installed**: exit 5. Install via `brew install awscli` or https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html.
- **NoCredentialProviders**: exit 2. Remediation printed with the specific credential paths tried.
- **InvalidClientTokenId**: exit 2. Credentials are present but rejected. Check `aws configure` or SSO session.
- **Region not set**: warning; defaults to `us-east-1` and notes it in the config.

## Cross-account access

This connector does NOT implement `sts:AssumeRole` itself. For cross-account access, configure the standard AWS CLI profile chain in `~/.aws/config`:

```ini
[profile audit-target]
role_arn = arn:aws:iam::222233334444:role/SecurityAudit
source_profile = default
mfa_serial = arn:aws:iam::111122223333:mfa/you # optional
```

Then run `/aws-secrets-inspector:setup --profile=audit-target` and the connector will pick up the AssumeRole transparently. See `commands/retrieve.md` for the same pattern applied to retrievals.

## Safe to re-run

Yes. Overwrites the config; preserves cached findings and the existing secrets directory (mode 0700 is preserved).
Loading
Loading