Harden control plane boundaries
CI / verify (pull_request) Canceled after 0s

This commit is contained in:
2026-07-25 10:13:22 -07:00
parent a0a4a21962
commit 72e8b2de91
24 changed files with 1507 additions and 1655 deletions
+17 -5
View File
@@ -79,10 +79,14 @@ test("equivalent resolved inputs are byte-identical despite different run IDs",
assert.equal(first.input.artifactBuildId, second.input.artifactBuildId);
const engine = new BuildEngine(output);
const one = engine.build(first.input);
assert.equal(fs.statSync(one.outputDirectory).mode & 0o777, 0o750);
if (process.platform !== "win32") {
assert.equal(fs.statSync(one.outputDirectory).mode & 0o777, 0o750);
}
fs.chmodSync(one.outputDirectory, 0o700);
const two = engine.build(second.input);
assert.equal(fs.statSync(two.outputDirectory).mode & 0o777, 0o750);
if (process.platform !== "win32") {
assert.equal(fs.statSync(two.outputDirectory).mode & 0o777, 0o750);
}
assert.equal(one.artifactChecksum, two.artifactChecksum);
assert.equal(one.outputDirectory, two.outputDirectory);
assert.deepEqual(one.generatedFiles, two.generatedFiles);
@@ -210,9 +214,17 @@ test("markdown output is escaped and heading IDs are deterministic", () => {
});
test("production sources contain no embedded renderer or synthesized preview fallback", () => {
assert.equal(fs.readFileSync(path.resolve("src/backend/buildEngine.ts"), "utf8").trim(), 'export * from "./repositoryBuildEngine";');
const boundary = fs.readFileSync(path.resolve("src/backend/buildEngine.ts"), "utf8");
assert.match(boundary, /AI or other open-schema output is never a release input/);
assert.match(boundary, /export \* from ".\/repositoryBuildEngine"/);
const server = fs.readFileSync(path.resolve("server.ts"), "utf8");
assert.doesNotMatch(server, /cdn\.tailwindcss\.com|Simple HTML renderer|SITE HEADER/);
assert.match(server, /E_RELEASE_UNAVAILABLE/);
assert.match(server, /E_CONFIG_READ_ONLY/);
assert.doesNotMatch(server, /child_process|execAsync/);
const preview = fs.readFileSync(path.resolve("src/backend/http/previewRouter.ts"), "utf8");
const control = fs.readFileSync(path.resolve("src/backend/http/controlRouter.ts"), "utf8");
const storeSource = fs.readFileSync(path.resolve("src/backend/store.ts"), "utf8");
assert.match(preview, /E_RELEASE_UNAVAILABLE/);
assert.match(control, /E_CONFIG_READ_ONLY/);
assert.match(storeSource, /REMOTE_PUBLICATION_IMPLEMENTED = false as const/);
assert.doesNotMatch(storeSource, /REMOTE_PUBLICATION_IMPLEMENTED\s*=\s*process\.env/);
});
+1
View File
@@ -1 +1,2 @@
// Security boundary: AI or other open-schema output is never a release input.
export * from "./repositoryBuildEngine";
+180
View File
@@ -0,0 +1,180 @@
import { execFile } from "node:child_process";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { promisify } from "node:util";
const execFileAsync = promisify(execFile);
const cacheRoot = path.join(os.tmpdir(), "labyricorn-cache");
const SAFE_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
const allowedHosts = (): Set<string> => {
const configured = (process.env.GIT_ALLOWED_HOSTS ?? "")
.split(",")
.map((host) => host.trim().toLowerCase())
.filter(Boolean);
if (process.env.GITEA_BASE_URL) {
configured.push(new URL(process.env.GITEA_BASE_URL).hostname.toLowerCase());
}
return new Set(configured);
};
const isWithin = (root: string, candidate: string): boolean => {
const relative = path.relative(root, candidate);
return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
};
const validateLocalRepository = (repository: string): string => {
const resolved = path.resolve(repository);
const configuredRoots = (process.env.GIT_ALLOWED_LOCAL_ROOTS ?? process.cwd())
.split(path.delimiter)
.map((entry) => path.resolve(entry));
if (!configuredRoots.some((root) => isWithin(root, resolved))) {
throw new Error("Local repository is outside GIT_ALLOWED_LOCAL_ROOTS.");
}
return resolved;
};
export const validateRepository = (repository: string): string => {
const value = repository.trim();
if (path.isAbsolute(value)) return validateLocalRepository(value);
if (!value.includes("://")) {
const scpLike = /^(?:[^@\s]+@)?([^:/\s]+):(.+)$/.exec(value);
if (scpLike) {
if (!allowedHosts().has(scpLike[1].toLowerCase())) {
throw new Error(`Git host '${scpLike[1]}' is not allow-listed.`);
}
return value;
}
}
const url = new URL(value);
if (!["https:", "ssh:"].includes(url.protocol) || url.username === "root") {
throw new Error("Repository URL must use HTTPS or SSH.");
}
if (
(url.protocol === "https:" && (url.username || url.password)) ||
(url.protocol === "ssh:" && url.password)
) throw new Error("Repository URL contains disallowed credentials.");
if (!allowedHosts().has(url.hostname.toLowerCase())) {
throw new Error(`Git host '${url.hostname}' is not allow-listed.`);
}
return value;
};
const validateRef = (ref: string): string => {
const value = ref.trim();
if (
value.length === 0 ||
value.length > 255 ||
/[\s~^:?*[\]\\]/.test(value) ||
value.includes("..") ||
value.includes("@{") ||
value.startsWith("-")
) {
throw new Error("Git ref is invalid.");
}
return value;
};
const validateId = (id: string): string => {
if (!SAFE_ID.test(id)) throw new Error("Git source ID is invalid.");
return id;
};
const gitEnvironment = {
...process.env,
GIT_TERMINAL_PROMPT: "0",
};
export const resolveRemote = async (
repository: string,
ref: string,
): Promise<string | null> => {
const safeRepository = validateRepository(repository);
const safeRef = validateRef(ref || "HEAD");
const { stdout } = await execFileAsync(
"git",
["-c", "protocol.ext.allow=never", "ls-remote", "--", safeRepository, safeRef],
{ env: gitEnvironment, timeout: 10_000 },
);
return stdout.trim().split("\t")[0] || null;
};
export const synchronizeRepository = async (
id: string,
repository: string,
ref: string,
): Promise<string> => {
validateId(id);
const safeRepository = validateRepository(repository);
const safeRef = validateRef(ref || "main");
fs.mkdirSync(cacheRoot, { recursive: true });
const target = path.resolve(cacheRoot, id);
if (!isWithin(cacheRoot, target) || target === cacheRoot) {
throw new Error("Git cache target is invalid.");
}
const temporary = fs.mkdtempSync(path.join(cacheRoot, `.${id}-`));
fs.rmSync(temporary, { recursive: true, force: true });
try {
await execFileAsync(
"git",
[
"-c",
"protocol.ext.allow=never",
"clone",
"--depth",
"1",
"--branch",
safeRef,
"--",
safeRepository,
temporary,
],
{ env: gitEnvironment, timeout: 60_000 },
);
fs.rmSync(target, { recursive: true, force: true });
fs.renameSync(temporary, target);
return target;
} catch (error) {
fs.rmSync(temporary, { recursive: true, force: true });
throw error;
}
};
export const cachedRepositoryPath = (id: string): string => {
validateId(id);
const target = path.resolve(cacheRoot, id);
if (!isWithin(cacheRoot, target) || target === cacheRoot) {
throw new Error("Git cache target is invalid.");
}
return target;
};
export const listRepositoryFiles = (
id: string,
predicate: (relative: string) => boolean = () => true,
maximum = 50,
): Array<{ path: string; sizeBytes: number }> => {
const root = cachedRepositoryPath(id);
if (!fs.existsSync(root)) return [];
const files: Array<{ path: string; sizeBytes: number }> = [];
const visit = (directory: string): void => {
for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
if (files.length >= maximum) return;
if (entry.name === ".git") continue;
const absolute = path.join(directory, entry.name);
if (entry.isSymbolicLink()) continue;
if (entry.isDirectory()) visit(absolute);
if (entry.isFile()) {
const relative = path.relative(root, absolute).replace(/\\/g, "/");
if (predicate(relative)) {
files.push({ path: relative, sizeBytes: fs.statSync(absolute).size });
}
}
}
};
visit(root);
return files.sort((a, b) => a.path.localeCompare(b.path)).slice(0, maximum);
};
+92
View File
@@ -0,0 +1,92 @@
import assert from "node:assert/strict";
import test from "node:test";
import {
fetchGiteaRepositories,
FixedWindowRateLimiter,
loadGiteaDiscoveryConfig,
toGitSource,
} from "./http/giteaDiscovery";
test("Gitea discovery requires an HTTPS allow-listed configured origin", () => {
assert.throws(
() => loadGiteaDiscoveryConfig({}),
/GITEA_TOKEN and GITEA_BASE_URL/,
);
assert.throws(
() =>
loadGiteaDiscoveryConfig({
GITEA_TOKEN: "secret",
GITEA_BASE_URL: "http://git.example.test",
GITEA_ALLOWED_ORIGINS: "http://git.example.test",
}),
/HTTPS/,
);
assert.throws(
() =>
loadGiteaDiscoveryConfig({
GITEA_TOKEN: "secret",
GITEA_BASE_URL: "https://git.example.test",
GITEA_ALLOWED_ORIGINS: "https://other.example.test",
}),
/not in GITEA_ALLOWED_ORIGINS/,
);
});
test("Gitea requests authenticate, disable redirects, and use an abort signal", async () => {
const config = loadGiteaDiscoveryConfig({
GITEA_TOKEN: "secret",
GITEA_BASE_URL: "https://git.example.test",
GITEA_ALLOWED_ORIGINS: "https://git.example.test",
});
let requestInit: RequestInit | undefined;
const fakeFetch = (async (_input: unknown, init?: RequestInit) => {
requestInit = init;
return new Response(JSON.stringify({
data: [
{
name: "engine",
full_name: "platform/engine",
clone_url: "https://git.example.test/platform/engine.git",
default_branch: "main",
private: true,
},
],
}));
}) as typeof fetch;
const repositories = await fetchGiteaRepositories(config, fakeFetch);
assert.equal(new Headers(requestInit?.headers).get("Authorization"), "token secret");
assert.equal(requestInit?.redirect, "error");
assert.ok(requestInit?.signal);
assert.equal(repositories.length, 1);
assert.equal(toGitSource(repositories[0]).id, "platform--engine");
});
test("Gitea fetch timeout aborts the request", async () => {
const config = {
apiUrl: new URL("https://git.example.test/api/v1/repos/search"),
token: "secret",
timeoutMs: 5,
};
const hangingFetch = ((_input: unknown, init?: RequestInit) =>
new Promise<Response>((_resolve, reject) => {
init?.signal?.addEventListener("abort", () =>
reject(new DOMException("aborted", "AbortError")),
);
})) as typeof fetch;
await assert.rejects(
fetchGiteaRepositories(config, hangingFetch),
/timed out after 5ms/,
);
});
test("Gitea discovery limiter is fixed-window and returns retry timing", () => {
const limiter = new FixedWindowRateLimiter(2, 1_000);
assert.equal(limiter.take("client", 0).allowed, true);
assert.equal(limiter.take("client", 1).allowed, true);
assert.deepEqual(limiter.take("client", 2), {
allowed: false,
retryAfter: 1,
});
assert.equal(limiter.take("client", 1_000).allowed, true);
});
+128
View File
@@ -0,0 +1,128 @@
import express from "express";
import fs from "node:fs";
import path from "node:path";
import { cachedRepositoryPath, listRepositoryFiles } from "../gitOperations";
import { store } from "../store";
const router = express.Router();
router.get("/style-packages", (_req, res) => {
res.json(store.stylePackages);
});
router.get("/style-instances", (_req, res) => {
res.json(store.styleInstances);
});
router.post("/style-instances", (req, res) => {
const instance = req.body;
store.styleInstances.push(instance);
store.addAudit(
"Add Style Instance",
"config",
`Created style instance '${instance.id}' using '${instance.uses}'`,
);
res.json({ success: true, instance });
});
router.put("/style-instances/:id", (req, res) => {
const index = store.styleInstances.findIndex(
(instance) => instance.id === req.params.id,
);
if (index === -1) return res.status(404).json({ error: "Instance not found" });
store.styleInstances[index] = { ...store.styleInstances[index], ...req.body };
store.addAudit(
"Update Style Instance",
"config",
`Updated style instance '${req.params.id}'`,
);
return res.json({ success: true, instance: store.styleInstances[index] });
});
router.get("/content", (_req, res) => {
res.json(store.contentItems);
});
router.post("/content/discover", (_req, res) => {
store.addAudit(
"Discover Content",
"config",
"Executed content discovery scan across all Git sources",
);
const discovered = [];
for (const source of store.gitSources) {
if (source.health !== "Healthy" || !source.projectionEnabled) continue;
let root: string;
try {
root = cachedRepositoryPath(source.id);
} catch {
continue;
}
if (!fs.existsSync(root)) continue;
const markdownFiles = listRepositoryFiles(
source.id,
(relative) => relative.toLowerCase().endsWith(".md"),
10_000,
);
for (const [index, file] of markdownFiles.entries()) {
const title = path.posix.basename(file.path, ".md").trim() || "Untitled";
const declaredModels = source.produces || [];
const defaultModel = store.siteConfig.contentModels[0]?.id ?? "page";
const artifactType = declaredModels[0] ?? defaultModel;
const slug = title.toLowerCase();
const model = store.siteConfig.contentModels.find(
(candidate) => candidate.id === artifactType,
);
const navigation = store.siteConfig.navigation.find(
(entry) => entry.contentModel === artifactType,
);
const routePattern =
model?.routing?.detail ??
(navigation
? `${navigation.route}/{slug}/`
: `/${artifactType}s/{slug}/`);
discovered.push({
id: `art-${source.id}-${index}`,
title: title
.replace(/-/g, " ")
.replace(/\b\w/g, (letter) => letter.toUpperCase()),
slug,
published: new Date().toISOString(),
status: "published" as const,
artifactType,
summary: `Discovered artifact from ${file.path}`,
tags: [source.id],
aliases: [],
sourceRepo: source.id,
path: file.path,
contentMarkdown: fs.readFileSync(
path.join(root, ...file.path.split("/")),
"utf8",
),
mediaReferences: [],
youtubeDirectives: [],
wikipediaLinks: [],
validationStatus: "valid" as const,
validationMessages: [],
route: routePattern.replace("{slug}", slug).replace("//", "/"),
styleInstanceId: "default",
});
}
}
store.contentItems = discovered;
res.json({
success: true,
discoveredCount: discovered.length,
mediaCount: store.mediaAssets.length,
items: discovered,
});
});
router.get("/media", (_req, res) => {
res.json(store.mediaAssets);
});
export { router as contentRouter };
+124
View File
@@ -0,0 +1,124 @@
import express from "express";
import { CredentialRef } from "../../types";
import { store } from "../store";
const router = express.Router();
router.get("/health", (_req, res) => {
res.json({ status: "ok", service: "labyricorn-site-builder" });
});
router.get("/site-config", (_req, res) => {
res.json(store.siteConfig);
});
router.put("/site-config", (_req, res) => {
res.status(405).json({
code: "E_CONFIG_READ_ONLY",
message: "Site configuration is Git-owned and read-only in v1.",
});
});
router.get("/theme", (_req, res) => {
res.json(store.themeConfig);
});
router.post("/theme/validate", (_req, res) => {
const theme = store.validateTheme();
res.status(theme.status === "valid" ? 200 : 422).json({
success: theme.status === "valid",
theme,
commits: theme.commit ? { [theme.sourceId ?? "theme"]: theme.commit } : {},
});
});
router.get("/builds", (_req, res) => {
res.json(store.builds);
});
router.post("/builds/trigger", (_req, res) => {
res.json(store.runBuild());
});
router.get("/nginx", (_req, res) => {
res.json(store.nginxStatus);
});
router.post("/nginx/test", (_req, res) => {
store.nginxStatus.configValid = true;
store.addAudit(
"Test Nginx Config",
"nginx",
"Control-plane simulation reported a valid Nginx configuration.",
);
res.json({
success: true,
simulated: true,
message: "Nginx validation is simulated by the control plane.",
});
});
router.post("/nginx/activate", (req, res) => {
res.json(store.activateReleaseLocally(req.body.buildId));
});
router.get("/push-targets", (_req, res) => {
res.json(store.pushTargets);
});
router.post("/push-targets/:id/push", (req, res) => {
const result = store.pushToTarget(req.params.id, req.body.buildId);
res.status(result.success ? 200 : 501).json(result);
});
router.post("/push-targets/:id/test", (_req, res) => {
res.status(501).json({
success: false,
code: "E_REMOTE_PUBLICATION_NOT_IMPLEMENTED",
message: "Remote SSH and rsync checks are not implemented.",
});
});
router.get("/credentials", (_req, res) => {
res.json(store.credentials);
});
router.post("/credentials", (req, res) => {
const { name, type, secretValue } = req.body;
if (
typeof name !== "string" ||
!["ssh_key", "token", "password"].includes(type) ||
typeof secretValue !== "string"
) {
return res.status(400).json({
error: "name, type, and secretValue are required.",
});
}
const id = `secret:${name.toLowerCase().replace(/[^a-z0-9]/g, "-")}`;
const masked =
type === "ssh_key"
? `${secretValue.substring(0, 20)}...`
: `${secretValue.substring(0, 4)}********************`;
const credential: CredentialRef = {
id,
name,
type: type as CredentialRef["type"],
maskedValue: masked,
createdAt: new Date().toISOString(),
lastUsedAt: new Date().toISOString(),
usageCount: 0,
};
store.credentials.push(credential);
store.addAudit(
"Add Credential",
"credential",
`Stored credential reference ${id}`,
);
return res.json({ success: true, credential });
});
router.get("/audit-logs", (_req, res) => {
res.json(store.auditLogs);
});
export { router as controlRouter };
+230
View File
@@ -0,0 +1,230 @@
import express from "express";
import fs from "node:fs";
import { GitSource } from "../../types";
import {
cachedRepositoryPath,
listRepositoryFiles,
resolveRemote,
synchronizeRepository,
validateRepository,
} from "../gitOperations";
import { store } from "../store";
import {
fetchGiteaRepositories,
FixedWindowRateLimiter,
loadGiteaDiscoveryConfig,
toGitSource,
} from "./giteaDiscovery";
const router = express.Router();
const discoveryLimiter = new FixedWindowRateLimiter();
let discoveryInFlight: Promise<ReturnType<typeof toGitSource>[]> | null = null;
const errorMessage = (error: unknown): string =>
error instanceof Error ? error.message : String(error);
router.post("/gitea/discover", async (req, res) => {
const limit = discoveryLimiter.take(req.ip || req.socket.remoteAddress || "unknown");
if (!limit.allowed) {
res.setHeader("Retry-After", String(limit.retryAfter));
return res.status(429).json({
code: "E_GITEA_DISCOVERY_RATE_LIMIT",
error: "Gitea discovery rate limit exceeded.",
success: false,
});
}
let config: ReturnType<typeof loadGiteaDiscoveryConfig>;
try {
config = loadGiteaDiscoveryConfig();
} catch (error) {
return res.status(503).json({
code: "E_GITEA_DISCOVERY_CONFIG",
error: errorMessage(error),
success: false,
});
}
store.addAudit(
"Gitea Discovery",
"config",
`Initiating repository discovery from ${config.apiUrl.origin}`,
);
try {
discoveryInFlight ??= fetchGiteaRepositories(config)
.then((repositories) => repositories.map(toGitSource))
.finally(() => {
discoveryInFlight = null;
});
const discovered = await discoveryInFlight;
const added = store.mergeDiscoveredGitSources(discovered);
return res.json({
success: true,
discoveredCount: discovered.length,
addedCount: added,
message: `Successfully discovered ${discovered.length} repositories from Gitea.`,
});
} catch (error) {
const message = errorMessage(error);
store.addAudit("Gitea Discovery Failed", "config", message, "failure");
return res.status(502).json({ error: message, success: false });
}
});
router.get("/git-sources", (_req, res) => {
res.json(store.gitSources);
});
router.post("/git-sources", async (req, res) => {
const body = req.body as Partial<GitSource>;
if (
typeof body.id !== "string" ||
typeof body.repository !== "string" ||
typeof body.ref !== "string"
) {
return res.status(400).json({ error: "id, repository, and ref are required." });
}
try {
validateRepository(body.repository);
} catch (error) {
return res.status(400).json({ error: errorMessage(error) });
}
const source: GitSource = {
id: body.id,
repository: body.repository,
ref: body.ref,
branch: body.branch || body.ref,
type: body.type || "hybrid",
required: body.required ?? false,
credential: body.credential,
health: "Warning",
reachability: "Unreachable",
configurationState: "Not Configured",
visibility: "Private in Gitea",
projectionEnabled: false,
produces: [],
explainWhy: "Awaiting initial synchronization.",
status: "syncing",
};
try {
const commit = await resolveRemote(source.repository, source.ref);
if (commit) {
source.status = "connected";
source.reachability = "Healthy";
source.lastResolvedCommit = commit;
source.lastSyncedAt = new Date().toISOString();
source.explainWhy =
"Connected to repository, but missing .labyricorn/site.yml metadata.";
} else {
source.status = "error";
source.explainWhy = "Ref not found in repository.";
}
} catch (error) {
source.status = "error";
source.explainWhy = `Git ls-remote failed: ${errorMessage(error)}`;
}
store.gitSources.push(source);
store.addAudit(
"Add Git Source",
"source",
`Added Git source ${source.id} (${source.repository})`,
);
return res.json({ success: true, source });
});
router.put("/git-sources/:id", (req, res) => {
const index = store.gitSources.findIndex((source) => source.id === req.params.id);
if (index === -1) return res.status(404).json({ error: "Source not found" });
store.gitSources[index] = { ...store.gitSources[index], ...req.body };
store.addAudit(
"Update Git Source",
"source",
`Updated Git source ${req.params.id}`,
);
return res.json({ success: true, source: store.gitSources[index] });
});
router.delete("/git-sources/:id", (req, res) => {
store.gitSources = store.gitSources.filter(
(source) => source.id !== req.params.id,
);
store.addAudit(
"Delete Git Source",
"source",
`Removed Git source ${req.params.id}`,
);
res.json({ success: true });
});
router.post("/git-sources/:id/test", async (req, res) => {
const source = store.gitSources.find((item) => item.id === req.params.id);
if (!source) return res.status(404).json({ error: "Source not found" });
source.status = "syncing";
source.explainWhy = "Synchronization running...";
try {
const commit = await resolveRemote(source.repository, source.ref || "HEAD");
if (!commit) {
source.status = "error";
source.explainWhy = "Ref not found in repository.";
return res.status(400).json({ error: source.explainWhy });
}
source.status = "connected";
source.reachability = "Healthy";
source.lastResolvedCommit = commit;
source.lastSyncedAt = new Date().toISOString();
const target = await synchronizeRepository(
source.id,
source.repository,
source.ref || "main",
);
if (fs.existsSync(`${target}/.labyricorn/site.yml`)) {
source.configurationState = "Valid";
source.projectionEnabled = true;
source.explainWhy =
"Repository synchronized and valid site.yml detected.";
source.health = "Healthy";
} else {
source.configurationState = "Not Configured";
source.projectionEnabled = false;
source.explainWhy =
"Repository synchronized, but missing .labyricorn/site.yml metadata.";
source.health = "Warning";
}
return res.json({
success: true,
message: `Successfully connected and synchronized ${source.repository}!`,
resolvedCommit: source.lastResolvedCommit,
syncedAt: source.lastSyncedAt,
});
} catch (error) {
source.status = "error";
source.health = "Invalid";
source.explainWhy = `Git sync failed: ${errorMessage(error)}`;
return res.status(400).json({ error: source.explainWhy });
}
});
router.get("/git-sources/:id/tree", (req, res) => {
if (!store.gitSources.some((source) => source.id === req.params.id)) {
return res.status(404).json({ error: "Source not found" });
}
try {
cachedRepositoryPath(req.params.id);
const files = listRepositoryFiles(req.params.id).map((file) => ({
path: file.path,
type: "file",
size: `${file.sizeBytes} B`,
}));
return res.json({ sourceId: req.params.id, files });
} catch {
return res.json({ sourceId: req.params.id, files: [] });
}
});
export { router as gitSourceRouter };
+198
View File
@@ -0,0 +1,198 @@
import { GitSource } from "../../types";
const DEFAULT_TIMEOUT_MS = 5_000;
const DEFAULT_RATE_LIMIT = 5;
const DEFAULT_RATE_WINDOW_MS = 60_000;
export interface GiteaDiscoveryConfig {
apiUrl: URL;
token: string;
timeoutMs: number;
}
interface GiteaRepository {
clone_url?: unknown;
default_branch?: unknown;
full_name?: unknown;
name?: unknown;
private?: unknown;
ssh_url?: unknown;
}
export interface DiscoveredRepository {
cloneUrl: string;
defaultBranch: string;
fullName: string;
name: string;
private: boolean;
sshUrl?: string;
}
const positiveInteger = (value: string | undefined, fallback: number): number => {
if (value === undefined) return fallback;
const parsed = Number.parseInt(value, 10);
if (!Number.isInteger(parsed) || parsed < 1) {
throw new Error(`Invalid positive integer configuration value '${value}'.`);
}
return parsed;
};
const allowedOrigins = (value: string | undefined): Set<string> =>
new Set(
(value ?? "")
.split(",")
.map((entry) => entry.trim())
.filter(Boolean)
.map((entry) => new URL(entry).origin),
);
export const loadGiteaDiscoveryConfig = (
env: NodeJS.ProcessEnv = process.env,
): GiteaDiscoveryConfig => {
const token = env.GITEA_TOKEN?.trim();
const baseUrl = env.GITEA_BASE_URL?.trim();
if (!token || !baseUrl) {
throw new Error("Gitea discovery requires GITEA_TOKEN and GITEA_BASE_URL.");
}
const base = new URL(baseUrl);
if (base.protocol !== "https:" || base.username || base.password) {
throw new Error("GITEA_BASE_URL must be an HTTPS origin without credentials.");
}
const allowlist = allowedOrigins(env.GITEA_ALLOWED_ORIGINS);
if (!allowlist.has(base.origin)) {
throw new Error(
`GITEA_BASE_URL origin '${base.origin}' is not in GITEA_ALLOWED_ORIGINS.`,
);
}
const apiUrl = new URL("/api/v1/repos/search", base);
apiUrl.searchParams.set("limit", "50");
return {
apiUrl,
token,
timeoutMs: positiveInteger(
env.GITEA_DISCOVERY_TIMEOUT_MS,
DEFAULT_TIMEOUT_MS,
),
};
};
const repository = (value: GiteaRepository): DiscoveredRepository | null => {
if (
typeof value.name !== "string" ||
typeof value.clone_url !== "string" ||
value.name.length === 0 ||
value.clone_url.length === 0
) {
return null;
}
const fullName =
typeof value.full_name === "string" && value.full_name.length > 0
? value.full_name
: value.name;
return {
cloneUrl: value.clone_url,
defaultBranch:
typeof value.default_branch === "string" && value.default_branch.length > 0
? value.default_branch
: "main",
fullName,
name: value.name,
private: value.private === true,
sshUrl: typeof value.ssh_url === "string" ? value.ssh_url : undefined,
};
};
export const fetchGiteaRepositories = async (
config: GiteaDiscoveryConfig,
fetchImplementation: typeof fetch = fetch,
): Promise<DiscoveredRepository[]> => {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), config.timeoutMs);
try {
const response = await fetchImplementation(config.apiUrl, {
headers: {
Accept: "application/json",
Authorization: `token ${config.token}`,
},
redirect: "error",
signal: controller.signal,
});
if (!response.ok) throw new Error(`Gitea returned HTTP ${response.status}.`);
const document = (await response.json()) as { data?: unknown };
if (!Array.isArray(document.data)) {
throw new Error("Gitea returned an invalid repository search response.");
}
return document.data
.map((entry) => repository(entry as GiteaRepository))
.filter((entry): entry is DiscoveredRepository => entry !== null);
} catch (error) {
if (controller.signal.aborted) {
throw new Error(`Gitea discovery timed out after ${config.timeoutMs}ms.`);
}
throw error;
} finally {
clearTimeout(timeout);
}
};
const sourceId = (repo: DiscoveredRepository): string =>
repo.fullName
.toLowerCase()
.replace(/[^a-z0-9._-]+/g, "--")
.replace(/^-+|-+$/g, "")
.slice(0, 128) || repo.name.toLowerCase();
export const toGitSource = (repo: DiscoveredRepository): GitSource => ({
id: sourceId(repo),
repository: repo.cloneUrl,
ref: repo.defaultBranch,
branch: repo.defaultBranch,
type: "hybrid",
health: "Warning",
reachability: "Unreachable",
configurationState: "Not Configured",
visibility: repo.private ? "Private in Gitea" : "Public in Gitea",
projectionEnabled: false,
produces: [],
status: "idle",
explainWhy: "Discovered from Gitea. Awaiting initial synchronization.",
required: false,
credential: "none",
});
export class FixedWindowRateLimiter {
private readonly clients = new Map<
string,
{ count: number; startedAt: number }
>();
constructor(
private readonly maximum = positiveInteger(
process.env.GITEA_DISCOVERY_RATE_LIMIT,
DEFAULT_RATE_LIMIT,
),
private readonly windowMs = positiveInteger(
process.env.GITEA_DISCOVERY_RATE_WINDOW_MS,
DEFAULT_RATE_WINDOW_MS,
),
) {}
take(client: string, now = Date.now()): { allowed: boolean; retryAfter: number } {
const current = this.clients.get(client);
if (!current || now - current.startedAt >= this.windowMs) {
this.clients.set(client, { count: 1, startedAt: now });
return { allowed: true, retryAfter: 0 };
}
if (current.count >= this.maximum) {
return {
allowed: false,
retryAfter: Math.ceil((this.windowMs - (now - current.startedAt)) / 1_000),
};
}
current.count += 1;
return { allowed: true, retryAfter: 0 };
}
}
+58
View File
@@ -0,0 +1,58 @@
import express from "express";
import fs from "node:fs";
import path from "node:path";
import { store } from "../store";
const router = express.Router();
router.get("/live-site/html", (req, res) => {
const rootDir = store.getPreviewDirectory();
if (!rootDir) {
return res.status(503).json({
code: "E_RELEASE_UNAVAILABLE",
message: "No verified staging or live release is selected.",
});
}
let requested: string;
try {
requested = decodeURIComponent(
String(req.query.route ?? "/").split(/[?#]/, 1)[0],
);
} catch {
return res.status(400).json({ code: "E_ROUTE_INVALID" });
}
if (
!requested.startsWith("/") ||
requested.includes("\\") ||
requested.split("/").includes("..")
) {
return res.status(400).json({ code: "E_ROUTE_INVALID" });
}
const relative = requested.replace(/^\/+/, "");
const outputFile =
relative && path.posix.extname(relative)
? relative
: path.posix.join(relative, "index.html");
const rootPath = `${path.resolve(rootDir)}${path.sep}`;
const target = path.resolve(rootDir, ...outputFile.split("/"));
if (!target.startsWith(rootPath)) {
return res.status(400).json({ code: "E_ROUTE_INVALID" });
}
res.setHeader(
"Content-Security-Policy",
"default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self'; font-src 'self'; object-src 'none'; base-uri 'none'; frame-ancestors 'self'",
);
if (fs.existsSync(target) && fs.statSync(target).isFile()) {
return res.type(path.extname(target)).send(fs.readFileSync(target));
}
const notFound = path.join(rootDir, "404.html");
if (fs.existsSync(notFound)) {
return res.status(404).type("html").send(fs.readFileSync(notFound));
}
return res.status(404).json({ code: "E_ROUTE_NOT_FOUND" });
});
export { router as previewRouter };
+161
View File
@@ -0,0 +1,161 @@
import assert from "node:assert/strict";
import { ChildProcess, spawn } from "node:child_process";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import test from "node:test";
import { BuildEngine } from "./buildEngine";
import {
commit,
createRepository,
loadFixture,
} from "./testing/repositoryFixture";
const waitForExit = (child: ChildProcess): Promise<string> =>
new Promise((resolve, reject) => {
let stdout = "";
let stderr = "";
child.stdout?.on("data", (chunk) => {
stdout += chunk;
});
child.stderr?.on("data", (chunk) => {
stderr += chunk;
});
child.on("error", reject);
child.on("exit", (code) => {
if (code === 0) resolve(stdout.trim());
else reject(new Error(`worker exited ${code}: ${stderr}`));
});
});
const waitFor = async (predicate: () => boolean): Promise<void> => {
const deadline = Date.now() + 10_000;
while (!predicate()) {
if (Date.now() > deadline) throw new Error("Timed out waiting for workers.");
await new Promise((resolve) => setTimeout(resolve, 10));
}
};
test("concurrent writers converge on one verified immutable release", async () => {
const root = createRepository();
const output = fs.mkdtempSync(path.join(os.tmpdir(), "concurrent-release-"));
const barrier = path.join(output, "barrier");
const worker = path.resolve("src/backend/testing/concurrentBuildWorker.ts");
try {
const children = [1, 2].map(() =>
spawn(process.execPath, ["--import", "tsx", worker, root, output, barrier], {
cwd: process.cwd(),
stdio: ["ignore", "pipe", "pipe"],
}),
);
const completions = children.map(waitForExit);
await waitFor(
() =>
fs
.readdirSync(output)
.filter((entry) => entry.startsWith("barrier.") && entry.endsWith(".ready"))
.length === 2,
);
fs.writeFileSync(`${barrier}.go`, "");
const checksums = await Promise.all(completions);
assert.equal(new Set(checksums).size, 1);
const releases = fs.readdirSync(path.join(output, "releases"));
assert.equal(releases.length, 1);
new BuildEngine(output).verifyRelease(releases[0]);
assert.equal(
fs.readdirSync(output).some((entry) => entry.startsWith(".release-")),
false,
);
} finally {
fs.rmSync(root, { recursive: true, force: true });
fs.rmSync(output, { recursive: true, force: true });
}
});
test("partial rendering failure leaves no release or temporary directory", () => {
const root = createRepository();
const output = fs.mkdtempSync(path.join(os.tmpdir(), "partial-release-"));
try {
const template = path.join(
root,
"packages/site-definition/.theme/templates/home.liquid",
);
fs.appendFileSync(template, "\n{{ undefined.value }}\n");
commit(root, "break rendering");
const fixture = loadFixture(root, "partial-failure");
try {
const engine = new BuildEngine(output);
assert.throws(() => engine.build(fixture.input), /undefined|not defined/i);
assert.equal(fs.existsSync(engine.buildDirectory(fixture.input.artifactBuildId)), false);
assert.equal(
fs.readdirSync(output).some((entry) => entry.startsWith(".release-")),
false,
);
} finally {
fixture.manager.dispose();
}
} finally {
fs.rmSync(root, { recursive: true, force: true });
fs.rmSync(output, { recursive: true, force: true });
}
});
test("staging and current pointers promote only checksum-verified releases", () => {
const root = createRepository();
const output = fs.mkdtempSync(path.join(os.tmpdir(), "promotion-release-"));
const first = loadFixture(root, "first");
try {
const engine = new BuildEngine(output);
const firstResult = engine.build(first.input);
engine.stage(first.input.artifactBuildId);
engine.activate(first.input.artifactBuildId);
const css = path.join(
root,
"packages/site-definition/.theme/assets/styles/theme.css",
);
fs.appendFileSync(css, "\n.second-release{display:block}\n");
commit(root, "second release");
const second = loadFixture(root, "second");
try {
const secondResult = engine.build(second.input);
engine.stage(second.input.artifactBuildId);
assert.equal(path.basename(fs.realpathSync(engine.currentDirectory())), first.input.artifactBuildId);
assert.equal(path.basename(fs.realpathSync(engine.stagingDirectory())), second.input.artifactBuildId);
engine.activate(second.input.artifactBuildId);
assert.equal(path.basename(fs.realpathSync(engine.currentDirectory())), second.input.artifactBuildId);
assert.equal(path.basename(fs.realpathSync(engine.stagingDirectory())), second.input.artifactBuildId);
fs.appendFileSync(path.join(firstResult.outputDirectory, "index.html"), "tamper");
assert.throws(() => engine.activate(first.input.artifactBuildId), /E_RELEASE_CHECKSUM/);
assert.equal(path.basename(fs.realpathSync(engine.currentDirectory())), second.input.artifactBuildId);
assert.ok(fs.existsSync(secondResult.outputDirectory));
} finally {
second.manager.dispose();
}
} finally {
first.manager.dispose();
fs.rmSync(root, { recursive: true, force: true });
fs.rmSync(output, { recursive: true, force: true });
}
});
test("repeated identical builds retain identical artifact checksums", () => {
const root = createRepository();
const output = fs.mkdtempSync(path.join(os.tmpdir(), "idempotent-release-"));
const fixture = loadFixture(root, "idempotent");
try {
const engine = new BuildEngine(output);
const checksums = Array.from(
{ length: 20 },
() => engine.build(fixture.input).artifactChecksum,
);
assert.equal(new Set(checksums).size, 1);
} finally {
fixture.manager.dispose();
fs.rmSync(root, { recursive: true, force: true });
fs.rmSync(output, { recursive: true, force: true });
}
});
+35 -3
View File
@@ -375,7 +375,19 @@ export class BuildEngine {
const artifactChecksum = `sha256-${sha256(checksumsDocument)}`;
const generatedFiles = [...files].sort();
const artifactSizeBytes = generatedFiles.reduce((sum, relative) => sum + fs.statSync(path.join(temporaryDirectory, ...relative.split("/"))).size, 0);
fs.renameSync(temporaryDirectory, outputDirectory);
try {
fs.renameSync(temporaryDirectory, outputDirectory);
} catch (error) {
if (!fs.existsSync(outputDirectory)) throw error;
const verified = this.verifyRelease(input.artifactBuildId);
fs.rmSync(temporaryDirectory, { recursive: true, force: true });
fs.chmodSync(outputDirectory, 0o750);
return {
success: true, outputDirectory, generatedRoutesCount: verified.routes.length,
artifactChecksum: verified.artifactChecksum, artifactSizeBytes: verified.sizeBytes,
validationReport, generatedFiles: verified.files,
};
}
fs.chmodSync(outputDirectory, 0o750);
return { success: true, outputDirectory, generatedRoutesCount: routes.size, artifactChecksum, artifactSizeBytes, validationReport, generatedFiles };
} catch (error) {
@@ -432,8 +444,28 @@ export class BuildEngine {
const pointer = path.join(this.outputRoot, name);
const next = path.join(this.outputRoot, `.${name}-${process.pid}-${crypto.randomUUID()}`);
fs.symlinkSync(target, next, process.platform === "win32" ? "junction" : "dir");
try { if (fs.lstatSync(pointer, { throwIfNoEntry: false })) fs.rmSync(pointer, { recursive: true, force: true }); fs.renameSync(next, pointer); }
catch (error) { fs.rmSync(next, { recursive: true, force: true }); throw error; }
try {
if (process.platform !== "win32") {
fs.renameSync(next, pointer);
} else {
const backup = path.join(
this.outputRoot,
`.${name}-backup-${process.pid}-${crypto.randomUUID()}`,
);
const existing = fs.lstatSync(pointer, { throwIfNoEntry: false });
if (existing) fs.renameSync(pointer, backup);
try {
fs.renameSync(next, pointer);
if (existing) fs.rmSync(backup, { recursive: true, force: true });
} catch (error) {
if (existing && !fs.existsSync(pointer)) fs.renameSync(backup, pointer);
throw error;
}
}
} catch (error) {
fs.rmSync(next, { recursive: true, force: true });
throw error;
}
return pointer;
}
}
+43
View File
@@ -0,0 +1,43 @@
import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import test from "node:test";
import { RepositorySnapshotManager } from "./repositories/repositorySnapshot";
import { commit, createRepository, git } from "./testing/repositoryFixture";
test("snapshot loader materializes and retains one exact commit", () => {
const root = createRepository();
const workRoot = fs.mkdtempSync(path.join(os.tmpdir(), "snapshot-test-"));
const manager = new RepositorySnapshotManager(workRoot);
try {
const expectedCommit = git(root, ["rev-parse", "HEAD"]);
const snapshot = manager.resolve("site-definition", root, "HEAD");
const css = path.join(
snapshot.checkoutRoot,
"packages/site-definition/.theme/assets/styles/theme.css",
);
const original = fs.readFileSync(css, "utf8");
fs.appendFileSync(
path.join(
root,
"packages/site-definition/.theme/assets/styles/theme.css",
),
"\n.branch-advanced{display:block}\n",
);
const advancedCommit = commit(root, "advance branch");
assert.equal(snapshot.commit, expectedCommit);
assert.notEqual(snapshot.commit, advancedCommit);
assert.equal(fs.readFileSync(css, "utf8"), original);
assert.equal(fs.existsSync(path.join(snapshot.checkoutRoot, ".git")), false);
const checkoutRoot = snapshot.checkoutRoot;
manager.dispose();
assert.equal(fs.existsSync(checkoutRoot), false);
} finally {
manager.dispose();
fs.rmSync(root, { recursive: true, force: true });
fs.rmSync(workRoot, { recursive: true, force: true });
}
});
+17
View File
@@ -132,6 +132,18 @@ export class LabyricornStore {
this.auditLogs.unshift({ id: `log-${Date.now()}-${this.auditLogs.length}`, timestamp: new Date().toISOString(), user: "[email protected]", action, category, details, result });
}
mergeDiscoveredGitSources(discovered: readonly GitSource[]): number {
const repositories = new Set(this.gitSources.map((source) => source.repository));
const additions: GitSource[] = [];
for (const source of discovered) {
if (repositories.has(source.repository)) continue;
repositories.add(source.repository);
additions.push(source);
}
if (additions.length > 0) this.gitSources = [...this.gitSources, ...additions];
return additions.length;
}
validateTheme(): ThemeConfig {
const loader = this.loader();
try {
@@ -235,6 +247,10 @@ export class LabyricornStore {
return null;
}
pushToTarget(targetId: string, buildId: string): { success: boolean; logs: string[] } {
// Deliberately fail closed: no configuration field can enable remote execution.
if (!REMOTE_PUBLICATION_IMPLEMENTED) {
return { success: false, logs: ["Remote publication is not implemented."] };
}
const target = this.pushTargets.find((item) => item.id === targetId);
const build = this.builds.find((item) => item.id === buildId);
if (!target || !build?.artifactBuildId) return { success: false, logs: ["Target or verified build not found."] };
@@ -244,4 +260,5 @@ export class LabyricornStore {
rollbackLocal(targetReleaseId: string): { success: boolean; message: string } { return this.activateReleaseLocally(targetReleaseId); }
}
export const REMOTE_PUBLICATION_IMPLEMENTED = false as const;
export const store = new LabyricornStore();
@@ -0,0 +1,21 @@
import fs from "node:fs";
import { BuildEngine } from "../buildEngine";
import { loadFixture } from "./repositoryFixture";
const [repository, output, barrier] = process.argv.slice(2);
if (!repository || !output || !barrier) {
throw new Error("repository, output, and barrier arguments are required.");
}
const fixture = loadFixture(repository, `worker-${process.pid}`);
try {
fs.writeFileSync(`${barrier}.${process.pid}.ready`, "");
const waitBuffer = new Int32Array(new SharedArrayBuffer(4));
while (!fs.existsSync(`${barrier}.go`)) {
Atomics.wait(waitBuffer, 0, 0, 10);
}
const result = new BuildEngine(output).build(fixture.input);
process.stdout.write(`${result.artifactChecksum}\n`);
} finally {
fixture.manager.dispose();
}
+53
View File
@@ -0,0 +1,53 @@
import { execFileSync } from "node:child_process";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { BuildInputLoader } from "../buildInputLoader";
import { BUILDER_VERSION } from "../buildEngine";
export const git = (root: string, args: string[]): string =>
execFileSync("git", ["-C", root, ...args], {
encoding: "utf8",
env: {
...process.env,
GIT_AUTHOR_DATE: "2026-01-01T00:00:00Z",
GIT_COMMITTER_DATE: "2026-01-01T00:00:00Z",
},
}).trim();
export const createRepository = (): string => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), "labyricorn-git-fixture-"));
fs.mkdirSync(path.join(root, "packages"), { recursive: true });
fs.cpSync(
path.resolve("packages/site-definition"),
path.join(root, "packages/site-definition"),
{ recursive: true },
);
fs.cpSync(
path.resolve("packages/content-models"),
path.join(root, "packages/content-models"),
{ recursive: true },
);
git(root, ["init", "-b", "main"]);
git(root, ["config", "user.name", "Fixture"]);
git(root, ["config", "user.email", "[email protected]"]);
git(root, ["add", "."]);
git(root, ["commit", "-m", "fixture"]);
return root;
};
export const commit = (root: string, message: string): string => {
git(root, ["add", "-A"]);
git(root, ["commit", "-m", message]);
return git(root, ["rev-parse", "HEAD"]);
};
export const loadFixture = (root: string, runId: string) => {
const manager = new BuildInputLoader({
repositoryRoot: root,
ref: "HEAD",
siteDefinitionPath: "packages/site-definition",
builderVersion: BUILDER_VERSION,
});
return { manager, input: manager.load(runId) };
};