Metadata-Version: 2.4
Name: clauditor
Version: 0.3.0
Summary: Security configuration scanner for Claude Code
License: Apache-2.0
License-File: LICENSE
Keywords: audit,claude,claude-code,scanner,security
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Information Technology
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Security
Requires-Python: >=3.13
Requires-Dist: gitpython>=3.1
Requires-Dist: pydantic>=2.0
Requires-Dist: pyyaml>=6.0
Requires-Dist: rich>=13.0
Requires-Dist: typer>=0.12
Provides-Extra: dev
Requires-Dist: bandit>=1.7; extra == 'dev'
Requires-Dist: mypy>=1.10; extra == 'dev'
Requires-Dist: pip-audit>=2.7; extra == 'dev'
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: ruff>=0.9; extra == 'dev'
Requires-Dist: types-pyyaml>=6.0; extra == 'dev'
Requires-Dist: vulture>=2.11; extra == 'dev'
Description-Content-Type: text/markdown

<p align="center">
  <img src="logo.png" alt="Clauditor" width="200">
</p>

# Clauditor

**Security configuration scanner for Claude Code.**

[![Tests](https://github.com/gabrielsoltz/clauditor/actions/workflows/test.yml/badge.svg)](https://github.com/gabrielsoltz/clauditor/actions/workflows/test.yml)
[![Security](https://github.com/gabrielsoltz/clauditor/actions/workflows/security.yml/badge.svg)](https://github.com/gabrielsoltz/clauditor/actions/workflows/security.yml)
[![Lint](https://github.com/gabrielsoltz/clauditor/actions/workflows/lint.yml/badge.svg)](https://github.com/gabrielsoltz/clauditor/actions/workflows/lint.yml)
[![PyPI](https://img.shields.io/pypi/v/clauditor)](https://pypi.org/project/clauditor/)
[![Python](https://img.shields.io/pypi/pyversions/clauditor)](https://pypi.org/project/clauditor/)
[![License](https://img.shields.io/github/license/gabrielsoltz/clauditor)](LICENSE)

Clauditor audits your Claude Code settings and repository configuration to detect security misconfigurations.

---

## Table of Contents

- [Features](#features)
- [Built-in Checks](#built-in-checks)
- [Installation](#installation)
- [Usage](#usage)
- [Configuration Scopes](#configuration-scopes)
- [How Findings Work](#how-findings-work)
- [Enforcing a Minimum Scope](#enforcing-a-minimum-scope)
- [Check Format](#check-format)
- [Generating a Settings File](#generating-a-settings-file)
- [Adding a Custom Check](#adding-a-custom-check)
- [Architecture](#architecture)
- [License](#license)

---

## Features

- Scans all Claude Code configuration scopes: **user**, **project**, **local**, and **managed**
- Checks **repository-level files** (CODEOWNERS, CLAUDE.md, etc.)
- Checks are defined as **YAML files** — easy to read, extend, and contribute
- Each check maps to a concrete **threat**, **severity**, and **remediation**
- Scan a **local path**, a **remote git URL**, or just the current directory
- Rich terminal output with optional verbose remediation steps
- CI-friendly `--exit-code` flag
- `--base-level` flag to enforce a minimum required scope

---

## Built-in Checks

| ID | Name | Severity | Scope | Threat Mitigated |
|----|------|----------|-------|-----------------|
| CC001 | CODEOWNERS Enforcement for Claude Code Paths | HIGH | repository | Supply chain attacks via unreviewed config changes |
| CC002 | Disable Bypass Permissions Mode | CRITICAL | user, project, local, managed | Unrestricted tool execution via --dangerously-skip-permissions |
| CC003 | Enforce Managed Permission Rules Only | LOW | managed | User/project permission rules bypassing IT policy |
| CC004 | Deny Sensitive File Operations | LOW | managed | Credential theft via .env, secrets/**, credential files |
| CC005 | Disable Auto-Approval of Project MCP Servers | LOW | managed | Supply chain attacks via malicious .mcp.json |
| CC006 | Enforce Managed Hooks Only | LOW | managed | Arbitrary code execution via project/user hooks |
| CC007 | Force SSO Login Method | MEDIUM | managed | Unmanaged personal accounts bypassing corporate identity |
| CC008 | Require SSO Organization UUID | MEDIUM | managed | Cross-tenant auth or unbound SSO enforcement |
| CC009 | Require Approval for Network-Fetching Tools | LOW | managed | Unlogged outbound requests via curl/wget |
| CC010 | Enable Bash Sandboxing | LOW | user, project, local, managed | Unrestricted shell access bypassing permission limits |
| CC011 | Restrict Sandbox Filesystem Write Paths | MEDIUM | user, project, local, managed | Writes to /etc, /usr, ~/.ssh, ~/.aws enabling persistence |
| CC012 | Restrict Sandbox Filesystem Read Paths | MEDIUM | user, project, local, managed | Exfiltration of SSH keys, cloud credentials, .env secrets |
| CC013 | Block Sandbox Escape Hatch | HIGH | user, project, local, managed | Silent sandbox bypass via auto-retry of failed commands |
| CC014 | Block Weaker Nested Sandbox Mode | HIGH | user, project, local, managed | Weakened bubblewrap isolation on Linux/WSL2 |
| CC015 | Block Unrestricted Unix Socket Access in Sandbox | HIGH | user, project, local, managed | Docker socket access granting root-equivalent host control |
| CC016 | Enforce Managed Network Domain Allowlist | HIGH | managed | Project settings bypassing IT network egress controls |
| CC017 | Enforce Managed MCP Servers Only | HIGH | managed | Unauthorized MCP server injection via project/user settings |
| CC018 | Restrict Plugin Marketplace Sources | CRITICAL | managed | Supply chain attacks via malicious marketplace plugins |
| CC019 | Disable Remote Sessions | HIGH | managed | Remote takeover of local execution context via web interface |
| CC020 | Enforce Stable Update Channel | MEDIUM | managed | Unreviewed Claude Code releases reaching developer fleet |
| CC021 | Enforce Short Transcript Retention Period | MEDIUM | user, project, managed | Credential-containing session transcripts accumulating on disk |
| CC022 | Block bypassPermissions as Default Mode | CRITICAL | managed | All permission checks disabled globally for every session |
| CC023 | Restrict HTTP Hook Destination URLs | HIGH | managed | Session data exfiltration via unrestricted HTTP hook endpoints |
| CC024 | Restrict Environment Variables Exposed to HTTP Hooks | HIGH | managed | Credential exfiltration via hook request headers |
| CC025 | Deny Direct Read Access to .env and Secrets Files | HIGH | managed, project | .env reads bypassing .claudeignore restrictions |
| CC026 | Deny Filesystem MCP Server | HIGH | managed, project | Filesystem MCP server bypassing permissions.deny rules |
| CC027 | Block apiKeyHelper in Project and Local Scope | HIGH | project, local | Arbitrary shell execution on session start via committed config |
| CC028 | Block otelHeadersHelper in Project and Local Scope | HIGH | project, local | Persistent periodic shell execution via committed config |
| CC029 | Block Unrestricted Bash in Project Permissions Allow List | HIGH | project | Supply chain RCE via allow-all Bash rule in committed settings |
| CC030 | CODEOWNERS Enforcement for .mcp.json | HIGH | repository | Unreviewed MCP server additions with full session authority |
| CC031 | Configure Sandbox Network Domain Allowlist | HIGH | managed, project | Unrestricted sandbox network egress enabling data exfiltration |
| CC032 | Disable Filesystem Server in MCP JSON Servers | MEDIUM | user, project, managed | Defense-in-depth block on high-risk filesystem MCP server |
| CC033 | Block Unrestricted WebFetch in Permissions Allow List | MEDIUM | project, managed | Prompt injection via unrestricted web content fetching |

---

## Installation

The recommended way to install Clauditor is with [pipx](https://pipx.pypa.io), which installs CLI tools in isolated environments and makes them available system-wide:

```bash
pipx install clauditor
```

Install pipx if you don't have it yet:

```bash
# macOS
brew install pipx && pipx ensurepath

# Linux / WSL
python3 -m pip install --user pipx && pipx ensurepath
```

**Alternative — pip inside a virtual environment:**

```bash
python3 -m venv .venv
source .venv/bin/activate
pip install clauditor
```

**From source:**

```bash
git clone https://github.com/gabrielsoltz/clauditor
cd clauditor
python3.13 -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
```

---

## Usage

```bash
# Scan current directory (project + user scope settings)
clauditor scan

# Scan a specific local repository
clauditor scan --path /path/to/repo

# Clone and scan a remote repository
clauditor scan --url https://github.com/org/repo

# Filter by severity
clauditor scan --severity CRITICAL,HIGH

# Show remediation steps for failed checks
clauditor scan -v

# Exit with code 1 if any failures found (for CI)
clauditor scan --exit-code

# List all available checks
clauditor list-checks

# Generate a settings file (user scope by default)
clauditor generate

# Generate a managed settings file covering all checks
clauditor generate --scope managed -o managed-settings.json
```

---

## Configuration Scopes

Claude Code reads settings from multiple locations. Each location is a **scope**. Understanding scopes is key to understanding Clauditor's output.

See the [official Claude Code settings documentation](https://code.claude.com/docs/en/settings) for details.

### The four Claude Code scopes

| Column | Scope | File | Who it applies to |
|--------|-------|------|-------------------|
| `M` | `managed` | System path (platform-specific) | Everyone on the machine; deployed by IT |
| `L` | `local` | `.claude/settings.local.json` | You, in this repo only; **gitignored** |
| `P` | `project` | `.claude/settings.json` | All collaborators; **committed to git** |
| `U` | `user` | `~/.claude/settings.json` | You, across all projects |

### Scope precedence

When the same setting exists in multiple scopes, Claude Code applies **the highest-precedence scope**:

```
managed  >  local  >  project  >  user
(highest)                        (lowest)
```

`managed` is set by an administrator and cannot be overridden by anyone. `local` takes precedence over `project`, which means a developer can use `.claude/settings.local.json` to override what the team committed in `.claude/settings.json`.

### Repository scope (Clauditor extension)

| Column | Scope | What it checks |
|--------|-------|----------------|
| `R` | `repository` | VCS governance files: CODEOWNERS, workflow configs, etc. |

`repository` is **not a Claude Code scope** — it's Clauditor's own concept for checks that look at repository governance files rather than Claude Code JSON settings. It has no precedence relationship with the config scopes above.

---

## How Findings Work

### Per-scope icons in the output table

Each scope column shows one icon:

| Icon | Meaning |
|------|---------|
| `✔` | Setting is correctly configured at this scope |
| `✘` | Setting is present but has the wrong value |
| `↑` | Covered — a higher-precedence scope already passes, so this scope is irrelevant |
| `–` | Skipped — the settings file for this scope was not found or is empty |
| `·` | N/A — this check does not apply to this scope |

### How the effective (overall) status is decided

The **effective status** in the Status column is determined by the highest-precedence scope that is not skipped:

- If `managed=PASS` → effective is **PASS**, regardless of lower scopes. All lower scopes show `↑` (covered).
- If `managed=FAIL` → effective is **FAIL**, regardless of lower scopes. A wrong value at the top level locks everyone.
- If `managed=–, local=–, project=PASS` → effective is **PASS**, user shows `↑` (covered).
- If all config scopes are `–` (not configured anywhere) → effective is **FAIL**. A missing setting is not a passing setting.

### Scenario examples

**Scenario A — Enforced via managed settings (best)**

| M | L | P | U | Status |
|---|---|---|---|--------|
| ✔ | ↑ | ↑ | ↑ | ✔ PASS |

Setting is in managed. Everyone on the machine is protected. Lower scopes are irrelevant.

**Scenario D — Nobody has it set**

| M | L | P | U | Status |
|---|---|---|---|--------|
| – | – | – | – | ✘ FAIL |

The setting is not configured anywhere. This is always FAIL — a missing setting provides no protection.

---

## Enforcing a Minimum Scope

By default, Clauditor marks a check as PASS if the setting is correctly configured at **any** scope. However, you may want to require enforcement at a specific level.

```bash
# Require the setting to exist at project scope or above (for team-wide enforcement)
clauditor scan --base-level project

# Require enterprise-wide enforcement through managed settings only
clauditor scan --base-level managed
```

With `--base-level project`, a setting that is only present in the user scope becomes FAIL — the setting is only personal and doesn't protect the team.

| `--base-level` | What passes |
|----------------|-------------|
| `user` (default) | Any scope: user, project, local, or managed |
| `project` | Must be in project, local, or managed (not just user) |
| `local` | Must be in local or managed (not just project/user) |
| `managed` | Only managed qualifies |

---

## Check Format

Checks live in the `checks/` directory as YAML files. Example structure:

```yaml
id: CC001
name: CODEOWNERS Enforcement for Claude Code Paths
description: >
  Ensures that /.claude/ and /CLAUDE.md have CODEOWNERS entries
  requiring security team review.

scope:
  - repository

severity: HIGH  # CRITICAL | HIGH | MEDIUM | LOW | INFO

threat: >
  Without CODEOWNERS enforcement, contributors can silently modify
  Claude Code settings, hooks, or instructions without security review...

category: access_control

check_type: file_content  # config_value | config_contains | config_set | file_content | file_exists

check_config:
  search_paths:
    - CODEOWNERS
    - .github/CODEOWNERS
  required_entries:
    - pattern: "/.claude/"
      owner: "@security-team"
    - pattern: "/CLAUDE.md"
      owner: "@security-team"

remediation: >
  Add to CODEOWNERS:
    /.claude/ @security-team
    /CLAUDE.md @security-team

fix_available: true

references:
  - https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners
  - https://code.claude.com/docs/en/settings
```

### Check Types

| `check_type` | `check_config` keys | Description |
|---|---|---|
| `config_value` | `key`, `expected_value` | Verifies a key/value in a JSON settings file |
| `config_contains` | `key`, `required_values` | Verifies a list key contains all required values |
| `config_set` | `key` | Verifies a key is present and non-empty (any truthy value) |
| `file_content` | `search_paths`, `required_entries` | Verifies required lines exist in a file |
| `file_exists` | `paths`, `any_of` | Verifies file(s) exist in the repository |

---

## Generating a Settings File

`clauditor generate` produces a ready-to-deploy JSON settings file containing all the values needed to remediate the applicable checks.

```bash
clauditor generate                          # All checks → user settings
clauditor generate --scope managed         # Full managed settings file
clauditor generate --scope project         # Project-level (.claude/settings.json)
clauditor generate --severity CRITICAL     # Only critical checks
clauditor generate --checks CC002,CC010    # Specific checks only
clauditor generate --scope managed -o managed-settings.json
```

**Scope controls which checks are included:**

| `--scope` | Checks included |
|-----------|----------------|
| `user` (default) | user-scoped checks only |
| `project` | project + user checks |
| `local` | local + project + user checks |
| `managed` | All config checks: managed + local + project + user |

**What gets generated:**
- `config_value` checks → sets the key to its required value
- `config_contains` checks → builds/merges the required list entries (e.g. multiple checks writing to `permissions.deny` are merged automatically)
- `config_set` checks → **skipped** (e.g. `forceLoginOrgUUID` requires your org-specific UUID; reported separately)
- `file_content` / `file_exists` checks → **skipped** (repository governance files, not settings values)

**Example output** (`clauditor generate --scope managed`):

```json
{
  "disableBypassPermissionsMode": "disable",
  "allowManagedPermissionRulesOnly": true,
  "permissions": {
    "deny": [
      "Read(.env)",
      "Read(**/.env)",
      "Read(secrets/**)",
      "Write(secrets/**)",
      "Read(**/credentials)",
      "Bash(curl:*)",
      "Bash(wget:*)"
    ]
  },
  "enableAllProjectMcpServers": false,
  "allowManagedHooksOnly": true,
  "forceLoginMethod": "claudeai",
  "sandbox": {
    "enabled": true,
    "filesystem": {
      "denyWrite": ["/etc", "/usr", "~/.ssh", "~/.aws"],
      "denyRead": ["~/.ssh", "~/.aws/credentials", ".env", "**/.env"]
    }
  }
}
```

---

## Adding a Custom Check

1. Create a new YAML file in `checks/` following the format above.
2. Assign the next available `CC###` ID.
3. Run `clauditor list-checks` to verify it loads correctly.
4. Run `clauditor scan` to see results.

No code changes required.

---

## Architecture

```
clauditor/
├── cli.py              # Typer CLI entry point
├── scanner.py          # Orchestrates checks against providers
├── loader.py           # YAML check loader with Pydantic validation
├── aggregator.py       # Scope precedence + base_level logic
├── generator.py        # Settings file generator
├── models/
│   ├── check.py        # Check, Scope, Severity, CheckType models
│   └── finding.py      # Finding, FindingStatus models
├── providers/
│   ├── base.py         # BaseProvider interface
│   ├── config_provider.py   # User, Project, Local, Managed providers
│   └── repository_provider.py  # Repository file provider + git clone
├── checkers/
│   ├── config_value.py     # Logic for config_value checks
│   ├── config_contains.py  # Logic for config_contains checks
│   ├── config_set.py       # Logic for config_set checks
│   ├── file_content.py     # Logic for file_content checks
│   └── file_exists.py      # Logic for file_exists checks
└── output/
    └── console.py       # Rich terminal output

checks/                  # YAML check definitions
```

---

## License

Apache 2.0
