feat: add aws-hardening Claude Code skill#8962
Conversation
|
Pipeline controller notification For optional jobs, comment This repository is configured in: LGTM mode |
|
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: devguyio The full list of commands accepted by this bot can be found here. The pull request process is described here DetailsNeeds approval from an approver in each of these files:
Approvers can indicate their approval by writing |
📝 WalkthroughWalkthroughThis PR adds a Claude Code AWS hardening skill and a supporting Bash script. The skill documents a read-only AWS setup using an assumed role, AWS CLI profile enforcement, Claude session Suggested reviewers: 🚥 Pre-merge checks | ✅ 11✅ Passed checks (11 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
|
I now have the complete picture. Here is the analysis: Test Failure Analysis CompleteJob Information
Test Failure AnalysisErrorSummaryAll three jobs were intentionally aborted by the Prow trigger plugin because the PR author ( Root CauseThe root cause is a deliberate user action, not a code or infrastructure failure. Sequence of events:
The Prow trigger plugin's behavior is by design: when a PR transitions to draft, all pending/running presubmit jobs for that PR are canceled to avoid wasting CI resources on work-in-progress code. The PR remains in draft state ( The PR only modifies two non-code files ( Recommendations
Evidence
|
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (2)
.claude/skills/aws-hardening/references/create-readonly-role.sh (2)
40-47: 🔒 Security & Privacy | 🔵 TrivialTrust policy is account-wide; consider scoping the principal.
The trust policy allows
arn:aws:iam::${ACCOUNT_ID}:root, i.e. any IAM identity in the account can assume this role. This is documented as intentional inSKILL.md, but sinceReadOnlyAccessgrants broad read across all services, any account principal can use this role to read (and potentially exfiltrate) data. If least-privilege is desired, scope thePrincipalto the specific user/role that Claude Code runs as, and/or add aCondition(e.g.aws:PrincipalArn).🤖 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 @.claude/skills/aws-hardening/references/create-readonly-role.sh around lines 40 - 47, The trust policy in create-readonly-role.sh is too broad because it uses the account root principal, allowing any IAM identity in the account to assume the ReadOnlyAccess role. Update the assume-role policy in the create-readonly-role.sh flow to scope Principal to the specific Claude Code user/role, and consider adding a Condition such as aws:PrincipalArn to further restrict who can assume the role. Keep the change localized to the trust policy construction used by the role creation logic.
58-80: 🔒 Security & Privacy | 🔵 Trivial | 💤 Low valueDeny guardrail omits self-protection and several destructive services.
SKILL.mdadvertises this as blocking "common destructive actions," but the list notably omits actions that could weaken this very control — e.g.iam:UpdateAssumeRolePolicy,iam:UpdateRole,iam:DeleteRolePolicy,iam:PutUserPolicy— as well as other destructive services likerds:Delete*,lambda:Delete*,dynamodb:DeleteTable, andcloudformation:DeleteStack. SinceReadOnlyAccessalready blocks all writes, this is only a defense-in-depth gap against policy drift, but expanding it would better match the documented intent.🤖 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 @.claude/skills/aws-hardening/references/create-readonly-role.sh around lines 58 - 80, The DenyDestructive policy in create-readonly-role.sh is missing self-protection and other destructive actions that should be blocked in defense of the readonly guardrail. Update the aws iam put-role-policy statement to include additional IAM write paths like iam:UpdateAssumeRolePolicy, iam:UpdateRole, iam:DeleteRolePolicy, and iam:PutUserPolicy, plus destructive service actions such as rds:Delete*, lambda:Delete*, dynamodb:DeleteTable, and cloudformation:DeleteStack. Keep the change scoped to the DenyDestructiveAndPrivileged statement so it still matches the documented “common destructive actions” intent.
🤖 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 @.claude/skills/aws-hardening/references/create-readonly-role.sh:
- Around line 35-50: The create-readonly-role.sh flow currently reuses an
existing role without confirming it is the intended managed role, which can
silently change unrelated permissions. Update the create/read-only role logic
around the aws iam get-role check and subsequent policy attachment steps so it
first verifies intent for an existing $ROLE_NAME, such as by checking for a
specific marker like the DenyDestructive inline policy or a dedicated tag, and
only then proceeds to attach ReadOnlyAccess or overwrite the inline policy;
otherwise stop and prompt for confirmation. Reference the existing create-role
and policy-update path to keep the guard close to the role mutation logic.
In @.claude/skills/aws-hardening/SKILL.md:
- Around line 104-129: The `Add Claude Code deny rules` and `Add CLAUDE.md
instruction` steps should not directly read or edit the agent’s own `~/.claude`
config or hooks, since that touches the high-risk `.claude` surface. Refactor
this section of `SKILL.md` so it only provides user-run setup guidance or
copy/paste snippets, and remove any instructions that have the agent modify
`~/.claude/settings.json`, `~/.claude/CLAUDE.md`, or `~/.claude/hooks` itself.
Keep the intent of the deny rules and `--profile` guidance, but route the actual
changes through the user instead of the agent.
- Around line 123-125: The fenced example in the AWS hardening skill markdown is
missing a language tag, which triggers markdownlint and reduces readability;
update the code fence around the `aws` CLI guidance to use a tagged fence such
as `text` or `markdown`, keeping the existing content unchanged and matching the
style used elsewhere in `SKILL.md`.
---
Nitpick comments:
In @.claude/skills/aws-hardening/references/create-readonly-role.sh:
- Around line 40-47: The trust policy in create-readonly-role.sh is too broad
because it uses the account root principal, allowing any IAM identity in the
account to assume the ReadOnlyAccess role. Update the assume-role policy in the
create-readonly-role.sh flow to scope Principal to the specific Claude Code
user/role, and consider adding a Condition such as aws:PrincipalArn to further
restrict who can assume the role. Keep the change localized to the trust policy
construction used by the role creation logic.
- Around line 58-80: The DenyDestructive policy in create-readonly-role.sh is
missing self-protection and other destructive actions that should be blocked in
defense of the readonly guardrail. Update the aws iam put-role-policy statement
to include additional IAM write paths like iam:UpdateAssumeRolePolicy,
iam:UpdateRole, iam:DeleteRolePolicy, and iam:PutUserPolicy, plus destructive
service actions such as rds:Delete*, lambda:Delete*, dynamodb:DeleteTable, and
cloudformation:DeleteStack. Keep the change scoped to the
DenyDestructiveAndPrivileged statement so it still matches the documented
“common destructive actions” intent.
🪄 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: Repository YAML (base), Central YAML (inherited)
Review profile: CHILL
Plan: Enterprise
Run ID: a5b89810-aa56-47a0-8964-d891281c2a85
📒 Files selected for processing (2)
.claude/skills/aws-hardening/SKILL.md.claude/skills/aws-hardening/references/create-readonly-role.sh
| ``` | ||
| - You MUST include `--profile ai-agent` in every `aws` CLI invocation. You MUST NOT run `aws` commands without an explicit `--profile` flag. | ||
| ``` |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Add a language tag to the fenced example.
The plain fence trips the markdownlint warning and makes the example slightly less clear. Use text or markdown on the fence.
Fix
-```
+```text📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| ``` | |
| - You MUST include `--profile ai-agent` in every `aws` CLI invocation. You MUST NOT run `aws` commands without an explicit `--profile` flag. | |
| ``` |
🧰 Tools
🪛 markdownlint-cli2 (0.22.1)
[warning] 123-123: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🪛 SkillSpector (2.3.7)
[error] 106: [AS1] Agent Config Directory Access: Skill reads from agent configuration directories (.claude/, .codex/, .gemini/). These directories may contain API keys, personal settings, and other credentials that the skill has no legitimate need to access.
Remediation: Remove all code or instructions that access agent configuration directories (.claude/, .codex/, .gemini/). If configuration values are needed, pass them explicitly as parameters or environment variables — never read the agent's own config files.
(Agent Snooping (AS1))
[warning] 47: [RA2] Session Persistence: Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.
Remediation: Remove any persistence mechanisms (cron jobs, startup scripts, state files). Skills should not maintain state across sessions without explicit user consent.
(Rogue Agent (RA2))
🤖 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 @.claude/skills/aws-hardening/SKILL.md around lines 123 - 125, The fenced
example in the AWS hardening skill markdown is missing a language tag, which
triggers markdownlint and reduces readability; update the code fence around the
`aws` CLI guidance to use a tagged fence such as `text` or `markdown`, keeping
the existing content unchanged and matching the style used elsewhere in
`SKILL.md`.
Source: Linters/SAST tools
6ff7c0d to
81bb0e1
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 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 @.claude/skills/aws-hardening/SKILL.md:
- Around line 140-148: The remaining untagged code fences in the AWS hardening
skill examples are still triggering MD040. Update the affected fences in the
examples near the allow/deny patterns to include a harmless language tag such as
text so the content stays unchanged while satisfying markdownlint; use the
existing example blocks in SKILL.md as the target for this cleanup.
- Line 145: The optional Bash allowlist in the AWS hardening skill still
includes aws configure *, which can mutate local AWS credential and config
files. Remove that entry from the allowlist in SKILL.md and keep only truly
read-only AWS commands; if you need a reference point, update the allowlist
section around the Bash patterns so it no longer permits aws configure *.
🪄 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: Repository YAML (base), Central YAML (inherited)
Review profile: CHILL
Plan: Enterprise
Run ID: d077e270-e20f-4d57-a5a3-24813e8c1a13
📒 Files selected for processing (2)
.claude/skills/aws-hardening/SKILL.md.claude/skills/aws-hardening/references/create-readonly-role.sh
🚧 Files skipped from review as they are similar to previous changes (1)
- .claude/skills/aws-hardening/references/create-readonly-role.sh
| ``` | ||
| Bash(aws * describe-*) | ||
| Bash(aws * list-*) | ||
| Bash(aws * get-*) | ||
| Bash(aws sts get-caller-identity) | ||
| Bash(aws configure *) | ||
| Bash(aws s3 ls *) | ||
| Bash(aws s3 cp s3://* *) | ||
| ``` |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Tag the remaining bare fences.
The allow/deny examples still trip MD040. Add a language tag like text so the examples stay lint-clean without changing the content. As per static analysis, the markdownlint warning is still present for these fences.
Also applies to: 151-167
🧰 Tools
🪛 markdownlint-cli2 (0.22.1)
[warning] 140-140: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🪛 SkillSpector (2.3.7)
[error] 108: [AS1] Agent Config Directory Access: Skill reads from agent configuration directories (.claude/, .codex/, .gemini/). These directories may contain API keys, personal settings, and other credentials that the skill has no legitimate need to access.
Remediation: Remove all code or instructions that access agent configuration directories (.claude/, .codex/, .gemini/). If configuration values are needed, pass them explicitly as parameters or environment variables — never read the agent's own config files.
(Agent Snooping (AS1))
[error] 34: [PE3] Credential Access: Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
Remediation: Remove references to credential paths. Use environment variables or secrets managers. For docs, use placeholder paths (e.g., /path/to/config). Never load .env or token files in production code paths.
(Privilege Escalation (PE3))
[error] 34: [PE3] Credential Access: Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
Remediation: Remove references to credential paths. Use environment variables or secrets managers. For docs, use placeholder paths (e.g., /path/to/config). Never load .env or token files in production code paths.
(Privilege Escalation (PE3))
[warning] 49: [RA2] Session Persistence: Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.
Remediation: Remove any persistence mechanisms (cron jobs, startup scripts, state files). Skills should not maintain state across sessions without explicit user consent.
(Rogue Agent (RA2))
🤖 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 @.claude/skills/aws-hardening/SKILL.md around lines 140 - 148, The remaining
untagged code fences in the AWS hardening skill examples are still triggering
MD040. Update the affected fences in the examples near the allow/deny patterns
to include a harmless language tag such as text so the content stays unchanged
while satisfying markdownlint; use the existing example blocks in SKILL.md as
the target for this cleanup.
Source: Linters/SAST tools
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #8962 +/- ##
==========================================
+ Coverage 43.50% 43.78% +0.27%
==========================================
Files 771 772 +1
Lines 95722 96000 +278
==========================================
+ Hits 41648 42033 +385
+ Misses 51174 51055 -119
- Partials 2900 2912 +12 see 26 files with indirect coverage changes
Flags with carried forward coverage won't be shown. Click here to find out more. 🚀 New features to boost your workflow:
|
81bb0e1 to
172ef77
Compare
|
/uncc |
Interactive skill that guides users through hardening Claude Code's AWS access with five defense layers: read-only IAM role, CLI profile, profile enforcement (shell-env/wrapper/hook), deny rules, and CLAUDE.md behavioral guardrail. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Signed-off-by: Ahmed Abdalla <aabdelre@redhat.com>
172ef77 to
7a1a709
Compare
There was a problem hiding this comment.
Actionable comments posted: 7
🤖 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 @.claude/skills/aws-hardening/SKILL.md:
- Around line 145-146: Tighten the S3 command allowlist by replacing the broad
Bash(aws s3 cp s3://* *) pattern with an explicitly approved bucket/prefix and
safe destination constraint, or remove it and require explicit approval for such
copies. Keep Bash(aws s3 ls *) unchanged.
- Around line 79-86: Update the hook script’s AWS_PROFILE emission to prevent
profile-name values from breaking the generated shell command. In the hook
example, either validate the profile name against a safe character set before
writing it or apply shell-safe quoting that preserves embedded apostrophes and
other special characters; retain the existing CLAUDE_ENV_FILE guard and
successful exit behavior.
- Around line 88-102: Update the settings.json SessionStart configuration to
include matchers for startup, resume, clear, and compact, ensuring each
lifecycle event invokes the existing aws-profile-env.sh command and preserves
the current hook configuration.
- Around line 73-77: Update the shell-env, wrapper, and Claude Code SessionStart
hook guidance to clear or reject AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and
AWS_SESSION_TOKEN before setting AWS_PROFILE. Require aws sts
get-caller-identity to verify the read-only role before performing any AWS work,
and preserve the existing shell-specific, wrapper, and CLAUDE_ENV_FILE setup
instructions.
- Line 3: Soften the absolute AWS boundary wording in the front-matter
description and the skill’s Goal, replacing claims that Claude Code “cannot
directly access” ~/.aws with language that it reduces direct access or is
constrained by the sandbox. Keep the existing Bash deny-rule behavior and later
caveat unchanged.
- Around line 73-76: The shell-env and wrapper guidance must explicitly state
that these options alter the user’s general AWS context, not just Claude
sessions. Update the shell-env and wrapper descriptions to note their broader
scope, and direct users seeking Claude-only isolation to the SessionStart hook.
- Around line 139-147: Update the read-only AWS CLI allowlist near the “Allow”
section so every permitted pattern requires the intended guarded profile,
including describe, list, get, sts, and s3 commands; alternatively remove this
allowlist and rely on the IAM role. Ensure commands specifying a different
profile cannot match these entries.
🪄 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: Repository YAML (base), Central YAML (inherited)
Review profile: CHILL
Plan: Enterprise
Run ID: fdcfac39-daa4-4cd5-91a8-f3f060ff67f2
📒 Files selected for processing (2)
.claude/skills/aws-hardening/SKILL.md.claude/skills/aws-hardening/references/create-readonly-role.sh
🚧 Files skipped from review as they are similar to previous changes (1)
- .claude/skills/aws-hardening/references/create-readonly-role.sh
| @@ -0,0 +1,174 @@ | |||
| --- | |||
| name: AWS Hardening | |||
| description: "Harden Claude Code's AWS access for safe AI-assisted cloud work. Sets up a read-only IAM role, configures an AWS CLI profile that assumes it via temporary STS credentials, enforces the profile as default through shell-env, wrapper, or SessionStart hook strategies, and locks Claude Code out of ~/.aws with deny rules. Use when the user says 'harden aws', 'aws hardening', 'lock down aws', 'read-only aws', 'aws-hardening', or wants to secure Claude's AWS access." | |||
There was a problem hiding this comment.
🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
file=".claude/skills/aws-hardening/SKILL.md"
printf '== file size ==\n'
wc -l "$file"
printf '\n== outline ==\n'
ast-grep outline "$file" --view expanded || true
printf '\n== lines 1-160 ==\n'
cat -n "$file" | sed -n '1,160p'Repository: openshift/hypershift
Length of output: 11435
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect Claude docs or references in-repo if present, to see how permissions are described.
rg -n "Read/Edit denies|Bash subprocesses|permission rules|sandboxing provides the OS-level boundary|~/.aws|SessionStart" .claude README.md docs . -g '!**/node_modules/**' || trueRepository: openshift/hypershift
Length of output: 21217
Soften the ~/.aws boundary language in the front matter and Goal.
“Cannot directly access ~/.aws” is too absolute here; the later note already says the Bash rules are only a speed bump. Reword this as “reduces direct access” or make the sandbox boundary explicit.
🧰 Tools
🪛 SkillSpector (2.3.7)
[error] 108: [AS1] Agent Config Directory Access: Skill reads from agent configuration directories (.claude/, .codex/, .gemini/). These directories may contain API keys, personal settings, and other credentials that the skill has no legitimate need to access.
Remediation: Remove all code or instructions that access agent configuration directories (.claude/, .codex/, .gemini/). If configuration values are needed, pass them explicitly as parameters or environment variables — never read the agent's own config files.
(Agent Snooping (AS1))
[error] 34: [PE3] Credential Access: Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
Remediation: Remove references to credential paths. Use environment variables or secrets managers. For docs, use placeholder paths (e.g., /path/to/config). Never load .env or token files in production code paths.
(Privilege Escalation (PE3))
[error] 34: [PE3] Credential Access: Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
Remediation: Remove references to credential paths. Use environment variables or secrets managers. For docs, use placeholder paths (e.g., /path/to/config). Never load .env or token files in production code paths.
(Privilege Escalation (PE3))
[warning] 49: [RA2] Session Persistence: Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.
Remediation: Remove any persistence mechanisms (cron jobs, startup scripts, state files). Skills should not maintain state across sessions without explicit user consent.
(Rogue Agent (RA2))
🤖 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 @.claude/skills/aws-hardening/SKILL.md at line 3, Soften the absolute AWS
boundary wording in the front-matter description and the skill’s Goal, replacing
claims that Claude Code “cannot directly access” ~/.aws with language that it
reduces direct access or is constrained by the sandbox. Keep the existing Bash
deny-rule behavior and later caveat unchanged.
| **shell-env** (recommended): Export `AWS_PROFILE=<profile>` in the user's shell rc. Simple, standard. Only applies in new shells. Detect their shell and adapt (fish uses `set -gx`, bash/zsh use `export`). | ||
|
|
||
| **wrapper**: A small script at `~/.local/bin/aws` that sets `AWS_PROFILE` then delegates to the real `aws` binary. Works everywhere including subprocesses. But — it shadows the real binary, can break package upgrades, and is an attack surface. Be upfront about this. | ||
|
|
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
nl -ba .claude/skills/aws-hardening/SKILL.md | sed -n '1,120p'Repository: openshift/hypershift
Length of output: 198
🏁 Script executed:
#!/bin/bash
set -euo pipefail
awk 'NR>=1 && NR<=120 { printf "%4d %s\n", NR, $0 }' .claude/skills/aws-hardening/SKILL.mdRepository: openshift/hypershift
Length of output: 9049
Make the scope distinction explicit. The shell-env and wrapper options are not Claude-only: they change the user’s default AWS context in new shells / every aws lookup on PATH, so “Your own AWS access is unaffected” is misleading. Call that out here, or steer users to the SessionStart hook for Claude-only isolation.
🧰 Tools
🪛 SkillSpector (2.3.7)
[error] 108: [AS1] Agent Config Directory Access: Skill reads from agent configuration directories (.claude/, .codex/, .gemini/). These directories may contain API keys, personal settings, and other credentials that the skill has no legitimate need to access.
Remediation: Remove all code or instructions that access agent configuration directories (.claude/, .codex/, .gemini/). If configuration values are needed, pass them explicitly as parameters or environment variables — never read the agent's own config files.
(Agent Snooping (AS1))
[error] 34: [PE3] Credential Access: Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
Remediation: Remove references to credential paths. Use environment variables or secrets managers. For docs, use placeholder paths (e.g., /path/to/config). Never load .env or token files in production code paths.
(Privilege Escalation (PE3))
[error] 34: [PE3] Credential Access: Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
Remediation: Remove references to credential paths. Use environment variables or secrets managers. For docs, use placeholder paths (e.g., /path/to/config). Never load .env or token files in production code paths.
(Privilege Escalation (PE3))
[warning] 49: [RA2] Session Persistence: Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.
Remediation: Remove any persistence mechanisms (cron jobs, startup scripts, state files). Skills should not maintain state across sessions without explicit user consent.
(Rogue Agent (RA2))
🤖 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 @.claude/skills/aws-hardening/SKILL.md around lines 73 - 76, The shell-env
and wrapper guidance must explicitly state that these options alter the user’s
general AWS context, not just Claude sessions. Update the shell-env and wrapper
descriptions to note their broader scope, and direct users seeking Claude-only
isolation to the SessionStart hook.
| **shell-env** (recommended): Export `AWS_PROFILE=<profile>` in the user's shell rc. Simple, standard. Only applies in new shells. Detect their shell and adapt (fish uses `set -gx`, bash/zsh use `export`). | ||
|
|
||
| **wrapper**: A small script at `~/.local/bin/aws` that sets `AWS_PROFILE` then delegates to the real `aws` binary. Works everywhere including subprocesses. But — it shadows the real binary, can break package upgrades, and is an attack surface. Be upfront about this. | ||
|
|
||
| **hook** (recommended for Claude Code-only): A Claude Code `SessionStart` hook that writes `export AWS_PROFILE=<profile>` to the `CLAUDE_ENV_FILE`. This env file is sourced by every subsequent Bash command Claude runs in the session, so all `aws` calls automatically use the read-only profile. Add it to `~/.claude/settings.json` under `hooks.SessionStart` with `"matcher": "startup"`. Only affects Claude Code sessions — zero impact on the user's normal shell or other tools. |
There was a problem hiding this comment.
🔒 Security & Privacy | 🔴 Critical | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
file=".claude/skills/aws-hardening/SKILL.md"
echo "== file info =="
wc -l "$file"
echo
echo "== relevant excerpt =="
sed -n '1,180p' "$file" | cat -n | sed -n '60,95p'
echo
echo "== search for ambient credential handling =="
rg -n "AWS_ACCESS_KEY_ID|AWS_SECRET_ACCESS_KEY|AWS_SESSION_TOKEN|AWS_PROFILE|sts get-caller-identity|get-caller-identity|ambient|credential" "$file" .claude/skills/aws-hardening -SRepository: openshift/hypershift
Length of output: 10823
🌐 Web query:
AWS CLI credential provider precedence environment variables override profile configuration documentation
💡 Result:
The AWS CLI determines which credentials and configuration settings to use based on a strict order of precedence. Settings defined at a higher level of precedence automatically override those defined at lower levels [1][2]. The general order of precedence, from highest to lowest, is as follows: 1. Command line options: Parameters explicitly passed to a command (e.g., --region, --profile, --output) take the highest precedence and override all other methods [1][2][3]. 2. Environment variables: Settings stored in system or user environment variables (e.g., AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_PROFILE) take precedence over values defined in configuration or credentials files [1][2][4][3]. 3. Assume role: Credentials derived from assuming an IAM role via configuration or command [1][2]. 4. Assume role with web identity: Credentials derived from assuming an IAM role using web identity [1][2]. 5. AWS IAM Identity Center: Configurations and credentials managed via aws configure sso and aws sso login [1][2]. 6. Credentials file: The shared credentials file (/.aws/credentials) [1][2]. 7. Custom process: Credentials obtained from an external source or command [1][2]. 8. Configuration file: The AWS CLI configuration file (/.aws/config) [1][2]. 9. Amazon EC2 instance profile credentials: IAM role credentials associated directly with an EC2 instance [1][2]. Regarding your specific query on environment variables and profiles: If you have both the AWS_PROFILE environment variable set and explicit credential environment variables (such as AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY) set, the explicit credential environment variables will override the credentials found within the profile specified by AWS_PROFILE [4]. Similarly, if a specific option (like AWS_REGION) is defined as an environment variable, it overrides the corresponding setting within your chosen profile [3][5].
Citations:
- 1: https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-authentication.html
- 2: https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html
- 3: https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-envvars.html?sc_channel=sm&sc_publisher=REDDIT&sc_outcome=AWS%20Support&linkId=203452251
- 4: https://docs.aws.amazon.com/cli/latest/topic/config-vars.html?highlight=credentials
- 5: https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html
Clear ambient AWS credentials before setting AWS_PROFILE. AWS_PROFILE does not override AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY / AWS_SESSION_TOKEN; if they’re present, Claude can still use broader credentials. Unset or reject them in the shell-env, wrapper, and hook flows, then require aws sts get-caller-identity to match the read-only role before any AWS work.
🧰 Tools
🪛 SkillSpector (2.3.7)
[error] 108: [AS1] Agent Config Directory Access: Skill reads from agent configuration directories (.claude/, .codex/, .gemini/). These directories may contain API keys, personal settings, and other credentials that the skill has no legitimate need to access.
Remediation: Remove all code or instructions that access agent configuration directories (.claude/, .codex/, .gemini/). If configuration values are needed, pass them explicitly as parameters or environment variables — never read the agent's own config files.
(Agent Snooping (AS1))
[error] 34: [PE3] Credential Access: Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
Remediation: Remove references to credential paths. Use environment variables or secrets managers. For docs, use placeholder paths (e.g., /path/to/config). Never load .env or token files in production code paths.
(Privilege Escalation (PE3))
[error] 34: [PE3] Credential Access: Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
Remediation: Remove references to credential paths. Use environment variables or secrets managers. For docs, use placeholder paths (e.g., /path/to/config). Never load .env or token files in production code paths.
(Privilege Escalation (PE3))
[warning] 49: [RA2] Session Persistence: Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.
Remediation: Remove any persistence mechanisms (cron jobs, startup scripts, state files). Skills should not maintain state across sessions without explicit user consent.
(Rogue Agent (RA2))
🤖 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 @.claude/skills/aws-hardening/SKILL.md around lines 73 - 77, Update the
shell-env, wrapper, and Claude Code SessionStart hook guidance to clear or
reject AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and AWS_SESSION_TOKEN before
setting AWS_PROFILE. Require aws sts get-caller-identity to verify the read-only
role before performing any AWS work, and preserve the existing shell-specific,
wrapper, and CLAUDE_ENV_FILE setup instructions.
| The hook script should look like: | ||
| ```bash | ||
| #!/bin/bash | ||
| if [ -n "$CLAUDE_ENV_FILE" ]; then | ||
| echo 'export AWS_PROFILE=<PROFILE_NAME>' >> "$CLAUDE_ENV_FILE" | ||
| fi | ||
| exit 0 | ||
| ``` |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
sed -n '1,140p' .claude/skills/aws-hardening/SKILL.md
rg -n "CLAUDE_ENV_FILE|AWS_PROFILE|SessionStart|hook script|export AWS_PROFILE" .claude/skills/aws-hardening -SRepository: openshift/hypershift
Length of output: 11874
Shell-escape the profile name before writing the hook
Line 83 interpolates the profile name into a shell snippet; if it contains ', the CLAUDE_ENV_FILE entry can break out of the quote and run arbitrary commands when sourced. Restrict profile names to a safe character set or emit the value with shell-safe quoting.
🧰 Tools
🪛 SkillSpector (2.3.7)
[error] 108: [AS1] Agent Config Directory Access: Skill reads from agent configuration directories (.claude/, .codex/, .gemini/). These directories may contain API keys, personal settings, and other credentials that the skill has no legitimate need to access.
Remediation: Remove all code or instructions that access agent configuration directories (.claude/, .codex/, .gemini/). If configuration values are needed, pass them explicitly as parameters or environment variables — never read the agent's own config files.
(Agent Snooping (AS1))
[error] 34: [PE3] Credential Access: Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
Remediation: Remove references to credential paths. Use environment variables or secrets managers. For docs, use placeholder paths (e.g., /path/to/config). Never load .env or token files in production code paths.
(Privilege Escalation (PE3))
[error] 34: [PE3] Credential Access: Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
Remediation: Remove references to credential paths. Use environment variables or secrets managers. For docs, use placeholder paths (e.g., /path/to/config). Never load .env or token files in production code paths.
(Privilege Escalation (PE3))
[warning] 49: [RA2] Session Persistence: Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.
Remediation: Remove any persistence mechanisms (cron jobs, startup scripts, state files). Skills should not maintain state across sessions without explicit user consent.
(Rogue Agent (RA2))
🤖 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 @.claude/skills/aws-hardening/SKILL.md around lines 79 - 86, Update the hook
script’s AWS_PROFILE emission to prevent profile-name values from breaking the
generated shell command. In the hook example, either validate the profile name
against a safe character set before writing it or apply shell-safe quoting that
preserves embedded apostrophes and other special characters; retain the existing
CLAUDE_ENV_FILE guard and successful exit behavior.
| And the settings.json entry: | ||
| ```json | ||
| "SessionStart": [ | ||
| { | ||
| "matcher": "startup", | ||
| "hooks": [ | ||
| { | ||
| "type": "command", | ||
| "command": "/home/<user>/.claude/hooks/aws-profile-env.sh", | ||
| "args": [] | ||
| } | ||
| ] | ||
| } | ||
| ] | ||
| ``` |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== File outline ==\n'
ast-grep outline .claude/skills/aws-hardening/SKILL.md --view expanded || true
printf '\n== Relevant lines ==\n'
nl -ba .claude/skills/aws-hardening/SKILL.md | sed -n '80,110p'
printf '\n== Search for SessionStart / matcher usage ==\n'
rg -n '"matcher"|SessionStart|resume|clear|compact' .claude/skills/aws-hardening/SKILL.md .claude -S || trueRepository: openshift/hypershift
Length of output: 292
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== File excerpt ==\n'
awk 'NR>=80 && NR<=110 {printf "%4d %s\n", NR, $0}' .claude/skills/aws-hardening/SKILL.md
printf '\n== Search for hook docs mentions in repo ==\n'
rg -n "SessionStart|resume|clear|compact|startup" .claude -S || trueRepository: openshift/hypershift
Length of output: 3034
🌐 Web query:
Claude Code hooks SessionStart startup resume clear compact matcher documentation
💡 Result:
In Claude Code, SessionStart is a type of hook that executes when a session is initialized or modified [1][2]. The hook supports four specific matchers that correspond to how the session is initiated or triggered [1][3]: - startup: Fires when a new session begins (e.g., running the claude command) or when using the --init-only flag [1][3]. - resume: Fires when resuming an existing conversation, such as using the --resume, --continue, or /resume commands [3]. - clear: Designed to fire when the /clear command is executed to reset the conversation history [3]. Note that there have been reports of this trigger failing to fire in some versions [3]. - compact: Fires automatically or manually when session compaction occurs [4][3]. These hooks are intended for tasks like loading development context, setting environment variables, or other setup actions required at the start of a session [3]. Because they run on every session, documentation advises keeping these hooks fast and restricting them to command-type hooks [3]. When configuring hooks, matcher fields use bare tool names [5]. If you are troubleshooting, be aware that some users have reported issues with SessionStart hooks not firing correctly on certain triggers, such as /clear [3].
Citations:
- 1: https://code.claude.com/docs/en/hooks
- 2: https://code.claude.com/docs/en/hooks-guide
- 3: SessionStart hooks do not fire on /clear (related to #10373) anthropics/claude-code#34072
- 4: https://codingnomads.com/claude-code-hooks-events-matchers-exit-codes-52614047
- 5: https://code.claude.com/docs/en/tools-reference
Run the profile hook on all SessionStart matchers. startup only covers new sessions; add resume, clear, and compact so AWS_PROFILE is exported across the full Claude Code session lifecycle.
🧰 Tools
🪛 SkillSpector (2.3.7)
[error] 108: [AS1] Agent Config Directory Access: Skill reads from agent configuration directories (.claude/, .codex/, .gemini/). These directories may contain API keys, personal settings, and other credentials that the skill has no legitimate need to access.
Remediation: Remove all code or instructions that access agent configuration directories (.claude/, .codex/, .gemini/). If configuration values are needed, pass them explicitly as parameters or environment variables — never read the agent's own config files.
(Agent Snooping (AS1))
[error] 34: [PE3] Credential Access: Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
Remediation: Remove references to credential paths. Use environment variables or secrets managers. For docs, use placeholder paths (e.g., /path/to/config). Never load .env or token files in production code paths.
(Privilege Escalation (PE3))
[error] 34: [PE3] Credential Access: Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
Remediation: Remove references to credential paths. Use environment variables or secrets managers. For docs, use placeholder paths (e.g., /path/to/config). Never load .env or token files in production code paths.
(Privilege Escalation (PE3))
[warning] 49: [RA2] Session Persistence: Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.
Remediation: Remove any persistence mechanisms (cron jobs, startup scripts, state files). Skills should not maintain state across sessions without explicit user consent.
(Rogue Agent (RA2))
🤖 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 @.claude/skills/aws-hardening/SKILL.md around lines 88 - 102, Update the
settings.json SessionStart configuration to include matchers for startup,
resume, clear, and compact, ensuring each lifecycle event invokes the existing
aws-profile-env.sh command and preserves the current hook configuration.
| **Allow** — read-only AWS CLI patterns: | ||
| ```text | ||
| Bash(aws * describe-*) | ||
| Bash(aws * list-*) | ||
| Bash(aws * get-*) | ||
| Bash(aws sts get-caller-identity) | ||
| Bash(aws s3 ls *) | ||
| Bash(aws s3 cp s3://* *) | ||
| ``` |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
git ls-files '.claude/skills/aws-hardening/SKILL.md'
wc -l .claude/skills/aws-hardening/SKILL.md
sed -n '120,170p' .claude/skills/aws-hardening/SKILL.mdRepository: openshift/hypershift
Length of output: 2481
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
p = Path('.claude/skills/aws-hardening/SKILL.md')
print("exists", p.exists())
if p.exists():
lines = p.read_text().splitlines()
for i in range(128, 155):
if i <= len(lines):
print(f"{i:4d}: {lines[i-1]}")
PYRepository: openshift/hypershift
Length of output: 1637
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n --hidden --glob '.claude/skills/aws-hardening/SKILL.md' --glob '*.md' --glob '*.json' --glob '*.yaml' --glob '*.yml' --glob '*.toml' 'read-only profile|--profile|aws * describe-|aws sts get-caller-identity|Bash\(aws' .claudeRepository: openshift/hypershift
Length of output: 3483
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
p = Path('.claude/skills/aws-hardening/SKILL.md')
text = p.read_text().splitlines()
for idx, line in enumerate(text, 1):
if 'profile' in line.lower() or 'Bash(aws' in line or 'Allow' in line or 'Deny' in line:
print(f"{idx:4d}: {line}")
PYRepository: openshift/hypershift
Length of output: 5143
🏁 Script executed:
#!/bin/bash
set -euo pipefail
sed -n '1,220p' .claude/skills/aws-hardening/SKILL.mdRepository: openshift/hypershift
Length of output: 10885
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
p = Path('.claude/skills/aws-hardening/SKILL.md')
lines = p.read_text().splitlines()
for i, line in enumerate(lines, 1):
if 130 <= i <= 155:
print(f"{i:4d}: {line}")
PYRepository: openshift/hypershift
Length of output: 1335
🌐 Web query:
Claude Code Bash permission patterns wildcard arbitrary arguments profile override aws --profile allowlist semantics
💡 Result:
Claude Code permission and configuration semantics are summarized as follows: Bash Permission Patterns and Wildcards Bash permission rules support glob patterns using the * wildcard [1][2]. These rules apply to the entire shell command string [2]. - Wildcards can appear at any position (beginning, middle, or end) [2][3]. - A single * matches any sequence of characters, including spaces [2]. - When * is used at the end of a command with a preceding space (e.g., Bash(ls *)), it enforces a word boundary, requiring the prefix to be followed by a space or end-of-string [2]. - Bash commands are evaluated against these rules by analyzing the command string [2][4]. For compound commands (e.g., cmd1 && cmd2; cmd1 | cmd2), Claude Code parses and evaluates each segment independently against the allowlist and denylist [4]. AWS Profile and Configuration Claude Code does not have a native, internal "aws --profile allowlist" mechanism. Instead, it relies on standard environment variable behavior and AWS CLI/SDK credential resolution [5]. - Credentials: Claude Code typically uses the AWS_PROFILE environment variable to determine which credentials to use for Bedrock API calls or shell commands executed within the environment [5][6]. - Profile Overrides: To use different profiles for Claude Code (e.g., for Bedrock API access) and for executed shell commands, users can set the AWS_PROFILE variable globally in the shell or via Claude Code settings [5][6]. - Advanced Control: If separate profiles are required for Claude Code (Bedrock) vs. the shell environment, users may need to define specific wrappers or use environment-level configurations. There is no explicit "allowlist" for AWS profiles within Claude Code; access is governed by the underlying IAM identity resolved from the environment [5]. Key Security Note The tool permission allowlist evaluates command strings. Users should avoid relying on settings to act as a definitive security boundary in unmanaged environments, as users with access to the machine can override settings or use flags like --dangerously-skip-permissions [7][8]. Always review permissions in the /permissions interface to verify effective rules [8].
Citations:
- 1: https://code.claude.com/docs/en/permissions
- 2: https://code.claude.com/docs/en/permissions.md
- 3: [DOCS] Documentation Contradiction: Bash Permission Matching Logic (Prefix vs Wildcard) anthropics/claude-code#18961
- 4: Chained bash commands silently bypass the tool permission allowlist anthropics/claude-code#36637
- 5: https://code.claude.com/docs/en/claude-platform-on-aws.md
- 6: Feature request: allow to set AWS profile anthropics/claude-code#148
- 7: https://code.claude.com/docs/en/permission-modes?_rsc=4Vrbm42DZc3Y7r9j
- 8: https://code.claude.com/docs/en/server-managed-settings.md
🌐 Web query:
site:docs.anthropic.com Claude Code Bash permission patterns wildcard command arguments profile
💡 Result:
Claude Code manages bash command permissions by enforcing a "strict read-only" environment by default [1]. When the agent needs to perform actions beyond reading, such as editing files, running tests, or executing shell commands, it requests explicit permission from the user [1]. There is no native mechanism in Claude Code that allows users to pre-define "permission patterns" (e.g., allow-lists for specific command arguments or wildcard-based command filtering) in a configuration profile. Instead, the security model relies on real-time interactive confirmation: 1. Dynamic Permission Prompting: When Claude Code proposes a command to execute, it surfaces the command for human review before execution [1]. 2. Global Overrides: Advanced users can use CLI flags to adjust behavior, such as --dangerously-skip-permissions, which bypasses these interactive prompts [2]. However, this is a global setting for the session, not a granular pattern-based filter. 3. Command Hooks: While you can configure hooks to execute shell commands, these operate with your full user permissions and are intended for automation rather than serving as a restrictive permission-filtering layer [3]. If you are concerned about specific command arguments or wildcards, the best practice is to review the proposed commands in the terminal interface as they appear, rather than attempting to configure a restrictive policy profile within the tool itself. [2][1]
Citations:
- 1: https://docs.anthropic.com/en/docs/claude-code/security
- 2: https://docs.anthropic.com/en/docs/claude-code/cli-reference
- 3: https://docs.anthropic.com/en/docs/claude-code/hooks
🌐 Web query:
Claude Code Bash permission patterns wildcard arbitrary arguments aws --profile docs
💡 Result:
Claude Code manages shell execution through a granular permission system that uses glob-style wildcard patterns [1][2]. Bash Permission Patterns and Wildcards Bash permission rules follow the format Bash(pattern) [3][4]. The asterisk (*) serves as a wildcard that matches any sequence of characters, including spaces, allowing it to span multiple arguments [1][5]. - Word Boundaries: When a pattern includes a space before the asterisk (e.g., Bash(ls *)), it enforces a word boundary, meaning the prefix must be followed by a space or end-of-string [1][6]. This prevents inadvertent matches (e.g., Bash(ls *) matches ls -la but not lsof) [1][5]. - Placement: Wildcards can be placed at the beginning, middle, or end of a command string [1]. - Behavioral Notes: - Deny rules always take precedence over allow rules, regardless of specificity [5][3]. - Compound commands (e.g., cmd1 && cmd2) are evaluated individually; you cannot use a wildcard to bypass restrictions on chained commands [5][2]. - Quoted arguments can sometimes cause matching issues; if a pattern fails to match despite appearing correct, ensure the command does not rely on quoted arguments that the permission parser may reject [7]. - Recent versions (v2.1.72+) have updated behavior so that patterns like Bash(cmd *) correctly match both the bare command (with no arguments) and commands with arguments [6]. AWS Profile Configuration Claude Code respects standard AWS CLI environment variables [8][9]. To ensure Claude Code uses a specific AWS profile: - Environment Variables: Set the AWS_PROFILE environment variable in your terminal session before launching Claude Code (e.g., export AWS_PROFILE=your-profile-name) [8][9]. Claude Code will pick this up for all AWS CLI commands it executes [9]. - Configuration: You can add this export to your shell configuration file (e.g., ~/.zshrc or ~/.bashrc) to ensure it is always set [9]. - Precedence: If you need Claude Code to use a different profile than your local development environment, some users suggest using custom environment variable wrappers, though standard AWS_PROFILE remains the primary mechanism for the underlying AWS SDK [8]. For robust security, always combine these permission patterns with a 'deny' list for sensitive paths (like.env or credentials) and use the most restrictive patterns possible [5][3]. If rules prove insufficient for complex logic, you can use PreToolUse hooks to implement custom validation [5].
Citations:
- 1: https://code.claude.com/docs/en/permissions.md
- 2: https://allahabadi.dev/blogs/ai/claude-code-permissions-settings-explained/
- 3: https://claudecodesessions.com/claude-code-permission-tuning/
- 4: https://code.claude.com/docs/en/settings?_rsc=crtel
- 5: https://getclaudekit.com/blog/guide/mechanics/permission-model
- 6: [BUG] Bash(foo *) wildcard does not match bare command with no arguments, contrary to docs anthropics/claude-code#26229
- 7: [BUG] Bash permission wildcard
*rejects commands with quoted arguments anthropics/claude-code#23670 - 8: Feature request: allow to set AWS profile anthropics/claude-code#148
- 9: https://www.threeportkeys.com/posts/aws-cli-claude-code-cloudwatch/
Bind the allowlist to the read-only profile. Bash(aws * describe-*)/list-*/get-* still match commands that pass --profile <other-profile>, so this optional layer can be used with broader credentials. If you keep it, include the guarded profile in each allowed shape; otherwise drop the allowlist and rely on the IAM role.
🧰 Tools
🪛 SkillSpector (2.3.7)
[error] 108: [AS1] Agent Config Directory Access: Skill reads from agent configuration directories (.claude/, .codex/, .gemini/). These directories may contain API keys, personal settings, and other credentials that the skill has no legitimate need to access.
Remediation: Remove all code or instructions that access agent configuration directories (.claude/, .codex/, .gemini/). If configuration values are needed, pass them explicitly as parameters or environment variables — never read the agent's own config files.
(Agent Snooping (AS1))
[error] 34: [PE3] Credential Access: Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
Remediation: Remove references to credential paths. Use environment variables or secrets managers. For docs, use placeholder paths (e.g., /path/to/config). Never load .env or token files in production code paths.
(Privilege Escalation (PE3))
[error] 34: [PE3] Credential Access: Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
Remediation: Remove references to credential paths. Use environment variables or secrets managers. For docs, use placeholder paths (e.g., /path/to/config). Never load .env or token files in production code paths.
(Privilege Escalation (PE3))
[warning] 49: [RA2] Session Persistence: Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.
Remediation: Remove any persistence mechanisms (cron jobs, startup scripts, state files). Skills should not maintain state across sessions without explicit user consent.
(Rogue Agent (RA2))
🤖 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 @.claude/skills/aws-hardening/SKILL.md around lines 139 - 147, Update the
read-only AWS CLI allowlist near the “Allow” section so every permitted pattern
requires the intended guarded profile, including describe, list, get, sts, and
s3 commands; alternatively remove this allowlist and rely on the IAM role.
Ensure commands specifying a different profile cannot match these entries.
| Bash(aws s3 ls *) | ||
| Bash(aws s3 cp s3://* *) |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
set -euo pipefail
file='.claude/skills/aws-hardening/SKILL.md'
wc -l "$file"
sed -n '120,180p' "$file" | cat -nRepository: openshift/hypershift
Length of output: 3144
Tighten the S3 copy allowlist
Bash(aws s3 cp s3://* *) is too broad: it allows downloading arbitrary S3 objects to arbitrary local paths, including project, config, or hook files. Constrain the bucket/prefix and destination, or require explicit approval for this pattern.
🧰 Tools
🪛 SkillSpector (2.3.7)
[error] 108: [AS1] Agent Config Directory Access: Skill reads from agent configuration directories (.claude/, .codex/, .gemini/). These directories may contain API keys, personal settings, and other credentials that the skill has no legitimate need to access.
Remediation: Remove all code or instructions that access agent configuration directories (.claude/, .codex/, .gemini/). If configuration values are needed, pass them explicitly as parameters or environment variables — never read the agent's own config files.
(Agent Snooping (AS1))
[error] 34: [PE3] Credential Access: Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
Remediation: Remove references to credential paths. Use environment variables or secrets managers. For docs, use placeholder paths (e.g., /path/to/config). Never load .env or token files in production code paths.
(Privilege Escalation (PE3))
[error] 34: [PE3] Credential Access: Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
Remediation: Remove references to credential paths. Use environment variables or secrets managers. For docs, use placeholder paths (e.g., /path/to/config). Never load .env or token files in production code paths.
(Privilege Escalation (PE3))
[warning] 49: [RA2] Session Persistence: Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.
Remediation: Remove any persistence mechanisms (cron jobs, startup scripts, state files). Skills should not maintain state across sessions without explicit user consent.
(Rogue Agent (RA2))
🤖 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 @.claude/skills/aws-hardening/SKILL.md around lines 145 - 146, Tighten the S3
command allowlist by replacing the broad Bash(aws s3 cp s3://* *) pattern with
an explicitly approved bucket/prefix and safe destination constraint, or remove
it and require explicit approval for such copies. Keep Bash(aws s3 ls *)
unchanged.
aws-hardening — Claude Code Skill
A Claude Code skill that interactively guides users through hardening AWS access for AI coding agents. Invoked via
/aws-hardening.The Problem
AI coding agents like Claude Code can run
awsCLI commands. By default they inherit whatever AWS credentials the user has configured — often long-lived access keys with broad permissions. This means the agent could accidentally (or through prompt injection) run destructive commands like deleting EC2 instances, S3 buckets, or IAM resources. There is no built-in separation between the human operator's AWS access and the AI agent's.The Solution: Defense in Depth
This skill sets up up to six layers of defense. Each layer is independent — if one is bypassed, the others still hold.
Layer 1: Read-Only IAM Role
The foundation. An IAM role (e.g.
AIAgentReadOnly) is created with:ReadOnlyAccesspolicy — the primary security control. This policy only grants read actions (Describe, Get, List, Head) across all AWS services. It does not grant any write, create, or delete permissions.ec2:Delete*,ec2:Terminate*,s3:DeleteObject*, IAM mutations,cloudtrail:StopLogging,organizations:*,account:*). Even if someone later attaches an additional policy granting write access, the deny overrides it for these actions.Claude assumes this role via STS
AssumeRole, getting temporary 1-hour credentials instead of long-lived access keys. The bundledreferences/create-readonly-role.shscript creates this role idempotently.Layer 2: AWS CLI Profile
A named profile is added to
~/.aws/configthat automatically assumes the read-only role:Any
awscommand using this profile gets scoped to the read-only role's permissions. The CLI handles theAssumeRolecall and credential caching transparently.Layer 3: Profile Enforcement
The skill offers three strategies to make Claude always use the read-only profile, explaining the tradeoffs of each before the user chooses:
AWS_PROFILE=ai-agentin shell rc (fish/zsh/bash)~/.local/bin/awsscript that setsAWS_PROFILEthenexecs the realawsbinarybrew upgrade awscli). Attack surface if~/.local/binis writable.SessionStarthook writesexport AWS_PROFILE=ai-agenttoCLAUDE_ENV_FILE, which is sourced by every subsequent Bash commandawscommand inherits the profile automatically.Layer 4: Credential File Deny Rules
Claude Code permission rules in
~/.claude/settings.jsonthat block direct access to~/.aws:File tools:
Read(~/.aws/**)— blocks Claude's Read tool and recognized file commandsEdit(~/.aws/**)— blocks Edit toolWrite(~/.aws/**)— blocks Write toolBash access:
cat,less,head,tail,greptargeting~/.awsClaude can still run
awsCLI commands — the CLI reads~/.awsinternally. But Claude itself cannot read, exfiltrate, or inspect the raw credential files.These rules are permanent. If the skill needs to modify
~/.awson re-run, it asks the user to make the edit themselves or relax the rules via/permissions.Layer 5: CLAUDE.md Behavioral Guardrail
A line is appended to the user's global
~/.claude/CLAUDE.mdusing RFC 2119 keywords:This is a behavioral layer — it doesn't enforce anything technically, but it shapes what Claude generates. Even if the environment variable is missing or a new shell doesn't have it, Claude will still explicitly pass
--profile.Layer 6 (Optional): Fine-Grained AWS CLI Command Permissions
Claude Code allow/deny rules that whitelist read-only AWS CLI patterns and blacklist destructive ones:
Allow — read-only patterns:
Deny — write/destructive patterns:
Skill Structure
How it Works
/aws-hardeningPrerequisites
jq(for merging deny rules into existing~/.claude/settings.json)Test Plan
/aws-hardeningin a new Claude Code session — verify onboarding walkthroughcreate-readonly-role.shfrom references when no role exists~/.claude/settings.jsoncat ~/.aws/credentialsafter deny rules/aws-hardening— verify status check worksai-agentanddefault🤖 Generated with Claude Code
Summary by CodeRabbit