v0.5.0-draft AI Drafted

Anthropic Claude Hardening Guide

Productivity Last updated: 2026-03-27

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

  1. Authentication & Access Controls
  2. API Key Management
  3. Workspace Security
  4. Data Security & Privacy
  5. Monitoring & Usage Controls
  6. Third-Party Integration Security
  7. Claude Code Enterprise Controls
  8. 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

  1. Navigate to: console.anthropic.comSettingsIdentity & Access
  2. Click Configure SSO

Step 2: Configure SSO via WorkOS

  1. Select your IdP type (SAML 2.0 or OIDC)
  2. 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)
  3. Download Claude’s Service Provider metadata for IdP configuration
  4. Map required user attributes (email, name)

Step 3: Configure Your IdP (Example: Okta)

  1. In Okta Admin: ApplicationsCreate App IntegrationSAML 2.0
  2. Enter Claude’s ACS URL and Entity ID from Step 2
  3. Configure attribute statements:
    • email → user.email
    • name → user.displayName
  4. Assign users/groups

Step 4: Verify Domain and Enforce SSO

  1. Complete domain verification (DNS TXT record)
  2. Enable Require SSO for all users
  3. Test login before full enforcement

Time to Complete: ~30 minutes

Code Implementation

Code Pack: API Script
hth-anthropic-claude-1.01-enforce-sso.sh View source on GitHub ↗
# 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

  1. Attempt login without SSO — should be redirected to IdP
  2. Complete SSO login — should succeed and land in Claude
  3. Remove user from IdP group — should lose Claude access
  4. 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 admin role can provision Admin API keys, manage all workspaces, and remove users
  • Admins automatically inherit workspace_admin in every workspace
  • The Admin API deliberately prevents assigning the admin role 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

  1. Navigate to: console.anthropic.comSettingsMembers
  2. Review each member’s role
  3. Document justification for each admin and billing role holder

Step 2: Downgrade Excessive Privileges

  1. For each user with unnecessary admin access:
    • Click the user → Edit Role
    • Select developer or user as appropriate
  2. Ensure at least 2 (but no more than 3) admins remain for redundancy

Step 3: Establish Role Assignment Policy

  1. Define criteria for each role level
  2. Require approval for admin role assignments
  3. Schedule quarterly role reviews

Time to Complete: ~15 minutes

Code Implementation

Code Pack: API Script
hth-anthropic-claude-1.02-enforce-least-privilege-roles.sh View source on GitHub ↗
# 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

  1. List all org members via Admin API — count admins <= 3
  2. Verify no user has admin role without documented justification
  3. Attempt to assign admin role via API — should fail (by design)
  4. 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

  1. Navigate to: console.anthropic.comSettingsAdmin Keys
  2. Review all provisioned admin keys
  3. Identify and revoke any keys without a clear owner or purpose

Step 2: Establish Key Hygiene

  1. Name each admin key descriptively (e.g., “CI/CD Org Audit — TeamName”)
  2. Store keys in a secrets manager — never in source code, env files, or chat
  3. Rotate admin keys every 90 days

Step 3: Limit Admin Key Provisioning

  1. Restrict admin role to 2-3 trusted operators (see Control 1.2)
  2. Require documented approval before provisioning new admin keys
  3. Log all admin key creation events

Time to Complete: ~10 minutes

Code Implementation

Code Pack: API Script
hth-anthropic-claude-1.03-protect-admin-api-keys.sh View source on GitHub ↗
# 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

  1. Validate admin key works via /v1/organizations/me endpoint
  2. Verify admin key is stored in secrets manager (not plaintext)
  3. 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

  1. Navigate to: console.anthropic.com → Select target workspace
  2. Go to: SettingsAPI Keys
  3. Click Create Key
  4. Name the key descriptively: {team}-{environment}-{purpose} (e.g., “ml-team-prod-inference”)

Step 2: Audit Existing Keys

  1. Navigate to: SettingsAPI Keys (org-wide view)
  2. Review each key’s workspace assignment
  3. Identify keys in the Default Workspace — migrate to dedicated workspaces

