Add AST-based security middleware and enforcement wiring

This commit is contained in:
2026-02-23 14:21:22 -05:00
parent 9b4216dda9
commit ef2a25b5fb
28 changed files with 1936 additions and 37 deletions

95
src/security/schemas.ts Normal file
View File

@@ -0,0 +1,95 @@
import { z } from "zod";
function dedupe(values: readonly string[]): string[] {
const out: string[] = [];
const seen = new Set<string>();
for (const value of values) {
if (seen.has(value)) {
continue;
}
seen.add(value);
out.push(value);
}
return out;
}
const stringTokenSchema = z
.string()
.trim()
.min(1)
.refine((value) => !value.includes("\0"), "String token cannot include null bytes.");
export const toolClearancePolicySchema = z
.object({
allowlist: z.array(stringTokenSchema).default([]),
banlist: z.array(stringTokenSchema).default([]),
})
.strict();
export type ToolClearancePolicy = z.infer<typeof toolClearancePolicySchema>;
export function parseToolClearancePolicy(input: unknown): ToolClearancePolicy {
const parsed = toolClearancePolicySchema.parse(input);
return {
allowlist: dedupe(parsed.allowlist),
banlist: dedupe(parsed.banlist),
};
}
export const shellValidationPolicySchema = z
.object({
allowedBinaries: z.array(stringTokenSchema).min(1),
worktreeRoot: stringTokenSchema,
protectedPaths: z.array(stringTokenSchema).default([]),
requireCwdWithinWorktree: z.boolean().default(true),
rejectRelativePathTraversal: z.boolean().default(true),
enforcePathBoundaryOnArguments: z.boolean().default(true),
allowedEnvAssignments: z.array(stringTokenSchema).default([]),
blockedEnvAssignments: z.array(stringTokenSchema).default([]),
})
.strict();
export type ShellValidationPolicy = z.infer<typeof shellValidationPolicySchema>;
export function parseShellValidationPolicy(input: unknown): ShellValidationPolicy {
const parsed = shellValidationPolicySchema.parse(input);
return {
...parsed,
allowedBinaries: dedupe(parsed.allowedBinaries),
protectedPaths: dedupe(parsed.protectedPaths),
allowedEnvAssignments: dedupe(parsed.allowedEnvAssignments),
blockedEnvAssignments: dedupe(parsed.blockedEnvAssignments),
};
}
export const executionEnvPolicySchema = z
.object({
inherit: z.array(stringTokenSchema).default(["PATH", "HOME", "TMPDIR", "TMP", "TEMP", "LANG", "LC_ALL"]),
scrub: z.array(stringTokenSchema).default([]),
inject: z.record(z.string(), z.string()).default({}),
})
.strict();
export type ExecutionEnvPolicy = z.infer<typeof executionEnvPolicySchema>;
export function parseExecutionEnvPolicy(input: unknown): ExecutionEnvPolicy {
const parsed = executionEnvPolicySchema.parse(input);
return {
inherit: dedupe(parsed.inherit),
scrub: dedupe(parsed.scrub),
inject: { ...parsed.inject },
};
}
export type SecurityViolationHandling = "hard_abort" | "validation_fail";
export const securityViolationHandlingSchema = z.union([
z.literal("hard_abort"),
z.literal("validation_fail"),
]);
export function parseSecurityViolationHandling(input: unknown): SecurityViolationHandling {
return securityViolationHandlingSchema.parse(input);
}