Compare commits

...
Author SHA1 Message Date
Alan Buscaglia 7598d1a636 feat(codex): add Prowler plugin
- Add the Codex marketplace and MCP-backed compliance workflow
- Document installation and environment-backed authentication
- Add packaging and parity coverage
2026-08-27 19:01:16 +02:00
7 changed files with 504 additions and 88 deletions
+20
View File
@@ -0,0 +1,20 @@
{
"name": "prowler-plugins",
"description": "Prowler Cloud Security for Codex",
"owner": {
"name": "Prowler",
"email": "support@prowler.com"
},
"plugins": [
{
"name": "prowler",
"source": {
"source": "local",
"path": "./codex_plugins/prowler"
},
"description": "Prowler for Codex — cloud security and compliance skills powered by the Prowler MCP server.",
"category": "security",
"homepage": "https://prowler.com"
}
]
}
@@ -0,0 +1,24 @@
{
"name": "prowler",
"displayName": "Prowler Cloud Security",
"version": "0.1.0",
"description": "Cloud security and compliance skills powered by the Prowler MCP server.",
"author": {
"name": "Prowler",
"email": "support@prowler.com",
"url": "https://prowler.com"
},
"homepage": "https://docs.prowler.com",
"repository": "https://github.com/prowler-cloud/prowler",
"license": "Apache-2.0",
"category": "security",
"keywords": [
"prowler",
"security",
"compliance",
"cloud-security",
"mcp"
],
"skills": "./skills/",
"mcpServers": "./.mcp.json"
}
+12
View File
@@ -0,0 +1,12 @@
{
"mcpServers": {
"prowler": {
"type": "http",
"url": "https://mcp.prowler.com/mcp",
"bearer_token_env_var": "PROWLER_API_KEY",
"headers": {
"User-Agent": "codex"
}
}
}
}
+54
View File
@@ -0,0 +1,54 @@
# Prowler for Codex
Prowler for Codex adds Prowler Cloud security and compliance skills and connects Codex to the Prowler MCP server.
## Prerequisites
- Codex CLI with plugin support. Check with `codex --version` and `codex plugin --help`.
- A Prowler Cloud API key. Create one in [Prowler Cloud](https://cloud.prowler.com).
## Install
Export the API key in the shell that launches Codex. Set `PROWLER_API_KEY` to the raw API key only, without the `Bearer` prefix. Codex adds the bearer prefix automatically. Do not add a real key to a repository, shell history, or shared configuration file.
```bash
export PROWLER_API_KEY=...
codex plugin marketplace add prowler-cloud/prowler --ref master
codex plugin add prowler@prowler-plugins
```
The marketplace does not install Prowler by default. The `codex plugin add` command explicitly installs it.
## Verify
```bash
codex plugin list --marketplace prowler-plugins
```
Start Codex from the same shell, then ask it to list your Prowler providers or to help triage a compliance framework.
## Update and Uninstall
Refresh the marketplace snapshot:
```bash
codex plugin marketplace upgrade prowler-plugins
```
Remove the plugin:
```bash
codex plugin remove prowler@prowler-plugins
```
If you no longer use the marketplace, remove it after uninstalling the plugin:
```bash
codex plugin marketplace remove prowler-plugins
```
## Environment Limitation
The Codex CLI reliably receives `PROWLER_API_KEY` when it starts from the shell where you exported it. Codex Desktop and IDE integrations may not inherit variables from your shell profile, so the plugin can fail to authenticate there even when it works in the CLI.
The Codex plugin workflow does not securely prompt for or store arbitrary API keys. If your Codex surface cannot inherit `PROWLER_API_KEY`, use the manual MCP setup in the [Prowler Codex guide](https://docs.prowler.com/user-guide/ai-agents/codex) instead.
@@ -0,0 +1,199 @@
---
name: prowler-framework-compliance-triage
description: Make a cloud account compliant with a security or industry framework using Prowler Cloud.
---
# Framework compliance
Iterative, interactive flow that takes a cloud account through setup, reporting, and remediation until it complies with the chosen security or industry framework.
## Checkpoints
This skill uses **checkpoints** to mark moments where you must stop, post a clear question or summary to the user, and wait for the reply before continuing. Each checkpoint is rendered like this:
> **Checkpoint — <name>**
>
> What to present, and what to wait for.
Treat every checkpoint as a hard stop:
- Do not skip a checkpoint because the user previously said "go ahead", "just do it", or similar. Confirmations are scoped to a single checkpoint and do not transfer to later ones.
- Do not bundle two checkpoints into one message. Post one, wait for the reply, then continue.
- Do not infer the user's answer from context or proceed on silence. Ask explicitly and wait.
- If a checkpoint is conditional (e.g. only fires when multiple accounts exist), evaluate the condition first; if it does not apply, continue without prompting.
- If the user's initial message already answers the question a checkpoint asks (e.g. "make my AWS subscription compliant with CIS using Terraform autonomously"), treat the checkpoint as satisfied for the parts they covered, and only ask for what is still missing.
## 1. Initial Prowler Cloud setup
> **Checkpoint — Provider and framework selection**
>
> If the user has not already specified both the provider and the framework, ask explicitly and wait for the answer. If they have specified them in their opening message, skip this checkpoint.
Confirm both are supported by the Prowler Hub MCP:
- Enumerate supported providers with `prowler_hub_list_providers`.
- Enumerate frameworks for the chosen provider with `prowler_hub_list_compliances`, passing the provider `id` as the only element of the `provider` input list.
If the framework is not supported, tell the user, suggest they request it or contribute it themselves, and end the flow. Otherwise continue.
### 1.1 Connect to Prowler Cloud
Verify the Prowler MCP connection by calling `prowler_search_providers` — a successful response returns the list of providers. If the call fails, walk the user through troubleshooting: internet connectivity, Prowler Cloud credentials, and permissions on the Prowler Cloud account.
For getting accurate information about configurations use `prowler_docs_search` to pull relevant instructions from the Prowler documentation.
### 1.2 Verify the provider is configured (or configure it)
Call `prowler_search_providers` to check whether the target provider (AWS account, Azure Subscription, GitHub Account...) exists in the user's Prowler Cloud account. Handle the result based on what's found:
- **Provider not present.** Guide the user through adding and configuring it. Retrieve the relevant connection, credential, and permission instructions with `prowler_docs_search`.
- **Provider present but misconfigured** (missing credentials, insufficient permissions, etc.). Walk the user through fixing the configuration, pulling the relevant guidance with `prowler_docs_search`.
- **Provider present and configured.** Continue.
> **Checkpoint — Account selection** *(conditional: more than one account of the chosen provider is configured)*
>
> List the accounts with helpful detail (account name, uid, last scan date) and ask which one to use. Wait for the answer. If only one account exists, skip this checkpoint and use it.
### 1.3 Review compliance report for the provider account
The flow needs at least one completed scan with a compliance report available.
Look for a completed scan first: call `prowler_list_scans` with the selected `provider_id` and `state: ["completed"]`, then call `prowler_get_compliance_overview` with each `scan_id` to find one whose compliance report is available. If one is found, continue to the next section.
If no completed scan has a report, call `prowler_list_scans` again with `state: ["available", "executing"]` to detect a scan in progress.
> **Checkpoint — Scan-in-progress decision** *(conditional: an in-progress scan was detected)*
>
> Tell the user a scan is already running and ask whether to wait for it to complete or start a fresh one. Wait for the answer.
If no scan is running (or the user chose to start a fresh one), trigger a new scan with `prowler_trigger_scan` and the `provider_id`. The link `https://cloud.prowler.com/scans?filter%5Bprovider_uid__in%5D={provider_id}` lets the user monitor progress.
When a scan is in progress (either pre-existing and elected to wait, or just triggered), stop the flow and ask the user to return when it's completed — restart this section to re-check the results.
## 2. Compliance report
Every iteration of the remediation loop reads and writes a single markdown file per provider account and framework, stored at `.prowler/compliance-<compliance_id>-<provider_uid>.md` relative to the current project root. Sanitize `<provider_uid>` to `[a-zA-Z0-9_-]` by replacing anything else with `-`. Create `.prowler/` if missing.
Across iterations, edit only: status tags on failed requirements and their findings, the per-requirement `Fix plan` / `Fix applied` sub-bullets added during sections 3.3–3.4, the **Global remediation approach** block, and the **Activity log** (append-only, newest on top). Requirement descriptions, finding IDs, and the entire **Manual review requirements** section are read-only after first render.
Status taxonomy for failed requirements and their findings:
- `[FAIL]` — failing in the latest scan.
- `[IN PROGRESS]` — picked up by section 3.3.
- `[FIXED-UNVERIFIED]` — remediation applied; not yet confirmed.
- `[PASS]` — passing in the latest scan (set when a rescan in section 3.5 confirms the fix).
- `[SKIPPED]` — user explicitly deferred.
### Report template
A fresh report is rendered like this (substituting values from the `prowler_get_compliance_framework_state_details` Prowler MCP tool response):
````markdown
# Compliance report: <compliance_id>
**Provider account**: <display name + uid>
**Scan ID**: <scan_id>
**Generated**: <ISO timestamp>
**Last update**: <ISO timestamp>
**Status**: <passed>/<total> passing (<pct>%) · <failed> failing · <manual_review> manual review
## Global remediation approach
<!-- Filled by section 3.1. -->
- **Primary tool**: _Terraform | Azure CLI | AWS CLI | web console | mixed_
- **Mode**: _Claude autonomous | Claude-assisted_
- **Notes**:
## Activity log
- <ISO timestamp> — Report initialized from scan `<scan_id>`.
## Failed requirements
### <code> — [FAIL]
**Description**: <text>
**Findings** (<n>):
- [FAIL] `<finding_id>`
## Manual review requirements
- **<code>** — [PENDING]: <description>
````
### 2.1 Generate or refresh the report
Resolve the report path for the current `compliance_id` and provider account.
If the file does not exist, call `prowler_get_compliance_framework_state_details` for the target scan, render the template above, and write the file with one initialization entry in the activity log.
If the file exists, read it and compare its `Scan ID` to the target scan from section 1.3. When the scan matches, reuse the file and summarize remaining `[FAIL]` and `[IN PROGRESS]` items in chat.
> **Checkpoint — Report refresh** *(conditional: the file's `Scan ID` differs from the current target scan)*
>
> Tell the user the report on disk was generated from a different scan and ask whether to refresh it from the new scan. Wait for the answer.
On confirmation, regenerate the failed-requirements section from the new `prowler_get_compliance_framework_state_details` response, carry forward the **Global remediation approach** block and the full activity log, and append an activity-log entry noting the scan change.
Once the file is current, surface the top failing requirements in chat: sort by finding count descending, show the top 5 with their codes and counts, and point to the file path for the full list.
## 3. Remediation loop
### 3.1 Define the global remediation approach
Two modes are available:
- **Claude-assisted** (default when the user has not specified): per-requirement confirmation. For each requirement Claude shows the target resource, exact commands, side effects, and reversibility, then waits for explicit go-ahead before applying.
- **Claude autonomous**: no per-requirement gate, but Claude still presents one batch-level fix plan up front (§3.2) and waits for a single confirmation, and pauses if a finding looks not applicable, requires a paid feature, or has wide blast radius (breaks dev workflow, forces collaborator changes, is hard to reverse).
If the user phrases their request as "just do it" or similar, treat that as autonomous **with** the batch-plan confirmation still required — the confirmation is a property of the skill, not the user's verbosity preference.
> **Checkpoint — Global remediation approach**
>
> Ask the user which tool to use for fixes (Terraform, gh / az / aws CLI, web console, mixed...) and which mode to operate in. Wait for the answer before continuing. This checkpoint is non-negotiable: never assume a default tool, and never assume autonomous mode.
Once answered, write the values into the **Global remediation approach** block of the report file.
> **Checkpoint — Overwriting an existing approach** *(conditional: the block is already populated from a previous session)*
>
> Show the previous values and the new ones, and ask the user to confirm before overwriting. Wait for the answer.
### 3.2 Present the batch fix plan *(autonomous mode only)*
In **assisted** mode, skip this section — the per-requirement gate in §3.3 confirms each fix as it comes up. Only run §3.2 in **autonomous** mode, where the loop will otherwise apply fixes without further input.
Before touching anything, post a single chat summary covering every `[FAIL]` requirement:
- Group findings that share a fix (e.g. ten branch-protection requirements satisfied by one PUT call → present as one group).
- For each group: target resource, exact tool calls, side effects, reversibility.
- Call out findings that look **not applicable** to this target (e.g. an Organization-only check evaluated against a User account, a feature gated by a paid plan, a resource type the user doesn't have) and propose `[SKIPPED]` with the reason.
- Call out findings that require manual user action Claude cannot perform.
> **Checkpoint — Batch fix plan approval** *(conditional: autonomous mode)*
>
> Post the grouped plan and wait for explicit confirmation. Do not start any fix before the user replies.
Once approved, the loop proceeds through the batch without further prompts unless something deviates from the approved plan.
### 3.3 Pick the first FAIL requirement and inspect its findings
Pick the first `[FAIL]` requirement at the top of the failed-requirements section. Move its status and every finding under it to `[IN PROGRESS]`, and add a `**Fix plan**:` sub-bullet describing what will be done.
Call `prowler_get_finding_details` for each `finding_id` to retrieve the failing resource and the Prowler Hub's remediation guidance for that check using the tool `prowler_hub_get_check_details` with the `check_id` from the finding details. Summarize the guidance in chat, and append it to the `**Fix plan**` note for each finding.
If a finding does not apply to the target resource (Organization-only check on a User account, paid-tier feature, missing resource type, etc.), set the requirement status to `[SKIPPED]` with the reason, log it in the activity log, and move on without attempting the fix — even if it was missed during §3.2.
> **Checkpoint — Per-requirement approval** *(conditional: assisted mode)*
>
> Post the per-requirement plan in chat — resource, command, side effects, reversibility — and wait for confirmation before moving to §3.4. In **autonomous** mode, post the plan for transparency but proceed unless it deviates from the batch plan agreed in §3.2.
### 3.4 Diagnose, fix, verify
Read the remediation guidance returned in §3.3, identify the root cause, and apply the fix using the tool defined in the **Global remediation approach** block. After applying, verify via the same tool that applied the fix or via a provider API call when applicable. If the re-read shows the change did not land, leave the status at `[IN PROGRESS]`, surface the error to the user, and stop the loop for this requirement. On post-fix verification failure, record the failure in the activity log and set the requirement status back to `[FAIL]` so the next loop can retry or choose another remediation.
When the change is in place, append a `**Fix applied**: <tool, summary, refs>` sub-bullet to the requirement, move each fixed finding to `[FIXED-UNVERIFIED]`, and add one activity-log entry describing the change. If no programmatic verification was possible (e.g. web console action), note in the activity log that confirmation depends on the rescan in §3.5.
### 3.5 Loop
Move to the next `[FAIL]` requirement and repeat from section 3.3.
> **Checkpoint — Rescan trigger** *(conditional: no `[FAIL]` requirements remain; all are `[FIXED-UNVERIFIED]` or `[SKIPPED]`)*
>
> Summarize what was applied, list any `[SKIPPED]` items with reasons, and ask whether to trigger a fresh scan with `prowler_trigger_scan` to verify the fixes end-to-end. Wait for the answer.
On confirmation, trigger the rescan. When it completes, restart section 2.1 with the carry-forward path — requirements no longer in the new FAIL list move to `[PASS]`, anything still failing reverts to `[FAIL]` with the previous fix attempt visible in the activity log.
+89 -88
View File
@@ -5,104 +5,106 @@ sidebarTitle: "Codex / ChatGPT"
Connect [OpenAI Codex](https://learn.chatgpt.com/docs/extend/mcp) to the Prowler Cloud MCP Server at `https://mcp.prowler.com/mcp` so Codex can query findings, inspect checks, and manage your Prowler providers.
## Which Codex Surfaces Work
## Preferred Setup: Install the Prowler Plugin
Codex keeps MCP servers in one file, `~/.codex/config.toml`. You can set it up from either the **Codex / ChatGPT desktop app** or the **Codex CLI** — both write to that same file, so pick whichever you already use.
The Prowler plugin is the recommended setup for Codex CLI. It adds Prowler Cloud security and compliance skills and configures the Prowler MCP Server with the required `User-Agent` header.
| Surface | Set it up here | Notes |
|---------|----------------|-------|
| **[Codex / ChatGPT desktop app](https://learn.chatgpt.com/docs/app)** (macOS, Windows) | ✅ Yes | **Settings → MCP servers** |
| **Codex CLI** (terminal) | ✅ Yes | `codex mcp` commands |
| **Codex IDE extension** (VS Code) | Inherits | Works automatically once the app or CLI is configured |
| **ChatGPT on the web** | ❌ No | Does not read local Codex configuration |
<Note>
**Codex and ChatGPT share one desktop app.** Since July 2026 the standalone Codex app and the ChatGPT desktop app are the same application: Codex is a dedicated coding surface inside it, alongside Chat and Work. If you already had the Codex app, updating turns it into the new ChatGPT desktop app and it still opens in Codex. Either way, this guide applies.
Not to be confused with **ChatGPT Classic**, the name given to the previous-generation ChatGPT desktop app.
</Note>
<Note>
**Configure once, use everywhere.** The Codex documentation states that the ChatGPT desktop app, Codex CLI, and IDE extension "share this configuration. Once you configure your MCP servers, you can switch among those clients without redoing setup." Set the server up in the app or the CLI and the IDE extension picks it up with no extra work.
</Note>
| Surface | Recommended Setup | Notes |
|---------|-------------------|-------|
| **Codex CLI** | Prowler plugin | Reliably inherits the API key from the launching shell. |
| **Codex / ChatGPT desktop app** | Plugin when supported, otherwise [manual MCP setup](#manual-mcp-setup-advanced-or-fallback) | May require configuring the application launch environment. |
| **Codex IDE extension** | Plugin when supported, otherwise [manual MCP setup](#manual-mcp-setup-advanced-or-fallback) | May require configuring the IDE launch environment. |
| **ChatGPT on the web** | Not supported | Does not read local Codex configuration. |
## Prerequisites
- **The Codex / ChatGPT desktop app, or Codex CLI 0.46.0 or later.** Remote MCP servers over streamable HTTP were added to the CLI in 0.46.0 — check with `codex --version` and upgrade if needed.
- **Codex CLI with plugin support.** Check with `codex --version` and `codex plugin --help`.
- **A Prowler Cloud account.** The free tier is enough to start. Sign up at [cloud.prowler.com](https://cloud.prowler.com).
## Step 1: Get Your Prowler API Key
Create an API key in Prowler Cloud and copy it. The key begins with `pk_` and is shown only once. Check the [API Keys](/user-guide/tutorials/prowler-app-api-keys#creating-api-keys) guide for details.
Create an API key in Prowler Cloud and copy it. The key is shown only once. Check the [API Keys](/user-guide/tutorials/prowler-app-api-keys#creating-api-keys) guide for details.
## Step 2: Add the Prowler MCP Server
## Step 2: Set the API Key Environment Variable
The Prowler MCP Server needs two request headers: `Authorization` to authenticate you, and `User-Agent` because Codex does not send one by default.
Export the key in the shell that launches Codex. Do not add a real key to a repository, shell history, or shared configuration file.
Each tab below is a complete setup — follow the one that matches the surface you use.
```bash
export PROWLER_API_KEY="<your-Prowler-Cloud-API-key>"
```
<Tabs>
<Tab title="Codex / ChatGPT desktop app">
1. Open **Settings** and select **Plugins → MCPs**
2. Click **Add server**
3. Enter `prowler` as the name and choose type **Streamable HTTP**
4. Enter the URL `https://mcp.prowler.com/mcp`
5. Add two headers:
| Header | Value |
|--------|-------|
| `Authorization` | `Bearer pk_your_api_key_here` |
| `User-Agent` | `codex` |
6. Save the server
<Frame>
<img src="/images/prowler-mcp/codex/codex-app-mcp-servers.png" alt="Codex / ChatGPT desktop app Settings showing the MCP servers panel with the Add server dialog and both headers filled in" />
</Frame>
<Note>
**Enter the key directly here rather than using an environment variable.** Codex can read credentials from an environment variable, but desktop applications do not reliably inherit variables exported in a shell profile — on macOS an app launched from Finder or the Dock typically sees none of them. Pasting the key into the dialog is the approach that works consistently in the app.
</Note>
<Warning>
**This stores your API key in plain text** in `~/.codex/config.toml`. Treat that file accordingly: exclude it from dotfile repositories and config sync, and create the key from an account with the minimum permissions you need so its exposure is limited. Revoke and re-issue the key in Prowler Cloud if the file is ever shared.
</Warning>
</Tab>
<Tab title="Codex CLI">
Register the server:
```bash
codex mcp add prowler --url https://mcp.prowler.com/mcp
```
Codex confirms with `Added global MCP server 'prowler'.`
Then add both headers by hand, since `codex mcp add` has no flag for headers. Open `~/.codex/config.toml` and complete the entry:
```toml
[mcp_servers.prowler]
url = "https://mcp.prowler.com/mcp"
http_headers = { Authorization = "Bearer pk_your_api_key_here", "User-Agent" = "codex" }
```
<Note>
**Write the key literally rather than using an environment variable.** This is the form that works across every Codex surface. All of them read this same file, but only the CLI reliably sees variables exported in your shell profile — see the warning below.
</Note>
<Warning>
**This stores your API key in plain text** in `~/.codex/config.toml`. Treat that file accordingly: exclude it from dotfile repositories and config sync, and create the key from an account with the minimum permissions you need so its exposure is limited. Revoke and re-issue the key in Prowler Cloud if the file is ever shared.
</Warning>
</Tab>
</Tabs>
Restart Codex once you are done.
`PROWLER_API_KEY` must contain the raw API key, without the `Bearer ` prefix. The plugin adds that prefix when it sends the `Authorization` header.
<Note>
**Local server:** Replace the URL with your own HTTP endpoint. Everything else stays the same.
**Codex CLI reliably inherits this variable** when it starts from the shell where you exported it. Codex Desktop and IDE integrations may not inherit variables from your shell profile. Configure the desktop application or IDE launch environment with `PROWLER_API_KEY` when you use those surfaces.
</Note>
## Step 3: Verify the Connection
## Step 3: Install the Plugin
Add the Prowler marketplace, then install the Prowler plugin:
```bash
codex plugin marketplace add prowler-cloud/prowler --ref master
codex plugin add prowler@prowler-plugins
```
The marketplace does not install Prowler by default. The `codex plugin add` command explicitly installs it.
## Step 4: Verify the Plugin
```bash
codex plugin list --marketplace prowler-plugins
```
Start Codex from the same shell, then ask it to list your Prowler providers or to help triage a compliance framework.
## Manual MCP Setup: Advanced or Fallback
Use this setup only when the plugin is unavailable for your Codex surface or when you need a custom MCP configuration. The manual configuration must include the `User-Agent` header because Codex does not send one by default.
### Codex CLI
Register the server:
```bash
codex mcp add prowler --url https://mcp.prowler.com/mcp
```
Then update `~/.codex/config.toml` because `codex mcp add` has no flag for the required header:
```toml
[mcp_servers.prowler]
url = "https://mcp.prowler.com/mcp"
bearer_token_env_var = "PROWLER_API_KEY"
http_headers = { "User-Agent" = "codex" }
```
This manual CLI configuration uses the same secure environment-backed contract as the plugin: `PROWLER_API_KEY` holds the raw key, and Codex adds the `Bearer ` prefix.
### Codex / ChatGPT Desktop App
First configure `PROWLER_API_KEY` in the desktop application's launch environment. An API key exported only in a shell profile may not be available when the app starts from Finder, the Dock, or a launcher.
If you cannot configure the launch environment, add the server manually:
1. Open **Settings** and select **Plugins → MCPs**
2. Click **Add server**
3. Enter `prowler` as the name and choose type **Streamable HTTP**
4. Enter the URL `https://mcp.prowler.com/mcp`
5. Add the `User-Agent` header with the value `codex`
6. If the app supports a reference to its launch environment, use `PROWLER_API_KEY`. Otherwise, add an `Authorization` header with `Bearer ` followed by the API key.
<Warning>
A literal `Authorization` header stores the API key in plain text in `~/.codex/config.toml`. Use it only as a last resort, exclude the file from dotfile repositories and configuration sync, and revoke and re-issue the key if the file is shared.
</Warning>
Restart Codex after installing the plugin or changing the MCP configuration.
<Note>
**Local server:** Replace the URL with your own HTTP endpoint. Keep the same authentication and `User-Agent` configuration.
</Note>
## Step 5: Verify the Connection
Run `/mcp` in the app or in a CLI session to list connected servers and their tools.
@@ -121,7 +123,7 @@ codex mcp get prowler # full entry, header values masked
**Verify rather than assume.** Codex silently ignores unrecognized keys in `config.toml` — a misspelled key name produces no error at all, and the server simply never receives your credentials. Always confirm with `codex mcp get prowler` after editing the file by hand.
</Warning>
## Step 4: Start Using Prowler MCP
## Step 6: Start Using Prowler MCP
Ask Codex questions that use the Prowler tools:
@@ -145,15 +147,14 @@ Codex reports a handshake failure on startup, with an HTML error page rather tha
<head><title>403 Forbidden</title></head>
```
The `User-Agent` header is missing. Codex's HTTP client does not send one, and requests without it are rejected before reaching the MCP server. Note this is a **403**, not a 401 — so it is not an API key problem. Add the header as shown in [Step 2](#step-2-add-the-prowler-mcp-server); the value itself does not matter, only that the header is present.
The `User-Agent` header is missing. Codex's HTTP client does not send one, and requests without it are rejected before reaching the MCP server. Note this is a **403**, not a 401 — so it is not an API key problem. The plugin adds this header automatically. For a manual configuration, add it as shown in [Manual MCP Setup](#manual-mcp-setup-advanced-or-fallback); the value itself does not matter, only that the header is present.
### Authentication Fails With 401
- Run `codex mcp get prowler` and confirm the entry has the headers you expect. Values are masked, but a missing header shows as `-`.
- If you used a literal header, confirm the value starts with `Bearer ` and contains the full key.
- **If it works in the CLI but fails in the desktop app or the VS Code extension, you are almost certainly using an environment variable.** Those surfaces do not inherit your shell profile. Switch that entry to a literal `Authorization` header as shown in [Step 2](#step-2-add-the-prowler-mcp-server).
- If you use an environment variable, verify it is set in the environment Codex was launched from: `echo $PROWLER_API_KEY`.
- With `env_http_headers` the variable must include the `Bearer ` prefix. With `bearer_token_env_var` it must **not** — Codex adds the prefix itself.
- For a manual configuration, run `codex mcp get prowler` and confirm the entry has the headers you expect. Values are masked, but a missing header shows as `-`.
- Verify the Codex process has `PROWLER_API_KEY` in its launch environment without printing the key: `test -n "$PROWLER_API_KEY" && echo "PROWLER_API_KEY is set"`.
- `PROWLER_API_KEY` must contain the raw key without `Bearer `. The plugin and the `bearer_token_env_var` manual configuration add the prefix automatically.
- If the CLI works but Codex Desktop or an IDE fails, configure `PROWLER_API_KEY` in that application's launch environment. Shell-profile exports may not be inherited. Use a literal `Authorization` header only as the last-resort desktop fallback described in [Manual MCP Setup](#manual-mcp-setup-advanced-or-fallback).
- Confirm the key has not been revoked in Prowler Cloud.
### Server Not Listed
+106
View File
@@ -0,0 +1,106 @@
import json
import re
import unittest
from pathlib import Path
REPOSITORY_ROOT = Path(__file__).resolve().parents[2]
CODEX_PLUGIN_ROOT = REPOSITORY_ROOT / "codex_plugins" / "prowler"
CLAUDE_SKILL = (
REPOSITORY_ROOT
/ "claude_plugins"
/ "prowler"
/ "skills"
/ "framework-compliance-triage"
/ "SKILL.md"
)
CODEX_SKILL = CODEX_PLUGIN_ROOT / "skills" / "framework-compliance-triage" / "SKILL.md"
CODEX_POST_FIX_VERIFICATION_FAILURE_SAFETY_SENTENCE = (
"On post-fix verification failure, record the failure in the activity log and set "
"the requirement status back to `[FAIL]` so the next loop can retry or choose another remediation."
)
def load_json(path: Path) -> dict:
with path.open(encoding="utf-8") as file:
return json.load(file)
def normalize_skill_runtime_identity(content: str) -> str:
"""Normalize intentional Codex-only runtime differences."""
content = re.sub(
r"^name: prowler-framework-compliance-triage$",
"name: framework-compliance-triage",
content,
count=1,
flags=re.MULTILINE,
)
content = content.replace(
"stored at `.prowler/compliance-<compliance_id>-<provider_uid>.md` "
"relative to the current project root.",
"stored at `${CLAUDE_PROJECT_DIR}/.prowler/compliance-<compliance_id>-<provider_uid>.md`.",
)
return content.replace(
f" {CODEX_POST_FIX_VERIFICATION_FAILURE_SAFETY_SENTENCE}", ""
)
class TestCodexPluginPackaging(unittest.TestCase):
def test_marketplace_and_plugin_package_match_the_codex_contract(self):
marketplace = load_json(REPOSITORY_ROOT / ".agents/plugins/marketplace.json")
plugin = load_json(CODEX_PLUGIN_ROOT / ".codex-plugin/plugin.json")
mcp = load_json(CODEX_PLUGIN_ROOT / ".mcp.json")
self.assertEqual(marketplace["name"], "prowler-plugins")
self.assertEqual(len(marketplace["plugins"]), 1)
self.assertEqual(marketplace["plugins"][0]["name"], "prowler")
self.assertEqual(
marketplace["plugins"][0]["source"],
{"source": "local", "path": "./codex_plugins/prowler"},
)
self.assertNotIn("installation", marketplace["plugins"][0].get("policy", {}))
self.assertEqual(plugin["name"], "prowler")
self.assertEqual(plugin["version"], "0.1.0")
self.assertEqual(plugin["license"], "Apache-2.0")
self.assertEqual(plugin["skills"], "./skills/")
self.assertEqual(plugin["mcpServers"], "./.mcp.json")
self.assertEqual(
mcp["mcpServers"]["prowler"],
{
"type": "http",
"url": "https://mcp.prowler.com/mcp",
"bearer_token_env_var": "PROWLER_API_KEY",
"headers": {"User-Agent": "codex"},
},
)
self.assertTrue((CODEX_PLUGIN_ROOT / "README.md").is_file())
def test_readme_requires_a_raw_api_key_without_bearer_prefix(self):
readme = (CODEX_PLUGIN_ROOT / "README.md").read_text(encoding="utf-8")
self.assertIn(
"Set `PROWLER_API_KEY` to the raw API key only, without the `Bearer` prefix. "
"Codex adds the bearer prefix automatically.",
readme,
)
def test_framework_compliance_skill_preserves_claude_behavior(self):
claude_skill = CLAUDE_SKILL.read_text(encoding="utf-8")
codex_skill = CODEX_SKILL.read_text(encoding="utf-8")
self.assertNotIn("CLAUDE_PROJECT_DIR", codex_skill)
self.assertIn(
"stored at `.prowler/compliance-<compliance_id>-<provider_uid>.md` "
"relative to the current project root.",
codex_skill,
)
self.assertIn(CODEX_POST_FIX_VERIFICATION_FAILURE_SAFETY_SENTENCE, codex_skill)
self.assertEqual(
normalize_skill_runtime_identity(codex_skill),
claude_skill,
)
if __name__ == "__main__":
unittest.main()