mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-12 02:02:13 +03:00
Merge remote-tracking branch 'origin/release/v3.8.50' into fix/docker-colocate-partial-trace
This commit is contained in:
@@ -126,6 +126,9 @@ export const PACK_ARTIFACT_ROOT_ALLOWED_EXACT_PATHS: string[] = [
|
||||
// #7802: imported by scripts/build/postinstall.mjs to repair tls-client-node's
|
||||
// native binary (chatgpt-web/claude-web/grok-web/lmarena/perplexity-web transport).
|
||||
"scripts/build/fixTlsClientNodeBinary.mjs",
|
||||
// #8859: imported by scripts/build/postinstall.mjs to repair playwright-core's
|
||||
// browser resolution on Termux/Android (no glibc, no bundled browsers).
|
||||
"scripts/build/fixPlaywrightAndroid.mjs",
|
||||
// #5227: imported at runtime by bin/cli/commands/serve.mjs (heap auto-calibration).
|
||||
"scripts/build/runtime-env.mjs",
|
||||
"scripts/build/sync-env.mjs",
|
||||
|
||||
@@ -63,6 +63,7 @@ export const INTENTIONALLY_INTERNAL = new Set([
|
||||
"optimizationSettings", // db-internal: imported by db/core.ts for SQLite PRAGMA application helpers that require the live adapter
|
||||
"pluginMetrics", // DEAD? (production): write path não foi conectado ainda (documentado no cabeçalho do módulo); testado por tests/unit/plugins-metrics.test.ts
|
||||
"prompts", // DEAD? (production): zero callers de produção encontrados; domínio domain/prompts.ts é independente; testado por tests/integration/proxy-pipeline.test.ts
|
||||
"probeUtils", // db-internal: importado so por db/core.ts (retryProbeIfTransient no caminho da corruption-probe, #9541); testado por tests/unit/probe-9541-repro.test.ts
|
||||
"providerNodeSelect", // db-internal: importado só por db/providers.ts (selectProviderNodeForConnection — lógica pura de seleção de provider node split do providers.ts, #4421)
|
||||
"providerStats", // intentionally-internal: src/app/api/provider-stats/route.ts
|
||||
"proxyLatency", // intentionally-internal: imported directly by src/lib/db/proxies.ts (anti-barrel, #6798)
|
||||
|
||||
@@ -107,6 +107,11 @@ export const COLLECTORS = [
|
||||
glob: "open-sse/services/__tests__/antigravity-quota-family.test.ts",
|
||||
sources: ["vitest.mcp.config.ts"],
|
||||
},
|
||||
// #8890 landed this suite here without wiring a runner, so it had never run once.
|
||||
{
|
||||
glob: "open-sse/services/__tests__/fail-fast-concurrency-gate.test.ts",
|
||||
sources: ["vitest.mcp.config.ts"],
|
||||
},
|
||||
{ glob: "tests/unit/autoCombo/**/*.test.ts", sources: ["vitest.mcp.config.ts"] },
|
||||
{ glob: "src/lib/memory/__tests__/generic-backend.test.ts", sources: ["vitest.mcp.config.ts"] },
|
||||
{ glob: "tests/unit/encryption.spec.ts", sources: ["vitest.mcp.config.ts"] },
|
||||
|
||||
5
scripts/devin-bridge/build
Executable file
5
scripts/devin-bridge/build
Executable file
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
source "$(dirname "$0")/common"
|
||||
bridge_prepare_sandbox
|
||||
docker compose -f "$BRIDGE_COMPOSE" --profile offline build
|
||||
12
scripts/devin-bridge/clean
Executable file
12
scripts/devin-bridge/clean
Executable file
@@ -0,0 +1,12 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
source "$(dirname "$0")/common"
|
||||
if [[ "${1:-}" == "--all" ]]; then
|
||||
docker compose -f "$BRIDGE_COMPOSE" --profile offline --profile live-devin down \
|
||||
--remove-orphans --volumes
|
||||
printf 'Containers, networks, and bridge-owned named volumes were removed.\n'
|
||||
else
|
||||
docker compose -f "$BRIDGE_COMPOSE" --profile offline --profile live-devin down \
|
||||
--remove-orphans
|
||||
printf 'Containers and networks stopped. Named auth/config volumes were preserved; use --all to remove them.\n'
|
||||
fi
|
||||
131
scripts/devin-bridge/common
Executable file
131
scripts/devin-bridge/common
Executable file
@@ -0,0 +1,131 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
BRIDGE_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
BRIDGE_COMPOSE="$BRIDGE_ROOT/docker/devin-bridge/compose.yml"
|
||||
BRIDGE_SANDBOX="$BRIDGE_ROOT/.sandbox"
|
||||
BRIDGE_GUARD_AUDIT_ROOT="$BRIDGE_SANDBOX/guard-audit"
|
||||
BRIDGE_CLAUDE_AUDIT="$BRIDGE_GUARD_AUDIT_ROOT/claude/egress.jsonl"
|
||||
BRIDGE_DEVIN_AUDIT="$BRIDGE_GUARD_AUDIT_ROOT/devin/egress.jsonl"
|
||||
BRIDGE_RUNTIME_POLICY="$BRIDGE_ROOT/scripts/devin-bridge/runtime-policy.mjs"
|
||||
bridge_prepare_sandbox() {
|
||||
mkdir -p "$BRIDGE_SANDBOX/home" "$BRIDGE_SANDBOX/test-data" \
|
||||
"$BRIDGE_SANDBOX/e2e-workspace" "$BRIDGE_SANDBOX/live-workspace" \
|
||||
"$BRIDGE_SANDBOX/evidence" "$BRIDGE_GUARD_AUDIT_ROOT/claude" \
|
||||
"$BRIDGE_GUARD_AUDIT_ROOT/devin"
|
||||
chmod 0777 "$BRIDGE_SANDBOX/e2e-workspace" "$BRIDGE_SANDBOX/live-workspace" \
|
||||
"$BRIDGE_SANDBOX/evidence"
|
||||
chmod 01777 "$BRIDGE_GUARD_AUDIT_ROOT/claude" "$BRIDGE_GUARD_AUDIT_ROOT/devin"
|
||||
}
|
||||
bridge_reset_guard_audit() {
|
||||
local audit_path="$1"
|
||||
local audit_dir
|
||||
local temp_path
|
||||
bridge_prepare_sandbox
|
||||
audit_dir="$(dirname "$audit_path")"
|
||||
temp_path="$(mktemp "$audit_dir/.egress.jsonl.XXXXXX")"
|
||||
chmod 0666 "$temp_path"
|
||||
mv -f "$temp_path" "$audit_path"
|
||||
}
|
||||
bridge_reset_claude_egress_audit() {
|
||||
bridge_reset_guard_audit "$BRIDGE_CLAUDE_AUDIT"
|
||||
}
|
||||
bridge_reset_devin_egress_audit() {
|
||||
bridge_reset_guard_audit "$BRIDGE_DEVIN_AUDIT"
|
||||
}
|
||||
bridge_reset_e2e_fixture() {
|
||||
bridge_prepare_sandbox
|
||||
cp -R "$BRIDGE_ROOT/tests/fixtures/devin-bridge/e2e-workspace/." \
|
||||
"$BRIDGE_SANDBOX/e2e-workspace/"
|
||||
rm -f "$BRIDGE_SANDBOX/e2e-workspace/.e2e-hook.log" \
|
||||
"$BRIDGE_SANDBOX/evidence/claude-stream.jsonl" \
|
||||
"$BRIDGE_SANDBOX/evidence/mock-acp.jsonl"
|
||||
bridge_reset_claude_egress_audit
|
||||
}
|
||||
bridge_reset_live_fixture() {
|
||||
bridge_prepare_sandbox
|
||||
cp -R "$BRIDGE_ROOT/tests/fixtures/devin-bridge/e2e-workspace/." \
|
||||
"$BRIDGE_SANDBOX/live-workspace/"
|
||||
rm -f "$BRIDGE_SANDBOX/live-workspace/.e2e-hook.log" \
|
||||
"$BRIDGE_SANDBOX/evidence/live-analysis.jsonl" \
|
||||
"$BRIDGE_SANDBOX/evidence/live-fix.jsonl" \
|
||||
"$BRIDGE_SANDBOX/evidence/live-command.jsonl" \
|
||||
"$BRIDGE_SANDBOX/evidence/live-models.json" \
|
||||
"$BRIDGE_SANDBOX/evidence/egress.jsonl"
|
||||
bridge_reset_claude_egress_audit
|
||||
bridge_reset_devin_egress_audit
|
||||
}
|
||||
bridge_test_env() {
|
||||
bridge_prepare_sandbox
|
||||
env HOME="$BRIDGE_SANDBOX/home" DATA_DIR="$BRIDGE_SANDBOX/test-data" SQLITE_FILE="$BRIDGE_SANDBOX/test-data/storage.sqlite" DEVIN_AGENTIC_HOME="$BRIDGE_SANDBOX/home" "$@"
|
||||
}
|
||||
|
||||
bridge_run_devin() {
|
||||
docker compose -f "$BRIDGE_COMPOSE" --profile live-devin run --rm --no-deps \
|
||||
omniroute-live sh -ceu '
|
||||
trusted_proxy=http://network-guard:8080
|
||||
test "${DEVIN_BRIDGE_PROXY_URL:-}" = "$trusted_proxy"
|
||||
export HTTP_PROXY="$trusted_proxy" HTTPS_PROXY="$trusted_proxy"
|
||||
unset ALL_PROXY NO_PROXY http_proxy https_proxy all_proxy no_proxy
|
||||
exec devin "$@"
|
||||
' bridge-devin "$@"
|
||||
}
|
||||
|
||||
bridge_assert_devin_auth_status() {
|
||||
local exit_status="$1"
|
||||
local output="$2"
|
||||
printf '%s' "$output" | node --input-type=module -e '
|
||||
import { pathToFileURL } from "node:url";
|
||||
import fs from "node:fs";
|
||||
const policy = await import(pathToFileURL(process.argv[1]));
|
||||
const result = policy.validateDevinAuthStatus(process.argv[2], fs.readFileSync(0, "utf8"));
|
||||
if (!result.ok) throw new Error(result.error);
|
||||
' "$BRIDGE_RUNTIME_POLICY" "$exit_status"
|
||||
}
|
||||
|
||||
bridge_check_devin_auth() {
|
||||
local output
|
||||
local exit_status
|
||||
set +e
|
||||
output="$(bridge_run_devin auth status 2>&1)"
|
||||
exit_status=$?
|
||||
set -e
|
||||
bridge_assert_devin_auth_status "$exit_status" "$output"
|
||||
printf 'PASS: Devin authentication confirmed\n'
|
||||
}
|
||||
|
||||
bridge_assert_zero_claude_egress() {
|
||||
local audit_path="$1"
|
||||
bridge_validate_guard_audit claude-zero "$audit_path"
|
||||
}
|
||||
|
||||
bridge_assert_claude_guard_denials() {
|
||||
local audit_path="$1"
|
||||
bridge_validate_guard_audit claude-denials "$audit_path"
|
||||
}
|
||||
|
||||
bridge_assert_devin_guard_audit() {
|
||||
local audit_path="$1"
|
||||
bridge_validate_guard_audit devin-allowed "$audit_path"
|
||||
}
|
||||
|
||||
bridge_validate_guard_audit() {
|
||||
local kind="$1"
|
||||
local audit_path="$2"
|
||||
node --input-type=module -e '
|
||||
import { pathToFileURL } from "node:url";
|
||||
const policy = await import(pathToFileURL(process.argv[1]));
|
||||
policy.validateAuditFile(process.argv[2], process.argv[3], process.argv[4]);
|
||||
' "$BRIDGE_RUNTIME_POLICY" "$kind" "$audit_path" "$(id -u)"
|
||||
}
|
||||
|
||||
bridge_export_guard_audit() {
|
||||
local audit_path="$1"
|
||||
local evidence_name="$2"
|
||||
cp "$audit_path" "$BRIDGE_SANDBOX/evidence/$evidence_name"
|
||||
chmod 0644 "$BRIDGE_SANDBOX/evidence/$evidence_name"
|
||||
}
|
||||
|
||||
bridge_cleanup_compose() {
|
||||
docker compose -f "$BRIDGE_COMPOSE" --profile offline --profile live-devin \
|
||||
down --remove-orphans >/dev/null 2>&1 || true
|
||||
}
|
||||
28
scripts/devin-bridge/launch
Executable file
28
scripts/devin-bridge/launch
Executable file
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
source "$(dirname "$0")/common"
|
||||
trap bridge_cleanup_compose EXIT
|
||||
bridge_cleanup_compose
|
||||
bridge_prepare_sandbox
|
||||
"$(dirname "$0")/verify-anthropic-isolation"
|
||||
bridge_reset_claude_egress_audit
|
||||
bridge_reset_devin_egress_audit
|
||||
docker compose -f "$BRIDGE_COMPOSE" --profile live-devin up -d --wait network-guard claude-egress-guard
|
||||
bridge_check_devin_auth
|
||||
bridge_run_devin models list --format json >"$BRIDGE_SANDBOX/evidence/live-models.json"
|
||||
devin_model="$(node --import tsx/esm "$BRIDGE_ROOT/scripts/devin-bridge/select-live-model.mjs" \
|
||||
<"$BRIDGE_SANDBOX/evidence/live-models.json")"
|
||||
export DEVIN_BRIDGE_MODEL="devin-cli-agentic/$devin_model"
|
||||
export DEVIN_BRIDGE_SONNET_MODEL="${DEVIN_BRIDGE_SONNET_MODEL:-$DEVIN_BRIDGE_MODEL}"
|
||||
export DEVIN_BRIDGE_OPUS_MODEL="${DEVIN_BRIDGE_OPUS_MODEL:-$DEVIN_BRIDGE_MODEL}"
|
||||
export DEVIN_BRIDGE_HAIKU_MODEL="${DEVIN_BRIDGE_HAIKU_MODEL:-$DEVIN_BRIDGE_MODEL}"
|
||||
export DEVIN_BRIDGE_SUBAGENT_MODEL="${DEVIN_BRIDGE_SUBAGENT_MODEL:-$DEVIN_BRIDGE_MODEL}"
|
||||
docker compose -f "$BRIDGE_COMPOSE" --profile live-devin up -d --wait omniroute-live
|
||||
docker compose -f "$BRIDGE_COMPOSE" --profile live-devin run --rm --no-deps \
|
||||
claude-live claude
|
||||
bridge_cleanup_compose
|
||||
bridge_assert_devin_guard_audit "$BRIDGE_DEVIN_AUDIT"
|
||||
bridge_assert_zero_claude_egress "$BRIDGE_CLAUDE_AUDIT"
|
||||
bridge_export_guard_audit "$BRIDGE_DEVIN_AUDIT" egress.jsonl
|
||||
bridge_export_guard_audit "$BRIDGE_CLAUDE_AUDIT" claude-egress.jsonl
|
||||
trap - EXIT
|
||||
13
scripts/devin-bridge/login-devin
Executable file
13
scripts/devin-bridge/login-devin
Executable file
@@ -0,0 +1,13 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
source "$(dirname "$0")/common"
|
||||
[[ "${ENABLE_LIVE_DEVIN_TESTS:-}" == 1 ]] || { echo 'Set ENABLE_LIVE_DEVIN_TESTS=1' >&2; exit 1; }
|
||||
trap bridge_cleanup_compose EXIT
|
||||
bridge_cleanup_compose
|
||||
bridge_prepare_sandbox
|
||||
bridge_reset_devin_egress_audit
|
||||
docker compose -f "$BRIDGE_COMPOSE" --profile live-devin up -d --wait network-guard
|
||||
bridge_run_devin auth login --force-manual-token-flow
|
||||
bridge_cleanup_compose
|
||||
trap - EXIT
|
||||
exec env ENABLE_LIVE_DEVIN_TESTS=1 "$(dirname "$0")/test-live-devin"
|
||||
111
scripts/devin-bridge/runtime-policy.mjs
Normal file
111
scripts/devin-bridge/runtime-policy.mjs
Normal file
@@ -0,0 +1,111 @@
|
||||
import fs from "node:fs";
|
||||
|
||||
const ALLOWED_DEVIN_SUFFIXES = [".devin.ai", ".cognition.ai"];
|
||||
const ALLOWED_DEVIN_EXACT = ["server.codeium.com", "unleash.codeium.com"];
|
||||
|
||||
function normalizedHostname(value) {
|
||||
return String(value || "")
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replace(/\.$/, "");
|
||||
}
|
||||
|
||||
export function isAllowedDevinAuditHostname(hostname) {
|
||||
const value = normalizedHostname(hostname);
|
||||
return (
|
||||
ALLOWED_DEVIN_EXACT.includes(value) ||
|
||||
ALLOWED_DEVIN_SUFFIXES.some((suffix) => value === suffix.slice(1) || value.endsWith(suffix))
|
||||
);
|
||||
}
|
||||
|
||||
export function validateDevinAuthStatus(exitStatus, output) {
|
||||
if (Number(exitStatus) !== 0) return { ok: false, error: "auth status command failed" };
|
||||
const lines = String(output)
|
||||
.split(/\r?\n/)
|
||||
.map((line) => line.trim());
|
||||
if (!lines.some((line) => /^Logged in \(via Devin\)\.?$/.test(line))) {
|
||||
return { ok: false, error: "auth status did not confirm login" };
|
||||
}
|
||||
if (lines.some((line) => /failed to fetch from server/i.test(line))) {
|
||||
return { ok: false, error: "auth status could not confirm server access" };
|
||||
}
|
||||
return { ok: true };
|
||||
}
|
||||
|
||||
export function parseAuditEntries(text) {
|
||||
const lines = String(text)
|
||||
.split(/\r?\n/)
|
||||
.filter((line) => line.trim().length > 0);
|
||||
return lines.map((line) => JSON.parse(line));
|
||||
}
|
||||
|
||||
export function validateZeroClaudeEgress(text) {
|
||||
if (String(text).length !== 0) {
|
||||
return { ok: false, error: "Claude attempted external egress during the real run" };
|
||||
}
|
||||
return { ok: true };
|
||||
}
|
||||
|
||||
export function validateClaudeGuardDenials(text) {
|
||||
const entries = parseAuditEntries(text);
|
||||
if (!entries.length) return { ok: false, error: "Claude egress audit has no records" };
|
||||
if (entries.some((entry) => entry.decision !== "deny")) {
|
||||
return { ok: false, error: "Claude egress audit contains a non-deny decision" };
|
||||
}
|
||||
for (const hostname of ["api.anthropic.com", "claude.ai"]) {
|
||||
if (!entries.some((entry) => entry.hostname === hostname && entry.decision === "deny")) {
|
||||
return { ok: false, error: `Claude egress audit is missing deny for ${hostname}` };
|
||||
}
|
||||
}
|
||||
return { ok: true };
|
||||
}
|
||||
|
||||
export function validateDevinGuardAudit(text) {
|
||||
const entries = parseAuditEntries(text);
|
||||
if (!entries.length) return { ok: false, error: "Devin egress audit has no records" };
|
||||
let sawAllowedDevinRequest = false;
|
||||
for (const entry of entries) {
|
||||
if (entry.decision === "deny") {
|
||||
if (/anthropic|claude\.ai/i.test(normalizedHostname(entry.hostname))) {
|
||||
return { ok: false, error: `forbidden Devin egress attempt: ${String(entry.hostname)}` };
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (entry.decision !== "allow" || !isAllowedDevinAuditHostname(entry.hostname)) {
|
||||
return { ok: false, error: `unexpected Devin egress record: ${String(entry.hostname)}` };
|
||||
}
|
||||
sawAllowedDevinRequest = true;
|
||||
}
|
||||
return sawAllowedDevinRequest
|
||||
? { ok: true }
|
||||
: { ok: false, error: "Devin egress audit has no approved request" };
|
||||
}
|
||||
|
||||
export function validateAuditFileStat(stat, expectedUid) {
|
||||
if (!stat || !stat.isFile() || stat.isSymbolicLink()) return "audit path is not a regular file";
|
||||
if (stat.nlink !== 1) return "audit file link count is not one";
|
||||
if (stat.uid !== Number(expectedUid)) return "audit file owner mismatch";
|
||||
if ((stat.mode & 0o777) !== 0o666) return "audit file mode mismatch";
|
||||
return null;
|
||||
}
|
||||
|
||||
export function readValidatedAuditFile(path, expectedUid) {
|
||||
const stat = fs.lstatSync(path);
|
||||
const statError = validateAuditFileStat(stat, expectedUid);
|
||||
if (statError) throw new Error(statError);
|
||||
return fs.readFileSync(path, "utf8");
|
||||
}
|
||||
|
||||
export function validateAuditFile(kind, path, expectedUid) {
|
||||
const text = readValidatedAuditFile(path, expectedUid);
|
||||
const result =
|
||||
kind === "claude-zero"
|
||||
? validateZeroClaudeEgress(text)
|
||||
: kind === "claude-denials"
|
||||
? validateClaudeGuardDenials(text)
|
||||
: kind === "devin-allowed"
|
||||
? validateDevinGuardAudit(text)
|
||||
: { ok: false, error: `unknown audit validation kind: ${kind}` };
|
||||
if (!result.ok) throw new Error(result.error);
|
||||
return text;
|
||||
}
|
||||
101
scripts/devin-bridge/select-live-model.mjs
Normal file
101
scripts/devin-bridge/select-live-model.mjs
Normal file
@@ -0,0 +1,101 @@
|
||||
#!/usr/bin/env node
|
||||
import fs from "node:fs";
|
||||
import { pathToFileURL } from "node:url";
|
||||
import { DEVIN_MODEL_CATALOG } from "../../open-sse/config/providers/registry/devin/catalog.ts";
|
||||
|
||||
const candidateFields = new Set([
|
||||
"model_id",
|
||||
"modelId",
|
||||
"model_uid",
|
||||
"modelUid",
|
||||
"family_uid",
|
||||
"familyUid",
|
||||
]);
|
||||
|
||||
function normalizeModelId(value) {
|
||||
return value
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9]+/g, "-")
|
||||
.replace(/^-+|-+$/g, "");
|
||||
}
|
||||
|
||||
function collect(value, candidates) {
|
||||
if (Array.isArray(value)) {
|
||||
value.forEach((item) => collect(item, candidates));
|
||||
return;
|
||||
}
|
||||
if (!value || typeof value !== "object") return;
|
||||
for (const [key, nested] of Object.entries(value)) {
|
||||
if (
|
||||
typeof nested === "string" &&
|
||||
candidateFields.has(key) &&
|
||||
/^[a-z0-9][a-z0-9._/-]*$/i.test(nested)
|
||||
) {
|
||||
candidates.push(nested);
|
||||
}
|
||||
collect(nested, candidates);
|
||||
}
|
||||
}
|
||||
|
||||
export function selectLiveModel(
|
||||
document,
|
||||
environment = process.env,
|
||||
catalog = DEVIN_MODEL_CATALOG
|
||||
) {
|
||||
const candidates = [];
|
||||
collect(document, candidates);
|
||||
const unique = [...new Set(candidates)];
|
||||
const normalizedCatalog = new Map();
|
||||
for (const entry of catalog) {
|
||||
const normalized = normalizeModelId(entry.id);
|
||||
const existing = normalizedCatalog.get(normalized) || [];
|
||||
existing.push(entry.id);
|
||||
normalizedCatalog.set(normalized, existing);
|
||||
}
|
||||
for (const [normalized, ids] of normalizedCatalog) {
|
||||
if (ids.length > 1) {
|
||||
throw new Error(
|
||||
`Ambiguous OmniRoute catalog normalization for ${normalized}: ${ids.join(", ")}`
|
||||
);
|
||||
}
|
||||
}
|
||||
const catalogIds = new Set(catalog.map((entry) => entry.id));
|
||||
const available = [
|
||||
...new Set(
|
||||
unique
|
||||
.map((candidate) => normalizeModelId(candidate))
|
||||
.filter((candidate) => normalizedCatalog.has(candidate))
|
||||
),
|
||||
];
|
||||
|
||||
for (const [name, configured] of [
|
||||
["DEVIN_BRIDGE_SONNET_MODEL", environment.DEVIN_BRIDGE_SONNET_MODEL],
|
||||
["DEVIN_BRIDGE_OPUS_MODEL", environment.DEVIN_BRIDGE_OPUS_MODEL],
|
||||
["DEVIN_BRIDGE_HAIKU_MODEL", environment.DEVIN_BRIDGE_HAIKU_MODEL],
|
||||
["DEVIN_BRIDGE_SUBAGENT_MODEL", environment.DEVIN_BRIDGE_SUBAGENT_MODEL],
|
||||
]) {
|
||||
if (!configured) continue;
|
||||
const prefix = "devin-cli-agentic/";
|
||||
const modelId = configured.startsWith(prefix) ? configured.slice(prefix.length) : "";
|
||||
if (!modelId || !catalogIds.has(modelId) || !available.includes(modelId)) {
|
||||
throw new Error(`${name} is not a model returned by Devin and present in OmniRoute`);
|
||||
}
|
||||
}
|
||||
|
||||
const selected =
|
||||
available.find((candidate) => candidate === "swe-1-7-lightning") ||
|
||||
available.find((candidate) => candidate === "swe-1-7") ||
|
||||
available.find((candidate) => /swe|claude|gpt|gemini/i.test(candidate)) ||
|
||||
available[0];
|
||||
|
||||
if (!selected) {
|
||||
throw new Error("Devin returned no model identifier present in OmniRoute's Devin catalog");
|
||||
}
|
||||
return selected;
|
||||
}
|
||||
|
||||
if (process.argv[1] && pathToFileURL(process.argv[1]).href === import.meta.url) {
|
||||
const document = JSON.parse(fs.readFileSync(0, "utf8"));
|
||||
process.stdout.write(selectLiveModel(document));
|
||||
}
|
||||
24
scripts/devin-bridge/test-contract
Executable file
24
scripts/devin-bridge/test-contract
Executable file
@@ -0,0 +1,24 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
source "$(dirname "$0")/common"
|
||||
bridge_prepare_sandbox
|
||||
rm -f "$BRIDGE_SANDBOX/evidence/mock-acp.jsonl"
|
||||
"$(dirname "$0")/verify-anthropic-isolation" --static
|
||||
docker compose -f "$BRIDGE_COMPOSE" --profile offline down --remove-orphans
|
||||
docker compose -f "$BRIDGE_COMPOSE" --profile offline up --abort-on-container-exit \
|
||||
--exit-code-from contract contract
|
||||
node -e '
|
||||
const fs = require("node:fs");
|
||||
const rows = fs.readFileSync(process.argv[1], "utf8").trim().split("\n").map(JSON.parse);
|
||||
const repairRows = rows.filter((row) => row.scenario === "narrative-repair");
|
||||
if (
|
||||
rows.length !== 7 ||
|
||||
rows.some((row) => row.provider !== "devin-cli-agentic") ||
|
||||
repairRows.length !== 2 ||
|
||||
repairRows[0].stage !== "initial" ||
|
||||
repairRows[1].stage !== "repair"
|
||||
) {
|
||||
throw new Error("wire contract observed a missing or non-Devin provider");
|
||||
}
|
||||
' "$BRIDGE_SANDBOX/evidence/mock-acp.jsonl"
|
||||
printf 'PASS: bridge wire contract suite completed without provider fallback\n'
|
||||
16
scripts/devin-bridge/test-e2e-mock
Executable file
16
scripts/devin-bridge/test-e2e-mock
Executable file
@@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
source "$(dirname "$0")/common"
|
||||
trap bridge_cleanup_compose EXIT
|
||||
bridge_cleanup_compose
|
||||
bridge_reset_e2e_fixture
|
||||
"$(dirname "$0")/verify-anthropic-isolation" --static
|
||||
docker compose -f "$BRIDGE_COMPOSE" --profile offline up --abort-on-container-exit \
|
||||
--exit-code-from claude claude
|
||||
grep -q '"action":"final"' "$BRIDGE_SANDBOX/evidence/mock-acp.jsonl"
|
||||
grep -q 'BRIDGE_E2E_COMPLETE' "$BRIDGE_SANDBOX/evidence/claude-stream.jsonl"
|
||||
bridge_cleanup_compose
|
||||
bridge_assert_zero_claude_egress "$BRIDGE_CLAUDE_AUDIT"
|
||||
bridge_export_guard_audit "$BRIDGE_CLAUDE_AUDIT" claude-egress.jsonl
|
||||
trap - EXIT
|
||||
printf 'PASS: real Claude Code completed the offline agentic fixture\n'
|
||||
37
scripts/devin-bridge/test-live-devin
Executable file
37
scripts/devin-bridge/test-live-devin
Executable file
@@ -0,0 +1,37 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
source "$(dirname "$0")/common"
|
||||
[[ "${ENABLE_LIVE_DEVIN_TESTS:-}" == 1 ]] || { echo 'Set ENABLE_LIVE_DEVIN_TESTS=1' >&2; exit 1; }
|
||||
trap bridge_cleanup_compose EXIT
|
||||
bridge_cleanup_compose
|
||||
bridge_reset_live_fixture
|
||||
"$(dirname "$0")/verify-anthropic-isolation" --static
|
||||
docker compose -f "$BRIDGE_COMPOSE" --profile live-devin up -d --wait network-guard
|
||||
bridge_check_devin_auth
|
||||
models_file="$BRIDGE_SANDBOX/evidence/live-models.json"
|
||||
if [[ -n "${DEVIN_BRIDGE_DISCOVERED_MODEL:-}" ]]; then
|
||||
devin_model="$DEVIN_BRIDGE_DISCOVERED_MODEL"
|
||||
else
|
||||
for attempt in 1 2 3; do
|
||||
if bridge_run_devin models list --format json >"$models_file"; then
|
||||
break
|
||||
fi
|
||||
[[ "$attempt" == 3 ]] && exit 1
|
||||
sleep 1
|
||||
done
|
||||
devin_model="$(node --import tsx/esm "$BRIDGE_ROOT/scripts/devin-bridge/select-live-model.mjs" \
|
||||
<"$models_file")"
|
||||
fi
|
||||
export DEVIN_BRIDGE_MODEL="devin-cli-agentic/$devin_model"
|
||||
export DEVIN_BRIDGE_SONNET_MODEL="$DEVIN_BRIDGE_MODEL"
|
||||
export DEVIN_BRIDGE_OPUS_MODEL="$DEVIN_BRIDGE_MODEL"
|
||||
export DEVIN_BRIDGE_HAIKU_MODEL="$DEVIN_BRIDGE_MODEL"
|
||||
export DEVIN_BRIDGE_SUBAGENT_MODEL="$DEVIN_BRIDGE_MODEL"
|
||||
docker compose -f "$BRIDGE_COMPOSE" --profile live-devin up --abort-on-container-exit --exit-code-from claude-live claude-live
|
||||
bridge_cleanup_compose
|
||||
bridge_assert_devin_guard_audit "$BRIDGE_DEVIN_AUDIT"
|
||||
bridge_assert_zero_claude_egress "$BRIDGE_CLAUDE_AUDIT"
|
||||
bridge_export_guard_audit "$BRIDGE_DEVIN_AUDIT" egress.jsonl
|
||||
bridge_export_guard_audit "$BRIDGE_CLAUDE_AUDIT" claude-egress.jsonl
|
||||
trap - EXIT
|
||||
printf 'PASS: live model %s was discovered and validated by three scenarios\n' "$devin_model"
|
||||
9
scripts/devin-bridge/test-unit
Executable file
9
scripts/devin-bridge/test-unit
Executable file
@@ -0,0 +1,9 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
source "$(dirname "$0")/common"
|
||||
cd "$BRIDGE_ROOT"
|
||||
bridge_test_env node --import tsx/esm --test \
|
||||
tests/unit/executor-devin-cli-agentic-core.test.ts \
|
||||
tests/unit/executor-devin-cli-agentic-acp.test.ts \
|
||||
tests/unit/devin-bridge-network-guard.test.ts \
|
||||
tests/unit/devin-bridge-live-runtime.test.ts
|
||||
125
scripts/devin-bridge/validate-claude-evidence.mjs
Normal file
125
scripts/devin-bridge/validate-claude-evidence.mjs
Normal file
@@ -0,0 +1,125 @@
|
||||
#!/usr/bin/env node
|
||||
import fs from "node:fs";
|
||||
import { pathToFileURL } from "node:url";
|
||||
|
||||
function contentBlocks(message) {
|
||||
return Array.isArray(message?.message?.content) ? message.message.content : [];
|
||||
}
|
||||
|
||||
export function validateClaudeEvidenceText(text, options) {
|
||||
const marker = String(options?.marker || "").trim();
|
||||
const requiredTools = Array.isArray(options?.requiredTools) ? options.requiredTools : [];
|
||||
if (!marker) throw new Error("A final marker is required");
|
||||
|
||||
const toolUses = new Map();
|
||||
const successfulResults = new Set();
|
||||
const slashCommands = new Set();
|
||||
const skills = new Set();
|
||||
let finalResult = null;
|
||||
|
||||
for (const [index, rawLine] of String(text).split(/\r?\n/).entries()) {
|
||||
const line = rawLine.trim();
|
||||
if (!line) continue;
|
||||
let event;
|
||||
try {
|
||||
event = JSON.parse(line);
|
||||
} catch {
|
||||
throw new Error(`Invalid Claude evidence JSON at line ${index + 1}`);
|
||||
}
|
||||
|
||||
if (event?.type === "system" && event?.subtype === "init") {
|
||||
for (const command of Array.isArray(event.slash_commands) ? event.slash_commands : []) {
|
||||
slashCommands.add(String(command));
|
||||
}
|
||||
for (const skill of Array.isArray(event.skills) ? event.skills : []) {
|
||||
skills.add(String(skill));
|
||||
}
|
||||
}
|
||||
|
||||
for (const block of contentBlocks(event)) {
|
||||
if (block?.type === "tool_use" && typeof block.id === "string") {
|
||||
toolUses.set(block.id, { name: String(block.name || ""), input: block.input || {} });
|
||||
}
|
||||
if (
|
||||
block?.type === "tool_result" &&
|
||||
typeof block.tool_use_id === "string" &&
|
||||
block.is_error !== true
|
||||
) {
|
||||
successfulResults.add(block.tool_use_id);
|
||||
}
|
||||
}
|
||||
|
||||
if (event?.type === "result") finalResult = event;
|
||||
}
|
||||
|
||||
if (!finalResult || finalResult.subtype !== "success" || finalResult.is_error === true) {
|
||||
throw new Error("Claude evidence has no successful terminal result");
|
||||
}
|
||||
const resultText = String(finalResult.result || "");
|
||||
const incompleteResult = [
|
||||
/(?:^|\n)\s*(?:\*\*)?blocker(?:\*\*)?\s*:/im,
|
||||
/\btask (?:is|remains) (?:not complete|incomplete)\b/i,
|
||||
/(?:^|\n)\s*(?:[-*]\s*)?(?:\*\*)?next steps? needed(?:\*\*)?\s*:/im,
|
||||
].some((pattern) => pattern.test(resultText));
|
||||
if (incompleteResult) {
|
||||
throw new Error("Claude terminal result explicitly reports incomplete work");
|
||||
}
|
||||
if (options?.requiredSlashCommand && !slashCommands.has(options.requiredSlashCommand)) {
|
||||
throw new Error(`Claude did not load required slash command: ${options.requiredSlashCommand}`);
|
||||
}
|
||||
if (options?.requiredSkill && !skills.has(options.requiredSkill)) {
|
||||
throw new Error(`Claude did not load required skill: ${options.requiredSkill}`);
|
||||
}
|
||||
const markerIsStandalone = resultText.split(/\r?\n/).some((line) => line.trim() === marker);
|
||||
|
||||
for (const requiredTool of requiredTools) {
|
||||
if (![...toolUses.values()].some((tool) => tool.name === requiredTool)) {
|
||||
throw new Error(`Claude did not request required client-owned tool: ${requiredTool}`);
|
||||
}
|
||||
}
|
||||
|
||||
const npmTestSucceeded = [...toolUses.entries()].some(
|
||||
([id, tool]) =>
|
||||
tool.name === "Bash" &&
|
||||
/\bnpm\s+test\b/.test(String(tool.input?.command || "")) &&
|
||||
successfulResults.has(id)
|
||||
);
|
||||
if (options?.requireSuccessfulNpmTest) {
|
||||
if (!npmTestSucceeded) throw new Error("Claude evidence has no successful npm test tool turn");
|
||||
}
|
||||
const markerIsCorroborated =
|
||||
options?.requireSuccessfulNpmTest && npmTestSucceeded && resultText.includes(marker);
|
||||
const explicitCompletionIsCorroborated =
|
||||
options?.acceptExplicitCompletion === true &&
|
||||
npmTestSucceeded &&
|
||||
/\btask is complete\b/i.test(resultText);
|
||||
if (!markerIsStandalone && !markerIsCorroborated && !explicitCompletionIsCorroborated) {
|
||||
throw new Error(`Claude result has no standalone marker: ${marker}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (process.argv[1] && pathToFileURL(process.argv[1]).href === import.meta.url) {
|
||||
const [
|
||||
evidencePath,
|
||||
marker,
|
||||
requiredTools = "",
|
||||
requireNpmTest = "false",
|
||||
requiredSlashCommand = "",
|
||||
requiredSkill = "",
|
||||
acceptExplicitCompletion = "false",
|
||||
] = process.argv.slice(2);
|
||||
if (!evidencePath)
|
||||
throw new Error("Usage: validate-claude-evidence.mjs FILE MARKER [TOOLS] [NPM_TEST]");
|
||||
validateClaudeEvidenceText(fs.readFileSync(evidencePath, "utf8"), {
|
||||
marker,
|
||||
requiredTools: requiredTools
|
||||
.split(",")
|
||||
.map((value) => value.trim())
|
||||
.filter(Boolean),
|
||||
requireSuccessfulNpmTest: requireNpmTest === "true",
|
||||
requiredSlashCommand: requiredSlashCommand || undefined,
|
||||
requiredSkill: requiredSkill || undefined,
|
||||
acceptExplicitCompletion: acceptExplicitCompletion === "true",
|
||||
});
|
||||
process.stdout.write(`PASS: validated Claude evidence for ${marker}\n`);
|
||||
}
|
||||
259
scripts/devin-bridge/verify-anthropic-isolation
Executable file
259
scripts/devin-bridge/verify-anthropic-isolation
Executable file
@@ -0,0 +1,259 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
source "$(dirname "$0")/common"
|
||||
fail() { printf 'FAIL: %s\n' "$1" >&2; exit 1; }
|
||||
bridge_prepare_sandbox
|
||||
compose_config=(docker compose -f "$BRIDGE_COMPOSE" --env-file /dev/null --profile offline --profile live-devin config)
|
||||
config="$("${compose_config[@]}")"
|
||||
config_json="$("${compose_config[@]}" --format json)"
|
||||
for forbidden in "$HOME/.claude" "$HOME/.claude.json" "$HOME/.ssh" "/var/run/docker.sock"; do
|
||||
[[ "$config" != *"$forbidden"* ]] || fail "forbidden host mount appears in compose: $forbidden"
|
||||
done
|
||||
grep -q 'user: 10001:10001' <<<"$config" || fail "runtime is not non-root"
|
||||
grep -q 'read_only: true' <<<"$config" || fail "runtime root filesystem is not read-only"
|
||||
grep -q 'internal: true' <<<"$config" || fail "internal network is missing"
|
||||
grep -q 'CLAUDE_CONFIG_DIR: /home/bridge/.claude-devin-isolated' <<<"$config" || fail "isolated Claude config is missing"
|
||||
node -e '
|
||||
const fs = require("node:fs");
|
||||
const config = JSON.parse(fs.readFileSync(0, "utf8"));
|
||||
const liveNetworks = Object.keys(config.services["omniroute-live"].networks || {}).sort();
|
||||
if (JSON.stringify(liveNetworks) !== JSON.stringify(["bridge-internal", "devin-guard-internal"])) {
|
||||
throw new Error(`live runtime network escape: ${liveNetworks.join(",")}`);
|
||||
}
|
||||
const guardNetworks = Object.keys(config.services["network-guard"].networks || {}).sort();
|
||||
if (JSON.stringify(guardNetworks) !== JSON.stringify(["devin-guard-internal", "guard-egress"])) {
|
||||
throw new Error(`network guard topology mismatch: ${guardNetworks.join(",")}`);
|
||||
}
|
||||
const claudeGuard = config.services["claude-egress-guard"];
|
||||
if (JSON.stringify(Object.keys(claudeGuard.networks || {})) !== JSON.stringify(["bridge-internal"])) {
|
||||
throw new Error("Claude egress guard must remain on the internal network only");
|
||||
}
|
||||
if (config.services["network-guard"].environment.GUARD_POLICY !== "devin") {
|
||||
throw new Error("Devin network guard policy mismatch");
|
||||
}
|
||||
if (claudeGuard.environment.GUARD_POLICY !== "deny-all") {
|
||||
throw new Error("Claude egress guard is not deny-all");
|
||||
}
|
||||
for (const guardName of ["network-guard", "claude-egress-guard"]) {
|
||||
const guard = config.services[guardName];
|
||||
const env = config.services[guardName].environment;
|
||||
if (env.GUARD_ALLOW_SUFFIXES || env.GUARD_ALLOW_HOSTS) {
|
||||
throw new Error(`${guardName} exposes mutable host allowlists`);
|
||||
}
|
||||
if (!guard.healthcheck?.test) throw new Error(`${guardName} has no healthcheck`);
|
||||
const auditMount = (guard.volumes || []).find((mount) => mount.target === "/guard-audit");
|
||||
if (!auditMount || auditMount.type !== "bind" || !auditMount.source.includes("/.sandbox/guard-audit/")) {
|
||||
throw new Error(`${guardName} does not use its guard-only audit bind`);
|
||||
}
|
||||
}
|
||||
const runtimeNames = ["omniroute", "claude", "contract", "omniroute-live", "claude-live"];
|
||||
for (const serviceName of [...runtimeNames, "network-guard", "claude-egress-guard"]) {
|
||||
const service = config.services[serviceName];
|
||||
if (String(service.user) !== "10001:10001" || !service.read_only) {
|
||||
throw new Error(`${serviceName} is not non-root and read-only`);
|
||||
}
|
||||
}
|
||||
for (const serviceName of runtimeNames) {
|
||||
const service = config.services[serviceName];
|
||||
if ((service.volumes || []).some((mount) => mount.target === "/guard-audit")) {
|
||||
throw new Error(`${serviceName} can mutate guard audit evidence`);
|
||||
}
|
||||
const namedVolumes = (service.volumes || []).filter((mount) => mount.type === "volume");
|
||||
const hasClaudeConfig = namedVolumes.some(
|
||||
(mount) => mount.target === "/home/bridge/.claude-devin-isolated",
|
||||
);
|
||||
const hasDevinAuth = namedVolumes.some(
|
||||
(mount) => mount.target === "/home/bridge/.local/share/devin",
|
||||
);
|
||||
const expectsClaudeConfig = serviceName === "claude" || serviceName === "claude-live";
|
||||
const expectsDevinAuth = serviceName === "omniroute-live";
|
||||
if (hasClaudeConfig !== expectsClaudeConfig) {
|
||||
throw new Error(`${serviceName} Claude config volume ownership mismatch`);
|
||||
}
|
||||
if (hasDevinAuth !== expectsDevinAuth) {
|
||||
throw new Error(`${serviceName} Devin auth volume ownership mismatch`);
|
||||
}
|
||||
for (const key of [
|
||||
"ANTHROPIC_MODEL",
|
||||
"ANTHROPIC_DEFAULT_SONNET_MODEL",
|
||||
"ANTHROPIC_DEFAULT_OPUS_MODEL",
|
||||
"ANTHROPIC_DEFAULT_HAIKU_MODEL",
|
||||
"CLAUDE_CODE_SUBAGENT_MODEL",
|
||||
]) {
|
||||
if (!String(service.environment[key] || "").startsWith("devin-cli-agentic/")) {
|
||||
throw new Error(`${serviceName} has a non-Devin model alias in ${key}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (config.services["omniroute-live"].depends_on["network-guard"].condition !== "service_healthy") {
|
||||
throw new Error("omniroute-live does not wait for a healthy Devin guard");
|
||||
}
|
||||
for (const serviceName of ["claude", "claude-live"]) {
|
||||
if (config.services[serviceName].depends_on["claude-egress-guard"].condition !== "service_healthy") {
|
||||
throw new Error(`${serviceName} does not wait for a healthy Claude guard`);
|
||||
}
|
||||
}
|
||||
const liveEnv = config.services["omniroute-live"].environment;
|
||||
if (liveEnv.DEVIN_BRIDGE_PROXY_URL !== "http://network-guard:8080") {
|
||||
throw new Error("trusted Devin bridge proxy is missing");
|
||||
}
|
||||
for (const key of ["HTTP_PROXY", "HTTPS_PROXY", "ALL_PROXY", "NO_PROXY"]) {
|
||||
if (liveEnv[key]) throw new Error(`omniroute-live must not inherit ${key}`);
|
||||
}
|
||||
for (const serviceName of runtimeNames.filter((name) => name !== "omniroute-live")) {
|
||||
if (config.services[serviceName].environment.DEVIN_BRIDGE_PROXY_URL) {
|
||||
throw new Error(`${serviceName} received the Devin bridge proxy setting`);
|
||||
}
|
||||
}
|
||||
for (const serviceName of ["claude", "claude-live"]) {
|
||||
const env = config.services[serviceName].environment;
|
||||
if (
|
||||
env.NODE_USE_ENV_PROXY !== "1" ||
|
||||
env.HTTP_PROXY !== "http://claude-egress-guard:8080" ||
|
||||
env.HTTPS_PROXY !== "http://claude-egress-guard:8080" ||
|
||||
env.NO_PROXY !== "omniroute"
|
||||
) {
|
||||
throw new Error(`${serviceName} does not use the deny-all Claude guard`);
|
||||
}
|
||||
if (env.HTTP_PROXY === "http://network-guard:8080") {
|
||||
throw new Error(`${serviceName} received the Devin-capable guard`);
|
||||
}
|
||||
}
|
||||
' <<<"$config_json" || fail "structured compose isolation checks failed"
|
||||
node --input-type=module -e '
|
||||
import { pathToFileURL } from "node:url";
|
||||
const policy = await import(pathToFileURL(process.argv[1]));
|
||||
const allowed = [
|
||||
"devin.ai",
|
||||
"api.devin.ai",
|
||||
"cognition.ai",
|
||||
"api.cognition.ai",
|
||||
"server.codeium.com",
|
||||
"unleash.codeium.com",
|
||||
];
|
||||
const denied = [
|
||||
"evildevin.ai",
|
||||
"codeium.com",
|
||||
"api.codeium.com",
|
||||
"o123.ingest.sentry.io",
|
||||
"api.anthropic.com",
|
||||
"claude.ai",
|
||||
];
|
||||
for (const hostname of allowed) {
|
||||
if (!policy.isAllowedGuardHostname(hostname, "devin")) throw new Error(`denied ${hostname}`);
|
||||
}
|
||||
for (const hostname of denied) {
|
||||
if (policy.isAllowedGuardHostname(hostname, "devin")) throw new Error(`allowed ${hostname}`);
|
||||
}
|
||||
if (policy.isAllowedGuardHostname("api.devin.ai", "deny-all")) {
|
||||
throw new Error("deny-all guard allowed Devin traffic");
|
||||
}
|
||||
' "$BRIDGE_ROOT/docker/devin-bridge/network-guard/policy.mjs" || fail "network guard policy checks failed"
|
||||
bridge_test_env node --import tsx/esm --input-type=module -e '
|
||||
import { pathToFileURL } from "node:url";
|
||||
const { buildDevinChildEnv } = await import(pathToFileURL(process.argv[1]));
|
||||
const home = process.env.DEVIN_AGENTIC_HOME;
|
||||
const trusted = buildDevinChildEnv({}, {
|
||||
DEVIN_AGENTIC_HOME: home,
|
||||
DEVIN_BRIDGE_PROXY_URL: "http://network-guard:8080",
|
||||
HTTP_PROXY: "http://user:password@host-proxy.example:3128",
|
||||
HTTPS_PROXY: "http://user:password@host-proxy.example:3128",
|
||||
ALL_PROXY: "socks5://host-proxy.example:1080",
|
||||
});
|
||||
if (
|
||||
trusted.HTTP_PROXY !== "http://network-guard:8080" ||
|
||||
trusted.HTTPS_PROXY !== "http://network-guard:8080" ||
|
||||
trusted.ALL_PROXY
|
||||
) {
|
||||
throw new Error("trusted child proxy derivation failed");
|
||||
}
|
||||
const untrusted = buildDevinChildEnv({}, {
|
||||
DEVIN_AGENTIC_HOME: home,
|
||||
DEVIN_BRIDGE_PROXY_URL: "http://user:password@network-guard:8080",
|
||||
HTTP_PROXY: "http://host-proxy.example:3128",
|
||||
});
|
||||
if (untrusted.HTTP_PROXY || untrusted.HTTPS_PROXY) {
|
||||
throw new Error("untrusted child proxy was inherited");
|
||||
}
|
||||
' "$BRIDGE_ROOT/open-sse/executors/devin-cli-agentic.ts" || \
|
||||
fail "Devin child proxy boundary checks failed"
|
||||
bridge_assert_devin_auth_status 0 $'Logged in (via Devin)\n' || fail "clean auth fixture was rejected"
|
||||
if bridge_assert_devin_auth_status 0 $'Logged in (via Devin)\nFailed to fetch from server\n' 2>/dev/null; then
|
||||
fail "server-fetch auth failure was accepted"
|
||||
fi
|
||||
if bridge_assert_devin_auth_status 0 $'Logged out\n' 2>/dev/null; then
|
||||
fail "logged-out auth fixture was accepted"
|
||||
fi
|
||||
if bridge_assert_devin_auth_status 0 $'Not Logged in (via Devin)\n' 2>/dev/null; then
|
||||
fail "misleading auth fixture was accepted"
|
||||
fi
|
||||
selected_model="$(printf '%s' '{"models":[{"family_uid":"swe-1.7"},{"modelUid":"swe-1.7-lightning"}]}' | \
|
||||
node --import tsx/esm "$BRIDGE_ROOT/scripts/devin-bridge/select-live-model.mjs")"
|
||||
[[ "$selected_model" == swe-1-7-lightning ]] || fail "live model normalization or preference failed"
|
||||
if printf '%s' '{"models":[{"family_uid":"unknown.9"}]}' | \
|
||||
node --import tsx/esm "$BRIDGE_ROOT/scripts/devin-bridge/select-live-model.mjs" >/dev/null 2>&1; then
|
||||
fail "unknown normalized live model was accepted"
|
||||
fi
|
||||
grep -q 'bridge_run_devin auth login --force-manual-token-flow' \
|
||||
"$BRIDGE_ROOT/scripts/devin-bridge/login-devin" || fail "manual token login flow is missing"
|
||||
if grep -Eqi 'read[[:space:]].*token|printf[[:space:]].*token|echo[[:space:]].*token' \
|
||||
"$BRIDGE_ROOT/scripts/devin-bridge/login-devin"; then
|
||||
fail "login script could expose a token"
|
||||
fi
|
||||
grep -q 'bridge_check_devin_auth' "$BRIDGE_ROOT/scripts/devin-bridge/test-live-devin" || \
|
||||
fail "live test bypasses strict auth status"
|
||||
grep -q 'bridge_check_devin_auth' "$BRIDGE_ROOT/scripts/devin-bridge/launch" || \
|
||||
fail "normal launch bypasses strict auth status"
|
||||
grep -q 'up -d --wait network-guard claude-egress-guard' "$BRIDGE_ROOT/scripts/devin-bridge/launch" || \
|
||||
fail "normal launch does not start the audited Claude egress guard"
|
||||
grep -qx '\.sandbox' "$BRIDGE_ROOT/.dockerignore" || fail ".sandbox is not excluded from builds"
|
||||
if [[ "${1:-}" == --static ]]; then printf 'PASS: static bridge isolation checks passed\n'; exit 0; fi
|
||||
trap bridge_cleanup_compose EXIT
|
||||
bridge_cleanup_compose
|
||||
bridge_reset_claude_egress_audit
|
||||
docker compose -f "$BRIDGE_COMPOSE" --profile offline up -d --wait claude-egress-guard
|
||||
docker compose -f "$BRIDGE_COMPOSE" --profile offline run --rm --no-deps claude bash -ceu '
|
||||
test "$(id -u)" = 10001
|
||||
test "$HOME" = /home/bridge
|
||||
test "$CLAUDE_CONFIG_DIR" = /home/bridge/.claude-devin-isolated
|
||||
test "$ANTHROPIC_BASE_URL" = http://omniroute:20128
|
||||
test "$ANTHROPIC_AUTH_TOKEN" = sk-local-devin-gateway
|
||||
test -z "${ANTHROPIC_API_KEY:-}${CLAUDE_CODE_OAUTH_TOKEN:-}${AWS_ACCESS_KEY_ID:-}${AWS_SECRET_ACCESS_KEY:-}${GOOGLE_APPLICATION_CREDENTIALS:-}${AZURE_OPENAI_API_KEY:-}"
|
||||
test ! -e /var/run/docker.sock
|
||||
if touch /bridge-must-remain-read-only 2>/dev/null; then
|
||||
echo "container root filesystem is writable" >&2; exit 1
|
||||
fi
|
||||
for host in api.anthropic.com claude.ai; do
|
||||
if node -e "require(\"net\").connect(443,process.argv[1]).on(\"connect\",()=>process.exit(0)).on(\"error\",()=>process.exit(1)).setTimeout(1500,()=>process.exit(1))" "$host"; then
|
||||
echo "unexpected network access to $host" >&2; exit 1
|
||||
fi
|
||||
done
|
||||
'
|
||||
docker compose -f "$BRIDGE_COMPOSE" --profile offline run --rm --no-deps claude \
|
||||
node --input-type=module -e '
|
||||
async function expectProxyDenial(request) {
|
||||
try {
|
||||
const response = await request;
|
||||
if (response.status !== 403) {
|
||||
throw new Error(`unexpected proxy response: ${response.status}`);
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof Error && error.message.startsWith("unexpected proxy response:")) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
await expectProxyDenial(fetch("https://api.anthropic.com", {
|
||||
signal: AbortSignal.timeout(3000),
|
||||
}));
|
||||
await expectProxyDenial(fetch("https://claude.ai", {
|
||||
signal: AbortSignal.timeout(3000),
|
||||
}));
|
||||
'
|
||||
bridge_cleanup_compose
|
||||
bridge_assert_claude_guard_denials "$BRIDGE_CLAUDE_AUDIT" || \
|
||||
fail "Claude proxy denial audit proof failed"
|
||||
bridge_export_guard_audit "$BRIDGE_CLAUDE_AUDIT" claude-egress-verifier.jsonl
|
||||
trap - EXIT
|
||||
bridge_reset_claude_egress_audit
|
||||
printf 'PASS: runtime bridge isolation checks passed\n'
|
||||
100
scripts/raycast/extract-credentials.mjs
Normal file
100
scripts/raycast/extract-credentials.mjs
Normal file
@@ -0,0 +1,100 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* @file extract-credentials.mjs
|
||||
* @description Print Raycast Pro credentials from local macOS install (redacted preview).
|
||||
*
|
||||
* Usage: node scripts/raycast/extract-credentials.mjs
|
||||
*
|
||||
* @changes
|
||||
* - [2026-07-27] [Composer] - CLI credential extractor for local Raycast
|
||||
*/
|
||||
|
||||
import { execFileSync } from "node:child_process";
|
||||
import { createHash } from "node:crypto";
|
||||
import { copyFileSync, existsSync, mkdtempSync, readFileSync, rmdirSync, unlinkSync } from "node:fs";
|
||||
import { homedir, tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
|
||||
const RAYCAST_SALT = "yvkwWXzxPPBAqY2tmaKrB*DvYjjMaeEf";
|
||||
const RAYCAST_SUPPORT = join(homedir(), "Library", "Application Support", "com.raycast.macos");
|
||||
const RAYCAST_DB = join(RAYCAST_SUPPORT, "raycast-enc.sqlite");
|
||||
|
||||
function redact(s, keep = 8) {
|
||||
if (!s || s.length <= keep * 2) return "***";
|
||||
return `${s.slice(0, keep)}…${s.slice(-4)}`;
|
||||
}
|
||||
|
||||
function readKeychain(account) {
|
||||
return JSON.parse(
|
||||
execFileSync("security", ["find-generic-password", "-s", "Raycast", "-a", account, "-w"], {
|
||||
encoding: "utf-8",
|
||||
}).trim()
|
||||
);
|
||||
}
|
||||
|
||||
function dbPassphrase() {
|
||||
const keyHex = execFileSync(
|
||||
"security",
|
||||
["find-generic-password", "-s", "Raycast", "-a", "database_key", "-w"],
|
||||
{ encoding: "utf-8" }
|
||||
).trim();
|
||||
return createHash("sha256")
|
||||
.update(keyHex + RAYCAST_SALT)
|
||||
.digest("hex");
|
||||
}
|
||||
|
||||
function queryDb(sql) {
|
||||
const tmpDir = mkdtempSync(join(tmpdir(), "raycast-extract-"));
|
||||
const tmpDb = join(tmpDir, "db.sqlite");
|
||||
copyFileSync(RAYCAST_DB, tmpDb);
|
||||
for (const ext of ["-wal", "-shm"]) {
|
||||
const src = RAYCAST_DB + ext;
|
||||
if (existsSync(src)) copyFileSync(src, tmpDb + ext);
|
||||
}
|
||||
const passphrase = dbPassphrase();
|
||||
const input = `PRAGMA key = '${passphrase}';\n.mode json\n${sql}`;
|
||||
const out = execFileSync("sqlcipher", [tmpDb], { input, encoding: "utf-8" });
|
||||
for (const ext of ["", "-wal", "-shm"]) {
|
||||
try {
|
||||
unlinkSync(tmpDb + ext);
|
||||
} catch {}
|
||||
}
|
||||
try {
|
||||
rmdirSync(tmpDir);
|
||||
} catch {}
|
||||
const jsonStr = out.startsWith("ok\n") ? out.slice(3) : out;
|
||||
return JSON.parse(jsonStr.trim() || "[]");
|
||||
}
|
||||
|
||||
if (process.platform !== "darwin") {
|
||||
console.error("macOS only");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const store = readKeychain("raycast-store_credentials");
|
||||
const token = store?.oauth?.access_token;
|
||||
if (!token) {
|
||||
console.error("No Raycast bearer token in Keychain — open Raycast and sign in");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const users = queryDb("SELECT analyticsId, email, username, hasProFeatures, hasBetterAI FROM user LIMIT 1;");
|
||||
const user = users[0] || {};
|
||||
const deviceId =
|
||||
user.analyticsId ||
|
||||
JSON.parse(readFileSync(join(RAYCAST_SUPPORT, "posthog.distinctId"), "utf-8"))["posthog.distinctId"];
|
||||
|
||||
console.log(JSON.stringify({
|
||||
accessTokenPreview: redact(token),
|
||||
accessToken: token,
|
||||
deviceId,
|
||||
aid: deviceId,
|
||||
email: user.email || store?.user?.email,
|
||||
username: user.username || store?.user?.username,
|
||||
hasProFeatures: !!user.hasProFeatures,
|
||||
hasBetterAI: !!user.hasBetterAI,
|
||||
sources: {
|
||||
bearer: "Keychain Raycast / raycast-store_credentials",
|
||||
deviceId: "raycast-enc.sqlite user.analyticsId",
|
||||
},
|
||||
}, null, 2));
|
||||
165
scripts/raycast/usage-benchmark.mjs
Normal file
165
scripts/raycast/usage-benchmark.mjs
Normal file
@@ -0,0 +1,165 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* @file usage-benchmark.mjs
|
||||
* @description Battle-test Raycast Pro usage via OmniRoute local endpoint.
|
||||
*
|
||||
* Env (required):
|
||||
* OMNIROUTE_URL default http://127.0.0.1:20128/v1
|
||||
* OMNIROUTE_API_KEY OmniRoute API key (if REQUIRE_API_KEY)
|
||||
*
|
||||
* Env (optional — direct Raycast probe without OmniRoute):
|
||||
* RAYCAST_BEARER_TOKEN
|
||||
* RAYCAST_DEVICE_ID
|
||||
* RAYCAST_AID
|
||||
* RAYCAST_SIG_SECRET
|
||||
*
|
||||
* Usage:
|
||||
* node scripts/raycast/usage-benchmark.mjs --models 5 --rounds 3
|
||||
* node scripts/raycast/usage-benchmark.mjs --model openai-gpt-5-mini --rounds 10
|
||||
*
|
||||
* @changes
|
||||
* - [2026-07-27] [Composer] - Initial Raycast Pro usage benchmark script
|
||||
*/
|
||||
|
||||
import { createHmac, createHash } from "node:crypto";
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
function arg(name, fallback) {
|
||||
const i = args.indexOf(`--${name}`);
|
||||
return i >= 0 && args[i + 1] ? args[i + 1] : fallback;
|
||||
}
|
||||
|
||||
const rounds = Number(arg("rounds", "3"));
|
||||
const model = arg("model", "");
|
||||
const modelCount = Number(arg("models", "5"));
|
||||
const omnirouteUrl = (process.env.OMNIROUTE_URL || "http://127.0.0.1:20128/v1").replace(/\/$/, "");
|
||||
const apiKey = process.env.OMNIROUTE_API_KEY || "";
|
||||
|
||||
const RAYCAST_CHAT_URL = "https://backend.raycast.com/api/v1/ai/chat_completions";
|
||||
const RAYCAST_MODELS_URL = "https://backend.raycast.com/api/v1/ai/models";
|
||||
const SIG_SECRET =
|
||||
process.env.RAYCAST_SIG_SECRET ||
|
||||
"6bc455473576ce2cd6f70426caff867aabbe3f7291c1a79681af5e8ce0ca1408";
|
||||
|
||||
function rot13rot5(input) {
|
||||
return input.replace(/[A-Za-z0-9]/g, (char) => {
|
||||
const code = char.charCodeAt(0);
|
||||
if (code >= 65 && code <= 90) return String.fromCharCode(((code - 65 + 13) % 26) + 65);
|
||||
if (code >= 97 && code <= 122) return String.fromCharCode(((code - 97 + 13) % 26) + 97);
|
||||
return String.fromCharCode(((code - 48 + 5) % 10) + 48);
|
||||
});
|
||||
}
|
||||
|
||||
function signatureV2(timestamp, deviceId, payload, secret) {
|
||||
const bodyHash = createHash("sha256").update(payload).digest("hex");
|
||||
const message = [timestamp, deviceId, bodyHash].map(rot13rot5).join(".");
|
||||
return createHmac("sha256", secret).update(message).digest("hex");
|
||||
}
|
||||
|
||||
function raycastJwt(aid, secret) {
|
||||
const iat = Date.now() / 1000;
|
||||
const header = Buffer.from(JSON.stringify({ typ: "JWT", alg: "HS256" })).toString("base64url");
|
||||
const payload = Buffer.from(JSON.stringify({ aid, exp: iat + 60, iat })).toString("base64url");
|
||||
const signature = createHmac("sha256", secret)
|
||||
.update(`${header}.${payload}`)
|
||||
.digest("base64url");
|
||||
return `${header}.${payload}.${signature}`;
|
||||
}
|
||||
|
||||
function raycastHeaders(payload) {
|
||||
const bearerToken = process.env.RAYCAST_BEARER_TOKEN;
|
||||
const deviceId = process.env.RAYCAST_DEVICE_ID;
|
||||
const aid = process.env.RAYCAST_AID;
|
||||
if (!bearerToken || !deviceId || !aid) {
|
||||
throw new Error("Set RAYCAST_BEARER_TOKEN, RAYCAST_DEVICE_ID, RAYCAST_AID for direct probe");
|
||||
}
|
||||
const timestamp = Math.floor(Date.now() / 1000).toString();
|
||||
return {
|
||||
Accept: "application/json",
|
||||
Authorization: `Bearer ${bearerToken}`,
|
||||
"X-Raycast-Timestamp": timestamp,
|
||||
"X-Raycast-DeviceId": deviceId,
|
||||
"Content-Type": "application/json",
|
||||
"X-Raycast-Signature-v2": signatureV2(timestamp, deviceId, payload, SIG_SECRET),
|
||||
"X-Raycast-Signature": raycastJwt(aid, SIG_SECRET),
|
||||
"X-Raycast-Experimental": "chatBranching, mcpHTTPServer",
|
||||
"User-Agent": "Raycast/1.104.20 (macOS Version 26.5.1 (Build 25F80))",
|
||||
};
|
||||
}
|
||||
|
||||
async function fetchRaycastModels() {
|
||||
const payload = "{}";
|
||||
const res = await fetch(RAYCAST_MODELS_URL, { method: "GET", headers: raycastHeaders(payload) });
|
||||
const text = await res.text();
|
||||
if (!res.ok) throw new Error(`models [${res.status}]: ${text.slice(0, 200)}`);
|
||||
const data = JSON.parse(text);
|
||||
return (data.models || []).map((m) => m.id);
|
||||
}
|
||||
|
||||
async function chatOmniroute(modelId, prompt) {
|
||||
const headers = { "Content-Type": "application/json" };
|
||||
if (apiKey) headers.Authorization = `Bearer ${apiKey}`;
|
||||
const started = Date.now();
|
||||
const res = await fetch(`${omnirouteUrl}/chat/completions`, {
|
||||
method: "POST",
|
||||
headers,
|
||||
body: JSON.stringify({
|
||||
model: `raycast/${modelId}`,
|
||||
messages: [{ role: "user", content: prompt }],
|
||||
stream: false,
|
||||
max_tokens: 32,
|
||||
}),
|
||||
});
|
||||
const ms = Date.now() - started;
|
||||
const body = await res.text();
|
||||
return { ok: res.ok, status: res.status, ms, body: body.slice(0, 300) };
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log(`OmniRoute: ${omnirouteUrl}`);
|
||||
console.log(`Rounds per model: ${rounds}`);
|
||||
|
||||
let models = [];
|
||||
if (model) {
|
||||
models = [model];
|
||||
} else if (process.env.RAYCAST_BEARER_TOKEN) {
|
||||
models = (await fetchRaycastModels()).slice(0, modelCount);
|
||||
console.log(`Direct Raycast model probe — testing ${models.length} models via OmniRoute`);
|
||||
} else {
|
||||
models = ["openai-gpt-5-mini"];
|
||||
console.log("No RAYCAST_* env — using default model openai-gpt-5-mini via OmniRoute combo id");
|
||||
}
|
||||
|
||||
const results = [];
|
||||
for (const modelId of models) {
|
||||
let ok = 0;
|
||||
let fail = 0;
|
||||
const latencies = [];
|
||||
for (let i = 0; i < rounds; i++) {
|
||||
const prompt = `Raycast benchmark round ${i + 1} — reply with exactly: pong`;
|
||||
try {
|
||||
const r = await chatOmniroute(modelId, prompt);
|
||||
latencies.push(r.ms);
|
||||
if (r.ok) ok++;
|
||||
else {
|
||||
fail++;
|
||||
console.error(` FAIL ${modelId} #${i + 1} [${r.status}]: ${r.body}`);
|
||||
}
|
||||
} catch (err) {
|
||||
fail++;
|
||||
console.error(` ERR ${modelId} #${i + 1}:`, err.message);
|
||||
}
|
||||
}
|
||||
const avg = latencies.length ? Math.round(latencies.reduce((a, b) => a + b, 0) / latencies.length) : 0;
|
||||
results.push({ modelId, ok, fail, avgMs: avg });
|
||||
console.log(`${modelId}: ${ok}/${rounds} ok, avg ${avg}ms`);
|
||||
}
|
||||
|
||||
console.log("\nSummary:");
|
||||
console.table(results);
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user