Time to Complete: ~10 minutes per key

Code Implementation

Code Pack: API Script
hth-anthropic-claude-2.01-scope-api-keys-to-workspaces.sh View source on GitHub ↗
# 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

  1. List all API keys via Admin API — verify each has a workspace assignment
  2. Verify no unnamed keys exist
  3. 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

  1. Navigate to: console.anthropic.comSettingsAPI Keys
  2. Review creation dates for each active key
  3. Flag any key older than 90 days

Step 2: Rotate

  1. Create a new key in the same workspace with the same naming convention
  2. Update the dependent application/service to use the new key
  3. Verify the application works with the new key
  4. Disable the old key (set status to inactive via Admin API)
  5. 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
hth-anthropic-claude-2.02-rotate-api-keys-regularly.sh View source on GitHub ↗
# 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

  1. Run stale key audit script — zero keys older than 90 days
  2. Verify disabled keys return 401 when used
  3. 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

  1. Define workspaces for each team and environment combination
  2. Determine data residency requirements per workspace (workspace_geo is immutable after creation)

Step 2: Create Workspaces

  1. Navigate to: console.anthropic.comSettingsWorkspaces
  2. Click Create Workspace
  3. Enter workspace name following naming convention
  4. Configure data residency settings if required
  5. Repeat for each planned workspace

Step 3: Archive Unused Workspaces

  1. Identify workspaces with no recent activity
  2. 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
hth-anthropic-claude-3.01-segment-workspaces.sh View source on GitHub ↗
# 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

  1. List all workspaces via Admin API — verify naming convention adherence
  2. Verify production workspaces have data residency configured
  3. 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

  1. Navigate to: console.anthropic.com → Select workspace → Members
  2. Review each member’s workspace role
  3. Document any users who don’t belong in this workspace

Step 2: Adjust Membership

  1. Remove users who no longer need access
  2. Downgrade workspace roles where appropriate (e.g., workspace_adminworkspace_developer)
  3. Add users to workspaces they need access to

Time to Complete: ~10 minutes per workspace

Code Implementation

Code Pack: API Script
hth-anthropic-claude-3.02-manage-workspace-membership.sh View source on GitHub ↗
# 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

  1. List workspace members via Admin API for each workspace
  2. Verify no workspace has more than 2 workspace_admin members (excluding inherited org admins)
  3. 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_geo cannot be changed after workspace creation — plan carefully
  • The inference_geo parameter can also be set per-request by API callers, but allowed_inference_geos restricts 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

  1. Navigate to: console.anthropic.comSettingsWorkspaces
  2. Review each workspace’s data residency configuration
  3. Note any workspaces without explicit geo settings

Step 2: Configure New Workspaces with Correct Geo

  1. When creating new workspaces, select the appropriate workspace_geo
  2. This setting is immutable — double-check before confirming

Step 3: Restrict Inference Geos

  1. For regulated workspaces, set allowed_inference_geos to ["us"] only
  2. Set default_inference_geo to "us" to ensure all requests default to US processing

Time to Complete: ~5 minutes per workspace

Code Implementation

Code Pack: API Script
hth-anthropic-claude-4.01-enforce-data-residency.sh View source on GitHub ↗
# 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

  1. List all workspaces via Admin API — verify workspace_geo and allowed_inference_geos
  2. Attempt a request with inference_geo: "global" against a US-restricted workspace — should fail
  3. 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

  1. Review Anthropic’s usage policy at anthropic.com/policies/usage-policy
  2. Confirm your plan’s default retention (API: 30 days, not used for training)

Step 2: Request Custom Retention (Enterprise)

  1. Contact your Anthropic account representative
  2. Specify desired retention period or request ZDR
  3. Obtain written confirmation of retention configuration

Step 3: Implement Data Handling Controls

  1. Establish guidelines for what data types may be sent to Claude
  2. Implement client-side PII redaction before sending sensitive prompts
  3. Use workspace segmentation to isolate sensitive vs. non-sensitive workloads

Time to Complete: ~30 minutes (policy review) + vendor coordination for custom retention

