Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
228 changes: 228 additions & 0 deletions plugins/auth0/skills/auth0-audit/.claude/commands/auth0-audit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
Run a security audit against the configured Auth0 tenant. Produces Markdown receipts and structured JSON findings.

**Usage:**
```
/auth0-audit [--tool actions|tokens|passkeys|all] [--tenant DOMAIN] [--cache]
```
Comment on lines +4 to +6

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add language identifiers to fenced code blocks (MD040).

The unlabeled fenced blocks trigger markdownlint warnings; please mark each with an explicit language (text, bash, json, markdown, etc.) to keep docs lint-clean.

Also applies to: 23-25, 63-118, 125-128, 147-150, 176-179, 200-203

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 4-4: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugins/auth0/skills/auth0-audit/.claude/commands/auth0-audit.md` around
lines 4 - 6, The markdown contains unlabeled fenced code blocks (for example the
snippet starting with "/auth0-audit [--tool actions|tokens|passkeys|all]...")
which trigger MD040; update each fenced block to include an appropriate language
identifier (e.g., ```bash for CLI examples, ```text for plain text, ```json for
JSON responses) so the blocks at the mentioned occurrences are explicitly
labeled; search for all triple-backtick blocks in the document (including the
ones around the CLI usage and the regions covering the other reported ranges)
and add the correct language tag to each opening fence.


**Arguments:**
- `--tool`: Which audit to run. Default: `all`
- `--tenant`: Override the `AUTH0_DOMAIN` env var for this run
- `--cache`: Use `scripts/demo-cache.json` instead of live API calls (offline demo mode)

## Step 0 — Verify MCP connection

Before doing anything else, attempt a lightweight MCP call (e.g. `auth0_list_applications` with `per_page=1`) to confirm the auth0-mcp-server is reachable.

- If the call **succeeds**: proceed to Step 1.
- If the call **fails** with an MCP connection error or authentication error: stop immediately and tell the user:

> The auth0-mcp-server is not connected or not authenticated.
> Please run the following command in your terminal, then re-run `/auth0-audit`:
>
> ```
> npx @auth0/auth0-mcp-server init
> ```
Comment on lines +20 to +25

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Remove manual auth0-mcp-server init recovery instructions.

Line 20–25 currently directs users to a manual init flow that conflicts with this plugin’s automated credential handling path and can send users down the wrong remediation route.

Suggested doc fix
-  > Please run the following command in your terminal, then re-run `/auth0-audit`:
-  >
-  > ```
-  > npx `@auth0/auth0-mcp-server` init
-  > ```
+  > Credential setup is managed automatically by the skill hooks.
+  > Please ensure the Auth0 audit skill hooks are active, then re-run `/auth0-audit`.

Based on learnings: "Do NOT attempt to run auth0-mcp-server init manually; credential handling is automated via hooks/mcp-server.js using device authorization flow".

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
> The auth0-mcp-server is not connected or not authenticated.
> Please run the following command in your terminal, then re-run `/auth0-audit`:
>
> ```
> npx @auth0/auth0-mcp-server init
> ```
> The auth0-mcp-server is not connected or not authenticated.
> Credential setup is managed automatically by the skill hooks.
> Please ensure the Auth0 audit skill hooks are active, then re-run `/auth0-audit`.
🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 23-23: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugins/auth0/skills/auth0-audit/.claude/commands/auth0-audit.md` around
lines 20 - 25, Replace the manual recovery instructions that tell users to run
`npx `@auth0/auth0-mcp-server` init` with guidance that credential setup is
automated by the plugin hooks; specifically update the auth0-audit message block
in auth0-audit.md to remove the manual init command and instead say something
like "Credential setup is managed automatically by the skill hooks. Please
ensure the Auth0 audit skill hooks are active, then re-run `/auth0-audit`." Also
verify references to the automated flow in mcp-server.js / the device
authorization flow and ensure no other docs or messages instruct a manual
`auth0-mcp-server init`.


Do not proceed to any audit steps until the MCP connection is confirmed.

## Step 1 — Parse arguments

Extract `--tool`, `--tenant`, `--cache` from the `/auth0-audit` invocation args.
Default `--tool` to `all` if not specified.
If `--tenant` is provided, set `AUTH0_DOMAIN` to that value for this run.

## Step 2 — Run selected tools

Run tools in order: `actions` → `tokens` → `passkeys` (skip any not selected).

---

## Tool: ActionsWhisperer (`--tool actions`)

**Purpose:** Analyze all Auth0 Actions for security risks.

**Steps:**

1. Use MCP tool `auth0_list_actions` to get all actions. If `--cache` is set, read from `scripts/demo-cache.json` key `actions_list` instead.

2. For each action returned, use MCP tool `auth0_get_action` with the action's `id` to fetch its full details including `code`, `secrets` (names only), and `dependencies`.

3. For each action, analyze the code using the **ActionsWhisperer Analysis Prompt** below.

4. Collect all findings into an array. Then run:
```bash
node tools/render-findings.js actions '<JSON_ARRAY>'
```
where `<JSON_ARRAY>` is the JSON-stringified array of per-action findings objects.

5. Print the console summary line.

**ActionsWhisperer Analysis Prompt** (apply this to each action's code):

```
You are a CIAM security analyst reviewing Auth0 Actions code. Analyze the following Auth0 Action and return ONLY a valid JSON object matching the schema below. Do not include markdown, explanation, or any text outside the JSON.

