fix(audit): validate raw invoker evidence

This commit is contained in:
joshavant
2026-08-11 15:15:15 -05:00
committed by Josh Avant
parent 2d9f64f872
commit 4ccf7d0dfa
2 changed files with 59 additions and 20 deletions
@@ -191,6 +191,38 @@ describe("execution identity admission envelope", () => {
).toThrow("execution identity admission envelope is not canonical");
});
it.each([
["malformed", { state: "invalid" }],
["mixed", { state: "unknown", rawPrincipalRef: "raw-substitute-secret" }],
["untagged", { kind: "local-account", rawPrincipalRef: "legacy-untagged" }],
[
"extra-field",
{
state: "present",
kind: "local-account",
rawPrincipalRef: "raw-principal",
extra: true,
},
],
])("rejects %s raw invoker facts before enqueue projection", (_variant, invoker) => {
const sink = vi.fn(() => true);
const clear = configureExecutionIdentityAdmissionSink(sink);
try {
expect(
enqueueExecutionIdentityContextAtAdmission(facts({ invoker: invoker as never }), {
enabled: true,
contextId: "context-invalid",
executionId: "execution-invalid",
now: 1,
runtimeInstanceId: "runtime-1",
}),
).toBeUndefined();
expect(sink).not.toHaveBeenCalled();
} finally {
clear();
}
});
it("rejects non-plain or lossy clone data without invoking accessors", () => {
const envelope = captureEnvelope(facts({ invoker: { state: "unknown" } }), {
contextId: "context-unknown",
+27 -20
View File
@@ -25,6 +25,25 @@ const evidenceState = () =>
const closedObject = <T extends Parameters<typeof Type.Object>[0]>(properties: T) =>
Type.Object(properties, { additionalProperties: false });
const ExecutionIdentityAdmissionInvokerSchema = Type.Union([
closedObject({
state: Type.Literal("present"),
kind: Type.Union([
Type.Literal("person"),
Type.Literal("agent"),
Type.Literal("service"),
Type.Literal("schedule"),
Type.Literal("webhook"),
Type.Literal("system"),
Type.Literal("local-account"),
Type.Literal("runtime"),
]),
rawPrincipalRef: rawRef(),
displayLabel: Type.Optional(Type.String({ maxLength: 128 })),
}),
closedObject({ state: Type.Literal("unknown") }),
]);
const ExecutionIdentityAdmissionEnvelopeSchema = closedObject({
envelopeVersion: Type.Literal(1),
contextId: boundedRef(),
@@ -62,26 +81,7 @@ const ExecutionIdentityAdmissionEnvelopeSchema = closedObject({
Type.Literal("acp"),
]),
}),
invoker: Type.Optional(
Type.Union([
closedObject({
state: Type.Literal("present"),
kind: Type.Union([
Type.Literal("person"),
Type.Literal("agent"),
Type.Literal("service"),
Type.Literal("schedule"),
Type.Literal("webhook"),
Type.Literal("system"),
Type.Literal("local-account"),
Type.Literal("runtime"),
]),
rawPrincipalRef: rawRef(),
displayLabel: Type.Optional(Type.String({ maxLength: 128 })),
}),
closedObject({ state: Type.Literal("unknown") }),
]),
),
invoker: Type.Optional(ExecutionIdentityAdmissionInvokerSchema),
applicableGrants: Type.Array(closedObject({ rawGrantRef: rawRef(), state: evidenceState() }), {
maxItems: EXECUTION_IDENTITY_ADMISSION_MAX_ITEMS,
}),
@@ -223,6 +223,12 @@ function validateEnvelope(value: unknown): asserts value is ExecutionIdentityAdm
}
}
function validateRawInvoker(value: unknown): void {
if (value !== undefined && !Value.Check(ExecutionIdentityAdmissionInvokerSchema, value)) {
throw new Error("execution identity admission invoker violates its bounded contract");
}
}
function validateToken(value: unknown): asserts value is ExecutionIdentityAdmissionToken {
if (
!Value.Check(ExecutionIdentityAdmissionTokenSchema, value) ||
@@ -419,6 +425,7 @@ export function enqueueExecutionIdentityContextAtAdmission(
}
try {
assertPlainCloneData(facts);
validateRawInvoker(facts.invoker);
const token =
options.token ??
createExecutionIdentityAdmissionToken(facts.runId, {