---
id: "ai-api-key-unmanaged"
type: "rule"
severity: "warning"
url: "https://localhost:3000/catalogue/rules/ai-api-key-unmanaged"
---

# AI API key not managed

An AI service is called with an API key from the team's own servers, workers or clients, the key is not held in a key-management component, and the design has none.

| | |
|---|---|
| Severity | warning |
| Selects | every node of kind ai-service |
| Components in scope | 126 |

## Why

This AI vendor is reached with an API key, and nothing in the design holds or rotates that key. It ends up in environment files or code, every caller shares it, and a single leak runs up the vendor bill until someone notices. Put the key in a key-management component, or say it is held there with keyStore.

## What it checks

It looks at every node of kind ai-service, and fires when:

- all of these hold:
  - the node's `auth` is "api-key"
  - the node's `keyStore` is false
  - the node has at least one incoming edge from an app-server, worker or client
  - this does not hold:
    - the design contains a node of kind key-management

Message template:

```
AI service '{{node.id}}' is called with an API key, the key is not held in a key-management component, and the design has none.
```

## Applies to

- [AI service](https://localhost:3000/catalogue/kinds/ai-service.md) `ai-service`

126 catalogue components fall under it.

## Tripped by

Reference designs whose broken twin names this rule in its header:

- [linear-sync](https://localhost:3000/d/zeema/linear-sync)
- [llm-gateway](https://localhost:3000/d/zeema/llm-gateway)

## Source (`rule` YAML, verbatim)

```yaml
# yaml-language-server: $schema=../schema/rule.schema.json
# An AI vendor reached with an API key, called from the team's own servers, workers or clients,
# with the key not declared as held in a key-management component and no key-management node in
# the design. The key then lives in configuration or code, unrotated, and one leak bills the account.
# Tripped by: designs/reference/llm-gateway/design.broken.yaml and designs/reference/linear-sync/design.broken.yaml
schemaVersion: "1"
type: "rule"
id: "ai-api-key-unmanaged"
severity: "warning"
select:
  node:
    kind: "ai-service"
when:
  all:
    - prop: "auth"
      equals: "api-key"
    - prop: "keyStore"
      equals: false
    - hasEdge:
        direction: "in"
        peerKind: ["app-server", "worker", "client"]
    - not:
        hasNode:
          kind: "key-management"
message: "AI service '{{node.id}}' is called with an API key, the key is not held in a key-management component, and the design has none."
```

Human page: https://localhost:3000/catalogue/rules/ai-api-key-unmanaged
