Skip to content

Commit 8027bc7

Browse files
tracycamualtinok
authored andcommitted
plugin: escape sqlite attach path in tool-owner backfill
1 parent 29a49cb commit 8027bc7

2 files changed

Lines changed: 62 additions & 1 deletion

File tree

packages/plugin/src/features/magic-context/tool-owner-backfill.test.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -522,4 +522,59 @@ describe("runToolOwnerBackfill", () => {
522522

523523
closeQuietly(mc);
524524
});
525+
526+
test("ATTACH succeeds when the OpenCode DB path contains a single quote", () => {
527+
// Regression guard: the OpenCode DB path is interpolated into an
528+
// `ATTACH '<path>'` statement (SQLite/bun:sqlite reject a bound
529+
// parameter there), so an unescaped single quote in the path would
530+
// break out of the SQL string literal and throw a syntax error. The
531+
// path is resolved from XDG_DATA_HOME via getDataDir(), so a data home
532+
// containing a quote exercises the escaping end-to-end.
533+
const fs = require("node:fs");
534+
const base = createTempDir("mc-backfill-quote-");
535+
const dataHome = join(base, "o'brien");
536+
fs.mkdirSync(dataHome, { recursive: true });
537+
process.env.XDG_DATA_HOME = dataHome;
538+
539+
// OpenCode DB at $XDG_DATA_HOME/opencode/opencode.db with one tool part.
540+
const ocDir = join(dataHome, "opencode");
541+
fs.mkdirSync(ocDir, { recursive: true });
542+
const oc = new Database(join(ocDir, "opencode.db"));
543+
oc.exec(`
544+
CREATE TABLE message (
545+
id TEXT PRIMARY KEY,
546+
session_id TEXT NOT NULL,
547+
time_created INTEGER NOT NULL,
548+
data TEXT NOT NULL
549+
);
550+
CREATE TABLE part (
551+
id TEXT PRIMARY KEY,
552+
message_id TEXT NOT NULL,
553+
time_created INTEGER NOT NULL,
554+
data TEXT NOT NULL
555+
);
556+
`);
557+
oc.prepare(
558+
"INSERT INTO message (id, session_id, time_created, data) VALUES (?, ?, ?, ?)",
559+
).run("msg-A", "ses-1", 1000, JSON.stringify({ role: "assistant" }));
560+
oc.prepare("INSERT INTO part (id, message_id, time_created, data) VALUES (?, ?, ?, ?)").run(
561+
"p-A",
562+
"msg-A",
563+
1100,
564+
JSON.stringify({ type: "tool", callID: "read:1" }),
565+
);
566+
oc.close();
567+
568+
const mc = createMcDb();
569+
insertTag(mc, "ses-1", "read:1", "tool", 100, 1);
570+
571+
// No throw + the session is backfilled proves ATTACH parsed the quoted path.
572+
const result = runToolOwnerBackfill(mc);
573+
expect(result.sessionsCompleted).toBe(1);
574+
expect(result.rowsUpdated).toBe(1);
575+
const tags = getTagsBySession(mc, "ses-1");
576+
expect(tags[0].toolOwnerMessageId).toBe("msg-A");
577+
578+
closeQuietly(mc);
579+
});
525580
});

packages/plugin/src/features/magic-context/tool-owner-backfill.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,13 @@ export function runToolOwnerBackfill(db: Database): BackfillResult {
151151
return result;
152152
}
153153

154-
db.exec(`ATTACH '${opencodeDbPath}' AS oc_backfill`);
154+
// Escape single quotes in the path: SQLite's ATTACH does not accept bound
155+
// parameters (bun:sqlite/node:sqlite reject `ATTACH ?`), so the path is
156+
// interpolated as a string literal. Doubling embedded single quotes is the
157+
// standard SQL-literal escape and prevents a path like `/tmp/o'brien` from
158+
// breaking out of the literal.
159+
const escapedDbPath = opencodeDbPath.replaceAll("'", "''");
160+
db.exec(`ATTACH '${escapedDbPath}' AS oc_backfill`);
155161
try {
156162
backfillToolOwnersInChunks(db, result);
157163
} finally {

0 commit comments

Comments
 (0)