Validation & Testing

  1. Confirm retention period with Anthropic account team (Enterprise)
  2. Verify client-side PII redaction is in place for sensitive workloads
  3. 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

  1. Navigate to: console.anthropic.comUsage
  2. Review token usage charts, rate limit utilization, and cache rates
  3. Filter by workspace, model, and time period

Step 2: Review Cost Dashboard

  1. Navigate to: console.anthropic.comCost
  2. Review cost breakdown by workspace and model
  3. Identify any unexpected cost increases

Step 3: Configure Observability Integration

  1. Integrate with supported platforms: CloudZero, Datadog, Grafana Cloud, Honeycomb, or Vantage
  2. Set up alerts for anomalous usage patterns

Time to Complete: ~15 minutes (dashboard review) + integration setup time

Code Implementation

Code Pack: API Script
hth-anthropic-claude-5.01-monitor-usage-and-costs.sh View source on GitHub ↗
# 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

  1. Run usage report API script — verify data returns for all active workspaces
  2. Run cost report API script — verify cost data is accurate
  3. 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

  1. Navigate to: console.anthropic.comSettingsLimits
  2. Review and configure organization-level spend limits
  3. For custom limits beyond Tier 4, contact Anthropic sales

Step 2: Set Workspace-Level Limits

  1. Navigate to the target workspace → SettingsLimits
  2. 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
  3. Repeat for each workspace

Time to Complete: ~5 minutes per workspace

Code Implementation

Code Pack: API Script
hth-anthropic-claude-5.02-configure-spend-limits.sh View source on GitHub ↗
# 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

  1. Verify spend limits are set for every workspace via Console
  2. Run cost anomaly detection script to validate monitoring
  3. Test that requests return 429 when rate limits are exceeded (check retry-after header)

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

  1. Navigate to: console.anthropic.comSettingsMembersInvites
  2. Review each pending invite: email, role, creation date
  3. Revoke any invite where the recipient is unknown or no longer needed

Step 2: Establish Invite Policy

  1. Require approval before sending invites
  2. Review pending invites weekly
  3. Use the lowest necessary role for each invite (invites cannot use admin role via API)

Time to Complete: ~5 minutes

Code Implementation

Code Pack: API Script
hth-anthropic-claude-6.01-audit-pending-invites.sh View source on GitHub ↗
# 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

  1. Run invite audit script — verify no stale pending invites
  2. Attempt to create invite with admin role via API — should fail (by design)
  3. 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

  1. Maintain inventory of all applications using Claude API keys
  2. Verify each application’s key is in an appropriately scoped workspace
  3. 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-permissions to 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)

  1. Navigate to: claude.aiAdmin SettingsClaude CodeManaged settings
  2. Add JSON configuration with required security settings
  3. Settings propagate to all users at next startup or within 1 hour

Option B: MDM Deployment

Step 1: Create managed-settings.json

  1. Create the JSON configuration file with your organization’s security policy
  2. Include at minimum: disableBypassPermissionsMode, permissions.deny rules, and permissions.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\ClaudeCodeSettings (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

  1. On a test machine, run claude --version to confirm Claude Code sees the managed settings
  2. Attempt to use --dangerously-skip-permissions — should be blocked if disableBypassPermissionsMode is set

Time to Complete: ~30 minutes (policy creation) + MDM deployment time

Code Implementation

Code Pack: Config
hth-anthropic-claude-7.01-managed-settings.jsonc View source on GitHub ↗
// 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
hth-anthropic-claude-7.01-deploy-managed-settings.sh View source on GitHub ↗
# 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

  1. Run validation script — managed-settings.json exists at correct OS path
  2. Verify disableBypassPermissionsMode is set to "disable"
  3. Attempt --dangerously-skip-permissions — should be rejected
  4. 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: true ensures 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

  1. Identify sensitive file patterns: .env, .env.*, secrets/, credentials files
  2. Identify restricted commands: curl (data exfiltration), rm -rf (destruction), credential access
  3. Define approved operations: npm run *, git status, git diff, test runners

Step 2: Configure via Admin Console or MDM

  1. 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 *)
  2. Set allowManagedPermissionRulesOnly: true to prevent user overrides
  3. Set disableBypassPermissionsMode: "disable" (see Control 7.1)

