Files
purpur-sentinel/scripts/secret-input.ts
T
dmg b211e9d337
CI / build (pull_request) Successful in 1m16s
CI / build (push) Successful in 3m24s
feat(sentinel): alert on repeated suspicious movement
2026-10-29 08:28:27 -04:00

36 lines
1.7 KiB
TypeScript

import { lstatSync, realpathSync } from "node:fs";
import { dirname, isAbsolute, relative, resolve, sep } from "node:path";
export interface SecretInputPlan {
command: "tea";
args: string[];
stdinFile?: string;
}
// Plan only: never reads secret bytes, spawns tea, or provisions a secret.
// This is an inspectable contract, not upload authorization or a race-free open.
// An operator must recheck metadata and approve the actual upload separately.
export function planSecretInput(login: string, file: string, mode: "file" | "stdin", repositoryRoot: string): SecretInputPlan {
try {
if (!/^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/.test(login) || !isAbsolute(file)
|| (mode !== "file" && mode !== "stdin")) throw new Error();
const canonical = realpathSync(file);
const local = relative(realpathSync(repositoryRoot), canonical);
if (canonical !== resolve(file) || !(local === ".." || local.startsWith(`..${sep}`) || isAbsolute(local))) throw new Error();
const metadata = lstatSync(file);
const directory = lstatSync(dirname(file));
const owner = process.getuid?.();
if (owner === undefined || !metadata.isFile() || metadata.isSymbolicLink() || metadata.size === 0
|| metadata.uid !== owner || (metadata.mode & 0o777) !== 0o600
|| !directory.isDirectory() || directory.uid !== owner || (directory.mode & 0o777) !== 0o700) throw new Error();
} catch {
throw new Error("Invalid protected secret input; no provisioning was performed.");
}
return {
command: "tea",
args: ["actions", "secrets", "create", "--login", login, "--repo", "dmg/purpur-sentinel",
...(mode === "file" ? ["--file", file] : ["--stdin"]), "RELEASE_TOKEN"],
...(mode === "stdin" ? { stdinFile: file } : {}),
};
}