- Containerized Pi hello-world proof (image mosaic-poc-agent:0.84.4, non-root) - Four immutable contract fixtures loaded into a generated system prompt - build/hello/verify/reset scripts with exact-match gating and reset safety - Documented Pi discovery (v0.84.4, -p mode, --system-prompt, container auth) - Append-only BUILD-LOG with corrections; deferred layers in LAYERS.md - Architecture plan: docs/plans/2026-09-02_atomic-mosaic-foundation.md
114 lines
3.0 KiB
JavaScript
Executable File
114 lines
3.0 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
|
/**
|
|
* Repository-local Git credential helper for git.mosaicstack.dev.
|
|
*
|
|
* Git invokes this helper with "get", "store", or "erase" and consumes its
|
|
* stdout directly. Never invoke it manually, enable shell tracing around it,
|
|
* or add credential values to logs.
|
|
*
|
|
* The credential file is intentionally not part of Git and must remain mode
|
|
* 0600. Override its location with MOSAIC_GITEA_CREDENTIAL_FILE if needed.
|
|
*/
|
|
|
|
import fs from "node:fs";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import process from "node:process";
|
|
|
|
const operation = process.argv[2] ?? "";
|
|
|
|
// Git may offer credentials back through stdin for store/erase. This helper is
|
|
// read-only: ignore those operations and never persist or print their input.
|
|
if (operation !== "get") {
|
|
process.exit(0);
|
|
}
|
|
|
|
const defaultCredentialFile = path.join(
|
|
os.homedir(),
|
|
"secrets",
|
|
"mosaic.gitea.json",
|
|
);
|
|
const credentialFile =
|
|
process.env.MOSAIC_GITEA_CREDENTIAL_FILE ?? defaultCredentialFile;
|
|
|
|
function fail(message) {
|
|
process.stderr.write(`git-credential-mosaic: ${message}\n`);
|
|
process.exit(1);
|
|
}
|
|
|
|
let stat;
|
|
try {
|
|
stat = fs.lstatSync(credentialFile);
|
|
} catch {
|
|
fail("credential file is unavailable");
|
|
}
|
|
|
|
if (!stat.isFile() || stat.isSymbolicLink()) {
|
|
fail("credential path must be a regular, non-symbolic-link file");
|
|
}
|
|
if ((stat.mode & 0o077) !== 0) {
|
|
fail("credential file permissions must be 0600 or stricter");
|
|
}
|
|
if (typeof process.getuid === "function" && stat.uid !== process.getuid()) {
|
|
fail("credential file must be owned by the current user");
|
|
}
|
|
|
|
let document;
|
|
try {
|
|
document = JSON.parse(fs.readFileSync(credentialFile, "utf8"));
|
|
} catch {
|
|
fail("credential file is not valid JSON");
|
|
}
|
|
|
|
const entry = document?.mosaicstack;
|
|
const configuredUrl = entry?.url;
|
|
const username = entry?.user;
|
|
const token = entry?.api_token;
|
|
|
|
if (
|
|
typeof configuredUrl !== "string" ||
|
|
typeof username !== "string" ||
|
|
typeof token !== "string" ||
|
|
username.length === 0 ||
|
|
token.length === 0 ||
|
|
/[\r\n]/.test(username) ||
|
|
/[\r\n]/.test(token)
|
|
) {
|
|
fail("credential file is missing valid mosaicstack url/user/api_token fields");
|
|
}
|
|
|
|
let credentialUrl;
|
|
try {
|
|
credentialUrl = new URL(configuredUrl);
|
|
} catch {
|
|
fail("configured credential URL is invalid");
|
|
}
|
|
|
|
if (
|
|
credentialUrl.protocol !== "https:" ||
|
|
credentialUrl.hostname !== "git.mosaicstack.dev"
|
|
) {
|
|
fail("credential URL is not the approved HTTPS Gitea host");
|
|
}
|
|
|
|
const request = {};
|
|
for (const line of fs.readFileSync(0, "utf8").split("\n")) {
|
|
const separator = line.indexOf("=");
|
|
if (separator > 0) {
|
|
request[line.slice(0, separator)] = line.slice(separator + 1);
|
|
}
|
|
}
|
|
|
|
// Fail closed: emit credentials only for the approved HTTPS host. A host may
|
|
// include an explicit port; it must match the configured URL exactly.
|
|
if (
|
|
request.protocol !== "https" ||
|
|
request.host !== credentialUrl.host
|
|
) {
|
|
process.exit(0);
|
|
}
|
|
|
|
// stdout is the Git credential-helper protocol channel, consumed directly by
|
|
// Git. Do not add status messages here.
|
|
process.stdout.write(`username=${username}\npassword=${token}\n`);
|