Step 3: Configure Network Sandbox (L3)

  1. Enable sandbox.enabled: true for OS-level isolation
  2. Set sandbox.network.allowedDomains to restrict outbound network access
  3. Restrict socket access as needed

Time to Complete: ~20 minutes

Code Implementation

Code Pack: Config
hth-anthropic-claude-7.02-permission-rules.jsonc View source on GitHub ↗
// 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
hth-anthropic-claude-7.02-restrict-permissions.sh View source on GitHub ↗
# 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

  1. Attempt to read a .env file via Claude Code — should be denied
  2. Attempt to run curl via Claude Code — should be denied
  3. Run an approved command (e.g., npm run test) — should succeed
  4. Verify user-added allow rules are ignored when allowManagedPermissionRulesOnly is 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.json file provides exclusive control — when present, users cannot add their own MCP servers
  • Deny rules in deniedMcpServers always 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

  1. Survey development teams for MCP servers in use
  2. Assess each server’s security posture (source, maintainer, permissions granted)
  3. 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

  1. Add allowedMcpServers to managed settings with approved server names, commands, or URLs
  2. Add deniedMcpServers for explicitly blocked servers (deny always wins)
  3. URL wildcards are supported (e.g., https://*.company.com/*)

Time to Complete: ~15 minutes

Code Implementation

Code Pack: Config
hth-anthropic-claude-7.03-mcp-access.jsonc View source on GitHub ↗
// 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
hth-anthropic-claude-7.03-control-mcp-access.sh View source on GitHub ↗
# 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

  1. Verify managed-mcp.json is deployed (if using exclusive control)
  2. Attempt to add an unapproved MCP server — should be blocked
  3. Verify approved MCP servers connect successfully
  4. 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

  1. Navigate to: console.anthropic.comUsage
  2. Filter for Claude Code usage
  3. Review per-user activity patterns

Step 2: Configure Alerts

  1. Set up daily automated checks using the API script below
  2. Configure alerts for:
    • Users with unusually high session counts
    • Tool acceptance rates below 70%
    • Cost exceeding per-user thresholds
  3. Integrate with observability platform (Datadog, Grafana, etc.)

Step 3: Configure OpenTelemetry (Optional)

  1. Set environment variables in managed settings for OTel export
  2. Available metrics: sessions, LOC, PRs, commits, cost, tokens, code edit decisions, active time
  3. 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
hth-anthropic-claude-7.04-monitor-claude-code-usage.sh View source on GitHub ↗
# 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

  1. Run analytics script — verify data returns for active Claude Code users
  2. Verify per-user session counts, commit counts, and LOC metrics
  3. Verify tool acceptance rates are calculated correctly
  4. 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 via curl, or modify system files
  • Kernel-level enforcement cannot be bypassed by the AI agent — restrictions are irrevocable for the process
  • allowManagedDomainsOnly: true prevents 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) and socat installed
  • Inventory of required network domains for development workflows

ClickOps Implementation

Step 1: Enable Sandbox

  1. Navigate to: claude.aiAdmin SettingsClaude CodeManaged settings
  2. Add "sandbox": { "enabled": true, "failIfUnavailable": true } to your managed settings JSON
  3. Set "allowUnsandboxedCommands": false to close the dangerouslyDisableSandbox escape hatch

Step 2: Configure Network Allowlist

  1. Identify required domains: github.com, package registries (*.npmjs.org, pypi.org), internal services
  2. Add to sandbox.network.allowedDomains array
  3. Set "allowManagedDomainsOnly": true to prevent user overrides
  4. Non-allowed domains are blocked automatically without prompting

Step 3: Configure Filesystem Restrictions

  1. Add sensitive paths to sandbox.filesystem.denyRead: ~/.aws/credentials, ~/.ssh/id_*, ~/.gnupg/
  2. Add critical system paths to sandbox.filesystem.denyWrite: /etc, /usr/local/bin
  3. Optionally set "allowManagedReadPathsOnly": true for L3 environments

