policies: add standard policy files, formatting, and secret scanning

- Add .editorconfig, .prettierrc, .prettierignore, .gitignore
- Add Makefile with fmt, fmt-check, secret-scan, test (skip) targets
- Add package.json with prettier
- Add tools/secret-scan.sh
- Add .secret-scan-allowlist for documentation token format references
- Sanitize documentation to use generic placeholders
- Auto-format with prettier
- make check passes clean
This commit is contained in:
sol
2026-03-01 07:28:54 +00:00
parent 32a4e739dc
commit 0b94cb8fd5
19 changed files with 291 additions and 110 deletions

View File

@@ -7,8 +7,8 @@
| Claude Code CLI | ================> | .credentials.json |
| (inside | (every ~8 hours, | { |
| claude-proxy | built-in to CLI) | "claudeAiOauth": { |
| container) | | "accessToken": "sk-ant-oat01-...", |
+--------------------+ | "refreshToken": "sk-ant-ort01-...", |
| container) | | "accessToken": "<access-token-value>", |
+--------------------+ | "refreshToken": "<refresh-token-value>", |
| "expiresAt": 1772120060006 |
| } |
| } |
@@ -28,7 +28,7 @@
| oauth.json | | .env | | docker compose |
| { | | ANTHROPIC_ | | down/up gateway |
| "anthropic": { | | OAUTH_TOKEN= | | (reloads env) |
| "access":..., | | "sk-ant-oat01-" | +---------+--------+
| "access":..., | | "<token-prefix>" | +---------+--------+
| "refresh":...,| +-----------------+ |
| "expires":... | +----------v----------+
| } | | OpenClaw Gateway |
@@ -78,7 +78,7 @@ When the gateway needs to authenticate with Anthropic:
8. -> If valid profile found: use it
9. -> If no valid profile: resolveEnvApiKey("anthropic")
10. -> Reads ANTHROPIC_OAUTH_TOKEN from container env
11. -> isOAuthToken(key) detects "sk-ant-oat" prefix
11. -> isOAuthToken(key) detects "<token-prefix>" prefix
12. -> Uses Bearer auth + Claude Code identity headers
13. -> Sends request to api.anthropic.com
@@ -106,13 +106,13 @@ docker compose down openclaw-gateway && docker compose up -d openclaw-gateway
## Source Code References (inside gateway container)
| File | Line | Function |
|------|------|----------|
| `/app/dist/paths-CyR9Pa1R.js` | 190 | `OAUTH_FILENAME = "oauth.json"` |
| `/app/dist/paths-CyR9Pa1R.js` | 198-204 | `resolveOAuthDir()` -> `$STATE_DIR/credentials/` |
| `/app/dist/paths-CyR9Pa1R.js` | 203 | `resolveOAuthPath()` -> joins dir + filename |
| `/app/dist/model-auth-CmUeBbp-.js` | 3048 | `mergeOAuthFileIntoStore()` -- reads oauth.json |
| `/app/dist/model-auth-CmUeBbp-.js` | 3358 | `buildOAuthApiKey()` -- returns `credentials.access` |
| `/app/dist/model-auth-CmUeBbp-.js` | 3832 | `isValidProfile()` -- for oauth, checks `cred.access` |
| `/app/dist/model-auth-CmUeBbp-.js` | 3942 | `resolveApiKeyForProvider()` -- profiles then env fallback |
| `/app/dist/model-auth-CmUeBbp-.js` | 4023 | `resolveEnvApiKey("anthropic")` -> reads env var |
| File | Line | Function |
| ---------------------------------- | ------- | ---------------------------------------------------------- |
| `/app/dist/paths-CyR9Pa1R.js` | 190 | `OAUTH_FILENAME = "oauth.json"` |
| `/app/dist/paths-CyR9Pa1R.js` | 198-204 | `resolveOAuthDir()` -> `$STATE_DIR/credentials/` |
| `/app/dist/paths-CyR9Pa1R.js` | 203 | `resolveOAuthPath()` -> joins dir + filename |
| `/app/dist/model-auth-CmUeBbp-.js` | 3048 | `mergeOAuthFileIntoStore()` -- reads oauth.json |
| `/app/dist/model-auth-CmUeBbp-.js` | 3358 | `buildOAuthApiKey()` -- returns `credentials.access` |
| `/app/dist/model-auth-CmUeBbp-.js` | 3832 | `isValidProfile()` -- for oauth, checks `cred.access` |
| `/app/dist/model-auth-CmUeBbp-.js` | 3942 | `resolveApiKeyForProvider()` -- profiles then env fallback |
| `/app/dist/model-auth-CmUeBbp-.js` | 4023 | `resolveEnvApiKey("anthropic")` -> reads env var |

View File

@@ -7,8 +7,8 @@ Written by Claude Code CLI when it refreshes the token.
```json
{
"claudeAiOauth": {
"accessToken": "sk-ant-oat01-...",
"refreshToken": "sk-ant-ort01-...",
"accessToken": "<access-token-value>",
"refreshToken": "<refresh-token-value>",
"expiresAt": 1772120060006,
"scopes": ["user:inference", "user:mcp_servers", "user:profile", "user:sessions:claude_code"],
"subscriptionType": "max",
@@ -24,8 +24,8 @@ Read by the gateway's `mergeOAuthFileIntoStore()` on startup.
```json
{
"anthropic": {
"access": "sk-ant-oat01-...",
"refresh": "sk-ant-ort01-...",
"access": "<access-token-value>",
"refresh": "<refresh-token-value>",
"expires": 1772120060006,
"scopes": ["user:inference", "user:mcp_servers", "user:profile", "user:sessions:claude_code"],
"subscriptionType": "max",
@@ -36,21 +36,21 @@ Read by the gateway's `mergeOAuthFileIntoStore()` on startup.
## Field name mapping
| Claude CLI | OpenClaw | Notes |
|------------|----------|-------|
| `accessToken` | `access` | The OAuth access token (`sk-ant-oat01-...`) |
| `refreshToken` | `refresh` | The refresh token (`sk-ant-ort01-...`) |
| `expiresAt` | `expires` | Unix timestamp in milliseconds |
| `scopes` | `scopes` | Same format (array of strings) |
| `subscriptionType` | `subscriptionType` | Same (`"max"`) |
| `rateLimitTier` | `rateLimitTier` | Same (`"default_claude_max_5x"`) |
| Claude CLI | OpenClaw | Notes |
| ------------------ | ------------------ | ----------------------------------------------- |
| `accessToken` | `access` | The OAuth access token (`<access-token-value>`) |
| `refreshToken` | `refresh` | The refresh token (`<refresh-token-value>`) |
| `expiresAt` | `expires` | Unix timestamp in milliseconds |
| `scopes` | `scopes` | Same format (array of strings) |
| `subscriptionType` | `subscriptionType` | Same (`"max"`) |
| `rateLimitTier` | `rateLimitTier` | Same (`"default_claude_max_5x"`) |
## .env format
Single env var, only the access token (no refresh/expiry):
```
ANTHROPIC_OAUTH_TOKEN="sk-ant-oat01-..."
ANTHROPIC_OAUTH_TOKEN="<access-token-value>"
```
## Auth profiles format (CORRECT)
@@ -61,7 +61,7 @@ ANTHROPIC_OAUTH_TOKEN="sk-ant-oat01-..."
"anthropic:default": {
"type": "oauth",
"provider": "anthropic",
"access": "sk-ant-oat01-..."
"access": "<access-token-value>"
}
}
}
@@ -75,7 +75,7 @@ ANTHROPIC_OAUTH_TOKEN="sk-ant-oat01-..."
"anthropic:default": {
"type": "oauth",
"provider": "anthropic",
"key": "sk-ant-oat01-..."
"key": "<access-token-value>"
}
}
}
@@ -85,9 +85,9 @@ ANTHROPIC_OAUTH_TOKEN="sk-ant-oat01-..."
## File locations
| File | Host Path | Container Path |
|------|-----------|---------------|
| Claude CLI creds | `/root/.openclaw/workspaces/workspace-claude-proxy/config/.claude/.credentials.json` | `/root/.claude/.credentials.json` (claude-proxy) |
| OpenClaw oauth | `/root/.openclaw/credentials/oauth.json` | `/home/node/.openclaw/credentials/oauth.json` (gateway) |
| .env | `/root/openclaw/.env` | loaded as env vars at container creation |
| Auth profiles | `/root/.openclaw/agents/<agent>/agent/auth-profiles.json` | `/home/node/.openclaw/agents/<agent>/agent/auth-profiles.json` (gateway) |
| File | Host Path | Container Path |
| ---------------- | ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------ |
| Claude CLI creds | `/root/.openclaw/workspaces/workspace-claude-proxy/config/.claude/.credentials.json` | `/root/.claude/.credentials.json` (claude-proxy) |
| OpenClaw oauth | `/root/.openclaw/credentials/oauth.json` | `/home/node/.openclaw/credentials/oauth.json` (gateway) |
| .env | `/root/openclaw/.env` | loaded as env vars at container creation |
| Auth profiles | `/root/.openclaw/agents/<agent>/agent/auth-profiles.json` | `/home/node/.openclaw/agents/<agent>/agent/auth-profiles.json` (gateway) |

