{
  "generated_from": "https://github.com/vaubaan/gateway/tree/main/plugins",
  "validation_thresholds": {
    "min_reports": 200,
    "min_success_rate": 0.9,
    "window_days": 90
  },
  "health_read_at": null,
  "plugins": [
    {
      "id": "cloudflare-dns",
      "name": "Cloudflare DNS",
      "version": "3.0.0",
      "status": "stable",
      "description": "DNS zones and records on Cloudflare : records, BIND import / export, zone settings, DNSSEC and cache purge — one resource per zone.",
      "page": "https://vaubaan.com/plugins/cloudflare-dns",
      "source": "https://github.com/vaubaan/gateway/tree/main/plugins/cloudflare-dns",
      "health": {
        "state": "not-enough-reports"
      },
      "tools": [
        {
          "name": "cloudflare_dns__list_zones",
          "title": "List DNS zones",
          "description": "List the DNS zones of the mission with their status and nameservers. Backed by GET /zones ; the answer is filtered to the zones the mission may touch."
        },
        {
          "name": "cloudflare_dns__get_zone",
          "title": "Read a DNS zone",
          "description": "Retrieve one DNS zone. Backed by GET /zones/:zone_id."
        },
        {
          "name": "cloudflare_dns__list_records",
          "title": "List DNS records",
          "description": "List the DNS records of a zone with optional filters. Backed by GET /zones/:zone_id/dns_records."
        },
        {
          "name": "cloudflare_dns__get_record",
          "title": "Read a DNS record",
          "description": "Retrieve one DNS record by id. Backed by GET /zones/:zone_id/dns_records/:record_id."
        },
        {
          "name": "cloudflare_dns__export_records",
          "title": "Export the DNS records of a zone",
          "description": "Export every DNS record of a zone as a BIND zone file. Backed by GET /zones/:zone_id/dns_records/export."
        },
        {
          "name": "cloudflare_dns__get_zone_settings",
          "title": "Read the settings of a zone",
          "description": "Retrieve every setting of a zone (SSL mode, minimum TLS version, cache level…). Backed by GET /zones/:zone_id/settings."
        },
        {
          "name": "cloudflare_dns__get_dnssec",
          "title": "Read the DNSSEC status",
          "description": "Retrieve the DNSSEC status and DS record of a zone. Backed by GET /zones/:zone_id/dnssec."
        },
        {
          "name": "cloudflare_dns__create_record",
          "title": "Create a DNS record",
          "description": "Create a DNS record in a zone. Backed by POST /zones/:zone_id/dns_records. Apex, wildcard, MX and NS records count as critical changes."
        },
        {
          "name": "cloudflare_dns__update_record",
          "title": "Change a DNS record",
          "description": "Update an existing DNS record. Backed by PATCH /zones/:zone_id/dns_records/:record_id. `type` and `name` are required so the gateway can classify the change ; the call is refused when they do not describe the current record."
        },
        {
          "name": "cloudflare_dns__delete_record",
          "title": "Delete a DNS record",
          "description": "Delete a DNS record. Backed by DELETE /zones/:zone_id/dns_records/:record_id. Pass the `type` and `name` of the record : without them the deletion counts as a critical change (apex, wildcard, MX, NS) ; with them the call is refused when they do not describe the current record."
        },
        {
          "name": "cloudflare_dns__import_records",
          "title": "Import DNS records in bulk",
          "description": "Bulk import DNS records from a BIND zone file (creates / overwrites records in batch). Backed by POST /zones/:zone_id/dns_records/import (multipart/form-data)."
        },
        {
          "name": "cloudflare_dns__edit_zone_settings",
          "title": "Change the settings of a zone",
          "description": "Edit one or more zone settings (SSL mode, minimum TLS version, cache level…). Backed by PATCH /zones/:zone_id/settings."
        },
        {
          "name": "cloudflare_dns__purge_cache",
          "title": "Purge the cache of a zone",
          "description": "Purge cached content of a zone — everything, or by URL, tag, host or prefix. Backed by POST /zones/:zone_id/purge_cache."
        },
        {
          "name": "cloudflare_dns__edit_dnssec",
          "title": "Turn DNSSEC on or off",
          "description": "Enable or disable DNSSEC on a zone. Backed by PATCH /zones/:zone_id/dnssec."
        },
        {
          "name": "cloudflare_dns__delete_zone",
          "title": "Delete a DNS zone",
          "description": "Delete a DNS zone. The domain stops resolving at once ; every record, setting and the DNSSEC configuration are lost. Backed by DELETE /zones/:zone_id."
        }
      ],
      "risks": [
        {
          "id": "cloudflare-dns.read-zone-metadata",
          "label": "Read zone and account metadata",
          "reason": "Inventory of zones, status, nameservers, plan, zone settings and DNSSEC posture. Metadata only, no secrets, no record values. Note that `zone[**].R` is recursive : reading records needs this permission too, so a mission that reads records holds both.",
          "severity": "low",
          "kind": "read",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "cloudflare-dns.read-records",
          "label": "Read DNS records (IPs, CNAMEs, MX, TXT values)",
          "reason": "Exposes the network topology (server IPs, MX relays, SPF/DKIM/DMARC configuration, ACME challenges). Reconnaissance surface for an attacker.",
          "severity": "low",
          "kind": "read",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "cloudflare-dns.export-zone-file",
          "label": "Export the whole zone as a BIND file (all records in one dump)",
          "reason": "Atomic export of every record in BIND format — the complete DNS topology in a single request. Distinct from read-records (paginated) because the export is a one-call bulk exfiltration vector.",
          "severity": "low",
          "kind": "read",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "cloudflare-dns.record-write",
          "label": "Create / update a DNS record",
          "reason": "Posture shift on a DNS record. A changed A/CNAME redirects traffic ; a changed TXT invalidates SPF/DKIM (mail) or ACME (TLS). Costly to revert because the previous value must be known (no DNS versioning). Public by nature. Touched by every record write, including the critical ones and the deletions, which also need their own permission.",
          "severity": "medium",
          "kind": "write",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "cloudflare-dns.critical-record-write",
          "label": "Change an apex, wildcard, MX or NS record",
          "reason": "The record governs the whole domain rather than one host : the apex (`example.com`, `@`) or the wildcard at the apex redirects the main site and every undefined subdomain ; MX reroutes all incoming mail ; NS delegates a subtree to another nameserver. Emitted next to the ordinary record write, so a mission holds it distinctly. When the arguments do not name the record's type and name, the write counts as critical.",
          "severity": "high",
          "kind": "write",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "cloudflare-dns.record-delete",
          "label": "Delete a DNS record",
          "reason": "Removes a record ; the host, mail route or validation token it served stops resolving immediately. Costly to revert : the previous value is gone and must be recovered from elsewhere. Emitted as a verb leaf so a mission can allow edits without allowing deletions.",
          "severity": "medium",
          "kind": "write",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "cloudflare-dns.import-records",
          "label": "Bulk import DNS records from a BIND zone file",
          "reason": "A BIND import creates or overwrites potentially hundreds of records in one call, apex and MX included. Higher blast than a single record write ; non-atomic (partial import on error). Rate-limited upstream.",
          "severity": "high",
          "kind": "write",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "cloudflare-dns.zone-settings-write",
          "label": "Edit zone settings (SSL mode, minimum TLS, cache level…)",
          "reason": "Modifies the security posture of the zone. Lowering the minimum TLS version re-enables known weaknesses ; switching SSL from Full (strict) to Flexible opens the origin leg to interception.",
          "severity": "high",
          "kind": "write",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "cloudflare-dns.dnssec-write",
          "label": "Enable / disable DNSSEC on a zone",
          "reason": "Toggling DNSSEC without the matching DS record at the registrar breaks resolution (SERVFAIL on validating resolvers). Zone-wide security posture shift.",
          "severity": "high",
          "kind": "write",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "cloudflare-dns.cache-purge",
          "label": "Purge the CDN cache (everything, by URL, tag, host or prefix)",
          "reason": "A purge-everything sends every request to the origin — a self-inflicted load spike on a busy site. Reversible (the cache rebuilds) but with an immediate performance cost.",
          "severity": "medium",
          "kind": "write",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "cloudflare-dns.zone-delete",
          "label": "Delete a whole DNS zone (the domain stops resolving)",
          "reason": "Deletes the zone. The domain stops resolving at once ; every record, setting and the DNSSEC configuration are lost. Irreversible in practice — the zone must be recreated and every record reimported. Listed in no preset : a mission that needs it enumerates it explicitly. The pattern ends on the zone ident (`zone[*]`, not `zone[**]`) : a recursive wildcard would make every record or settings write need this irreversible permission.",
          "severity": "critical",
          "kind": "destructive",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "cloudflare-dns.uncategorized-write",
          "label": "Uncategorized Cloudflare DNS write",
          "reason": "Catch-all for writes without a dedicated entry. Seen in production, it means a tool classifies to a slice no entry covers : add the entry.",
          "severity": "high",
          "kind": "write",
          "acceptable_in_envs": null,
          "fallback": true
        }
      ],
      "presets": [
        {
          "id": "read-only",
          "label": "Read-only",
          "description": "Zone metadata, settings, DNSSEC posture and record values of the selected zones. No export, no write.",
          "entries": [
            {
              "risk_id": "cloudflare-dns.read-zone-metadata",
              "gate": "auto",
              "quota": 2000,
              "rate_limit": {
                "max": 120,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "cloudflare-dns.read-records",
              "gate": "auto",
              "quota": 2000,
              "rate_limit": {
                "max": 120,
                "window_seconds": 60
              }
            }
          ]
        },
        {
          "id": "dns-operator",
          "label": "DNS operator",
          "description": "Read, then create and update ordinary subdomain records and purge the cache. No apex / MX / NS change, no deletion, no import, no zone settings.",
          "entries": [
            {
              "risk_id": "cloudflare-dns.read-zone-metadata",
              "gate": "auto",
              "quota": 2000,
              "rate_limit": {
                "max": 120,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "cloudflare-dns.read-records",
              "gate": "auto",
              "quota": 2000,
              "rate_limit": {
                "max": 120,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "cloudflare-dns.record-write",
              "gate": "auto",
              "quota": 500,
              "rate_limit": {
                "max": 30,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "cloudflare-dns.cache-purge",
              "gate": "auto",
              "quota": 50,
              "rate_limit": {
                "max": 10,
                "window_seconds": 60
              }
            }
          ]
        },
        {
          "id": "zone-admin",
          "label": "Zone admin",
          "description": "Everything on the selected zones except deleting them; record deletion, apex / MX / NS changes, imports, zone settings and DNSSEC ask first.",
          "entries": [
            {
              "risk_id": "cloudflare-dns.read-zone-metadata",
              "gate": "auto",
              "quota": 5000,
              "rate_limit": {
                "max": 120,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "cloudflare-dns.read-records",
              "gate": "auto",
              "quota": 5000,
              "rate_limit": {
                "max": 120,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "cloudflare-dns.export-zone-file",
              "gate": "auto",
              "quota": 50,
              "rate_limit": {
                "max": 10,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "cloudflare-dns.record-write",
              "gate": "auto",
              "quota": 1000,
              "rate_limit": {
                "max": 60,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "cloudflare-dns.critical-record-write",
              "gate": "approval",
              "quota": 50,
              "rate_limit": {
                "max": 10,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "cloudflare-dns.record-delete",
              "gate": "approval",
              "quota": 200,
              "rate_limit": {
                "max": 30,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "cloudflare-dns.import-records",
              "gate": "approval",
              "quota": 10,
              "rate_limit": {
                "max": 3,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "cloudflare-dns.zone-settings-write",
              "gate": "approval",
              "quota": 50,
              "rate_limit": {
                "max": 10,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "cloudflare-dns.dnssec-write",
              "gate": "approval",
              "quota": 10,
              "rate_limit": {
                "max": 5,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "cloudflare-dns.cache-purge",
              "gate": "auto",
              "quota": 100,
              "rate_limit": {
                "max": 20,
                "window_seconds": 60
              }
            }
          ]
        }
      ]
    },
    {
      "id": "context7",
      "name": "Context7",
      "version": "0.1.0",
      "status": "stable",
      "description": "Look up current library documentation with Context7, through the official Context7 MCP server.",
      "page": "https://vaubaan.com/plugins/context7",
      "source": "https://github.com/vaubaan/gateway/tree/main/plugins/context7",
      "health": {
        "state": "not-enough-reports"
      },
      "tools": [
        {
          "name": "context7__resolve-library-id",
          "title": "Find a library in Context7",
          "description": "Resolves a package/product name to a Context7-compatible library ID and returns matching libraries.\n\nYou MUST call this function before 'Query Documentation' tool to obtain a valid Context7-compatible library ID UNLESS the user explicitly provides a library ID in the format '/org/project' or '/org/project/version' in their query.\n\nEach result includes:\n- Library ID: Context7-compatible identifier (format: /org/project)\n- Name: Library or package name\n- Description: Short summary\n- Code Snippets: Number of available code examples\n- Source Reputation: Authority indicator (High, Medium, Low, or Unknown)\n- Benchmark Score: Quality indicator (100 is the highest score)\n- Versions: List of versions if available. Use one of those versions if the user provides a version in their query. The format of the version is /org/project/version.\n\nFor best results, select libraries based on name match, source reputation, snippet coverage, benchmark score, and relevance to your use case.\n\nSelection Process:\n1. Analyze the query to understand what library/package the user is looking for\n2. Return the most relevant match based on:\n- Name similarity to the query (exact matches prioritized)\n- Description relevance to the query's intent\n- Documentation coverage (prioritize libraries with higher Code Snippet counts)\n- Source reputation (consider libraries with High or Medium reputation more authoritative)\n- Benchmark Score: Quality indicator (100 is the highest score)\n\nResponse Format:\n- Return the selected library ID in a clearly marked section\n- Provide a brief explanation for why this library was chosen\n- If multiple good matches exist, acknowledge this but proceed with the most relevant one\n- If no good matches exist, clearly state this and suggest query refinements\n\nFor ambiguous queries, request clarification before proceeding with a best-guess match.\n\nIMPORTANT: Do not call this tool more than 3 times per question. If you cannot find what you need after 3 calls, use the best result you have."
        },
        {
          "name": "context7__query-docs",
          "title": "Read a library's documentation",
          "description": "Retrieves and queries up-to-date documentation and code examples from Context7 for any programming library or framework.\n\nYou must call 'Resolve Context7 Library ID' tool first to obtain the exact Context7-compatible library ID required to use this tool, UNLESS the user explicitly provides a library ID in the format '/org/project' or '/org/project/version' in their query.\n\nDo not call this tool more than 3 times per question.\n\nIf you know the programming language the user is working in, pass it as `language` so the results match their setup."
        }
      ],
      "risks": [
        {
          "id": "context7.query-docs",
          "label": "Ask Context7 about library documentation",
          "reason": "The question the agent writes, which may carry code or internal context, is sent to Context7, a third party that stores queries; the documentation that comes back is third-party text. Each call also spends the key's monthly Context7 quota.",
          "severity": "medium",
          "kind": "read",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "context7.uncategorized-write",
          "label": "Uncategorized write",
          "reason": "Catch-all for writes without a dedicated entry. No Context7 tool of this plugin writes.",
          "severity": "high",
          "kind": "write",
          "acceptable_in_envs": null,
          "fallback": true
        }
      ],
      "presets": [
        {
          "id": "read-only",
          "label": "Read-only",
          "description": "Ask Context7 about library documentation. The questions the agent writes are sent to Context7.",
          "entries": [
            {
              "risk_id": "context7.query-docs",
              "gate": "auto",
              "quota": 300,
              "rate_limit": {
                "max": 20,
                "window_seconds": 60
              }
            }
          ]
        }
      ]
    },
    {
      "id": "fly",
      "name": "Fly.io",
      "version": "0.1.0",
      "status": "stable",
      "description": "Apps on Fly.io: machines, images, volumes and snapshots, secrets, IP addresses and certificates — one resource per app.",
      "page": "https://vaubaan.com/plugins/fly",
      "source": "https://github.com/vaubaan/gateway/tree/main/plugins/fly",
      "health": {
        "state": "not-enough-reports"
      },
      "tools": [
        {
          "name": "fly__list_regions",
          "title": "List regions",
          "description": "List the Fly regions a machine or volume can be placed in."
        },
        {
          "name": "fly__list_apps",
          "title": "List apps",
          "description": "List the organization's apps that this mission may touch (other apps are left out)."
        },
        {
          "name": "fly__get_app",
          "title": "Read an app",
          "description": "Read one app: id, status, network, organization."
        },
        {
          "name": "fly__create_app",
          "title": "Create an app",
          "description": "Create an app in the connection organization, on a private network of its own. The new app is not part of this mission until its owner labels it and adds it."
        },
        {
          "name": "fly__destroy_app",
          "title": "Destroy an app",
          "description": "Destroy an app with its machines, volumes and their snapshots. Cannot be undone. Running machines must be stopped first."
        },
        {
          "name": "fly__list_machines",
          "title": "List machines",
          "description": "List the machines of an app."
        },
        {
          "name": "fly__get_machine",
          "title": "Read a machine",
          "description": "Read one machine: state, config, image, events."
        },
        {
          "name": "fly__list_machine_events",
          "title": "List machine events",
          "description": "List the events of a machine."
        },
        {
          "name": "fly__list_machine_processes",
          "title": "List machine processes",
          "description": "List the processes running in a machine."
        },
        {
          "name": "fly__wait_machine",
          "title": "Wait for a machine state",
          "description": "Wait until a machine reaches a state (at most 60 seconds)."
        },
        {
          "name": "fly__create_machine",
          "title": "Create a machine",
          "description": "Create a machine in an app from a full config. Every image of the config must be pinned by digest under an allowed prefix."
        },
        {
          "name": "fly__update_machine",
          "title": "Replace a machine configuration",
          "description": "Replace the whole config of a machine (Fly takes no partial update); the machine restarts. Prefer deploy_image or scale_machine to change one thing. A volume it mounts needs a fresh snapshot when the image changes."
        },
        {
          "name": "fly__deploy_image",
          "title": "Change the image of a machine",
          "description": "Change only the image of a machine, under a lease; the machine restarts. Every volume it mounts needs a snapshot taken in the last hour (create_snapshot, then wait for it)."
        },
        {
          "name": "fly__scale_machine",
          "title": "Resize a machine",
          "description": "Change only the CPU and memory of a machine, under a lease; the machine restarts."
        },
        {
          "name": "fly__start_machine",
          "title": "Start a machine",
          "description": "Start a stopped or suspended machine."
        },
        {
          "name": "fly__stop_machine",
          "title": "Stop a machine",
          "description": "Stop a machine."
        },
        {
          "name": "fly__restart_machine",
          "title": "Restart a machine",
          "description": "Restart a machine."
        },
        {
          "name": "fly__suspend_machine",
          "title": "Suspend a machine",
          "description": "Suspend a machine (memory snapshot)."
        },
        {
          "name": "fly__cordon_machine",
          "title": "Route traffic away from a machine",
          "description": "Route traffic away from a machine."
        },
        {
          "name": "fly__uncordon_machine",
          "title": "Route traffic back to a machine",
          "description": "Route traffic back to a machine."
        },
        {
          "name": "fly__destroy_machine",
          "title": "Destroy a machine",
          "description": "Destroy a stopped machine. Its volumes stay."
        },
        {
          "name": "fly__exec_machine",
          "title": "Run a command inside a machine",
          "description": "Run one command inside a machine and return its output (at most 60 seconds, output cut at 64 KiB). The command runs with the app secrets in its environment."
        },
        {
          "name": "fly__list_volumes",
          "title": "List volumes",
          "description": "List the volumes of an app."
        },
        {
          "name": "fly__get_volume",
          "title": "Read a volume",
          "description": "Read one volume."
        },
        {
          "name": "fly__list_snapshots",
          "title": "List volume snapshots",
          "description": "List the snapshots of a volume."
        },
        {
          "name": "fly__create_volume",
          "title": "Create a volume",
          "description": "Create a volume, empty or from a snapshot or another volume of the same app."
        },
        {
          "name": "fly__extend_volume",
          "title": "Extend a volume",
          "description": "Grow a volume. It cannot shrink."
        },
        {
          "name": "fly__update_volume",
          "title": "Change automatic snapshots",
          "description": "Change the automatic daily snapshots of a volume and their retention."
        },
        {
          "name": "fly__create_snapshot",
          "title": "Take a volume snapshot",
          "description": "Take a snapshot of a volume now."
        },
        {
          "name": "fly__destroy_volume",
          "title": "Destroy a volume",
          "description": "Destroy a volume. Cannot be undone; only its snapshots remain, until they expire."
        },
        {
          "name": "fly__list_secrets",
          "title": "List secret names",
          "description": "List the names and digests of the app secrets (never their values)."
        },
        {
          "name": "fly__set_secrets",
          "title": "Set app secrets",
          "description": "Set app secrets. Machines pick them up when next updated (deploy_image with the same image)."
        },
        {
          "name": "fly__unset_secret",
          "title": "Remove an app secret",
          "description": "Remove one app secret."
        },
        {
          "name": "fly__list_ips",
          "title": "List IP addresses",
          "description": "List the IP addresses of an app."
        },
        {
          "name": "fly__allocate_ip",
          "title": "Allocate an IP address",
          "description": "Allocate an IP address to an app. A dedicated IPv4 is billed monthly."
        },
        {
          "name": "fly__release_ip",
          "title": "Release an IP address",
          "description": "Release an IP address of an app."
        },
        {
          "name": "fly__list_certificates",
          "title": "List certificates",
          "description": "List the TLS certificates of an app."
        },
        {
          "name": "fly__get_certificate",
          "title": "Read a certificate",
          "description": "Read one certificate and its DNS requirements."
        },
        {
          "name": "fly__request_certificate",
          "title": "Request a certificate",
          "description": "Ask Fly to issue a certificate (ACME) for a hostname pointing at the app."
        },
        {
          "name": "fly__check_certificate",
          "title": "Check a certificate",
          "description": "Check the DNS of a hostname and validate its certificate again."
        },
        {
          "name": "fly__remove_certificate",
          "title": "Remove a certificate",
          "description": "Remove the certificate of a hostname."
        },
        {
          "name": "fly__get_logs",
          "title": "Read app logs",
          "description": "Read recent logs of an app (100 lines per page)."
        }
      ],
      "risks": [
        {
          "id": "fly.read-org",
          "label": "List the apps and regions of the organization",
          "reason": "App names, ids and networks, and the platform regions. In an organization that hosts one app per customer, the list of apps is the list of customers; the answer is filtered to the apps of the mission.",
          "severity": "low",
          "kind": "read",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "fly.read-runtime",
          "label": "Read machines, volumes, snapshots, IP addresses and certificates",
          "reason": "Machine configurations (image, region, size, services, plain environment variables), events, processes, volumes and their snapshots, the app's addresses and certificates. People sometimes put secrets in plain environment variables.",
          "severity": "low",
          "kind": "read",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "fly.read-secret-names",
          "label": "List the names of the app secrets",
          "reason": "Names, digests and dates of the secrets set on an app, never their values: any value in Fly's answer is replaced before the agent sees it.",
          "severity": "low",
          "kind": "read",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "fly.read-logs",
          "label": "Read the app logs",
          "reason": "What the app printed recently. Logs can carry customer data, and secrets when an app prints them.",
          "severity": "low",
          "kind": "read",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "fly.app-create",
          "label": "Create an app in the organization",
          "reason": "A new app on a private network of its own. Costs nothing until it runs machines. It is not part of the mission until its owner labels it and adds it.",
          "severity": "medium",
          "kind": "write",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "fly.app-destroy",
          "label": "Destroy an app",
          "reason": "Removes the app with its machines, addresses, certificates, volumes and their snapshots. For a customer instance, its data is gone for good.",
          "severity": "critical",
          "kind": "destructive",
          "acceptable_in_envs": [
            "staging",
            "prod"
          ],
          "fallback": false
        },
        {
          "id": "fly.machine-create",
          "label": "Create a machine",
          "reason": "A new VM in the app, billed while it runs. A creation sets a whole configuration and an image: a mission needs those two permissions too.",
          "severity": "low",
          "kind": "write",
          "acceptable_in_envs": [
            "staging",
            "prod"
          ],
          "fallback": false
        },
        {
          "id": "fly.machine-config-write",
          "label": "Set a machine configuration",
          "reason": "Environment variables, start command, files written at boot, mounted volumes, autostop and the services the machine exposes. A configuration can start any command with the app's secrets in its environment.",
          "severity": "high",
          "kind": "write",
          "acceptable_in_envs": [
            "staging",
            "prod"
          ],
          "fallback": false
        },
        {
          "id": "fly.deploy",
          "label": "Change the image a machine runs",
          "reason": "The machine restarts on the new image, which runs with the app's secrets and volume. Only images pinned by digest under the connection's allowed prefixes are accepted, and a mounted volume needs a fresh snapshot first.",
          "severity": "medium",
          "kind": "write",
          "acceptable_in_envs": [
            "staging",
            "prod"
          ],
          "fallback": false
        },
        {
          "id": "fly.scale",
          "label": "Resize a machine",
          "reason": "CPU and memory of a machine, billed while it runs. The machine restarts.",
          "severity": "low",
          "kind": "write",
          "acceptable_in_envs": [
            "staging",
            "prod"
          ],
          "fallback": false
        },
        {
          "id": "fly.machine-power",
          "label": "Start, stop, restart, suspend or cordon a machine",
          "reason": "A stopped or cordoned machine serves no traffic until it starts again or a request wakes it. Changes no data.",
          "severity": "low",
          "kind": "write",
          "acceptable_in_envs": [
            "staging",
            "prod"
          ],
          "fallback": false
        },
        {
          "id": "fly.machine-destroy",
          "label": "Destroy a machine",
          "reason": "The machine and its root filesystem are gone; a volume it mounted stays. A stopped machine only: a running one must be stopped first.",
          "severity": "medium",
          "kind": "write",
          "acceptable_in_envs": [
            "staging",
            "prod"
          ],
          "fallback": false
        },
        {
          "id": "fly.machine-exec",
          "label": "Run a command inside a machine",
          "reason": "Any command, with the app's secrets in its environment and its volume mounted; the output comes back to the agent. On a Vaubaan instance this reaches the master key and every account it seals.",
          "severity": "high",
          "kind": "destructive",
          "acceptable_in_envs": [
            "staging",
            "prod"
          ],
          "fallback": false
        },
        {
          "id": "fly.volume-create",
          "label": "Create an empty volume",
          "reason": "Persistent storage billed by size, in one region. Holds no data yet.",
          "severity": "low",
          "kind": "write",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "fly.volume-restore",
          "label": "Create a volume from a snapshot or another volume",
          "reason": "Copies data into a new volume. A machine that mounts it serves that data, which may be older than the live one. The source must belong to the same app.",
          "severity": "medium",
          "kind": "write",
          "acceptable_in_envs": [
            "staging",
            "prod"
          ],
          "fallback": false
        },
        {
          "id": "fly.volume-extend",
          "label": "Extend a volume",
          "reason": "A volume grows and never shrinks: the extra size is billed for as long as it exists.",
          "severity": "medium",
          "kind": "destructive",
          "acceptable_in_envs": [
            "staging",
            "prod"
          ],
          "fallback": false
        },
        {
          "id": "fly.volume-backup-settings",
          "label": "Change automatic snapshots of a volume",
          "reason": "Turns daily snapshots on or off and sets how many days they are kept. Off, or a shorter retention, leaves less to restore from.",
          "severity": "medium",
          "kind": "write",
          "acceptable_in_envs": [
            "staging",
            "prod"
          ],
          "fallback": false
        },
        {
          "id": "fly.volume-snapshot",
          "label": "Take a volume snapshot",
          "reason": "A point-in-time copy, kept for the volume retention period. Changes nothing that runs.",
          "severity": "low",
          "kind": "write",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "fly.volume-destroy",
          "label": "Destroy a volume",
          "reason": "Cannot be undone. The data survives only in snapshots still within their retention period.",
          "severity": "high",
          "kind": "destructive",
          "acceptable_in_envs": [
            "staging",
            "prod"
          ],
          "fallback": false
        },
        {
          "id": "fly.secret-write",
          "label": "Set or remove app secrets",
          "reason": "Machines pick up new values when they are next updated. A wrong or missing value, such as a master key or a sign-in setting, can lock everyone out of an instance or make its sealed data unreadable.",
          "severity": "medium",
          "kind": "write",
          "acceptable_in_envs": [
            "staging",
            "prod"
          ],
          "fallback": false
        },
        {
          "id": "fly.ip-write",
          "label": "Allocate an IP address",
          "reason": "A public address makes the app reachable from the internet; a dedicated IPv4 address is billed monthly.",
          "severity": "medium",
          "kind": "write",
          "acceptable_in_envs": [
            "staging",
            "prod"
          ],
          "fallback": false
        },
        {
          "id": "fly.ip-release",
          "label": "Release an IP address",
          "reason": "The app stops answering on that address and DNS pointing at it breaks. A released dedicated address is not given back.",
          "severity": "medium",
          "kind": "destructive",
          "acceptable_in_envs": [
            "staging",
            "prod"
          ],
          "fallback": false
        },
        {
          "id": "fly.certificate-write",
          "label": "Request or re-check a certificate",
          "reason": "Asks Fly to issue a certificate for a hostname, or to validate it again. Issuance succeeds only when the hostname's DNS points at the app.",
          "severity": "medium",
          "kind": "write",
          "acceptable_in_envs": [
            "staging",
            "prod"
          ],
          "fallback": false
        },
        {
          "id": "fly.certificate-remove",
          "label": "Remove a certificate",
          "reason": "HTTPS on that hostname fails until a certificate is issued again.",
          "severity": "medium",
          "kind": "write",
          "acceptable_in_envs": [
            "staging",
            "prod"
          ],
          "fallback": false
        },
        {
          "id": "fly.uncategorized-write",
          "label": "Uncategorized write",
          "reason": "Catch-all for a write no other entry covers. Seeing it in the activity means the catalog misses an entry.",
          "severity": "medium",
          "kind": "write",
          "acceptable_in_envs": null,
          "fallback": true
        }
      ],
      "presets": [
        {
          "id": "read-only",
          "label": "Read-only",
          "description": "Look at apps, machines, volumes, secret names and logs. Changes nothing.",
          "entries": [
            {
              "risk_id": "fly.read-org",
              "gate": "auto",
              "quota": 2000,
              "rate_limit": {
                "max": 60,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "fly.read-runtime",
              "gate": "auto",
              "quota": 5000,
              "rate_limit": {
                "max": 120,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "fly.read-secret-names",
              "gate": "auto",
              "quota": 500,
              "rate_limit": {
                "max": 30,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "fly.read-logs",
              "gate": "auto",
              "quota": 1000,
              "rate_limit": {
                "max": 30,
                "window_seconds": 60
              }
            }
          ]
        },
        {
          "id": "operator",
          "label": "Operator",
          "description": "Day-to-day care of existing instances: staging runs freely; production configuration, upgrades, resizes, secrets, commands inside a machine and anything destructive ask first.",
          "entries": [
            {
              "risk_id": "fly.read-org",
              "gate": "auto",
              "quota": 2000,
              "rate_limit": {
                "max": 60,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "fly.read-runtime",
              "gate": "auto",
              "quota": 5000,
              "rate_limit": {
                "max": 120,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "fly.read-secret-names",
              "gate": "auto",
              "quota": 500,
              "rate_limit": {
                "max": 30,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "fly.read-logs",
              "gate": "auto",
              "quota": 1000,
              "rate_limit": {
                "max": 30,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "fly[staging].machine-config-write",
              "gate": "auto",
              "quota": 50,
              "rate_limit": {
                "max": 10,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "fly[prod].machine-config-write",
              "gate": "approval",
              "quota": 10,
              "rate_limit": {
                "max": 2,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "fly[staging].deploy",
              "gate": "auto",
              "quota": 100,
              "rate_limit": {
                "max": 10,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "fly[prod].deploy",
              "gate": "approval",
              "quota": 20,
              "rate_limit": {
                "max": 5,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "fly[staging].scale",
              "gate": "auto",
              "quota": 20,
              "rate_limit": {
                "max": 5,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "fly[prod].scale",
              "gate": "approval",
              "quota": 10,
              "rate_limit": {
                "max": 2,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "fly[staging].machine-power",
              "gate": "auto",
              "quota": 200,
              "rate_limit": {
                "max": 20,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "fly[prod].machine-power",
              "gate": "auto",
              "quota": 200,
              "rate_limit": {
                "max": 20,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "fly[staging].machine-destroy",
              "gate": "auto",
              "quota": 20,
              "rate_limit": {
                "max": 5,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "fly[prod].machine-destroy",
              "gate": "approval",
              "quota": 5,
              "rate_limit": {
                "max": 1,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "fly[staging].machine-exec",
              "gate": "approval",
              "quota": 10,
              "rate_limit": {
                "max": 2,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "fly[prod].machine-exec",
              "gate": "approval",
              "quota": 10,
              "rate_limit": {
                "max": 2,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "fly.volume-create",
              "gate": "auto",
              "quota": 10,
              "rate_limit": {
                "max": 2,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "fly[staging].volume-restore",
              "gate": "approval",
              "quota": 5,
              "rate_limit": {
                "max": 1,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "fly[prod].volume-restore",
              "gate": "approval",
              "quota": 5,
              "rate_limit": {
                "max": 1,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "fly[staging].volume-extend",
              "gate": "auto",
              "quota": 10,
              "rate_limit": {
                "max": 2,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "fly[prod].volume-extend",
              "gate": "approval",
              "quota": 5,
              "rate_limit": {
                "max": 1,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "fly[staging].volume-backup-settings",
              "gate": "approval",
              "quota": 10,
              "rate_limit": {
                "max": 2,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "fly[prod].volume-backup-settings",
              "gate": "approval",
              "quota": 10,
              "rate_limit": {
                "max": 2,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "fly.volume-snapshot",
              "gate": "auto",
              "quota": 100,
              "rate_limit": {
                "max": 10,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "fly[staging].volume-destroy",
              "gate": "approval",
              "quota": 5,
              "rate_limit": {
                "max": 1,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "fly[staging].secret-write",
              "gate": "approval",
              "quota": 20,
              "rate_limit": {
                "max": 5,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "fly[prod].secret-write",
              "gate": "approval",
              "quota": 20,
              "rate_limit": {
                "max": 5,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "fly[staging].ip-write",
              "gate": "approval",
              "quota": 5,
              "rate_limit": {
                "max": 1,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "fly[prod].ip-write",
              "gate": "approval",
              "quota": 5,
              "rate_limit": {
                "max": 1,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "fly[staging].ip-release",
              "gate": "approval",
              "quota": 5,
              "rate_limit": {
                "max": 1,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "fly[prod].ip-release",
              "gate": "approval",
              "quota": 5,
              "rate_limit": {
                "max": 1,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "fly[staging].certificate-write",
              "gate": "auto",
              "quota": 20,
              "rate_limit": {
                "max": 5,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "fly[prod].certificate-write",
              "gate": "auto",
              "quota": 20,
              "rate_limit": {
                "max": 5,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "fly[staging].certificate-remove",
              "gate": "approval",
              "quota": 5,
              "rate_limit": {
                "max": 1,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "fly[prod].certificate-remove",
              "gate": "approval",
              "quota": 5,
              "rate_limit": {
                "max": 1,
                "window_seconds": 60
              }
            }
          ]
        },
        {
          "id": "fleet-upgrade",
          "label": "Fleet upgrade",
          "description": "Roll a new image across many instances: snapshot each volume, then change the image and restart, without asking each time. Only pinned images under the allowed prefixes, after a fresh snapshot. Nothing else.",
          "entries": [
            {
              "risk_id": "fly.read-org",
              "gate": "auto",
              "quota": 2000,
              "rate_limit": {
                "max": 60,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "fly.read-runtime",
              "gate": "auto",
              "quota": 5000,
              "rate_limit": {
                "max": 120,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "fly.read-logs",
              "gate": "auto",
              "quota": 1000,
              "rate_limit": {
                "max": 30,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "fly.volume-snapshot",
              "gate": "auto",
              "quota": 500,
              "rate_limit": {
                "max": 10,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "fly[staging].deploy",
              "gate": "auto",
              "quota": 200,
              "rate_limit": {
                "max": 10,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "fly[prod].deploy",
              "gate": "auto",
              "quota": 500,
              "rate_limit": {
                "max": 10,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "fly[staging].machine-power",
              "gate": "auto",
              "quota": 500,
              "rate_limit": {
                "max": 20,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "fly[prod].machine-power",
              "gate": "auto",
              "quota": 500,
              "rate_limit": {
                "max": 20,
                "window_seconds": 60
              }
            }
          ]
        },
        {
          "id": "provisioner",
          "label": "Provisioner",
          "description": "Create and configure customer instances: apps, machines, volumes, addresses, certificates. Secrets, restores and destruction ask first; commands inside a machine are not included.",
          "entries": [
            {
              "risk_id": "fly.read-org",
              "gate": "auto",
              "quota": 2000,
              "rate_limit": {
                "max": 60,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "fly.read-runtime",
              "gate": "auto",
              "quota": 5000,
              "rate_limit": {
                "max": 120,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "fly.read-secret-names",
              "gate": "auto",
              "quota": 500,
              "rate_limit": {
                "max": 30,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "fly.read-logs",
              "gate": "auto",
              "quota": 1000,
              "rate_limit": {
                "max": 30,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "fly.app-create",
              "gate": "auto",
              "quota": 10,
              "rate_limit": {
                "max": 2,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "fly[staging].app-destroy",
              "gate": "approval",
              "quota": 5,
              "rate_limit": {
                "max": 1,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "fly[prod].app-destroy",
              "gate": "approval",
              "quota": 2,
              "rate_limit": {
                "max": 1,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "fly[staging].machine-create",
              "gate": "auto",
              "quota": 20,
              "rate_limit": {
                "max": 5,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "fly[prod].machine-create",
              "gate": "auto",
              "quota": 20,
              "rate_limit": {
                "max": 5,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "fly[staging].machine-config-write",
              "gate": "auto",
              "quota": 50,
              "rate_limit": {
                "max": 10,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "fly[prod].machine-config-write",
              "gate": "auto",
              "quota": 20,
              "rate_limit": {
                "max": 5,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "fly[staging].deploy",
              "gate": "auto",
              "quota": 50,
              "rate_limit": {
                "max": 10,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "fly[prod].deploy",
              "gate": "auto",
              "quota": 20,
              "rate_limit": {
                "max": 5,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "fly[staging].scale",
              "gate": "auto",
              "quota": 20,
              "rate_limit": {
                "max": 5,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "fly[prod].scale",
              "gate": "auto",
              "quota": 20,
              "rate_limit": {
                "max": 5,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "fly[staging].machine-power",
              "gate": "auto",
              "quota": 100,
              "rate_limit": {
                "max": 20,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "fly[prod].machine-power",
              "gate": "auto",
              "quota": 100,
              "rate_limit": {
                "max": 20,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "fly[staging].machine-destroy",
              "gate": "auto",
              "quota": 20,
              "rate_limit": {
                "max": 5,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "fly[prod].machine-destroy",
              "gate": "approval",
              "quota": 10,
              "rate_limit": {
                "max": 2,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "fly.volume-create",
              "gate": "auto",
              "quota": 20,
              "rate_limit": {
                "max": 5,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "fly[staging].volume-restore",
              "gate": "approval",
              "quota": 5,
              "rate_limit": {
                "max": 1,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "fly[prod].volume-restore",
              "gate": "approval",
              "quota": 5,
              "rate_limit": {
                "max": 1,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "fly[staging].volume-extend",
              "gate": "auto",
              "quota": 10,
              "rate_limit": {
                "max": 2,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "fly[prod].volume-extend",
              "gate": "auto",
              "quota": 10,
              "rate_limit": {
                "max": 2,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "fly[staging].volume-backup-settings",
              "gate": "auto",
              "quota": 20,
              "rate_limit": {
                "max": 5,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "fly[prod].volume-backup-settings",
              "gate": "auto",
              "quota": 20,
              "rate_limit": {
                "max": 5,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "fly.volume-snapshot",
              "gate": "auto",
              "quota": 50,
              "rate_limit": {
                "max": 10,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "fly[staging].volume-destroy",
              "gate": "approval",
              "quota": 5,
              "rate_limit": {
                "max": 1,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "fly[prod].volume-destroy",
              "gate": "approval",
              "quota": 2,
              "rate_limit": {
                "max": 1,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "fly[staging].secret-write",
              "gate": "approval",
              "quota": 50,
              "rate_limit": {
                "max": 10,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "fly[prod].secret-write",
              "gate": "approval",
              "quota": 50,
              "rate_limit": {
                "max": 10,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "fly[staging].ip-write",
              "gate": "auto",
              "quota": 20,
              "rate_limit": {
                "max": 5,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "fly[prod].ip-write",
              "gate": "auto",
              "quota": 20,
              "rate_limit": {
                "max": 5,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "fly[staging].ip-release",
              "gate": "approval",
              "quota": 5,
              "rate_limit": {
                "max": 1,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "fly[prod].ip-release",
              "gate": "approval",
              "quota": 5,
              "rate_limit": {
                "max": 1,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "fly[staging].certificate-write",
              "gate": "auto",
              "quota": 20,
              "rate_limit": {
                "max": 5,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "fly[prod].certificate-write",
              "gate": "auto",
              "quota": 20,
              "rate_limit": {
                "max": 5,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "fly[staging].certificate-remove",
              "gate": "approval",
              "quota": 5,
              "rate_limit": {
                "max": 1,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "fly[prod].certificate-remove",
              "gate": "approval",
              "quota": 5,
              "rate_limit": {
                "max": 1,
                "window_seconds": 60
              }
            }
          ]
        }
      ]
    },
    {
      "id": "github",
      "name": "GitHub",
      "version": "3.2.0",
      "status": "stable",
      "description": "Repositories, branches, files, pull requests, issues, releases and workflows of the GitHub App installation.",
      "page": "https://vaubaan.com/plugins/github",
      "source": "https://github.com/vaubaan/gateway/tree/main/plugins/github",
      "health": {
        "state": "not-enough-reports"
      },
      "tools": [
        {
          "name": "github__list_branches",
          "title": "List branches",
          "description": "List branches of a repository."
        },
        {
          "name": "github__list_commits",
          "title": "List commits",
          "description": "List commits on a branch / ref."
        },
        {
          "name": "github__list_pull_requests",
          "title": "List pull requests",
          "description": "List pull requests on a repository."
        },
        {
          "name": "github__list_issues",
          "title": "List issues",
          "description": "List issues on a repository."
        },
        {
          "name": "github__get_file_contents",
          "title": "Read a file",
          "description": "Get the contents of a file at a ref."
        },
        {
          "name": "github__get_pull_request",
          "title": "Read a pull request",
          "description": "Get a single pull request."
        },
        {
          "name": "github__get_issue",
          "title": "Read an issue",
          "description": "Get a single issue."
        },
        {
          "name": "github__get_commit",
          "title": "Read a commit",
          "description": "Get a single commit by SHA."
        },
        {
          "name": "github__search_code",
          "title": "Search code",
          "description": "Search code with a GitHub search query."
        },
        {
          "name": "github__search_issues",
          "title": "Search issues and pull requests",
          "description": "Search issues and pull requests."
        },
        {
          "name": "github__create_branch",
          "title": "Create a branch",
          "description": "Create a branch from a base ref."
        },
        {
          "name": "github__create_or_update_file",
          "title": "Write a file on a branch",
          "description": "Create or update a single file in a branch."
        },
        {
          "name": "github__push_files",
          "title": "Push files to a branch",
          "description": "Push multiple files in one commit to a branch."
        },
        {
          "name": "github__create_pull_request",
          "title": "Open a pull request",
          "description": "Open a pull request from head to base."
        },
        {
          "name": "github__add_issue_comment",
          "title": "Comment on an issue or pull request",
          "description": "Add a comment on an issue or pull request."
        },
        {
          "name": "github__mint_install_token",
          "title": "Get a short-lived token for git",
          "description": "Mint a short-lived (1h) GitHub App installation token scoped to repositories of the mission. Used for clone or push flows outside the gateway ; `purpose` picks the permission set."
        },
        {
          "name": "github__list_workflows",
          "title": "List workflows",
          "description": "List the workflows defined under .github/workflows on the repository."
        },
        {
          "name": "github__list_workflow_runs",
          "title": "List workflow runs",
          "description": "List recent workflow runs on the repository (optionally filtered by branch or workflow id)."
        },
        {
          "name": "github__get_branch_protection",
          "title": "Read the protection of a branch",
          "description": "Get the protection rules for a branch (required reviewers, status checks, restrictions)."
        },
        {
          "name": "github__merge_pull_request",
          "title": "Merge a pull request",
          "description": "Merge a pull request. Pass `base`, the base branch of the pull request : the call is classified on it and refused when the pull request targets another branch ; defaults to `main`."
        },
        {
          "name": "github__delete_file",
          "title": "Delete a file on a branch",
          "description": "Delete a file in a branch."
        },
        {
          "name": "github__update_branch_protection",
          "title": "Change the protection of a branch",
          "description": "Set/replace branch protection rules. Backed by PUT /repos/{owner}/{repo}/branches/{branch}/protection. `required_status_checks`, `required_pull_request_reviews`, `enforce_admins`, `restrictions` follow the GitHub REST shape. Pass `null` for any top-level field to disable it."
        },
        {
          "name": "github__list_releases",
          "title": "List releases",
          "description": "List releases on a repository. Backed by GET /repos/{owner}/{repo}/releases."
        },
        {
          "name": "github__list_tags",
          "title": "List tags",
          "description": "List tags on a repository. Backed by GET /repos/{owner}/{repo}/tags."
        },
        {
          "name": "github__create_release",
          "title": "Create a release",
          "description": "Create a new release on a tag (creates the tag if needed). Backed by POST /repos/{owner}/{repo}/releases."
        },
        {
          "name": "github__delete_release",
          "title": "Delete a release",
          "description": "Delete a release (does not delete the underlying tag). Backed by DELETE /repos/{owner}/{repo}/releases/{release_id}."
        }
      ],
      "risks": [
        {
          "id": "github.read-code",
          "label": "Read code, branches, commits, files",
          "reason": "Reads may leak secrets through `.env`-style commits (rare but non-zero). The contents of fetched files are attacker-injectable into the agent's context (OWASP LLM01) — a malicious README can steer a downstream prompt.",
          "severity": "low",
          "kind": "read",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "github.read-meta",
          "label": "Read PRs, issues, releases, workflows, branch-protection",
          "reason": "Repository metadata. Issue/PR bodies are attacker-controlled (OWASP LLM01) — handle with care on the agent side. Reading branch protection rules is diagnostic only, never dangerous in itself.",
          "severity": "low",
          "kind": "read",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "github.search-global",
          "label": "Global GitHub search (code / issues, beyond the mission resources)",
          "reason": "Search traverses every public GitHub repo plus every repo the App installation has access to. Cross-org leakage is possible if the App is installed widely. Sensitivity is low by default but the surface is account-shaped, not resource-shaped.",
          "severity": "low",
          "kind": "read",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "github.feature-branch-write",
          "label": "Create/modify branches under feature/*, fix/*, chore/*, …",
          "reason": "Reversible (`git revert` or branch delete). No production side effect until merged. This is the heart of the coding-agent flow.",
          "severity": "low",
          "kind": "write",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "github.merge-protected",
          "label": "Write on main / master / release-* (direct commit OR PR merge)",
          "reason": "The canonical branch — triggers downstream CI/CD on prod. Costly to revert (release notes, communications, downstream dependencies). Covers direct commits (create_or_update_file without a PR) AND merges of PRs whose base is one of these branches.",
          "severity": "medium",
          "kind": "write",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "github.workflow-write-via-file",
          "label": "Modify a GitHub Actions workflow (.github/workflows/*.yml)",
          "reason": "The CI YAML defines what runs with the repo's secrets. Modifying a workflow is a potential privilege-escalation vector (steal secrets via curl to attacker-controlled domain, `pull_request_target`). `dispatch_workflow` was retired for this reason — but the attack persists through file writes on the YAML itself.",
          "severity": "high",
          "kind": "write",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "github.bulk-file-write",
          "label": "Bulk multi-file push (push_files)",
          "reason": "A single commit touching many files. Harder to review manually than a single-file write. Kept distinct from file-by-file so the creator can opt-in/out specifically — useful signal for spotting malicious or buggy agents. Emitted as a `files-bulk` virtual leaf alongside the per-file `file[<path>].W` slices.",
          "severity": "low",
          "kind": "write",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "github.create-pr",
          "label": "Open a pull request",
          "reason": "No prod mutation. Notifies reviewers (visibility=team). PR title and body are reviewer-injectable.",
          "severity": "low",
          "kind": "write",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "github.comment-write",
          "label": "Comment on an issue or PR",
          "reason": "A comment on a PR notifies reviewers ; on an issue, subscribers. Low risk but pollution / spam potential if abused.",
          "severity": "low",
          "kind": "write",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "github.create-release",
          "label": "Create a release (potentially public publication)",
          "reason": "With `draft=false && prerelease=false`, the release is publicly visible, notifies watchers, and triggers `on: release` workflows. Hard to \"retract\" — downstream consumers may already have pulled tarballs / asset URLs by the time you delete.",
          "severity": "medium",
          "kind": "write",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "github.delete-release",
          "label": "Delete a release",
          "reason": "Irreversible w.r.t. audit / external notifications. The tag itself persists, but the release body and assets vanish — downstream consumers may see 404s.",
          "severity": "medium",
          "kind": "destructive",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "github.branch-protection-write",
          "label": "Modify branch protection",
          "reason": "Posture-shift : opens the branch to out-of-process writes (e.g. disabling required reviews). Persists after the token's lifetime. Even when *tightening* (adding a required check), the change outlives the session.",
          "severity": "medium",
          "kind": "write",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "github.git-push",
          "label": "Push with git through the gateway",
          "reason": "The git remote of the gateway relays the pack and moves the ref only after the decision. Touched by every pushed ref, next to the risk of the branch or tag it lands on ; it is what a creator limits to bound how often an agent pushes. The pack itself is not read : the decision is on refs and changed paths, never on content.",
          "severity": "low",
          "kind": "write",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "github.force-push",
          "label": "Force-push (rewrite the history of a branch or move a tag)",
          "reason": "A push that is not a fast-forward replaces commits others may have built on ; once replaced, they may survive only in someone's clone. Agents rebase often, which is precisely where a mistake destroys work.",
          "severity": "low",
          "kind": "write",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "github.branch-delete",
          "label": "Delete a branch with git",
          "reason": "The branch disappears for everyone ; its commits remain reachable only from other refs and clones. A deleted protected branch also touches `merge-protected`.",
          "severity": "low",
          "kind": "write",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "github.tag-write",
          "label": "Push or delete a tag with git",
          "reason": "A tag names a release point : pushing one can trigger `on: push: tags` workflows (publication, deployment) and downstream consumers pin tags. Overlaps `create-release` on purpose : a tag pushed by git also writes `tag[<name>]`, which `create-release` covers, so a mission that may push tags says so twice. Matched on the git leaves only : `create_release` keeps touching `create-release` alone.",
          "severity": "medium",
          "kind": "write",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "github.mint-install-token-clone",
          "label": "Mint an install token (purpose=clone, read-only)",
          "reason": "Credential creation, but scoped to `contents:read` only. The token has its own TTL (1h) and its own scope. Moderate risk : code extraction is possible, no mutation.",
          "severity": "medium",
          "kind": "write",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "github.mint-install-token-push",
          "label": "Mint an install token (purpose=push, write)",
          "reason": "Credential creation with `contents:write + pull_requests:write + workflows:write`. The token leaves the gateway's gating perimeter ; no Vaubaan policy applies to usages of this credential. De-facto posture-shift.",
          "severity": "medium",
          "kind": "write",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "github.uncategorized-write",
          "label": "Uncategorized GitHub write (fallback)",
          "reason": "Catch-all for writes no fine-grained entry categorises — a branch outside the feature/* and protected conventions, or a future tool added without a catalog entry. Never offered by a preset ; the creator opts in explicitly (no implicit accept).",
          "severity": "medium",
          "kind": "write",
          "acceptable_in_envs": null,
          "fallback": true
        }
      ],
      "presets": [
        {
          "id": "read-only",
          "label": "Read-only",
          "description": "Read code and metadata of the selected repositories; no write, no global search.",
          "entries": [
            {
              "risk_id": "github.read-code",
              "gate": "auto",
              "quota": 5000,
              "rate_limit": {
                "max": 120,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "github.read-meta",
              "gate": "auto",
              "quota": 5000,
              "rate_limit": {
                "max": 120,
                "window_seconds": 60
              }
            }
          ]
        },
        {
          "id": "developer",
          "label": "Developer",
          "description": "One mission per code repository — clone and push with git on feature branches (a force-push asks first), read everything, open PRs and comment; no merge on protected branches, no branch-protection changes.",
          "entries": [
            {
              "risk_id": "github.read-code",
              "gate": "auto",
              "quota": 10000,
              "rate_limit": {
                "max": 240,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "github.read-meta",
              "gate": "auto",
              "quota": 10000,
              "rate_limit": {
                "max": 240,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "github.search-global",
              "gate": "auto",
              "quota": 500,
              "rate_limit": {
                "max": 30,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "github.feature-branch-write",
              "gate": "auto",
              "quota": 3000,
              "rate_limit": {
                "max": 60,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "github.bulk-file-write",
              "gate": "auto",
              "quota": 1000,
              "rate_limit": {
                "max": 30,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "github.create-pr",
              "gate": "auto",
              "quota": 200,
              "rate_limit": {
                "max": 10,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "github.comment-write",
              "gate": "auto",
              "quota": 500,
              "rate_limit": {
                "max": 20,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "github.workflow-write-via-file",
              "gate": "approval",
              "quota": 50,
              "rate_limit": {
                "max": 5,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "github.git-push",
              "gate": "auto",
              "quota": 200,
              "rate_limit": {
                "max": 10,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "github.force-push",
              "gate": "approval",
              "quota": 20,
              "rate_limit": {
                "max": 2,
                "window_seconds": 60
              }
            }
          ]
        },
        {
          "id": "release-manager",
          "label": "Release manager",
          "description": "Ship a release — merge into protected branches, create and delete releases, push tags with git, each asking the creator first; read everything.",
          "entries": [
            {
              "risk_id": "github.read-code",
              "gate": "auto",
              "quota": 5000,
              "rate_limit": {
                "max": 120,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "github.read-meta",
              "gate": "auto",
              "quota": 5000,
              "rate_limit": {
                "max": 120,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "github.feature-branch-write",
              "gate": "auto",
              "quota": 500,
              "rate_limit": {
                "max": 30,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "github.create-pr",
              "gate": "auto",
              "quota": 100,
              "rate_limit": {
                "max": 10,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "github.comment-write",
              "gate": "auto",
              "quota": 200,
              "rate_limit": {
                "max": 10,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "github.merge-protected",
              "gate": "approval",
              "quota": 50,
              "rate_limit": {
                "max": 5,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "github.create-release",
              "gate": "approval",
              "quota": 20,
              "rate_limit": {
                "max": 2,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "github.delete-release",
              "gate": "approval",
              "quota": 5,
              "rate_limit": {
                "max": 1,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "github.git-push",
              "gate": "auto",
              "quota": 100,
              "rate_limit": {
                "max": 10,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "github.tag-write",
              "gate": "approval",
              "quota": 20,
              "rate_limit": {
                "max": 2,
                "window_seconds": 60
              }
            }
          ]
        }
      ]
    },
    {
      "id": "gmail",
      "name": "Gmail",
      "version": "0.3.0",
      "status": "experimental",
      "description": "Experimental, classifier-only : identity-zoned risks for reading, drafting and labelling in a Gmail mailbox. No tool is executable yet.",
      "page": "https://vaubaan.com/plugins/gmail",
      "source": "https://github.com/vaubaan/gateway/tree/main/plugins/gmail",
      "health": {
        "state": "not-enough-reports"
      },
      "tools": [],
      "risks": [
        {
          "id": "gmail.read-mailbox",
          "label": "Read mailbox structure (labels, threads, message headers)",
          "reason": "Inventory of labels, thread ids, subjects and sender names, and the containment slice of a thread fetch (whose content risk is carried by the from-zone marker emitted alongside). Limited attack surface, but the baseline every read needs. Also covers the synthetic `gmail__describe_zones` tool, which returns the zoning configuration.",
          "severity": "low",
          "kind": "read",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "gmail.read-from-zone-automated",
          "label": "Read messages from automated / notification senders",
          "reason": "Notifications (GitHub, Stripe, Vercel…). Little direct PII, but the bodies often carry injectable URLs and markdown (OWASP LLM01). Kept distinct so an \"ops\" agent can read alerts without unlocking personal correspondence.",
          "severity": "low",
          "kind": "read",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "gmail.read-from-zone-internal",
          "label": "Read messages from internal-domain senders",
          "reason": "Correspondence between colleagues : may contain secrets, strategic decisions and privileged context. Distinct from the automated zone by least privilege.",
          "severity": "low",
          "kind": "read",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "gmail.read-from-zone-known-external",
          "label": "Read messages from known external contacts (allowlist)",
          "reason": "Messages from pre-approved partners and customers (allowlist). Routine business traffic, comparatively low risk.",
          "severity": "low",
          "kind": "read",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "gmail.read-from-zone-unknown",
          "label": "Read messages from unknown senders",
          "reason": "Messages whose sender is neither internal nor allowlisted. The cardinal OWASP LLM01 vector — prompt injection through the message body. Strong posture recommended, tight quota and rate limit.",
          "severity": "low",
          "kind": "read",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "gmail.read-drafts",
          "label": "Read existing drafts",
          "reason": "Drafts reveal unsent intentions and earlier versions of external communications.",
          "severity": "low",
          "kind": "read",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "gmail.read-sensitive-label",
          "label": "Read messages tagged with a sensitive label (financial, legal, HR, …)",
          "reason": "Zone override : whoever the sender is, a thread carrying a label the owner declared sensitive is escalated. Guard rail for structurally private correspondence (payroll, legal, HR, invoices). The labels of a thread are unknown before the fetch, so on a mailbox that declares sensitive labels every thread fetch needs this permission, and so does every search that does not exclude each of them with `-label:<name>`.",
          "severity": "low",
          "kind": "read",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "gmail.bulk-read",
          "label": "Bulk read across the mailbox (non-discriminating query)",
          "reason": "`search_threads(q=\"is:unread\")`, or any query that is not a plain conjunction bounded by a `from:<address>` clause (a union, a grouping, a negated or missing sender), may return hundreds of threads at once and bypass per-message zoning. A distinct marker forces an explicit acceptance.",
          "severity": "medium",
          "kind": "read",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "gmail.draft-write",
          "label": "Create / modify a draft (any recipient)",
          "reason": "Baseline write of a draft, whatever its recipients ; the exfiltration axis is carried by the `draft-to-zone-*` permission needed alongside. Drafts are reversible (delete) and never sent by this plugin.",
          "severity": "low",
          "kind": "write",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "gmail.draft-to-zone-self",
          "label": "Create / modify a draft addressed to self only",
          "reason": "A memo to oneself. No external leak as long as the draft is not sent ; the official Google MCP server cannot send, so a draft is a terminal action here.",
          "severity": "low",
          "kind": "write",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "gmail.draft-to-zone-internal",
          "label": "Create / modify a draft addressed to internal recipients only",
          "reason": "A draft for colleagues. Stays internal while unsent ; no external exfiltration path.",
          "severity": "medium",
          "kind": "write",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "gmail.draft-to-zone-allowlist",
          "label": "Create / modify a draft to pre-approved external contacts (allowlist)",
          "reason": "A draft to pre-approved external contacts (CRM-like). Limited risk while unsent ; if a `send_draft` tool is ever wrapped, this permission covers it naturally.",
          "severity": "medium",
          "kind": "write",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "gmail.draft-to-zone-external-unbounded",
          "label": "Create / modify a draft to an external recipient not in the allowlist",
          "reason": "A draft to an arbitrary external address : the cardinal exfiltration vector once sent, and even unsent its existence can leak intentions. Never acceptable : the classifier refuses such a draft before any permission is evaluated, so holding this one grants nothing. The id is kept (ids are never deleted) and marked deprecated so no new mission is offered it.",
          "severity": "high",
          "kind": "write",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "gmail.label-write",
          "label": "Apply / remove labels on a thread or message (single)",
          "reason": "Mailbox organisation on one identified thread or message. UI-level and reversible (re-label). `TRASH` and `SPAM` are refused by the classifier (a deletion is not a label write) ; a label that is, or may be, one the owner declared sensitive also needs the fallback write permission.",
          "severity": "low",
          "kind": "write",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "gmail.bulk-label-write",
          "label": "Bulk apply / remove labels across many threads",
          "reason": "One call labelling many threads at once. Distinct from the single label write. No modelled tool can touch it today : `label_thread` takes exactly one thread id and a call without one is refused. Kept for a future batch tool ; in no preset until then.",
          "severity": "medium",
          "kind": "write",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "gmail.uncategorized-write",
          "label": "Uncategorized Gmail write",
          "reason": "Catch-all for writes no entry above covers. Today : applying or removing a label the owner declared sensitive, or a user label the classifier cannot tell apart from one (`sensitive-label[..].W`). If the plugin ever surfaces `send_message`, `delete_message` or `create_filter`, they land here until a dedicated permission is added — fail-loud by design. Never offered in a preset.",
          "severity": "medium",
          "kind": "write",
          "acceptable_in_envs": null,
          "fallback": true
        }
      ],
      "presets": [
        {
          "id": "read-only",
          "label": "Read-only triage",
          "description": "List labels and drafts, search and read threads from automated, internal, allowlisted and unknown senders; no write, no bulk read, no sensitive label.",
          "entries": [
            {
              "risk_id": "gmail.read-mailbox",
              "gate": "auto",
              "quota": 2000,
              "rate_limit": {
                "max": 120,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "gmail.read-from-zone-automated",
              "gate": "auto",
              "quota": 1000,
              "rate_limit": {
                "max": 60,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "gmail.read-from-zone-internal",
              "gate": "auto",
              "quota": 500,
              "rate_limit": {
                "max": 30,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "gmail.read-from-zone-known-external",
              "gate": "auto",
              "quota": 500,
              "rate_limit": {
                "max": 30,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "gmail.read-from-zone-unknown",
              "gate": "auto",
              "quota": 200,
              "rate_limit": {
                "max": 20,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "gmail.read-drafts",
              "gate": "auto",
              "quota": 200,
              "rate_limit": {
                "max": 30,
                "window_seconds": 60
              }
            }
          ]
        },
        {
          "id": "developer",
          "label": "Developer",
          "description": "Experimental — read-only triage plus drafts to self, colleagues and allowlisted contacts and single-thread labelling; bulk reads ask first.",
          "entries": [
            {
              "risk_id": "gmail.read-mailbox",
              "gate": "auto",
              "quota": 5000,
              "rate_limit": {
                "max": 120,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "gmail.read-from-zone-automated",
              "gate": "auto",
              "quota": 2000,
              "rate_limit": {
                "max": 60,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "gmail.read-from-zone-internal",
              "gate": "auto",
              "quota": 1000,
              "rate_limit": {
                "max": 30,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "gmail.read-from-zone-known-external",
              "gate": "auto",
              "quota": 1000,
              "rate_limit": {
                "max": 30,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "gmail.read-from-zone-unknown",
              "gate": "auto",
              "quota": 300,
              "rate_limit": {
                "max": 20,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "gmail.read-drafts",
              "gate": "auto",
              "quota": 500,
              "rate_limit": {
                "max": 30,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "gmail.draft-write",
              "gate": "auto",
              "quota": 300,
              "rate_limit": {
                "max": 20,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "gmail.draft-to-zone-self",
              "gate": "auto",
              "quota": 200,
              "rate_limit": {
                "max": 20,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "gmail.draft-to-zone-internal",
              "gate": "auto",
              "quota": 200,
              "rate_limit": {
                "max": 20,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "gmail.draft-to-zone-allowlist",
              "gate": "auto",
              "quota": 100,
              "rate_limit": {
                "max": 10,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "gmail.label-write",
              "gate": "auto",
              "quota": 500,
              "rate_limit": {
                "max": 60,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "gmail.bulk-read",
              "gate": "approval",
              "quota": 20,
              "rate_limit": {
                "max": 5,
                "window_seconds": 60
              }
            }
          ]
        }
      ]
    },
    {
      "id": "miro",
      "name": "Miro",
      "version": "0.1.0",
      "status": "stable",
      "description": "Miro boards: sticky notes, shapes, texts, cards, frames, connectors and tags — one resource per board.",
      "page": "https://vaubaan.com/plugins/miro",
      "source": "https://github.com/vaubaan/gateway/tree/main/plugins/miro",
      "health": {
        "state": "not-enough-reports"
      },
      "tools": [
        {
          "name": "miro__list_boards",
          "title": "List the boards",
          "description": "List the boards of this mission, with their details."
        },
        {
          "name": "miro__get_board",
          "title": "Read a board",
          "description": "Read the details of a board."
        },
        {
          "name": "miro__list_items",
          "title": "List a board's items",
          "description": "List the items of a board, one page at a time (pass back the cursor of the answer)."
        },
        {
          "name": "miro__get_item",
          "title": "Read an item",
          "description": "Read one item of a board, whatever its type."
        },
        {
          "name": "miro__list_connectors",
          "title": "List connectors",
          "description": "List the connectors of a board, one page at a time."
        },
        {
          "name": "miro__get_connector",
          "title": "Read a connector",
          "description": "Read one connector."
        },
        {
          "name": "miro__list_tags",
          "title": "List a board's tags",
          "description": "List the tags of a board."
        },
        {
          "name": "miro__list_item_tags",
          "title": "List an item's tags",
          "description": "List the tags attached to an item."
        },
        {
          "name": "miro__create_sticky_note",
          "title": "Add a sticky note",
          "description": "Add a sticky note to a board."
        },
        {
          "name": "miro__update_sticky_note",
          "title": "Edit a sticky note",
          "description": "Edit a sticky note: its content, style, position or size, or the frame it is in."
        },
        {
          "name": "miro__delete_sticky_note",
          "title": "Delete a sticky note",
          "description": "Delete a sticky note, and the connectors attached to it. There is no restore."
        },
        {
          "name": "miro__create_shape",
          "title": "Add a shape",
          "description": "Add a shape to a board."
        },
        {
          "name": "miro__update_shape",
          "title": "Edit a shape",
          "description": "Edit a shape: its content, style, position or size, or the frame it is in."
        },
        {
          "name": "miro__delete_shape",
          "title": "Delete a shape",
          "description": "Delete a shape, and the connectors attached to it. There is no restore."
        },
        {
          "name": "miro__create_text",
          "title": "Add a text",
          "description": "Add a text to a board."
        },
        {
          "name": "miro__update_text",
          "title": "Edit a text",
          "description": "Edit a text: its content, style, position or size, or the frame it is in."
        },
        {
          "name": "miro__delete_text",
          "title": "Delete a text",
          "description": "Delete a text, and the connectors attached to it. There is no restore."
        },
        {
          "name": "miro__create_card",
          "title": "Add a card",
          "description": "Add a card to a board."
        },
        {
          "name": "miro__update_card",
          "title": "Edit a card",
          "description": "Edit a card: its content, style, position or size, or the frame it is in."
        },
        {
          "name": "miro__delete_card",
          "title": "Delete a card",
          "description": "Delete a card, and the connectors attached to it. There is no restore."
        },
        {
          "name": "miro__create_frame",
          "title": "Add a frame",
          "description": "Add a frame to a board."
        },
        {
          "name": "miro__update_frame",
          "title": "Edit a frame",
          "description": "Edit a frame: its content, style, position or size."
        },
        {
          "name": "miro__delete_frame",
          "title": "Delete a frame",
          "description": "Delete a frame. The items inside may go with it; there is no restore."
        },
        {
          "name": "miro__create_connector",
          "title": "Connect two items",
          "description": "Draw a connector between two items of a board."
        },
        {
          "name": "miro__update_connector",
          "title": "Edit a connector",
          "description": "Edit a connector: its ends, shape, captions or style."
        },
        {
          "name": "miro__delete_connector",
          "title": "Delete a connector",
          "description": "Delete a connector. The two items stay."
        },
        {
          "name": "miro__create_tag",
          "title": "Create a tag",
          "description": "Create a tag on a board."
        },
        {
          "name": "miro__attach_tag",
          "title": "Tag an item",
          "description": "Attach a tag of the board to a card or a sticky note."
        },
        {
          "name": "miro__remove_tag",
          "title": "Remove a tag from an item",
          "description": "Remove a tag from an item. The tag stays on the board."
        },
        {
          "name": "miro__create_items",
          "title": "Add several items at once",
          "description": "Add 1 to 20 sticky notes, shapes, texts or cards in one call, all or none. Each item takes the fields of the tool that creates its type."
        }
      ],
      "risks": [
        {
          "id": "miro.read-boards",
          "label": "Read board details",
          "reason": "Name, description, team, links and sharing policy of the selected boards, with the names of the people who own, created, last modified and last opened them. No email address is returned. Listing the boards reads every board of the mission in one call.",
          "severity": "low",
          "kind": "read",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "miro.read-board-content",
          "label": "Read the items, connectors and tags of a board",
          "reason": "Everything written on the board: sticky notes, texts, cards, shapes, frames, connectors and tags, with their position and the id of who created them. Content written by people, which can carry instructions aimed at the agent.",
          "severity": "low",
          "kind": "read",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "miro.content-write",
          "label": "Add and edit items on a board",
          "reason": "Creates or edits sticky notes, shapes, texts, cards, frames and connectors, moves an item in or out of a frame, creates tags and tags items. Seen at once by everyone on the board; reversible by editing again, but the previous text is not kept by the gateway. Every deletion and every bulk creation needs this permission too.",
          "severity": "low",
          "kind": "write",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "miro.bulk-create",
          "label": "Add up to 20 items in one call",
          "reason": "Creates up to twenty sticky notes, shapes, texts or cards at once, all or none. A mission's limits count calls, so one such call adds twenty times what a single creation adds; undoing it takes one deletion per item.",
          "severity": "high",
          "kind": "write",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "miro.item-delete",
          "label": "Delete an item or a connector",
          "reason": "Removes a sticky note, shape, text, card or connector. Deleting an item also removes the connectors attached to it. The REST API has no restore: the content must be written again.",
          "severity": "medium",
          "kind": "write",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "miro.frame-delete",
          "label": "Delete a frame",
          "reason": "Removes a frame, which can group up to 5000 items. Miro's documentation does not say whether the items inside go with it, so this permission assumes they may: a whole section of the board, with no restore through the API.",
          "severity": "high",
          "kind": "destructive",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "miro.uncategorized-write",
          "label": "Uncategorized Miro write",
          "reason": "Catch-all for writes without a dedicated entry. A mission that holds this permission allows the writes this catalog has not reviewed.",
          "severity": "high",
          "kind": "write",
          "acceptable_in_envs": null,
          "fallback": true
        }
      ],
      "presets": [
        {
          "id": "read-only",
          "label": "Read-only",
          "description": "Board details and everything on the selected boards. Nothing is changed.",
          "entries": [
            {
              "risk_id": "miro.read-boards",
              "gate": "auto",
              "quota": 1000,
              "rate_limit": {
                "max": 10,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "miro.read-board-content",
              "gate": "auto",
              "quota": 5000,
              "rate_limit": {
                "max": 120,
                "window_seconds": 60
              }
            }
          ]
        },
        {
          "id": "scribe",
          "label": "Scribe",
          "description": "Read the boards, then add and edit sticky notes, texts, cards, shapes, frames, connectors and tags. Never deletes anything.",
          "entries": [
            {
              "risk_id": "miro.read-boards",
              "gate": "auto",
              "quota": 1000,
              "rate_limit": {
                "max": 10,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "miro.read-board-content",
              "gate": "auto",
              "quota": 5000,
              "rate_limit": {
                "max": 120,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "miro.content-write",
              "gate": "auto",
              "quota": 2000,
              "rate_limit": {
                "max": 60,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "miro.bulk-create",
              "gate": "auto",
              "quota": 100,
              "rate_limit": {
                "max": 5,
                "window_seconds": 60
              }
            }
          ]
        },
        {
          "id": "facilitator",
          "label": "Facilitator",
          "description": "Run a workshop board end to end. Adding and editing run freely; every deletion asks first.",
          "entries": [
            {
              "risk_id": "miro.read-boards",
              "gate": "auto",
              "quota": 1000,
              "rate_limit": {
                "max": 10,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "miro.read-board-content",
              "gate": "auto",
              "quota": 5000,
              "rate_limit": {
                "max": 120,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "miro.content-write",
              "gate": "auto",
              "quota": 2000,
              "rate_limit": {
                "max": 60,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "miro.bulk-create",
              "gate": "auto",
              "quota": 100,
              "rate_limit": {
                "max": 5,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "miro.item-delete",
              "gate": "approval",
              "quota": 300,
              "rate_limit": {
                "max": 20,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "miro.frame-delete",
              "gate": "approval",
              "quota": 10,
              "rate_limit": {
                "max": 2,
                "window_seconds": 60
              }
            }
          ]
        }
      ]
    },
    {
      "id": "notion",
      "name": "Notion",
      "version": "0.1.0",
      "status": "stable",
      "description": "Read and edit the pages, page content, databases and comments of the Notion pages shared with an internal connection.",
      "page": "https://vaubaan.com/plugins/notion",
      "source": "https://github.com/vaubaan/gateway/tree/main/plugins/notion",
      "health": {
        "state": "not-enough-reports"
      },
      "tools": [
        {
          "name": "notion__list_roots",
          "title": "List the shared roots",
          "description": "List the shared roots of this mission: the top-level pages and databases the agent may work in, with their id, title and kind. Sends no request to Notion."
        },
        {
          "name": "notion__search_content",
          "title": "Search pages and databases",
          "description": "Search page and database titles within one root. Notion's search matches titles only, is not exhaustive and lags behind recent changes; results that cannot be shown to be under the root are left out. To find entries of a database, query its data source instead."
        },
        {
          "name": "notion__get_page",
          "title": "Read a page",
          "description": "Read a page's properties (title, icon, cover, database values). Its content is read with notion__list_block_children. A relation or a rollup may look empty when the related pages are not shared with the connection."
        },
        {
          "name": "notion__list_block_children",
          "title": "Read a page's content",
          "description": "Read the content of a page or a block: its child blocks, and with depth 2 or 3 their nested blocks in the same call (at most 500 blocks, 20 listings). Sub-pages are listed, not expanded."
        },
        {
          "name": "notion__get_database",
          "title": "Read a database",
          "description": "Read a database: its title, its parent and the list of its data sources."
        },
        {
          "name": "notion__get_data_source",
          "title": "Read a database's schema",
          "description": "Read a data source's schema: its properties and their types."
        },
        {
          "name": "notion__query_data_source",
          "title": "Query a database",
          "description": "Query the entries of a data source with a Notion filter and sorts. Returns pages (entries) only."
        },
        {
          "name": "notion__create_page",
          "title": "Create a page",
          "description": "Create a page under an existing page of the root, with a title and optional first content. Sent silently: Notion notifies nobody of the change (database automations still run)."
        },
        {
          "name": "notion__update_page",
          "title": "Update a page",
          "description": "Change the title, icon or cover of a page (not a database entry: use notion__update_entry). Sent silently: Notion notifies nobody of the change (database automations still run)."
        },
        {
          "name": "notion__append_blocks",
          "title": "Add content to a page",
          "description": "Add blocks to a page or a block, at the end (default), at the start or after a given child block. Sent silently: Notion notifies nobody of the change (database automations still run)."
        },
        {
          "name": "notion__update_block",
          "title": "Rewrite a block of a page",
          "description": "Rewrite the content of one block (a paragraph, a heading, a to-do, a table row…): give its type and the new object for that type. The block's children are not changed. Notion offers no silent mode here: a mention in the new text notifies the person it names."
        },
        {
          "name": "notion__delete_block",
          "title": "Delete a block of a page",
          "description": "Move one block of a page (with the blocks nested in it) to the trash. Not a page: use notion__trash_page."
        },
        {
          "name": "notion__trash_page",
          "title": "Move a page to the trash",
          "description": "Move a page, and everything under it, to the trash. It can be restored with notion__restore_page. Sent silently: Notion notifies nobody of the change (database automations still run)."
        },
        {
          "name": "notion__restore_page",
          "title": "Bring a page back from the trash",
          "description": "Bring a page back from the trash, with everything under it. Sent silently: Notion notifies nobody of the change (database automations still run)."
        },
        {
          "name": "notion__create_entry",
          "title": "Add a database entry",
          "description": "Add an entry to a database: its data source and the property values. Relation targets must be under the same root. Sent silently: Notion notifies nobody of the change (database automations still run)."
        },
        {
          "name": "notion__update_entry",
          "title": "Update a database entry",
          "description": "Change property values of a database entry; a list value replaces the whole list. Relation targets must be under the same root. Sent silently: Notion notifies nobody of the change (database automations still run)."
        },
        {
          "name": "notion__list_comments",
          "title": "Read comments",
          "description": "Read the open (unresolved) comments on a page or a block."
        },
        {
          "name": "notion__create_comment",
          "title": "Add a comment",
          "description": "Open a discussion on a page or a block, or reply to an open discussion of that page or block. Signed by the connection; notifies the people it mentions."
        }
      ],
      "risks": [
        {
          "id": "notion.read-content",
          "label": "Read pages, their content and database descriptions",
          "reason": "Page titles, properties and block content of the selected pages and databases, search results within them, and the description of their databases. Content written by people, which can carry instructions aimed at the agent.",
          "severity": "low",
          "kind": "read",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "notion.query-databases",
          "label": "Query database entries in bulk",
          "reason": "One query returns up to a hundred entries with all their properties — a CRM, a hiring pipeline or a task list in a few calls. Kept apart from reading one page so a mission can read documents without exporting tables.",
          "severity": "medium",
          "kind": "read",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "notion.read-comments",
          "label": "Read comments and discussions",
          "reason": "Open discussions on pages and blocks, with the names of who wrote them. Often more candid than the page itself.",
          "severity": "low",
          "kind": "read",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "notion.page-create",
          "label": "Create a page under a page",
          "reason": "Adds a page, with its first content, under an existing page of the selected roots. Additive and visible to everyone who can see the parent page; removed by moving it to the trash.",
          "severity": "medium",
          "kind": "write",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "notion.page-update",
          "label": "Change a page's title, icon or cover",
          "reason": "Overwrites what a page is called and how it looks. The previous value is kept only in Notion's page history, whose length depends on the workspace's plan.",
          "severity": "medium",
          "kind": "write",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "notion.content-append",
          "label": "Add content to a page",
          "reason": "Adds blocks (text, lists, to-dos, code, links) at the start or the end of a page or a block, or after a given block. Nothing existing is changed.",
          "severity": "medium",
          "kind": "write",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "notion.block-update",
          "label": "Rewrite a block of a page",
          "reason": "Replaces the content of one existing block — a paragraph, a heading, a to-do, a line of a table — with new text. What people wrote there is kept only in Notion's page history, and a mention in the new text can notify the person it names.",
          "severity": "medium",
          "kind": "write",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "notion.block-delete",
          "label": "Delete a block of a page",
          "reason": "Moves one block of a page, and the blocks nested in it, to Notion's trash: it disappears for everyone. It can be restored in Notion, not by the agent. Never a page or a database, and never a block that holds one: moving a page to the trash is its own permission. A synced block deleted at its original also disappears wherever it is copied.",
          "severity": "medium",
          "kind": "write",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "notion.page-trash",
          "label": "Move a page to the trash",
          "reason": "The page and everything under it — sub-pages, inline databases — disappear for everyone. It can be restored from Notion's trash; the API cannot delete it for good. Database entries are pages too.",
          "severity": "high",
          "kind": "write",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "notion.page-restore",
          "label": "Bring a page back from the trash",
          "reason": "A page someone moved to the trash — out of date, wrong or confidential — reappears with everything under it for everyone who can see where it was. It can be moved to the trash again, but whoever saw it in between has seen it.",
          "severity": "high",
          "kind": "write",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "notion.root-trash",
          "label": "Move a whole shared root page to the trash",
          "reason": "Trashing the page that was shared with the connection takes the whole resource away: every page under it, for every person and every mission. Listed in no preset; a mission that needs it holds it explicitly, beside the permission to trash a page.",
          "severity": "high",
          "kind": "write",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "notion.entry-create",
          "label": "Add an entry to a database",
          "reason": "Creates a row in a database: a task, a contact, a ticket. Can start the database's automations, which run whatever the notifications.",
          "severity": "medium",
          "kind": "write",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "notion.entry-update",
          "label": "Change a database entry's properties",
          "reason": "Overwrites the values of an entry — a status, an owner, a date, a relation; a list value replaces the whole list. Views, automations and relations elsewhere follow the change; the previous value is only in the page history.",
          "severity": "medium",
          "kind": "write",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "notion.comment-create",
          "label": "Add a comment",
          "reason": "Opens a discussion on a page or a block, or replies to an open one, signed by the connection. Notifies the people it mentions: Notion offers no silent comment.",
          "severity": "low",
          "kind": "write",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "notion.uncategorized-write",
          "label": "Uncategorized Notion write",
          "reason": "Catch-all for writes without a dedicated entry. Seen in an audit, it means a tool classifies to a slice no entry covers: add the entry.",
          "severity": "medium",
          "kind": "write",
          "acceptable_in_envs": null,
          "fallback": true
        }
      ],
      "presets": [
        {
          "id": "read-only",
          "label": "Read-only",
          "description": "Read the pages, databases and comments of the selected roots. No write, no comment.",
          "entries": [
            {
              "risk_id": "notion.read-content",
              "gate": "auto",
              "quota": 3000,
              "rate_limit": {
                "max": 30,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "notion.query-databases",
              "gate": "auto",
              "quota": 500,
              "rate_limit": {
                "max": 10,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "notion.read-comments",
              "gate": "auto",
              "quota": 500,
              "rate_limit": {
                "max": 10,
                "window_seconds": 60
              }
            }
          ]
        },
        {
          "id": "reviewer",
          "label": "Reviewer",
          "description": "Read everything in the selected roots and leave comments. No change to pages or databases.",
          "entries": [
            {
              "risk_id": "notion.read-content",
              "gate": "auto",
              "quota": 3000,
              "rate_limit": {
                "max": 30,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "notion.query-databases",
              "gate": "auto",
              "quota": 500,
              "rate_limit": {
                "max": 10,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "notion.read-comments",
              "gate": "auto",
              "quota": 500,
              "rate_limit": {
                "max": 10,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "notion.comment-create",
              "gate": "auto",
              "quota": 100,
              "rate_limit": {
                "max": 5,
                "window_seconds": 60
              }
            }
          ]
        },
        {
          "id": "editor",
          "label": "Editor",
          "description": "Daily work: read, comment, create and edit pages, their content and database entries freely; moving a page to the trash and deleting a block ask first. Never trashes a whole root.",
          "entries": [
            {
              "risk_id": "notion.read-content",
              "gate": "auto",
              "quota": 5000,
              "rate_limit": {
                "max": 30,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "notion.query-databases",
              "gate": "auto",
              "quota": 1000,
              "rate_limit": {
                "max": 10,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "notion.read-comments",
              "gate": "auto",
              "quota": 1000,
              "rate_limit": {
                "max": 10,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "notion.comment-create",
              "gate": "auto",
              "quota": 200,
              "rate_limit": {
                "max": 5,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "notion.page-create",
              "gate": "auto",
              "quota": 200,
              "rate_limit": {
                "max": 10,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "notion.page-update",
              "gate": "auto",
              "quota": 300,
              "rate_limit": {
                "max": 10,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "notion.content-append",
              "gate": "auto",
              "quota": 500,
              "rate_limit": {
                "max": 15,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "notion.block-update",
              "gate": "auto",
              "quota": 300,
              "rate_limit": {
                "max": 10,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "notion.entry-create",
              "gate": "auto",
              "quota": 300,
              "rate_limit": {
                "max": 10,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "notion.entry-update",
              "gate": "auto",
              "quota": 500,
              "rate_limit": {
                "max": 15,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "notion.page-restore",
              "gate": "auto",
              "quota": 20,
              "rate_limit": {
                "max": 2,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "notion.page-trash",
              "gate": "approval",
              "quota": 20,
              "rate_limit": {
                "max": 2,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "notion.block-delete",
              "gate": "approval",
              "quota": 50,
              "rate_limit": {
                "max": 5,
                "window_seconds": 60
              }
            }
          ]
        }
      ]
    },
    {
      "id": "railway",
      "name": "Railway",
      "version": "4.2.0",
      "status": "stable",
      "description": "Deploy, redeploy, read logs and manage variables of Railway projects through the official Railway MCP server and GraphQL API.",
      "page": "https://vaubaan.com/plugins/railway",
      "source": "https://github.com/vaubaan/gateway/tree/main/plugins/railway",
      "health": {
        "state": "not-enough-reports"
      },
      "tools": [
        {
          "name": "railway__whoami",
          "title": "Read who the account is",
          "description": "Get the current authenticated Railway user's profile information"
        },
        {
          "name": "railway__list-projects",
          "title": "List projects",
          "description": "List all Railway projects accessible to the authenticated user"
        },
        {
          "name": "railway__create-project",
          "title": "Create a project",
          "description": "Create a new Railway project"
        },
        {
          "name": "railway__list-services",
          "title": "List services and environments",
          "description": "List all services and environments in a Railway project"
        },
        {
          "name": "railway__list-deployments",
          "title": "List deployments",
          "description": "List recent deployments for a Railway project, optionally filtered by environment, service, or status. Returns the most recent first."
        },
        {
          "name": "railway__get-status",
          "title": "Read the deployment status",
          "description": "Get the deployment status of a Railway project environment. Returns project + environment metadata and, for each service in the environment, its latest deployment status, replica count, and cron schedule. If environmentId is omitted, the project's `production` environment is used when present, otherwise the oldest environment."
        },
        {
          "name": "railway__get-logs",
          "title": "Read logs",
          "description": "Get logs from Railway for a deployment — covers deploy (runtime), build, and http (proxy request) contexts. Pass a specific deploymentId, or pass serviceId + environmentId to resolve the latest deployment. Use `types` to choose which streams to return (default: ['deploy']). This gateway does not accept deploymentId. Give serviceId and environmentId instead: the logs of the latest deployment of that service are read."
        },
        {
          "name": "railway__redeploy",
          "title": "Redeploy a service",
          "description": "Trigger a redeployment of a service in a given environment"
        },
        {
          "name": "railway__accept-deploy",
          "title": "Commit staged changes and deploy",
          "description": "DESTRUCTIVE: Commits all staged changes in a Railway environment and triggers a deploy. Only use this when the user has explicitly confirmed they want to deploy."
        },
        {
          "name": "railway__railway-agent",
          "title": "Ask Railway's agent to act",
          "description": "Send a message to Railway's AI agent for complex infrastructure operations. The agent can inspect services, diagnose issues, and take actions on your behalf. This gateway does not accept threadId. Every call starts a new conversation: put the context it needs in the message."
        },
        {
          "name": "railway__list-variables",
          "title": "List environment variables",
          "description": "List environment variables on a Railway project / environment / service. Backed by GraphQL `variables(projectId, environmentId, serviceId)`. Returns an object mapping each variable name to its (rendered) value. Pass `unrendered: true` to keep `${{Ref}}` references intact."
        },
        {
          "name": "railway__upsert-variable",
          "title": "Set an environment variable",
          "description": "Create or update a single Railway environment variable. Backed by GraphQL `variableUpsert(VariableUpsertInput)`. Pass `skipDeploys: true` to change the value without triggering a redeploy."
        },
        {
          "name": "railway__upsert-variables",
          "title": "Set several environment variables",
          "description": "Bulk-upsert Railway environment variables in one call. Backed by GraphQL `variableCollectionUpsert(VariableCollectionUpsertInput)`. With `replace: true`, ANY variable not present in `variables` is DELETED — destructive."
        },
        {
          "name": "railway__delete-variable",
          "title": "Delete an environment variable",
          "description": "Delete a single Railway environment variable by name. Backed by GraphQL `variableDelete(VariableDeleteInput)`."
        }
      ],
      "risks": [
        {
          "id": "railway.read-account",
          "label": "Read the account and the list of projects",
          "reason": "Account metadata only (`whoami`, `list-projects`). No secret is returned ; the blast radius is the whole workspace because the call is not scoped to a project.",
          "severity": "low",
          "kind": "read",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "railway.read-runtime",
          "label": "Read services, deployments and statuses",
          "reason": "Runtime metadata of a project (services, deployments, status). No secret payload. Broad by design : it co-matches every project-scoped read, so a more specific read permission (logs, variables) is required on top of it.",
          "severity": "low",
          "kind": "read",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "railway.read-logs",
          "label": "Read the logs of a deployment",
          "reason": "Application logs often contain printed secrets (DSN, JWT, stack traces with PII). Classified `data` rather than `secret` because a well-run project does not log its secrets ; a strict creator leaves it out.",
          "severity": "low",
          "kind": "read",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "railway.read-variables",
          "label": "Read environment variables (including secrets)",
          "reason": "Returns variable values in clear by default. The main exfiltration surface for production credentials, hence env-scoped : holding it on staging says nothing about prod.",
          "severity": "low",
          "kind": "read",
          "acceptable_in_envs": [
            "staging",
            "preview",
            "dev",
            "prod"
          ],
          "fallback": false
        },
        {
          "id": "railway.deploy",
          "label": "Redeploy a service or accept staged changes (deploy)",
          "reason": "Reversible : a new deploy fixes a bad one, and Railway bills usage. On prod the deployed state is visible to customers until reverted, hence the per-env vector override.",
          "severity": "medium",
          "kind": "write",
          "acceptable_in_envs": [
            "staging",
            "preview",
            "dev",
            "prod"
          ],
          "fallback": false
        },
        {
          "id": "railway.upsert-variable",
          "label": "Create, modify or delete an environment variable",
          "reason": "Scalar change of named variables (upsert or delete). Triggers an implicit redeploy unless `skipDeploys` is set. On prod a wrong value breaks the service immediately.",
          "severity": "medium",
          "kind": "write",
          "acceptable_in_envs": [
            "staging",
            "preview",
            "dev",
            "prod"
          ],
          "fallback": false
        },
        {
          "id": "railway.bulk-variables-replace",
          "label": "Replace every environment variable in bulk (replace=true)",
          "reason": "DESTRUCTIVE : every variable absent from the payload is deleted. Detected from `replace` set on `upsert-variables`, from an empty or malformed variable payload, and on every `railway-agent` call. Any environment, prod or not.",
          "severity": "high",
          "kind": "destructive",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "railway.create-project",
          "label": "Create a new Railway project",
          "reason": "Account-level write. Not reversible from the MCP surface (deleting a project needs the dashboard) and may generate unbounded cost.",
          "severity": "high",
          "kind": "write",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "railway.agent-action",
          "label": "Invoke the Railway agent (conversational meta-tool)",
          "reason": "The upstream agent decides alone what to mutate ; no fine classification is possible. Catch-all by construction : the classifier also emits the deployment, variable write and variable replace slices, so a mission must hold those permissions too. Explicit opt-in only ; absent from every preset.",
          "severity": "high",
          "kind": "write",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "railway.uncategorized-write",
          "label": "Uncategorized Railway write",
          "reason": "Catch-all for writes without a dedicated entry. Seen in production, it means a tool classifies to a slice no fine-grained entry covers : add the entry.",
          "severity": "medium",
          "kind": "write",
          "acceptable_in_envs": null,
          "fallback": true
        }
      ],
      "presets": [
        {
          "id": "read-only",
          "label": "Read-only",
          "description": "Inspect projects, services, deployments and logs. No variable values, no write.",
          "entries": [
            {
              "risk_id": "railway.read-account",
              "gate": "auto",
              "quota": 1000,
              "rate_limit": {
                "max": 60,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "railway.read-runtime",
              "gate": "auto",
              "quota": 2000,
              "rate_limit": {
                "max": 120,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "railway.read-logs",
              "gate": "auto",
              "quota": 500,
              "rate_limit": {
                "max": 30,
                "window_seconds": 60
              }
            }
          ]
        },
        {
          "id": "developer",
          "label": "Developer",
          "description": "One mission per code project — deploy, redeploy and manage variables on non-production environments freely; read logs; production deploys and variable writes ask first.",
          "entries": [
            {
              "risk_id": "railway.read-account",
              "gate": "auto",
              "quota": 1000,
              "rate_limit": {
                "max": 60,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "railway.read-runtime",
              "gate": "auto",
              "quota": 5000,
              "rate_limit": {
                "max": 120,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "railway.read-logs",
              "gate": "auto",
              "quota": 2000,
              "rate_limit": {
                "max": 60,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "railway[dev].read-variables",
              "gate": "auto",
              "quota": 200,
              "rate_limit": {
                "max": 20,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "railway[preview].read-variables",
              "gate": "auto",
              "quota": 200,
              "rate_limit": {
                "max": 20,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "railway[staging].read-variables",
              "gate": "auto",
              "quota": 200,
              "rate_limit": {
                "max": 20,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "railway[dev].deploy",
              "gate": "auto",
              "quota": 300,
              "rate_limit": {
                "max": 10,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "railway[preview].deploy",
              "gate": "auto",
              "quota": 300,
              "rate_limit": {
                "max": 10,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "railway[staging].deploy",
              "gate": "auto",
              "quota": 300,
              "rate_limit": {
                "max": 10,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "railway[prod].deploy",
              "gate": "approval",
              "quota": 20,
              "rate_limit": {
                "max": 2,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "railway[dev].upsert-variable",
              "gate": "auto",
              "quota": 200,
              "rate_limit": {
                "max": 20,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "railway[preview].upsert-variable",
              "gate": "auto",
              "quota": 200,
              "rate_limit": {
                "max": 20,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "railway[staging].upsert-variable",
              "gate": "auto",
              "quota": 200,
              "rate_limit": {
                "max": 20,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "railway[prod].upsert-variable",
              "gate": "approval",
              "quota": 20,
              "rate_limit": {
                "max": 5,
                "window_seconds": 60
              }
            }
          ]
        },
        {
          "id": "operator",
          "label": "Operator",
          "description": "Run production — every read including production variables, deploys everywhere, destructive variable replacement and project creation asking first.",
          "entries": [
            {
              "risk_id": "railway.read-account",
              "gate": "auto",
              "quota": 2000,
              "rate_limit": {
                "max": 60,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "railway.read-runtime",
              "gate": "auto",
              "quota": 10000,
              "rate_limit": {
                "max": 120,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "railway.read-logs",
              "gate": "auto",
              "quota": 5000,
              "rate_limit": {
                "max": 60,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "railway[dev].read-variables",
              "gate": "auto",
              "quota": 500,
              "rate_limit": {
                "max": 30,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "railway[preview].read-variables",
              "gate": "auto",
              "quota": 500,
              "rate_limit": {
                "max": 30,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "railway[staging].read-variables",
              "gate": "auto",
              "quota": 500,
              "rate_limit": {
                "max": 30,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "railway[prod].read-variables",
              "gate": "auto",
              "quota": 50,
              "rate_limit": {
                "max": 5,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "railway[dev].deploy",
              "gate": "auto",
              "quota": 500,
              "rate_limit": {
                "max": 10,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "railway[preview].deploy",
              "gate": "auto",
              "quota": 500,
              "rate_limit": {
                "max": 10,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "railway[staging].deploy",
              "gate": "auto",
              "quota": 500,
              "rate_limit": {
                "max": 10,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "railway[prod].deploy",
              "gate": "approval",
              "quota": 50,
              "rate_limit": {
                "max": 5,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "railway[dev].upsert-variable",
              "gate": "auto",
              "quota": 500,
              "rate_limit": {
                "max": 30,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "railway[preview].upsert-variable",
              "gate": "auto",
              "quota": 500,
              "rate_limit": {
                "max": 30,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "railway[staging].upsert-variable",
              "gate": "auto",
              "quota": 500,
              "rate_limit": {
                "max": 30,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "railway[prod].upsert-variable",
              "gate": "approval",
              "quota": 50,
              "rate_limit": {
                "max": 5,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "railway.bulk-variables-replace",
              "gate": "approval",
              "quota": 5,
              "rate_limit": {
                "max": 1,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "railway.create-project",
              "gate": "approval",
              "quota": 5,
              "rate_limit": {
                "max": 1,
                "window_seconds": 60
              }
            }
          ]
        }
      ]
    },
    {
      "id": "resend",
      "name": "Resend",
      "version": "0.3.0",
      "status": "stable",
      "description": "Outbound email : transactional sends classified by recipient zone, segments and contacts, broadcast campaigns, sending domains — one resource per domain and per segment.",
      "page": "https://vaubaan.com/plugins/resend",
      "source": "https://github.com/vaubaan/gateway/tree/main/plugins/resend",
      "health": {
        "state": "not-enough-reports"
      },
      "tools": [
        {
          "name": "resend__get_email",
          "title": "Read the status of a sent email",
          "description": "Retrieve the status and metadata of a sent email. Backed by GET /emails/:id."
        },
        {
          "name": "resend__list_domains",
          "title": "List sending domains",
          "description": "List the sending domains of the mission with their verification status. Backed by GET /domains."
        },
        {
          "name": "resend__get_domain",
          "title": "Read a sending domain",
          "description": "Retrieve a sending domain with its DNS records (DKIM, SPF, DMARC). Backed by GET /domains/:id."
        },
        {
          "name": "resend__list_segments",
          "title": "List segments",
          "description": "List the segments (contact lists) of the mission. Backed by GET /segments."
        },
        {
          "name": "resend__get_segment",
          "title": "Read a segment",
          "description": "Retrieve a segment. Backed by GET /segments/:id."
        },
        {
          "name": "resend__list_contacts",
          "title": "List contacts",
          "description": "List contacts, optionally those of one segment. Contacts carry PII. Backed by GET /contacts."
        },
        {
          "name": "resend__get_contact",
          "title": "Read a contact",
          "description": "Retrieve a contact by id or email address. Carries PII. Backed by GET /contacts/:id."
        },
        {
          "name": "resend__list_broadcasts",
          "title": "List broadcasts",
          "description": "List the broadcasts of the segments of the mission. Backed by GET /broadcasts."
        },
        {
          "name": "resend__get_broadcast",
          "title": "Read a broadcast",
          "description": "Retrieve a broadcast of a segment of the mission. Backed by GET /broadcasts/:id."
        },
        {
          "name": "resend__describe_zones",
          "title": "Read the recipient zones",
          "description": "Return the recipient-zone configuration (internal_domains, allowlist_domains, allowlist_addresses) of every sending domain of the mission. Synthetic : no upstream call."
        },
        {
          "name": "resend__send_email",
          "title": "Send an email",
          "description": "Send a transactional email from a sending domain of the mission. Classified by recipient zone (internal / allowlist / external-unbounded) : the worst zone among to, cc and bcc. Attachments and scheduling are refused. Backed by POST /emails."
        },
        {
          "name": "resend__send_batch_emails",
          "title": "Send a batch of emails",
          "description": "Send up to 100 transactional emails in one call. Every from address must be on a sending domain of the mission ; the worst recipient zone per domain applies. Backed by POST /emails/batch."
        },
        {
          "name": "resend__create_domain",
          "title": "Add a sending domain",
          "description": "Add a sending domain to the account. Posture shift : the owner must publish the DNS records and add the new resource to missions. Backed by POST /domains."
        },
        {
          "name": "resend__update_domain",
          "title": "Change a sending domain",
          "description": "Update the tracking and TLS settings of a sending domain. Backed by PATCH /domains/:id."
        },
        {
          "name": "resend__delete_domain",
          "title": "Remove a sending domain",
          "description": "Remove a sending domain. Breaks delivery for every sender on it. Backed by DELETE /domains/:id."
        },
        {
          "name": "resend__verify_domain",
          "title": "Re-check a sending domain",
          "description": "Ask Resend to re-check the DNS records of a sending domain. Idempotent. Backed by POST /domains/:id/verify."
        },
        {
          "name": "resend__create_segment",
          "title": "Create a segment",
          "description": "Create a segment (contact list). Backed by POST /segments."
        },
        {
          "name": "resend__delete_segment",
          "title": "Delete a segment",
          "description": "Delete a segment of the mission. Memberships are lost, contacts survive. Backed by DELETE /segments/:id."
        },
        {
          "name": "resend__create_contact",
          "title": "Create a contact",
          "description": "Create a contact, optionally as a member of a segment of the mission. Carries PII. Backed by POST /contacts (+ POST /contacts/:id/segments/:segment_id)."
        },
        {
          "name": "resend__update_contact",
          "title": "Change a contact",
          "description": "Update a contact by id or email. Carries PII. Backed by PATCH /contacts/:id."
        },
        {
          "name": "resend__delete_contact",
          "title": "Delete a contact",
          "description": "Delete a contact by id or email. Backed by DELETE /contacts/:id."
        },
        {
          "name": "resend__add_contact_to_segment",
          "title": "Add a contact to a segment",
          "description": "Add an existing contact to a segment of the mission. Backed by POST /contacts/:id/segments/:segment_id."
        },
        {
          "name": "resend__remove_contact_from_segment",
          "title": "Remove a contact from a segment",
          "description": "Remove a contact from a segment of the mission. Backed by DELETE /contacts/:id/segments/:segment_id."
        },
        {
          "name": "resend__create_broadcast",
          "title": "Create a draft broadcast",
          "description": "Create a draft broadcast for a segment of the mission. Never sends : \"send\" and \"scheduled_at\" are refused. Backed by POST /broadcasts."
        },
        {
          "name": "resend__update_broadcast",
          "title": "Change a draft broadcast",
          "description": "Update a draft broadcast of a segment of the mission. Backed by PATCH /broadcasts/:id."
        },
        {
          "name": "resend__send_broadcast",
          "title": "Send a broadcast to its segment",
          "description": "Send a broadcast to its segment now. Cardinal risk : mass delivery, irreversible. The broadcast must belong to the named segment. Backed by POST /broadcasts/:id/send."
        },
        {
          "name": "resend__delete_broadcast",
          "title": "Delete a broadcast",
          "description": "Delete a broadcast of a segment of the mission (cancels it if scheduled). Backed by DELETE /broadcasts/:id."
        }
      ],
      "risks": [
        {
          "id": "resend.read-email-status",
          "label": "Read email send status and metadata",
          "reason": "Retrieve the status, subject, from/to and tags of a sent email. Metadata-level : GET /emails/:id returns no body content. Low risk.",
          "severity": "low",
          "kind": "read",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "resend.read-domains",
          "label": "Read sending domains and DNS verification status",
          "reason": "List or retrieve the mission's sending domains with their DKIM / SPF / DMARC records and the recipient-zone configuration (describe_zones). Reveals the sending posture ; no secret (DNS records are public).",
          "severity": "low",
          "kind": "read",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "resend.read-audiences",
          "label": "Read audiences and contacts (PII) — deprecated, replaced by read-segments",
          "reason": "Deprecated : matches no call any more. Resend renamed audiences to segments and made contacts account-level ; `resend.read-segments` carries the same PII read. Kept so the id stays resolvable.",
          "severity": "medium",
          "kind": "read",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "resend.read-segments",
          "label": "Read segments and contacts (PII)",
          "reason": "List or retrieve the mission's segments, the account's contacts and the members of a segment. Contacts carry PII (email, first_name, last_name, custom properties) — GDPR-relevant, every read by an agent is a minimisation question. Distinct from read-broadcasts so a campaign status can be checked without unlocking the contact database.",
          "severity": "medium",
          "kind": "read",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "resend.read-broadcasts",
          "label": "Read broadcast campaigns",
          "reason": "List or retrieve broadcast metadata (name, subject, status, schedule) of the mission's segments. No contact-level PII, aggregate campaign information only.",
          "severity": "low",
          "kind": "read",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "resend.send-email-internal",
          "label": "Send a transactional email to internal recipients only",
          "reason": "Every recipient belongs to the sending domain's internal_domains. Low exfiltration risk — the message stays inside the organisation. Side effect : a real email lands in a real inbox.",
          "severity": "low",
          "kind": "write",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "resend.send-email-allowlist",
          "label": "Send a transactional email to pre-approved external recipients",
          "reason": "Every recipient is internal or matches allowlist_domains / allowlist_addresses of the sending domain. Pre-approved but external : the message leaves the organisation and cannot be recalled.",
          "severity": "medium",
          "kind": "write",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "resend.send-email-external-unbounded",
          "label": "Send a transactional email to an arbitrary external recipient",
          "reason": "At least one recipient matches neither internal_domains nor the allowlists. Cardinal exfiltration vector : an agent could put sensitive data in the body and send it to an attacker-controlled address, irreversibly. Acceptable only on domains labelled staging or dev.",
          "severity": "high",
          "kind": "destructive",
          "acceptable_in_envs": [
            "staging",
            "dev"
          ],
          "fallback": false
        },
        {
          "id": "resend.send-batch",
          "label": "Send a batch of transactional emails (up to 100 per call)",
          "reason": "POST /emails/batch sends up to 100 emails in one call. Bulk amplification whatever the recipient zone — the classifier also emits the zone slice, so both permissions must be held. A distinct marker so the creator consents to bulk sending explicitly.",
          "severity": "high",
          "kind": "destructive",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "resend.audience-write",
          "label": "Create or delete an audience — deprecated, replaced by segment-write",
          "reason": "Deprecated : matches no call any more. Resend renamed audiences to segments ; `resend.segment-write` carries the same structural change. Kept so the id stays resolvable.",
          "severity": "medium",
          "kind": "write",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "resend.segment-write",
          "label": "Create or delete a segment",
          "reason": "Structural change to the contact database. Creating a segment is low risk ; deleting one drops every membership it held (the contacts survive). Conservative vector for the worst case.",
          "severity": "medium",
          "kind": "write",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "resend.contact-write",
          "label": "Create, update or delete contacts ; add or remove segment members",
          "reason": "Mutate the contact database : add subscribers, edit PII fields, unsubscribe, delete, or move contacts in and out of a segment (which decides who a broadcast reaches). Reversible for creates, not for deletes.",
          "severity": "high",
          "kind": "write",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "resend.broadcast-draft",
          "label": "Create or update a broadcast (draft, not sent)",
          "reason": "Draft a campaign for a segment of the mission. No side effect until sent — the classifier refuses a create carrying `send` or `scheduled_at`, which would bypass broadcast-send. Reversible (delete the draft).",
          "severity": "low",
          "kind": "write",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "resend.broadcast-send",
          "label": "Send a broadcast to its segment (mass email delivery)",
          "reason": "Deliver a broadcast to every contact of the segment. Cardinal bulk action — potentially thousands of real emails, irreversibly, at a cost that scales with the segment. The plugin re-reads the broadcast upstream and refuses to send one that targets another segment than the one named. The highest-risk operation of this service.",
          "severity": "critical",
          "kind": "destructive",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "resend.broadcast-delete",
          "label": "Delete a broadcast",
          "reason": "Remove a broadcast of a segment of the mission. A sent broadcast loses its record only (delivered emails are not recalled) ; a scheduled one is cancelled.",
          "severity": "low",
          "kind": "write",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "resend.domain-write",
          "label": "Add, update or remove a sending domain",
          "reason": "Posture shift : adding a domain widens what the account can send from ; removing one breaks delivery for every sender on it ; tracking and TLS settings change what recipients see. DNS must be configured outside Resend. Suggested : ask first.",
          "severity": "high",
          "kind": "write",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "resend.domain-verify",
          "label": "Trigger DNS verification of a domain",
          "reason": "Idempotent request that asks Resend to re-check the DNS records of a domain of the mission. No side effect beyond the attempt ; a step of the domain set-up posture shift.",
          "severity": "low",
          "kind": "write",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "resend.uncategorized-write",
          "label": "Uncategorized Resend write",
          "reason": "Catch-all for a write no named permission covers. If Resend adds a surface (webhooks, API keys, templates) and Vaubaan wraps it before the catalog is updated, calls land here — fail-loud by design. Never offered in a preset.",
          "severity": "medium",
          "kind": "write",
          "acceptable_in_envs": null,
          "fallback": true
        }
      ],
      "presets": [
        {
          "id": "read-only",
          "label": "Read-only",
          "description": "Email status, sending domains and campaign metadata of the selected resources. No PII, no write.",
          "entries": [
            {
              "risk_id": "resend.read-email-status",
              "gate": "auto",
              "quota": 2000,
              "rate_limit": {
                "max": 120,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "resend.read-domains",
              "gate": "auto",
              "quota": 1000,
              "rate_limit": {
                "max": 60,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "resend.read-broadcasts",
              "gate": "auto",
              "quota": 1000,
              "rate_limit": {
                "max": 60,
                "window_seconds": 60
              }
            }
          ]
        },
        {
          "id": "developer",
          "label": "Developer",
          "description": "One mission per code project — transactional sends to internal and allow-listed recipients, generous limits; no bulk, no PII, no domain change.",
          "entries": [
            {
              "risk_id": "resend.read-email-status",
              "gate": "auto",
              "quota": 5000,
              "rate_limit": {
                "max": 120,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "resend.read-domains",
              "gate": "auto",
              "quota": 2000,
              "rate_limit": {
                "max": 60,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "resend.read-broadcasts",
              "gate": "auto",
              "quota": 1000,
              "rate_limit": {
                "max": 60,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "resend.send-email-internal",
              "gate": "auto",
              "quota": 2000,
              "rate_limit": {
                "max": 60,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "resend.send-email-allowlist",
              "gate": "auto",
              "quota": 1000,
              "rate_limit": {
                "max": 30,
                "window_seconds": 60
              }
            }
          ]
        },
        {
          "id": "marketing-operator",
          "label": "Marketing operator",
          "description": "Contacts, segments and campaigns; broadcast sends, batch sends and segment deletion ask first. No domain change.",
          "entries": [
            {
              "risk_id": "resend.read-email-status",
              "gate": "auto",
              "quota": 2000,
              "rate_limit": {
                "max": 120,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "resend.read-domains",
              "gate": "auto",
              "quota": 1000,
              "rate_limit": {
                "max": 60,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "resend.read-segments",
              "gate": "auto",
              "quota": 5000,
              "rate_limit": {
                "max": 120,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "resend.read-broadcasts",
              "gate": "auto",
              "quota": 2000,
              "rate_limit": {
                "max": 120,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "resend.send-email-internal",
              "gate": "auto",
              "quota": 1000,
              "rate_limit": {
                "max": 60,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "resend.send-email-allowlist",
              "gate": "auto",
              "quota": 1000,
              "rate_limit": {
                "max": 30,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "resend.send-batch",
              "gate": "approval",
              "quota": 50,
              "rate_limit": {
                "max": 5,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "resend.contact-write",
              "gate": "auto",
              "quota": 5000,
              "rate_limit": {
                "max": 120,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "resend.segment-write",
              "gate": "approval",
              "quota": 20,
              "rate_limit": {
                "max": 5,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "resend.broadcast-draft",
              "gate": "auto",
              "quota": 200,
              "rate_limit": {
                "max": 20,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "resend.broadcast-send",
              "gate": "approval",
              "quota": 10,
              "rate_limit": {
                "max": 1,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "resend.broadcast-delete",
              "gate": "auto",
              "quota": 50,
              "rate_limit": {
                "max": 10,
                "window_seconds": 60
              }
            }
          ]
        },
        {
          "id": "domain-admin",
          "label": "Domain admin",
          "description": "Provision sending domains — read and verify freely, every domain mutation asks first. No send.",
          "entries": [
            {
              "risk_id": "resend.read-domains",
              "gate": "auto",
              "quota": 2000,
              "rate_limit": {
                "max": 60,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "resend.domain-verify",
              "gate": "auto",
              "quota": 200,
              "rate_limit": {
                "max": 10,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "resend.domain-write",
              "gate": "approval",
              "quota": 20,
              "rate_limit": {
                "max": 5,
                "window_seconds": 60
              }
            }
          ]
        }
      ]
    },
    {
      "id": "ssh",
      "name": "SSH (bash)",
      "version": "2.0.0",
      "status": "stable",
      "description": "Shell commands on a host over SSH — inspection on a read allow-list, writes priced by class (destructive, package, service, sudo), lines that cannot be classified priced as all of them.",
      "page": "https://vaubaan.com/plugins/ssh",
      "source": "https://github.com/vaubaan/gateway/tree/main/plugins/ssh",
      "health": {
        "state": "not-enough-reports"
      },
      "tools": [
        {
          "name": "ssh__exec",
          "title": "Run a shell command on a host",
          "description": "Run a shell command on an SSH host of the mission. A simple command on the read allow-list (journalctl, cat, ls, tail, head, grep, pgrep, ps, docker ps/inspect/logs, systemctl status, df, free, uptime, who, ss -tlnp, md5sum, stat — bare command name, known options only) is low-risk ; anything else is a write, and destructive deletes, package changes, service control and sudo need their own accepted risk. Keep the line plain : a command substitution, a here-document, a variable as the command, inline code (bash -c, python -c, eval), ssh, docker exec or su cannot be classified and need every write risk plus the credential-file risk. Output is capped at 1 MiB."
        }
      ],
      "risks": [
        {
          "id": "ssh.exec-read",
          "label": "Run a whitelisted read-only command",
          "reason": "Inspection commands recognised by the read allow-list (`journalctl`, `cat`, `ls`, `tail`, `head`, `grep`, `pgrep`, `ps`, `docker ps/inspect/logs`, `systemctl status`, `df`, `free`, `uptime`, `who`, `ss -tlnp`, `md5sum`, `stat`) with their known read-only options, alone or piped together. No mutation. Logs and files may still contain secrets, which is why `exec-read-secret-files` exists next to it.",
          "severity": "low",
          "kind": "read",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "ssh.exec-read-secret-files",
          "label": "Read a credential-bearing file",
          "reason": "The read allow-list accepts `cat <path>` without filtering the path, so `cat /etc/shadow`, `cat /root/.env` or `stat /home/<u>/.ssh/id_ed25519` are reads. The classifier recognises credential-bearing paths however they are spelled (`.ssh/…`, `.env`, private keys, `/etc/shadow`, `/root`, raw devices), and paths it cannot see (an expansion, `..`, a recursive `grep`, `docker inspect`, a line it cannot classify), and emits an extra slice, so a mission must hold this permission on top of `exec-read` to read them. Exfiltration of host credentials ; refused by every preset but `operator`, where it asks first.",
          "severity": "low",
          "kind": "read",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "ssh.exec-write",
          "label": "Run an arbitrary shell command",
          "reason": "Any simple command outside the read allow-list and any output redirection. File edits, builds, git operations, process launches, scripts : the program may do whatever its user can. A line the classifier cannot see through (command substitution, inline code, another host or container) needs this permission and every class permission below. On a `dev` or `staging` host the blast radius is the host ; on `prod` a write can shift the security posture or cause downtime, hence the override.",
          "severity": "medium",
          "kind": "write",
          "acceptable_in_envs": [
            "dev",
            "staging",
            "prod"
          ],
          "fallback": false
        },
        {
          "id": "ssh.exec-destructive",
          "label": "Destructive command (recursive delete, disk, reboot)",
          "reason": "`rm` with `-r` or `-f`, `mkfs`, `dd`, `shred`, `wipefs`, partitioning tools, `reboot` / `shutdown`, `userdel`, firewall flushes, Docker prunes. Irreversible at the host level ; emitted together with `exec-write`. Presets make it ask first outside `dev`.",
          "severity": "high",
          "kind": "destructive",
          "acceptable_in_envs": [
            "dev",
            "staging",
            "prod"
          ],
          "fallback": false
        },
        {
          "id": "ssh.exec-package-install",
          "label": "Install, remove or upgrade packages",
          "reason": "System package managers (`apt`, `dpkg`, `yum`, `dnf`, `zypper`, `apk`, `pacman`, `snap`, `brew`, `flatpak`, `rpm`) in a mutating mode, global `npm` / `pnpm` / `yarn` installs, `pipx`. Changes what runs on the host for every service ; supply-chain exposure. Emitted together with `exec-write`.",
          "severity": "medium",
          "kind": "write",
          "acceptable_in_envs": [
            "dev",
            "staging",
            "prod"
          ],
          "fallback": false
        },
        {
          "id": "ssh.exec-service-control",
          "label": "Start, stop or restart a service or process",
          "reason": "`systemctl` lifecycle verbs, `service <x> restart`, Docker and Compose lifecycle, `pm2`, `supervisorctl`, `nginx -s`, `kill` / `pkill`. Availability impact, reversible by starting again. Emitted together with `exec-write`. A developer preset allows it on `dev` and asks first on `staging`.",
          "severity": "medium",
          "kind": "write",
          "acceptable_in_envs": [
            "dev",
            "staging",
            "prod"
          ],
          "fallback": false
        },
        {
          "id": "ssh.exec-sudo",
          "label": "Privilege escalation (sudo, su, doas)",
          "reason": "A non-whitelisted command run through `sudo`, `su`, `doas` or `pkexec`. Whatever the inner command does, it does as another user, usually root. Emitted together with `exec-write` and the inner command's classes. Inspection verbs the whitelist already admits under `sudo` (`sudo journalctl`, `sudo systemctl status`, `sudo ss -tlnp`) stay reads.",
          "severity": "high",
          "kind": "write",
          "acceptable_in_envs": [
            "dev",
            "staging",
            "prod"
          ],
          "fallback": false
        },
        {
          "id": "ssh.uncategorized-write",
          "label": "Uncategorized SSH write",
          "reason": "Catch-all for writes without a dedicated entry. Every write of the single `ssh__exec` tool is covered by `exec-write` ; this entry exists for tools added later (an `ssh__sftp_put`, say) and is never referenced by a preset.",
          "severity": "medium",
          "kind": "write",
          "acceptable_in_envs": null,
          "fallback": true
        }
      ],
      "presets": [
        {
          "id": "read-only",
          "label": "Read-only",
          "description": "Inspect a host — logs, processes, disk, services, files outside credential paths. No write.",
          "entries": [
            {
              "risk_id": "ssh.exec-read",
              "gate": "auto",
              "quota": 2000,
              "rate_limit": {
                "max": 60,
                "window_seconds": 60
              }
            }
          ]
        },
        {
          "id": "developer",
          "label": "Developer",
          "description": "One mission per code project — reads everywhere, file and process operations on dev and staging hosts, service restarts on dev; nothing destructive, no packages, no sudo, no prod.",
          "entries": [
            {
              "risk_id": "ssh.exec-read",
              "gate": "auto",
              "quota": 5000,
              "rate_limit": {
                "max": 120,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "ssh[dev].exec-write",
              "gate": "auto",
              "quota": 1000,
              "rate_limit": {
                "max": 30,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "ssh[staging].exec-write",
              "gate": "auto",
              "quota": 500,
              "rate_limit": {
                "max": 20,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "ssh[dev].exec-service-control",
              "gate": "auto",
              "quota": 200,
              "rate_limit": {
                "max": 10,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "ssh[staging].exec-service-control",
              "gate": "approval",
              "quota": 50,
              "rate_limit": {
                "max": 5,
                "window_seconds": 60
              }
            }
          ]
        },
        {
          "id": "operator",
          "label": "Operator",
          "description": "Everything on every labelled host; credential-file reads, destructive commands, package changes, service control and sudo asking first outside dev; every prod write asking first.",
          "entries": [
            {
              "risk_id": "ssh.exec-read",
              "gate": "auto",
              "quota": 10000,
              "rate_limit": {
                "max": 120,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "ssh.exec-read-secret-files",
              "gate": "approval",
              "quota": 50,
              "rate_limit": {
                "max": 5,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "ssh[dev].exec-write",
              "gate": "auto",
              "quota": 2000,
              "rate_limit": {
                "max": 30,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "ssh[staging].exec-write",
              "gate": "auto",
              "quota": 1000,
              "rate_limit": {
                "max": 30,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "ssh[prod].exec-write",
              "gate": "approval",
              "quota": 200,
              "rate_limit": {
                "max": 10,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "ssh[dev].exec-destructive",
              "gate": "auto",
              "quota": 100,
              "rate_limit": {
                "max": 10,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "ssh[staging].exec-destructive",
              "gate": "approval",
              "quota": 20,
              "rate_limit": {
                "max": 5,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "ssh[prod].exec-destructive",
              "gate": "approval",
              "quota": 10,
              "rate_limit": {
                "max": 2,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "ssh[dev].exec-package-install",
              "gate": "auto",
              "quota": 100,
              "rate_limit": {
                "max": 10,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "ssh[staging].exec-package-install",
              "gate": "approval",
              "quota": 20,
              "rate_limit": {
                "max": 5,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "ssh[prod].exec-package-install",
              "gate": "approval",
              "quota": 10,
              "rate_limit": {
                "max": 2,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "ssh[dev].exec-service-control",
              "gate": "auto",
              "quota": 500,
              "rate_limit": {
                "max": 20,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "ssh[staging].exec-service-control",
              "gate": "approval",
              "quota": 100,
              "rate_limit": {
                "max": 10,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "ssh[prod].exec-service-control",
              "gate": "approval",
              "quota": 20,
              "rate_limit": {
                "max": 5,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "ssh[dev].exec-sudo",
              "gate": "auto",
              "quota": 200,
              "rate_limit": {
                "max": 20,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "ssh[staging].exec-sudo",
              "gate": "approval",
              "quota": 50,
              "rate_limit": {
                "max": 5,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "ssh[prod].exec-sudo",
              "gate": "approval",
              "quota": 20,
              "rate_limit": {
                "max": 2,
                "window_seconds": 60
              }
            }
          ]
        }
      ]
    },
    {
      "id": "stripe",
      "name": "Stripe",
      "version": "0.4.0",
      "status": "stable",
      "description": "Billing operations on a Stripe account : catalog, customers, subscriptions, invoices, refunds, payouts, webhooks — test and live modes as separate resources.",
      "page": "https://vaubaan.com/plugins/stripe",
      "source": "https://github.com/vaubaan/gateway/tree/main/plugins/stripe",
      "health": {
        "state": "not-enough-reports"
      },
      "tools": [
        {
          "name": "stripe__list_products",
          "title": "List products",
          "description": "List all products. Backed by GET /v1/products."
        },
        {
          "name": "stripe__list_prices",
          "title": "List prices",
          "description": "List all prices, optionally filtered by product. Backed by GET /v1/prices."
        },
        {
          "name": "stripe__list_customers",
          "title": "List customers",
          "description": "List all customers. Returns PII fields (email, name, address, phone). Backed by GET /v1/customers."
        },
        {
          "name": "stripe__retrieve_customer",
          "title": "Read a customer",
          "description": "Retrieve a single customer by id. Returns PII fields (email, name, address, phone, shipping). Backed by GET /v1/customers/:id."
        },
        {
          "name": "stripe__list_subscriptions",
          "title": "List subscriptions",
          "description": "List subscriptions, optionally filtered by customer or status. Backed by GET /v1/subscriptions."
        },
        {
          "name": "stripe__list_invoices",
          "title": "List invoices",
          "description": "List invoices, optionally filtered by customer or status. Backed by GET /v1/invoices."
        },
        {
          "name": "stripe__list_charges",
          "title": "List charges",
          "description": "List charges, optionally filtered by customer. Backed by GET /v1/charges."
        },
        {
          "name": "stripe__list_payment_intents",
          "title": "List payment intents",
          "description": "List payment intents, optionally filtered by customer. Backed by GET /v1/payment_intents."
        },
        {
          "name": "stripe__get_balance",
          "title": "Read the balance",
          "description": "Retrieve the current account balance (available, pending, connect_reserved). Backed by GET /v1/balance."
        },
        {
          "name": "stripe__list_events",
          "title": "List events",
          "description": "List events (webhook delivery log / audit feed). Backed by GET /v1/events."
        },
        {
          "name": "stripe__create_product",
          "title": "Create a product",
          "description": "Create a new product. Backed by POST /v1/products."
        },
        {
          "name": "stripe__update_product",
          "title": "Change a product",
          "description": "Update an existing product. Backed by POST /v1/products/:id."
        },
        {
          "name": "stripe__create_price",
          "title": "Create a price",
          "description": "Create a new price for a product. Backed by POST /v1/prices."
        },
        {
          "name": "stripe__update_price",
          "title": "Change a price",
          "description": "Update an existing price. Backed by POST /v1/prices/:id."
        },
        {
          "name": "stripe__create_coupon",
          "title": "Create a coupon",
          "description": "Create a new coupon (amount-off or percent-off). Backed by POST /v1/coupons."
        },
        {
          "name": "stripe__create_promotion_code",
          "title": "Create a promotion code",
          "description": "Create a customer-facing promotion code for an existing coupon. Backed by POST /v1/promotion_codes."
        },
        {
          "name": "stripe__create_subscription",
          "title": "Create a subscription",
          "description": "Create a new subscription for a customer. May trigger an immediate charge if proration applies. Backed by POST /v1/subscriptions."
        },
        {
          "name": "stripe__update_subscription",
          "title": "Change a subscription",
          "description": "Update an existing subscription (change plan, quantity, payment method, metadata). Backed by POST /v1/subscriptions/:id."
        },
        {
          "name": "stripe__cancel_subscription",
          "title": "Cancel a subscription",
          "description": "Cancel a subscription. Irreversible — stops future billing. Backed by DELETE /v1/subscriptions/:id."
        },
        {
          "name": "stripe__pause_subscription",
          "title": "Pause a subscription",
          "description": "Pause a subscription by setting pause_collection. Technically an update with pause_collection field. Backed by POST /v1/subscriptions/:id."
        },
        {
          "name": "stripe__create_invoice",
          "title": "Create a draft invoice",
          "description": "Create a draft invoice for a customer. No notification is sent until finalized. Backed by POST /v1/invoices."
        },
        {
          "name": "stripe__finalize_invoice",
          "title": "Finalize an invoice",
          "description": "Finalize a draft invoice — locks it, notifies the customer (email by default), and triggers automatic collection if auto_advance is true. Backed by POST /v1/invoices/:id/finalize."
        },
        {
          "name": "stripe__void_invoice",
          "title": "Void an invoice",
          "description": "Void a finalized invoice. Irreversible — the invoice becomes permanently inert. Backed by POST /v1/invoices/:id/void."
        },
        {
          "name": "stripe__create_refund",
          "title": "Refund a payment",
          "description": "Create a refund on a charge or payment intent. Real money leaves the merchant account. Irreversible. Backed by POST /v1/refunds."
        },
        {
          "name": "stripe__create_webhook_endpoint",
          "title": "Create a webhook endpoint",
          "description": "Create a webhook endpoint. Returns a signing secret (whsec_*) that is only available at creation time. Backed by POST /v1/webhook_endpoints."
        },
        {
          "name": "stripe__create_payout",
          "title": "Pay out to the bank account",
          "description": "Send funds from the Stripe balance to the bank account or debit card on file (real money out in live mode). Backed by POST /v1/payouts."
        },
        {
          "name": "stripe__delete_customer",
          "title": "Delete a customer",
          "description": "Permanently delete a customer. Cannot be undone ; immediately cancels every active subscription of the customer. Backed by DELETE /v1/customers/:id."
        }
      ],
      "risks": [
        {
          "id": "stripe.read-catalog",
          "label": "Read catalog (products, prices, coupons, promotion codes)",
          "reason": "Inventory of the commercial catalog. Public metadata by construction (prices are shown on the marketing site). No PII, no secret.",
          "severity": "low",
          "kind": "read",
          "acceptable_in_envs": [
            "test",
            "live"
          ],
          "fallback": false
        },
        {
          "id": "stripe.read-billing-history",
          "label": "Read billing history (subscriptions, invoices, charges, payment intents)",
          "reason": "Reads the transactional history. No card number leaves Stripe, but amounts, dates and descriptions per customer reveal the commercial trajectory. Distinct from read-customer-pii so anonymised statistics can be allowed without identities.",
          "severity": "low",
          "kind": "read",
          "acceptable_in_envs": [
            "test",
            "live"
          ],
          "fallback": false
        },
        {
          "id": "stripe.read-customer-pii",
          "label": "Read customer PII (email, name, address, phone)",
          "reason": "Reads the personal fields of customers (`email`, `name`, `address`, `phone`, `shipping`). GDPR/CCPA surface : any consumption by an agent triggers a minimisation duty. Emitted alongside the business payload of every customer read, so a mission without it cannot read customers.",
          "severity": "medium",
          "kind": "read",
          "acceptable_in_envs": [
            "test",
            "live"
          ],
          "fallback": false
        },
        {
          "id": "stripe.read-balance",
          "label": "Read balance, ledger and events (cash position)",
          "reason": "Available and pending balance, ledger history, event feed. Real-time cash position : sensitive for finance, no PII. Distinct from read-billing-history so an \"ops monitoring\" agent needs no customer details.",
          "severity": "low",
          "kind": "read",
          "acceptable_in_envs": [
            "test",
            "live"
          ],
          "fallback": false
        },
        {
          "id": "stripe.catalog-write",
          "label": "Create / update products, prices, coupons, promotion codes",
          "reason": "Changes the commercial catalog. Reversible (archive and recreate), no effect on existing payments, but every future checkout sees it. A soft posture shift on the offer.",
          "severity": "medium",
          "kind": "write",
          "acceptable_in_envs": [
            "test",
            "live"
          ],
          "fallback": false
        },
        {
          "id": "stripe.subscription-create",
          "label": "Create a subscription (may charge immediately)",
          "reason": "Creates a subscription. With immediate proration (the default without `billing_cycle_anchor`) Stripe charges the customer at creation. The classifier also emits `payment-intent.W` for that cash side effect ; this entry covers both slices (the second used to fall to the fallback, which presets no longer carry — catalog v3).",
          "severity": "medium",
          "kind": "write",
          "acceptable_in_envs": [
            "test",
            "live"
          ],
          "fallback": false
        },
        {
          "id": "stripe.subscription-update",
          "label": "Update a subscription (plan, quantity, pause, metadata)",
          "reason": "Changes an existing subscription : plan, quantity, metadata, `pause_collection`. Costly when proration charges or credits the customer. Posture shift on recurring billing.",
          "severity": "medium",
          "kind": "write",
          "acceptable_in_envs": [
            "test",
            "live"
          ],
          "fallback": false
        },
        {
          "id": "stripe.subscription-cancel",
          "label": "Cancel a subscription (stops future charges)",
          "reason": "Stops future billing. Semantically irreversible : resubscribing creates a new subscription and loses the history. The customer loses access at the end of the period.",
          "severity": "medium",
          "kind": "destructive",
          "acceptable_in_envs": [
            "test",
            "live"
          ],
          "fallback": false
        },
        {
          "id": "stripe.invoice-create-draft",
          "label": "Create a draft invoice (no customer notification)",
          "reason": "Creates an invoice in draft state. No notification, no money movement, deletable until finalised. Acceptable surface for a billing assistant.",
          "severity": "low",
          "kind": "write",
          "acceptable_in_envs": [
            "test",
            "live"
          ],
          "fallback": false
        },
        {
          "id": "stripe.invoice-finalize",
          "label": "Finalize an invoice (locks it, notifies, attempts collection)",
          "reason": "Locks the draft, emails the customer by default and attempts collection when `auto_advance` is set. Costly to revert : voiding leaves a trace.",
          "severity": "medium",
          "kind": "write",
          "acceptable_in_envs": [
            "test",
            "live"
          ],
          "fallback": false
        },
        {
          "id": "stripe.invoice-void",
          "label": "Void a finalized invoice (cannot be undone)",
          "reason": "Voids a finalised invoice. Irreversible by design (the paper trail stays, the invoice is inert for ever). No direct money movement but the receivable is written off : accounting impact.",
          "severity": "medium",
          "kind": "destructive",
          "acceptable_in_envs": [
            "test",
            "live"
          ],
          "fallback": false
        },
        {
          "id": "stripe.refund-create",
          "label": "Create a refund (real money out of the merchant account)",
          "reason": "Issues a refund. Real money leaves the account ; bounded per call by the charge amount, unbounded over N calls. Irreversible : a refund is not reversed, the customer has to be charged again and may refuse. The cardinal financial-exfiltration vector of a compromised or prompt-injected agent.",
          "severity": "high",
          "kind": "destructive",
          "acceptable_in_envs": [
            "test",
            "live"
          ],
          "fallback": false
        },
        {
          "id": "stripe.payout-create",
          "label": "Create a payout (moves the balance to the bank account on file)",
          "reason": "Sends funds from the Stripe balance to the external account on file. Cancellable only while pending, then irreversible ; the amount is bounded by the balance, not by a limit of the mission. In test mode no money moves.",
          "severity": "high",
          "kind": "destructive",
          "acceptable_in_envs": [
            "test",
            "live"
          ],
          "fallback": false
        },
        {
          "id": "stripe.customer-delete",
          "label": "Delete a customer (cancels its subscriptions, cannot be undone)",
          "reason": "Permanently deletes a customer and immediately cancels every active subscription. Irreversible ; erases personal data (a GDPR erasure path) and recurring revenue in one call.",
          "severity": "high",
          "kind": "destructive",
          "acceptable_in_envs": [
            "test",
            "live"
          ],
          "fallback": false
        },
        {
          "id": "stripe.webhook-endpoint-write",
          "label": "Create / update / delete a webhook endpoint configuration",
          "reason": "Configures the URL and the events an endpoint receives. Posture shift : a misconfigured endpoint routes events (customer data included) to an attacker. Distinct from mint-webhook-secret because an update does not regenerate the secret.",
          "severity": "medium",
          "kind": "write",
          "acceptable_in_envs": [
            "test",
            "live"
          ],
          "fallback": false
        },
        {
          "id": "stripe.mint-webhook-secret",
          "label": "Mint a webhook signing secret (create_webhook_endpoint)",
          "reason": "Credential creation. The `whsec_*` secret is returned once, at creation, and authenticates every event received. Same intention as creating an endpoint, asymmetric secret-bearing effect.",
          "severity": "medium",
          "kind": "write",
          "acceptable_in_envs": [
            "test",
            "live"
          ],
          "fallback": false
        },
        {
          "id": "stripe.uncategorized-write",
          "label": "Uncategorized Stripe write",
          "reason": "Catch-all for writes without a dedicated entry. Seen in production, it means a tool classifies to a slice no entry covers : add the entry.",
          "severity": "high",
          "kind": "write",
          "acceptable_in_envs": [
            "test",
            "live"
          ],
          "fallback": true
        }
      ],
      "presets": [
        {
          "id": "read-only",
          "label": "Read-only",
          "description": "Catalog, billing history and cash position of the selected mode. No PII, no write.",
          "entries": [
            {
              "risk_id": "stripe.read-catalog",
              "gate": "auto",
              "quota": 2000,
              "rate_limit": {
                "max": 120,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "stripe.read-billing-history",
              "gate": "auto",
              "quota": 2000,
              "rate_limit": {
                "max": 120,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "stripe.read-balance",
              "gate": "auto",
              "quota": 500,
              "rate_limit": {
                "max": 60,
                "window_seconds": 60
              }
            }
          ]
        },
        {
          "id": "test-mode-developer",
          "label": "Test-mode developer",
          "description": "Everything on the test-mode resource, generous budgets, nothing on live.",
          "entries": [
            {
              "risk_id": "stripe[test].read-catalog",
              "gate": "auto",
              "quota": 5000,
              "rate_limit": {
                "max": 120,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "stripe[test].read-billing-history",
              "gate": "auto",
              "quota": 5000,
              "rate_limit": {
                "max": 120,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "stripe[test].read-customer-pii",
              "gate": "auto",
              "quota": 5000,
              "rate_limit": {
                "max": 120,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "stripe[test].read-balance",
              "gate": "auto",
              "quota": 1000,
              "rate_limit": {
                "max": 60,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "stripe[test].catalog-write",
              "gate": "auto",
              "quota": 1000,
              "rate_limit": {
                "max": 60,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "stripe[test].subscription-create",
              "gate": "auto",
              "quota": 500,
              "rate_limit": {
                "max": 30,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "stripe[test].subscription-update",
              "gate": "auto",
              "quota": 500,
              "rate_limit": {
                "max": 30,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "stripe[test].subscription-cancel",
              "gate": "auto",
              "quota": 200,
              "rate_limit": {
                "max": 30,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "stripe[test].invoice-create-draft",
              "gate": "auto",
              "quota": 500,
              "rate_limit": {
                "max": 30,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "stripe[test].invoice-finalize",
              "gate": "auto",
              "quota": 200,
              "rate_limit": {
                "max": 30,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "stripe[test].invoice-void",
              "gate": "auto",
              "quota": 100,
              "rate_limit": {
                "max": 30,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "stripe[test].refund-create",
              "gate": "auto",
              "quota": 100,
              "rate_limit": {
                "max": 10,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "stripe[test].payout-create",
              "gate": "auto",
              "quota": 20,
              "rate_limit": {
                "max": 5,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "stripe[test].customer-delete",
              "gate": "auto",
              "quota": 100,
              "rate_limit": {
                "max": 30,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "stripe[test].webhook-endpoint-write",
              "gate": "auto",
              "quota": 50,
              "rate_limit": {
                "max": 10,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "stripe[test].mint-webhook-secret",
              "gate": "auto",
              "quota": 20,
              "rate_limit": {
                "max": 10,
                "window_seconds": 60
              }
            }
          ]
        },
        {
          "id": "finance-operator",
          "label": "Finance operator",
          "description": "Live-mode billing operations; refunds, payouts, cancellations, voids and deletions ask first.",
          "entries": [
            {
              "risk_id": "stripe[live].read-catalog",
              "gate": "auto",
              "quota": 2000,
              "rate_limit": {
                "max": 120,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "stripe[live].read-billing-history",
              "gate": "auto",
              "quota": 2000,
              "rate_limit": {
                "max": 120,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "stripe[live].read-customer-pii",
              "gate": "auto",
              "quota": 500,
              "rate_limit": {
                "max": 60,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "stripe[live].read-balance",
              "gate": "auto",
              "quota": 500,
              "rate_limit": {
                "max": 60,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "stripe[live].subscription-create",
              "gate": "auto",
              "quota": 50,
              "rate_limit": {
                "max": 10,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "stripe[live].subscription-update",
              "gate": "auto",
              "quota": 100,
              "rate_limit": {
                "max": 10,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "stripe[live].subscription-cancel",
              "gate": "approval",
              "quota": 50,
              "rate_limit": {
                "max": 5,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "stripe[live].invoice-create-draft",
              "gate": "auto",
              "quota": 200,
              "rate_limit": {
                "max": 20,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "stripe[live].invoice-finalize",
              "gate": "auto",
              "quota": 100,
              "rate_limit": {
                "max": 10,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "stripe[live].invoice-void",
              "gate": "approval",
              "quota": 20,
              "rate_limit": {
                "max": 5,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "stripe[live].refund-create",
              "gate": "approval",
              "quota": 20,
              "rate_limit": {
                "max": 3,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "stripe[live].payout-create",
              "gate": "approval",
              "quota": 5,
              "rate_limit": {
                "max": 2,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "stripe[live].customer-delete",
              "gate": "approval",
              "quota": 10,
              "rate_limit": {
                "max": 2,
                "window_seconds": 60
              }
            }
          ]
        }
      ]
    },
    {
      "id": "supabase",
      "name": "Supabase",
      "version": "4.2.0",
      "status": "stable",
      "description": "Postgres, storage, auth and edge functions of Supabase projects, through the Management API.",
      "page": "https://vaubaan.com/plugins/supabase",
      "source": "https://github.com/vaubaan/gateway/tree/main/plugins/supabase",
      "health": {
        "state": "not-enough-reports"
      },
      "tools": [
        {
          "name": "supabase__get_project",
          "title": "Read the project",
          "description": "Get project metadata (region, status, database version)."
        },
        {
          "name": "supabase__get_anon_key",
          "title": "Read the API URL and the anonymous key",
          "description": "Get the project API URL (https://{ref}.supabase.co) and the anonymous (public) API key — the values client-side env vars need (NEXT_PUBLIC_SUPABASE_URL / NEXT_PUBLIC_SUPABASE_ANON_KEY). The anon key is meant to ship in browser code ; service_role is never returned."
        },
        {
          "name": "supabase__list_tables",
          "title": "List tables",
          "description": "List tables in the public schema (and optional extra schemas)."
        },
        {
          "name": "supabase__list_migrations",
          "title": "List applied migrations",
          "description": "List applied migrations (supabase_migrations.schema_migrations ledger)."
        },
        {
          "name": "supabase__execute_sql",
          "title": "Run a read-only SQL query",
          "description": "Run a read-only SQL query against the project database. Executed through the Management API read-only endpoint (POST /v1/projects/{ref}/database/query/read-only) : Postgres refuses any INSERT/UPDATE/DELETE/DDL. Use apply_migration for schema changes."
        },
        {
          "name": "supabase__get_advisors",
          "title": "Read the advisors",
          "description": "Get the project advisors (security or performance recommendations)."
        },
        {
          "name": "supabase__apply_migration",
          "title": "Apply a database migration",
          "description": "Apply a database migration. Backed by POST /v1/projects/{ref}/database/migrations. Every migration is a schema write ; statements that write rows or drop a table or a column, change RLS, privileges or extensions each add the matching risk, and SQL the gateway cannot read statement by statement (DO blocks, function, trigger or view definitions, CTEs, SET, COPY, bare SELECT) requires all of them. Migrations that write or destroy rows are refused on prod-labelled projects : keep prod migrations to plain DDL and RLS statements."
        },
        {
          "name": "supabase__list_buckets",
          "title": "List storage buckets",
          "description": "List storage buckets of the project. Backed by GET /v1/projects/{ref}/storage/buckets."
        },
        {
          "name": "supabase__get_storage_config",
          "title": "Read the storage configuration",
          "description": "Get the project-wide storage config (file size limit, allowed MIME types). Backed by GET /v1/projects/{ref}/config/storage."
        },
        {
          "name": "supabase__update_storage_config",
          "title": "Change the storage configuration",
          "description": "Update the project-wide storage config (fileSizeLimit, features.s3Protocol.enabled, features.imageTransformation.enabled). Backed by PATCH /v1/projects/{ref}/config/storage."
        },
        {
          "name": "supabase__get_auth_config",
          "title": "Read the sign-in configuration",
          "description": "Get the project auth config (JWT expiry, SMTP, password policy, external providers). Secret values (OAuth client secrets, SMTP password, hook secrets, SMS provider tokens) are never returned : the gateway replaces each by a fixed marker. Backed by GET /v1/projects/{ref}/config/auth."
        },
        {
          "name": "supabase__update_auth_config",
          "title": "Change the sign-in configuration",
          "description": "Update the project auth config (site URL, redirect allow-list, JWT/OTP expiry, password policy, mailer subjects, external OAuth toggles and client ids/secrets). Backed by PATCH /v1/projects/{ref}/config/auth. Only the fields sent are mutated."
        },
        {
          "name": "supabase__list_sso_providers",
          "title": "List SSO providers",
          "description": "List SAML SSO providers configured on the project. Backed by GET /v1/projects/{ref}/config/auth/sso/providers."
        },
        {
          "name": "supabase__get_sso_provider",
          "title": "Read an SSO provider",
          "description": "Get one SAML SSO provider by id. Backed by GET /v1/projects/{ref}/config/auth/sso/providers/{provider_id}."
        },
        {
          "name": "supabase__create_sso_provider",
          "title": "Register an SSO provider",
          "description": "Register a SAML SSO provider (enterprise SSO). Backed by POST /v1/projects/{ref}/config/auth/sso/providers. Pass metadata_url or metadata_xml."
        },
        {
          "name": "supabase__update_sso_provider",
          "title": "Change an SSO provider",
          "description": "Update a SAML SSO provider by id. Backed by PUT /v1/projects/{ref}/config/auth/sso/providers/{provider_id} (partial : only the fields sent are mutated)."
        },
        {
          "name": "supabase__delete_sso_provider",
          "title": "Delete an SSO provider",
          "description": "Delete a SAML SSO provider by id. Backed by DELETE /v1/projects/{ref}/config/auth/sso/providers/{provider_id}."
        },
        {
          "name": "supabase__list_third_party_auth",
          "title": "List third-party sign-in providers",
          "description": "List third-party JWT auth providers (Firebase, Auth0, Cognito, generic OIDC issuers). Backed by GET /v1/projects/{ref}/config/auth/third-party-auth."
        },
        {
          "name": "supabase__get_third_party_auth",
          "title": "Read a third-party sign-in provider",
          "description": "Get one third-party JWT auth provider by id. Backed by GET /v1/projects/{ref}/config/auth/third-party-auth/{tpa_id}."
        },
        {
          "name": "supabase__create_third_party_auth",
          "title": "Register a third-party sign-in provider",
          "description": "Register a third-party JWT auth provider (federated JWT issuer). Backed by POST /v1/projects/{ref}/config/auth/third-party-auth."
        },
        {
          "name": "supabase__delete_third_party_auth",
          "title": "Delete a third-party sign-in provider",
          "description": "Delete a third-party JWT auth provider by id. Backed by DELETE /v1/projects/{ref}/config/auth/third-party-auth/{tpa_id}."
        },
        {
          "name": "supabase__list_functions",
          "title": "List edge functions",
          "description": "List edge functions of the project. Backed by GET /v1/projects/{ref}/functions."
        },
        {
          "name": "supabase__get_function",
          "title": "Read an edge function",
          "description": "Get edge function metadata (verify_jwt, status, version). Backed by GET /v1/projects/{ref}/functions/{slug}."
        },
        {
          "name": "supabase__get_function_body",
          "title": "Read the source of an edge function",
          "description": "Get the source of the currently deployed edge function. Backed by GET /v1/projects/{ref}/functions/{slug}/body."
        },
        {
          "name": "supabase__create_function",
          "title": "Create an edge function",
          "description": "Create an edge function (metadata and initial body). Backed by POST /v1/projects/{ref}/functions. Prefer deploy_function for multi-file bundles."
        },
        {
          "name": "supabase__update_function",
          "title": "Change an edge function",
          "description": "Update edge function metadata or source (PATCH semantics). Backed by PATCH /v1/projects/{ref}/functions/{slug}."
        },
        {
          "name": "supabase__delete_function",
          "title": "Delete an edge function",
          "description": "Delete an edge function. Backed by DELETE /v1/projects/{ref}/functions/{slug}. Irreversible unless the source is in git."
        },
        {
          "name": "supabase__deploy_function",
          "title": "Deploy an edge function",
          "description": "Deploy an edge function from a multi-file bundle (entrypoint and assets). Backed by POST /v1/projects/{ref}/functions/deploy?slug={slug}. `files` is an array of `{ name, content }`."
        },
        {
          "name": "supabase__list_function_secrets",
          "title": "List the names of function secrets",
          "description": "List the project secret names available to edge functions, with their update date. Values are never returned : the gateway replaces each by a fixed marker. Backed by GET /v1/projects/{ref}/secrets."
        },
        {
          "name": "supabase__create_function_secrets",
          "title": "Set function secrets",
          "description": "Set or overwrite project secrets exposed to edge functions. Backed by POST /v1/projects/{ref}/secrets with a `[{ name, value }]` body."
        },
        {
          "name": "supabase__delete_function_secrets",
          "title": "Delete function secrets",
          "description": "Delete project secrets by name. Backed by DELETE /v1/projects/{ref}/secrets with the names in the JSON body."
        },
        {
          "name": "supabase__list_users",
          "title": "List users",
          "description": "List project users (GoTrue admin). The gateway fetches service_role server-side ; the key never leaves it. Backed by GET {ref}.supabase.co/auth/v1/admin/users."
        },
        {
          "name": "supabase__get_user",
          "title": "Read a user",
          "description": "Get one project user by id. Backed by GET {ref}.supabase.co/auth/v1/admin/users/{user_id}."
        },
        {
          "name": "supabase__create_user",
          "title": "Create a user",
          "description": "Create a project user. Backed by POST {ref}.supabase.co/auth/v1/admin/users. Fields : email, password, email_confirm, phone, phone_confirm, user_metadata, app_metadata, ban_duration."
        },
        {
          "name": "supabase__update_user",
          "title": "Change a user",
          "description": "Update a project user (fields sent overwrite). Backed by PUT {ref}.supabase.co/auth/v1/admin/users/{user_id}."
        },
        {
          "name": "supabase__delete_user",
          "title": "Delete a user",
          "description": "Delete a project user by id. Backed by DELETE {ref}.supabase.co/auth/v1/admin/users/{user_id}. Irreversible ; cascades on owned rows."
        },
        {
          "name": "supabase__generate_link",
          "title": "Generate a one-time sign-in link",
          "description": "Generate a one-time auth link (signup, invite, magiclink, recovery, email_change_current, email_change_new). Backed by POST {ref}.supabase.co/auth/v1/admin/generate_link. The response carries `action_link`."
        },
        {
          "name": "supabase__create_bucket",
          "title": "Create a storage bucket",
          "description": "Create a storage bucket. Backed by POST {ref}.supabase.co/storage/v1/bucket. `public: true` serves objects by URL ; otherwise readers need a signed URL."
        },
        {
          "name": "supabase__update_bucket",
          "title": "Change a storage bucket",
          "description": "Update a storage bucket (public flag, size limit, MIME list). Backed by PUT {ref}.supabase.co/storage/v1/bucket/{id}."
        },
        {
          "name": "supabase__delete_bucket",
          "title": "Delete a storage bucket",
          "description": "Delete a storage bucket. Backed by DELETE {ref}.supabase.co/storage/v1/bucket/{id}. The bucket must be empty."
        },
        {
          "name": "supabase__list_objects",
          "title": "List the objects of a bucket",
          "description": "List objects of a bucket (prefix filter and pagination). Backed by POST {ref}.supabase.co/storage/v1/object/list/{bucket}."
        },
        {
          "name": "supabase__upload_object",
          "title": "Upload an object",
          "description": "Upload an object to a bucket. The body is sent base64-encoded and decoded gateway-side. Backed by POST {ref}.supabase.co/storage/v1/object/{bucket}/{path}."
        },
        {
          "name": "supabase__delete_object",
          "title": "Delete an object",
          "description": "Delete one object. Backed by DELETE {ref}.supabase.co/storage/v1/object/{bucket}/{path}."
        },
        {
          "name": "supabase__create_signed_url",
          "title": "Create a signed URL for an object",
          "description": "Issue a signed URL granting time-limited read access to a private object. Backed by POST {ref}.supabase.co/storage/v1/object/sign/{bucket}/{path}. The response carries `signedURL`."
        }
      ],
      "risks": [
        {
          "id": "supabase.read-data",
          "label": "Read data and schema",
          "reason": "SELECT and schema inspection, including `execute_sql`, which runs on the Management API read-only endpoint. Can leak PII held in application tables. Deliberately broad : every Supabase read fires it, and the more specific read permissions (`read-managed-schemas`, `auth.read-users`, `storage.signed-url-read`, `functions.read`) co-fire to harden the policy. Alone, it lets `execute_sql` run only a plain query that names no schema Supabase manages.",
          "severity": "low",
          "kind": "read",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "supabase.read-managed-schemas",
          "label": "Read the schemas Supabase manages, through SQL",
          "reason": "`execute_sql` on what Supabase keeps for itself : `auth` (users, identities, sessions, refresh tokens), `vault` (secrets and their decrypted view), `storage` internals, `pgsodium` keys, `net` and `cron` queues, and the catalog relations that hold password hashes or other sessions' queries. Decided by what the SQL names, not by what the database role may read. Also touched by any query the classifier cannot read with confidence : a statement that is not a plain query, a function outside the list it knows to be inert, dollar quoting, prefixed strings. No read-only preset carries it.",
          "severity": "medium",
          "kind": "read",
          "acceptable_in_envs": [
            "preview",
            "staging",
            "prod"
          ],
          "fallback": false
        },
        {
          "id": "supabase.row-write-one",
          "label": "Write rows one by one (bounded target) — deprecated, no tool touches it",
          "reason": "Deprecated : matches no call. No tool of this plugin writes rows one by one (`execute_sql` runs on the read-only endpoint, and a migration that writes rows is a bulk write). Holding it grants nothing ; it has no successor. Kept so the id stays resolvable in the accesses and missions that name it.",
          "severity": "low",
          "kind": "write",
          "acceptable_in_envs": [
            "preview",
            "staging",
            "prod"
          ],
          "fallback": false
        },
        {
          "id": "supabase.bulk-row-write",
          "label": "Bulk write on a table",
          "reason": "Mass INSERT, UPDATE/DELETE without or with a wide WHERE, TRUNCATE, DROP TABLE or COLUMN inside a migration. No automatic rollback, high blast. Not acceptable on prod : a call touching it there is refused.",
          "severity": "high",
          "kind": "write",
          "acceptable_in_envs": [
            "preview",
            "staging"
          ],
          "fallback": false
        },
        {
          "id": "supabase.migration-write",
          "label": "Apply a migration (DDL)",
          "reason": "CREATE/ALTER TABLE or INDEX. Costly to revert (manual down migration) and can break the applications that read the schema. Touched by every migration ; a migration that also writes rows, drops a table or a column, changes RLS, privileges or extensions needs the matching permission too, and one the classifier cannot read (function body, dynamic SQL) touches all of them. Matches the schema/table-level write strictly (no row segment), so a row write never fires it by accident.",
          "severity": "medium",
          "kind": "write",
          "acceptable_in_envs": [
            "preview",
            "staging",
            "prod"
          ],
          "fallback": false
        },
        {
          "id": "supabase.policy-write",
          "label": "Change RLS (CREATE/ALTER/DROP POLICY)",
          "reason": "Critical posture shift : opens or closes data access for every role. One mistake is a public data leak.",
          "severity": "high",
          "kind": "write",
          "acceptable_in_envs": [
            "preview",
            "staging",
            "prod"
          ],
          "fallback": false
        },
        {
          "id": "supabase.db-role-write",
          "label": "Create or modify Postgres roles",
          "reason": "Credential posture : CREATE / ALTER / DROP ROLE, GRANT, REVOKE, default privileges. Reached through `apply_migration`.",
          "severity": "medium",
          "kind": "write",
          "acceptable_in_envs": [
            "preview",
            "staging",
            "prod"
          ],
          "fallback": false
        },
        {
          "id": "supabase.extension-write",
          "label": "Enable or disable a Postgres extension",
          "reason": "Shifts the available SQL surface. Some extensions (`pg_net`, `http`) open outbound exfiltration. Reached through `apply_migration`.",
          "severity": "medium",
          "kind": "write",
          "acceptable_in_envs": [
            "preview",
            "staging",
            "prod"
          ],
          "fallback": false
        },
        {
          "id": "supabase.storage.object-read",
          "label": "List or download storage objects",
          "reason": "Reads user files, which may be PII (identity documents, photos, exports). Served through the service key regardless of bucket ACL.",
          "severity": "low",
          "kind": "read",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "supabase.storage.object-write-one",
          "label": "Upload or delete one object",
          "reason": "Mutation of one precise path. Reversible when the agent keeps a copy before overwriting.",
          "severity": "low",
          "kind": "write",
          "acceptable_in_envs": [
            "preview",
            "staging",
            "prod"
          ],
          "fallback": false
        },
        {
          "id": "supabase.storage.bulk-object-write",
          "label": "Bulk upload or delete of objects",
          "reason": "Prefix-wide or glob deletes, bucket cascade. Supabase Storage has no versioning by default : a bulk delete is irreversible. Structurally not grantable on prod.",
          "severity": "critical",
          "kind": "destructive",
          "acceptable_in_envs": [
            "preview",
            "staging"
          ],
          "fallback": false
        },
        {
          "id": "supabase.storage.signed-url-read",
          "label": "Issue a signed URL",
          "reason": "A read on the bucket that may exfiltrate the object to an external recipient through a temporary signed URL. Distinct from a plain list/download : the recipient is chosen by the agent.",
          "severity": "medium",
          "kind": "read",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "supabase.bucket-write",
          "label": "Create, modify or delete a bucket",
          "reason": "Posture shift : a public bucket leaks misclassified private objects ; a delete cascades irreversibly over the objects. Matches the bucket-level write strictly (no object segment).",
          "severity": "critical",
          "kind": "destructive",
          "acceptable_in_envs": [
            "preview",
            "staging",
            "prod"
          ],
          "fallback": false
        },
        {
          "id": "supabase.storage-config-write",
          "label": "Change the project storage configuration",
          "reason": "Quotas, transformations, CDN. Affects the whole project and every application consuming storage.",
          "severity": "low",
          "kind": "write",
          "acceptable_in_envs": [
            "preview",
            "staging",
            "prod"
          ],
          "fallback": false
        },
        {
          "id": "supabase.auth.read-users",
          "label": "List or read users",
          "reason": "PII : emails, providers, last sign-in, user metadata ; the volume can be large. Through the auth admin API only : a SELECT on `auth.users` through `execute_sql` is `read-managed-schemas`.",
          "severity": "medium",
          "kind": "read",
          "acceptable_in_envs": [
            "preview",
            "staging",
            "prod"
          ],
          "fallback": false
        },
        {
          "id": "supabase.auth.user-write-one",
          "label": "Create, modify or delete one user",
          "reason": "Affects one identity. Reversible (re-create) except delete, which cascades on the rows the user owns (FK ON DELETE CASCADE).",
          "severity": "medium",
          "kind": "write",
          "acceptable_in_envs": [
            "preview",
            "staging",
            "prod"
          ],
          "fallback": false
        },
        {
          "id": "supabase.auth.bulk-user-write",
          "label": "Bulk user manipulation",
          "reason": "Mass creation or deletion. Risk of locking real users out. Structurally not grantable on prod.",
          "severity": "critical",
          "kind": "destructive",
          "acceptable_in_envs": [
            "preview",
            "staging"
          ],
          "fallback": false
        },
        {
          "id": "supabase.auth.generate-link",
          "label": "Generate a magic link, recovery or invite",
          "reason": "The returned URL signs in as that user. If it leaks (logs, audit, wrong recipient), account takeover.",
          "severity": "low",
          "kind": "write",
          "acceptable_in_envs": [
            "preview",
            "staging",
            "prod"
          ],
          "fallback": false
        },
        {
          "id": "supabase.sso-provider-write",
          "label": "Configure a SAML / OIDC identity provider",
          "reason": "Federated identity posture. A wrong configuration lets anyone with an account on that IdP sign in.",
          "severity": "medium",
          "kind": "write",
          "acceptable_in_envs": [
            "preview",
            "staging",
            "prod"
          ],
          "fallback": false
        },
        {
          "id": "supabase.third-party-auth-write",
          "label": "Configure a third-party auth provider (Firebase, Auth0, …)",
          "reason": "Same as SSO : identity posture, federates an external JWT issuer for authentication.",
          "severity": "medium",
          "kind": "write",
          "acceptable_in_envs": [
            "preview",
            "staging",
            "prod"
          ],
          "fallback": false
        },
        {
          "id": "supabase.auth-config-write",
          "label": "Change the project auth configuration",
          "reason": "JWT secret, expiries, redirect allow-list, signup policy. The smallest mistake opens or breaks authentication for everyone.",
          "severity": "high",
          "kind": "write",
          "acceptable_in_envs": [
            "preview",
            "staging",
            "prod"
          ],
          "fallback": false
        },
        {
          "id": "supabase.functions.read",
          "label": "List or read Edge Functions (metadata and body)",
          "reason": "The body may contain inline secrets when the author hard-coded a key (a frequent anti-pattern).",
          "severity": "low",
          "kind": "read",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "supabase.functions.body-write",
          "label": "Create, modify or delete the code of an Edge Function",
          "reason": "Changing the code injects custom behaviour into every invocation. Reversible when the source is in git. Bounded to `edge-function[*]` strictly so child writes such as `function-deployment` do not fire it.",
          "severity": "medium",
          "kind": "write",
          "acceptable_in_envs": [
            "preview",
            "staging",
            "prod"
          ],
          "fallback": false
        },
        {
          "id": "supabase.functions.deploy",
          "label": "Deploy a new version of an Edge Function",
          "reason": "Switches the runtime to the new version. On prod, an immediate change of public behaviour.",
          "severity": "high",
          "kind": "write",
          "acceptable_in_envs": [
            "preview",
            "staging",
            "prod"
          ],
          "fallback": false
        },
        {
          "id": "supabase.function-secret-write",
          "label": "Create, modify or delete a function secret",
          "reason": "Secrets consumed by Edge Functions at runtime. A delete can break the function immediately ; a create or update can poison it.",
          "severity": "medium",
          "kind": "write",
          "acceptable_in_envs": [
            "preview",
            "staging",
            "prod"
          ],
          "fallback": false
        },
        {
          "id": "supabase.uncategorized-write",
          "label": "Uncategorized Supabase write",
          "reason": "Catch-all for writes without a dedicated entry. A mission holding it for an environment allows every uncategorized write of the service there ; its limits and “ask me first” bound it. A tool needing only this permission is a catalog bug.",
          "severity": "medium",
          "kind": "write",
          "acceptable_in_envs": [
            "preview",
            "staging",
            "prod"
          ],
          "fallback": true
        }
      ],
      "presets": [
        {
          "id": "read-only-analyst",
          "label": "Read-only analyst",
          "description": "Query application data and schema, list buckets and objects, on every selected project. No write, no user listing, no SQL on the schemas Supabase manages (auth, vault, storage).",
          "entries": [
            {
              "risk_id": "supabase.read-data",
              "gate": "auto",
              "quota": 5000,
              "rate_limit": {
                "max": 120,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "supabase.storage.object-read",
              "gate": "auto",
              "quota": 1000,
              "rate_limit": {
                "max": 60,
                "window_seconds": 60
              }
            }
          ]
        },
        {
          "id": "staging-developer",
          "label": "Staging developer",
          "description": "One mission per application. Day-to-day development on staging-labelled projects — migrations of any kind, RLS, buckets and objects, users one by one, edge functions and their secrets. Reads data, schema, objects and functions on every selected project. No auth, storage or SSO configuration, no signed URL, nothing written outside staging.",
          "entries": [
            {
              "risk_id": "supabase.read-data",
              "gate": "auto",
              "quota": 10000,
              "rate_limit": {
                "max": 120,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "supabase.storage.object-read",
              "gate": "auto",
              "quota": 2000,
              "rate_limit": {
                "max": 60,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "supabase.functions.read",
              "gate": "auto",
              "quota": 1000,
              "rate_limit": {
                "max": 60,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "supabase[staging].auth.read-users",
              "gate": "auto",
              "quota": 500,
              "rate_limit": {
                "max": 30,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "supabase[staging].bulk-row-write",
              "gate": "auto",
              "quota": 200,
              "rate_limit": {
                "max": 20,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "supabase[staging].migration-write",
              "gate": "auto",
              "quota": 200,
              "rate_limit": {
                "max": 10,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "supabase[staging].policy-write",
              "gate": "auto",
              "quota": 100,
              "rate_limit": {
                "max": 10,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "supabase[staging].db-role-write",
              "gate": "auto",
              "quota": 100,
              "rate_limit": {
                "max": 10,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "supabase[staging].extension-write",
              "gate": "auto",
              "quota": 100,
              "rate_limit": {
                "max": 10,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "supabase[staging].bucket-write",
              "gate": "auto",
              "quota": 50,
              "rate_limit": {
                "max": 10,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "supabase[staging].storage.object-write-one",
              "gate": "auto",
              "quota": 1000,
              "rate_limit": {
                "max": 60,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "supabase[staging].storage.bulk-object-write",
              "gate": "auto",
              "quota": 50,
              "rate_limit": {
                "max": 10,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "supabase[staging].auth.user-write-one",
              "gate": "auto",
              "quota": 500,
              "rate_limit": {
                "max": 30,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "supabase[staging].auth.generate-link",
              "gate": "auto",
              "quota": 100,
              "rate_limit": {
                "max": 10,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "supabase[staging].functions.body-write",
              "gate": "auto",
              "quota": 500,
              "rate_limit": {
                "max": 30,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "supabase[staging].functions.deploy",
              "gate": "auto",
              "quota": 200,
              "rate_limit": {
                "max": 10,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "supabase[staging].function-secret-write",
              "gate": "auto",
              "quota": 50,
              "rate_limit": {
                "max": 10,
                "window_seconds": 60
              }
            }
          ]
        },
        {
          "id": "migration-operator",
          "label": "Migration operator",
          "description": "Apply migrations. Any migration on staging-labelled projects. On prod, only plain schema changes (create or alter tables and indexes) and RLS changes, each asking first; a prod migration that writes rows, drops a table or a column, grants privileges, enables an extension or defines a function is refused.",
          "entries": [
            {
              "risk_id": "supabase.read-data",
              "gate": "auto",
              "quota": 2000,
              "rate_limit": {
                "max": 60,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "supabase[staging].migration-write",
              "gate": "auto",
              "quota": 100,
              "rate_limit": {
                "max": 10,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "supabase[staging].bulk-row-write",
              "gate": "auto",
              "quota": 50,
              "rate_limit": {
                "max": 10,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "supabase[staging].policy-write",
              "gate": "auto",
              "quota": 50,
              "rate_limit": {
                "max": 10,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "supabase[staging].db-role-write",
              "gate": "auto",
              "quota": 50,
              "rate_limit": {
                "max": 10,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "supabase[staging].extension-write",
              "gate": "auto",
              "quota": 50,
              "rate_limit": {
                "max": 10,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "supabase[prod].migration-write",
              "gate": "approval",
              "quota": 20,
              "rate_limit": {
                "max": 2,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "supabase[prod].policy-write",
              "gate": "approval",
              "quota": 10,
              "rate_limit": {
                "max": 2,
                "window_seconds": 60
              }
            }
          ]
        }
      ]
    },
    {
      "id": "upstash-redis",
      "name": "Upstash Redis",
      "version": "3.0.0",
      "status": "stable",
      "description": "Serverless Redis plane — databases through the Management API, key-value data through each database REST endpoint.",
      "page": "https://vaubaan.com/plugins/upstash-redis",
      "source": "https://github.com/vaubaan/gateway/tree/main/plugins/upstash-redis",
      "health": {
        "state": "not-enough-reports"
      },
      "tools": [
        {
          "name": "upstash_redis__list_databases",
          "title": "List databases",
          "description": "List all Redis databases in the Upstash account. Backed by GET /v2/redis/databases."
        },
        {
          "name": "upstash_redis__get_database",
          "title": "Read a database",
          "description": "Get details of a specific Redis database (name, endpoint, region, state, etc.). Backed by GET /v2/redis/database/{id}."
        },
        {
          "name": "upstash_redis__create_database",
          "title": "Create a database",
          "description": "Create a new Redis database. Backed by POST /v2/redis/database."
        },
        {
          "name": "upstash_redis__delete_database",
          "title": "Delete a database",
          "description": "Delete a Redis database permanently. Backed by DELETE /v2/redis/database/{id}."
        },
        {
          "name": "upstash_redis__rename_database",
          "title": "Rename a database",
          "description": "Rename a Redis database. Backed by POST /v2/redis/rename/{id}."
        },
        {
          "name": "upstash_redis__get_database_stats",
          "title": "Read the usage of a database",
          "description": "Get usage statistics for a Redis database. Backed by GET /v2/redis/stats/{id}."
        },
        {
          "name": "upstash_redis__execute",
          "title": "Run a Redis command",
          "description": "Execute one generic Redis command. The command is an array of strings (e.g. [\"SET\", \"key\", \"value\"]). Use this for commands not covered by specific tools. The command is authorized as what it does: a read, a write, a deletion (DEL, UNLINK, EXPIRE with a TTL of 0…), a flush, or — for scripts, administration and any command the gateway does not know — arbitrary execution."
        },
        {
          "name": "upstash_redis__get",
          "title": "Read a key",
          "description": "GET a key from Redis. Returns the value or null if the key does not exist."
        },
        {
          "name": "upstash_redis__set",
          "title": "Set a key",
          "description": "SET a key in Redis with optional TTL and conditional flags."
        },
        {
          "name": "upstash_redis__del",
          "title": "Delete keys",
          "description": "DEL one or more keys from Redis."
        },
        {
          "name": "upstash_redis__keys",
          "title": "Find keys by pattern",
          "description": "KEYS — find all keys matching a glob-style pattern."
        },
        {
          "name": "upstash_redis__scan",
          "title": "Scan the keys",
          "description": "SCAN — incrementally iterate through the key space. Returns a cursor and a list of keys."
        },
        {
          "name": "upstash_redis__ttl",
          "title": "Read the time to live of a key",
          "description": "TTL — get the time-to-live of a key in seconds. Returns -1 if no TTL, -2 if key does not exist."
        },
        {
          "name": "upstash_redis__expire",
          "title": "Set the expiry of a key",
          "description": "EXPIRE — set a timeout on a key in seconds. The key is removed when it elapses (at once for a TTL of 0 or less), so the call is authorized as a deletion, whatever the TTL."
        },
        {
          "name": "upstash_redis__hset",
          "title": "Set fields of a hash",
          "description": "HSET — set one or more field-value pairs in a hash."
        },
        {
          "name": "upstash_redis__hgetall",
          "title": "Read a hash",
          "description": "HGETALL — get all field-value pairs from a hash."
        },
        {
          "name": "upstash_redis__lpush",
          "title": "Prepend values to a list",
          "description": "LPUSH — prepend one or more values to a list."
        },
        {
          "name": "upstash_redis__lrange",
          "title": "Read a range of a list",
          "description": "LRANGE — get a range of elements from a list."
        },
        {
          "name": "upstash_redis__sadd",
          "title": "Add members to a set",
          "description": "SADD — add one or more members to a set."
        },
        {
          "name": "upstash_redis__smembers",
          "title": "Read the members of a set",
          "description": "SMEMBERS — get all members of a set."
        },
        {
          "name": "upstash_redis__dbsize",
          "title": "Count the keys of a database",
          "description": "DBSIZE — get the number of keys in the database."
        }
      ],
      "risks": [
        {
          "id": "upstash-redis.read-database-metadata",
          "label": "Read database list / details / stats (Management API reads)",
          "reason": "Inventory of databases, their configuration, endpoints, and usage statistics. Surface of attack is negligible : metadata only, no data or credential leaks (`execute` strips the REST tokens from the Management API answers). This permission is the always-on baseline ; every read of the service needs it.",
          "severity": "low",
          "kind": "read",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "upstash-redis.read-data",
          "label": "Read Redis data (GET, KEYS, SCAN, HGETALL, LRANGE, SMEMBERS, TTL, DBSIZE)",
          "reason": "Read operations on the key-value data stored in a database. May expose application secrets if stored in Redis (session tokens, cached credentials). Standard posture for a coding agent in debug mode.",
          "severity": "low",
          "kind": "read",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "upstash-redis.write-data",
          "label": "Write Redis data (SET, HSET, LPUSH, SADD — without a time to live)",
          "reason": "Create, overwrite or modify individual keys, through the dedicated tools or the generic `execute` tool. An overwrite replaces the previous value, which is lost unless it is kept elsewhere ; a RENAME replaces the destination key. Affects application state if the app reads from Redis at runtime. The deletion commands (DEL, UNLINK, the POP and REM families), every command that sets a time to live (EXPIRE, SETEX, SET … EX) or moves data out of a key (RENAME, LMOVE, SMOVE), FLUSH and arbitrary commands need it next to their own, more severe permission, so a mission holding `write-data` alone cannot run them : it can write and overwrite values that stay, nothing else.",
          "severity": "low",
          "kind": "write",
          "acceptable_in_envs": [
            "development",
            "staging",
            "prod"
          ],
          "fallback": false
        },
        {
          "id": "upstash-redis.delete-data",
          "label": "Delete keys, fields, members or entries — now, or by a time to live or a move (DEL, UNLINK, HDEL, LPOP, EXPIRE, SET … EX, RENAME)",
          "reason": "Targeted removal of data : whole keys (DEL, UNLINK, GETDEL), parts of a value (HDEL, LPOP, SREM, ZREM, XTRIM…), any time to live given to a key or a field (EXPIRE, PEXPIRE, SETEX, SET with EX or PX, the absolute expiries) — the data is gone when it elapses, and one second is a deletion in all but name —, and the moves (RENAME, LMOVE, SMOVE, RPOPLPUSH), which take a key or an element out of its source. Through the dedicated `del`, `expire` and `set` tools or the generic `execute` tool. Costly to reverse if the data is not backed up elsewhere. May break application state. A cache write with a time to live therefore needs this permission next to `write-data`.",
          "severity": "low",
          "kind": "write",
          "acceptable_in_envs": [
            "development",
            "staging",
            "prod"
          ],
          "fallback": false
        },
        {
          "id": "upstash-redis.flush-database",
          "label": "FLUSHDB / FLUSHALL — mass data deletion via the execute tool",
          "reason": "FLUSHDB / FLUSHALL wipes all keys in the database. Irreversible without a backup. Only reachable via the generic `execute` tool (not exposed as a dedicated tool). Presets ask first outside development.",
          "severity": "high",
          "kind": "destructive",
          "acceptable_in_envs": [
            "development",
            "staging",
            "prod"
          ],
          "fallback": false
        },
        {
          "id": "upstash-redis.execute-arbitrary",
          "label": "Generic execute — scripts, administration and any uncategorized Redis command",
          "reason": "The `execute` tool can run any Redis command. A command that is not a known read, write, deletion or flush falls into this catch-all : scripts and functions (EVAL, FCALL), server administration (CONFIG, ACL), transaction control, and any command the gateway does not know or cannot read. A script can delete or flush the database, so holding this permission allows everything a Redis command can do. Presets ask first outside development.",
          "severity": "medium",
          "kind": "write",
          "acceptable_in_envs": [
            "development",
            "staging",
            "prod"
          ],
          "fallback": false
        },
        {
          "id": "upstash-redis.create-database",
          "label": "Create a new database (Management API)",
          "reason": "Creates a new Redis database in the Upstash account. Reversible (can delete it). May incur costs if the account has a paid plan. Account-rooted : the new database is not a mission resource yet.",
          "severity": "medium",
          "kind": "write",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "upstash-redis.delete-database",
          "label": "Delete an entire database (Management API)",
          "reason": "Permanently deletes a Redis database and all its data. Irreversible without a backup. Equivalent to dropping a production data store. Presets ask first in every environment.",
          "severity": "high",
          "kind": "destructive",
          "acceptable_in_envs": [
            "development",
            "staging",
            "prod"
          ],
          "fallback": false
        },
        {
          "id": "upstash-redis.modify-database",
          "label": "Rename database (Management API)",
          "reason": "Modifies database metadata (currently: rename). Rename can break any downstream reference that uses the old name. Classified as credentials touch because it affects the database identity. Presets ask first in every environment (configuration change).",
          "severity": "medium",
          "kind": "write",
          "acceptable_in_envs": [
            "development",
            "staging",
            "prod"
          ],
          "fallback": false
        },
        {
          "id": "upstash-redis.read-stats",
          "label": "Read usage statistics",
          "reason": "Read-only access to database usage statistics (commands processed, memory usage, throughput). No data or credential exposure.",
          "severity": "low",
          "kind": "read",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "upstash-redis.uncategorized-write",
          "label": "Uncategorized Upstash Redis write",
          "reason": "Catch-all for writes that have not been explicitly categorised. If observed in production, signals that a tool was added without a matching catalog entry — the mission creator must opt in explicitly (no implicit accept). Never carried by a preset.",
          "severity": "medium",
          "kind": "write",
          "acceptable_in_envs": [
            "development",
            "staging",
            "prod"
          ],
          "fallback": true
        }
      ],
      "presets": [
        {
          "id": "read-only",
          "label": "Read-only",
          "description": "Inspect databases, usage statistics and key-value data on the selected databases; no write.",
          "entries": [
            {
              "risk_id": "upstash-redis.read-database-metadata",
              "gate": "auto",
              "quota": 2000,
              "rate_limit": {
                "max": 120,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "upstash-redis.read-stats",
              "gate": "auto",
              "quota": 500,
              "rate_limit": {
                "max": 30,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "upstash-redis.read-data",
              "gate": "auto",
              "quota": 5000,
              "rate_limit": {
                "max": 120,
                "window_seconds": 60
              }
            }
          ]
        },
        {
          "id": "developer",
          "label": "Developer",
          "description": "Key-level reads and writes (with TTL) and targeted deletions on development and staging databases; no FLUSH, no arbitrary command, no database management.",
          "entries": [
            {
              "risk_id": "upstash-redis.read-database-metadata",
              "gate": "auto",
              "quota": 5000,
              "rate_limit": {
                "max": 120,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "upstash-redis.read-stats",
              "gate": "auto",
              "quota": 1000,
              "rate_limit": {
                "max": 60,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "upstash-redis.read-data",
              "gate": "auto",
              "quota": 10000,
              "rate_limit": {
                "max": 120,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "upstash-redis[development].write-data",
              "gate": "auto",
              "quota": 5000,
              "rate_limit": {
                "max": 120,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "upstash-redis[staging].write-data",
              "gate": "auto",
              "quota": 2000,
              "rate_limit": {
                "max": 60,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "upstash-redis[development].delete-data",
              "gate": "auto",
              "quota": 1000,
              "rate_limit": {
                "max": 60,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "upstash-redis[staging].delete-data",
              "gate": "auto",
              "quota": 500,
              "rate_limit": {
                "max": 30,
                "window_seconds": 60
              }
            }
          ]
        },
        {
          "id": "operator",
          "label": "Operator",
          "description": "Everything a developer can do plus production key writes, FLUSH, arbitrary commands and database management — production deletions, FLUSH and arbitrary commands outside development, every database deletion and every configuration change asking first.",
          "entries": [
            {
              "risk_id": "upstash-redis.read-database-metadata",
              "gate": "auto",
              "quota": 5000,
              "rate_limit": {
                "max": 120,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "upstash-redis.read-stats",
              "gate": "auto",
              "quota": 1000,
              "rate_limit": {
                "max": 60,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "upstash-redis.read-data",
              "gate": "auto",
              "quota": 10000,
              "rate_limit": {
                "max": 120,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "upstash-redis[development].write-data",
              "gate": "auto",
              "quota": 5000,
              "rate_limit": {
                "max": 120,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "upstash-redis[staging].write-data",
              "gate": "auto",
              "quota": 2000,
              "rate_limit": {
                "max": 60,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "upstash-redis[prod].write-data",
              "gate": "auto",
              "quota": 500,
              "rate_limit": {
                "max": 30,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "upstash-redis[development].delete-data",
              "gate": "auto",
              "quota": 1000,
              "rate_limit": {
                "max": 60,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "upstash-redis[staging].delete-data",
              "gate": "auto",
              "quota": 500,
              "rate_limit": {
                "max": 30,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "upstash-redis[prod].delete-data",
              "gate": "approval",
              "quota": 100,
              "rate_limit": {
                "max": 10,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "upstash-redis[development].flush-database",
              "gate": "auto",
              "quota": 50,
              "rate_limit": {
                "max": 5,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "upstash-redis[staging].flush-database",
              "gate": "approval",
              "quota": 20,
              "rate_limit": {
                "max": 2,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "upstash-redis[prod].flush-database",
              "gate": "approval",
              "quota": 5,
              "rate_limit": {
                "max": 1,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "upstash-redis[development].execute-arbitrary",
              "gate": "auto",
              "quota": 500,
              "rate_limit": {
                "max": 30,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "upstash-redis[staging].execute-arbitrary",
              "gate": "approval",
              "quota": 200,
              "rate_limit": {
                "max": 20,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "upstash-redis[prod].execute-arbitrary",
              "gate": "approval",
              "quota": 50,
              "rate_limit": {
                "max": 5,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "upstash-redis.create-database",
              "gate": "auto",
              "quota": 10,
              "rate_limit": {
                "max": 2,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "upstash-redis[development].delete-database",
              "gate": "approval",
              "quota": 5,
              "rate_limit": {
                "max": 1,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "upstash-redis[staging].delete-database",
              "gate": "approval",
              "quota": 5,
              "rate_limit": {
                "max": 1,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "upstash-redis[prod].delete-database",
              "gate": "approval",
              "quota": 2,
              "rate_limit": {
                "max": 1,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "upstash-redis[development].modify-database",
              "gate": "approval",
              "quota": 10,
              "rate_limit": {
                "max": 2,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "upstash-redis[staging].modify-database",
              "gate": "approval",
              "quota": 10,
              "rate_limit": {
                "max": 2,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "upstash-redis[prod].modify-database",
              "gate": "approval",
              "quota": 5,
              "rate_limit": {
                "max": 1,
                "window_seconds": 60
              }
            }
          ]
        }
      ]
    },
    {
      "id": "vercel",
      "name": "Vercel",
      "version": "3.0.0",
      "status": "stable",
      "description": "Runtime hosting plane — projects, deployments, logs, env vars, custom domains, firewall, preview comments.",
      "page": "https://vaubaan.com/plugins/vercel",
      "source": "https://github.com/vaubaan/gateway/tree/main/plugins/vercel",
      "health": {
        "state": "not-enough-reports"
      },
      "tools": [
        {
          "name": "vercel__search_vercel_documentation",
          "title": "Search the Vercel documentation",
          "description": "Search the Vercel documentation."
        },
        {
          "name": "vercel__deploy_to_vercel",
          "title": "Deploy the project",
          "description": "Deploy the current project to Vercel"
        },
        {
          "name": "vercel__list_projects",
          "title": "List projects",
          "description": "List all Vercel projects for a user (with a max of 50). Use this to help discover the Project ID of the project that the user is working on."
        },
        {
          "name": "vercel__get_project",
          "title": "Read a project",
          "description": "Get a specific project in Vercel"
        },
        {
          "name": "vercel__list_deployments",
          "title": "List deployments",
          "description": "List all deployments for a project"
        },
        {
          "name": "vercel__get_deployment",
          "title": "Read a deployment",
          "description": "Get a specific deployment by ID or URL."
        },
        {
          "name": "vercel__get_deployment_build_logs",
          "title": "Read the build logs of a deployment",
          "description": "Get the build logs of a deployment by deployment ID or URL. Can be used to investigate why a deployment failed. It can work as an infinite stream of logs or as a JSON endpoint depending on the input p"
        },
        {
          "name": "vercel__get_runtime_logs",
          "title": "Read runtime logs",
          "description": "Get runtime logs for a project or deployment. Runtime logs show application output (console.log, errors, etc.) from serverless functions and edge functions during execution. Supports filtering by envi"
        },
        {
          "name": "vercel__get_access_to_vercel_url",
          "title": "Create a link that bypasses deployment protection",
          "description": "Creates a temporary shareable link that bypasses authentication for protected Vercel deployments."
        },
        {
          "name": "vercel__web_fetch_vercel_url",
          "title": "Fetch a deployment URL",
          "description": "Fetches a Vercel deployment URL and returns the response."
        },
        {
          "name": "vercel__list_teams",
          "title": "List teams",
          "description": "List the user's teams. Use this to help discover the Team ID of the teams that the user is part of."
        },
        {
          "name": "vercel__check_domain_availability_and_price",
          "title": "Check the availability and price of a domain",
          "description": "Check if domain names are available for purchase and get pricing information"
        },
        {
          "name": "vercel__list_toolbar_threads",
          "title": "List toolbar comment threads",
          "description": "List Vercel toolbar comment threads for a team. Returns unresolved threads by default. Use this to see feedback, comments, or discussions on deployments and previews."
        },
        {
          "name": "vercel__get_toolbar_thread",
          "title": "Read a toolbar comment thread",
          "description": "Get a specific toolbar thread by ID, including all messages and context."
        },
        {
          "name": "vercel__change_toolbar_thread_resolve_status",
          "title": "Resolve or reopen a toolbar thread",
          "description": "Change the resolve status of a toolbar thread. Can be used to mark a thread as resolved or unresolve a previously resolved thread."
        },
        {
          "name": "vercel__reply_to_toolbar_thread",
          "title": "Reply to a toolbar thread",
          "description": "Add a reply message to an existing toolbar thread."
        },
        {
          "name": "vercel__edit_toolbar_message",
          "title": "Edit a toolbar message",
          "description": "Edit an existing message in a toolbar thread."
        },
        {
          "name": "vercel__add_toolbar_reaction",
          "title": "React to a toolbar message",
          "description": "Add an emoji reaction to a message in a toolbar thread."
        },
        {
          "name": "vercel__update_project_settings",
          "title": "Change the settings of a project",
          "description": "Update common project settings on Vercel (root directory, framework, build/dev/install commands, output directory, node version). Backed by PATCH /v9/projects/{projectId}."
        },
        {
          "name": "vercel__upsert_env_var",
          "title": "Set an environment variable",
          "description": "Create or update an environment variable on a Vercel project. Backed by POST /v10/projects/{projectId}/env?upsert=true. Omitting `target` applies the variable to all three environments (production included) — a fan-out the catalog treats as its own risk."
        },
        {
          "name": "vercel__list_env_vars",
          "title": "List environment variables",
          "description": "List environment variables on a Vercel project. Values are returned encrypted by default; pass `decrypt: true` to receive plaintext (a distinct, high-severity risk of the catalog). Backed by GET /v10/projects/{projectId}/env."
        },
        {
          "name": "vercel__delete_env_var",
          "title": "Delete an environment variable",
          "description": "Delete an environment variable on a Vercel project by its Vercel-issued id. The Vercel API removes the variable from every environment it lives in at once, so the gateway always classifies this call as touching all three environments. Backed by DELETE /v9/projects/{projectId}/env/{id}."
        },
        {
          "name": "vercel__list_project_domains",
          "title": "List the domains of a project",
          "description": "List custom domains attached to a Vercel project (including verification status). Backed by GET /v9/projects/{projectId}/domains."
        },
        {
          "name": "vercel__add_project_domain",
          "title": "Attach a domain to a project",
          "description": "Attach a custom domain to a Vercel project. Backed by POST /v10/projects/{projectId}/domains. If `verified: false` in the response, call `verify_project_domain` after configuring DNS."
        },
        {
          "name": "vercel__verify_project_domain",
          "title": "Re-check a domain of a project",
          "description": "Trigger Vercel re-check of a domain attachment (re-runs the verification challenge after DNS records are in place). Backed by POST /v9/projects/{projectId}/domains/{domain}/verify."
        },
        {
          "name": "vercel__remove_project_domain",
          "title": "Detach a domain from a project",
          "description": "Detach a custom domain from a Vercel project. Backed by DELETE /v9/projects/{projectId}/domains/{domain}."
        },
        {
          "name": "vercel__get_firewall_config",
          "title": "Read the firewall configuration",
          "description": "Read the active firewall (WAF) configuration for a Vercel project — returns firewallEnabled flag, custom rules, IP blocking rules, managed rulesets, and OWASP CRS settings. Backed by GET /v1/security/firewall/config/active."
        },
        {
          "name": "vercel__toggle_firewall",
          "title": "Turn the firewall on or off",
          "description": "Enable or disable the firewall (WAF) on a Vercel project. Backed by PATCH /v1/security/firewall/config."
        },
        {
          "name": "vercel__upsert_firewall_rule",
          "title": "Set a firewall rule",
          "description": "Create or update a custom firewall rule on a Vercel project. When `ruleId` is provided the existing rule is updated ; otherwise a new rule is inserted. Backed by PATCH /v1/security/firewall/config with action `rules.insert` or `rules.update`."
        },
        {
          "name": "vercel__remove_firewall_rule",
          "title": "Remove a firewall rule",
          "description": "Remove a custom firewall rule from a Vercel project by its rule id. Backed by PATCH /v1/security/firewall/config with action `rules.remove`."
        },
        {
          "name": "vercel__manage_ip_blocking",
          "title": "Change an IP blocking rule",
          "description": "Add or remove an IP-based blocking / challenge / bypass / log rule in the Vercel project firewall. Use `operation: \"insert\"` to add an IP rule, `operation: \"remove\"` to delete one by its id. Backed by PATCH /v1/security/firewall/config with action `ip.insert` or `ip.remove`."
        }
      ],
      "risks": [
        {
          "id": "vercel.read-account",
          "label": "Read account / team / project metadata",
          "reason": "Inventory of teams, projects, deployments, build/runtime logs, env-var keys (encrypted), domains, toolbar threads. Surface of attack is negligible : metadata only, no plaintext secret leaks. This permission is the always-on baseline that every other read assumes.",
          "severity": "low",
          "kind": "read",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "vercel.read-deployment",
          "label": "Read deployments + build/runtime logs",
          "reason": "Runtime logs can carry app data (PII if the app logs by accident, OWASP LLM01 surface via tokens injected into logs). Standard posture for a coding agent in debug mode but still distinct from pure metadata read.",
          "severity": "low",
          "kind": "read",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "vercel.read-env-var",
          "label": "Read env vars (encrypted values only)",
          "reason": "Listing of env-var keys with values returned encrypted by Vercel. No direct secret exfiltration possible. Discriminates from the plaintext variant which is the higher-severity write.",
          "severity": "low",
          "kind": "read",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "vercel.read-env-var-plaintext",
          "label": "Read env vars in plaintext (`decrypt=true`)",
          "reason": "`list_env_vars` with `decrypt=true` returns Stripe keys, DB URLs, JWT signing secrets etc. in the clear. Equivalent to a full credentials compromise on the project. Distinct risk class from the encrypted read.",
          "severity": "medium",
          "kind": "read",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "vercel.fetch-deployment-url",
          "label": "HTTP fetch to a Vercel deployment URL",
          "reason": "`web_fetch_vercel_url` makes an outbound HTTP request against a deployment URL. Logs an access on the target side, may consume bandwidth. Not a major security risk but worth a dedicated permission for audit-log granularity.",
          "severity": "low",
          "kind": "read",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "vercel.mint-bypass-token",
          "label": "Mint a deployment-protection bypass token",
          "reason": "Credential creation. The token bypasses SSO / Password Gate on a single deployment URL with a short TTL. Exposing the URL out-of-band bypasses the project deployment-protection.",
          "severity": "low",
          "kind": "write",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "vercel.deploy",
          "label": "Trigger a deployment",
          "reason": "Preview / development builds have no prod impact and are reversible. Production deploys are public and costly to roll back (prod alias re-pointed, CDN cache invalidated). The vector_overrides for prod reflect the higher blast and visibility.",
          "severity": "medium",
          "kind": "write",
          "acceptable_in_envs": [
            "preview",
            "development",
            "prod"
          ],
          "fallback": false
        },
        {
          "id": "vercel.toolbar-write",
          "label": "Write / edit / react on toolbar threads",
          "reason": "UI comment surface on deployment previews. Visible to the team only, reversible via delete/edit, zero effect on build / runtime / routing. Semantically a mutate but the risk is minimal.",
          "severity": "low",
          "kind": "write",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "vercel.env-var-write",
          "label": "Write / update / delete env vars",
          "reason": "Affects future builds on the targeted env. The stored value is a secret on the Vercel side. On prod a bad value breaks the service at the next deploy ; an attacker-injected value exfiltrates traffic or takes over the app. The vector_overrides for prod reflect the costly reversibility.",
          "severity": "medium",
          "kind": "write",
          "acceptable_in_envs": [
            "preview",
            "development",
            "prod"
          ],
          "fallback": false
        },
        {
          "id": "vercel.env-var-fan-out",
          "label": "Write env var on every env in one call (prod included)",
          "reason": "`upsert_env_var target=['production','preview','development']` (or target omitted = all 3 by Vercel default), and `delete_env_var` (whose upstream API removes the var from every env it lives in regardless of the Vaubaan hint), touch all 3 envs in a single tool call. Treated as its own permission so the agent who only holds `env-var-write` on preview does NOT silently escalate to prod.",
          "severity": "high",
          "kind": "write",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "vercel.project-settings-write",
          "label": "Update project settings (framework, build cmd, node version, root dir)",
          "reason": "Modifies the build pipeline for *every* future deployment of all 3 envs. A bad install / build command breaks preview AND prod at the next push. Posture-shift on the CI chain.",
          "severity": "medium",
          "kind": "write",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "vercel.firewall-read",
          "label": "Read firewall / WAF configuration",
          "reason": "Exposes the full WAF posture of a project : custom rules, IP blocking entries, managed rulesets, OWASP CRS settings, and the firewallEnabled toggle. Metadata-only (no mutations), but reveals the security policy surface — an attacker who sees the rules can craft bypasses.",
          "severity": "low",
          "kind": "read",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "vercel.firewall-write",
          "label": "Toggle the project firewall on/off",
          "reason": "Flipping the WAF off exposes the project to all unfiltered traffic. Flipping it on may block legitimate visitors if rules are misconfigured. Posture-shift affecting production traffic immediately.",
          "severity": "medium",
          "kind": "write",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "vercel.firewall-rule-write",
          "label": "Create / update / remove a custom firewall rule",
          "reason": "A bad rule can silently expose production to attacks (too permissive) or block legitimate traffic (too restrictive). Each rule is project-scoped and applies to all deployments. Posture-shift with public-facing impact on every request.",
          "severity": "medium",
          "kind": "write",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "vercel.ip-rule-write",
          "label": "Add / remove an IP blocking rule",
          "reason": "IP rules directly control which source IPs can reach the project. A wrong deny blocks paying customers ; a wrong bypass lets an attacker in. Immediate public-facing posture-shift.",
          "severity": "medium",
          "kind": "write",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "vercel.domain-write",
          "label": "Attach / verify / detach a custom domain",
          "reason": "External DNS coupling. Attach → public routing (can hijack traffic from a domain the org controls). Detach → breaks the public service if the domain is prod. Verify is idempotent but folded into the same risk for UX simplicity.",
          "severity": "high",
          "kind": "write",
          "acceptable_in_envs": null,
          "fallback": false
        },
        {
          "id": "vercel.uncategorized-write",
          "label": "Uncategorized Vercel write",
          "reason": "Catch-all for writes that have not been explicitly categorised. If observed in production, signals that a tool was added without a matching catalog entry — the mission creator must opt in explicitly (no implicit accept).",
          "severity": "medium",
          "kind": "write",
          "acceptable_in_envs": null,
          "fallback": true
        }
      ],
      "presets": [
        {
          "id": "read-only",
          "label": "Read-only",
          "description": "Inspect projects, deployments, logs, env-var keys and the firewall; no write.",
          "entries": [
            {
              "risk_id": "vercel.read-account",
              "gate": "auto",
              "quota": 2000,
              "rate_limit": {
                "max": 120,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "vercel.read-deployment",
              "gate": "auto",
              "quota": 2000,
              "rate_limit": {
                "max": 120,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "vercel.read-env-var",
              "gate": "auto",
              "quota": 500,
              "rate_limit": {
                "max": 30,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "vercel.fetch-deployment-url",
              "gate": "auto",
              "quota": 300,
              "rate_limit": {
                "max": 30,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "vercel.firewall-read",
              "gate": "auto",
              "quota": 200,
              "rate_limit": {
                "max": 30,
                "window_seconds": 60
              }
            }
          ]
        },
        {
          "id": "developer",
          "label": "Developer",
          "description": "One mission per code project — preview and development deploys, logs, preview env vars, toolbar comments; nothing that touches production configuration.",
          "entries": [
            {
              "risk_id": "vercel.read-account",
              "gate": "auto",
              "quota": 5000,
              "rate_limit": {
                "max": 120,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "vercel.read-deployment",
              "gate": "auto",
              "quota": 5000,
              "rate_limit": {
                "max": 120,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "vercel.read-env-var",
              "gate": "auto",
              "quota": 1000,
              "rate_limit": {
                "max": 60,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "vercel.fetch-deployment-url",
              "gate": "auto",
              "quota": 1000,
              "rate_limit": {
                "max": 60,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "vercel[preview].deploy",
              "gate": "auto",
              "quota": 200,
              "rate_limit": {
                "max": 10,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "vercel[development].deploy",
              "gate": "auto",
              "quota": 200,
              "rate_limit": {
                "max": 10,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "vercel[preview].env-var-write",
              "gate": "auto",
              "quota": 200,
              "rate_limit": {
                "max": 20,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "vercel[development].env-var-write",
              "gate": "auto",
              "quota": 200,
              "rate_limit": {
                "max": 20,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "vercel.toolbar-write",
              "gate": "auto",
              "quota": 500,
              "rate_limit": {
                "max": 30,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "vercel.mint-bypass-token",
              "gate": "auto",
              "quota": 50,
              "rate_limit": {
                "max": 5,
                "window_seconds": 60
              }
            }
          ]
        },
        {
          "id": "operator",
          "label": "Operator",
          "description": "Everything a developer can do, plus production deploys, production env vars, project settings, domains and firewall — production-affecting changes asking first.",
          "entries": [
            {
              "risk_id": "vercel.read-account",
              "gate": "auto",
              "quota": 5000,
              "rate_limit": {
                "max": 120,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "vercel.read-deployment",
              "gate": "auto",
              "quota": 5000,
              "rate_limit": {
                "max": 120,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "vercel.read-env-var",
              "gate": "auto",
              "quota": 1000,
              "rate_limit": {
                "max": 60,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "vercel.read-env-var-plaintext",
              "gate": "approval",
              "quota": 20,
              "rate_limit": {
                "max": 5,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "vercel.fetch-deployment-url",
              "gate": "auto",
              "quota": 1000,
              "rate_limit": {
                "max": 60,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "vercel[preview].deploy",
              "gate": "auto",
              "quota": 200,
              "rate_limit": {
                "max": 10,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "vercel[development].deploy",
              "gate": "auto",
              "quota": 200,
              "rate_limit": {
                "max": 10,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "vercel[prod].deploy",
              "gate": "approval",
              "quota": 50,
              "rate_limit": {
                "max": 5,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "vercel[preview].env-var-write",
              "gate": "auto",
              "quota": 200,
              "rate_limit": {
                "max": 20,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "vercel[development].env-var-write",
              "gate": "auto",
              "quota": 200,
              "rate_limit": {
                "max": 20,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "vercel[prod].env-var-write",
              "gate": "approval",
              "quota": 50,
              "rate_limit": {
                "max": 5,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "vercel.env-var-fan-out",
              "gate": "approval",
              "quota": 50,
              "rate_limit": {
                "max": 5,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "vercel.project-settings-write",
              "gate": "approval",
              "quota": 50,
              "rate_limit": {
                "max": 5,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "vercel.domain-write",
              "gate": "approval",
              "quota": 20,
              "rate_limit": {
                "max": 5,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "vercel.firewall-read",
              "gate": "auto",
              "quota": 500,
              "rate_limit": {
                "max": 30,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "vercel.firewall-write",
              "gate": "approval",
              "quota": 20,
              "rate_limit": {
                "max": 5,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "vercel.firewall-rule-write",
              "gate": "approval",
              "quota": 50,
              "rate_limit": {
                "max": 10,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "vercel.ip-rule-write",
              "gate": "approval",
              "quota": 100,
              "rate_limit": {
                "max": 20,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "vercel.toolbar-write",
              "gate": "auto",
              "quota": 500,
              "rate_limit": {
                "max": 30,
                "window_seconds": 60
              }
            },
            {
              "risk_id": "vercel.mint-bypass-token",
              "gate": "auto",
              "quota": 100,
              "rate_limit": {
                "max": 10,
                "window_seconds": 60
              }
            }
          ]
        }
      ]
    }
  ]
}