Step 4: Install Linux Dependencies (if needed)

  1. On Ubuntu/Debian: sudo apt install bubblewrap socat
  2. On Fedora/RHEL: sudo dnf install bubblewrap socat
  3. Verify: Run /sandbox in Claude Code — should report “Sandbox active”

Time to Complete: ~20 minutes

Code Implementation

Code Pack: Config
hth-anthropic-claude-7.05-sandbox-isolation.jsonc View source on GitHub ↗
// 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

  1. Run Claude Code with sandbox enabled — verify /sandbox shows active status
  2. Attempt to read ~/.aws/credentials via Claude Code — should be denied
  3. Attempt to curl a non-allowlisted domain — should be blocked
  4. Set failIfUnavailable: true and remove bubblewrap (Linux) — Claude Code should refuse to start
  5. Verify allowManagedDomainsOnly prevents 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 allowedDomains as development tooling evolves
  • Quarterly: Audit denyRead/denyWrite paths 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
  • allowManagedHooksOnly prevents 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.json hook 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

  1. Navigate to: claude.aiAdmin SettingsClaude CodeManaged settings
  2. Add "allowManagedHooksOnly": true — blocks all user, project, and plugin hooks
  3. Define any required hooks directly in managed settings under the "hooks" key

Step 2: Restrict Plugin Marketplaces

  1. Add "strictKnownMarketplaces" with your approved marketplace repos only
  2. Set to empty array [] to block all marketplace plugin installations
  3. Add "blockedMarketplaces" for explicitly banned sources — checked before download
  4. Optionally set "pluginTrustMessage" with org-specific guidance for developers

Step 3: Allowlist HTTP Hook URLs

  1. Add "allowedHttpHookUrls": ["https://hooks.example.com/*"] with approved webhook endpoints
  2. Set to empty array [] to block all HTTP hooks
  3. Add "httpHookAllowedEnvVars": ["HOOK_AUTH_TOKEN"] to restrict which env vars hooks can access

Time to Complete: ~15 minutes

Code Implementation

Code Pack: Config
hth-anthropic-claude-7.06-hooks-plugins.jsonc View source on GitHub ↗
// 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

  1. Create a hook in .claude/settings.json — verify it does not execute when allowManagedHooksOnly is true
  2. Attempt to install a plugin from a non-approved marketplace — should be blocked
  3. Verify blockedMarketplaces entries are rejected before download
  4. Create an HTTP hook targeting a non-allowlisted URL — verify it is blocked
  5. Verify pluginTrustMessage appears 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_URL override 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

  1. Before opening any new repository with Claude Code, review CLAUDE.md and .claude/ directory contents
  2. Look for: encoded payloads (base64), invisible Unicode characters, instruction override patterns, network exfiltration commands
  3. Check for files with hidden characters: cat -v CLAUDE.md | grep -c '\^'

Step 2: Install Protective Hooks

  1. 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
  2. Safety Net acts as a PreToolUse hook that catches destructive git and filesystem commands before execution
  3. It inspects commands before the permission system, providing a fallback layer

Step 3: Deploy Rules File Scanning in CI

  1. Add the HTH rules file scanner script as a pre-commit hook or CI step
  2. The scanner checks for: data exfiltration patterns, encoded payloads, invisible Unicode, instruction override attempts, safety bypass requests
  3. Configure to run on every PR that modifies CLAUDE.md, AGENTS.md, or .claude/ directory

Step 4: Use Security Scanner Plugins (Optional)

  1. 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
  2. Use Snyk agent-scan to audit MCP server configurations for vulnerabilities. Source: github.com/snyk/agent-scan (Apache-2.0)
  3. 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)
  4. 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
hth-anthropic-claude-7.07-prompt-injection-defense.sh View source on GitHub ↗
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

  1. Run the rules file scanner against a clean repository — should return exit code 0
  2. Create a test CLAUDE.md with ignore all previous instructions — scanner should flag it
  3. Create a test file with invisible Unicode (zero-width space) — scanner should detect it
  4. Verify claude-code-safety-net blocks git reset --hard and rm -rf / commands
  5. 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_KEY secret is available to the action and could be stolen via network exfiltration
  • harden-runner builds a baseline of allowed outbound connections and can block or alert on anomalous network calls
  • claude-code-security-review provides 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-action and anthropics/claude-code-security-review Actions

