fix(sse): strip third-party-agent signals from the Hermes system prompt that trigger Anthropic 400 extra-usage (#8350) (#8358)

This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-07-24 11:44:37 -03:00
committed by GitHub
parent 70275be59b
commit 493708f575
6 changed files with 118 additions and 4 deletions

View File

@@ -0,0 +1 @@
- fix(sse): strip third-party-agent signals from the Hermes system prompt that trigger Anthropic 400 extra-usage (#8350)

View File

@@ -96,6 +96,9 @@ export const DEFAULT_OBFUSCATE_WORDS = [
// Open WebUI additions
"openwebui",
"open-webui",
// Hermes additions (#8350)
"hermes-agent",
"hermes",
];
/**
@@ -118,6 +121,19 @@ export const PI_PARAGRAPH_ANCHORS = [
"Pi documentation (read only when the user asks about pi itself",
];
/**
* Hermes (NousResearch/hermes-agent) paragraph anchors — the doc-link
* paragraph injected by `agent/prompt_builder.py` on the upstream CLI.
* Added on top of the other anchor lists for #8350.
*/
export const HERMES_PARAGRAPH_ANCHORS = [
"hermes-agent.nousresearch.com",
"github.com/NousResearch/hermes-agent",
];
/** Hermes identity paragraph prefixes ("You are Hermes Agent, ..."). */
export const HERMES_IDENTITY_PREFIXES = ["You are Hermes Agent"];
// ────────────────────────────────────────────────────────────────────────────
// Per-provider defaults.
// ────────────────────────────────────────────────────────────────────────────
@@ -164,12 +180,18 @@ export const DEFAULT_CLAUDE_PIPELINE: TransformOp[] = [
...DEFAULT_PARAGRAPH_REMOVAL_ANCHORS,
...OPENWEBUI_PARAGRAPH_ANCHORS,
...PI_PARAGRAPH_ANCHORS,
...HERMES_PARAGRAPH_ANCHORS,
],
},
// Drop "You are OpenCode" + "You are Open WebUI" identity paragraphs.
// Drop "You are OpenCode" + "You are Open WebUI" + "You are Hermes Agent"
// identity paragraphs.
{
kind: "drop_paragraph_if_starts_with",
prefixes: [...DEFAULT_IDENTITY_PREFIXES, ...OPENWEBUI_IDENTITY_PREFIXES],
prefixes: [
...DEFAULT_IDENTITY_PREFIXES,
...OPENWEBUI_IDENTITY_PREFIXES,
...HERMES_IDENTITY_PREFIXES,
],
},
// Replace the "Here is some useful information about the environment you are
// running in:" billing-gate trigger phrase + the "if OpenCode honestly"

View File

@@ -12,6 +12,7 @@ import {
} from "@/shared/constants/cliCompatProviders";
import { AI_PROVIDERS } from "@/shared/constants/providers";
import { compareTr } from "@/shared/utils/turkishText";
import { HERMES } from "./systemTransformsHermesDefaults";
// Provider keys (mirror of open-sse/services/systemTransforms.ts).
const PROVIDER_CLAUDE = "claude";
@@ -80,6 +81,8 @@ const DEFAULT_OBFUSCATE_WORDS = [
"codecompanion",
"openwebui",
"open-webui",
"hermes-agent",
"hermes",
];
// Mirror of DEFAULT_SYSTEM_TRANSFORMS_CONFIG from open-sse/services/systemTransforms.ts.
@@ -96,11 +99,12 @@ const DEFAULT_SYSTEM_TRANSFORMS_CLIENT = {
...DEFAULT_PARAGRAPH_REMOVAL_ANCHORS,
...OPENWEBUI_PARAGRAPH_ANCHORS,
...PI_PARAGRAPH_ANCHORS,
...HERMES.anchors,
],
},
{
kind: "drop_paragraph_if_starts_with",
prefixes: [...DEFAULT_IDENTITY_PREFIXES, "You are Open WebUI"],
prefixes: [...DEFAULT_IDENTITY_PREFIXES, "You are Open WebUI", ...HERMES.prefixes],
},
...DEFAULT_TEXT_REPLACEMENTS.map((r) => ({
kind: "replace_text" as const,

View File

@@ -0,0 +1,13 @@
/**
* Hermes (NousResearch/hermes-agent) system-transform anchors — client-side
* mirror constant shared by RoutingTab.tsx and tests/unit/system-transforms.test.ts's
* UI-parity snapshot. Extracted to a standalone module to keep RoutingTab.tsx
* under its frozen file-size baseline (#8350).
*
* Server source of truth: open-sse/services/systemTransforms.ts
* (HERMES_PARAGRAPH_ANCHORS / HERMES_IDENTITY_PREFIXES).
*/
export const HERMES = {
anchors: ["hermes-agent.nousresearch.com", "github.com/NousResearch/hermes-agent"],
prefixes: ["You are Hermes Agent"],
};

View File

@@ -0,0 +1,70 @@
// Regression test for #8350 — Hermes (NousResearch/hermes-agent) system-prompt
// signals reach Anthropic's native Claude OAuth path untouched, tripping
// `[400] Third-party apps now draw from extra usage, not plan limits.`
//
// Mirrors the "OpenWebUI fixture" / "Pi documentation" style tests in
// tests/unit/system-transforms.test.ts. Fixture text matches the real
// identity + doc-link paragraphs injected by
// NousResearch/hermes-agent/agent/prompt_builder.py (verified via WebFetch
// against the upstream repo during triage).
import test from "node:test";
import assert from "node:assert/strict";
const { applySystemTransformPipeline, DEFAULT_SYSTEM_TRANSFORMS_CONFIG, PROVIDER_CLAUDE } =
await import("../../open-sse/services/systemTransforms.ts");
test("Hermes fixture: claude provider drops Hermes identity + doc-link paragraphs (#8350)", () => {
const body = {
system: [
{
type: "text",
text: [
"You are Hermes Agent, an intelligent AI assistant created by Nous Research.",
"Guidelines:\n- Be concise.",
"For more information, see the documentation at https://hermes-agent.nousresearch.com/docs.",
].join("\n\n"),
},
],
messages: [{ role: "user", content: "hi" }],
};
const result = applySystemTransformPipeline(
PROVIDER_CLAUDE,
body,
DEFAULT_SYSTEM_TRANSFORMS_CONFIG
);
const out = (body.system as Array<{ text: string }>)[0].text;
assert.ok(
!out.includes("You are Hermes Agent"),
"Hermes identity paragraph should be dropped by the default claude pipeline"
);
assert.ok(
!out.includes("hermes-agent.nousresearch.com"),
"Hermes doc-link paragraph should be dropped by the default claude pipeline"
);
// Unrelated legitimate content survives untouched.
assert.ok(out.includes("Guidelines:"));
assert.ok(out.includes("Be concise."));
assert.ok(result.appliedOpKinds.includes("drop_paragraph_if_contains"));
// No billing header injected on the native OAuth path (native code handles that).
assert.ok(!result.appliedOpKinds.includes("inject_billing_header"));
});
test("non-Hermes system prompt passes through byte-identical through the claude pipeline (no drive-by regression)", () => {
const body = {
system: [
{
type: "text",
text: "You are a helpful operator-configured assistant. Follow company policy X and always answer in English.",
},
],
messages: [{ role: "user", content: "hi" }],
};
const before = JSON.stringify(body);
applySystemTransformPipeline(PROVIDER_CLAUDE, body, DEFAULT_SYSTEM_TRANSFORMS_CONFIG);
assert.equal(
JSON.stringify(body),
before,
"a normal operator system prompt with no third-party-agent anchors must pass through untouched"
);
});

View File

@@ -446,11 +446,13 @@ const UI_DEFAULTS_SNAPSHOT = {
"@earendil-works/pi-coding-agent",
"/.pi/",
"Pi documentation (read only when the user asks about pi itself",
"hermes-agent.nousresearch.com",
"github.com/NousResearch/hermes-agent",
],
},
{
kind: "drop_paragraph_if_starts_with",
prefixes: ["You are OpenCode", "You are Open WebUI"],
prefixes: ["You are OpenCode", "You are Open WebUI", "You are Hermes Agent"],
},
{
kind: "replace_text",
@@ -481,6 +483,8 @@ const UI_DEFAULTS_SNAPSHOT = {
"codecompanion",
"openwebui",
"open-webui",
"hermes-agent",
"hermes",
],
targets: ["system", "messages", "tools"],
},