mirror of
https://github.com/morluto/rea.git
synced 2026-11-07 07:16:13 +00:00
490 lines
17 KiB
TypeScript
490 lines
17 KiB
TypeScript
import { isAbsolute } from "node:path";
|
|
|
|
import { ConfigurationError } from "../domain/errors.js";
|
|
import { err, ok, type Result } from "../domain/result.js";
|
|
import { parseEnvironment, type Environment } from "./environment.js";
|
|
import {
|
|
parseStringArray,
|
|
parseBrowserArray,
|
|
parseElectronFileRoots,
|
|
parseLoaderArgs,
|
|
} from "./parsers.js";
|
|
import {
|
|
filePolicy,
|
|
permissionScope,
|
|
administratorGrants,
|
|
browserNetworkScope,
|
|
} from "./permissions.js";
|
|
import type { AppConfig } from "./types.js";
|
|
import type { PermissionCeiling } from "../domain/permissionPolicy.js";
|
|
import {
|
|
browserEndpointSchema,
|
|
browserOriginSchema,
|
|
} from "../domain/browserObservation.js";
|
|
|
|
const DEFAULT_HOPPER_LAUNCHER_PATH =
|
|
"/Applications/Hopper Disassembler.app/Contents/MacOS/hopper";
|
|
const defaultHopperLauncherPath = (): string =>
|
|
process.platform === "linux"
|
|
? "/opt/hopper/bin/Hopper"
|
|
: DEFAULT_HOPPER_LAUNCHER_PATH;
|
|
|
|
interface ParsedArrays {
|
|
readonly loaderArgs: readonly string[];
|
|
readonly executableRoots: readonly string[];
|
|
readonly workingRoots: readonly string[];
|
|
readonly allowedEnvironment: readonly string[];
|
|
readonly evidenceRoots: readonly string[];
|
|
readonly investigationInputRoots: readonly string[];
|
|
readonly analysisSnapshotRoots: readonly string[];
|
|
readonly referenceRoots: readonly string[];
|
|
readonly secretPatterns: readonly string[];
|
|
readonly browserEndpoints: readonly string[];
|
|
readonly browserOrigins: readonly string[];
|
|
readonly browserScenarioExecutableRoots: readonly string[];
|
|
readonly browserScenarioEndpoints: readonly string[];
|
|
readonly browserScenarioOrigins: readonly string[];
|
|
readonly browserScenarioEnvironment: readonly string[];
|
|
readonly electronEndpoints: readonly string[];
|
|
readonly electronFileRoots: readonly string[];
|
|
readonly v8InspectorEndpoints: readonly string[];
|
|
readonly v8InspectorFileRoots: readonly string[];
|
|
readonly v8InspectorOrigins: readonly string[];
|
|
readonly javascriptReplayRoots: readonly string[];
|
|
readonly managedRuntimeRoots: readonly string[];
|
|
}
|
|
|
|
type ParsedObservationArrays = Pick<
|
|
ParsedArrays,
|
|
| "browserEndpoints"
|
|
| "browserOrigins"
|
|
| "browserScenarioExecutableRoots"
|
|
| "browserScenarioEndpoints"
|
|
| "browserScenarioOrigins"
|
|
| "browserScenarioEnvironment"
|
|
| "electronEndpoints"
|
|
| "electronFileRoots"
|
|
| "v8InspectorEndpoints"
|
|
| "v8InspectorFileRoots"
|
|
| "v8InspectorOrigins"
|
|
>;
|
|
|
|
const parseAllArrays = (
|
|
env: Environment,
|
|
): Result<ParsedArrays, ConfigurationError> => {
|
|
const loaderArgs = parseLoaderArgs(env.HOPPER_LOADER_ARGS_JSON);
|
|
if (!loaderArgs.ok) return loaderArgs;
|
|
const executableRoots = parseStringArray(
|
|
env.REA_PROCESS_EXECUTABLE_ROOTS_JSON,
|
|
"REA_PROCESS_EXECUTABLE_ROOTS_JSON",
|
|
);
|
|
if (!executableRoots.ok) return executableRoots;
|
|
const workingRoots = parseStringArray(
|
|
env.REA_PROCESS_WORKING_ROOTS_JSON,
|
|
"REA_PROCESS_WORKING_ROOTS_JSON",
|
|
);
|
|
if (!workingRoots.ok) return workingRoots;
|
|
const allowedEnvironment = parseStringArray(
|
|
env.REA_PROCESS_ALLOWED_ENV_JSON,
|
|
"REA_PROCESS_ALLOWED_ENV_JSON",
|
|
);
|
|
if (!allowedEnvironment.ok) return allowedEnvironment;
|
|
const evidenceRoots = parseStringArray(
|
|
env.REA_EVIDENCE_ROOTS_JSON,
|
|
"REA_EVIDENCE_ROOTS_JSON",
|
|
);
|
|
if (!evidenceRoots.ok) return evidenceRoots;
|
|
const investigationInputRoots = parseStringArray(
|
|
env.REA_INVESTIGATION_INPUT_ROOTS_JSON,
|
|
"REA_INVESTIGATION_INPUT_ROOTS_JSON",
|
|
);
|
|
if (!investigationInputRoots.ok) return investigationInputRoots;
|
|
const analysisSnapshotRoots = parseStringArray(
|
|
env.REA_ANALYSIS_SNAPSHOT_ROOTS_JSON,
|
|
"REA_ANALYSIS_SNAPSHOT_ROOTS_JSON",
|
|
);
|
|
if (!analysisSnapshotRoots.ok) return analysisSnapshotRoots;
|
|
const referenceRoots = parseStringArray(
|
|
env.REA_REFERENCE_ROOTS_JSON,
|
|
"REA_REFERENCE_ROOTS_JSON",
|
|
);
|
|
if (!referenceRoots.ok) return referenceRoots;
|
|
const secretPatterns = parseStringArray(
|
|
env.REA_REFERENCE_SECRET_PATTERNS_JSON,
|
|
"REA_REFERENCE_SECRET_PATTERNS_JSON",
|
|
);
|
|
if (!secretPatterns.ok) return secretPatterns;
|
|
const observations = parseObservationArrays(env);
|
|
if (!observations.ok) return observations;
|
|
const javascriptReplayRoots = parseAbsoluteRoots(
|
|
env.REA_JAVASCRIPT_REPLAY_ROOTS_JSON,
|
|
"REA_JAVASCRIPT_REPLAY_ROOTS_JSON",
|
|
);
|
|
if (!javascriptReplayRoots.ok) return javascriptReplayRoots;
|
|
const managedRuntimeRoots = parseAbsoluteRoots(
|
|
env.REA_MANAGED_RUNTIME_ROOTS_JSON,
|
|
"REA_MANAGED_RUNTIME_ROOTS_JSON",
|
|
);
|
|
if (!managedRuntimeRoots.ok) return managedRuntimeRoots;
|
|
return ok({
|
|
loaderArgs: loaderArgs.value,
|
|
executableRoots: executableRoots.value,
|
|
workingRoots: workingRoots.value,
|
|
allowedEnvironment: allowedEnvironment.value,
|
|
evidenceRoots: evidenceRoots.value,
|
|
investigationInputRoots: investigationInputRoots.value,
|
|
analysisSnapshotRoots: analysisSnapshotRoots.value,
|
|
referenceRoots: referenceRoots.value,
|
|
secretPatterns: secretPatterns.value,
|
|
...observations.value,
|
|
javascriptReplayRoots: javascriptReplayRoots.value,
|
|
managedRuntimeRoots: managedRuntimeRoots.value,
|
|
});
|
|
};
|
|
|
|
const parseObservationArrays = (
|
|
env: Environment,
|
|
): Result<ParsedObservationArrays, ConfigurationError> => {
|
|
const browserEndpoints = parseBrowserArray(
|
|
env.REA_BROWSER_CDP_ENDPOINTS_JSON,
|
|
"REA_BROWSER_CDP_ENDPOINTS_JSON",
|
|
browserEndpointSchema,
|
|
16,
|
|
);
|
|
if (!browserEndpoints.ok) return browserEndpoints;
|
|
const browserOrigins = parseBrowserArray(
|
|
env.REA_BROWSER_ALLOWED_ORIGINS_JSON,
|
|
"REA_BROWSER_ALLOWED_ORIGINS_JSON",
|
|
browserOriginSchema,
|
|
32,
|
|
);
|
|
if (!browserOrigins.ok) return browserOrigins;
|
|
const browserScenarioExecutableRoots = parseAbsoluteRoots(
|
|
env.REA_BROWSER_SCENARIO_EXECUTABLE_ROOTS_JSON,
|
|
"REA_BROWSER_SCENARIO_EXECUTABLE_ROOTS_JSON",
|
|
);
|
|
if (!browserScenarioExecutableRoots.ok) return browserScenarioExecutableRoots;
|
|
const browserScenarioEndpoints = parseBrowserArray(
|
|
env.REA_BROWSER_SCENARIO_CDP_ENDPOINTS_JSON,
|
|
"REA_BROWSER_SCENARIO_CDP_ENDPOINTS_JSON",
|
|
browserEndpointSchema,
|
|
16,
|
|
);
|
|
if (!browserScenarioEndpoints.ok) return browserScenarioEndpoints;
|
|
const browserScenarioOrigins = parseBrowserArray(
|
|
env.REA_BROWSER_SCENARIO_ALLOWED_ORIGINS_JSON,
|
|
"REA_BROWSER_SCENARIO_ALLOWED_ORIGINS_JSON",
|
|
browserOriginSchema,
|
|
32,
|
|
);
|
|
if (!browserScenarioOrigins.ok) return browserScenarioOrigins;
|
|
const browserScenarioEnvironment = parseStringArray(
|
|
env.REA_BROWSER_SCENARIO_ALLOWED_ENV_JSON,
|
|
"REA_BROWSER_SCENARIO_ALLOWED_ENV_JSON",
|
|
);
|
|
if (!browserScenarioEnvironment.ok) return browserScenarioEnvironment;
|
|
const electronEndpoints = parseBrowserArray(
|
|
env.REA_ELECTRON_CDP_ENDPOINTS_JSON,
|
|
"REA_ELECTRON_CDP_ENDPOINTS_JSON",
|
|
browserEndpointSchema,
|
|
16,
|
|
);
|
|
if (!electronEndpoints.ok) return electronEndpoints;
|
|
const electronFileRoots = parseElectronFileRoots(
|
|
env.REA_ELECTRON_FILE_ROOTS_JSON,
|
|
);
|
|
if (!electronFileRoots.ok) return electronFileRoots;
|
|
const v8InspectorEndpoints = parseBrowserArray(
|
|
env.REA_V8_INSPECTOR_ENDPOINTS_JSON,
|
|
"REA_V8_INSPECTOR_ENDPOINTS_JSON",
|
|
browserEndpointSchema,
|
|
16,
|
|
);
|
|
if (!v8InspectorEndpoints.ok) return v8InspectorEndpoints;
|
|
const v8InspectorFileRoots = parseAbsoluteRoots(
|
|
env.REA_V8_INSPECTOR_FILE_ROOTS_JSON,
|
|
"REA_V8_INSPECTOR_FILE_ROOTS_JSON",
|
|
);
|
|
if (!v8InspectorFileRoots.ok) return v8InspectorFileRoots;
|
|
const v8InspectorOrigins = parseBrowserArray(
|
|
env.REA_V8_INSPECTOR_ALLOWED_ORIGINS_JSON,
|
|
"REA_V8_INSPECTOR_ALLOWED_ORIGINS_JSON",
|
|
browserOriginSchema,
|
|
32,
|
|
);
|
|
if (!v8InspectorOrigins.ok) return v8InspectorOrigins;
|
|
return ok({
|
|
browserEndpoints: browserEndpoints.value,
|
|
browserOrigins: browserOrigins.value,
|
|
browserScenarioExecutableRoots: browserScenarioExecutableRoots.value,
|
|
browserScenarioEndpoints: browserScenarioEndpoints.value,
|
|
browserScenarioOrigins: browserScenarioOrigins.value,
|
|
browserScenarioEnvironment: browserScenarioEnvironment.value,
|
|
electronEndpoints: electronEndpoints.value,
|
|
electronFileRoots: electronFileRoots.value,
|
|
v8InspectorEndpoints: v8InspectorEndpoints.value,
|
|
v8InspectorFileRoots: v8InspectorFileRoots.value,
|
|
v8InspectorOrigins: v8InspectorOrigins.value,
|
|
});
|
|
};
|
|
|
|
const parseAbsoluteRoots = (
|
|
value: string,
|
|
name: string,
|
|
): Result<readonly string[], ConfigurationError> => {
|
|
const parsed = parseStringArray(value, name);
|
|
if (!parsed.ok) return parsed;
|
|
return parsed.value.some((root) => !isAbsolute(root))
|
|
? err(new ConfigurationError(`${name} must encode absolute roots`))
|
|
: parsed;
|
|
};
|
|
|
|
const appendProcessCaptureCeiling = (
|
|
ceilings: PermissionCeiling[],
|
|
env: Environment,
|
|
arrays: ParsedArrays,
|
|
): void => {
|
|
if (env.REA_PROCESS_CAPTURE_ENABLED !== "true") return;
|
|
ceilings.push(
|
|
permissionScope("process_capture", arrays.workingRoots, {
|
|
executables: arrays.executableRoots,
|
|
environment_names: arrays.allowedEnvironment,
|
|
network:
|
|
env.REA_PROCESS_ALLOW_EXTERNAL_NETWORK === "true" ? "external" : "none",
|
|
}),
|
|
);
|
|
};
|
|
|
|
const appendBrowserObservationCeiling = (
|
|
ceilings: PermissionCeiling[],
|
|
env: Environment,
|
|
arrays: ParsedArrays,
|
|
): void => {
|
|
if (env.REA_BROWSER_OBSERVE_ENABLED !== "true") return;
|
|
ceilings.push(
|
|
permissionScope("browser_observe", [], {
|
|
origins: [...arrays.browserEndpoints, ...arrays.browserOrigins],
|
|
network: browserNetworkScope(arrays.browserOrigins),
|
|
}),
|
|
);
|
|
};
|
|
|
|
const appendBrowserScenarioCeiling = (
|
|
ceilings: PermissionCeiling[],
|
|
env: Environment,
|
|
arrays: ParsedArrays,
|
|
): void => {
|
|
if (env.REA_BROWSER_SCENARIO_ENABLED !== "true") return;
|
|
ceilings.push(
|
|
permissionScope("browser_automate", [], {
|
|
executables: arrays.browserScenarioExecutableRoots,
|
|
environment_names: arrays.browserScenarioEnvironment,
|
|
origins: [
|
|
...arrays.browserScenarioEndpoints,
|
|
...arrays.browserScenarioOrigins,
|
|
],
|
|
network: browserNetworkScope(arrays.browserScenarioOrigins),
|
|
}),
|
|
);
|
|
};
|
|
|
|
const appendElectronObservationCeiling = (
|
|
ceilings: PermissionCeiling[],
|
|
env: Environment,
|
|
arrays: ParsedArrays,
|
|
): void => {
|
|
if (env.REA_ELECTRON_OBSERVE_ENABLED !== "true") return;
|
|
ceilings.push(
|
|
permissionScope("electron_observe", arrays.electronFileRoots, {
|
|
origins: arrays.electronEndpoints,
|
|
network: "loopback",
|
|
}),
|
|
);
|
|
};
|
|
|
|
const appendV8InspectorObservationCeiling = (
|
|
ceilings: PermissionCeiling[],
|
|
env: Environment,
|
|
arrays: ParsedArrays,
|
|
): void => {
|
|
if (env.REA_V8_INSPECTOR_OBSERVE_ENABLED !== "true") return;
|
|
ceilings.push(
|
|
permissionScope("v8_inspector_observe", arrays.v8InspectorFileRoots, {
|
|
origins: [...arrays.v8InspectorEndpoints, ...arrays.v8InspectorOrigins],
|
|
network: "loopback",
|
|
}),
|
|
);
|
|
};
|
|
|
|
const appendNativeMountCeiling = (
|
|
ceilings: PermissionCeiling[],
|
|
env: Environment,
|
|
): void => {
|
|
if (env.REA_ARTIFACT_NATIVE_MOUNT_ENABLED === "true")
|
|
ceilings.push(permissionScope("native_mount", [], { mount: true }));
|
|
};
|
|
|
|
const appendJavaScriptReplayCeiling = (
|
|
ceilings: PermissionCeiling[],
|
|
env: Environment,
|
|
arrays: ParsedArrays,
|
|
): void => {
|
|
if (env.REA_JAVASCRIPT_REPLAY_ENABLED !== "true") return;
|
|
ceilings.push(
|
|
permissionScope("javascript_replay", arrays.javascriptReplayRoots, {
|
|
executables: [
|
|
env.REA_JAVASCRIPT_REPLAY_NODE_PATH,
|
|
env.REA_JAVASCRIPT_REPLAY_BWRAP_PATH,
|
|
env.REA_JAVASCRIPT_REPLAY_SYSTEMD_RUN_PATH,
|
|
env.REA_JAVASCRIPT_REPLAY_SYSTEMCTL_PATH,
|
|
env.REA_JAVASCRIPT_REPLAY_SHELL_PATH,
|
|
],
|
|
network: "none",
|
|
mount: true,
|
|
}),
|
|
);
|
|
};
|
|
|
|
const appendManagedRuntimeCeiling = (
|
|
ceilings: PermissionCeiling[],
|
|
env: Environment,
|
|
arrays: ParsedArrays,
|
|
): void => {
|
|
if (env.REA_MANAGED_RUNTIME_ENABLED !== "true") return;
|
|
ceilings.push(
|
|
permissionScope("managed_runtime", arrays.managedRuntimeRoots, {
|
|
executables: [env.REA_MANAGED_RUNTIME_EXECUTABLE_PATH],
|
|
network: "none",
|
|
}),
|
|
);
|
|
};
|
|
|
|
const buildPermissionCeilings = (
|
|
env: Environment,
|
|
arrays: ParsedArrays,
|
|
): readonly PermissionCeiling[] => {
|
|
const ceilings: PermissionCeiling[] = [
|
|
permissionScope("evidence_read", arrays.evidenceRoots),
|
|
permissionScope("evidence_write", arrays.evidenceRoots),
|
|
permissionScope("investigation_input", arrays.investigationInputRoots),
|
|
permissionScope("investigation_workspace_read", arrays.evidenceRoots),
|
|
permissionScope("investigation_workspace_write", arrays.evidenceRoots),
|
|
permissionScope("snapshot_read", arrays.analysisSnapshotRoots),
|
|
permissionScope("snapshot_write", arrays.analysisSnapshotRoots),
|
|
permissionScope("artifact_extract", ["/"]),
|
|
permissionScope("reference_read", arrays.referenceRoots),
|
|
];
|
|
appendProcessCaptureCeiling(ceilings, env, arrays);
|
|
appendBrowserObservationCeiling(ceilings, env, arrays);
|
|
appendBrowserScenarioCeiling(ceilings, env, arrays);
|
|
appendElectronObservationCeiling(ceilings, env, arrays);
|
|
appendV8InspectorObservationCeiling(ceilings, env, arrays);
|
|
appendNativeMountCeiling(ceilings, env);
|
|
appendJavaScriptReplayCeiling(ceilings, env, arrays);
|
|
appendManagedRuntimeCeiling(ceilings, env, arrays);
|
|
return ceilings;
|
|
};
|
|
|
|
const buildAppConfig = (
|
|
env: Environment,
|
|
arrays: ParsedArrays,
|
|
permissionCeilings: readonly PermissionCeiling[],
|
|
): AppConfig => ({
|
|
analysisProvider: env.REA_ANALYSIS_PROVIDER,
|
|
ghidraInstallDir: env.GHIDRA_INSTALL_DIR,
|
|
ghidraJavaHome: env.JAVA_HOME,
|
|
ilspyCmdPath: env.REA_ILSPY_CMD_PATH,
|
|
hopperLauncherPath: env.HOPPER_LAUNCHER_PATH ?? defaultHopperLauncherPath(),
|
|
hopperTargetPath: env.HOPPER_TARGET_PATH,
|
|
hopperTargetKind: env.HOPPER_TARGET_KIND,
|
|
hopperLoaderArgs: arrays.loaderArgs,
|
|
logLevel: env.REA_LOG_LEVEL,
|
|
processExecutionPolicy: {
|
|
enabled: env.REA_PROCESS_CAPTURE_ENABLED === "true",
|
|
executableRoots: arrays.executableRoots,
|
|
workingRoots: arrays.workingRoots,
|
|
allowedEnvironment: arrays.allowedEnvironment,
|
|
allowExternalNetwork: env.REA_PROCESS_ALLOW_EXTERNAL_NETWORK === "true",
|
|
},
|
|
artifactNativeMountEnabled: env.REA_ARTIFACT_NATIVE_MOUNT_ENABLED === "true",
|
|
artifactIntegrityContinueEnabled:
|
|
env.REA_ARTIFACT_INTEGRITY_CONTINUE_ENABLED === "true",
|
|
evidenceFilePolicy: filePolicy(arrays.evidenceRoots),
|
|
investigationInputRoots: arrays.investigationInputRoots,
|
|
analysisSnapshotFilePolicy: filePolicy(arrays.analysisSnapshotRoots),
|
|
referenceSourcePolicy: {
|
|
roots: arrays.referenceRoots,
|
|
secretPatterns: arrays.secretPatterns,
|
|
maxBytes: 16 * 1024 * 1024,
|
|
maxEntries: 10_000,
|
|
maxDepth: 32,
|
|
maxPathBytes: 4_096,
|
|
},
|
|
browserObservationEnabled: env.REA_BROWSER_OBSERVE_ENABLED === "true",
|
|
browserCdpEndpoints: arrays.browserEndpoints,
|
|
browserAllowedOrigins: arrays.browserOrigins,
|
|
browserScenarioPolicy: {
|
|
enabled: env.REA_BROWSER_SCENARIO_ENABLED === "true",
|
|
executableRoots: arrays.browserScenarioExecutableRoots,
|
|
cdpEndpoints: arrays.browserScenarioEndpoints,
|
|
allowedOrigins: arrays.browserScenarioOrigins,
|
|
allowedEnvironment: arrays.browserScenarioEnvironment,
|
|
},
|
|
electronObservationEnabled: env.REA_ELECTRON_OBSERVE_ENABLED === "true",
|
|
electronCdpEndpoints: arrays.electronEndpoints,
|
|
electronFileRoots: arrays.electronFileRoots,
|
|
v8InspectorObservationEnabled:
|
|
env.REA_V8_INSPECTOR_OBSERVE_ENABLED === "true",
|
|
v8InspectorEndpoints: arrays.v8InspectorEndpoints,
|
|
v8InspectorFileRoots: arrays.v8InspectorFileRoots,
|
|
v8InspectorAllowedOrigins: arrays.v8InspectorOrigins,
|
|
javascriptReplayPolicy: {
|
|
enabled: env.REA_JAVASCRIPT_REPLAY_ENABLED === "true",
|
|
roots: arrays.javascriptReplayRoots,
|
|
nodePath: env.REA_JAVASCRIPT_REPLAY_NODE_PATH,
|
|
bubblewrapPath: env.REA_JAVASCRIPT_REPLAY_BWRAP_PATH,
|
|
systemdRunPath: env.REA_JAVASCRIPT_REPLAY_SYSTEMD_RUN_PATH,
|
|
systemctlPath: env.REA_JAVASCRIPT_REPLAY_SYSTEMCTL_PATH,
|
|
shellPath: env.REA_JAVASCRIPT_REPLAY_SHELL_PATH,
|
|
},
|
|
managedRuntimePolicy: {
|
|
enabled: env.REA_MANAGED_RUNTIME_ENABLED === "true",
|
|
roots: arrays.managedRuntimeRoots,
|
|
executablePath: env.REA_MANAGED_RUNTIME_EXECUTABLE_PATH,
|
|
},
|
|
permissionCeilings,
|
|
administratorPermissionGrants: administratorGrants(
|
|
permissionCeilings.filter(
|
|
({ capability }) =>
|
|
(capability !== "process_capture" ||
|
|
env.REA_PROCESS_CAPTURE_AUTO_GRANT === "true") &&
|
|
(capability !== "browser_automate" ||
|
|
env.REA_BROWSER_SCENARIO_AUTO_GRANT === "true"),
|
|
),
|
|
),
|
|
permissionProjectRoot: env.REA_PERMISSION_PROJECT_ROOT,
|
|
permissionProjectStore: env.REA_PERMISSION_PROJECT_STORE,
|
|
});
|
|
|
|
/**
|
|
* Parse provider and authority configuration once at the composition root.
|
|
* Explicit loader arguments override REA's header-derived defaults for a
|
|
* supported target, allowing callers to refine Hopper's loader behavior without
|
|
* bypassing REA's path and executable-header checks.
|
|
*/
|
|
export const parseConfig = (
|
|
environment: Readonly<Record<string, string | undefined>>,
|
|
): Result<AppConfig, ConfigurationError> => {
|
|
const envResult = parseEnvironment(environment);
|
|
if (!envResult.ok) return envResult;
|
|
const arraysResult = parseAllArrays(envResult.value);
|
|
if (!arraysResult.ok) return arraysResult;
|
|
const permissionCeilings = buildPermissionCeilings(
|
|
envResult.value,
|
|
arraysResult.value,
|
|
);
|
|
return ok(
|
|
buildAppConfig(envResult.value, arraysResult.value, permissionCeilings),
|
|
);
|
|
};
|