ClickOps Implementation

Step 1: Add Harden-Runner to Claude Code Workflows

  1. Add step-security/harden-runner as the first step in any job using Claude Code
  2. Start with egress-policy: audit to build a baseline of expected network connections
  3. After baseline is established, switch to egress-policy: block with explicit allowed-endpoints
  4. Required endpoints: api.anthropic.com:443, github.com:443, api.github.com:443

Step 2: Configure Claude Code Action with Tool Restrictions

  1. Use allowed_tools to restrict Claude Code to safe operations: Read, Glob, Grep, Agent
  2. Use disallowed_tools to block dangerous tools: Bash, WebFetch, WebSearch
  3. Set max_turns to limit agent loops (recommended: 10-20 for review tasks)
  4. Pin the action by SHA, not tag (see Control 7.8 CI/CD workflow example)

Step 3: Add Security Review to PR Workflows

  1. Add anthropics/claude-code-security-review action to PR workflows
  2. WARNING: Only use on trusted PRs from your organization — the action is not hardened against prompt injection
  3. Do not enable on fork PRs or PRs from external contributors

Step 4: Set Minimal Permissions

  1. Set workflow-level permissions: {} (no permissions by default)
  2. Grant only required permissions per job: contents: read, pull-requests: write
  3. Never use permissions: write-all for Claude Code workflows

Time to Complete: ~20 minutes

Code Implementation

Code Pack: Config
hth-anthropic-claude-7.08-cicd-hardening.yml View source on GitHub ↗
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

  1. Verify harden-runner is the first step in Claude Code CI jobs
  2. Run workflow in audit mode — review network connection baseline
  3. Verify allowed_tools/disallowed_tools restrict Claude Code capabilities
  4. Verify max_turns limits agent execution length
  5. 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, curl for 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

  1. macOS/Linux: brew install nono
  2. Verify: nono --version

Step 2: Run Claude Code in nono

  1. Basic: nono run --profile claude-code -- claude
  2. Hardened: Add --rollback for filesystem snapshots, --supervised for interactive approval, --proxy-credential to inject API keys without disk exposure
  3. The claude-code profile grants read/write to CWD only, network via allowlisted proxy, credential injection without disk exposure

Step 3: Review Audit Trail

  1. nono audit list — view all recorded sessions
  2. nono audit show <session-id> --json — detailed session audit
  3. nono rollback list — view available restore points
  4. nono rollback restore — restore to pre-session state

Option B: NVIDIA OpenShell (Container Sandbox)

Step 1: Install OpenShell

  1. curl -LsSf https://raw.githubusercontent.com/NVIDIA/OpenShell/main/install.sh | sh
  2. Verify: openshell --version

Step 2: Launch Claude Code in Sandbox

  1. openshell sandbox create -- claude
  2. OpenShell auto-detects ANTHROPIC_API_KEY, creates a provider, and injects credentials without persisting to disk
  3. Filesystem is locked at creation, network blocked by default

Step 3: Apply Security Policy

  1. Create a YAML policy file defining filesystem, network, and process restrictions
  2. openshell policy set hardened-claude --policy ./claude-policy.yaml
  3. Static policies (filesystem, process) locked at creation; dynamic policies (network) hot-reloadable

Step 4: Monitor

  1. openshell term — real-time terminal UI
  2. openshell logs --tail — stream sandbox logs

Time to Complete: ~15 minutes per tool

Code Implementation

