fix: fail closed on incompatible Devin ACP behavior

This commit is contained in:
Lucas Israel
2026-07-27 15:00:48 -03:00
parent ccbe6bc288
commit 71af74bee4
12 changed files with 220 additions and 23 deletions

View File

@@ -89,8 +89,8 @@ bridge_check_devin_auth() {
output="$(bridge_run_devin auth status 2>&1)"
exit_status=$?
set -e
printf '%s\n' "$output"
bridge_assert_devin_auth_status "$exit_status" "$output"
printf 'PASS: Devin authentication confirmed\n'
}
bridge_assert_zero_claude_egress() {

View File

@@ -23,7 +23,7 @@ export function validateDevinAuthStatus(exitStatus, output) {
const lines = String(output)
.split(/\r?\n/)
.map((line) => line.trim());
if (!lines.includes("Logged in (via Devin)")) {
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))) {
@@ -63,12 +63,22 @@ export function validateClaudeGuardDenials(text) {
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 { ok: true };
return sawAllowedDevinRequest
? { ok: true }
: { ok: false, error: "Devin egress audit has no approved request" };
}
export function validateAuditFileStat(stat, expectedUid) {

View File

@@ -10,7 +10,14 @@ docker compose -f "$BRIDGE_COMPOSE" --profile offline up --abort-on-container-ex
node -e '
const fs = require("node:fs");
const rows = fs.readFileSync(process.argv[1], "utf8").trim().split("\n").map(JSON.parse);
if (rows.length !== 5 || rows.some((row) => row.provider !== "devin-cli-agentic")) {
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"

View File

@@ -8,9 +8,20 @@ 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
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")"
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"