rea/src/application/JavaScriptApplicationServic...

119 lines
4.0 KiB
TypeScript

import { z } from "zod";
import { ArtifactReaderFailure } from "../artifacts/ArtifactReader.js";
import {
analyzeJavaScriptApplicationInputSchema,
javascriptApplicationAnalysisResultSchema,
} from "../domain/javascriptApplicationAnalysis.js";
import {
AnalysisCapabilityUnavailableError,
AnalysisInputError,
AnalysisProtocolError,
ArtifactOperationError,
PermissionRequiredError,
type AnalysisError,
} from "../domain/errors.js";
import { err, ok, type Result } from "../domain/result.js";
import type { Evidence } from "../domain/evidence.js";
import type { ExecutionOptions } from "./AnalysisProvider.js";
import { createJavaScriptApplicationEvidence } from "./JavaScriptApplicationEvidence.js";
import { reconstructJavaScriptArtifact } from "./JavaScriptArtifactReconstruction.js";
import type { PermissionAuthority } from "./PermissionAuthority.js";
const OPERATION = "analyze_javascript_application" as const;
/** Authorize and statically analyze one local JavaScript/Electron application. */
export const analyzeJavaScriptApplication = async (
authority: PermissionAuthority | undefined,
rawInput: unknown,
options: ExecutionOptions = {},
): Promise<Result<Evidence, AnalysisError>> => {
const parsed = analyzeJavaScriptApplicationInputSchema.safeParse(rawInput);
if (!parsed.success) return err(new AnalysisInputError(OPERATION));
return analyzeJavaScriptApplicationValidated(authority, parsed.data, options);
};
/** Analyze input already parsed by a trusted adapter boundary. */
export const analyzeJavaScriptApplicationValidated = async (
authority: PermissionAuthority | undefined,
input: z.output<typeof analyzeJavaScriptApplicationInputSchema>,
options: ExecutionOptions = {},
): Promise<Result<Evidence, AnalysisError>> => {
if (authority === undefined)
return err(
new AnalysisCapabilityUnavailableError(
"rea-javascript-application",
OPERATION,
"JavaScript application permission policy is not configured",
),
);
const authorized = await authority.authorize(
{
capability: "investigation_input",
roots: [input.input_path],
executables: [],
environment_names: [],
network: "none",
mount: false,
operation_identity: `${OPERATION}:${input.input_path}`,
},
"read",
);
if (!authorized.ok)
return err(
authorized.error instanceof PermissionRequiredError
? authorized.error
: new ArtifactOperationError(OPERATION, "path", {
logicalPath: input.input_path,
declaredSha256: null,
calculatedSha256: null,
unpacked: false,
}),
);
await options.progress?.report({
phase: "analyze_javascript_application",
completed: 0,
total: 1,
message: "Inventorying and parsing approved application artifacts",
});
try {
const reconstructed = await reconstructJavaScriptArtifact(
{
input_path: input.input_path,
format: input.format,
source_map_read_approved: input.source_map_read_approved,
limits: input.limits,
},
options.signal,
);
const { electron_summary: summary, ...application } = reconstructed;
const result = javascriptApplicationAnalysisResultSchema.parse({
schema_version: 1,
...application,
summary,
limitations: reconstructed.graph.limitations,
});
await options.progress?.report({
phase: "analyze_javascript_application",
completed: 1,
total: 1,
message: "Application graph and Electron boundaries reconstructed",
terminal: true,
});
return ok(createJavaScriptApplicationEvidence(input, result));
} catch (cause: unknown) {
if (cause instanceof ArtifactReaderFailure)
return err(
new ArtifactOperationError(OPERATION, cause.reason, cause.details),
);
if (cause instanceof z.ZodError)
return err(
new AnalysisProtocolError(
"JavaScript application analysis produced an invalid result",
{ cause },
),
);
return err(new ArtifactOperationError(OPERATION, "io"));
}
};