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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
version: 2
updates:
- package-ecosystem: "composer"
directory: "/"
schedule:
interval: "weekly"
day: "monday"
labels:
- "type:dependencies"
- "priority:low"
commit-message:
prefix: "deps(composer):"

- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"
day: "monday"
labels:
- "type:dependencies"
- "priority:low"
commit-message:
prefix: "deps(npm):"

- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
day: "monday"
labels:
- "type:dependencies"
- "priority:low"
commit-message:
prefix: "deps(actions):"

36 changes: 36 additions & 0 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: CodeQL

on:
push:
branches: [dev, main]
pull_request:
branches: [dev, main]
schedule:
- cron: "0 6 * * 1"

jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: javascript

- name: Autobuild
uses: github/codeql-action/autobuild@v3

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
with:
category: "/language:javascript"

319 changes: 319 additions & 0 deletions docs/packages/agentic/api-keys.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,319 @@
---
title: API Keys
description: Guide to Agent API key management
updated: 2026-01-29
---

# API Key Management

Agent API keys provide authenticated access to the MCP tools and agentic services. This guide covers key creation, permissions, and security.

## Key Structure

API keys follow the format: `ak_` + 32 random alphanumeric characters.

Example: `ak_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6`

The key is only displayed once at creation. Store it securely.

## Creating Keys

### Via Admin Panel

1. Navigate to Workspace Settings > API Keys
2. Click "Create New Key"
3. Enter a descriptive name
4. Select permissions
5. Set expiration (optional)
6. Click Create
7. Copy the displayed key immediately

### Programmatically

```php
use Core\Mod\Agentic\Services\AgentApiKeyService;

$service = app(AgentApiKeyService::class);

$key = $service->create(
workspace: $workspace,
name: 'My Agent Key',
permissions: [
AgentApiKey::PERM_PLANS_READ,
AgentApiKey::PERM_PLANS_WRITE,
AgentApiKey::PERM_SESSIONS_WRITE,
],
rateLimit: 100,
expiresAt: now()->addYear()
);

// Only available once
$plainKey = $key->plainTextKey;
```

## Permissions

### Available Permissions

| Permission | Constant | Description |
|------------|----------|-------------|
| `plans.read` | `PERM_PLANS_READ` | List and view plans |
| `plans.write` | `PERM_PLANS_WRITE` | Create, update, archive plans |
| `phases.write` | `PERM_PHASES_WRITE` | Update phases, manage tasks |
| `sessions.read` | `PERM_SESSIONS_READ` | List and view sessions |
| `sessions.write` | `PERM_SESSIONS_WRITE` | Start, update, end sessions |
| `tools.read` | `PERM_TOOLS_READ` | View tool analytics |
| `templates.read` | `PERM_TEMPLATES_READ` | List and view templates |
| `templates.instantiate` | `PERM_TEMPLATES_INSTANTIATE` | Create plans from templates |
| `notify:read` | `PERM_NOTIFY_READ` | List push campaigns |
| `notify:write` | `PERM_NOTIFY_WRITE` | Create/update campaigns |
| `notify:send` | `PERM_NOTIFY_SEND` | Send notifications |

### Permission Checking

```php
// Single permission
$key->hasPermission('plans.write');

// Any of several
$key->hasAnyPermission(['plans.read', 'sessions.read']);

// All required
$key->hasAllPermissions(['plans.write', 'phases.write']);
```

### Updating Permissions

```php
$service->updatePermissions($key, [
AgentApiKey::PERM_PLANS_READ,
AgentApiKey::PERM_SESSIONS_READ,
]);
```

## Rate Limiting

### Configuration

Each key has a configurable rate limit (requests per minute):

```php
$key = $service->create(
workspace: $workspace,
name: 'Limited Key',
permissions: [...],
rateLimit: 50 // 50 requests/minute
);

// Update later
$service->updateRateLimit($key, 100);
```

### Checking Status

```php
$status = $service->getRateLimitStatus($key);
// Returns:
// [
// 'limit' => 100,
// 'remaining' => 85,
// 'reset_in_seconds' => 45,
// 'used' => 15
// ]
```

### Response Headers

Rate limit info is included in API responses:

```
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 85
X-RateLimit-Reset: 45
```

When rate limited (HTTP 429):
```
Retry-After: 45
```

## IP Restrictions

Keys can be restricted to specific IP addresses or ranges.

