fix: 强化NapCat审计只读与路径校验
This commit is contained in:
parent
1f60506c89
commit
7619dd5928
@ -129,8 +129,8 @@ export async function runSelfTest(): Promise<void> {
|
||||
const dryRunAuditCommandText = dryRunAuditCommands.join("\n");
|
||||
for (const expected of [
|
||||
"git status --short --branch",
|
||||
"git fetch upstream --tags --prune",
|
||||
"git fetch origin --prune",
|
||||
"git fetch --dry-run upstream --tags --prune",
|
||||
"git fetch --dry-run origin --prune",
|
||||
"git diff --name-only",
|
||||
"git merge-tree",
|
||||
]) {
|
||||
@ -140,6 +140,17 @@ export async function runSelfTest(): Promise<void> {
|
||||
);
|
||||
}
|
||||
}
|
||||
for (const command of dryRunAudit.commands) {
|
||||
if (
|
||||
command.readOnly &&
|
||||
/^git\s+fetch(?:\s|$)/.test(command.command) &&
|
||||
!/\s--dry-run(?:\s|$)/.test(command.command)
|
||||
) {
|
||||
throw new Error(
|
||||
"NapCat upstream audit read-only fetch must use --dry-run",
|
||||
);
|
||||
}
|
||||
}
|
||||
for (const forbidden of [
|
||||
{ label: "git merge", regex: /(^|\n)\s*git\s+merge(?:\s|$)/ },
|
||||
{ label: "git push", regex: /(^|\n)\s*git\s+push(?:\s|$)/ },
|
||||
@ -153,6 +164,50 @@ export async function runSelfTest(): Promise<void> {
|
||||
);
|
||||
}
|
||||
}
|
||||
for (const invalidPathCase of [
|
||||
{
|
||||
forkPatchFiles: [],
|
||||
name: "Windows absolute path",
|
||||
upstreamChangedFiles: ["D:/repo/packages/napcat-core/login/runtime.ts"],
|
||||
},
|
||||
{
|
||||
forkPatchFiles: [],
|
||||
name: "Unix absolute path",
|
||||
upstreamChangedFiles: ["/repo/packages/napcat-core/login/runtime.ts"],
|
||||
},
|
||||
{
|
||||
forkPatchFiles: [],
|
||||
name: "UNC path",
|
||||
upstreamChangedFiles: [
|
||||
"\\\\server\\share\\packages\\napcat-core\\login\\runtime.ts",
|
||||
],
|
||||
},
|
||||
{
|
||||
forkPatchFiles: [],
|
||||
name: "parent segment path",
|
||||
upstreamChangedFiles: ["packages/../napcat-core/login/runtime.ts"],
|
||||
},
|
||||
{
|
||||
forkPatchFiles: ["packages/../napcat-core/login/runtime.ts"],
|
||||
name: "fork patch parent segment path",
|
||||
upstreamChangedFiles: [],
|
||||
},
|
||||
]) {
|
||||
const invalidPathAudit = classifyNapcatUpstreamAudit({
|
||||
dryMergeConflict: false,
|
||||
forkPatchFiles: invalidPathCase.forkPatchFiles,
|
||||
upstreamChangedFiles: invalidPathCase.upstreamChangedFiles,
|
||||
});
|
||||
if (
|
||||
invalidPathAudit.classification !== "blocked" ||
|
||||
invalidPathAudit.recommendedAction !== "block" ||
|
||||
!invalidPathAudit.reasonCodes.includes("METADATA_UNAVAILABLE")
|
||||
) {
|
||||
throw new Error(
|
||||
`NapCat invalid audit path self-check failed: ${invalidPathCase.name}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
for (const promptName of napcatAutomationPromptNames) {
|
||||
const promptText = loadNapcatAutomationPrompt(promptName);
|
||||
if (!promptText.includes("Do not edit files")) {
|
||||
|
||||
@ -75,6 +75,46 @@ const hotZoneMatchers: Array<{ pattern: string; regex: RegExp }> = [
|
||||
{ pattern: 'vite*.ts', regex: /(?:^|\/)vite[^/]*\.ts$/i },
|
||||
];
|
||||
|
||||
/**
|
||||
* Checks whether Git audit metadata contains a repo-relative path that the classifier can trust.
|
||||
* @param value - Raw path string from fork/upstream Git diff collectors; empty entries are allowed because collectors may emit blank lines.
|
||||
* @returns True when a non-empty path is absolute, UNC-based, or contains a parent-directory segment.
|
||||
*/
|
||||
function hasInvalidAuditPath(value: string): boolean {
|
||||
const trimmed = value.trim();
|
||||
if (!trimmed) return false;
|
||||
if (/^[a-zA-Z]:[\\/]/.test(trimmed)) return true;
|
||||
if (trimmed.startsWith('\\\\') || trimmed.startsWith('//')) return true;
|
||||
if (trimmed.startsWith('/')) return true;
|
||||
|
||||
const normalized = trimmed
|
||||
.replace(/\\/g, '/')
|
||||
.replace(/^\.\/+/, '')
|
||||
.replace(/\/+/g, '/');
|
||||
return normalized.split('/').includes('..');
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks both file lists for invalid non repo-relative metadata.
|
||||
* @param input - Collector result carrying fork patch and upstream changed file paths; both arrays must contain repo-relative Git paths when non-empty.
|
||||
* @returns True when any path entry would make deterministic hot-zone and overlap classification untrustworthy.
|
||||
*/
|
||||
function hasInvalidAuditMetadata(
|
||||
input: NapcatAuditClassificationInput,
|
||||
): boolean {
|
||||
for (const file of input.forkPatchFiles) {
|
||||
if (hasInvalidAuditPath(file)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
for (const file of input.upstreamChangedFiles) {
|
||||
if (hasInvalidAuditPath(file)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes Git path output into a stable comparison key for audit classifiers.
|
||||
* @param value - File path from Git diff/status collectors; may contain Windows separators, leading `./`, or surrounding whitespace, but must represent a repo-relative path.
|
||||
@ -195,6 +235,16 @@ function buildClassificationSummary(
|
||||
export function classifyNapcatUpstreamAudit(
|
||||
input: NapcatAuditClassificationInput,
|
||||
): NapcatAuditClassificationResult {
|
||||
if (hasInvalidAuditMetadata(input)) {
|
||||
return {
|
||||
classification: 'blocked',
|
||||
reasonCodes: ['METADATA_UNAVAILABLE'],
|
||||
recommendedAction: 'block',
|
||||
summary:
|
||||
'Invalid audit metadata: fork and upstream file paths must be repo-relative Git paths without absolute roots, UNC prefixes, or parent-directory segments.',
|
||||
};
|
||||
}
|
||||
|
||||
if (input.dryMergeConflict) {
|
||||
return {
|
||||
classification: 'blocked',
|
||||
@ -254,13 +304,15 @@ export function buildNapcatUpstreamAudit(
|
||||
readOnly: true,
|
||||
},
|
||||
{
|
||||
command: 'git fetch upstream --tags --prune',
|
||||
description: 'Refresh upstream release refs before file-level audit collection.',
|
||||
command: 'git fetch --dry-run upstream --tags --prune',
|
||||
description:
|
||||
'Probe upstream release ref availability without updating local refs.',
|
||||
readOnly: true,
|
||||
},
|
||||
{
|
||||
command: 'git fetch origin --prune',
|
||||
description: 'Refresh fork refs before comparing KT patch and upstream ranges.',
|
||||
command: 'git fetch --dry-run origin --prune',
|
||||
description:
|
||||
'Probe fork ref availability without updating remote-tracking refs.',
|
||||
readOnly: true,
|
||||
},
|
||||
{
|
||||
|
||||
Loading…
Reference in New Issue
Block a user