Action context:
- Name: {action_name}
- Trigger: {trigger_type} (e.g. post-login, credentials-exchange, pre-user-registration, post-user-registration)
- Named secrets available (values not accessible via API): {secret_names}
- NPM dependencies declared: {dependencies}

Code:
{code}

Identify and flag the following risk patterns:

(a) EXTERNAL_HTTP_CALL — any fetch(), axios(), http.request(), or XMLHttpRequest to a non-hardcoded URL or to any URL not in the declared allowlist. If the URL is a string literal, include it. If it uses a variable or env secret, note it.

(b) PII_METADATA_WRITE — any call to api.user.setUserMetadata() or api.idTokenClaims.setCustomClaim() where the value comes from event.request.* (ip, userAgent, geoip, hostname, query, body) — this persists request-context data as PII in the user record.

(c) HARDCODED_SECRET — any string literal matching patterns for API keys, tokens, or passwords:
- Length 20–80 characters with high entropy (mixed alphanumeric + special chars)
- Matches patterns like: /[A-Za-z0-9_\-]{32,}/ in a string assignment to a variable named *key*, *token*, *secret*, *password*, *apikey*, *api_key*
- Do NOT flag named secret references like event.secrets.MY_SECRET — those are safe

(d) EXTERNAL_CLAIM_INJECTION — any call to api.accessToken.setCustomClaim() or api.idTokenClaims.setCustomClaim() where the value originates from an external HTTP response, event.request.*, or event.authorization.* — this could inject attacker-controlled values into tokens

(e) ACCESS_DENY_CONDITION — any call to api.access.deny() — document the condition, but do not automatically flag as risky; rate this "low" unless the condition itself looks exploitable