View File

@@ -4,8 +4,8 @@
Claude Max subscriptions use OAuth tokens for API authentication.
- **Access token** (`sk-ant-oat01-...`): Used for API requests, expires in ~8 hours
- **Refresh token** (`sk-ant-ort01-...`): Used to get new access tokens, long-lived
- **Access token** (`<access-token-value>`): Used for API requests, expires in ~8 hours
- **Refresh token** (`<refresh-token-value>`): Used to get new access tokens, long-lived
- **Token endpoint**: `POST https://console.anthropic.com/v1/oauth/token`
- **Client ID**: `9d1c250a-e61b-44d9-88ed-5944d1962f5e` (Claude Code public OAuth client)
@@ -25,21 +25,21 @@ The relevant function in Claude Code's minified source (`cli.js`):
```javascript
// Simplified from minified source
async function refreshToken(refreshToken, scopes) {
const params = {
grant_type: "refresh_token",
refresh_token: refreshToken,
client_id: CLIENT_ID,
scope: scopes.join(" ")
};
const response = await axios.post(TOKEN_URL, params, {
headers: { "Content-Type": "application/json" }
});
return {
accessToken: response.data.access_token,
refreshToken: response.data.refresh_token || refreshToken,
expiresAt: Date.now() + response.data.expires_in * 1000,
scopes: response.data.scope.split(" ")
};
const params = {
grant_type: 'refresh_token',
refresh_token: refreshToken,
client_id: CLIENT_ID,
scope: scopes.join(' '),
};
const response = await axios.post(TOKEN_URL, params, {
headers: { 'Content-Type': 'application/json' },
});
return {
accessToken: response.data.access_token,
refreshToken: response.data.refresh_token || refreshToken,
expiresAt: Date.now() + response.data.expires_in * 1000,
scopes: response.data.scope.split(' '),
};
}
```
@@ -65,6 +65,7 @@ inotifywait -q -e close_write,moved_to "$WATCH_DIR"
- We watch the **directory** not the file, because atomic renames create a new inode
When the file changes:
1. Read `accessToken`, `refreshToken`, `expiresAt` from Claude CLI format
2. Map fields: `accessToken` -> `access`, `refreshToken` -> `refresh`, `expiresAt` -> `expires`
3. Write to `oauth.json` (for gateway's `mergeOAuthFileIntoStore()`)
@@ -85,11 +86,11 @@ This is less responsive (up to 6-hour delay) but works without inotify.
## Field Mapping
| Claude CLI (`.credentials.json`) | OpenClaw (`oauth.json`) |
|----------------------------------|------------------------|
| `claudeAiOauth.accessToken` | `anthropic.access` |
| `claudeAiOauth.refreshToken` | `anthropic.refresh` |
| `claudeAiOauth.expiresAt` | `anthropic.expires` |
| `claudeAiOauth.scopes` | `anthropic.scopes` |
| -------------------------------- | ----------------------- |
| `claudeAiOauth.accessToken` | `anthropic.access` |
| `claudeAiOauth.refreshToken` | `anthropic.refresh` |
| `claudeAiOauth.expiresAt` | `anthropic.expires` |
| `claudeAiOauth.scopes` | `anthropic.scopes` |
## Timeline

View File

@@ -5,6 +5,7 @@
OpenClaw has a **built-in** Anthropic provider. You do NOT need to (and must NOT) add a custom `anthropic` entry to `models.providers` in `openclaw.json`.
Adding one causes the Anthropic SDK to append `/v1` to your `baseUrl`, which already has `/v1`, resulting in:
```
https://api.anthropic.com/v1/v1/messages -> 404 Not Found
```
@@ -21,10 +22,7 @@ In `openclaw.json`, under `agents.defaults.model`:
"defaults": {
"model": {
"primary": "anthropic/claude-opus-4-6",
"fallbacks": [
"anthropic/claude-sonnet-4-6",
"google/gemini-3.1-pro-preview"
]
"fallbacks": ["anthropic/claude-sonnet-4-6", "google/gemini-3.1-pro-preview"]
}
}
}
@@ -59,7 +57,7 @@ Under `agents.defaults.models`:
In your OpenClaw `.env` file (e.g., `/root/openclaw/.env`):
```
ANTHROPIC_OAUTH_TOKEN="sk-ant-oat01-YOUR_TOKEN_HERE"
ANTHROPIC_OAUTH_TOKEN="<YOUR-ACCESS-TOKEN>"
```
This is the fallback auth method. The gateway reads it as a container environment variable.
@@ -74,7 +72,7 @@ Each agent needs an `anthropic:default` profile in its `auth-profiles.json`:
"anthropic:default": {
"type": "oauth",
"provider": "anthropic",
"access": "sk-ant-oat01-YOUR_TOKEN_HERE"
"access": "<YOUR-ACCESS-TOKEN>"
}
},
"lastGood": {
@@ -92,8 +90,8 @@ At `/root/.openclaw/credentials/oauth.json` (maps to `/home/node/.openclaw/crede
```json
{
"anthropic": {
"access": "sk-ant-oat01-YOUR_TOKEN_HERE",
"refresh": "sk-ant-ort01-YOUR_REFRESH_TOKEN",
"access": "<YOUR-ACCESS-TOKEN>",
"refresh": "<YOUR-REFRESH-TOKEN>",
"expires": 1772120060006,
"scopes": ["user:inference", "user:mcp_servers", "user:profile", "user:sessions:claude_code"],
"subscriptionType": "max",
@@ -105,6 +103,7 @@ At `/root/.openclaw/credentials/oauth.json` (maps to `/home/node/.openclaw/crede
## Available Built-in Models
When using the built-in Anthropic provider:
- `anthropic/claude-opus-4-6`
- `anthropic/claude-sonnet-4-6`
- Other models listed in the Anthropic API
@@ -132,7 +131,7 @@ You can set a specific model per agent:
2. For `type: "oauth"`, it requires the `access` field (not `key`)
3. If no valid profile: falls back to `ANTHROPIC_OAUTH_TOKEN` env var
4. On startup, `mergeOAuthFileIntoStore()` reads `oauth.json` and merges credentials
5. `isOAuthToken()` detects the `sk-ant-oat` prefix
5. `isOAuthToken()` detects the `<token-prefix>` prefix
6. Uses Bearer auth + Claude Code identity headers to call `api.anthropic.com`
## OAuth Token Lifecycle

View File

@@ -5,12 +5,14 @@
The most common error. The OAuth token has a ~8 hour lifetime.
**Check:**
1. Is the sync service running? `systemctl status sync-oauth-token.service`
2. Is inotifywait watching? `pgrep -af inotifywait`
3. Is the source credentials file being updated? `stat /root/.openclaw/workspaces/workspace-claude-proxy/config/.claude/.credentials.json`
4. Check service logs: `journalctl -u sync-oauth-token.service -f`
**Fix:**
- If service stopped: `systemctl restart sync-oauth-token.service`
- If token expired everywhere: run `./scripts/refresh-claude-token.sh` manually
- Nuclear option: `claude login` inside the Claude CLI container, then restart sync service
@@ -24,6 +26,7 @@ This is a Docker Compose design behavior, not a bug.
`docker compose restart` only sends SIGTERM and restarts the container process. The container keeps its original environment variables from creation time.
**Always use:**
```bash
cd /root/openclaw
docker compose down openclaw-gateway
@@ -43,7 +46,7 @@ OpenClaw's `isValidProfile()` for `type: "oauth"` checks for `cred.access`, not
"anthropic:default": {
"type": "oauth",
"provider": "anthropic",
"key": "sk-ant-oat01-..." <-- WRONG
"key": "<access-token-value>" <-- WRONG
}
}
```
@@ -53,12 +56,13 @@ The profile is silently skipped and falls through to the env var.
**Fix:** Run `./scripts/fix-auth-profiles.sh`
The correct format is:
```json
{
"anthropic:default": {
"type": "oauth",
"provider": "anthropic",
"access": "sk-ant-oat01-..." <-- CORRECT
"access": "<access-token-value>" <-- CORRECT
}
}
```
@@ -70,6 +74,7 @@ The correct format is:
This happens when you add `anthropic` to `models.providers` in `openclaw.json`.
**Do NOT do this:**
```json
"models": {
"providers": {
@@ -92,6 +97,7 @@ The built-in Anthropic provider already handles routing. Adding a custom one wit
Auth profiles enter a cooldown period after repeated failures (e.g., expired tokens, wrong model names).
**Fix:**
```bash
./scripts/fix-auth-profiles.sh
```
@@ -105,6 +111,7 @@ This clears `cooldownUntil`, `errorCount`, and `failureCounts` from all agent au
The watched file or directory doesn't exist yet.
**Check:**
- Does the Claude CLI container exist? `docker ps | grep claude`
- Does the credentials path exist? `ls -la /root/.openclaw/workspaces/workspace-claude-proxy/config/.claude/`
- Has Claude CLI been authenticated? You may need to run `claude login` inside the container first.