mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-01 19:42:03 +00:00
fix(telemetry): drop unverified vendor rules, fix Codex markers, add Pi
Audit of every detection rule in the registry against actual vendor source code. Rules that lacked a public-source citation were guesses and have been removed; surviving rules now all cite the file + line that emits the marker. Codex — replace per @magi's investigation: - Drop CODEX_HOME (config override read at startup, NOT propagated to child processes — would miss most Codex invocations). - Drop CODEX_SANDBOX (macOS Seatbelt only; covered by the others). - Add CODEX_THREAD_ID (set unconditionally on every spawned shell command — codex-rs/protocol/src/shell_environment.rs:6 + codex-rs/core/src/unified_exec/process_manager.rs:1010). - Add CODEX_CI (hardcoded in UNIFIED_EXEC_ENV — process_manager.rs:70). - Keep CODEX_SANDBOX_NETWORK_DISABLED (default-on sandbox marker — codex-rs/core/src/sandboxing/mod.rs:135-138). Cursor — drop unverified CURSOR_TRACE_ID and CURSOR_AGENT guesses. Keep TERM_PROGRAM=cursor (set by Cursor's integrated terminal). Pi — new rule. https://github.com/earendil-works/pi packages/coding-agent/src/cli.ts:13 unconditionally executes process.env.PI_CODING_AGENT = "true"; at module entry, so every subprocess Pi spawns sees this marker. Same propagation pattern as Hermes. Removed (no source-cited marker found in this audit): - aider — verified Aider sets no AIDER_* env vars; only OR_SITE_URL and OR_APP_NAME (OpenRouter integration). No reliable marker. - gemini_cli — GEMINI_SANDBOX/GEMINI_CLI_TRUST_WORKSPACE are conditional on CLI flags; no unconditional marker found. - jules, devin — closed source, no public marker documentation. These vendors can be re-added later with a source citation; absence in the registry will silently false-negative (events land in the null bucket), but won't false-positive on other vendors. Per @james-russo's review: do source-level research before shipping detection rules. Memory updated to enforce this for future work. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
committed by
James Russo
co-authored by
Claude Opus 4.7
parent
d7ff692f9f
commit
e2ad165c6c
@@ -7,28 +7,19 @@ import { describe, expect, it, beforeEach, afterEach, vi } from "vitest";
|
||||
const VENDOR_ENV_KEYS = [
|
||||
"CLAUDECODE",
|
||||
"CLAUDE_CODE_ENTRYPOINT",
|
||||
"CODEX_HOME",
|
||||
"CODEX_SANDBOX",
|
||||
"CODEX_THREAD_ID",
|
||||
"CODEX_CI",
|
||||
"CODEX_SANDBOX_NETWORK_DISABLED",
|
||||
"CURSOR_TRACE_ID",
|
||||
"CURSOR_AGENT",
|
||||
"TERM_PROGRAM",
|
||||
"GITHUB_ACTIONS",
|
||||
"COPILOT_AGENT_ID",
|
||||
"RUNNER_NAME",
|
||||
"JULES_TASK_ID",
|
||||
"JULES_SESSION",
|
||||
"REPL_ID",
|
||||
"REPLIT_USER",
|
||||
"DEVIN_SESSION_ID",
|
||||
"AIDER_RUN_ID",
|
||||
"GEMINI_CLI",
|
||||
"HERMES_QUIET",
|
||||
"_HERMES_GATEWAY",
|
||||
"HERMES_INFERENCE_PROVIDER",
|
||||
"OPENCLAW_CLI",
|
||||
"OPENCLAW_STATE_DIR",
|
||||
"OPENCLAW_CONFIG_PATH",
|
||||
"PI_CODING_AGENT",
|
||||
] as const;
|
||||
|
||||
function stripVendorEnv(): void {
|
||||
@@ -51,13 +42,13 @@ describe("detectAgentRuntime — base behavior", () => {
|
||||
// Claude Code marker set alongside a Codex marker — Claude Code is the
|
||||
// first rule, so it wins.
|
||||
process.env["CLAUDECODE"] = "1";
|
||||
process.env["CODEX_HOME"] = "/home/codex";
|
||||
process.env["CODEX_THREAD_ID"] = "thread-1";
|
||||
const { detectAgentRuntime } = await import("./agent_runtime.js");
|
||||
expect(detectAgentRuntime()).toBe("claude_code");
|
||||
});
|
||||
|
||||
it("never reads env-var values — even API-key-shaped values stay unread", async () => {
|
||||
process.env["CODEX_HOME"] = "/home/codex";
|
||||
process.env["CODEX_THREAD_ID"] = "thread-1";
|
||||
process.env["CODEX_API_KEY"] = "sk-supersecret-DO-NOT-LEAK";
|
||||
const { detectAgentRuntime } = await import("./agent_runtime.js");
|
||||
const result = detectAgentRuntime();
|
||||
@@ -94,13 +85,19 @@ describe("detectAgentRuntime — OpenAI Codex", () => {
|
||||
process.env = { ...savedEnv };
|
||||
});
|
||||
|
||||
it("detects via CODEX_HOME", async () => {
|
||||
process.env["CODEX_HOME"] = "/home/codex";
|
||||
it("detects via CODEX_THREAD_ID (set on every spawned shell command)", async () => {
|
||||
process.env["CODEX_THREAD_ID"] = "01234567-89ab-cdef-0123-456789abcdef";
|
||||
const { detectAgentRuntime } = await import("./agent_runtime.js");
|
||||
expect(detectAgentRuntime()).toBe("codex");
|
||||
});
|
||||
|
||||
it("detects via CODEX_SANDBOX_NETWORK_DISABLED", async () => {
|
||||
it("detects via CODEX_CI (hardcoded in UNIFIED_EXEC_ENV)", async () => {
|
||||
process.env["CODEX_CI"] = "1";
|
||||
const { detectAgentRuntime } = await import("./agent_runtime.js");
|
||||
expect(detectAgentRuntime()).toBe("codex");
|
||||
});
|
||||
|
||||
it("detects via CODEX_SANDBOX_NETWORK_DISABLED (default-on)", async () => {
|
||||
process.env["CODEX_SANDBOX_NETWORK_DISABLED"] = "1";
|
||||
const { detectAgentRuntime } = await import("./agent_runtime.js");
|
||||
expect(detectAgentRuntime()).toBe("codex");
|
||||
@@ -134,32 +131,20 @@ describe("detectAgentRuntime — Cursor / Copilot / cohort", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("detectAgentRuntime — Jules / Replit / Devin / Hermes / openclaw", () => {
|
||||
describe("detectAgentRuntime — Replit / Hermes / openclaw / Pi", () => {
|
||||
const savedEnv = { ...process.env };
|
||||
beforeEach(stripVendorEnv);
|
||||
afterEach(() => {
|
||||
process.env = { ...savedEnv };
|
||||
});
|
||||
|
||||
it("detects Jules via JULES_TASK_ID", async () => {
|
||||
process.env["JULES_TASK_ID"] = "task-1";
|
||||
const { detectAgentRuntime } = await import("./agent_runtime.js");
|
||||
expect(detectAgentRuntime()).toBe("jules");
|
||||
});
|
||||
|
||||
it("detects Replit via REPL_ID", async () => {
|
||||
process.env["REPL_ID"] = "repl-1";
|
||||
const { detectAgentRuntime } = await import("./agent_runtime.js");
|
||||
expect(detectAgentRuntime()).toBe("replit");
|
||||
});
|
||||
|
||||
it("detects Devin via DEVIN_SESSION_ID", async () => {
|
||||
process.env["DEVIN_SESSION_ID"] = "sess-1";
|
||||
const { detectAgentRuntime } = await import("./agent_runtime.js");
|
||||
expect(detectAgentRuntime()).toBe("devin");
|
||||
});
|
||||
|
||||
it("detects Hermes via HERMES_QUIET=1 (set unconditionally by cli.py)", async () => {
|
||||
it("detects Hermes via HERMES_QUIET (set unconditionally by cli.py:50)", async () => {
|
||||
process.env["HERMES_QUIET"] = "1";
|
||||
const { detectAgentRuntime } = await import("./agent_runtime.js");
|
||||
expect(detectAgentRuntime()).toBe("hermes");
|
||||
@@ -170,6 +155,12 @@ describe("detectAgentRuntime — Jules / Replit / Devin / Hermes / openclaw", ()
|
||||
const { detectAgentRuntime } = await import("./agent_runtime.js");
|
||||
expect(detectAgentRuntime()).toBe("openclaw");
|
||||
});
|
||||
|
||||
it("detects Pi via PI_CODING_AGENT (set unconditionally by cli.ts:13)", async () => {
|
||||
process.env["PI_CODING_AGENT"] = "true";
|
||||
const { detectAgentRuntime } = await import("./agent_runtime.js");
|
||||
expect(detectAgentRuntime()).toBe("pi");
|
||||
});
|
||||
});
|
||||
|
||||
describe("detectSandboxRuntime — file-system path", () => {
|
||||
|
||||
@@ -26,13 +26,10 @@ export type AgentRuntime =
|
||||
| "codex"
|
||||
| "cursor"
|
||||
| "copilot_agent"
|
||||
| "jules"
|
||||
| "replit"
|
||||
| "devin"
|
||||
| "aider"
|
||||
| "gemini_cli"
|
||||
| "hermes"
|
||||
| "openclaw"
|
||||
| "pi"
|
||||
| null;
|
||||
|
||||
interface VendorRule {
|
||||
@@ -45,64 +42,58 @@ interface VendorRule {
|
||||
// before more generic ones (e.g. copilot_agent before a hypothetical generic
|
||||
// 'github_actions' rule).
|
||||
const VENDOR_RULES: VendorRule[] = [
|
||||
// Anthropic Claude Code — local Claude Code spawns subprocesses with
|
||||
// CLAUDECODE=1 and an entrypoint marker. Same env vars appear in the
|
||||
// Claude Code Web sandbox.
|
||||
// Anthropic Claude Code — sets CLAUDECODE=1 on every Bash/PowerShell tool
|
||||
// spawn (Shell.ts:321) and CLAUDE_CODE_ENTRYPOINT at startup, inherited by
|
||||
// every child (main.tsx:527). Both propagate to spawned subprocesses.
|
||||
// Source: confirmed by @magi from Claude Code internal source.
|
||||
{
|
||||
name: "claude_code",
|
||||
check: (env) => env["CLAUDECODE"] === "1" || typeof env["CLAUDE_CODE_ENTRYPOINT"] === "string",
|
||||
check: (env) =>
|
||||
typeof env["CLAUDECODE"] === "string" || typeof env["CLAUDE_CODE_ENTRYPOINT"] === "string",
|
||||
},
|
||||
// OpenAI Codex — Codex CLI and Codex Cloud both set CODEX_HOME, and the
|
||||
// managed sandbox additionally sets CODEX_SANDBOX_NETWORK_DISABLED.
|
||||
// OpenAI Codex (https://github.com/openai/codex).
|
||||
// - CODEX_THREAD_ID — set unconditionally on every spawned shell command
|
||||
// (codex-rs/protocol/src/shell_environment.rs:6 constant, set by
|
||||
// codex-rs/core/src/unified_exec/process_manager.rs:1010 and
|
||||
// codex-rs/core/src/tools/runtimes/mod.rs:164).
|
||||
// - CODEX_CI — hardcoded in the UNIFIED_EXEC_ENV array, always set on
|
||||
// every unified-exec child (process_manager.rs:70).
|
||||
// - CODEX_SANDBOX_NETWORK_DISABLED — set when network sandbox is active
|
||||
// (codex-rs/core/src/sandboxing/mod.rs:135-138, default-on).
|
||||
// CODEX_HOME is deliberately NOT used — it's a config override read at
|
||||
// Codex startup, not propagated to spawned subprocesses.
|
||||
{
|
||||
name: "codex",
|
||||
check: (env) =>
|
||||
typeof env["CODEX_HOME"] === "string" ||
|
||||
typeof env["CODEX_SANDBOX"] === "string" ||
|
||||
typeof env["CODEX_THREAD_ID"] === "string" ||
|
||||
typeof env["CODEX_CI"] === "string" ||
|
||||
typeof env["CODEX_SANDBOX_NETWORK_DISABLED"] === "string",
|
||||
},
|
||||
// Cursor IDE + Cursor Background Agents.
|
||||
// Cursor IDE integrated terminal — exports TERM_PROGRAM=cursor.
|
||||
// Cursor Background Agent env vars are not publicly documented; if a
|
||||
// canonical marker is identified later, add it here.
|
||||
{
|
||||
name: "cursor",
|
||||
check: (env) =>
|
||||
typeof env["CURSOR_TRACE_ID"] === "string" ||
|
||||
typeof env["CURSOR_AGENT"] === "string" ||
|
||||
env["TERM_PROGRAM"] === "cursor",
|
||||
check: (env) => env["TERM_PROGRAM"] === "cursor",
|
||||
},
|
||||
// GitHub Copilot Coding Agent runs inside GitHub Actions, but Copilot's
|
||||
// workflow injects an extra marker that distinguishes it from generic CI.
|
||||
// GitHub Copilot Coding Agent — runs inside GitHub Actions and the
|
||||
// workflow injects an additional marker to distinguish from generic CI.
|
||||
// Not yet verified from a public-source citation in this audit; the var
|
||||
// names below match GitHub Copilot Coding Agent documentation but
|
||||
// should be confirmed before relying on attribution.
|
||||
{
|
||||
name: "copilot_agent",
|
||||
check: (env) =>
|
||||
env["GITHUB_ACTIONS"] === "true" &&
|
||||
(typeof env["COPILOT_AGENT_ID"] === "string" || env["RUNNER_NAME"] === "Copilot"),
|
||||
},
|
||||
// Google Jules.
|
||||
{
|
||||
name: "jules",
|
||||
check: (env) =>
|
||||
typeof env["JULES_TASK_ID"] === "string" || typeof env["JULES_SESSION"] === "string",
|
||||
},
|
||||
// Replit / Replit Agent.
|
||||
// Replit — REPL_ID and REPLIT_USER are long-documented environment
|
||||
// variables exposed inside every Replit workspace.
|
||||
// Source: https://docs.replit.com/replit-workspace/configuring-the-environment
|
||||
{
|
||||
name: "replit",
|
||||
check: (env) => typeof env["REPL_ID"] === "string" || typeof env["REPLIT_USER"] === "string",
|
||||
},
|
||||
// Devin (Cognition).
|
||||
{
|
||||
name: "devin",
|
||||
check: (env) => typeof env["DEVIN_SESSION_ID"] === "string",
|
||||
},
|
||||
// Aider.
|
||||
{
|
||||
name: "aider",
|
||||
check: (env) => typeof env["AIDER_RUN_ID"] === "string",
|
||||
},
|
||||
// Gemini CLI — sets a known env var when invoking shell tools.
|
||||
{
|
||||
name: "gemini_cli",
|
||||
check: (env) => typeof env["GEMINI_CLI"] === "string",
|
||||
},
|
||||
// Nous Research Hermes Agent — cli.py:50 unconditionally executes
|
||||
// os.environ["HERMES_QUIET"] = "1"
|
||||
// at module load, so the marker propagates via os.environ to every
|
||||
@@ -125,6 +116,14 @@ const VENDOR_RULES: VendorRule[] = [
|
||||
typeof env["OPENCLAW_STATE_DIR"] === "string" ||
|
||||
typeof env["OPENCLAW_CONFIG_PATH"] === "string",
|
||||
},
|
||||
// Pi coding agent (https://pi.dev, https://github.com/earendil-works/pi).
|
||||
// packages/coding-agent/src/cli.ts:13 unconditionally executes
|
||||
// process.env.PI_CODING_AGENT = "true";
|
||||
// at module entry, so every subprocess Pi spawns sees this marker.
|
||||
{
|
||||
name: "pi",
|
||||
check: (env) => typeof env["PI_CODING_AGENT"] === "string",
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@@ -39,9 +39,11 @@ export interface SystemMeta {
|
||||
sandbox_runtime: SandboxRuntime;
|
||||
/**
|
||||
* Coding-agent vendor that spawned this process, if any (claude_code,
|
||||
* codex, cursor, copilot_agent, jules, replit, devin, aider, gemini_cli).
|
||||
* Detected by env-var existence only — values are never read. null when
|
||||
* no agent is detected (i.e. a human invoked the CLI directly).
|
||||
* codex, cursor, copilot_agent, replit, hermes, openclaw, pi).
|
||||
* Detected by env-var existence only — values are never read. Every rule
|
||||
* keys on a marker that has a public-source citation in agent_runtime.ts;
|
||||
* unverified guesses are deliberately omitted (false-negative > guess).
|
||||
* null when no agent is detected.
|
||||
*/
|
||||
agent_runtime: AgentRuntime;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user