Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions fixtures/differential-oracle-swift/vuln-exec.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import Foundation

// Planted-vulnerable command-approval gate (the exec canary). It BELIEVES a command is safe unless
// it starts with a tiny denylist — so a plain `echo`, an `sh -c ...`, or a bare executable path is
// auto-approved and, when run, executes. Its BELIEF (safe) diverges from GROUND TRUTH (it ran). The
// lane's exec-marker strategy must catch this to prove itself LIVE.
enum NaiveCommandApprover {
static func isCommandSafe(_ command: String) -> Bool {
let banned = ["rm ", "curl ", "dd ", "mkfs"]
for b in banned where command.contains(b) { return false }
return true // naive: everything else is "safe" — including a marker command / marker executable
}
}
17 changes: 17 additions & 0 deletions fixtures/differential-oracle-swift/vuln.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import Foundation

// Planted-vulnerable confinement DECISION (the canary for the Swift differential-oracle lane).
//
// It BELIEVES a path is confined to `base` using a lexical `hasPrefix` check on the RAW joined
// string — it never canonicalizes symlinks or collapses `..`. A `../` payload, or a path through an
// in-base symlink that points outside, escapes `base` yet is approved. Its BELIEF (approved) diverges
// from GROUND TRUTH (the write lands outside `base`). The lane must report this divergence to prove
// itself LIVE — a lane that cannot catch its own planted flaw is quarantined, never a silent pass.
enum NaiveConfiner {
static func resolvePath(_ path: String) -> URL? {
let base = FileManager.default.currentDirectoryPath + "/base"
let joined = base + "/" + path
guard joined.hasPrefix(base) else { return nil } // naive: no symlink/`..` canonicalization
return URL(fileURLWithPath: joined)
}
}
59 changes: 59 additions & 0 deletions scripts/run-oracle.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#!/usr/bin/env node
// Run the Swift differential-oracle lane over a whole repo: static discovery of decision functions,
// then the belief-vs-ground-truth execute hunt on the candidates. Usage: run-oracle.mjs <repo-dir>
import { readFileSync, readdirSync, mkdtempSync } from "node:fs";
import { join, relative } from "node:path";
import { tmpdir } from "node:os";
import { DifferentialOracleSwiftAttacker } from "../dist/attackers/differential-oracle-swift.js";
import { openSandbox } from "../dist/sandbox.js";

const repo = process.argv[2];
if (!repo) {
console.error("usage: run-oracle.mjs <repo-dir>");
process.exit(2);
}
const lane = new DifferentialOracleSwiftAttacker();
const SKIP = new Set([".git", ".build", "node_modules", "DerivedData", ".swiftpm"]);

function walk(dir, acc = []) {
let entries = [];
try { entries = readdirSync(dir, { withFileTypes: true }); } catch { return acc; }
for (const e of entries) {
if (e.name.startsWith(".") && e.name !== "." ) continue;
if (SKIP.has(e.name)) continue;
const full = join(dir, e.name);
if (e.isDirectory()) walk(full, acc);
else if (e.name.endsWith(".swift")) acc.push(full);
}
return acc;
}

const all = walk(repo).map((f) => relative(repo, f));
const nonTest = all.filter((f) => !/(^|\/)(Tests?|.*Tests)(\/|\.)/.test(f));

// Static discovery: which files hold decision (belief) candidates the oracle can drive.
const candidates = [];
for (const f of nonTest) {
let leads = [];
try { leads = lane.staticLeads(readFileSync(join(repo, f), "utf8")); } catch {}
if (leads.length) candidates.push({ file: f, leads });
}
console.log(`[oracle] ${repo}`);
console.log(`[oracle] swift files: ${all.length} (non-test ${nonTest.length}); decision-candidate files: ${candidates.length}`);
for (const c of candidates.slice(0, 40)) {
console.log(` DECISION ${c.file}: ${c.leads.map((l) => l.sink).join(" | ")}`);
}

// Execute: drive the divergence hunt on candidate files only (each compiled in isolation).
const box = openSandbox(mkdtempSync(join(tmpdir(), "rk-oracle-")), { prefer: "local" });
let exploits = [];
try {
exploits = lane.hunt(repo, candidates.map((c) => c.file), box);
} finally {
box.dispose();
}
console.log(`\n[oracle] belief-divergence findings: ${exploits.length}`);
for (const e of exploits) {
console.log(` DIVERGENCE ${e.file} [${e.sink}]: ${e.summary}`);
}
process.exit(0);
Loading