mirror of
https://github.com/morluto/rea.git
synced 2026-10-30 19:35:56 +00:00
185 lines
6.0 KiB
TypeScript
185 lines
6.0 KiB
TypeScript
import { projectPermissionFailure } from "./PermissionFailure.js";
|
|
import {
|
|
AnalysisCapabilityUnavailableError,
|
|
AnalysisInputError,
|
|
AnalysisProtocolError,
|
|
type AnalysisError,
|
|
} from "../domain/errors.js";
|
|
import {
|
|
createEvidence,
|
|
parseEvidence,
|
|
type Evidence,
|
|
} from "../domain/evidence.js";
|
|
import { jsonValueSchema } from "../domain/jsonValue.js";
|
|
import {
|
|
managedRuntimeCorrelationInputSchema,
|
|
planManagedRuntimeCorrelation,
|
|
type ManagedRuntimeCorrelationInput,
|
|
} from "../domain/managedRuntimeCorrelation.js";
|
|
import { err, ok, type Result } from "../domain/result.js";
|
|
import type { PermissionAuthority } from "./PermissionAuthority.js";
|
|
import { MANAGED_WORKFLOW_PROVIDER } from "./InvestigationProviders.js";
|
|
import { digestJson } from "./JavaScriptReplayPlanning.js";
|
|
|
|
const OPERATION = "plan_managed_runtime_correlation" as const;
|
|
|
|
/** Parsed availability and authority scope for managed runtime correlation. */
|
|
export type ManagedRuntimePolicy =
|
|
| { readonly status: "disabled" }
|
|
| {
|
|
readonly status: "enabled";
|
|
readonly roots: readonly [string, ...string[]];
|
|
readonly executablePath: string;
|
|
};
|
|
|
|
/** A managed-runtime policy that grants no runtime-correlation authority. */
|
|
export const MANAGED_RUNTIME_DISABLED = {
|
|
status: "disabled",
|
|
} as const satisfies ManagedRuntimePolicy;
|
|
|
|
/** Live policy and authority dependencies for managed runtime correlation. */
|
|
export interface ManagedRuntimeCorrelationDependencies {
|
|
readonly policy: () => ManagedRuntimePolicy;
|
|
readonly authority: PermissionAuthority | undefined;
|
|
}
|
|
|
|
/** Create a separately authorized, non-executing managed runtime plan Evidence. */
|
|
export const planManagedRuntimeCorrelationEvidence = async (
|
|
dependencies: ManagedRuntimeCorrelationDependencies,
|
|
rawInput: unknown,
|
|
): Promise<Result<Evidence, AnalysisError>> => {
|
|
const parsed = managedRuntimeCorrelationInputSchema.safeParse(rawInput);
|
|
if (!parsed.success)
|
|
return err(new AnalysisInputError(OPERATION, { cause: parsed.error }));
|
|
return planManagedRuntimeCorrelationEvidenceValidated(
|
|
dependencies,
|
|
parsed.data,
|
|
);
|
|
};
|
|
|
|
/** Plan runtime correlation from input parsed by a trusted adapter. */
|
|
export const planManagedRuntimeCorrelationEvidenceValidated = async (
|
|
dependencies: ManagedRuntimeCorrelationDependencies,
|
|
input: ManagedRuntimeCorrelationInput,
|
|
): Promise<Result<Evidence, AnalysisError>> => {
|
|
const policy = dependencies.policy();
|
|
if (policy.status === "disabled")
|
|
return err(
|
|
new AnalysisCapabilityUnavailableError(
|
|
MANAGED_WORKFLOW_PROVIDER.id,
|
|
OPERATION,
|
|
"managed runtime correlation is disabled; configure exact roots and runtime executable before enabling it",
|
|
),
|
|
);
|
|
if (dependencies.authority === undefined)
|
|
return err(
|
|
new AnalysisCapabilityUnavailableError(
|
|
MANAGED_WORKFLOW_PROVIDER.id,
|
|
OPERATION,
|
|
"managed runtime permission policy is not configured",
|
|
),
|
|
);
|
|
let staticEvidence: Evidence, artifactPath: string;
|
|
try {
|
|
staticEvidence = parseEvidence(input.static_members);
|
|
artifactPath = artifactPathFor(staticEvidence);
|
|
} catch (cause: unknown) {
|
|
return err(new AnalysisInputError(OPERATION, { cause }));
|
|
}
|
|
const authorized = await dependencies.authority.explain(
|
|
{
|
|
capability: "managed_runtime",
|
|
roots: [artifactPath],
|
|
executables: [policy.executablePath],
|
|
environment_names: [],
|
|
network: "none",
|
|
mount: false,
|
|
operation_identity: operationIdentity(input, staticEvidence),
|
|
},
|
|
"read",
|
|
{ remediation: "restart" },
|
|
);
|
|
if (!authorized.ok) return err(projectPermissionFailure(authorized.error));
|
|
try {
|
|
const result = planManagedRuntimeCorrelation(
|
|
input,
|
|
policy.executablePath,
|
|
authorized.value.grant_id,
|
|
);
|
|
return ok(
|
|
createEvidence(
|
|
{
|
|
path: result.static_observation.artifact_path,
|
|
sha256: result.static_observation.artifact_sha256,
|
|
format: "pe",
|
|
},
|
|
MANAGED_WORKFLOW_PROVIDER,
|
|
{
|
|
predicateType: "rea.managed-runtime-correlation-plan",
|
|
operation: OPERATION,
|
|
parameters: {
|
|
static_members_evidence_id: staticEvidence.evidence_id,
|
|
method: jsonValueSchema.parse(input.method),
|
|
requested_effect: input.requested_effect,
|
|
host: jsonValueSchema.parse(input.host),
|
|
bounds: jsonValueSchema.parse(input.bounds),
|
|
},
|
|
result: jsonValueSchema.parse(result),
|
|
rawResult: null,
|
|
confidence: "derived",
|
|
authority: "analyst-inference",
|
|
environment: null,
|
|
limitations: result.limitations,
|
|
locations: [
|
|
{
|
|
kind: "artifact-path",
|
|
path: result.static_observation.artifact_path,
|
|
},
|
|
],
|
|
evidenceLinks: result.evidence_links,
|
|
},
|
|
),
|
|
);
|
|
} catch (cause: unknown) {
|
|
return err(
|
|
cause instanceof TypeError
|
|
? new AnalysisInputError(OPERATION, { cause })
|
|
: new AnalysisProtocolError(
|
|
"Managed runtime correlation planning produced an invalid result",
|
|
{ cause },
|
|
),
|
|
);
|
|
}
|
|
};
|
|
|
|
const artifactPathFor = (evidence: Evidence): string => {
|
|
const result = evidence.normalized_result;
|
|
if (
|
|
typeof result === "object" &&
|
|
result !== null &&
|
|
"artifact" in result &&
|
|
typeof result.artifact === "object" &&
|
|
result.artifact !== null &&
|
|
"path" in result.artifact &&
|
|
typeof result.artifact.path === "string"
|
|
)
|
|
return result.artifact.path;
|
|
throw new AnalysisInputError(OPERATION, {
|
|
cause: new TypeError(
|
|
"Managed member Evidence does not expose an artifact path",
|
|
),
|
|
});
|
|
};
|
|
|
|
const operationIdentity = (
|
|
input: ManagedRuntimeCorrelationInput,
|
|
evidence: Evidence,
|
|
): string =>
|
|
`${OPERATION}:${digestJson({
|
|
evidence_id: evidence.evidence_id,
|
|
method: input.method,
|
|
requested_effect: input.requested_effect,
|
|
host: input.host,
|
|
bounds: input.bounds,
|
|
})}`;
|