mirror of
https://github.com/github/awesome-copilot.git
synced 2026-08-08 18:19:14 +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.
|
||||
const ID_RE = /^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/;
|
||||
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 num(v) {
|
||||
if (v === undefined || v === null || v === "") return undefined;
|
||||
@@ -102,6 +107,7 @@ export function normalize(doc) {
|
||||
}
|
||||
}
|
||||
doc.progress = p;
|
||||
doc.kind = BOARD_MARKER;
|
||||
return doc;
|
||||
}
|
||||
|
||||
@@ -344,14 +350,28 @@ export async function resolveStateFile(p) {
|
||||
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) {
|
||||
const file = await resolveStateFile(inputPath);
|
||||
if (!existsSync(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);
|
||||
if (existsSync(file)) {
|
||||
let parsed;
|
||||
try { parsed = JSON.parse(await readFile(file, "utf-8")); }
|
||||
catch { throw new Error(`refusing to use existing non-JSON file as a focus board: ${file}`); }
|
||||
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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user