feat(deslop-shared-libs): route every Git read through bin/gstack-safe-git

The skill made the model retype a long safe-Git prefix on each call and a
dropped flag failed shared-libs-read-only. bin/gstack-safe-git applies the
fixed env + flag prefix, adds --no-ext-diff --no-textconv to log/show/diff,
allows diff only between two explicit object IDs and ls-files only in the
NUL-delimited overlay form, and refuses every other shape with one line
naming the allowed forms. The template now points at the installed helper
(host global runtime via {{SAFE_GIT}}) and drops the prose it enforces.

Fixtures resolve the helper to this checkout, the git shim records the safety
environment, and isGuardedGitRequest requires the complete prefix (env
included) for every repository read.
This commit is contained in:
garrytan committed 2026-09-30 17:59:23 +00:00
1 parent 157a5ff520
commit a367a1f265
13 files changed
+511 -77

No files matched your search

+9 -1
View File
@@ -133,6 +133,7 @@ export function installSkillToTempHome(
skillName: string,
tempHome?: string,
sections?: string[],
runtimeRoot?: string,
): string {
const home = tempHome || fs.mkdtempSync(path.join(os.tmpdir(), 'codex-e2e-'));
const destDir = path.join(home, '.codex', 'skills', skillName);
@@ -149,6 +150,11 @@ export function installSkillToTempHome(
// nonexistent skill in its response and otherwise pass discovery checks.
fs.copyFileSync(srcSkill, path.join(destDir, 'SKILL.md'));
}
if (runtimeRoot) {
// The temp HOME has no installed gstack runtime; point runtime helpers at the one under test.
const installed = path.join(destDir, 'SKILL.md');
fs.writeFileSync(installed, fs.readFileSync(installed, 'utf8').replaceAll('~/.codex/skills/gstack', runtimeRoot));
}
const srcOpenAIYaml = path.join(skillDir, 'agents', 'openai.yaml');
if (fs.existsSync(srcOpenAIYaml)) {
@@ -180,6 +186,7 @@ export async function runCodexSkill(opts: {
configOverrides?: string[]; // TOML key=value overrides (passed with -c)
ignoreUserConfig?: boolean; // Add --ignore-user-config; auth still comes from CODEX_HOME
signal?: AbortSignal; // Abort the process group when an enclosing eval expires
runtimeRoot?: string; // gstack runtime that ~/.codex/skills/gstack helper paths resolve to
}): Promise<CodexResult> {
const {
skillDir,
@@ -193,6 +200,7 @@ export async function runCodexSkill(opts: {
configOverrides = [],
ignoreUserConfig = false,
signal,
runtimeRoot,
} = opts;
const startTime = Date.now();
@@ -223,7 +231,7 @@ export async function runCodexSkill(opts: {
const realHome = os.homedir();
try {
installSkillToTempHome(skillDir, name, tempHome, sections);
installSkillToTempHome(skillDir, name, tempHome, sections, runtimeRoot);
// Copy authentication only. Copying the whole operator ~/.codex tree leaks
// plugins, MCP servers, rules, memories, and skills into a supposedly