Skip to content

feat: add aws-hardening Claude Code skill#8962

Draft
devguyio wants to merge 1 commit into
openshift:mainfrom
devguyio:aws-hardening-skill
Draft

feat: add aws-hardening Claude Code skill#8962
devguyio wants to merge 1 commit into
openshift:mainfrom
devguyio:aws-hardening-skill

Conversation

@devguyio

@devguyio devguyio commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

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 aws CLI 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:

  • AWS managed ReadOnlyAccess policy — 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.
  • A deny guardrail inline policy — defense-in-depth against policy drift. Explicitly denies common destructive actions (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.
  • Account-scoped trust policy — any IAM identity in the account can assume the role, so it works for the whole team regardless of whether they use IAM users, SSO, or role chaining.

Claude assumes this role via STS AssumeRole, getting temporary 1-hour credentials instead of long-lived access keys. The bundled references/create-readonly-role.sh script creates this role idempotently.

Layer 2: AWS CLI Profile

A named profile is added to ~/.aws/config that automatically assumes the read-only role:

[profile ai-agent]
role_arn = arn:aws:iam::123456789012:role/AIAgentReadOnly
source_profile = default
duration_seconds = 3600
region = us-east-1

Any aws command using this profile gets scoped to the read-only role's permissions. The CLI handles the AssumeRole call 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:

Strategy How it works Scope Tradeoffs
shell-env Exports AWS_PROFILE=ai-agent in shell rc (fish/zsh/bash) All new shells Simple, standard. Only applies in new shells — need to source or open new terminal.
wrapper Installs ~/.local/bin/aws script that sets AWS_PROFILE then execs the real aws binary All contexts including subprocesses Shadows the real binary. Can break package upgrades (brew upgrade awscli). Attack surface if ~/.local/bin is writable.
hook (recommended for Claude Code) Claude Code SessionStart hook writes export AWS_PROFILE=ai-agent to CLAUDE_ENV_FILE, which is sourced by every subsequent Bash command Claude Code sessions only Cleanest option. Zero impact on the user's normal shell. Every aws command inherits the profile automatically.

Layer 4: Credential File Deny Rules

Claude Code permission rules in ~/.claude/settings.json that block direct access to ~/.aws:

File tools:

  • Read(~/.aws/**)— blocks Claude's Read tool and recognized file commands
  • Edit(~/.aws/**) — blocks Edit tool
  • Write(~/.aws/**) — blocks Write tool

Bash access:

  • Deny patterns for cat, less, head, tail, grep targeting ~/.aws

Claude can still run aws CLI commands — the CLI reads ~/.aws internally. But Claude itself cannot read, exfiltrate, or inspect the raw credential files.

Honest limitation: These Bash deny rules are a speed bump, not a complete barrier. Commands like sed, awk, python3 -c, or shell builtins can still read files. The IAM role (Layer 1) is the real security enforcement. The deny rules reduce the surface area of accidental credential exposure.

These rules are permanent. If the skill needs to modify ~/.aws on 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.md using RFC 2119 keywords:

- You MUST include `--profile ai-agent` in every `aws` CLI invocation.
  You MUST NOT run `aws` commands without an explicit `--profile` flag.

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:

Bash(aws * describe-*), Bash(aws * list-*), Bash(aws * get-*)
Bash(aws sts get-caller-identity)
Bash(aws s3 ls *), Bash(aws s3 cp s3://* *)

Deny — write/destructive patterns:

Bash(aws * create-*), Bash(aws * delete-*), Bash(aws * terminate-*)
Bash(aws * put-*), Bash(aws * update-*), Bash(aws * modify-*)
Bash(aws * run-*), Bash(aws * start-*), Bash(aws * stop-*)
Bash(aws * reboot-*), Bash(aws * attach-*), Bash(aws * detach-*)
Bash(aws * remove-*), Bash(aws s3 rm *), Bash(aws s3 mv *)

Honest limitation: Bash permission patterns are fragile — argument reordering, variables, and compound commands can bypass them. This is belt-and-suspenders on top of the IAM role, not a standalone security boundary. The allow/deny lists are tailored interactively based on the user's actual AWS usage.


Skill Structure

.claude/skills/aws-hardening/
├── SKILL.md                              # Skill instructions (what Claude follows)
└── references/
    └── create-readonly-role.sh           # IAM role creation script (idempotent)

How it Works

  1. User invokes /aws-hardening
  2. Claude presents a full onboarding walkthrough explaining all layers
  3. User confirms, then Claude walks through each step — discovering the user's environment, asking questions, explaining tradeoffs, confirming before changes
  4. On subsequent invocations, supports status checks and profile switching

Prerequisites

  • AWS CLI configured with at least one working profile
  • jq (for merging deny rules into existing ~/.claude/settings.json)

Test Plan

  • Invoke /aws-hardening in a new Claude Code session — verify onboarding walkthrough
  • Verify the skill reads create-readonly-role.sh from references when no role exists
  • Walk through profile creation and strategy selection (all 3 strategies)
  • Verify deny rules are applied to ~/.claude/settings.json
  • Confirm Claude Code blocks cat ~/.aws/credentials after deny rules
  • Verify CLAUDE.md instruction is appended correctly
  • Test optional Layer 6 fine-grained command permissions
  • Re-invoke /aws-hardening — verify status check works
  • Test profile switching between ai-agent and default

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added an “AWS Hardening” guide for Claude Code with defense-in-depth setup, including a read-only STS-assumed role and guardrails that block access to local AWS credential/config caches.
    • Added selectable enforcement options to ensure all AWS CLI commands use the dedicated profile, plus optional command allow/deny patterns and a switching checklist.
    • Added a setup script to create/update the read-only role and apply an inline deny policy against destructive/high-risk actions.

@openshift-merge-bot

Copy link
Copy Markdown
Contributor

Pipeline controller notification
This repo is configured to use the pipeline controller. Second-stage tests will be triggered either automatically or after lgtm label is added, depending on the repository configuration. The pipeline controller will automatically detect which contexts are required and will utilize /test Prow commands to trigger the second stage.

For optional jobs, comment /test ? to see a list of all defined jobs. To trigger manually all jobs from second stage use /pipeline required command.

This repository is configured in: LGTM mode

@devguyio devguyio marked this pull request as draft July 8, 2026 12:39
@openshift-ci openshift-ci Bot added the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Jul 8, 2026
@openshift-ci openshift-ci Bot requested review from cblecker and muraee July 8, 2026 12:40
@openshift-ci openshift-ci Bot added area/ai Indicates the PR includes changes related to AI - Claude agents, Cursor rules, etc. and removed do-not-merge/needs-area labels Jul 8, 2026
@openshift-ci

openshift-ci Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

[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

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@openshift-ci openshift-ci Bot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Jul 8, 2026
@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This 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 AWS_PROFILE controls, deny rules for direct ~/.aws access, an aws --profile requirement in CLAUDE.md, optional command allow/deny patterns, and status guidance. The script creates or updates the IAM role, attaches ReadOnlyAccess, and applies an inline DenyDestructive policy.

Suggested reviewers: muraee

🚥 Pre-merge checks | ✅ 11
✅ Passed checks (11 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding a Claude Code aws-hardening skill.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Stable And Deterministic Test Names ✅ Passed The PR only adds a skill markdown and shell script; neither contains Ginkgo test definitions or dynamic test titles.
Test Structure And Quality ✅ Passed PASS: The PR only adds a Claude skill markdown and a shell helper; no Ginkgo tests or cluster-interaction test code were changed.
Topology-Aware Scheduling Compatibility ✅ Passed Only a skill doc and an AWS IAM helper script changed; no deployment manifests, operator code, or controllers were added, so topology-aware scheduling constraints aren’t applicable.
Ipv6 And Disconnected Network Test Compatibility ✅ Passed No Ginkgo e2e tests were added or modified; the diff only adds a skill doc and a Bash helper script.
No-Weak-Crypto ✅ Passed No MD5/SHA1/DES/RC4/3DES/Blowfish/ECB, custom crypto, or secret/token comparisons appear in the new skill docs or shell script.
Container-Privileges ✅ Passed PR only changes a markdown skill and shell script; no container/K8s manifests or privilege settings appear in the diff.
No-Sensitive-Data-In-Logs ✅ Passed No passwords, tokens, API keys, PII, session IDs, or customer data are logged; the script only echoes account/role status, and the skill file has no runtime logging.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@hypershift-jira-solve-ci

Copy link
Copy Markdown
Contributor

I now have the complete picture. Here is the analysis:

Test Failure Analysis Complete

Job Information

  • Prow Jobs: pull-ci-openshift-hypershift-main-okd-scos-images, pull-ci-openshift-hypershift-main-images, pull-ci-openshift-hypershift-main-verify-deps
  • Build IDs: 2074835758631358464, 2074835758597804032, 2074835758664912896
  • PR: feat: add aws-hardening Claude Code skill #8962 — "feat: add aws-hardening Claude Code skill"
  • Result: All three jobs ABORTED (not failed)

Test Failure Analysis

Error

Entrypoint received interrupt: terminated
Status: aborted — "Aborted by trigger plugin."

Summary

All three jobs were intentionally aborted by the Prow trigger plugin because the PR author (devguyio) converted the PR to draft status 9 seconds after the jobs were created. The Prow trigger plugin automatically cancels in-flight presubmit jobs when a PR is converted to draft, since draft PRs should not consume CI resources. The jobs ran for approximately 3 seconds before receiving SIGTERM. No test code was executed and no build steps completed — this is not a product or test bug.

Root Cause

The root cause is a deliberate user action, not a code or infrastructure failure.

Sequence of events:

  1. 12:39:28Z — PR feat: add aws-hardening Claude Code skill #8962 was opened (or pushed to), triggering three presubmit Prow jobs
  2. 12:39:37Z — The PR author devguyio converted the PR to draft status
  3. 12:39:41Z — The Prow trigger plugin detected the draft conversion and sent SIGTERM to all three running job pods, aborting them with the message "Aborted by trigger plugin."
  4. 12:39:42Zopenshift-ci[bot] applied the do-not-merge/work-in-progress label to the PR

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 (isDraft: true) and the jobs were never re-triggered.

The PR only modifies two non-code files (.claude/skills/aws-hardening/SKILL.md and .claude/skills/aws-hardening/references/create-readonly-role.sh), so when the PR is marked ready for review, the image-build and verify-deps jobs will likely pass since no Go source code or dependencies were changed.

Recommendations
  1. No action needed for these failures — They are expected aborts from converting the PR to draft, not real test or build failures.
  2. When ready for review: Mark the PR as "Ready for review" to exit draft status. This will allow the Prow trigger plugin to re-trigger the presubmit jobs, or use /retest to manually trigger them.
  3. The jobs will likely pass: The PR only adds Claude Code skill files (.claude/skills/aws-hardening/), which do not affect Go builds, image builds, or dependency verification.
Evidence
Evidence Detail
Job state All three: "state": "aborted" in prowjob.json
Abort reason "description": "Aborted by trigger plugin."
Build duration ~3 seconds (started 12:39:38Z, terminated 12:39:41Z)
Draft conversion devguyioconvert_to_draft at 12:39:37Z (GitHub events API)
WIP label do-not-merge/work-in-progress applied at 12:39:42Z
PR is draft isDraft: true confirmed via gh pr view
Build log "Entrypoint received interrupt: terminated" — no build steps completed
Sidecar log "Received an interrupt: terminated, cancelling..."
Files changed Only .claude/skills/aws-hardening/SKILL.md and .claude/skills/aws-hardening/references/create-readonly-role.sh — no Go code

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (2)
.claude/skills/aws-hardening/references/create-readonly-role.sh (2)

40-47: 🔒 Security & Privacy | 🔵 Trivial

Trust 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 in SKILL.md, but since ReadOnlyAccess grants broad read across all services, any account principal can use this role to read (and potentially exfiltrate) data. If least-privilege is desired, scope the Principal to the specific user/role that Claude Code runs as, and/or add a Condition (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 value

Deny guardrail omits self-protection and several destructive services.

SKILL.md advertises 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 like rds:Delete*, lambda:Delete*, dynamodb:DeleteTable, and cloudformation:DeleteStack. Since ReadOnlyAccess already 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

📥 Commits

Reviewing files that changed from the base of the PR and between 8b5103a and 6ff7c0d.

📒 Files selected for processing (2)
  • .claude/skills/aws-hardening/SKILL.md
  • .claude/skills/aws-hardening/references/create-readonly-role.sh

Comment thread .claude/skills/aws-hardening/references/create-readonly-role.sh
Comment thread .claude/skills/aws-hardening/SKILL.md
Comment thread .claude/skills/aws-hardening/SKILL.md Outdated
Comment on lines +123 to +125
```
- You MUST include `--profile ai-agent` in every `aws` CLI invocation. You MUST NOT run `aws` commands without an explicit `--profile` flag.
```

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

Suggested change
```
- 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

@devguyio devguyio force-pushed the aws-hardening-skill branch from 6ff7c0d to 81bb0e1 Compare July 8, 2026 12:51

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

📥 Commits

Reviewing files that changed from the base of the PR and between 6ff7c0d and 81bb0e1.

📒 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

Comment thread .claude/skills/aws-hardening/SKILL.md Outdated
Comment on lines +140 to +148
```
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://* *)
```

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 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

Comment thread .claude/skills/aws-hardening/SKILL.md Outdated
@codecov

codecov Bot commented Jul 8, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 43.78%. Comparing base (8b5103a) to head (7a1a709).
⚠️ Report is 38 commits behind head on main.

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

Flag Coverage Δ
cmd-support 37.43% <ø> (+0.30%) ⬆️
cpo-hostedcontrolplane 45.91% <ø> (+0.40%) ⬆️
cpo-other 45.11% <ø> (+0.01%) ⬆️
hypershift-operator 54.03% <ø> (+0.38%) ⬆️
other 32.08% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@devguyio devguyio force-pushed the aws-hardening-skill branch from 81bb0e1 to 172ef77 Compare July 8, 2026 13:09
@cblecker

cblecker commented Jul 9, 2026

Copy link
Copy Markdown
Member

/uncc

@openshift-ci openshift-ci Bot removed the request for review from cblecker July 9, 2026 00:31
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>
@devguyio devguyio force-pushed the aws-hardening-skill branch from 172ef77 to 7a1a709 Compare July 13, 2026 08:26

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

📥 Commits

Reviewing files that changed from the base of the PR and between 172ef77 and 7a1a709.

📒 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."

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔒 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/**' || true

Repository: 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.

Comment on lines +73 to +76
**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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 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.md

Repository: 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.

Comment on lines +73 to +77
**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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔒 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 -S

Repository: 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:


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.

Comment on lines +79 to +86
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
```

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔒 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 -S

Repository: 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.

Comment on lines +88 to +102
And the settings.json entry:
```json
"SessionStart": [
{
"matcher": "startup",
"hooks": [
{
"type": "command",
"command": "/home/<user>/.claude/hooks/aws-profile-env.sh",
"args": []
}
]
}
]
```

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔒 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 || true

Repository: 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 || true

Repository: 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:


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.

Comment on lines +139 to +147
**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://* *)
```

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔒 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.md

Repository: 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]}")
PY

Repository: 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' .claude

Repository: 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}")
PY

Repository: openshift/hypershift

Length of output: 5143


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,220p' .claude/skills/aws-hardening/SKILL.md

Repository: 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}")
PY

Repository: 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:


🌐 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:


🌐 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:


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.

Comment on lines +145 to +146
Bash(aws s3 ls *)
Bash(aws s3 cp s3://* *)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔒 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 -n

Repository: 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.

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

Labels

approved Indicates a PR has been approved by an approver from all required OWNERS files. area/ai Indicates the PR includes changes related to AI - Claude agents, Cursor rules, etc. do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants