mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-15 11:43:10 +03:00
fix(antigravity): ban-safety hardening — bounded onboarding retries, gate thought-signature bypass sentinel (#9939)
* fix(deps): bump nanoid, dompurify for 2 new Dependabot alerts (#189, #190) Bumps: nanoid ^3.3.17 (was transitive, now overridden), dompurify ^3.4.13 (with monaco-editor scoped override). Closes Dependabot #189, #190. Remaining #182-#188 (js-yaml + mermaid) already closed by #9651 merge — awaiting Dependabot re-scan. npm audit → 0 vulnerabilities. * fix(repo): harden .gitignore to also ignore a _tasks symlink (/_tasks) _tasks is a SEPARATE nested git repo (gitignored). The pattern _tasks/ (trailing slash) ignores only a directory, not a SYMLINK named _tasks. A self-referential _tasks symlink can slip in via git add -A and, once pulled, checkout materializes it over the real _tasks repo (destroying plans/specs/hands-off). Anchored /_tasks ignores the symlink too, preventing re-capture. * fix(antigravity): ban-safety hardening — bounded onboarding retries with jitter, gate the thought-signature bypass sentinel - onboardAntigravityUser: cap retries 10->3 and jitter the delay (3-7s) so a stuck loop cannot read as scripted automation to the upstream - openai-to-gemini: the skip_thought_signature_validator sentinel is an audit-trail risk; gate it behind ANTIGRAVITY_ALLOW_SIGNATURE_BYPASS (default enabled for compatibility, set 0 to disable). Real signatures always win. * test(antigravity): cover the signature-bypass sentinel gate (default on, env-disabled) Adds tests/unit/translator-antigravity-signature-bypass.test.ts (2 tests, verified locally with node --import tsx/esm) + CHANGELOG entry for the ban-safety hardening. --------- Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com> Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@outlook.com> Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com> Co-authored-by: benzntech <benzntech@users.noreply.github.com> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
This commit is contained in:
@@ -464,12 +464,17 @@ function openaiToGeminiBase(
|
||||
|
||||
// Gemini expects the signature on the functionCall part itself.
|
||||
// If we are in a mode where missing signatures cause 400s (and we couldn't find one),
|
||||
// safely default to the bypass string to protect against 400s.
|
||||
// safely default to the bypass string to protect against 400s. The bypass sentinel is
|
||||
// an audit-trail risk (a magic validator-bypass string upstream could log/flag), so
|
||||
// operators can disable it via ANTIGRAVITY_ALLOW_SIGNATURE_BYPASS=0 — real signatures
|
||||
// are always preferred; the sentinel only fills the gap when none is available.
|
||||
const signatureBypassEnabled =
|
||||
toolNameOptions.supportsSignatureBypass &&
|
||||
signaturelessToolCallMode !== "text" &&
|
||||
process.env.ANTIGRAVITY_ALLOW_SIGNATURE_BYPASS !== "0";
|
||||
const finalSignature =
|
||||
embeddedThoughtSignature ||
|
||||
(toolNameOptions.supportsSignatureBypass && signaturelessToolCallMode !== "text"
|
||||
? "skip_thought_signature_validator"
|
||||
: undefined);
|
||||
(signatureBypassEnabled ? "skip_thought_signature_validator" : undefined);
|
||||
parts.push({
|
||||
...(finalSignature ? { thoughtSignature: finalSignature } : {}),
|
||||
functionCall: {
|
||||
|
||||
@@ -112,7 +112,12 @@ async function onboardAntigravityUser(
|
||||
tierId: string,
|
||||
metadata: Record<string, string>
|
||||
): Promise<void> {
|
||||
for (let i = 0; i < 10; i++) {
|
||||
// Bounded onboarding: cap retries (was 10) and jitter the delay so a stuck
|
||||
// loop cannot look like scripted automation to the upstream (ban-safety).
|
||||
const MAX_ONBOARD_RETRIES = 3;
|
||||
const BASE_RETRY_MS = 3000;
|
||||
const JITTER_MS = 4000;
|
||||
for (let i = 0; i < MAX_ONBOARD_RETRIES; i++) {
|
||||
try {
|
||||
const response = await fetchFirstOk(
|
||||
config.onboardUserEndpoints,
|
||||
@@ -124,7 +129,7 @@ async function onboardAntigravityUser(
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
await new Promise((resolve) => setTimeout(resolve, 5000));
|
||||
await new Promise((resolve) => setTimeout(resolve, BASE_RETRY_MS + Math.random() * JITTER_MS));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
62
tests/unit/translator-antigravity-signature-bypass.test.ts
Normal file
62
tests/unit/translator-antigravity-signature-bypass.test.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
const { openaiToAntigravityRequest } =
|
||||
await import("../../open-sse/translator/request/openai-to-gemini.ts");
|
||||
|
||||
const body = {
|
||||
messages: [
|
||||
{ role: "user", content: "Use the terminal tool to echo hi." },
|
||||
{
|
||||
role: "assistant",
|
||||
tool_calls: [
|
||||
{
|
||||
id: "call_1",
|
||||
type: "function",
|
||||
function: { name: "terminal", arguments: JSON.stringify({ command: "echo hi" }) },
|
||||
},
|
||||
],
|
||||
},
|
||||
{ role: "tool", tool_call_id: "call_1", name: "terminal", content: "hi" },
|
||||
],
|
||||
tools: [
|
||||
{
|
||||
type: "function",
|
||||
function: {
|
||||
name: "terminal",
|
||||
description: "Run a shell command",
|
||||
parameters: { type: "object", properties: { command: { type: "string" } } },
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
function modelParts(model: string, b: unknown) {
|
||||
const envelope = openaiToAntigravityRequest(model, b, true) as {
|
||||
request: { contents: Array<{ role: string; parts: Array<Record<string, unknown>> }> };
|
||||
};
|
||||
const modelMsg = envelope.request.contents.find((c) => c.role === "model");
|
||||
assert.ok(modelMsg, "expected an assistant (model-role) message in the translation");
|
||||
return modelMsg.parts;
|
||||
}
|
||||
|
||||
test("antigravity multi-turn tool call carries the signature bypass sentinel by default", () => {
|
||||
const parts = modelParts("gemini-3.1-pro-low", body);
|
||||
const fc = parts.find((p) => p.functionCall);
|
||||
assert.ok(fc, "expected a functionCall part");
|
||||
assert.equal(fc.thoughtSignature, "skip_thought_signature_validator");
|
||||
});
|
||||
|
||||
test("ANTIGRAVITY_ALLOW_SIGNATURE_BYPASS=0 disables the sentinel", () => {
|
||||
const prev = process.env.ANTIGRAVITY_ALLOW_SIGNATURE_BYPASS;
|
||||
process.env.ANTIGRAVITY_ALLOW_SIGNATURE_BYPASS = "0";
|
||||
try {
|
||||
const parts = modelParts("gemini-3.1-pro-low", body);
|
||||
const fc = parts.find((p) => p.functionCall);
|
||||
assert.ok(fc, "expected a functionCall part");
|
||||
assert.equal(fc.thoughtSignature, undefined);
|
||||
} finally {
|
||||
if (prev === undefined) delete process.env.ANTIGRAVITY_ALLOW_SIGNATURE_BYPASS;
|
||||
else process.env.ANTIGRAVITY_ALLOW_SIGNATURE_BYPASS = prev;
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user