|
#!/usr/bin/env bun |
|
/** |
|
* ============================================================ |
|
* PROOF: Anthropic is specifically blocking "OpenCode" |
|
* in Claude Code OAuth system prompts |
|
* ============================================================ |
|
* |
|
* Video covering this script here: https://www.youtube.com/watch?v=G9YX6StP2-M |
|
* |
|
* This script demonstrates that Anthropic has specifically blocked |
|
* the phrase "You are OpenCode" in system prompts when using Claude's |
|
* OAuth tokens (Pro/Max subscription), while allowing other identity |
|
* statements like "You are Cursor", "You are Pi", etc. |
|
* |
|
* OpenCode (https://opencode.ai) is an open-source AI coding assistant. |
|
* |
|
* SETUP: |
|
* mkdir test-anthropic && cd test-anthropic |
|
* bun init -y |
|
* bun add @openauthjs/openauth |
|
* # Copy this file as opencode.ts |
|
* bun opencode.ts |
|
* |
|
* REQUIREMENTS: |
|
* - Bun runtime (https://bun.sh) |
|
* - Claude Pro or Max subscription |
|
* |
|
* HOW IT WORKS: |
|
* 1. Authenticates via OAuth using your Claude Pro/Max account |
|
* 2. Tests various system prompts against the Claude API |
|
* 3. Shows which prompts are blocked vs allowed |
|
* |
|
* FINDINGS: |
|
* - First system block MUST be exactly: |
|
* "You are Claude Code, Anthropic's official CLI for Claude." |
|
* - Second block can contain almost anything EXCEPT: |
|
* "You are OpenCode" or "You are opencode" |
|
* - Other identity statements work fine (Cursor, Pi, Droid, etc.) |
|
* |
|
* This appears to be targeted blocking of a specific competitor. |
|
*/ |
|
|
|
import * as readline from "node:readline"; |
|
import { generatePKCE } from "@openauthjs/openauth/pkce"; |
|
|
|
const CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e"; |
|
const CC_HEADER = "You are Claude Code, Anthropic's official CLI for Claude."; |
|
|
|
const rl = readline.createInterface({ |
|
input: process.stdin, |
|
output: process.stdout, |
|
}); |
|
|
|
function prompt(question: string): Promise<string> { |
|
return new Promise((resolve) => { |
|
rl.question(question, (answer) => { |
|
resolve(answer); |
|
}); |
|
}); |
|
} |
|
|
|
// ============ OAuth Functions ============ |
|
|
|
async function authorize() { |
|
const pkce = await generatePKCE(); |
|
const url = new URL("https://claude.ai/oauth/authorize"); |
|
url.searchParams.set("code", "true"); |
|
url.searchParams.set("client_id", CLIENT_ID); |
|
url.searchParams.set("response_type", "code"); |
|
url.searchParams.set("redirect_uri", "https://console.anthropic.com/oauth/code/callback"); |
|
url.searchParams.set("scope", "org:create_api_key user:profile user:inference"); |
|
url.searchParams.set("code_challenge", pkce.challenge); |
|
url.searchParams.set("code_challenge_method", "S256"); |
|
url.searchParams.set("state", pkce.verifier); |
|
return { url: url.toString(), verifier: pkce.verifier }; |
|
} |
|
|
|
async function exchange(code: string, verifier: string) { |
|
const splits = code.split("#"); |
|
const result = await fetch("https://console.anthropic.com/v1/oauth/token", { |
|
method: "POST", |
|
headers: { "Content-Type": "application/json" }, |
|
body: JSON.stringify({ |
|
code: splits[0], |
|
state: splits[1], |
|
grant_type: "authorization_code", |
|
client_id: CLIENT_ID, |
|
redirect_uri: "https://console.anthropic.com/oauth/code/callback", |
|
code_verifier: verifier, |
|
}), |
|
}); |
|
if (!result.ok) return null; |
|
const json = await result.json(); |
|
return json.access_token; |
|
} |
|
|
|
// ============ Test Functions ============ |
|
|
|
interface TestResult { |
|
name: string; |
|
secondBlock: string; |
|
success: boolean; |
|
} |
|
|
|
async function runTest(accessToken: string, name: string, secondBlock: string): Promise<TestResult> { |
|
process.stdout.write(` ${name}... `); |
|
|
|
const response = await fetch("https://api.anthropic.com/v1/messages?beta=true", { |
|
method: "POST", |
|
headers: { |
|
"Content-Type": "application/json", |
|
"Authorization": `Bearer ${accessToken}`, |
|
"anthropic-version": "2023-06-01", |
|
"anthropic-beta": "oauth-2025-04-20,interleaved-thinking-2025-05-14,claude-code-20250219", |
|
"user-agent": "claude-cli/2.1.2 (external, cli)", |
|
}, |
|
body: JSON.stringify({ |
|
model: "claude-sonnet-4-20250514", |
|
max_tokens: 1024, |
|
system: [ |
|
{ type: "text", text: CC_HEADER }, |
|
{ type: "text", text: secondBlock }, |
|
], |
|
tools: [{ |
|
name: "mcp_test", |
|
description: "Test tool", |
|
input_schema: { type: "object", properties: {} }, |
|
}], |
|
messages: [{ role: "user", content: "Say hello in 5 words." }], |
|
}), |
|
}); |
|
|
|
if (!response.ok) { |
|
console.log("FAIL"); |
|
return { name, secondBlock, success: false }; |
|
} |
|
|
|
await response.json(); |
|
console.log("PASS"); |
|
return { name, secondBlock, success: true }; |
|
} |
|
|
|
async function runTests(accessToken: string) { |
|
console.log("\n" + "=".repeat(50)); |
|
console.log("SYSTEM PROMPT VALIDATION TESTS"); |
|
console.log("=".repeat(50)); |
|
console.log(`\nFirst block (required): "${CC_HEADER}"`); |
|
console.log("\nTesting second block content:\n"); |
|
|
|
const tests = [ |
|
// These FAIL - "You are OpenCode" pattern is blocked |
|
{ name: "You are OpenCode.", block: "You are OpenCode." }, |
|
{ name: "You are opencode.", block: "You are opencode." }, |
|
|
|
// These PASS - other identity statements work fine |
|
{ name: "You are Cursor.", block: "You are Cursor." }, |
|
{ name: "You are Pi.", block: "You are Pi." }, |
|
{ name: "You are Droid.", block: "You are Droid." }, |
|
]; |
|
|
|
const results: TestResult[] = []; |
|
for (const test of tests) { |
|
const result = await runTest(accessToken, test.name, test.block); |
|
results.push(result); |
|
await new Promise((r) => setTimeout(r, 300)); |
|
} |
|
|
|
// Summary |
|
console.log("\n" + "=".repeat(50)); |
|
console.log("RESULTS"); |
|
console.log("=".repeat(50)); |
|
|
|
const passed = results.filter((r) => r.success); |
|
const failed = results.filter((r) => !r.success); |
|
|
|
console.log(`\nPassed: ${passed.length} | Failed: ${failed.length}`); |
|
|
|
if (failed.length > 0) { |
|
console.log("\nBLOCKED (cannot use in 2nd system block):"); |
|
failed.forEach((r) => console.log(` - "${r.secondBlock}"`)); |
|
} |
|
|
|
if (passed.length > 0) { |
|
console.log("\nALLOWED:"); |
|
passed.forEach((r) => console.log(` - "${r.secondBlock}"`)); |
|
} |
|
|
|
console.log("\n" + "=".repeat(50)); |
|
console.log("CONCLUSION: Anthropic specifically blocks 'You are OpenCode'"); |
|
console.log(" but allows other identity statements."); |
|
console.log("=".repeat(50) + "\n"); |
|
} |
|
|
|
// ============ Main ============ |
|
|
|
async function main() { |
|
const args = process.argv.slice(2); |
|
|
|
console.log("\n" + "=".repeat(50)); |
|
console.log(" ANTHROPIC OAUTH SYSTEM PROMPT TESTER"); |
|
console.log("=".repeat(50)); |
|
|
|
let accessToken = args[0]; |
|
|
|
if (!accessToken) { |
|
console.log("\nNo token provided. Starting OAuth flow...\n"); |
|
|
|
const { url, verifier } = await authorize(); |
|
|
|
console.log("1. Open this URL in your browser:\n"); |
|
console.log(url); |
|
console.log("\n2. Authorize and copy the code shown.\n"); |
|
|
|
const code = await prompt("3. Paste the code here: "); |
|
|
|
console.log("\nExchanging code for token..."); |
|
accessToken = await exchange(code.trim(), verifier); |
|
|
|
if (!accessToken) { |
|
console.error("\nFailed to get access token!"); |
|
process.exit(1); |
|
} |
|
|
|
console.log("Got access token!\n"); |
|
} |
|
|
|
await runTests(accessToken); |
|
rl.close(); |
|
} |
|
|
|
main().catch(console.error).finally(() => rl.close()); |