fix: 收紧NapCat提示词加载边界
This commit is contained in:
parent
e29ce3be86
commit
d5e9bf5b01
@ -42,6 +42,7 @@ import {
|
||||
} from "./tools/napcatAutomation.types.js";
|
||||
import {
|
||||
loadNapcatAutomationPrompt,
|
||||
type NapcatAutomationPromptName,
|
||||
napcatAutomationPromptNames,
|
||||
} from "./tools/napcatAutomation.prompts.js";
|
||||
import {
|
||||
@ -99,6 +100,17 @@ export async function runSelfTest(): Promise<void> {
|
||||
throw new Error(`NapCat prompt safety contract missing: ${promptName}`);
|
||||
}
|
||||
}
|
||||
let traversalPromptRejected = false;
|
||||
try {
|
||||
loadNapcatAutomationPrompt("../../README" as NapcatAutomationPromptName);
|
||||
} catch (error) {
|
||||
traversalPromptRejected = String(error).includes(
|
||||
"Unsupported NapCat automation prompt",
|
||||
);
|
||||
}
|
||||
if (!traversalPromptRejected) {
|
||||
throw new Error("NapCat prompt traversal self-check failed");
|
||||
}
|
||||
|
||||
const reviewClassifier = {
|
||||
localizedSecretLabelAllowed: isBenignCredentialReviewValue("密钥"),
|
||||
|
||||
@ -17,14 +17,48 @@ const promptsRoot = path.resolve(
|
||||
'prompts',
|
||||
'napcat',
|
||||
);
|
||||
const promptsRootBoundary = promptsRoot.endsWith(path.sep)
|
||||
? promptsRoot
|
||||
: `${promptsRoot}${path.sep}`;
|
||||
const napcatAutomationPromptNameSet = new Set<string>(
|
||||
napcatAutomationPromptNames,
|
||||
);
|
||||
const napcatAutomationPromptFileNames = {
|
||||
'upstream-audit': 'upstream-audit.md',
|
||||
'sync-candidate-review': 'sync-candidate-review.md',
|
||||
'runtime-release-readiness': 'runtime-release-readiness.md',
|
||||
'remote-dev-handoff': 'remote-dev-handoff.md',
|
||||
} satisfies Record<NapcatAutomationPromptName, string>;
|
||||
|
||||
/**
|
||||
* Parses an external prompt name into the source-controlled NapCat prompt enum.
|
||||
* @param value - Raw prompt name from CLI, JSON, MCP input, or a typed caller; only exact entries in `napcatAutomationPromptNames` are accepted.
|
||||
* @returns A stable prompt name that can be mapped to a fixed file under `prompts/napcat`.
|
||||
*/
|
||||
export function parseNapcatAutomationPromptName(
|
||||
value: string,
|
||||
): NapcatAutomationPromptName {
|
||||
if (napcatAutomationPromptNameSet.has(value)) {
|
||||
return value as NapcatAutomationPromptName;
|
||||
}
|
||||
throw new Error(`Unsupported NapCat automation prompt: ${value}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a source-controlled NapCat automation prompt by stable prompt name.
|
||||
* @param name - Prompt identifier from `napcatAutomationPromptNames`; because it is an enum value rather than a user-supplied path, callers cannot escape `prompts/napcat`.
|
||||
* @param name - Prompt identifier from `napcatAutomationPromptNames`; runtime callers may still pass untyped external strings, so the loader re-validates and resolves only fixed filenames.
|
||||
* @returns UTF-8 prompt text used by Codex CLI automation.
|
||||
*/
|
||||
export function loadNapcatAutomationPrompt(
|
||||
name: NapcatAutomationPromptName,
|
||||
): string {
|
||||
return readFileSync(path.join(promptsRoot, `${name}.md`), 'utf8');
|
||||
const promptName = parseNapcatAutomationPromptName(String(name));
|
||||
const promptPath = path.resolve(
|
||||
promptsRoot,
|
||||
napcatAutomationPromptFileNames[promptName],
|
||||
);
|
||||
if (!promptPath.startsWith(promptsRootBoundary)) {
|
||||
throw new Error(`NapCat automation prompt escaped prompt root: ${name}`);
|
||||
}
|
||||
return readFileSync(promptPath, 'utf8');
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user