153 lines
5.1 KiB
JavaScript
153 lines
5.1 KiB
JavaScript
|
|
#!/usr/bin/env node
|
||
|
|
import fs from "node:fs";
|
||
|
|
import os from "node:os";
|
||
|
|
import path from "node:path";
|
||
|
|
import { fileURLToPath } from "node:url";
|
||
|
|
|
||
|
|
const codexHome = process.env.CODEX_HOME ?? path.join(os.homedir(), ".codex");
|
||
|
|
const controlRoot = process.env.GH_CODEX_CONTROL_ROOT ?? path.join(
|
||
|
|
codexHome,
|
||
|
|
"runtime",
|
||
|
|
"guanghu-codex-host-bridge",
|
||
|
|
"state",
|
||
|
|
"control",
|
||
|
|
);
|
||
|
|
const controllerPath = path.join(controlRoot, "current-controller.json");
|
||
|
|
const leasePath = path.join(controlRoot, "write-lease.json");
|
||
|
|
const leaseScript = path.join(path.dirname(fileURLToPath(import.meta.url)), "write-lease.mjs");
|
||
|
|
|
||
|
|
function readJson(file) {
|
||
|
|
try {
|
||
|
|
return JSON.parse(fs.readFileSync(file, "utf8"));
|
||
|
|
} catch {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function emit(value) {
|
||
|
|
process.stdout.write(JSON.stringify(value));
|
||
|
|
}
|
||
|
|
|
||
|
|
function allow(additionalContext = null) {
|
||
|
|
emit(additionalContext ? {
|
||
|
|
hookSpecificOutput: {
|
||
|
|
hookEventName: "PreToolUse",
|
||
|
|
additionalContext,
|
||
|
|
},
|
||
|
|
} : { continue: true });
|
||
|
|
}
|
||
|
|
|
||
|
|
function deny(reason) {
|
||
|
|
emit({
|
||
|
|
hookSpecificOutput: {
|
||
|
|
hookEventName: "PreToolUse",
|
||
|
|
permissionDecision: "deny",
|
||
|
|
permissionDecisionReason: reason,
|
||
|
|
},
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
function payloadOf(input) {
|
||
|
|
if (typeof input.tool_input === "string") return input.tool_input;
|
||
|
|
try {
|
||
|
|
return JSON.stringify(input.tool_input ?? {});
|
||
|
|
} catch {
|
||
|
|
return "";
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function highRiskCategory(input) {
|
||
|
|
const name = String(input.tool_name ?? "");
|
||
|
|
const payload = payloadOf(input);
|
||
|
|
const combined = `${name}\n${payload}`;
|
||
|
|
if (combined.includes(leaseScript)) return null;
|
||
|
|
if (
|
||
|
|
/\bgit\b[\s\S]{0,900}\bpush\b/iu.test(combined) ||
|
||
|
|
/\b(?:gh\s+(?:pr\s+(?:create|merge)|release\s+create|repo\s+delete)|docker\s+push)\b/iu.test(combined)
|
||
|
|
) return "remote_git";
|
||
|
|
if (
|
||
|
|
/\b(?:npm|pnpm|yarn|cargo|twine)\s+publish\b/iu.test(combined) ||
|
||
|
|
/\b(?:vercel|flyctl|netlify)\b[\s\S]{0,300}\b(?:deploy|publish|--prod)\b/iu.test(combined) ||
|
||
|
|
/\b(?:wrangler|kubectl|helm)\b[\s\S]{0,300}\b(?:deploy|publish|apply|delete|patch|replace|upgrade)\b/iu.test(combined) ||
|
||
|
|
/\bcurl\b[\s\S]{0,500}(?:-X|--request)\s*(?:POST|PUT|PATCH|DELETE)\b/iu.test(combined) ||
|
||
|
|
/(?:deploy|publish|create_release|merge_pull_request|send_email)/iu.test(name)
|
||
|
|
) return "external_publish";
|
||
|
|
if (
|
||
|
|
/\brm\s+/iu.test(payload) ||
|
||
|
|
/\bfind\b[\s\S]{0,700}\s-delete\b/iu.test(payload) ||
|
||
|
|
/\b(?:npm|pnpm|yarn)\s+cache\s+(?:clean|clear)\b/iu.test(payload) ||
|
||
|
|
/\bcargo\s+clean\b/iu.test(payload) ||
|
||
|
|
/\bxcodebuild\b[\s\S]{0,300}\bclean\b/iu.test(payload) ||
|
||
|
|
/\b(?:brew|port)\s+cleanup\b/iu.test(payload) ||
|
||
|
|
/\bgit\b[\s\S]{0,300}\b(?:clean\b|reset\s+--hard\b)/iu.test(payload) ||
|
||
|
|
/\bdiskutil\b[\s\S]{0,200}\berase/iu.test(payload) ||
|
||
|
|
/\*\*\* Delete File:/u.test(payload)
|
||
|
|
) return "destructive_cleanup";
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
function capabilityTool(input) {
|
||
|
|
const name = String(input.tool_name ?? "");
|
||
|
|
return (
|
||
|
|
["Bash", "exec_command", "functions.exec_command", "functions.exec", "apply_patch", "write_stdin"].includes(name) ||
|
||
|
|
/(?:^|__)(?:exec|exec_command|apply_patch|write_stdin|computer_use|control_chrome|control_in_app_browser)(?:$|__)/iu.test(name) ||
|
||
|
|
/(?:create|update|delete|remove|write|send|merge|deploy|publish|push)/iu.test(name)
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
function consumeLease(input, controller, category) {
|
||
|
|
const lease = readJson(leasePath);
|
||
|
|
if (
|
||
|
|
!lease ||
|
||
|
|
lease.schema !== "guanghu.codex-one-shot-write-lease/v1" ||
|
||
|
|
lease.one_shot !== true ||
|
||
|
|
lease.control_epoch !== controller.control_epoch ||
|
||
|
|
lease.session_id !== input.session_id ||
|
||
|
|
lease.turn_id !== input.turn_id ||
|
||
|
|
lease.category !== category ||
|
||
|
|
lease.cwd !== path.resolve(input.cwd ?? process.cwd()) ||
|
||
|
|
!Number.isFinite(lease.expires_at_unix_ms) ||
|
||
|
|
lease.expires_at_unix_ms < Date.now()
|
||
|
|
) return null;
|
||
|
|
try {
|
||
|
|
fs.unlinkSync(leasePath);
|
||
|
|
} catch {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
return lease;
|
||
|
|
}
|
||
|
|
|
||
|
|
let input = {};
|
||
|
|
try {
|
||
|
|
const raw = fs.readFileSync(0, "utf8").trim();
|
||
|
|
if (raw) input = JSON.parse(raw);
|
||
|
|
} catch {
|
||
|
|
input = {};
|
||
|
|
}
|
||
|
|
if ((input.hook_event_name ?? input.hookEventName) !== "PreToolUse") process.exit(0);
|
||
|
|
|
||
|
|
const controller = readJson(controllerPath);
|
||
|
|
const currentSession = Boolean(
|
||
|
|
controller?.schema === "guanghu.codex-current-controller/v1" &&
|
||
|
|
controller.direct_human_natural_language === true &&
|
||
|
|
controller.session_id === input.session_id
|
||
|
|
);
|
||
|
|
if (!currentSession && capabilityTool(input)) {
|
||
|
|
deny(`STALE_OR_UNCLAIMED_CODEX_TASK_CAPABILITY_BLOCKED; current_session=${controller?.session_id ?? "NONE"}; attempted_session=${input.session_id ?? "UNKNOWN"}`);
|
||
|
|
process.exit(0);
|
||
|
|
}
|
||
|
|
const category = highRiskCategory(input);
|
||
|
|
if (!category) {
|
||
|
|
allow();
|
||
|
|
process.exit(0);
|
||
|
|
}
|
||
|
|
if (!currentSession) {
|
||
|
|
deny(`STALE_OR_UNCLAIMED_CODEX_TASK_HIGH_RISK_BLOCKED; category=${category}`);
|
||
|
|
process.exit(0);
|
||
|
|
}
|
||
|
|
const lease = consumeLease(input, controller, category);
|
||
|
|
if (!lease) {
|
||
|
|
deny(`CURRENT_CODEX_TASK_ONE_SHOT_LEASE_REQUIRED; category=${category}; issue_only_after_explicit_current_turn_human_authorization`);
|
||
|
|
process.exit(0);
|
||
|
|
}
|
||
|
|
allow(`GUANGHU_ONE_SHOT_WRITE_LEASE_CONSUMED lease_id=${lease.lease_id}; category=${category}; target=${lease.target}`);
|