Code Pack: API Script
hth-anthropic-claude-7.09-external-sandbox.sh View source on GitHub ↗
# ── 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

  1. Install nono — verify nono --version returns version
  2. Run nono run --profile claude-code -- claude — verify sandbox active
  3. Attempt to read ~/.ssh/id_rsa from within nono sandbox — should be denied
  4. Install OpenShell — verify openshell --version returns version
  5. Run openshell sandbox create -- claude — verify isolated container launches
  6. Verify nono audit list shows 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 forceLoginMethod and forceLoginOrgUUID, 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: 0 deletes all transcripts at startup and disables session persistence entirely — critical for environments handling classified or regulated data
  • disableAutoMode prevents 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 curl to api.anthropic.com file 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

  1. Navigate to: claude.aiAdmin SettingsClaude CodeManaged settings
  2. Add "forceLoginMethod": "claudeai" to require Claude.ai account login
  3. Add "forceLoginOrgUUID": "your-org-uuid" to auto-select the organization
  4. This prevents developers from using personal accounts or switching organizations

Step 2: Disable Channels (L2)

  1. Add "channelsEnabled": false to block channel message delivery
  2. Add "allowedChannelPlugins": [] to block all channel plugins
  3. For L2 environments that need channels: use allowedChannelPlugins with specific approved plugins only

Step 3: Configure Session Retention

  1. Set "cleanupPeriodDays": 7 for standard environments (7-day retention)
  2. Set "cleanupPeriodDays": 0 for maximum security (no transcript retention, no session persistence)
  3. Note: Setting to 0 disables /resume functionality

Step 4: Restrict Auto-Mode

  1. For L2: Add "disableAutoMode": "disable" to prevent auto-mode activation entirely. This ensures all tool operations go through explicit permission evaluation and removes auto from the Shift+Tab permission mode cycle
  2. For organizations that choose to allow auto-mode: configure autoMode.environment with trusted infrastructure descriptions (repos, domains, cloud buckets), autoMode.soft_deny with natural-language block rules, and autoMode.allow with explicit exceptions. Use claude auto-mode critique to get AI feedback on custom rules before deployment

Step 5: Disable Chrome in Cowork (L2)

  1. Navigate to: claude.aiAdmin SettingsCapabilities
  2. Disable “Chrome in Cowork” — this prevents Claude from automating browser actions (screenshots, clicks, form fills, JavaScript execution)
  3. Note: Chrome is disabled by default on Enterprise but enabled by default on Team — verify your tier’s default
  4. 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

  1. Add "companyAnnouncements" with security policy reminders
  2. Messages display at startup; multiple announcements are cycled randomly

Step 7: Restrict Connector Write Access

  1. Review all enabled connectors (Google Drive, Gmail, Slack, GitHub, etc.) in Admin Settings
  2. For each connector, set per-tool permissions: Allow (runs automatically), Ask (requires confirmation), or Block (never runs)
  3. Block all write-access connector tools (send_email, post_message, create_file) unless explicitly justified
  4. Keep read-only access where needed; disable connectors not required by your workflows

Step 8: Implement Tenant Restrictions (L3)

  1. Configure network proxy with TLS inspection to inject anthropic-allowed-org-ids HTTP header to block personal account access
  2. Without tenant restrictions, developers can switch to personal Claude accounts and bypass all organizational controls
  3. Tenant restrictions are Enterprise-only and require network proxy configuration

Step 9: Address Local Storage Risks

  1. All Cowork conversation history and project data is stored locally on each user’s machine — there is no centralized storage or admin export capability
  2. Ensure endpoint disk encryption (FileVault on macOS, BitLocker on Windows) is enforced via MDM
  3. Deploy EDR on all machines running Claude Desktop to detect anomalous file access patterns
  4. Set cleanupPeriodDays to minimize transcript retention exposure

Time to Complete: ~15 minutes

Code Implementation

Code Pack: Config
hth-anthropic-claude-7.10-cowork-governance.jsonc View source on GitHub ↗
// 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

  1. Verify forceLoginMethod restricts login to Claude.ai accounts only
  2. Verify forceLoginOrgUUID auto-selects the correct organization
  3. Verify channels are disabled — no external messages delivered
  4. Set cleanupPeriodDays: 0 — verify no .jsonl transcripts are written
  5. Verify disableAutoMode removes auto from permission mode options
  6. 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:

Identity Provider Integration:

Claude Code Security:

Open-Source Security Tools:

Security Research:

Claude Cowork:

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?