Anthropic Claude Hardening Guide
AI platform security hardening for Claude API, Console, SSO, workspace isolation, and admin controls
Overview
Anthropic Claude is an AI assistant platform serving organizations through both a web-based chat interface (claude.ai) and a developer API (api.anthropic.com). As AI adoption accelerates, properly securing Claude deployments is critical to prevent API key compromise, enforce data residency requirements, manage costs, and maintain compliance. Anthropic provides a comprehensive Admin API with 25 endpoints for programmatic organization management, alongside a web Console for GUI-based administration.
Intended Audience
- Security engineers managing AI tools and API integrations
- IT administrators configuring Claude for enterprise teams
- GRC professionals assessing AI compliance posture
- Third-party risk managers evaluating AI platform controls
How to Use This Guide
- L1 (Baseline): Essential controls for all organizations using Claude
- L2 (Hardened): Enhanced controls for security-sensitive environments
- L3 (Maximum Security): Strictest controls for regulated industries
Scope
This guide covers Anthropic Claude security configurations including authentication (SSO/SCIM), organization role management, API key lifecycle, workspace segmentation, data residency, usage monitoring, integration security, and comprehensive Claude Code and Cowork enterprise controls — MDM/server-managed settings (including drop-in directory and OS-level policy delivery), permission restrictions, MCP server governance, developer analytics, bash sandbox isolation (Seatbelt/bubblewrap), hook and plugin supply chain security, prompt injection and rules file attack defense, CI/CD pipeline hardening (harden-runner, security review actions), external sandbox tooling (nono, NVIDIA OpenShell), and Cowork collaborative session governance. Model behavior configuration (system prompts, safety settings) is out of scope. This guide applies to Claude API, Claude Team, and Claude Enterprise plans.
Table of Contents
- Authentication & Access Controls
- API Key Management
- Workspace Security
- Data Security & Privacy
- Monitoring & Usage Controls
- Third-Party Integration Security
- Claude Code Enterprise Controls
- 7.1 Deploy Managed Settings via MDM
- 7.2 Restrict Claude Code Permissions and Tools
- 7.3 Control MCP Server Access
- 7.4 Monitor Claude Code Developer Metrics
- 7.5 Enforce Bash Sandbox Isolation
- 7.6 Lock Down Hooks and Plugins
- 7.7 Defend Against Prompt Injection and Rules File Attacks
- 7.8 Harden Claude Code in CI/CD Pipelines
- 7.9 Deploy External Sandbox Tooling
- 7.10 Govern Claude Cowork and Collaborative Sessions
- Compliance Quick Reference
1. Authentication & Access Controls
1.1 Enforce Single Sign-On
Profile Level: L1 (Baseline)
| Framework | Control |
|---|---|
| CIS Controls | 6.3, 12.5 |
| NIST 800-53 | IA-2, IA-8 |
Description
Configure SAML 2.0 or OIDC-based SSO to authenticate Claude users through your corporate identity provider. Anthropic integrates SSO via WorkOS, supporting domain verification and just-in-time provisioning.
Rationale
Why This Matters:
- Centralizes authentication and user lifecycle management
- Enables MFA enforcement through your IdP’s Conditional Access policies
- Eliminates standalone Claude passwords and reduces credential sprawl
- Automatic deprovisioning when users leave the organization
Attack Prevented: Credential theft, unauthorized access, orphaned accounts
Prerequisites
- Claude Team or Enterprise subscription
- SAML 2.0 or OIDC compatible identity provider (Okta, Azure AD, OneLogin, Google Workspace)
- Organization Admin access to Claude Console
- Domain ownership for domain verification
ClickOps Implementation
Step 1: Access Identity & Access Settings
- Navigate to: console.anthropic.com → Settings → Identity & Access
- Click Configure SSO
Step 2: Configure SSO via WorkOS
- Select your IdP type (SAML 2.0 or OIDC)
- Enter Identity Provider details:
- SSO URL: Your IdP’s SSO endpoint
- Entity ID / Issuer: IdP entity ID
- Certificate: X.509 certificate from IdP (for SAML)
- Download Claude’s Service Provider metadata for IdP configuration
- Map required user attributes (email, name)
Step 3: Configure Your IdP (Example: Okta)
- In Okta Admin: Applications → Create App Integration → SAML 2.0
- Enter Claude’s ACS URL and Entity ID from Step 2
- Configure attribute statements:
- email → user.email
- name → user.displayName
- Assign users/groups
Step 4: Verify Domain and Enforce SSO
- Complete domain verification (DNS TXT record)
- Enable Require SSO for all users
- Test login before full enforcement
Time to Complete: ~30 minutes
Code Implementation
Code Pack: API Script
# Validate SSO enforcement by listing org members and checking for
# users who may not have authenticated via SSO.
# Note: The Admin API does not expose SSO status directly.
# This audit lists all members so you can cross-reference with your IdP.
info "Listing all organization members for SSO cross-reference audit..."
MEMBERS=$(anthropic_list_all "/v1/organizations/users") || {
fail "1.1 Failed to list organization users"
summary; exit 0
}
MEMBER_COUNT=$(echo "${MEMBERS}" | jq 'length')
info "Found ${MEMBER_COUNT} organization members"
echo "${MEMBERS}" | jq -r '.[] | "\(.name)\t\(.email)\t\(.role)"' | \
column -t -s $'\t' -N "NAME,EMAIL,ROLE"
pass "1.1 Member list retrieved — cross-reference with IdP to verify SSO coverage"
Validation & Testing
- Attempt login without SSO — should be redirected to IdP
- Complete SSO login — should succeed and land in Claude
- Remove user from IdP group — should lose Claude access
- Cross-reference org member list (API) with IdP directory
Expected result: All users authenticate via SSO; no standalone password logins
Monitoring & Maintenance
Ongoing monitoring:
- Monitor IdP for failed authentication attempts to Claude
- Review pending invites monthly for unauthorized additions
Maintenance schedule:
- Monthly: Review org member list vs IdP directory
- Quarterly: Rotate SSO certificates before expiration
- Annually: Re-verify domain ownership
Operational Impact
| Aspect | Impact Level | Details |
|---|---|---|
| User Experience | Low | Users authenticate via familiar IdP login |
| System Performance | None | No performance impact |
| Maintenance Burden | Low | Standard IdP maintenance applies |
| Rollback Difficulty | Easy | Disable SSO enforcement in Console |
Compliance Mappings
| Framework | Control ID | Control Description |
|---|---|---|
| SOC 2 | CC6.1 | Logical access security over protected information assets |
| NIST 800-53 | IA-2, IA-8 | Identification and authentication (org users + non-org users) |
| ISO 27001 | A.9.2.1 | User registration and de-registration |
1.2 Enforce Least-Privilege Organization Roles
Profile Level: L1 (Baseline)
| Framework | Control |
|---|---|
| NIST 800-53 | AC-6, AC-6(1) |
| SOC 2 | CC6.1, CC6.3 |
Description
Assign the minimum necessary organization role to each user. Anthropic provides six organization roles: user, developer, billing, admin, claude_code_user, and managed. Limit the admin role to a small number of trusted operators.
Rationale
Why This Matters:
- The
adminrole can provision Admin API keys, manage all workspaces, and remove users - Admins automatically inherit
workspace_adminin every workspace - The Admin API deliberately prevents assigning the
adminrole programmatically — a security design decision - Admins cannot be removed via API — only through the Console
Attack Prevented: Privilege escalation, unauthorized admin key provisioning, insider threat
Prerequisites
- Organization Admin access
- Current member inventory with role justifications
ClickOps Implementation
Step 1: Review Current Role Assignments
- Navigate to: console.anthropic.com → Settings → Members
- Review each member’s role
- Document justification for each admin and billing role holder
Step 2: Downgrade Excessive Privileges
- For each user with unnecessary admin access:
- Click the user → Edit Role
- Select
developeroruseras appropriate
- Ensure at least 2 (but no more than 3) admins remain for redundancy
Step 3: Establish Role Assignment Policy
- Define criteria for each role level
- Require approval for admin role assignments
- Schedule quarterly role reviews
Time to Complete: ~15 minutes
Code Implementation
Code Pack: API Script
# Audit organization member roles — flag users with admin or billing roles
info "Auditing organization member roles..."
MEMBERS=$(anthropic_list_all "/v1/organizations/users") || {
fail "1.2 Failed to list organization users"
summary; exit 0
}
# Count by role
ADMIN_COUNT=$(echo "${MEMBERS}" | jq '[.[] | select(.role == "admin")] | length')
BILLING_COUNT=$(echo "${MEMBERS}" | jq '[.[] | select(.role == "billing")] | length')
DEVELOPER_COUNT=$(echo "${MEMBERS}" | jq '[.[] | select(.role == "developer")] | length')
USER_COUNT=$(echo "${MEMBERS}" | jq '[.[] | select(.role == "user")] | length')
TOTAL=$(echo "${MEMBERS}" | jq 'length')
info "Role distribution: admin=${ADMIN_COUNT}, billing=${BILLING_COUNT}, developer=${DEVELOPER_COUNT}, user=${USER_COUNT}, total=${TOTAL}"
# Flag excessive admin count
if [[ "${ADMIN_COUNT}" -gt 3 ]]; then
warn "1.2 ${ADMIN_COUNT} users have admin role — review for least privilege"
echo "Admins:"
echo "${MEMBERS}" | jq -r '.[] | select(.role == "admin") | " \(.name) <\(.email)>"'
else
pass "1.2 Admin count (${ADMIN_COUNT}) is within recommended limit (<=3)"
fi
# Downgrade a user from a privileged role to 'user' or 'developer'
# Usage: Set USER_ID and TARGET_ROLE before running
if [[ -n "${USER_ID:-}" && -n "${TARGET_ROLE:-}" ]]; then
info "Updating user ${USER_ID} to role '${TARGET_ROLE}'..."
anthropic_post "/v1/organizations/users/${USER_ID}" \
"{\"role\": \"${TARGET_ROLE}\"}" || {
fail "1.2 Failed to update user role"
summary; exit 0
}
pass "1.2 User ${USER_ID} updated to role '${TARGET_ROLE}'"
fi
Validation & Testing
- List all org members via Admin API — count admins <= 3
- Verify no user has admin role without documented justification
- Attempt to assign admin role via API — should fail (by design)
- Verify billing role holders match authorized finance contacts
Expected result: Admin role limited to 2-3 operators; all other users at minimum necessary privilege
Monitoring & Maintenance
Ongoing monitoring:
- Alert on new admin role assignments (Console audit)
- Monthly review of role distribution
Maintenance schedule:
- Monthly: Review member roles via API script
- Quarterly: Full access review with role justifications
Compliance Mappings
| Framework | Control ID | Control Description |
|---|---|---|
| SOC 2 | CC6.1, CC6.3 | Logical access security; role-based access |
| NIST 800-53 | AC-6, AC-6(1) | Least privilege; authorize access for security functions |
| ISO 27001 | A.9.2.3 | Management of privileged access rights |
1.3 Protect Admin API Keys
Profile Level: L1 (Baseline)
| Framework | Control |
|---|---|
| NIST 800-53 | IA-5, SC-12 |
| SOC 2 | CC6.1, CC6.6 |
Description
Admin API keys (sk-ant-admin...) grant organization-wide management access. They can only be created through the Console by users with the admin role — a deliberate security design. Treat Admin API keys with the same care as cloud provider root credentials.
Rationale
Why This Matters:
- Admin keys can list all users, manage all workspaces, disable API keys, and view usage data
- Unlike standard API keys, admin keys are not scoped to a single workspace
- Compromise of an admin key exposes the entire organization’s Claude infrastructure
Attack Prevented: Organization takeover, unauthorized workspace creation, data exfiltration via usage APIs
Prerequisites
- Organization Admin access
- Secrets management solution (Vault, AWS Secrets Manager, etc.)
ClickOps Implementation
Step 1: Audit Existing Admin Keys
- Navigate to: console.anthropic.com → Settings → Admin Keys
- Review all provisioned admin keys
- Identify and revoke any keys without a clear owner or purpose
Step 2: Establish Key Hygiene
- Name each admin key descriptively (e.g., “CI/CD Org Audit — TeamName”)
- Store keys in a secrets manager — never in source code, env files, or chat
- Rotate admin keys every 90 days
Step 3: Limit Admin Key Provisioning
- Restrict admin role to 2-3 trusted operators (see Control 1.2)
- Require documented approval before provisioning new admin keys
- Log all admin key creation events
Time to Complete: ~10 minutes
Code Implementation
Code Pack: API Script
# Admin API keys (sk-ant-admin...) can only be created in the Console.
# This script cannot list admin keys — it validates that the current
# admin key works, and audits all standard API keys for hygiene.
info "Validating admin key by retrieving organization info..."
ORG_INFO=$(anthropic_get "/v1/organizations/me") || {
fail "1.3 Admin key is invalid or expired"
summary; exit 0
}
ORG_NAME=$(echo "${ORG_INFO}" | jq -r '.name')
ORG_TYPE=$(echo "${ORG_INFO}" | jq -r '.type')
info "Organization: ${ORG_NAME} (type: ${ORG_TYPE})"
pass "1.3 Admin API key is valid and active"
Validation & Testing
- Validate admin key works via
/v1/organizations/meendpoint - Verify admin key is stored in secrets manager (not plaintext)
- Confirm admin key naming convention is followed
Expected result: All admin keys are named, stored securely, and have documented owners
Compliance Mappings
| Framework | Control ID | Control Description |
|---|---|---|
| SOC 2 | CC6.1, CC6.6 | Logical access; security of system boundaries |
| NIST 800-53 | IA-5, SC-12 | Authenticator management; cryptographic key management |
| ISO 27001 | A.9.4.3 | Password management system |
2. API Key Management
2.1 Scope API Keys to Workspaces
Profile Level: L1 (Baseline)
| Framework | Control |
|---|---|
| NIST 800-53 | AC-3, AC-6 |
| SOC 2 | CC6.1, CC6.3 |
Description
Every standard API key in Anthropic Claude is scoped to a single workspace. Leverage this design by creating separate workspaces for different environments (development, staging, production) and teams, ensuring API keys cannot access resources across workspace boundaries.
Rationale
Why This Matters:
- A compromised development API key cannot access production workspaces
- Workspace-scoped keys enable granular cost tracking and rate limiting
- API keys persist when users are removed — they’re scoped to the organization, not individuals
- Keys can only be created via the Console (not via API) — another security design choice
Attack Prevented: Lateral movement from development to production, blast radius of key compromise
Prerequisites
- Organization Admin or Workspace Admin access
- Workspace naming convention established
ClickOps Implementation
Step 1: Create Workspace-Scoped Keys
- Navigate to: console.anthropic.com → Select target workspace
- Go to: Settings → API Keys
- Click Create Key
- Name the key descriptively:
{team}-{environment}-{purpose}(e.g., “ml-team-prod-inference”)
Step 2: Audit Existing Keys
- Navigate to: Settings → API Keys (org-wide view)
- Review each key’s workspace assignment
- Identify keys in the Default Workspace — migrate to dedicated workspaces
Time to Complete: ~10 minutes per key
Code Implementation
Code Pack: API Script
# List all API keys across the organization, grouped by workspace
info "Auditing API keys across all workspaces..."
API_KEYS=$(anthropic_list_all "/v1/organizations/api_keys?status=active") || {
fail "2.1 Failed to list API keys"
summary; exit 0
}
KEY_COUNT=$(echo "${API_KEYS}" | jq 'length')
info "Found ${KEY_COUNT} active API keys"
# Group by workspace
echo "${API_KEYS}" | jq -r 'group_by(.workspace_id) | .[] |
"Workspace: \(.[0].workspace_id)\n" +
([ .[] | " Key: \(.name // "unnamed") | Status: \(.status) | Created: \(.created_at)" ] | join("\n"))
'
# Flag unnamed keys
UNNAMED=$(echo "${API_KEYS}" | jq '[.[] | select(.name == null or .name == "")] | length')
if [[ "${UNNAMED}" -gt 0 ]]; then
warn "2.1 ${UNNAMED} API keys have no name — add descriptive names for auditability"
else
pass "2.1 All API keys have descriptive names"
fi
# Flag keys in default workspace
DEFAULT_WS_KEYS=$(echo "${API_KEYS}" | jq '[.[] | select(.workspace_id == null)] | length')
if [[ "${DEFAULT_WS_KEYS}" -gt 0 ]]; then
warn "2.1 ${DEFAULT_WS_KEYS} keys are in the default workspace — consider scoping to dedicated workspaces"
fi
# Disable an API key by setting status to inactive
# Usage: Set KEY_ID before running
if [[ -n "${KEY_ID:-}" ]]; then
info "Disabling API key ${KEY_ID}..."
anthropic_post "/v1/organizations/api_keys/${KEY_ID}" \
'{"status": "inactive"}' || {
fail "2.1 Failed to disable API key"
summary; exit 0
}
pass "2.1 API key ${KEY_ID} disabled"
fi
Validation & Testing
- List all API keys via Admin API — verify each has a workspace assignment
- Verify no unnamed keys exist
- Test that a key scoped to Workspace A cannot be used with Workspace B resources
Expected result: All API keys have descriptive names and are assigned to appropriate workspaces
Compliance Mappings
| Framework | Control ID | Control Description |
|---|---|---|
| SOC 2 | CC6.1, CC6.3 | Logical access security; role-based access |
| NIST 800-53 | AC-3, AC-6 | Access enforcement; least privilege |
| ISO 27001 | A.9.4.1 | Information access restriction |
2.2 Rotate API Keys Regularly
Profile Level: L2 (Hardened)
| Framework | Control |
|---|---|
| NIST 800-53 | IA-5(1) |
| SOC 2 | CC6.1 |
Description
Establish a 90-day rotation schedule for all API keys. Since API keys can only be created via the Console, rotation requires creating a new key, updating dependent services, and then disabling/archiving the old key via the Admin API.
Rationale
Why This Matters:
- Long-lived API keys increase the window of opportunity for attackers
- Keys may be accidentally exposed in logs, error messages, or code repositories
- Anthropic API keys persist after user removal — orphaned keys remain active
Attack Prevented: Stale credential exploitation, leaked key abuse
Prerequisites
- API key inventory with creation dates
- Deployment pipeline that supports key rotation (secrets manager integration)
ClickOps Implementation
Step 1: Identify Keys Due for Rotation
- Navigate to: console.anthropic.com → Settings → API Keys
- Review creation dates for each active key
- Flag any key older than 90 days
Step 2: Rotate
- Create a new key in the same workspace with the same naming convention
- Update the dependent application/service to use the new key
- Verify the application works with the new key
- Disable the old key (set status to
inactivevia Admin API) - After a 7-day grace period, archive the old key
Time to Complete: ~15 minutes per key (excluding application updates)
Code Implementation
Code Pack: API Script
# Identify API keys that have not been rotated within 90 days
info "Checking for stale API keys (>90 days since creation)..."
API_KEYS=$(anthropic_list_all "/v1/organizations/api_keys?status=active") || {
fail "2.2 Failed to list API keys"
summary; exit 0
}
CUTOFF=$(date -d '90 days ago' '+%Y-%m-%dT%H:%M:%SZ' 2>/dev/null || \
date -v-90d '+%Y-%m-%dT%H:%M:%SZ' 2>/dev/null)
STALE_KEYS=$(echo "${API_KEYS}" | jq --arg cutoff "${CUTOFF}" \
'[.[] | select(.created_at < $cutoff)]')
STALE_COUNT=$(echo "${STALE_KEYS}" | jq 'length')
if [[ "${STALE_COUNT}" -gt 0 ]]; then
warn "2.2 ${STALE_COUNT} API keys are older than 90 days:"
echo "${STALE_KEYS}" | jq -r '.[] | " \(.name // "unnamed") | Created: \(.created_at) | Workspace: \(.workspace_id)"'
else
pass "2.2 All API keys are within 90-day rotation window"
fi
# Archive an old API key after rotation
# Usage: Set OLD_KEY_ID before running
if [[ -n "${OLD_KEY_ID:-}" ]]; then
info "Archiving old API key ${OLD_KEY_ID}..."
anthropic_post "/v1/organizations/api_keys/${OLD_KEY_ID}" \
'{"status": "archived"}' || {
fail "2.2 Failed to archive API key"
summary; exit 0
}
pass "2.2 API key ${OLD_KEY_ID} archived"
fi
Validation & Testing
- Run stale key audit script — zero keys older than 90 days
- Verify disabled keys return 401 when used
- Confirm application functionality with rotated keys
Expected result: No API key is older than 90 days; old keys are archived
Compliance Mappings
| Framework | Control ID | Control Description |
|---|---|---|
| SOC 2 | CC6.1 | Logical access security over protected information assets |
| NIST 800-53 | IA-5(1) | Authenticator management — password-based authentication |
| ISO 27001 | A.9.3.1 | Use of secret authentication information |
3. Workspace Security
3.1 Segment Workspaces by Environment
Profile Level: L1 (Baseline)
| Framework | Control |
|---|---|
| NIST 800-53 | AC-4, SC-7 |
| SOC 2 | CC6.6 |
Description
Create separate workspaces for development, staging, and production environments. Each workspace provides an isolated boundary for API keys, rate limits, spend limits, and data residency settings. Anthropic allows up to 100 workspaces per organization (archived workspaces do not count).
Rationale
Why This Matters:
- Workspace segmentation limits blast radius of API key compromise
- Enables different rate limits and spend caps per environment
- Data residency can be set per workspace (immutable after creation)
- Simplifies cost attribution and usage monitoring
Attack Prevented: Cross-environment contamination, production data exposure via development keys
Prerequisites
- Organization Admin access
- Environment naming convention (e.g.,
engineering-prod,engineering-dev,analytics-prod)
ClickOps Implementation
Step 1: Plan Workspace Structure
- Define workspaces for each team and environment combination
- Determine data residency requirements per workspace (
workspace_geois immutable after creation)
Step 2: Create Workspaces
- Navigate to: console.anthropic.com → Settings → Workspaces
- Click Create Workspace
- Enter workspace name following naming convention
- Configure data residency settings if required
- Repeat for each planned workspace
Step 3: Archive Unused Workspaces
- Identify workspaces with no recent activity
- Archive via Console (caution: this deactivates ALL API keys in the workspace and is irreversible)
Time to Complete: ~5 minutes per workspace
Code Implementation
Code Pack: API Script
# List all workspaces and their configuration
info "Listing all workspaces..."
WORKSPACES=$(anthropic_list_all "/v1/organizations/workspaces") || {
fail "3.1 Failed to list workspaces"
summary; exit 0
}
WS_COUNT=$(echo "${WORKSPACES}" | jq 'length')
info "Found ${WS_COUNT} workspaces (limit: 100 per organization)"
echo "${WORKSPACES}" | jq -r '.[] |
" \(.display_name) | ID: \(.id) | Geo: \(.settings.workspace_geo // "default") | Archived: \(.archived_at // "no")"'
pass "3.1 Workspace inventory complete"
# Create a new workspace for environment segmentation
# Usage: Set WS_NAME and optionally WS_GEO before running
if [[ -n "${WS_NAME:-}" ]]; then
BODY="{\"name\": \"${WS_NAME}\"}"
if [[ -n "${WS_GEO:-}" ]]; then
BODY=$(echo "${BODY}" | jq --arg geo "${WS_GEO}" '. + {settings: {workspace_geo: $geo}}')
fi
info "Creating workspace '${WS_NAME}'..."
RESULT=$(anthropic_post "/v1/organizations/workspaces" "${BODY}") || {
fail "3.1 Failed to create workspace"
summary; exit 0
}
NEW_ID=$(echo "${RESULT}" | jq -r '.id')
pass "3.1 Workspace created: ${NEW_ID}"
fi
Validation & Testing
- List all workspaces via Admin API — verify naming convention adherence
- Verify production workspaces have data residency configured
- Confirm workspace count is within the 100-workspace limit
Expected result: Separate workspaces exist for each team/environment; naming convention followed
Compliance Mappings
| Framework | Control ID | Control Description |
|---|---|---|
| SOC 2 | CC6.6 | System boundaries and security measures |
| NIST 800-53 | AC-4, SC-7 | Information flow enforcement; boundary protection |
| ISO 27001 | A.13.1.3 | Segregation in networks |
3.2 Manage Workspace Membership
Profile Level: L1 (Baseline)
| Framework | Control |
|---|---|
| NIST 800-53 | AC-2, AC-6 |
| SOC 2 | CC6.2, CC6.3 |
Description
Assign users to only the workspaces they need. Workspace roles (workspace_user, workspace_developer, workspace_admin, workspace_billing) provide granular access control within each workspace. Organization admins automatically inherit workspace_admin in every workspace.
Rationale
Why This Matters:
- Users should only access workspaces relevant to their team and function
- Workspace-level roles limit what actions a user can take within that workspace
- Regular membership audits catch stale access from role changes or departures
Attack Prevented: Unauthorized workspace access, privilege creep, insider threat
Prerequisites
- Workspace Admin or Organization Admin access
- Team-to-workspace mapping documented
ClickOps Implementation
Step 1: Review Current Membership
- Navigate to: console.anthropic.com → Select workspace → Members
- Review each member’s workspace role
- Document any users who don’t belong in this workspace
Step 2: Adjust Membership
- Remove users who no longer need access
- Downgrade workspace roles where appropriate (e.g.,
workspace_admin→workspace_developer) - Add users to workspaces they need access to
Time to Complete: ~10 minutes per workspace
Code Implementation
Code Pack: API Script
# Audit membership for a specific workspace
# Usage: Set WORKSPACE_ID before running
WORKSPACE_ID="${WORKSPACE_ID:-}"
if [[ -z "${WORKSPACE_ID}" ]]; then
info "Listing all workspaces to select for audit..."
WORKSPACES=$(anthropic_list_all "/v1/organizations/workspaces") || {
fail "3.2 Failed to list workspaces"
summary; exit 0
}
echo "${WORKSPACES}" | jq -r '.[] | " \(.id)\t\(.display_name)"' | column -t -s $'\t'
info "Set WORKSPACE_ID=<id> and re-run to audit a specific workspace"
summary; exit 0
fi
info "Auditing members of workspace ${WORKSPACE_ID}..."
MEMBERS=$(anthropic_list_all "/v1/organizations/workspaces/${WORKSPACE_ID}/members") || {
fail "3.2 Failed to list workspace members"
summary; exit 0
}
MEMBER_COUNT=$(echo "${MEMBERS}" | jq 'length')
info "Workspace has ${MEMBER_COUNT} members"
# List members with roles
echo "${MEMBERS}" | jq -r '.[] | " \(.user_id)\t\(.workspace_role)"' | \
column -t -s $'\t' -N "USER_ID,ROLE"
# Flag workspace admins
ADMIN_COUNT=$(echo "${MEMBERS}" | jq '[.[] | select(.workspace_role == "workspace_admin")] | length')
if [[ "${ADMIN_COUNT}" -gt 2 ]]; then
warn "3.2 ${ADMIN_COUNT} workspace admins found — review for least privilege"
else
pass "3.2 Workspace admin count (${ADMIN_COUNT}) is appropriate"
fi
# Remove a user from a workspace
# Usage: Set WORKSPACE_ID and REMOVE_USER_ID before running
if [[ -n "${REMOVE_USER_ID:-}" ]]; then
info "Removing user ${REMOVE_USER_ID} from workspace ${WORKSPACE_ID}..."
anthropic_delete "/v1/organizations/workspaces/${WORKSPACE_ID}/members/${REMOVE_USER_ID}" || {
fail "3.2 Failed to remove workspace member"
summary; exit 0
}
pass "3.2 User ${REMOVE_USER_ID} removed from workspace"
fi
Validation & Testing
- List workspace members via Admin API for each workspace
- Verify no workspace has more than 2
workspace_adminmembers (excluding inherited org admins) - Confirm removed users cannot access workspace resources
Expected result: Each workspace has only authorized members at appropriate role levels
Compliance Mappings
| Framework | Control ID | Control Description |
|---|---|---|
| SOC 2 | CC6.2, CC6.3 | Access provisioning; role-based access |
| NIST 800-53 | AC-2, AC-6 | Account management; least privilege |
| ISO 27001 | A.9.2.5 | Review of user access rights |
4. Data Security & Privacy
4.1 Enforce Data Residency Restrictions
Profile Level: L2 (Hardened)
| Framework | Control |
|---|---|
| NIST 800-53 | SC-7, SA-9(5) |
| SOC 2 | CC6.6, P6.1 |
Description
Configure data residency at the workspace level to control where Claude processes inference requests. The workspace_geo setting (immutable after creation) controls data storage location. default_inference_geo and allowed_inference_geos control where requests are processed. Available regions include us and global.
Rationale
Why This Matters:
- Regulatory requirements (GDPR, data sovereignty laws) may mandate processing within specific regions
workspace_geocannot be changed after workspace creation — plan carefully- The
inference_geoparameter can also be set per-request by API callers, butallowed_inference_geosrestricts what values are permitted
Attack Prevented: Data sovereignty violations, regulatory non-compliance
Prerequisites
- Organization Admin access
- Data residency requirements documented per team/workspace
- Legal/compliance approval for geo settings
ClickOps Implementation
Step 1: Audit Current Settings
- Navigate to: console.anthropic.com → Settings → Workspaces
- Review each workspace’s data residency configuration
- Note any workspaces without explicit geo settings
Step 2: Configure New Workspaces with Correct Geo
- When creating new workspaces, select the appropriate
workspace_geo - This setting is immutable — double-check before confirming
Step 3: Restrict Inference Geos
- For regulated workspaces, set
allowed_inference_geosto["us"]only - Set
default_inference_geoto"us"to ensure all requests default to US processing
Time to Complete: ~5 minutes per workspace
Code Implementation
Code Pack: API Script
# Audit data residency settings across all workspaces
info "Auditing data residency configuration per workspace..."
WORKSPACES=$(anthropic_list_all "/v1/organizations/workspaces") || {
fail "4.1 Failed to list workspaces"
summary; exit 0
}
echo "${WORKSPACES}" | jq -r '.[] | {
name: .display_name,
id: .id,
workspace_geo: (.settings.workspace_geo // "not set"),
default_inference_geo: (.settings.default_inference_geo // "not set"),
allowed_inference_geos: (.settings.allowed_inference_geos // ["not restricted"])
} | " \(.name) | Geo: \(.workspace_geo) | Default Inference: \(.default_inference_geo) | Allowed: \(.allowed_inference_geos | join(", "))"'
# Flag workspaces without data residency configured
UNCONFIGURED=$(echo "${WORKSPACES}" | jq '[.[] | select(
.settings.workspace_geo == null or .settings.workspace_geo == ""
)] | length')
if [[ "${UNCONFIGURED}" -gt 0 ]]; then
warn "4.1 ${UNCONFIGURED} workspaces have no explicit data residency setting"
else
pass "4.1 All workspaces have data residency configured"
fi
# Restrict a workspace to US-only inference
# Usage: Set WORKSPACE_ID before running
if [[ -n "${WORKSPACE_ID:-}" ]]; then
info "Restricting workspace ${WORKSPACE_ID} to US-only inference..."
anthropic_post "/v1/organizations/workspaces/${WORKSPACE_ID}" '{
"settings": {
"default_inference_geo": "us",
"allowed_inference_geos": ["us"]
}
}' || {
fail "4.1 Failed to update workspace data residency"
summary; exit 0
}
pass "4.1 Workspace ${WORKSPACE_ID} restricted to US-only inference"
fi
Validation & Testing
- List all workspaces via Admin API — verify
workspace_geoandallowed_inference_geos - Attempt a request with
inference_geo: "global"against a US-restricted workspace — should fail - Verify new workspaces are created with correct geo from the start
Expected result: Regulated workspaces have explicit data residency configuration; inference geo restrictions enforced
Compliance Mappings
| Framework | Control ID | Control Description |
|---|---|---|
| SOC 2 | CC6.6, P6.1 | System boundaries; privacy — consent and choice |
| NIST 800-53 | SC-7, SA-9(5) | Boundary protection; processing, storage, and service location |
| ISO 27001 | A.18.1.4 | Privacy and protection of personally identifiable information |
4.2 Configure Data Retention Policies
Profile Level: L2 (Hardened)
| Framework | Control |
|---|---|
| NIST 800-53 | SI-12 |
| SOC 2 | P4.1 |
Description
Understand and configure Anthropic’s data retention policies. By default, API inputs and outputs are retained for up to 30 days and are not used for model training. Enterprise customers can negotiate custom retention periods or Zero Data Retention (ZDR), where no inputs or outputs are stored after the response is delivered.
Rationale
Why This Matters:
- Sensitive prompts containing PII, financial data, or intellectual property are retained for 30 days by default
- ZDR eliminates server-side storage of prompts and completions entirely
- Custom retention periods allow organizations to balance compliance needs with debugging capabilities
Attack Prevented: Post-breach data exposure, regulatory non-compliance for data minimization
Prerequisites
- Claude Enterprise plan (for custom retention or ZDR)
- Data classification policy for content sent to Claude
- Legal review of Anthropic’s data handling agreement
ClickOps Implementation
Step 1: Review Default Retention
- Review Anthropic’s usage policy at anthropic.com/policies/usage-policy
- Confirm your plan’s default retention (API: 30 days, not used for training)
Step 2: Request Custom Retention (Enterprise)
- Contact your Anthropic account representative
- Specify desired retention period or request ZDR
- Obtain written confirmation of retention configuration
Step 3: Implement Data Handling Controls
- Establish guidelines for what data types may be sent to Claude
- Implement client-side PII redaction before sending sensitive prompts
- Use workspace segmentation to isolate sensitive vs. non-sensitive workloads
Time to Complete: ~30 minutes (policy review) + vendor coordination for custom retention
Validation & Testing
- Confirm retention period with Anthropic account team (Enterprise)
- Verify client-side PII redaction is in place for sensitive workloads
- Review data classification guidelines with engineering team
Expected result: Data retention policy documented and aligned with organizational requirements
Compliance Mappings
| Framework | Control ID | Control Description |
|---|---|---|
| SOC 2 | P4.1 | Privacy — data minimization |
| NIST 800-53 | SI-12 | Information management and retention |
| ISO 27001 | A.8.10 | Information deletion |
5. Monitoring & Usage Controls
5.1 Monitor API Usage and Costs
Profile Level: L1 (Baseline)
| Framework | Control |
|---|---|
| NIST 800-53 | AU-6, SI-4 |
| SOC 2 | CC7.2 |
Description
Use Anthropic’s Admin API usage and cost reporting endpoints to monitor token consumption, request patterns, and spending across workspaces. The usage API supports 1-minute, 1-hour, and 1-day bucket granularity with filtering by model, workspace, API key, service tier, and geography.
Rationale
Why This Matters:
- Unusual usage spikes may indicate compromised API keys
- Cost monitoring prevents unexpected bills from runaway applications
- Per-workspace usage data enables accurate cost attribution to teams
- Data is available within ~5 minutes of request completion
Attack Prevented: API key abuse, cryptocurrency mining via API, unauthorized bulk data extraction
Prerequisites
- Admin API key provisioned
- Monitoring infrastructure (Datadog, Grafana, etc.) or cron job for regular checks
ClickOps Implementation
Step 1: Review Usage Dashboard
- Navigate to: console.anthropic.com → Usage
- Review token usage charts, rate limit utilization, and cache rates
- Filter by workspace, model, and time period
Step 2: Review Cost Dashboard
- Navigate to: console.anthropic.com → Cost
- Review cost breakdown by workspace and model
- Identify any unexpected cost increases
Step 3: Configure Observability Integration
- Integrate with supported platforms: CloudZero, Datadog, Grafana Cloud, Honeycomb, or Vantage
- Set up alerts for anomalous usage patterns
Time to Complete: ~15 minutes (dashboard review) + integration setup time
Code Implementation
Code Pack: API Script
# Generate a daily usage report for the past 7 days, grouped by workspace
START_DATE=$(date -d '7 days ago' '+%Y-%m-%dT00:00:00Z' 2>/dev/null || \
date -v-7d '+%Y-%m-%dT00:00:00Z' 2>/dev/null)
END_DATE=$(date '+%Y-%m-%dT23:59:59Z')
info "Fetching usage report from ${START_DATE} to ${END_DATE}..."
USAGE=$(anthropic_get "/v1/organizations/usage_report/messages?start_time=${START_DATE}&end_time=${END_DATE}&group_by=workspace&bucket_width=1d") || {
fail "5.1 Failed to fetch usage report"
summary; exit 0
}
echo "${USAGE}" | jq -r '.data[] | " \(.workspace_id // "default") | Input: \(.input_tokens) | Output: \(.output_tokens) | Date: \(.bucket_start_time)"'
pass "5.1 Usage report retrieved"
# Generate a cost report for the past 30 days, grouped by workspace
COST_START=$(date -d '30 days ago' '+%Y-%m-%dT00:00:00Z' 2>/dev/null || \
date -v-30d '+%Y-%m-%dT00:00:00Z' 2>/dev/null)
info "Fetching cost report from ${COST_START}..."
COST=$(anthropic_get "/v1/organizations/cost_report?start_time=${COST_START}&end_time=${END_DATE}&group_by=workspace") || {
fail "5.1 Failed to fetch cost report"
summary; exit 0
}
echo "${COST}" | jq -r '.data[] | " \(.workspace_id // "default") | Cost: $\(.cost_usd) | Date: \(.bucket_start_time)"'
pass "5.1 Cost report retrieved"
Validation & Testing
- Run usage report API script — verify data returns for all active workspaces
- Run cost report API script — verify cost data is accurate
- Confirm observability integration is receiving data
Expected result: Usage and cost data is monitored regularly with alerts for anomalies
Compliance Mappings
| Framework | Control ID | Control Description |
|---|---|---|
| SOC 2 | CC7.2 | System monitoring |
| NIST 800-53 | AU-6, SI-4 | Audit record review; system monitoring |
| ISO 27001 | A.12.4.1 | Event logging |
5.2 Configure Spend Limits per Workspace
Profile Level: L1 (Baseline)
| Framework | Control |
|---|---|
| NIST 800-53 | SA-9, SI-4 |
| SOC 2 | CC7.2, CC6.8 |
Description
Set per-workspace spend limits and rate limits to prevent cost overruns and abuse. Workspace limits cannot exceed organization-level limits. Configure both monthly spend caps and per-model rate limits (requests per minute, input/output tokens per minute).
Rationale
Why This Matters:
- A compromised API key without spend limits can generate unlimited costs
- Rate limits prevent individual workspaces from consuming the organization’s entire quota
- Workspace-level limits enable differentiated resource allocation (e.g., production gets higher limits)
Attack Prevented: Denial-of-wallet attacks, runaway cost from compromised keys or bugs
Prerequisites
- Organization Admin or Workspace Admin access
- Budget allocation per workspace/team
ClickOps Implementation
Step 1: Set Organization-Level Limits
- Navigate to: console.anthropic.com → Settings → Limits
- Review and configure organization-level spend limits
- For custom limits beyond Tier 4, contact Anthropic sales
Step 2: Set Workspace-Level Limits
- Navigate to the target workspace → Settings → Limits
- Configure:
- Monthly spend limit: Set below org limit (e.g., $500 for dev, $5000 for prod)
- Rate limits: RPM, ITPM, OTPM per model as needed
- Repeat for each workspace
Time to Complete: ~5 minutes per workspace
Code Implementation
Code Pack: API Script
# Check for cost anomalies — alert if any workspace exceeds a threshold
THRESHOLD_USD="${THRESHOLD_USD:-1000}"
PERIOD_DAYS="${PERIOD_DAYS:-7}"
START_DATE=$(date -d "${PERIOD_DAYS} days ago" '+%Y-%m-%dT00:00:00Z' 2>/dev/null || \
date -v-"${PERIOD_DAYS}"d '+%Y-%m-%dT00:00:00Z' 2>/dev/null)
END_DATE=$(date '+%Y-%m-%dT23:59:59Z')
info "Checking for workspaces exceeding \$${THRESHOLD_USD} in the past ${PERIOD_DAYS} days..."
COST=$(anthropic_get "/v1/organizations/cost_report?start_time=${START_DATE}&end_time=${END_DATE}&group_by=workspace") || {
fail "5.2 Failed to fetch cost report"
summary; exit 0
}
# Aggregate cost per workspace
OVER_THRESHOLD=$(echo "${COST}" | jq --argjson threshold "${THRESHOLD_USD}" '
[.data | group_by(.workspace_id)[] |
{workspace: .[0].workspace_id, total: ([.[].cost_usd] | add)} |
select(.total > $threshold)]')
ALERT_COUNT=$(echo "${OVER_THRESHOLD}" | jq 'length')
if [[ "${ALERT_COUNT}" -gt 0 ]]; then
warn "5.2 ${ALERT_COUNT} workspace(s) exceed \$${THRESHOLD_USD} threshold:"
echo "${OVER_THRESHOLD}" | jq -r '.[] | " \(.workspace) — $\(.total)"'
else
pass "5.2 No workspaces exceed \$${THRESHOLD_USD} threshold in ${PERIOD_DAYS}-day window"
fi
Validation & Testing
- Verify spend limits are set for every workspace via Console
- Run cost anomaly detection script to validate monitoring
- Test that requests return 429 when rate limits are exceeded (check
retry-afterheader)
Expected result: Every workspace has explicit spend and rate limits configured
Compliance Mappings
| Framework | Control ID | Control Description |
|---|---|---|
| SOC 2 | CC7.2, CC6.8 | System monitoring; change management |
| NIST 800-53 | SA-9, SI-4 | External system services; system monitoring |
| ISO 27001 | A.12.1.3 | Capacity management |
6. Third-Party Integration Security
6.1 Audit and Clean Up Pending Invites
Profile Level: L1 (Baseline)
| Framework | Control |
|---|---|
| NIST 800-53 | AC-2(3) |
| SOC 2 | CC6.2 |
Description
Regularly audit pending organization invites. Invites in Anthropic expire after 21 days (not configurable). Stale or unauthorized invites should be revoked promptly. The invite system supports role assignment at invite time, so a malicious invite could grant elevated access.
Rationale
Why This Matters:
- Pending invites represent pre-authorized access that hasn’t been claimed
- An attacker who gains access to an invited email account could join the organization
- Invites specify the role upfront — verify that invited roles follow least privilege
Attack Prevented: Unauthorized organization access via intercepted/forwarded invites
Prerequisites
- Organization Admin access
ClickOps Implementation
Step 1: Review Pending Invites
- Navigate to: console.anthropic.com → Settings → Members → Invites
- Review each pending invite: email, role, creation date
- Revoke any invite where the recipient is unknown or no longer needed
Step 2: Establish Invite Policy
- Require approval before sending invites
- Review pending invites weekly
- Use the lowest necessary role for each invite (invites cannot use
adminrole via API)
Time to Complete: ~5 minutes
Code Implementation
Code Pack: API Script
# List all pending invites and identify stale ones
info "Listing all organization invites..."
INVITES=$(anthropic_list_all "/v1/organizations/invites") || {
fail "6.1 Failed to list invites"
summary; exit 0
}
TOTAL=$(echo "${INVITES}" | jq 'length')
PENDING=$(echo "${INVITES}" | jq '[.[] | select(.status == "pending")] | length')
EXPIRED=$(echo "${INVITES}" | jq '[.[] | select(.status == "expired")] | length')
ACCEPTED=$(echo "${INVITES}" | jq '[.[] | select(.status == "accepted")] | length')
info "Invites: total=${TOTAL}, pending=${PENDING}, expired=${EXPIRED}, accepted=${ACCEPTED}"
if [[ "${PENDING}" -gt 0 ]]; then
warn "6.1 ${PENDING} pending invites found — review for stale or unauthorized invitations:"
echo "${INVITES}" | jq -r '.[] | select(.status == "pending") |
" \(.email) | Role: \(.role) | Created: \(.created_at) | Expires: \(.expires_at)"'
else
pass "6.1 No pending invites"
fi
# Revoke a specific pending invite
# Usage: Set INVITE_ID before running
if [[ -n "${INVITE_ID:-}" ]]; then
info "Revoking invite ${INVITE_ID}..."
anthropic_delete "/v1/organizations/invites/${INVITE_ID}" || {
fail "6.1 Failed to revoke invite"
summary; exit 0
}
pass "6.1 Invite ${INVITE_ID} revoked"
fi
Validation & Testing
- Run invite audit script — verify no stale pending invites
- Attempt to create invite with admin role via API — should fail (by design)
- Confirm expired invites cannot be accepted
Expected result: All pending invites are reviewed, authorized, and time-bound
Compliance Mappings
| Framework | Control ID | Control Description |
|---|---|---|
| SOC 2 | CC6.2 | Prior to issuing system credentials and granting system access |
| NIST 800-53 | AC-2(3) | Account management — disable accounts |
| ISO 27001 | A.9.2.1 | User registration and de-registration |
6.2 Integration Risk Assessment
Profile Level: L2 (Hardened)
| Framework | Control |
|---|---|
| NIST 800-53 | RA-3, SA-9 |
| SOC 2 | CC3.2, CC9.2 |
Description
Assess the security posture of applications and services that consume your Claude API keys. API keys are bearer tokens — any application with the key can make requests on your behalf within the workspace scope.
Rationale
Why This Matters:
- Any application holding your API key can generate costs and access model capabilities
- Third-party tools (LangChain, LlamaIndex, custom applications) embed API keys
- A compromised third-party application with your key = a compromised key
Integration Risk Assessment Matrix
| Risk Factor | Low | Medium | High |
|---|---|---|---|
| Data Sensitivity | Non-sensitive prompts | Internal business data | PII, financial, health data |
| Key Scope | Dedicated low-limit workspace | Shared development workspace | Production workspace |
| Application Trust | First-party, audited code | Vendor with SOC 2 | Unaudited open-source tool |
| Key Storage | Secrets manager | Environment variable | Hardcoded or config file |
| Rate Limit | Strict per-workspace limits | Moderate limits | Organization-level defaults |
Decision Matrix:
- 0-5 points: Standard controls (workspace scoping, naming)
- 6-10 points: Enhanced controls (dedicated workspace, low spend limits, key rotation)
- 11-15 points: Reject or isolate (dedicated workspace with minimum limits, frequent rotation, monitoring)
Validation & Testing
- Maintain inventory of all applications using Claude API keys
- Verify each application’s key is in an appropriately scoped workspace
- Confirm keys are stored in secrets managers, not in source code
Expected result: All API key consumers are inventoried with risk ratings and appropriate controls
Compliance Mappings
| Framework | Control ID | Control Description |
|---|---|---|
| SOC 2 | CC3.2, CC9.2 | Risk assessment; vendor and third-party risk management |
| NIST 800-53 | RA-3, SA-9 | Risk assessment; external system services |
| ISO 27001 | A.15.1.2 | Addressing security within supplier agreements |
7. Claude Code Enterprise Controls
7.1 Deploy Managed Settings via MDM
Profile Level: L1 (Baseline)
| Framework | Control |
|---|---|
| NIST 800-53 | CM-6, CM-7 |
| SOC 2 | CC6.1, CC8.1 |
Description
Deploy organization-wide Claude Code security policies using one of four managed settings delivery mechanisms. Managed settings cannot be overridden by user or project settings. Options include: (1) Server-managed settings via the Claude.ai admin console (Team v2.1.38+ / Enterprise v2.1.30+), requiring no MDM; (2) MDM/OS-level policies via macOS managed preferences (com.anthropic.claudecode domain in Jamf/Kandji) or Windows registry (HKLM\SOFTWARE\Policies\ClaudeCode via GPO/Intune); (3) File-based managed-settings.json deployed to system paths; (4) Drop-in directory (managed-settings.d/*.json) for modular policy fragments that deep-merge onto the base config.
Rationale
Why This Matters:
- Without managed settings, individual developers can use
--dangerously-skip-permissionsto bypass all safety checks - User-defined hooks and MCP servers can introduce supply chain risks
- Managed settings enforce a security baseline that developers cannot weaken
- Server-managed settings are fetched at startup and polled hourly with offline caching
Attack Prevented: Permission bypass, unauthorized tool execution, malicious hook injection
Prerequisites
- MDM solution deployed to developer machines (Jamf, Intune, Kandji), OR Claude Team/Enterprise plan for server-managed settings
- Security team consensus on default permission mode and deny rules
- Inventory of approved MCP servers and tools
ClickOps Implementation
Option A: Server-Managed Settings (No MDM Required)
- Navigate to: claude.ai → Admin Settings → Claude Code → Managed settings
- Add JSON configuration with required security settings
- Settings propagate to all users at next startup or within 1 hour
Option B: MDM Deployment
Step 1: Create managed-settings.json
- Create the JSON configuration file with your organization’s security policy
- Include at minimum:
disableBypassPermissionsMode,permissions.denyrules, andpermissions.defaultMode
Step 2: Deploy via MDM
Deploy to the correct OS-specific path:
| OS | File Path | MDM/Policy Path |
|---|---|---|
| macOS | /Library/Application Support/ClaudeCode/managed-settings.json |
com.anthropic.claudecode preferences domain (Jamf/Kandji profile) |
| Linux / WSL | /etc/claude-code/managed-settings.json |
N/A |
| Windows | C:\Program Files\ClaudeCode\managed-settings.json |
HKLM\SOFTWARE\Policies\ClaudeCode → Settings (REG_SZ with JSON) |
For modular policies, create a managed-settings.d/ directory alongside the base file. Use numeric prefixes to control merge order (e.g., 10-telemetry.json, 20-security.json). Files are sorted alphabetically, deep-merged onto the base — arrays are concatenated and de-duplicated, objects are deep-merged, and later files override earlier ones for scalar values.
Step 3: Verify Deployment
- On a test machine, run
claude --versionto confirm Claude Code sees the managed settings - Attempt to use
--dangerously-skip-permissions— should be blocked ifdisableBypassPermissionsModeis set
Time to Complete: ~30 minutes (policy creation) + MDM deployment time
Code Implementation
Code Pack: Config
// Anthropic official example: settings-lax.json (L1 Baseline)
// Source: github.com/anthropics/claude-code/blob/main/examples/settings/settings-lax.json
// Prevents --dangerously-skip-permissions and blocks plugin marketplaces.
{
"permissions": {
"disableBypassPermissionsMode": "disable"
},
"strictKnownMarketplaces": []
}
// Anthropic official example: settings-strict.json (L2 Hardened)
// Source: github.com/anthropics/claude-code/blob/main/examples/settings/settings-strict.json
// Blocks bypass, enforces managed-only permissions and hooks,
// denies web access, requires Bash approval, locks sandbox settings.
{
"permissions": {
"disableBypassPermissionsMode": "disable",
"ask": [
"Bash"
],
"deny": [
"WebSearch",
"WebFetch"
]
},
"allowManagedPermissionRulesOnly": true,
"allowManagedHooksOnly": true,
"strictKnownMarketplaces": [],
"sandbox": {
"autoAllowBashIfSandboxed": false,
"excludedCommands": [],
"network": {
"allowUnixSockets": [],
"allowAllUnixSockets": false,
"allowLocalBinding": false,
"allowedDomains": [],
"httpProxyPort": null,
"socksProxyPort": null
},
"enableWeakerNestedSandbox": false
}
}
// Anthropic official example: settings-bash-sandbox.json (L3 Sandbox)
// Source: github.com/anthropics/claude-code/blob/main/examples/settings/settings-bash-sandbox.json
// Enables OS-level bash sandboxing with no escape hatch.
// Platform support: macOS (Seatbelt), Linux/WSL2 (bubblewrap).
{
"allowManagedPermissionRulesOnly": true,
"sandbox": {
"enabled": true,
"autoAllowBashIfSandboxed": false,
"allowUnsandboxedCommands": false,
"excludedCommands": [],
"network": {
"allowUnixSockets": [],
"allowAllUnixSockets": false,
"allowLocalBinding": false,
"allowedDomains": [],
"httpProxyPort": null,
"socksProxyPort": null
},
"enableWeakerNestedSandbox": false
}
}
// Comprehensive managed-settings.json — combines all security controls.
// Reference: code.claude.com/docs/en/settings
// Extends the official examples with practical deny rules, model
// restrictions, org login enforcement, and MCP server controls.
{
"$schema": "https://json.schemastore.org/claude-code-settings.json",
"permissions": {
"disableBypassPermissionsMode": "disable",
"deny": [
"Bash(curl *)",
"Bash(wget *)",
"Bash(rm -rf *)",
"Bash(ssh *)",
"Bash(scp *)",
"Read(./.env)",
"Read(./.env.*)",
"Read(./secrets/**)",
"Read(./credentials/**)",
"Read(~/.ssh/**)",
"Read(~/.aws/**)",
"WebSearch",
"WebFetch"
],
"ask": [
"Bash"
],
"allow": [
"Bash(npm run *)",
"Bash(npm test)",
"Bash(git status)",
"Bash(git diff *)",
"Bash(git log *)"
]
},
"allowManagedPermissionRulesOnly": true,
"allowManagedHooksOnly": true,
"strictKnownMarketplaces": [],
"env": {
"CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1"
},
"model": "claude-sonnet-4-6",
"availableModels": ["sonnet", "haiku"],
"forceLoginMethod": "claudeai",
"forceLoginOrgUUID": "REPLACE-WITH-YOUR-ORG-UUID",
"cleanupPeriodDays": 7,
"allowedMcpServers": [
{"serverName": "github"},
{"serverName": "memory"}
],
"deniedMcpServers": [
{"serverName": "filesystem"},
{"serverName": "shell"},
{"serverName": "puppeteer"}
],
"allowManagedMcpServersOnly": true,
"channelsEnabled": false,
"disableAutoMode": "disable",
"blockedMarketplaces": [],
"pluginTrustMessage": "Only IT-approved plugins are permitted.",
"allowedHttpHookUrls": ["https://hooks.example.com/*"],
"httpHookAllowedEnvVars": ["HOOK_AUTH_TOKEN"],
"sandbox": {
"enabled": true,
"failIfUnavailable": true,
"autoAllowBashIfSandboxed": false,
"allowUnsandboxedCommands": false,
"excludedCommands": ["docker"],
"filesystem": {
"denyRead": ["~/.aws/credentials", "~/.ssh/id_*"],
"denyWrite": ["/etc", "/usr/local/bin"],
"allowWrite": ["/tmp/build"]
},
"network": {
"allowUnixSockets": [],
"allowAllUnixSockets": false,
"allowLocalBinding": false,
"allowedDomains": [
"github.com",
"*.npmjs.org",
"registry.yarnpkg.com"
],
"allowManagedDomainsOnly": true,
"httpProxyPort": null,
"socksProxyPort": null
},
"enableWeakerNestedSandbox": false
}
}
Code Pack: API Script
# Detect OS and check for managed-settings.json in the correct path
case "$(uname -s)" in
Darwin)
MANAGED_PATH="/Library/Application Support/ClaudeCode/managed-settings.json"
;;
Linux)
MANAGED_PATH="/etc/claude-code/managed-settings.json"
;;
MINGW*|MSYS*|CYGWIN*)
MANAGED_PATH="C:\\Program Files\\ClaudeCode\\managed-settings.json"
;;
*)
warn "7.1 Unknown OS — cannot determine managed-settings.json path"
summary; exit 0
;;
esac
info "Checking for managed-settings.json at: ${MANAGED_PATH}"
if [[ ! -f "${MANAGED_PATH}" ]]; then
fail "7.1 managed-settings.json not found — MDM deployment may not be configured"
summary; exit 0
fi
pass "7.1 managed-settings.json exists at ${MANAGED_PATH}"
# Validate JSON structure
if ! jq empty "${MANAGED_PATH}" 2>/dev/null; then
fail "7.1 managed-settings.json is not valid JSON"
summary; exit 0
fi
pass "7.1 managed-settings.json is valid JSON"
# Check critical security settings
BYPASS_DISABLED=$(jq -r '(.permissions.disableBypassPermissionsMode // .disableBypassPermissionsMode // "not set")' "${MANAGED_PATH}")
MANAGED_PERMS_ONLY=$(jq -r '.allowManagedPermissionRulesOnly // "not set"' "${MANAGED_PATH}")
MANAGED_HOOKS_ONLY=$(jq -r '.allowManagedHooksOnly // "not set"' "${MANAGED_PATH}")
DEFAULT_MODE=$(jq -r '.permissions.defaultMode // "not set"' "${MANAGED_PATH}")
info "Security settings:"
info " disableBypassPermissionsMode: ${BYPASS_DISABLED}"
info " allowManagedPermissionRulesOnly: ${MANAGED_PERMS_ONLY}"
info " allowManagedHooksOnly: ${MANAGED_HOOKS_ONLY}"
info " permissions.defaultMode: ${DEFAULT_MODE}"
if [[ "${BYPASS_DISABLED}" == "disable" ]]; then
pass "7.1 Bypass permissions mode is disabled"
else
warn "7.1 disableBypassPermissionsMode is not set to 'disable'"
fi
if [[ "${MANAGED_PERMS_ONLY}" == "true" ]]; then
pass "7.1 Only managed permission rules are enforced"
else
warn "7.1 allowManagedPermissionRulesOnly is not enabled"
fi
# Check for deny rules
DENY_COUNT=$(jq '.permissions.deny // [] | length' "${MANAGED_PATH}" 2>/dev/null || echo 0)
info " Deny rules configured: ${DENY_COUNT}"
if [[ "${DENY_COUNT}" -gt 0 ]]; then
jq -r '.permissions.deny[]' "${MANAGED_PATH}" 2>/dev/null | while read -r rule; do
info " - ${rule}"
done
pass "7.1 Deny rules are configured (${DENY_COUNT} rules)"
else
warn "7.1 No deny rules configured — consider adding restrictions"
fi
Validation & Testing
- Run validation script — managed-settings.json exists at correct OS path
- Verify
disableBypassPermissionsModeis set to"disable" - Attempt
--dangerously-skip-permissions— should be rejected - Verify deny rules block restricted operations
Expected result: All developer machines have managed settings deployed; bypass mode is disabled
Monitoring & Maintenance
Ongoing monitoring:
- MDM compliance dashboard confirms file is present on all enrolled devices
- Alert on devices missing managed-settings.json
Maintenance schedule:
- Monthly: Review and update deny rules as tooling changes
- Quarterly: Audit managed settings against security policy
Operational Impact
| Aspect | Impact Level | Details |
|---|---|---|
| User Experience | Medium | Developers cannot bypass permission checks |
| System Performance | None | Settings loaded once at startup |
| Maintenance Burden | Low | MDM handles deployment; policy changes are centralized |
| Rollback Difficulty | Easy | Remove file from MDM profile |
Compliance Mappings
| Framework | Control ID | Control Description |
|---|---|---|
| SOC 2 | CC6.1, CC8.1 | Logical access security; change management |
| NIST 800-53 | CM-6, CM-7 | Configuration settings; least functionality |
| ISO 27001 | A.12.5.1 | Installation of software on operational systems |
7.2 Restrict Claude Code Permissions and Tools
Profile Level: L2 (Hardened)
| Framework | Control |
|---|---|
| NIST 800-53 | AC-3, CM-7 |
| SOC 2 | CC6.1, CC6.3 |
Description
Configure granular permission rules in managed settings to control which tools Claude Code can use, which files it can access, and which commands it can execute. Use deny rules (which always take precedence) to block sensitive operations like reading .env files, executing curl commands, or accessing secrets directories.
Rationale
Why This Matters:
- Claude Code can read, write, and execute arbitrary commands by default
- Without restrictions, a compromised or confused AI agent could exfiltrate secrets, modify production configs, or execute malicious commands
- Deny rules are evaluated before allow rules — they provide a hard security boundary
allowManagedPermissionRulesOnly: trueensures users cannot add their own allow rules to weaken the policy
Attack Prevented: Secret exfiltration via AI agent, unauthorized file access, command injection
Prerequisites
- Managed settings deployment (Control 7.1)
- Inventory of sensitive file patterns and restricted commands
ClickOps Implementation
Step 1: Define Permission Policy
- Identify sensitive file patterns:
.env,.env.*,secrets/, credentials files - Identify restricted commands:
curl(data exfiltration),rm -rf(destruction), credential access - Define approved operations:
npm run *,git status,git diff, test runners
Step 2: Configure via Admin Console or MDM
- Add permission rules to managed settings:
- deny:
Read(./.env),Read(./.env.*),Read(./secrets/**),Bash(curl *),Bash(rm -rf *) - allow:
Bash(npm run *),Bash(git status),Bash(git diff *) - ask:
Bash(git push *),Bash(git commit *)
- deny:
- Set
allowManagedPermissionRulesOnly: trueto prevent user overrides - Set
disableBypassPermissionsMode: "disable"(see Control 7.1)
Step 3: Configure Network Sandbox (L3)
- Enable
sandbox.enabled: truefor OS-level isolation - Set
sandbox.network.allowedDomainsto restrict outbound network access - Restrict socket access as needed
Time to Complete: ~20 minutes
Code Implementation
Code Pack: Config
// Example permission deny/ask/allow rules for managed-settings.json
// Deny blocks access. Ask prompts the user. Allow auto-approves.
// Glob patterns: * matches files in one directory, ** matches recursively.
{
"permissions": {
"deny": [
"Read(./.env)",
"Read(./.env.*)",
"Read(./secrets/**)",
"Read(./credentials/**)",
"Read(~/.ssh/**)",
"Read(~/.aws/**)",
"Bash(curl *)",
"Bash(wget *)",
"Bash(rm -rf *)",
"Bash(ssh *)",
"Bash(scp *)",
"Bash(nc *)",
"Bash(base64 *)",
"WebSearch",
"WebFetch"
],
"ask": [
"Bash",
"Bash(git push *)",
"Bash(git commit *)",
"Bash(docker *)",
"Bash(kubectl *)"
],
"allow": [
"Bash(npm run *)",
"Bash(npm test)",
"Bash(git status)",
"Bash(git diff *)",
"Bash(git log *)",
"Bash(ls *)",
"Bash(cat package.json)",
"Read",
"Edit"
]
}
}
// Enforce managed-only permission rules
// Reference: code.claude.com/docs/en/settings
// When allowManagedPermissionRulesOnly is true, user and project
// settings cannot define allow, ask, or deny rules — only rules
// from managed settings apply.
{
"permissions": {
"disableBypassPermissionsMode": "disable",
"defaultMode": "default"
},
"allowManagedPermissionRulesOnly": true,
"allowManagedHooksOnly": true,
"disableAllHooks": false
}
// OS-level bash sandbox configuration (L3 — Maximum Security)
// Reference: code.claude.com/docs/en/sandboxing
// Sandboxing provides filesystem and network isolation for Bash commands.
// Platform support:
// macOS: Seatbelt (built-in, no install needed)
// Linux/WSL2: bubblewrap (apt install bubblewrap socat)
// Windows: Not yet supported
{
"sandbox": {
"enabled": true,
"autoAllowBashIfSandboxed": false,
"allowUnsandboxedCommands": false,
"excludedCommands": [
"docker"
],
"network": {
"allowUnixSockets": [],
"allowAllUnixSockets": false,
"allowLocalBinding": false,
"allowedDomains": [
"*.npmjs.org",
"registry.npmjs.org",
"github.com"
],
"httpProxyPort": null,
"socksProxyPort": null
},
"enableWeakerNestedSandbox": false
}
}
Code Pack: API Script
# Validate permission configuration on this machine
MANAGED_PATH=""
case "$(uname -s)" in
Darwin) MANAGED_PATH="/Library/Application Support/ClaudeCode/managed-settings.json" ;;
Linux) MANAGED_PATH="/etc/claude-code/managed-settings.json" ;;
MINGW*|MSYS*|CYGWIN*) MANAGED_PATH="C:\\Program Files\\ClaudeCode\\managed-settings.json" ;;
esac
if [[ -z "${MANAGED_PATH}" ]] || [[ ! -f "${MANAGED_PATH}" ]]; then
warn "7.2 managed-settings.json not found — cannot validate permissions"
summary; exit 0
fi
DENY_COUNT=$(jq '.permissions.deny // [] | length' "${MANAGED_PATH}" 2>/dev/null || echo 0)
ALLOW_COUNT=$(jq '.permissions.allow // [] | length' "${MANAGED_PATH}" 2>/dev/null || echo 0)
ASK_COUNT=$(jq '.permissions.ask // [] | length' "${MANAGED_PATH}" 2>/dev/null || echo 0)
info "Permission rules: deny=${DENY_COUNT}, allow=${ALLOW_COUNT}, ask=${ASK_COUNT}"
if [[ "${DENY_COUNT}" -gt 0 ]]; then
pass "7.2 Deny rules configured (${DENY_COUNT} rules)"
else
warn "7.2 No deny rules — sensitive files and commands are unrestricted"
fi
PERMS_ONLY=$(jq -r '.allowManagedPermissionRulesOnly // false' "${MANAGED_PATH}")
if [[ "${PERMS_ONLY}" == "true" ]]; then
pass "7.2 Managed-only permission rules enforced"
else
warn "7.2 allowManagedPermissionRulesOnly is not enabled — users can override rules"
fi
SANDBOX_ENABLED=$(jq -r '.sandbox.enabled // false' "${MANAGED_PATH}")
if [[ "${SANDBOX_ENABLED}" == "true" ]]; then
pass "7.2 Bash sandbox is enabled"
UNSANDBOXED=$(jq -r '.sandbox.allowUnsandboxedCommands // true' "${MANAGED_PATH}")
if [[ "${UNSANDBOXED}" == "false" ]]; then
pass "7.2 Unsandboxed command escape hatch is disabled"
else
warn "7.2 allowUnsandboxedCommands is true — users can bypass sandbox"
fi
else
info "7.2 Bash sandbox is not enabled (optional L3 control)"
fi
Validation & Testing
- Attempt to read a
.envfile via Claude Code — should be denied - Attempt to run
curlvia Claude Code — should be denied - Run an approved command (e.g.,
npm run test) — should succeed - Verify user-added allow rules are ignored when
allowManagedPermissionRulesOnlyis true
Expected result: Sensitive files and commands are blocked; only approved operations succeed
Operational Impact
| Aspect | Impact Level | Details |
|---|---|---|
| User Experience | Medium | Developers see denials for restricted operations |
| System Performance | None | Rule evaluation is instant |
| Maintenance Burden | Medium | Rules need updating as tooling evolves |
| Rollback Difficulty | Easy | Remove deny rules from managed settings |
Compliance Mappings
| Framework | Control ID | Control Description |
|---|---|---|
| SOC 2 | CC6.1, CC6.3 | Logical access security; role-based access |
| NIST 800-53 | AC-3, CM-7 | Access enforcement; least functionality |
| ISO 27001 | A.9.4.1 | Information access restriction |
7.3 Control MCP Server Access
Profile Level: L2 (Hardened)
| Framework | Control |
|---|---|
| NIST 800-53 | CM-7, SA-9 |
| SOC 2 | CC6.6, CC9.2 |
Description
Restrict which Model Context Protocol (MCP) servers Claude Code can connect to using a managed MCP configuration file or allowlist/denylist settings. MCP servers extend Claude Code’s capabilities by providing additional tools — an uncontrolled MCP server can introduce arbitrary tool access.
Rationale
Why This Matters:
- MCP servers can provide Claude Code with tools to access databases, APIs, cloud services, and more
- A malicious or misconfigured MCP server can grant unintended access to sensitive systems
- The
managed-mcp.jsonfile provides exclusive control — when present, users cannot add their own MCP servers - Deny rules in
deniedMcpServersalways take precedence over allow rules
Attack Prevented: Supply chain attack via malicious MCP server, unauthorized system access, data exfiltration through MCP tools
Prerequisites
- MDM deployment capability (for managed-mcp.json) or managed settings access
- Inventory of approved MCP servers and their security posture
ClickOps Implementation
Step 1: Inventory MCP Servers
- Survey development teams for MCP servers in use
- Assess each server’s security posture (source, maintainer, permissions granted)
- Create an approved list
Step 2: Deploy Managed MCP Configuration
Deploy managed-mcp.json to the OS-specific path:
| OS | Path |
|---|---|
| macOS | /Library/Application Support/ClaudeCode/managed-mcp.json |
| Linux / WSL | /etc/claude-code/managed-mcp.json |
| Windows | C:\Program Files\ClaudeCode\managed-mcp.json |
When this file exists, it takes exclusive control — users cannot add, modify, or use any MCP servers other than those defined in this file.
Step 3: Alternative — Allowlist/Denylist via Managed Settings
- Add
allowedMcpServersto managed settings with approved server names, commands, or URLs - Add
deniedMcpServersfor explicitly blocked servers (deny always wins) - URL wildcards are supported (e.g.,
https://*.company.com/*)
Time to Complete: ~15 minutes
Code Implementation
Code Pack: Config
// managed-mcp.json — exclusive MCP server control
// Reference: code.claude.com/docs/en/mcp#managed-mcp-configuration
// When this file exists at the system path, it takes exclusive control:
// users cannot add, modify, or use any MCP servers not defined here.
// Deploy alongside managed-settings.json at the same OS-specific path:
// macOS: /Library/Application Support/ClaudeCode/managed-mcp.json
// Linux: /etc/claude-code/managed-mcp.json
// Windows: C:\Program Files\ClaudeCode\managed-mcp.json
//
// Note: Server-managed settings cannot distribute MCP server configs —
// this file must be deployed via MDM, Group Policy, or Ansible.
{
"mcpServers": {
"approved-github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": {
"GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_TOKEN}"
}
},
"approved-postgres": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-postgres"],
"env": {
"DATABASE_URL": "${APPROVED_DB_URL}"
}
}
}
}
// MCP server allowlist/denylist via managed-settings.json
// Reference: code.claude.com/docs/en/settings#mcp-configuration-managed
// Use this when you want guardrails without exclusive MCP control.
// Deny rules always take precedence over allow rules.
// undefined = no restrictions; empty array [] = complete lockdown.
{
"allowedMcpServers": [
{"serverName": "github"},
{"serverName": "memory"},
{"serverName": "postgres"}
],
"deniedMcpServers": [
{"serverName": "filesystem"},
{"serverName": "shell"},
{"serverName": "puppeteer"}
],
"enableAllProjectMcpServers": false
}
Code Pack: API Script
# Validate MCP configuration on this machine
MCP_PATH=""
MANAGED_PATH=""
case "$(uname -s)" in
Darwin)
MCP_PATH="/Library/Application Support/ClaudeCode/managed-mcp.json"
MANAGED_PATH="/Library/Application Support/ClaudeCode/managed-settings.json"
;;
Linux)
MCP_PATH="/etc/claude-code/managed-mcp.json"
MANAGED_PATH="/etc/claude-code/managed-settings.json"
;;
MINGW*|MSYS*|CYGWIN*)
MCP_PATH="C:\\Program Files\\ClaudeCode\\managed-mcp.json"
MANAGED_PATH="C:\\Program Files\\ClaudeCode\\managed-settings.json"
;;
esac
# Check for managed-mcp.json (exclusive control)
if [[ -n "${MCP_PATH}" ]] && [[ -f "${MCP_PATH}" ]]; then
if jq empty "${MCP_PATH}" 2>/dev/null; then
SERVER_COUNT=$(jq '.mcpServers | length' "${MCP_PATH}" 2>/dev/null || echo 0)
pass "7.3 managed-mcp.json deployed — ${SERVER_COUNT} approved MCP servers"
info "Approved servers:"
jq -r '.mcpServers | keys[]' "${MCP_PATH}" 2>/dev/null | while read -r server; do
info " - ${server}"
done
else
fail "7.3 managed-mcp.json exists but is not valid JSON"
fi
else
info "7.3 No managed-mcp.json — checking allowlist/denylist in managed settings"
fi
# Check allowlist/denylist in managed-settings.json
if [[ -n "${MANAGED_PATH}" ]] && [[ -f "${MANAGED_PATH}" ]]; then
ALLOWED=$(jq '.allowedMcpServers // [] | length' "${MANAGED_PATH}" 2>/dev/null || echo 0)
DENIED=$(jq '.deniedMcpServers // [] | length' "${MANAGED_PATH}" 2>/dev/null || echo 0)
if [[ "${ALLOWED}" -gt 0 ]] || [[ "${DENIED}" -gt 0 ]]; then
info "MCP allowlist: ${ALLOWED} servers, denylist: ${DENIED} servers"
if [[ "${DENIED}" -gt 0 ]]; then
pass "7.3 MCP deny rules configured (${DENIED} servers blocked)"
fi
if [[ "${ALLOWED}" -gt 0 ]]; then
pass "7.3 MCP allowlist configured (${ALLOWED} servers approved)"
fi
else
warn "7.3 No MCP allowlist or denylist — all MCP servers are permitted"
fi
AUTO_APPROVE=$(jq -r '.enableAllProjectMcpServers // "not set"' "${MANAGED_PATH}")
if [[ "${AUTO_APPROVE}" == "true" ]]; then
warn "7.3 enableAllProjectMcpServers is true — project MCP servers auto-approved"
elif [[ "${AUTO_APPROVE}" == "false" ]]; then
pass "7.3 Project MCP servers require explicit approval"
fi
fi
Validation & Testing
- Verify managed-mcp.json is deployed (if using exclusive control)
- Attempt to add an unapproved MCP server — should be blocked
- Verify approved MCP servers connect successfully
- Test deny rule against a specific server name — should be blocked
Expected result: Only approved MCP servers can be used; all others are blocked
Operational Impact
| Aspect | Impact Level | Details |
|---|---|---|
| User Experience | Medium | Developers can only use pre-approved MCP servers |
| System Performance | None | MCP config is loaded once at startup |
| Maintenance Burden | Medium | Approved list needs updates as new servers are adopted |
| Rollback Difficulty | Easy | Remove managed-mcp.json or update allowlist |
Compliance Mappings
| Framework | Control ID | Control Description |
|---|---|---|
| SOC 2 | CC6.6, CC9.2 | System boundaries; vendor risk management |
| NIST 800-53 | CM-7, SA-9 | Least functionality; external system services |
| ISO 27001 | A.15.1.2 | Addressing security within supplier agreements |
7.4 Monitor Claude Code Developer Metrics
Profile Level: L1 (Baseline)
| Framework | Control |
|---|---|
| NIST 800-53 | AU-6, SI-4 |
| SOC 2 | CC7.2 |
Description
Use the Claude Code Analytics API (/v1/organizations/usage_report/claude_code) to monitor per-user developer activity including sessions, commits, pull requests, lines of code, tool acceptance rates, and cost by model. This endpoint provides daily granularity with per-user breakdowns.
Rationale
Why This Matters:
- Per-user metrics enable detection of anomalous Claude Code usage patterns
- Tool acceptance rates below 70% may indicate permission configuration issues or developer friction
- Cost attribution by user and model enables budget management
- Tracking commits and PRs created by Claude Code quantifies AI-assisted development impact
Attack Prevented: Unauthorized bulk code generation, cost abuse, shadow AI usage detection
Prerequisites
- Admin API key provisioned
- Claude Team or Enterprise plan with Claude Code enabled
- Monitoring infrastructure for alert thresholds
ClickOps Implementation
Step 1: Review Usage in Console
- Navigate to: console.anthropic.com → Usage
- Filter for Claude Code usage
- Review per-user activity patterns
Step 2: Configure Alerts
- Set up daily automated checks using the API script below
- Configure alerts for:
- Users with unusually high session counts
- Tool acceptance rates below 70%
- Cost exceeding per-user thresholds
- Integrate with observability platform (Datadog, Grafana, etc.)
Step 3: Configure OpenTelemetry (Optional)
- Set environment variables in managed settings for OTel export
- Available metrics: sessions, LOC, PRs, commits, cost, tokens, code edit decisions, active time
- Available events: user prompts, tool results, API requests, API errors, tool decisions
Time to Complete: ~15 minutes (API setup) + integration time
Code Implementation
Code Pack: API Script
# Fetch per-user Claude Code analytics for a given day
# Usage: Set REPORT_DATE (YYYY-MM-DD) or defaults to yesterday
REPORT_DATE="${REPORT_DATE:-$(date -d 'yesterday' '+%Y-%m-%d' 2>/dev/null || \
date -v-1d '+%Y-%m-%d' 2>/dev/null)}"
info "Fetching Claude Code analytics for ${REPORT_DATE}..."
ANALYTICS=$(anthropic_get "/v1/organizations/usage_report/claude_code?starting_at=${REPORT_DATE}&limit=100") || {
fail "7.4 Failed to fetch Claude Code analytics"
summary; exit 0
}
RECORD_COUNT=$(echo "${ANALYTICS}" | jq '.data | length')
info "Found ${RECORD_COUNT} user records for ${REPORT_DATE}"
# Per-user summary
echo "${ANALYTICS}" | jq -r '.data[] | [
(.actor.email_address // .actor.api_key_name // "unknown"),
(.terminal_type // "n/a"),
(.core_metrics.num_sessions // 0),
(.core_metrics.commits_by_claude_code // 0),
(.core_metrics.pull_requests_by_claude_code // 0),
(.core_metrics.lines_of_code.added // 0),
(.core_metrics.lines_of_code.removed // 0)
] | @tsv' | column -t -s $'\t' -N "USER,TERMINAL,SESSIONS,COMMITS,PRS,LOC_ADD,LOC_DEL"
pass "7.4 Claude Code analytics retrieved"
# Analyze tool acceptance rates — low acceptance may indicate
# overly permissive settings or developer friction
info "Analyzing tool acceptance rates..."
echo "${ANALYTICS}" | jq -r '.data[] |
.actor.email_address as $user |
.tool_actions // {} | to_entries[] |
[$user, .key, (.value.accepted // 0), (.value.rejected // 0),
(if ((.value.accepted // 0) + (.value.rejected // 0)) > 0
then ((.value.accepted // 0) * 100 / ((.value.accepted // 0) + (.value.rejected // 0)) | floor | tostring) + "%"
else "n/a" end)] | @tsv' | column -t -s $'\t' -N "USER,TOOL,ACCEPTED,REJECTED,RATE"
# Flag users with low acceptance rates (below 70%)
LOW_ACCEPTANCE=$(echo "${ANALYTICS}" | jq '[.data[] |
.actor.email_address as $user |
.tool_actions // {} | to_entries[] |
{user: $user, tool: .key, accepted: (.value.accepted // 0), rejected: (.value.rejected // 0)} |
select((.accepted + .rejected) > 5) |
select((.accepted / (.accepted + .rejected)) < 0.7)]')
LOW_COUNT=$(echo "${LOW_ACCEPTANCE}" | jq 'length')
if [[ "${LOW_COUNT}" -gt 0 ]]; then
warn "7.4 ${LOW_COUNT} user/tool combinations have <70% acceptance rate — review permission configuration"
else
pass "7.4 All tool acceptance rates are healthy (>=70%)"
fi
# Cost breakdown by model across all Claude Code users
info "Cost breakdown by model:"
echo "${ANALYTICS}" | jq -r '[.data[].model_breakdown[]? |
{model: .model, cost: ((.estimated_cost.amount // 0) / 100)}] |
group_by(.model) | .[] |
{model: .[0].model, total_cost: ([.[].cost] | add | . * 100 | round / 100)} |
" \(.model): $\(.total_cost)"'
# Total cost for the day
TOTAL_COST=$(echo "${ANALYTICS}" | jq '[.data[].model_breakdown[]?.estimated_cost.amount // 0] | add // 0 | . / 100 | . * 100 | round / 100')
info "Total Claude Code cost for ${REPORT_DATE}: \$${TOTAL_COST}"
pass "7.4 Cost analysis complete"
Validation & Testing
- Run analytics script — verify data returns for active Claude Code users
- Verify per-user session counts, commit counts, and LOC metrics
- Verify tool acceptance rates are calculated correctly
- Confirm cost breakdown by model matches Console dashboard
Expected result: Per-user Claude Code metrics are monitored daily with alerts for anomalies
Monitoring & Maintenance
Ongoing monitoring:
- Daily automated analytics report via cron or CI
- Weekly review of tool acceptance rate trends
- Monthly cost review by user and model
Maintenance schedule:
- Weekly: Review automated analytics reports
- Monthly: Adjust alert thresholds based on team growth
- Quarterly: Full access review correlating Claude Code users with org membership
Compliance Mappings
| Framework | Control ID | Control Description |
|---|---|---|
| SOC 2 | CC7.2 | System monitoring |
| NIST 800-53 | AU-6, SI-4 | Audit record review; system monitoring |
| ISO 27001 | A.12.4.1 | Event logging |
7.5 Enforce Bash Sandbox Isolation
Profile Level: L2 (Hardened)
| Framework | Control |
|---|---|
| NIST 800-53 | SC-39, CM-7 |
| SOC 2 | CC6.1, CC6.8 |
Description
Enable OS-level bash command sandboxing to isolate Claude Code’s subprocess execution. The sandbox restricts filesystem access to the current working directory, routes network traffic through a validating proxy with domain allowlisting, and enforces restrictions at the kernel level using Seatbelt (macOS) or bubblewrap (Linux/WSL2). Set failIfUnavailable: true to prevent Claude Code from starting if sandboxing cannot be established.
Rationale
Why This Matters:
- Without sandboxing, Claude Code bash commands have full access to the developer’s filesystem and network
- A prompt injection or confused agent could read credentials (
~/.aws/credentials,~/.ssh/), exfiltrate data viacurl, or modify system files - Kernel-level enforcement cannot be bypassed by the AI agent — restrictions are irrevocable for the process
allowManagedDomainsOnly: trueprevents developers from approving new network domains at runtime
Attack Prevented: Credential theft via filesystem access, data exfiltration via network, unauthorized file modification, supply chain attacks via package manager hijacking
Prerequisites
- Managed settings deployment (Control 7.1)
- macOS: Seatbelt available (built-in on all supported macOS versions)
- Linux/WSL2: bubblewrap (
bwrap) andsocatinstalled - Inventory of required network domains for development workflows
ClickOps Implementation
Step 1: Enable Sandbox
- Navigate to: claude.ai → Admin Settings → Claude Code → Managed settings
- Add
"sandbox": { "enabled": true, "failIfUnavailable": true }to your managed settings JSON - Set
"allowUnsandboxedCommands": falseto close thedangerouslyDisableSandboxescape hatch
Step 2: Configure Network Allowlist
- Identify required domains:
github.com, package registries (*.npmjs.org,pypi.org), internal services - Add to
sandbox.network.allowedDomainsarray - Set
"allowManagedDomainsOnly": trueto prevent user overrides - Non-allowed domains are blocked automatically without prompting
Step 3: Configure Filesystem Restrictions
- Add sensitive paths to
sandbox.filesystem.denyRead:~/.aws/credentials,~/.ssh/id_*,~/.gnupg/ - Add critical system paths to
sandbox.filesystem.denyWrite:/etc,/usr/local/bin - Optionally set
"allowManagedReadPathsOnly": truefor L3 environments
Step 4: Install Linux Dependencies (if needed)
- On Ubuntu/Debian:
sudo apt install bubblewrap socat - On Fedora/RHEL:
sudo dnf install bubblewrap socat - Verify: Run
/sandboxin Claude Code — should report “Sandbox active”
Time to Complete: ~20 minutes
Code Implementation
Code Pack: Config
// L2 Hardened sandbox configuration.
// Locks filesystem to CWD, denies sensitive credential paths,
// restricts network to approved domains only.
{
"sandbox": {
"enabled": true,
"failIfUnavailable": true,
"autoAllowBashIfSandboxed": false,
"allowUnsandboxedCommands": false,
"excludedCommands": ["docker", "git"],
"filesystem": {
"denyRead": [
"~/.aws/credentials",
"~/.ssh/id_*",
"~/.gnupg/",
"~/.config/gcloud/",
"~/.kube/config"
],
"denyWrite": [
"/etc",
"/usr/local/bin",
"~/.claude/managed-settings.json"
],
"allowWrite": [
"/tmp/build"
]
},
"network": {
"allowedDomains": [
"github.com",
"*.githubusercontent.com",
"*.npmjs.org",
"registry.yarnpkg.com",
"pypi.org",
"files.pythonhosted.org"
],
"allowManagedDomainsOnly": true,
"allowAllUnixSockets": false,
"allowUnixSockets": [],
"allowLocalBinding": false
},
"enableWeakerNestedSandbox": false
}
}
// L3 Maximum Security sandbox configuration.
// No unsandboxed commands, no excluded commands, strict filesystem
// and network lockdown. Suitable for regulated environments.
{
"sandbox": {
"enabled": true,
"failIfUnavailable": true,
"autoAllowBashIfSandboxed": false,
"allowUnsandboxedCommands": false,
"excludedCommands": [],
"filesystem": {
"denyRead": [
"~/.aws/",
"~/.ssh/",
"~/.gnupg/",
"~/.config/gcloud/",
"~/.kube/",
"~/.docker/config.json",
"~/.npmrc",
"~/.pypirc"
],
"allowManagedReadPathsOnly": true,
"denyWrite": [
"/etc",
"/usr",
"/var",
"~/.claude/"
],
"allowWrite": []
},
"network": {
"allowedDomains": [],
"allowManagedDomainsOnly": true,
"allowAllUnixSockets": false,
"allowUnixSockets": [],
"allowLocalBinding": false
},
"enableWeakerNestedSandbox": false,
"enableWeakerNetworkIsolation": false
}
}
Validation & Testing
- Run Claude Code with sandbox enabled — verify
/sandboxshows active status - Attempt to read
~/.aws/credentialsvia Claude Code — should be denied - Attempt to
curla non-allowlisted domain — should be blocked - Set
failIfUnavailable: trueand remove bubblewrap (Linux) — Claude Code should refuse to start - Verify
allowManagedDomainsOnlyprevents user domain approval prompts
Expected result: All bash commands execute in kernel-enforced sandbox; credential paths are unreadable; network limited to approved domains
Monitoring & Maintenance
Ongoing monitoring:
- Monitor for sandbox startup failures via OpenTelemetry metrics
- Track domain approval requests that hit the managed-only block
Maintenance schedule:
- Monthly: Review and update
allowedDomainsas development tooling evolves - Quarterly: Audit
denyRead/denyWritepaths against new credential storage patterns
Operational Impact
| Aspect | Impact Level | Details |
|---|---|---|
| User Experience | Medium | Some commands may fail if domains not allowlisted |
| System Performance | Low | Proxy adds <5ms latency per network request |
| Maintenance Burden | Medium | Domain allowlist needs updating as tooling changes |
| Rollback Difficulty | Easy | Set sandbox.enabled: false in managed settings |
Compliance Mappings
| Framework | Control ID | Control Description |
|---|---|---|
| SOC 2 | CC6.1, CC6.8 | Logical access security; system boundaries |
| NIST 800-53 | SC-39, CM-7 | Process isolation; least functionality |
| ISO 27001 | A.13.1.3 | Network segregation |
7.6 Lock Down Hooks and Plugins
Profile Level: L2 (Hardened)
| Framework | Control |
|---|---|
| NIST 800-53 | CM-7, SI-7 |
| SOC 2 | CC6.1, CC8.1 |
Description
Restrict the Claude Code extensibility surface by enforcing managed-only hooks, controlling plugin marketplace access, and allowlisting HTTP hook destinations. Hooks execute at lifecycle events (PreToolUse, PostToolUse, SessionStart, etc.) and can run arbitrary commands — a malicious hook can exfiltrate data or modify tool behavior. Plugins extend Claude Code with skills, agents, commands, and hooks from external sources.
Rationale
Why This Matters:
- CVE-2025-59536 (CVSS 8.7) demonstrated RCE via malicious hooks in
.claude/settings.json, executing commands before the trust dialog appeared - The Snyk ToxicSkills study (February 2026) found 30+ malicious skills on ClawHub, with 91% combining prompt injection and malicious code
- Publishing a new skill requires only a SKILL.md file and a one-week-old GitHub account — no code signing or security review
- HTTP hooks can exfiltrate session data to attacker-controlled servers if URLs are not restricted
allowManagedHooksOnlyprevents user/project/plugin hooks from executing — only admin-deployed hooks run
Attack Prevented: Malicious hook execution, plugin supply chain compromise, HTTP-based data exfiltration, unauthorized skill installation
Real-World Incidents:
- CVE-2025-59536 (October 2025): RCE via
.claude/settings.jsonhook injection, patched in Claude Code update - Snyk ToxicSkills (February 2026): 30+ malicious skills distributed via ClawHub marketplace targeting Claude Code and OpenClaw users
Prerequisites
- Managed settings deployment (Control 7.1)
- Inventory of approved internal plugin marketplaces
- List of authorized HTTP webhook endpoints
ClickOps Implementation
Step 1: Lock Hooks to Managed-Only
- Navigate to: claude.ai → Admin Settings → Claude Code → Managed settings
- Add
"allowManagedHooksOnly": true— blocks all user, project, and plugin hooks - Define any required hooks directly in managed settings under the
"hooks"key
Step 2: Restrict Plugin Marketplaces
- Add
"strictKnownMarketplaces"with your approved marketplace repos only - Set to empty array
[]to block all marketplace plugin installations - Add
"blockedMarketplaces"for explicitly banned sources — checked before download - Optionally set
"pluginTrustMessage"with org-specific guidance for developers
Step 3: Allowlist HTTP Hook URLs
- Add
"allowedHttpHookUrls": ["https://hooks.example.com/*"]with approved webhook endpoints - Set to empty array
[]to block all HTTP hooks - Add
"httpHookAllowedEnvVars": ["HOOK_AUTH_TOKEN"]to restrict which env vars hooks can access
Time to Complete: ~15 minutes
Code Implementation
Code Pack: Config
// L2 Hardened hook and plugin lockdown.
// Only managed hooks execute; plugin installs restricted to
// approved marketplaces; HTTP hooks limited to internal URLs.
{
"allowManagedHooksOnly": true,
"allowedHttpHookUrls": [
"https://hooks.example.com/*",
"https://security.example.com/api/*"
],
"httpHookAllowedEnvVars": [
"HOOK_AUTH_TOKEN"
],
"strictKnownMarketplaces": [
{
"source": "github",
"repo": "your-org/approved-plugins"
}
],
"blockedMarketplaces": [
{
"source": "github",
"repo": "untrusted-org/plugins"
}
],
"pluginTrustMessage": "Only plugins approved by the security team are permitted. Contact #security-approvals for new plugin requests."
}
// L3 Maximum Security — no plugins, no hooks, no HTTP hook URLs.
// Complete lockdown of extensibility surface.
{
"allowManagedHooksOnly": true,
"disableAllHooks": false,
"allowedHttpHookUrls": [],
"httpHookAllowedEnvVars": [],
"strictKnownMarketplaces": [],
"blockedMarketplaces": [],
"pluginTrustMessage": "Plugin installation is disabled by organizational policy.",
"allowManagedMcpServersOnly": true,
"allowManagedPermissionRulesOnly": true
}
Validation & Testing
- Create a hook in
.claude/settings.json— verify it does not execute whenallowManagedHooksOnlyis true - Attempt to install a plugin from a non-approved marketplace — should be blocked
- Verify
blockedMarketplacesentries are rejected before download - Create an HTTP hook targeting a non-allowlisted URL — verify it is blocked
- Verify
pluginTrustMessageappears during plugin trust prompts
Expected result: Only managed hooks execute; plugins limited to approved sources; HTTP hooks restricted to approved endpoints
Operational Impact
| Aspect | Impact Level | Details |
|---|---|---|
| User Experience | High | Developers cannot install arbitrary plugins or create hooks |
| System Performance | None | Settings evaluated once at startup |
| Maintenance Burden | Medium | Approved marketplace and webhook lists need updates |
| Rollback Difficulty | Easy | Remove restrictive settings from managed config |
Compliance Mappings
| Framework | Control ID | Control Description |
|---|---|---|
| SOC 2 | CC6.1, CC8.1 | Logical access security; change management |
| NIST 800-53 | CM-7, SI-7 | Least functionality; software integrity |
| ISO 27001 | A.12.5.1, A.12.6.1 | Software installation controls; technical vulnerability management |
7.7 Defend Against Prompt Injection and Rules File Attacks
Profile Level: L2 (Hardened)
| Framework | Control |
|---|---|
| NIST 800-53 | SI-10, SI-7 |
| SOC 2 | CC6.1, CC7.2 |
Description
Implement defenses against prompt injection attacks that target Claude Code through repository files. Attackers embed malicious instructions in CLAUDE.md, AGENTS.md, SKILL.md, and other rules files that Claude Code reads as context. These “rules file backdoor” attacks can instruct the AI to exfiltrate data, disable safety features, or execute malicious commands. Deploy automated scanning of rules files and use open-source security tools to detect threats before they execute.
Rationale
Why This Matters:
- Claude Code automatically reads CLAUDE.md, AGENTS.md, and
.claude/directory contents as trusted instructions - Pillar Security’s “Rules File Backdoor” research demonstrated that invisible Unicode characters and carefully crafted instructions in rules files can hijack AI agent behavior
- Lasso Security found that indirect prompt injection through code context can cause Claude to exfiltrate user data via Anthropic’s own APIs
- The InversePrompt attack (CVE-2025-54794, CVE-2025-54795) showed Claude could be turned against itself through crafted prompts
- Open-source tools like claude-code-safety-net provide PreToolUse hooks that catch destructive commands before the permission system evaluates them
Attack Prevented: Data exfiltration via injected instructions, credential theft through rules file manipulation, destructive commands via confused agent, supply chain compromise through malicious skills
Real-World Incidents:
- Pillar Security “Rules File Backdoor” (March 2025): Demonstrated invisible instruction injection in AI agent config files using hidden Unicode characters
- CVE-2025-54794/54795 InversePrompt (2025): Claude turned into data exfiltration tool via crafted prompts, CVSS 8.7
- CVE-2025-59536 (October 2025): RCE via malicious hooks in
.claude/settings.json, CVSS 8.7 — fixed in v1.0.111 - CVE-2025-59828/65099 (2025): Pre-trust-dialog RCE via Yarn config files, CVSS 7.7 — fixed in v1.0.39
- CVE-2026-21852 (January 2026): API key exfiltration via
ANTHROPIC_BASE_URLoverride in repo settings, CVSS 5.3 — fixed in v2.0.65 - Lasso Security (2026): Indirect prompt injection causing 30MB data uploads via Anthropic APIs
- Snyk ToxicSkills (February 2026): 534 skills (13.4%) with critical issues, 76 confirmed malicious payloads on ClawHub; 12% of entire registry compromised during ClawHavoc campaign
- PromptArmor / Cowork (January 2026): File exfiltration from Claude Cowork via prompt injection using Anthropic’s own whitelisted API as exfil channel
- Oasis Security “Claudy Day” (March 2026): Chained invisible prompt injection, Anthropic Files API exfil, and open redirect for complete attack pipeline
- Postmark-MCP (September 2025): Malicious MCP server on npm BCC’d all outgoing emails to attacker — 1,643 downloads affected
Prerequisites
- Git pre-commit hook infrastructure or CI/CD pipeline
- Familiarity with Claude Code rules file locations (CLAUDE.md, AGENTS.md,
.claude/directory)
ClickOps Implementation
Step 1: Scan Rules Files Before Trusting Repositories
- Before opening any new repository with Claude Code, review
CLAUDE.mdand.claude/directory contents - Look for: encoded payloads (base64), invisible Unicode characters, instruction override patterns, network exfiltration commands
- Check for files with hidden characters:
cat -v CLAUDE.md | grep -c '\^'
Step 2: Install Protective Hooks
- Install claude-code-safety-net via Claude Code plugin marketplace:
- Run
/plugin marketplace add kenryu42/cc-marketplace - Run
/plugin install safety-net@cc-marketplace - Run
/reload-plugins
- Run
- Safety Net acts as a PreToolUse hook that catches destructive git and filesystem commands before execution
- It inspects commands before the permission system, providing a fallback layer
Step 3: Deploy Rules File Scanning in CI
- Add the HTH rules file scanner script as a pre-commit hook or CI step
- The scanner checks for: data exfiltration patterns, encoded payloads, invisible Unicode, instruction override attempts, safety bypass requests
- Configure to run on every PR that modifies
CLAUDE.md,AGENTS.md, or.claude/directory
Step 4: Use Security Scanner Plugins (Optional)
- Install vexscan for comprehensive plugin/skill scanning: Detects malicious patterns in plugins, skills, MCP servers, and hooks using pattern detection and AI-powered analysis. Source:
github.com/edimuj/vexscan-claude-code - Use Snyk agent-scan to audit MCP server configurations for vulnerabilities. Source:
github.com/snyk/agent-scan(Apache-2.0) - Use Cisco mcp-scanner to scan MCP servers for tool poisoning, excessive permissions, and SSRF risks. Source:
github.com/cisco-ai-defense/mcp-scanner(Apache-2.0) - Deploy Wiz secure-rules-files as baseline CLAUDE.md templates that enforce secure coding patterns. Source:
github.com/wiz-sec-public/secure-rules-files
Time to Complete: ~30 minutes
Code Implementation
Code Pack: API Script
set -euo pipefail
TARGET_DIR="${1:-.}"
FINDINGS=0
echo "=== Claude Code Rules File Security Scanner ==="
echo "Scanning: ${TARGET_DIR}"
echo ""
# Patterns commonly found in prompt injection attacks against AI coding agents.
# Sources: Pillar Security "Rules File Backdoor" (2025), Snyk ToxicSkills (2026),
# Lasso Security indirect prompt injection research (2026).
SUSPICIOUS_PATTERNS=(
# Data exfiltration via network commands
'curl\s+.*\$'
'wget\s+.*\$'
'fetch\(.*\$'
'nc\s+-'
# Encoded payloads hiding instructions
'base64\s+--decode'
'eval\s*\('
'exec\s*\('
# Invisible Unicode characters used to hide instructions
'\xe2\x80\x8b' # zero-width space
'\xe2\x80\x8c' # zero-width non-joiner
'\xe2\x80\x8d' # zero-width joiner
'\xef\xbb\xbf' # BOM in middle of file
# Instruction override attempts
'ignore\s+(all\s+)?previous\s+instructions'
'disregard\s+(all\s+)?prior'
'override\s+system\s+prompt'
'you\s+are\s+now\s+in\s+.*mode'
'new\s+instructions:'
'IMPORTANT:\s*override'
# Exfiltration via MCP or tool abuse
'mcp.*install.*--force'
'plugin.*install.*--trust'
# Requests to disable safety
'dangerously-skip-permissions'
'bypass.*permission'
'disable.*safety'
'allow.*all.*commands'
)
# Files to scan: CLAUDE.md, AGENTS.md, skills, hooks, and plugin configs
SCAN_FILES=()
while IFS= read -r -d '' file; do
SCAN_FILES+=("$file")
done < <(find "$TARGET_DIR" \
\( -name "CLAUDE.md" -o -name "AGENTS.md" -o -name "GEMINI.md" \
-o -name "COPILOT.md" -o -name "*.skill.md" -o -name "SKILL.md" \
-o -path "*/.claude/settings.json" \
-o -path "*/.claude/settings.local.json" \
-o -path "*/.claude/agents/*.md" \
-o -path "*/.claude/commands/*.md" \) \
-not -path "*/node_modules/*" \
-not -path "*/.git/*" \
-print0 2>/dev/null)
if [ ${#SCAN_FILES[@]} -eq 0 ]; then
echo "No rules files found to scan."
exit 0
fi
echo "Found ${#SCAN_FILES[@]} rules file(s) to scan."
echo ""
for file in "${SCAN_FILES[@]}"; do
echo "--- Scanning: ${file} ---"
for pattern in "${SUSPICIOUS_PATTERNS[@]}"; do
matches=$(grep -cEi "$pattern" "$file" 2>/dev/null || true)
if [ "$matches" -gt 0 ]; then
echo " [ALERT] Pattern matched ($matches occurrences): $pattern"
grep -nEi "$pattern" "$file" 2>/dev/null | head -3 | while read -r line; do
echo " $line"
done
FINDINGS=$((FINDINGS + matches))
fi
done
done
echo ""
echo "=== Scan Complete ==="
echo "Total suspicious patterns found: ${FINDINGS}"
if [ "$FINDINGS" -gt 0 ]; then
echo ""
echo "ACTION REQUIRED: Review flagged patterns before trusting this repository."
echo "Not all findings are malicious — review context carefully."
echo "See: howtoharden.com/guides/anthropic-claude/#77"
exit 1
fi
echo "No suspicious patterns detected."
exit 0
Validation & Testing
- Run the rules file scanner against a clean repository — should return exit code 0
- Create a test CLAUDE.md with
ignore all previous instructions— scanner should flag it - Create a test file with invisible Unicode (zero-width space) — scanner should detect it
- Verify claude-code-safety-net blocks
git reset --hardandrm -rf /commands - Verify scanner runs in CI on PRs modifying rules files
Expected result: Malicious rules files detected before Claude Code processes them; destructive commands caught by safety-net hook
Monitoring & Maintenance
Ongoing monitoring:
- CI pipeline alerts when rules file scanner finds suspicious patterns
- Review safety-net hook blocks in Claude Code session logs
Maintenance schedule:
- Monthly: Update scanner patterns as new attack techniques emerge
- Quarterly: Review security research for new prompt injection vectors
Operational Impact
| Aspect | Impact Level | Details |
|---|---|---|
| User Experience | Low | Scanner runs in background; safety-net is transparent for safe commands |
| System Performance | Low | Scanner adds <5s to pre-commit; safety-net adds negligible latency |
| Maintenance Burden | Low | Scanner patterns updated infrequently |
| Rollback Difficulty | Easy | Remove pre-commit hook or uninstall plugin |
Compliance Mappings
| Framework | Control ID | Control Description |
|---|---|---|
| SOC 2 | CC6.1, CC7.2 | Logical access security; system monitoring |
| NIST 800-53 | SI-10, SI-7 | Information input validation; software integrity |
| ISO 27001 | A.12.2.1, A.14.2.8 | Controls against malware; system security testing |
7.8 Harden Claude Code in CI/CD Pipelines
Profile Level: L2 (Hardened)
| Framework | Control |
|---|---|
| NIST 800-53 | SA-11, SA-15 |
| SOC 2 | CC7.1, CC8.1 |
Description
Secure Claude Code when used in CI/CD pipelines via GitHub Actions. Unlike GitHub Copilot which includes a network firewall by default, anthropics/claude-code-action operates without network restrictions, giving unrestricted access to external resources. Use step-security/harden-runner to monitor and control network egress, and anthropics/claude-code-security-review for automated security analysis of pull requests.
Rationale
Why This Matters:
- Claude Code in GitHub Actions has unrestricted network access by default — a compromised or confused agent can exfiltrate secrets to any external server
- The
ANTHROPIC_API_KEYsecret is available to the action and could be stolen via network exfiltration harden-runnerbuilds a baseline of allowed outbound connections and can block or alert on anomalous network callsclaude-code-security-reviewprovides AI-powered security analysis but is not hardened against prompt injection — only use on trusted PRs- Tool restrictions (
allowed_tools/disallowed_tools) limit what Claude Code can do in CI context
Attack Prevented: Secret exfiltration via CI network access, unauthorized API calls from CI, malicious code generation in automated PRs, supply chain attacks through CI/CD
Real-World Incidents:
- StepSecurity research (2026): Documented unrestricted network access in claude-code-action as a security gap vs. GitHub Copilot’s default firewall
Prerequisites
- GitHub Actions workflow infrastructure
- Anthropic API key stored as GitHub Actions secret
- Understanding of
anthropics/claude-code-actionandanthropics/claude-code-security-reviewActions
ClickOps Implementation
Step 1: Add Harden-Runner to Claude Code Workflows
- Add
step-security/harden-runneras the first step in any job using Claude Code - Start with
egress-policy: auditto build a baseline of expected network connections - After baseline is established, switch to
egress-policy: blockwith explicitallowed-endpoints - Required endpoints:
api.anthropic.com:443,github.com:443,api.github.com:443
Step 2: Configure Claude Code Action with Tool Restrictions
- Use
allowed_toolsto restrict Claude Code to safe operations:Read,Glob,Grep,Agent - Use
disallowed_toolsto block dangerous tools:Bash,WebFetch,WebSearch - Set
max_turnsto limit agent loops (recommended: 10-20 for review tasks) - Pin the action by SHA, not tag (see Control 7.8 CI/CD workflow example)
Step 3: Add Security Review to PR Workflows
- Add
anthropics/claude-code-security-reviewaction to PR workflows - WARNING: Only use on trusted PRs from your organization — the action is not hardened against prompt injection
- Do not enable on fork PRs or PRs from external contributors
Step 4: Set Minimal Permissions
- Set workflow-level
permissions: {}(no permissions by default) - Grant only required permissions per job:
contents: read,pull-requests: write - Never use
permissions: write-allfor Claude Code workflows
Time to Complete: ~20 minutes
Code Implementation
Code Pack: Config
name: Hardened Claude Code CI
on:
pull_request:
types: [opened, synchronize]
permissions: {}
jobs:
security-review:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
issues: read
steps:
# Network egress monitoring via StepSecurity Harden-Runner.
# Builds a baseline of allowed outbound connections and blocks
# any future calls not in the baseline.
# Source: github.com/step-security/harden-runner (Apache-2.0)
- name: Harden Runner
uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0
with:
egress-policy: audit
allowed-endpoints: >
api.anthropic.com:443
github.com:443
api.github.com:443
objects.githubusercontent.com:443
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 0
# Claude Code security review analyzes PR diffs for vulnerabilities.
# Source: github.com/anthropics/claude-code-security-review (MIT)
# WARNING: Only use on trusted PRs — not hardened against prompt injection.
- name: Claude Security Review
uses: anthropics/claude-code-security-review@4c30e5b23b045e24fc98a810f318f8ad4aad1539 # v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
model: claude-sonnet-4-6
claude-code-task:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
issues: read
steps:
- name: Harden Runner
uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0
with:
egress-policy: audit
allowed-endpoints: >
api.anthropic.com:443
github.com:443
api.github.com:443
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
# Claude Code Action runs Claude as a CI agent.
# Source: github.com/anthropics/claude-code-action (MIT)
# Use allowed_tools and disallowed_tools to restrict capabilities.
- name: Claude Code
uses: anthropics/claude-code-action@3e460685e1084c53f5a6ddd25eab3a0d3fa4b9a6 # v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
model: claude-sonnet-4-6
allowed_tools: "Read,Glob,Grep,Agent"
disallowed_tools: "Bash,WebFetch,WebSearch"
max_turns: "10"
Validation & Testing
- Verify harden-runner is the first step in Claude Code CI jobs
- Run workflow in audit mode — review network connection baseline
- Verify
allowed_tools/disallowed_toolsrestrict Claude Code capabilities - Verify
max_turnslimits agent execution length - Confirm security review action runs only on trusted PRs (not forks)
Expected result: Claude Code CI workflows have monitored network egress, restricted tool access, and automated security review
Operational Impact
| Aspect | Impact Level | Details |
|---|---|---|
| User Experience | Low | Security checks run automatically in CI |
| System Performance | Low | Harden-runner adds <10s to job startup |
| Maintenance Burden | Medium | Network baseline needs updating when new endpoints are added |
| Rollback Difficulty | Easy | Remove harden-runner step from workflow |
Compliance Mappings
| Framework | Control ID | Control Description |
|---|---|---|
| SOC 2 | CC7.1, CC8.1 | Vulnerability management; change management |
| NIST 800-53 | SA-11, SA-15 | Developer security testing; development process |
| ISO 27001 | A.14.2.1, A.14.2.8 | Secure development policy; system security testing |
7.9 Deploy External Sandbox Tooling
Profile Level: L3 (Maximum Security)
| Framework | Control |
|---|---|
| NIST 800-53 | SC-39, SC-7 |
| SOC 2 | CC6.1, CC6.8 |
Description
Deploy kernel-enforced sandbox tools that wrap Claude Code in an isolation layer independent of Claude’s own built-in sandbox. These tools provide defense-in-depth: even if Claude Code’s sandbox is bypassed, kernel-level restrictions (Landlock, Seatbelt) or container-level isolation remain enforced. Recommended open-source options: nono (kernel-enforced sandbox with credential protection and atomic rollback), NVIDIA OpenShell (container-based sandbox with network policy enforcement), Trail of Bits devcontainer (Docker-based sandboxed environment for security audits), and Stacklok CodeGate (security proxy/gateway intercepting AI assistant requests to detect secrets leakage and malicious packages).
Rationale
Why This Matters:
- Claude Code’s built-in sandbox is controlled by Claude Code itself — a vulnerability in Claude Code could theoretically bypass its own sandbox
- External sandboxes operate at the kernel or container level, outside Claude Code’s control
- nono uses Landlock (Linux) and Seatbelt (macOS) to create irrevocable restrictions — once applied, not even nono itself can remove them
- OpenShell provides container-based isolation where API keys never touch disk and network egress is policy-controlled
- Both tools provide cryptographic audit trails for compliance and incident response
Attack Prevented: Sandbox escape, credential exposure via filesystem, network exfiltration bypassing built-in controls, unauthorized privilege escalation
Prerequisites
- nono: macOS or Linux, Homebrew (optional, for easy install)
- OpenShell: Linux with container runtime support,
curlfor installer - Understanding that these are complementary to (not replacements for) Claude Code’s built-in sandbox
ClickOps Implementation
Option A: nono (Kernel-Enforced Sandbox)
Step 1: Install nono
- macOS/Linux:
brew install nono - Verify:
nono --version
Step 2: Run Claude Code in nono
- Basic:
nono run --profile claude-code -- claude - Hardened: Add
--rollbackfor filesystem snapshots,--supervisedfor interactive approval,--proxy-credentialto inject API keys without disk exposure - The
claude-codeprofile grants read/write to CWD only, network via allowlisted proxy, credential injection without disk exposure
Step 3: Review Audit Trail
nono audit list— view all recorded sessionsnono audit show <session-id> --json— detailed session auditnono rollback list— view available restore pointsnono rollback restore— restore to pre-session state
Option B: NVIDIA OpenShell (Container Sandbox)
Step 1: Install OpenShell
curl -LsSf https://raw.githubusercontent.com/NVIDIA/OpenShell/main/install.sh | sh- Verify:
openshell --version
Step 2: Launch Claude Code in Sandbox
openshell sandbox create -- claude- OpenShell auto-detects
ANTHROPIC_API_KEY, creates a provider, and injects credentials without persisting to disk - Filesystem is locked at creation, network blocked by default
Step 3: Apply Security Policy
- Create a YAML policy file defining filesystem, network, and process restrictions
openshell policy set hardened-claude --policy ./claude-policy.yaml- Static policies (filesystem, process) locked at creation; dynamic policies (network) hot-reloadable
Step 4: Monitor
openshell term— real-time terminal UIopenshell logs --tail— stream sandbox logs
Time to Complete: ~15 minutes per tool
Code Implementation
Code Pack: API Script
# ── nono: Kernel-Enforced Agent Sandbox ──
# Source: github.com/always-further/nono (Apache-2.0)
# Platforms: macOS (Seatbelt), Linux (Landlock)
# Docs: docs.nono.sh
# Install nono via Homebrew
brew install nono
# Run Claude Code inside nono with the built-in profile.
# The claude-code profile grants:
# - Read/write to CWD only
# - Network access via allowlisted proxy
# - Credential injection without disk exposure
# - Filesystem snapshots for atomic rollback
nono run --profile claude-code -- claude
# Custom hardened invocation:
# --rollback Enable filesystem snapshot/restore
# --proxy-credential Inject API key via HTTPS proxy (never touches disk)
# --supervised Require interactive approval for flagged operations
nono run \
--profile claude-code \
--rollback \
--proxy-credential anthropic-api-key \
--supervised \
-- claude
# Audit trail: review all actions taken during a session
nono audit list
nono audit show <session-id> --json
# Rollback: restore filesystem to pre-session state
nono rollback list
nono rollback restore
# ── NVIDIA OpenShell: Container-Based Agent Sandbox ──
# Source: github.com/NVIDIA/OpenShell (Apache-2.0)
# Platforms: Linux (container-based via K3s)
# Docs: github.com/NVIDIA/OpenShell/tree/main/docs
# Install OpenShell
curl -LsSf https://raw.githubusercontent.com/NVIDIA/OpenShell/main/install.sh | sh
# Launch Claude Code in an isolated sandbox.
# OpenShell auto-detects ANTHROPIC_API_KEY, creates a provider,
# and injects credentials without persisting them to disk.
openshell sandbox create -- claude
# Apply a custom security policy (YAML-based).
# Static policies (filesystem, process) are locked at creation.
# Dynamic policies (network, inference) can be hot-reloaded.
openshell policy set hardened-claude --policy ./claude-policy.yaml
# Monitor sandbox activity in real time
openshell term
# View sandbox logs
openshell logs --tail
# List and manage running sandboxes
openshell sandbox list
openshell sandbox connect <name>
Validation & Testing
- Install nono — verify
nono --versionreturns version - Run
nono run --profile claude-code -- claude— verify sandbox active - Attempt to read
~/.ssh/id_rsafrom within nono sandbox — should be denied - Install OpenShell — verify
openshell --versionreturns version - Run
openshell sandbox create -- claude— verify isolated container launches - Verify
nono audit listshows session history
Expected result: Claude Code runs inside kernel-enforced or container-enforced sandbox with full audit trail
Operational Impact
| Aspect | Impact Level | Details |
|---|---|---|
| User Experience | Medium | Developers must launch Claude Code through wrapper command |
| System Performance | Low | Kernel sandbox adds negligible overhead; container adds ~1s startup |
| Maintenance Burden | Low | Profiles maintained by tool projects; custom policies need occasional updates |
| Rollback Difficulty | Easy | Stop using the wrapper; Claude Code runs normally |
Compliance Mappings
| Framework | Control ID | Control Description |
|---|---|---|
| SOC 2 | CC6.1, CC6.8 | Logical access security; system boundaries |
| NIST 800-53 | SC-39, SC-7 | Process isolation; boundary protection |
| ISO 27001 | A.13.1.3, A.13.1.1 | Network segregation; network controls |
7.10 Govern Claude Cowork and Collaborative Sessions
Profile Level: L2 (Hardened)
| Framework | Control |
|---|---|
| NIST 800-53 | AC-3, AU-6 |
| SOC 2 | CC6.1, CC7.2 |
Description
Configure governance controls for Claude Cowork collaborative sessions, including channel restrictions, session retention policies, organizational login enforcement, and auto-mode restrictions. Claude Cowork enables multi-user collaborative AI sessions — without governance, sensitive data may be shared across session boundaries and audit trails may be incomplete.
Rationale
Why This Matters:
- Cowork activity does not currently appear in audit logs, the Compliance API, or data exports — this is a significant visibility gap across all tiers
- Without
forceLoginMethodandforceLoginOrgUUID, developers can use personal Claude accounts, bypassing organizational security policies - Channels enable external message delivery to Claude Code sessions — without restrictions, unauthorized plugins could push messages
cleanupPeriodDays: 0deletes all transcripts at startup and disables session persistence entirely — critical for environments handling classified or regulated datadisableAutoModeprevents the auto-mode classifier from running, ensuring all tool operations require explicit permission evaluation
Attack Prevented: Data leakage through uncontrolled collaboration, shadow AI usage via personal accounts, unauthorized channel message injection, session transcript exposure, data exfiltration via Chrome automation, unattended scheduled task abuse
Critical Limitations (as of March 2026):
- Cowork activity is excluded from audit logs, the Compliance API, and data exports — a complete visibility blind spot across all tiers
- All conversation history is stored locally on user machines with no centralized management or admin export
- Cowork access is all-or-nothing at the organization level — no per-user or per-role controls during research preview
- Scheduled tasks run unattended while the app is open with no built-in approval workflows
- Chrome automation can screenshot, click, fill forms, and execute JavaScript on any non-blocked site
- A demonstrated attack (reported October 2025) showed prompt injection in documents could trigger
curltoapi.anthropic.comfile upload using attacker credentials, exfiltrating victim files via a whitelisted domain
Prerequisites
- Claude Team or Enterprise plan
- Managed settings deployment (Control 7.1)
- Organization UUID (found in Claude.ai admin settings)
ClickOps Implementation
Step 1: Enforce Organizational Login
- Navigate to: claude.ai → Admin Settings → Claude Code → Managed settings
- Add
"forceLoginMethod": "claudeai"to require Claude.ai account login - Add
"forceLoginOrgUUID": "your-org-uuid"to auto-select the organization - This prevents developers from using personal accounts or switching organizations
Step 2: Disable Channels (L2)
- Add
"channelsEnabled": falseto block channel message delivery - Add
"allowedChannelPlugins": []to block all channel plugins - For L2 environments that need channels: use
allowedChannelPluginswith specific approved plugins only
Step 3: Configure Session Retention
- Set
"cleanupPeriodDays": 7for standard environments (7-day retention) - Set
"cleanupPeriodDays": 0for maximum security (no transcript retention, no session persistence) - Note: Setting to 0 disables
/resumefunctionality
Step 4: Restrict Auto-Mode
- For L2: Add
"disableAutoMode": "disable"to prevent auto-mode activation entirely. This ensures all tool operations go through explicit permission evaluation and removesautofrom theShift+Tabpermission mode cycle - For organizations that choose to allow auto-mode: configure
autoMode.environmentwith trusted infrastructure descriptions (repos, domains, cloud buckets),autoMode.soft_denywith natural-language block rules, andautoMode.allowwith explicit exceptions. Useclaude auto-mode critiqueto get AI feedback on custom rules before deployment
Step 5: Disable Chrome in Cowork (L2)
- Navigate to: claude.ai → Admin Settings → Capabilities
- Disable “Chrome in Cowork” — this prevents Claude from automating browser actions (screenshots, clicks, form fills, JavaScript execution)
- Note: Chrome is disabled by default on Enterprise but enabled by default on Team — verify your tier’s default
- If Chrome is required: Create a domain allowlist for approved sites only; financial services, banking, and crypto sites are blocked by default but healthcare portals, cloud consoles, and HR systems are not
Step 6: Configure Company Announcements
- Add
"companyAnnouncements"with security policy reminders - Messages display at startup; multiple announcements are cycled randomly
Step 7: Restrict Connector Write Access
- Review all enabled connectors (Google Drive, Gmail, Slack, GitHub, etc.) in Admin Settings
- For each connector, set per-tool permissions: Allow (runs automatically), Ask (requires confirmation), or Block (never runs)
- Block all write-access connector tools (
send_email,post_message,create_file) unless explicitly justified - Keep read-only access where needed; disable connectors not required by your workflows
Step 8: Implement Tenant Restrictions (L3)
- Configure network proxy with TLS inspection to inject
anthropic-allowed-org-idsHTTP header to block personal account access - Without tenant restrictions, developers can switch to personal Claude accounts and bypass all organizational controls
- Tenant restrictions are Enterprise-only and require network proxy configuration
Step 9: Address Local Storage Risks
- All Cowork conversation history and project data is stored locally on each user’s machine — there is no centralized storage or admin export capability
- Ensure endpoint disk encryption (FileVault on macOS, BitLocker on Windows) is enforced via MDM
- Deploy EDR on all machines running Claude Desktop to detect anomalous file access patterns
- Set
cleanupPeriodDaysto minimize transcript retention exposure
Time to Complete: ~15 minutes
Code Implementation
Code Pack: Config
// L2 Hardened Cowork governance configuration.
// Disables channels by default, enforces org login,
// restricts session retention, and controls auto-mode.
{
"channelsEnabled": false,
"allowedChannelPlugins": [],
"forceLoginMethod": "claudeai",
"forceLoginOrgUUID": "REPLACE-WITH-YOUR-ORG-UUID",
"cleanupPeriodDays": 7,
"disableAutoMode": "disable",
"env": {
"CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1"
},
"companyAnnouncements": [
"Reminder: All Claude Code sessions are subject to organizational security policies. Report suspicious behavior with /feedback."
]
}
// L3 Maximum Security Cowork configuration.
// Zero session retention, channels disabled, auto-mode disabled,
// org-locked login, and full managed-only lockdown.
{
"channelsEnabled": false,
"allowedChannelPlugins": [],
"forceLoginMethod": "claudeai",
"forceLoginOrgUUID": "REPLACE-WITH-YOUR-ORG-UUID",
"cleanupPeriodDays": 0,
"disableAutoMode": "disable",
"allowManagedPermissionRulesOnly": true,
"allowManagedHooksOnly": true,
"allowManagedMcpServersOnly": true,
"strictKnownMarketplaces": [],
"availableModels": ["sonnet"],
"env": {
"CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1"
},
"companyAnnouncements": [
"This environment operates under maximum security policy. Session transcripts are not retained. All extensions are disabled."
]
}
Validation & Testing
- Verify
forceLoginMethodrestricts login to Claude.ai accounts only - Verify
forceLoginOrgUUIDauto-selects the correct organization - Verify channels are disabled — no external messages delivered
- Set
cleanupPeriodDays: 0— verify no.jsonltranscripts are written - Verify
disableAutoModeremoves auto from permission mode options - Verify company announcements display at startup
Expected result: Collaborative sessions governed by organizational policy; personal account access blocked; session retention controlled
Monitoring & Maintenance
Ongoing monitoring:
- Monitor for login attempts outside the forced organization
- Track channel message delivery attempts (if channels selectively enabled)
- Note: Cowork audit logs are currently limited — plan for enhanced logging when Anthropic adds support
Maintenance schedule:
- Monthly: Review company announcements for relevance
- Quarterly: Audit organization UUID and login enforcement settings
- Ongoing: Monitor Anthropic documentation for Cowork audit log improvements
Operational Impact
| Aspect | Impact Level | Details |
|---|---|---|
| User Experience | Medium | Developers cannot use personal accounts or auto-mode |
| System Performance | None | Settings evaluated once at startup |
| Maintenance Burden | Low | Settings rarely change once configured |
| Rollback Difficulty | Easy | Remove governance settings from managed config |
Compliance Mappings
| Framework | Control ID | Control Description |
|---|---|---|
| SOC 2 | CC6.1, CC7.2 | Logical access security; system monitoring |
| NIST 800-53 | AC-3, AU-6 | Access enforcement; audit record review |
| ISO 27001 | A.9.4.1, A.12.4.1 | Information access restriction; event logging |
8. Compliance Quick Reference
SOC 2 Trust Services Criteria Mapping
| Control ID | Anthropic Claude Control | Guide Section |
|---|---|---|
| CC6.1 | Enforce SSO, Least-Privilege Roles, API Key Scoping, Managed Settings, Sandbox, Hooks/Plugins, Prompt Injection Defense, Cowork Governance | 1.1, 1.2, 1.3, 2.1, 2.2, 7.1, 7.2, 7.5, 7.6, 7.7, 7.10 |
| CC6.2 | Invite Management, Workspace Membership | 3.2, 6.1 |
| CC6.3 | Role-Based Access, Workspace Scoping | 1.2, 2.1, 3.2 |
| CC6.6 | Workspace Segmentation, Data Residency | 1.3, 3.1, 4.1 |
| CC6.8 | Spend Limits, Sandbox Boundaries, External Sandbox | 5.2, 7.5, 7.9 |
| CC7.1 | CI/CD Pipeline Security | 7.8 |
| CC7.2 | Usage Monitoring, Cost Monitoring, Prompt Injection Detection, Cowork Audit | 5.1, 5.2, 7.7, 7.10 |
| CC8.1 | Managed Settings, Change Management, Hook/Plugin Governance, CI/CD Hardening | 7.1, 7.6, 7.8 |
| CC9.2 | Integration Risk Assessment, MCP Server Control | 6.2, 7.3 |
NIST 800-53 Rev 5 Mapping
| Control | Anthropic Claude Control | Guide Section |
|---|---|---|
| AC-2 | Account Management, Workspace Membership | 3.2, 6.1 |
| AC-3 | API Key Scoping, Workspace Isolation | 2.1 |
| AC-4 | Workspace Segmentation | 3.1 |
| AC-6 | Least-Privilege Roles | 1.2, 2.1, 3.2 |
| AU-6 | Usage Monitoring | 5.1 |
| IA-2 | SSO Enforcement | 1.1 |
| IA-5 | Admin Key Protection, Key Rotation | 1.3, 2.2 |
| IA-8 | Non-Organization User Authentication | 1.1 |
| RA-3 | Integration Risk Assessment | 6.2 |
| SA-9 | External Service Controls, Spend Limits | 5.2, 6.2 |
| SC-7 | Workspace Boundaries, Data Residency | 3.1, 4.1 |
| SI-4 | System Monitoring | 5.1, 5.2 |
| CM-6 | Managed Configuration Settings | 7.1 |
| CM-7 | Least Functionality, Tool Restrictions, MCP Control, Sandbox, Hooks/Plugins | 7.1, 7.2, 7.3, 7.5, 7.6 |
| SA-11 | Developer Security Testing, CI/CD Hardening | 7.8 |
| SA-15 | Development Process Security | 7.8 |
| SC-7 | Boundary Protection, External Sandbox | 3.1, 4.1, 7.9 |
| SC-39 | Process Isolation, Sandbox Enforcement | 7.5, 7.9 |
| SI-7 | Software Integrity, Hook/Plugin Validation | 7.6, 7.7 |
| SI-10 | Information Input Validation, Prompt Injection Defense | 7.7 |
| SI-12 | Data Retention | 4.2 |
ISO 27001:2022 Mapping
| Control | Anthropic Claude Control | Guide Section |
|---|---|---|
| A.8.10 | Data Retention and Deletion | 4.2 |
| A.9.2.1 | User Registration | 1.1, 6.1 |
| A.9.2.3 | Privileged Access Management | 1.2 |
| A.9.2.5 | User Access Review | 3.2 |
| A.9.3.1 | Secret Authentication Information | 2.2 |
| A.9.4.1 | Information Access Restriction | 2.1 |
| A.9.4.3 | Password/Key Management | 1.3 |
| A.12.1.3 | Capacity Management | 5.2 |
| A.12.4.1 | Event Logging | 5.1 |
| A.12.2.1 | Controls Against Malware | 7.7 |
| A.12.5.1 | Software Installation Controls | 7.6 |
| A.12.6.1 | Technical Vulnerability Management | 7.6 |
| A.13.1.1 | Network Controls | 7.9 |
| A.13.1.3 | Network Segregation | 3.1, 7.5, 7.9 |
| A.14.2.1 | Secure Development Policy | 7.8 |
| A.14.2.8 | System Security Testing | 7.7, 7.8 |
| A.15.1.2 | Supplier Security | 6.2 |
| A.18.1.4 | Privacy Protection | 4.1 |
Appendix A: Edition/Tier Compatibility
| Control | API (All Tiers) | Team | Enterprise |
|---|---|---|---|
| 1.1 Enforce SSO | N/A (API-only) | ✅ | ✅ |
| 1.2 Least-Privilege Roles | ✅ | ✅ | ✅ |
| 1.3 Admin Key Protection | ✅ | ✅ | ✅ |
| 2.1 API Key Scoping | ✅ | ✅ | ✅ |
| 2.2 API Key Rotation | ✅ | ✅ | ✅ |
| 3.1 Workspace Segmentation | ✅ | ✅ | ✅ |
| 3.2 Workspace Membership | ✅ | ✅ | ✅ |
| 4.1 Data Residency | ✅ | ✅ | ✅ |
| 4.2 Custom Data Retention | ❌ | ❌ | ✅ |
| 4.2 Zero Data Retention | ❌ | ❌ | ✅ (by arrangement) |
| 5.1 Usage Monitoring | ✅ | ✅ | ✅ |
| 5.2 Spend Limits | ✅ | ✅ | ✅ |
| 6.1 Invite Auditing | ✅ | ✅ | ✅ |
| 6.2 Integration Risk | ✅ | ✅ | ✅ |
| 7.1 Managed Settings (MDM) | ✅ (MDM only) | ✅ | ✅ |
| 7.1 Server-Managed Settings | ❌ | ✅ (v2.1.38+) | ✅ (v2.1.30+) |
| 7.2 Permission Restrictions | ✅ (MDM only) | ✅ | ✅ |
| 7.3 MCP Server Control | ✅ (MDM only) | ✅ | ✅ |
| 7.4 Claude Code Analytics | ✅ | ✅ | ✅ |
| 7.5 Bash Sandbox Isolation | ✅ (MDM only) | ✅ | ✅ |
| 7.6 Hooks & Plugin Lockdown | ✅ (MDM only) | ✅ | ✅ |
| 7.7 Prompt Injection Defense | ✅ (open-source tools) | ✅ | ✅ |
| 7.8 CI/CD Pipeline Hardening | ✅ (GitHub Actions) | ✅ | ✅ |
| 7.9 External Sandbox (nono/OpenShell) | ✅ (open-source tools) | ✅ | ✅ |
| 7.10 Cowork Governance | ❌ | ✅ | ✅ |
| SCIM Provisioning | ❌ | ❌ | ✅ |
| Audit Logs | ❌ | ❌ | ✅ |
Appendix B: References
Official Anthropic Documentation:
- Admin API Overview
- Admin API Reference
- Workspaces Guide
- Rate Limits
- Data Residency
- Zero Data Retention
- Usage and Cost API
Identity Provider Integration:
Claude Code Security:
- Claude Code Security Best Practices
- Claude Code Sandboxing
- Claude Code Permissions
- Claude Code Settings Reference
- Claude Code Hooks
- Claude Code MCP Configuration
- Claude Code Monitoring (OpenTelemetry)
- Claude Code GitHub Actions
Open-Source Security Tools:
- nono — Kernel-Enforced Agent Sandbox (Apache-2.0)
- NVIDIA OpenShell — Container Agent Sandbox (Apache-2.0)
- claude-code-safety-net — Destructive Command Hook (MIT)
- claude-code-security-review — AI Security Review Action (MIT)
- claude-code-action — GitHub Actions Integration (MIT)
- step-security/harden-runner — CI/CD Network Egress Control (Apache-2.0)
- snyk/agent-scan — AI Agent and MCP Server Security Scanner (Apache-2.0)
- stacklok/toolhive — Enterprise MCP Server Management (Apache-2.0)
- cisco-ai-defense/mcp-scanner — MCP Threat Scanner (Apache-2.0)
- stacklok/codegate — AI Coding Assistant Security Gateway (Apache-2.0)
- trailofbits/claude-code-devcontainer — Sandboxed Devcontainer (Apache-2.0)
- wiz-sec-public/secure-rules-files — Baseline Secure CLAUDE.md Files
- seojoonkim/prompt-guard — Prompt Injection Defense System (MIT)
- vexscan — Plugin/Skill Security Scanner
Security Research:
- Pillar Security: Rules File Backdoor Attack (2025)
- Snyk: ToxicSkills — Malicious AI Agent Skills Study (February 2026)
- Lasso Security: Indirect Prompt Injection in Claude Code (2026)
- Cymulate: InversePrompt (CVE-2025-54794, CVE-2025-54795) (2025)
- StepSecurity: Securing claude-code-action in GitHub Actions (2026)
- Check Point: RCE and API Token Exfiltration via Claude Code (CVE-2025-59536) (2026)
- PromptArmor: Claude Cowork File Exfiltration (2026)
- Oasis Security: Claudy Day — Claude.ai Prompt Injection Chain (2026)
- Invariant Labs: MCP Tool Poisoning Attacks (2025)
- OWASP Top 10 for Agentic Applications 2026
- Redguard: Arbitrary Code Execution in Claude Code (CVE-2025-59828) (2025)
Claude Cowork:
- Use Cowork on Team and Enterprise Plans
- Use Cowork Safely
- Use Plugins in Cowork
- Securing Claude Cowork — Harmonic Security
- Claude Cowork Security — MintMCP
Security and Compliance:
Changelog
| Date | Version | Maturity | Changes | Author |
|---|---|---|---|---|
| 2026-02-21 | 0.1.0 | draft | Initial guide: 12 controls across 6 categories, API pack scripts for Admin API | Claude Code (Opus 4.6) |
| 2026-02-21 | 0.2.0 | draft | Added Section 7: Claude Code Enterprise Controls — MDM managed settings, permission restrictions, MCP server control, developer analytics | Claude Code (Opus 4.6) |
| 2026-02-21 | 0.3.0 | draft | Added MDM config templates (L1/L2/L3 profiles), permission deny rule examples, sandbox config, managed-mcp.json template, MCP allowlist/denylist config | Claude Code (Opus 4.6) |
| 2026-02-21 | 0.4.0 | draft | Added Config-as-Code pack type with standalone .jsonc config files; added code pack buttons, doc links; moved JSON configs from API scripts to config/ directory | Claude Code (Opus 4.6) |
| 2026-03-27 | 0.5.0 | draft | Major expansion: Added 6 new controls (7.5-7.10) — Bash sandbox isolation, hook/plugin lockdown, prompt injection defense, CI/CD pipeline hardening, external sandbox tooling (nono, OpenShell), Cowork governance. Updated 7.1 with drop-in directory, plist/registry delivery, new managed settings. Added comprehensive references for security research (ToxicSkills, Rules File Backdoor, InversePrompt CVEs) and open-source tools. Updated all compliance mappings. | Claude Code (Opus 4.6) |
Contributing
Found an issue or want to improve this guide?
- Report outdated information: Open an issue with tag
content-outdated - Propose new controls: Open an issue with tag
new-control - Submit improvements: See Contributing Guide