### Enabling Restrictions

```php
// Enable with whitelist
$service->enableIpRestrictions($key, [
'192.168.1.0/24', // CIDR range
'10.0.0.5', // Single IPv4
'2001:db8::1', // Single IPv6
'2001:db8::/32', // IPv6 CIDR
]);

// Disable restrictions
$service->disableIpRestrictions($key);
```

### Managing Whitelist

```php
// Add single entry
$key->addToIpWhitelist('192.168.2.0/24');

// Remove entry
$key->removeFromIpWhitelist('192.168.1.0/24');

// Replace entire list
$key->updateIpWhitelist([
'10.0.0.0/8',
'172.16.0.0/12',
]);
```

### Parsing Input

For user-entered whitelists:

```php
$result = $service->parseIpWhitelistInput("
192.168.1.1
192.168.2.0/24
# This is a comment
invalid-ip
");

// Result:
// [
// 'entries' => ['192.168.1.1', '192.168.2.0/24'],
// 'errors' => ['invalid-ip: Invalid IP address']
// ]
```

## Key Lifecycle

### Expiration

```php
// Set expiration on create
$key = $service->create(
...
expiresAt: now()->addMonths(6)
);

// Extend expiration
$service->extendExpiry($key, now()->addYear());

// Remove expiration (never expires)
$service->removeExpiry($key);
```

### Revocation

```php
// Immediately revoke
$service->revoke($key);

// Check status
$key->isRevoked(); // true
$key->isActive(); // false
```

### Status Helpers

```php
$key->isActive(); // Not revoked, not expired
$key->isRevoked(); // Has been revoked
$key->isExpired(); // Past expiration date
$key->getStatusLabel(); // "Active", "Revoked", or "Expired"
```

## Authentication

### Making Requests

Include the API key as a Bearer token:

```bash
curl -H "Authorization: Bearer ak_your_key_here" \
https://mcp.host.uk.com/api/agent/plans
```

### Authentication Flow

1. Middleware extracts Bearer token
2. Key looked up by SHA-256 hash
3. Status checked (revoked, expired)
4. IP validated if restrictions enabled
5. Permissions checked against required scopes
6. Rate limit checked and incremented
7. Usage recorded (count, timestamp, IP)

### Error Responses

| HTTP Code | Error | Description |
|-----------|-------|-------------|
| 401 | `unauthorised` | Missing or invalid key |
| 401 | `key_revoked` | Key has been revoked |
| 401 | `key_expired` | Key has expired |
| 403 | `ip_not_allowed` | Request IP not whitelisted |
| 403 | `permission_denied` | Missing required permission |
| 429 | `rate_limited` | Rate limit exceeded |

## Usage Tracking

Each key tracks:
- `call_count` - Total lifetime calls
- `last_used_at` - Timestamp of last use
- `last_used_ip` - IP of last request

Access via:
```php
$key->call_count;
$key->getLastUsedForHumans(); // "2 hours ago"
```

## Best Practices

1. **Use descriptive names** - "Production Agent" not "Key 1"
2. **Minimal permissions** - Only grant needed scopes
3. **Set expiration** - Rotate keys periodically
4. **Enable IP restrictions** - When agents run from known IPs
5. **Monitor usage** - Review call patterns regularly
6. **Revoke promptly** - If key may be compromised
7. **Separate environments** - Different keys for dev/staging/prod

## Example: Complete Setup

```php
use Core\Mod\Agentic\Services\AgentApiKeyService;
use Core\Mod\Agentic\Models\AgentApiKey;

$service = app(AgentApiKeyService::class);

// Create a production key
$key = $service->create(
workspace: $workspace,
name: 'Production Agent - Claude',
permissions: [
AgentApiKey::PERM_PLANS_READ,
AgentApiKey::PERM_PLANS_WRITE,
AgentApiKey::PERM_PHASES_WRITE,
AgentApiKey::PERM_SESSIONS_WRITE,
AgentApiKey::PERM_TEMPLATES_READ,
AgentApiKey::PERM_TEMPLATES_INSTANTIATE,
],
rateLimit: 200,
expiresAt: now()->addYear()
);

// Restrict to known IPs
$service->enableIpRestrictions($key, [
'203.0.113.0/24', // Office network
'198.51.100.50', // CI/CD server
]);

// Store the key securely
$plainKey = $key->plainTextKey; // Only chance to get this!
```
Loading
Loading