mirror of
https://github.com/github/awesome-copilot.git
synced 2026-08-14 05:06:54 +00:00
daily-focus-board: guard external stateFile + reserve feed sentinel ids
Addresses the Copilot review on board-core.mjs: an externally supplied stateFile pointing at an existing non-board file was silently normalized to an empty board and overwritten on first mutation. ensureStateFile now refuses an existing file unless it parses as JSON and looks like a board (a 'daily-focus-board' marker or the board schema); the marker is stamped on every write. Also reserve 'day'/'brain' as task ids so a task can't collide with the momentum/brain-dump feed delete routing. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: cb356aa8-0af2-48f3-b3c6-8086c69d5308
This commit is contained in:
@@ -40,7 +40,12 @@ export function isCrossSiteRequest(req) {
|
|||||||
// Task ids double as object keys and HTML data-attributes, so keep them tight.
|
// Task ids double as object keys and HTML data-attributes, so keep them tight.
|
||||||
const ID_RE = /^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/;
|
const ID_RE = /^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/;
|
||||||
const TAG_COLORS = ["new", "deadline", "career"];
|
const TAG_COLORS = ["new", "deadline", "career"];
|
||||||
export function validId(s) { return typeof s === "string" && ID_RE.test(s); }
|
const BOARD_MARKER = "daily-focus-board";
|
||||||
|
// "day"/"brain" are the momentum-feed and brain-dump sentinels used in the UI's
|
||||||
|
// delete routing, so a task may not claim them as an id — otherwise deleting a
|
||||||
|
// task note could splice the shared feed instead.
|
||||||
|
const RESERVED_IDS = new Set(["day", "brain"]);
|
||||||
|
export function validId(s) { return typeof s === "string" && ID_RE.test(s) && !RESERVED_IDS.has(s); }
|
||||||
function text(s, max = 2000) { return typeof s === "string" ? s.slice(0, max) : ""; }
|
function text(s, max = 2000) { return typeof s === "string" ? s.slice(0, max) : ""; }
|
||||||
function num(v) {
|
function num(v) {
|
||||||
if (v === undefined || v === null || v === "") return undefined;
|
if (v === undefined || v === null || v === "") return undefined;
|
||||||
@@ -102,6 +107,7 @@ export function normalize(doc) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
doc.progress = p;
|
doc.progress = p;
|
||||||
|
doc.kind = BOARD_MARKER;
|
||||||
return doc;
|
return doc;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -344,14 +350,28 @@ export async function resolveStateFile(p) {
|
|||||||
return join(process.cwd(), "focus-board-state.json");
|
return join(process.cwd(), "focus-board-state.json");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create + seed the state file if it doesn't exist yet. Returns the resolved path.
|
// A parsed value we're willing to treat as an existing board — so we never adopt
|
||||||
|
// (and then overwrite on first mutation) an unrelated file the caller pointed at.
|
||||||
|
export function looksLikeBoard(o) {
|
||||||
|
return !!o && typeof o === "object" && !Array.isArray(o)
|
||||||
|
&& (o.kind === BOARD_MARKER || (Array.isArray(o.tasks) && !!o.progress && typeof o.progress === "object"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create + seed the state file if it doesn't exist yet. If it DOES exist, refuse
|
||||||
|
// to use it unless it's clearly one of our boards (a marker or the board schema),
|
||||||
|
// so an externally supplied stateFile can't cause us to clobber an unrelated file.
|
||||||
export async function ensureStateFile(inputPath, seed) {
|
export async function ensureStateFile(inputPath, seed) {
|
||||||
const file = await resolveStateFile(inputPath);
|
const file = await resolveStateFile(inputPath);
|
||||||
if (!existsSync(file)) {
|
if (existsSync(file)) {
|
||||||
const doc = normalize(seed && typeof seed === "object" ? seed : demoSeed());
|
let parsed;
|
||||||
doc.updatedAt = new Date().toISOString();
|
try { parsed = JSON.parse(await readFile(file, "utf-8")); }
|
||||||
await mkdir(dirname(file), { recursive: true }).catch(() => {});
|
catch { throw new Error(`refusing to use existing non-JSON file as a focus board: ${file}`); }
|
||||||
await atomicWrite(file, doc);
|
if (!looksLikeBoard(parsed)) throw new Error(`refusing to use an existing file that is not a daily-focus-board: ${file}`);
|
||||||
|
return file;
|
||||||
}
|
}
|
||||||
|
const doc = normalize(seed && typeof seed === "object" ? seed : demoSeed());
|
||||||
|
doc.updatedAt = new Date().toISOString();
|
||||||
|
await mkdir(dirname(file), { recursive: true }).catch(() => {});
|
||||||
|
await atomicWrite(file, doc);
|
||||||
return file;
|
return file;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user