(f) UNALLOWLISTED_SERVICE — any call to an external service (HTTP or SDK) that is not in the declared allowlist (assume the declared allowlist is empty unless the action's code or comments explicitly define one)

IMPORTANT: event.secrets.ANYTHING is safe — do not flag secret references. Only flag hardcoded literal values.
IMPORTANT: Do not echo any email addresses, user IDs, or PII you find in code comments.

Return this JSON schema (no other text):
{
"action_id": "<id>",
"action_name": "<name>",
"trigger": "<trigger_type>",
"risk_level": "critical|high|medium|low|none",
"findings": [
{
"pattern": "EXTERNAL_HTTP_CALL|PII_METADATA_WRITE|HARDCODED_SECRET|EXTERNAL_CLAIM_INJECTION|ACCESS_DENY_CONDITION|UNALLOWLISTED_SERVICE",
"location": "<function name or line description>",
"description": "<what was found, no PII>",
"remediation": "<specific fix recommendation>"
}
],
"plain_english_summary": "<2-3 sentence plain English description of what this Action does and its risk profile>"
}

Risk level rules:
- critical: HARDCODED_SECRET found
- high: PII_METADATA_WRITE or EXTERNAL_CLAIM_INJECTION with externally sourced values
- medium: EXTERNAL_HTTP_CALL or UNALLOWLISTED_SERVICE
- low: ACCESS_DENY_CONDITION or minor issues
- none: no findings
```

**Output files written by** `node tools/render-findings.js actions`:
- `actions-audit.md` — full receipts grouped by trigger type
- `actions-findings.json` — structured findings array

**Console summary format:**
```
Analyzed N actions. Found X critical, Y high, Z medium findings.
Top risk: [action_name] ([trigger]) — [one-line description of worst finding].
```

---

## Tool: TokenGraveyard (`--tool tokens`)

**Purpose:** Identify dormant and overprivileged M2M (non_interactive) applications and provide approval-gated remediation.

**Steps:**

1. Use MCP tool `auth0_list_applications` with filter `app_type=non_interactive` (or equivalent parameter). If `--cache`, read from `scripts/demo-cache.json` key `m2m_apps`.

2. Run the TokenGraveyard analysis script to compute last-used dates and blast-radius scores:
```bash
node tools/token-graveyard.js
```
This script queries Auth0 logs directly for each app's last `sce` event, computes scores, writes `tokens-audit.md` and `tokens-findings.json`, and prints a ranked remediation list to stdout.

3. Show the ranked remediation list to the user. For each app in the **"Revoke now"** tier, ask:
```
Revoke client grant for "[app_name]" (client_id: [id])? [y/N]
```
Wait for explicit `y` before proceeding. `N` or any other input skips that app.

4. For each approved revocation, use MCP tool `auth0_delete_client_grant` with the grant ID.

5. After all approvals processed, run:
```bash
node tools/render-findings.js tokens-receipt '<RECEIPTS_JSON>'
```
to write `tokens-remediation-receipt.md`.

**Blast-radius scoring** (implemented in `tools/token-graveyard.js`):
- `update:users`, `delete:users` → 4 pts each
- `create:clients`, `update:clients`, `delete:clients` → 5 pts each
- `read:user_idp_tokens` → 3 pts
- `update:tenant_settings` → 5 pts
- `read:users` → 1 pt
- All other Management API scopes → 1 pt
- Non-Management-API scopes → 0.5 pts

**Tier assignments:**
- **Revoke now:** never used OR (last used >90 days AND blast radius > 5)
- **Rotate within 7 days:** last used 60–90 days OR blast radius > 8
- **Scope-narrow within 30 days:** scope overprivilege relative to app name/purpose
- **Monitor:** last used <30 days, normal blast radius

**Console summary format:**
```
Found N M2M applications. X never used, Y dormant >60 days, Z with high blast radius (score >8).
Recommended: revoke X, rotate Y, scope-narrow Z.
```

---

## Tool: PasskeysReadiness (`--tool passkeys`)

**Purpose:** Assess whether the tenant meets all 4 prerequisites for enabling Passkeys (WebAuthn).

**Steps:**

1. Run the PasskeysReadiness script:
```bash
node tools/passkeys-readiness.js
```
If `--cache`, pass `--cache` flag: `node tools/passkeys-readiness.js --cache`

2. The script writes `passkeys-readiness.md` and prints the console summary.

3. Read `passkeys-readiness.md` and present its contents to the user.

**Console summary format:**
```
Passkeys readiness: N/4 prerequisites met. [not_ready|partially_ready|ready].
Primary blocker: [blocking issue in one sentence, or "None — tenant is ready for Passkeys"].
```

---

## Orchestration (`--tool all`)

Run all three tools in sequence: ActionsWhisperer → TokenGraveyard → PasskeysReadiness.

After all tools complete, write `auth0-audit-summary.md`:

```markdown
# Auth0 Security Audit Summary
Generated: [ISO timestamp]
Tenant: [AUTH0_DOMAIN]

## Actions (ActionsWhisperer)
[Total actions analyzed, finding counts by severity, top risk]

## M2M Tokens (TokenGraveyard)
[Total apps, dormancy breakdown, revocations executed]

## Passkeys Readiness
[Score N/4, status, primary blocker]
```

Print a 5-line console summary on completion.
20 changes: 20 additions & 0 deletions plugins/auth0/skills/auth0-audit/.claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"mcpServers": {
"auth0": {
"command": "node",
"args": ["hooks/mcp-server.js"]
}
},
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "node hooks/inject-auth0-context.js"
}
]
}
]
}
}
5 changes: 5 additions & 0 deletions plugins/auth0/skills/auth0-audit/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.env
.claudecreds
.auth0-token-cache
node_modules/
./claude/settings.local.json
40 changes: 40 additions & 0 deletions plugins/auth0/skills/auth0-audit/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Auth0 Security Audit Plugin

This project is an Auth0 Customer Identity Cloud (CIC) security audit plugin for Claude Code.
It targets Auth0 CIC only — not Okta Workforce Identity Cloud or Okta Identity Engine.

**Prerequisites:**
- Node.js ^20.19.0 || ^22.12.0 || ^24.0.0
- `auth0-mcp-server` configured in `.claude/settings.json` (already done — runs via `hooks/mcp-server.js`)

**Credential handling — IMPORTANT:**
All Auth0 credential acquisition is handled automatically by `hooks/mcp-server.js`. Do NOT look for tokens in the OS keychain, `~/.config`, or any other system location. Do NOT attempt to run `auth0-mcp-server init` manually.

The wrapper uses the device authorization flow:
1. Checks for an existing token in the OS keychain (stored by a previous `auth0-mcp-server init` run)
2. If no token is found, runs `npx @auth0/auth0-mcp-server init` (without options) — this opens the Auth0 device-auth page in the browser and persists the token in the OS keychain
3. Spawns `npx @auth0/auth0-mcp-server run`

The MCP tools (`auth0_list_actions`, `auth0_list_applications`, etc.) are ready to use without any additional auth steps after the first browser-based login.

**Slash command:** `/auth0-audit` — see `.claude/commands/auth0-audit.md` for full implementation steps.

## Project Structure

```
.claude/
commands/
auth0-audit.md ← slash command definition (skill steps)
settings.json ← MCP server + hook config
hooks/
inject-auth0-context.js ← SessionStart hook
mcp-server.js ← MCP server wrapper (token caching)
tools/
render-findings.js ← ActionsWhisperer output renderer
token-graveyard.js ← TokenGraveyard analysis + scoring
passkeys-readiness.js ← PasskeysReadiness 4-checkpoint script
scripts/
setup-demo-tenant.js ← Populates dev tenant with synthetic data
teardown-demo-tenant.js
demo-cache.json ← Cached API responses for offline demo
```
97 changes: 97 additions & 0 deletions plugins/auth0/skills/auth0-audit/hooks/inject-auth0-context.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
#!/usr/bin/env node
/**
* SessionStart hook — injects Auth0 tenant context into Claude's session.
* Warns if AUTH0_CLIENT_SECRET is in .env but auth0-mcp-server is not configured.
* Output is read by Claude Code and prepended to the system context.
*/

import { readFileSync, existsSync } from 'fs';
import { resolve } from 'path';

const cwd = process.cwd();

function loadEnvFile(path) {
if (!existsSync(path)) return {};
const vars = {};
for (const line of readFileSync(path, 'utf8').split('\n')) {
const trimmed = line.trim();
if (!trimmed || trimmed.startsWith('#')) continue;
const eq = trimmed.indexOf('=');
if (eq === -1) continue;
vars[trimmed.slice(0, eq).trim()] = trimmed.slice(eq + 1).trim().replace(/^["']|["']$/g, '');
}
return vars;
}

function loadClaudeCreds(path) {
if (!existsSync(path)) return {};
try {
return JSON.parse(readFileSync(path, 'utf8'));
} catch {
return {};
}
}

const envVars = loadEnvFile(resolve(cwd, '.env'));
const claudeCreds = loadClaudeCreds(resolve(cwd, '.claudecreds'));

const domain =
process.env.AUTH0_DOMAIN ||
claudeCreds.AUTH0_DOMAIN ||
envVars.AUTH0_DOMAIN ||
null;

const settingsPath = resolve(cwd, '.claude/settings.json');
let mcpConfigured = false;
if (existsSync(settingsPath)) {
try {
const settings = JSON.parse(readFileSync(settingsPath, 'utf8'));
mcpConfigured = !!(settings.mcpServers && settings.mcpServers.auth0);
} catch { /* ignore */ }
}

const hasStaticSecret =
!!(envVars.AUTH0_CLIENT_SECRET || process.env.AUTH0_CLIENT_SECRET);

const warnings = [];
if (hasStaticSecret && !mcpConfigured) {
warnings.push(
'WARNING: Static Management API credential detected in .env (AUTH0_CLIENT_SECRET). ' +
'Consider using auth0-mcp-server for credential-safe access — it stores credentials ' +
'in the OS keychain and avoids committing secrets to source control.'
);
}

const today = new Date().toISOString().split('T')[0];

const lines = [
'=== Auth0 Security Audit Plugin — Session Context ===',
'',
`Date: ${today}`,
`Tenant: ${domain || '(not configured — set AUTH0_DOMAIN in .env or .claudecreds)'}`,
'',
'Available audit tools (invoke with /auth0-audit):',
' --tool actions → ActionsWhisperer: analyze Action JS code for security risks',
' --tool tokens → TokenGraveyard: identify dormant/overprivileged M2M apps',
' --tool passkeys → PasskeysReadiness: check 4 prerequisites for enabling Passkeys',
' --tool all → Run all three tools in sequence (default)',
'',
'Context for judges: Auth0 Actions are server-side JavaScript functions that execute',
'at specific points in the authentication pipeline (e.g., after login, during token',
'exchange). They can add custom claims, call external services, and modify user',
'metadata — making them a critical surface for security review.',
'',
'Scope: Auth0 Customer Identity Cloud (CIC) only.',
'All write operations require explicit human approval before execution.',
];

if (warnings.length > 0) {
lines.push('');
for (const w of warnings) lines.push(w);
}

lines.push('');
lines.push('=== End Auth0 Context ===');

// Claude Code SessionStart hooks output plain text injected into the system context.
process.stdout.write(lines.join('\n') + '\n');
Loading
Loading