rea-agents 5.0.0 → 6.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +157 -650
- package/bridge/mitmproxy/capture.py +210 -0
- package/bridge/native/rea_lldb_tracer.py +303 -0
- package/bridge/process/ProcessRunTokenReader.swift +263 -0
- package/bridge/process/ProcessRunTokenReaderMain.swift +35 -0
- package/bridge/pwndbg/core_context.py +101 -0
- package/bridge/pwndbg/launch.py +39 -0
- package/bridge/pwndbg/launch_impl.py +34 -0
- package/bridge/pwntools/decoder_runtime.py +59 -0
- package/bridge/pwntools/layout.py +39 -0
- package/bridge/pwntools/layout_impl.py +309 -0
- package/bridge/pwntools/recorded_crash.py +39 -0
- package/bridge/pwntools/recorded_crash_impl.py +143 -0
- package/dist/android/JadxProvider.js +1 -0
- package/dist/application/CapabilityInventory.js +21 -0
- package/dist/application/DirectAnalysis.js +97 -23
- package/dist/application/EvidenceBundleFiles.js +1 -1
- package/dist/application/ReconstructionObligationCandidates.js +5 -1
- package/dist/application/WebNetworkCapturePort.js +1 -0
- package/dist/application/WebNetworkCaptureService.js +132 -0
- package/dist/application/WindowsCapabilities.js +1 -1
- package/dist/application/binary/AnalysisSnapshotCache.js +49 -6
- package/dist/application/binary/BinarySession.js +38 -10
- package/dist/application/binary/BinarySessionOpen.js +16 -3
- package/dist/application/binary/BinarySessionRecords.js +20 -0
- package/dist/application/binaryDiagnostics/BinaryLayoutPort.js +1 -0
- package/dist/application/binaryDiagnostics/BinaryLayoutService.js +57 -0
- package/dist/application/binaryDiagnostics/RecordedCrashPort.js +1 -0
- package/dist/application/binaryDiagnostics/RecordedCrashService.js +60 -0
- package/dist/application/evm/EvmInterfacePort.js +1 -0
- package/dist/application/evm/EvmInterfaceService.js +62 -0
- package/dist/application/javascript/JavaScriptApplicationEvidence.js +1 -2
- package/dist/application/javascript/JavaScriptApplicationService.js +15 -2
- package/dist/application/javascript/JavaScriptArtifactAnalysis.js +42 -23
- package/dist/application/javascript/JavaScriptArtifactGraphDocuments.js +26 -2
- package/dist/application/javascript/JavaScriptArtifactPathResolution.js +74 -24
- package/dist/application/javascript/JavaScriptArtifactReconstruction.js +23 -10
- package/dist/application/javascript/JavaScriptSemanticGraphAsyncProjection.js +7 -7
- package/dist/application/javascript/JavaScriptSemanticGraphBuilder.js +145 -70
- package/dist/application/javascript/JavaScriptSemanticGraphChildProcessProjection.js +14 -15
- package/dist/application/javascript/JavaScriptSemanticGraphConstruction.js +41 -1
- package/dist/application/javascript/JavaScriptSemanticGraphDataProjection.js +7 -7
- package/dist/application/javascript/JavaScriptSemanticGraphFlowProjection.js +5 -9
- package/dist/application/javascript/JavaScriptSemanticGraphObjectProjection.js +3 -3
- package/dist/application/javascript/JavaScriptSemanticGraphProjection.js +118 -12
- package/dist/application/javascript/JavaScriptSemanticGraphResourceProjection.js +3 -3
- package/dist/application/javascript/JavaScriptSemanticGraphValueProjection.js +5 -5
- package/dist/application/{ProcessCli.js → process/ProcessCli.js} +10 -10
- package/dist/application/{ProcessEvidence.js → process/ProcessEvidence.js} +4 -4
- package/dist/artifacts/ArtifactProvider.js +30 -3
- package/dist/artifacts/ArtifactProviderMetadata.js +2 -1
- package/dist/artifacts/ZipArtifactReader.js +3 -0
- package/dist/artifacts/apple/DylibResolutionReader.js +259 -0
- package/dist/artifacts/apple/MachoLoadCommandReader.js +294 -0
- package/dist/{application → artifacts/extraction}/ArtifactExtraction.js +10 -10
- package/dist/{application → artifacts/inventory}/ArtifactGraphConstruction.js +3 -3
- package/dist/{application → artifacts/inventory}/ArtifactInventory.js +4 -4
- package/dist/{application/ArtifactInventory → artifacts/inventory}/classify.js +2 -2
- package/dist/{application/ArtifactInventory → artifacts/inventory}/reader.js +5 -5
- package/dist/{application/ArtifactInventory → artifacts/inventory}/scanCanonical.js +3 -3
- package/dist/{application/ArtifactInventory → artifacts/inventory}/scanReader.js +5 -5
- package/dist/artifacts/readStableArtifact.js +20 -4
- package/dist/browser/CdpCaptureEventHandlers.js +90 -2
- package/dist/browser/CdpCaptureEvents.js +9 -0
- package/dist/browser/PlaywrightElectronActiveProvider.js +12 -3
- package/dist/browser/history/CaptureFailures.js +101 -0
- package/dist/browser/history/CaptureFormatError.js +10 -0
- package/dist/browser/history/CaptureRedaction.js +65 -0
- package/dist/browser/history/CaptureRelease.js +12 -0
- package/dist/browser/history/HarCapture.js +165 -0
- package/dist/browser/history/HarCaptureAdapter.js +20 -0
- package/dist/browser/history/HarCaptureProcess.js +57 -0
- package/dist/browser/history/HarJson.js +91 -0
- package/dist/browser/history/HarSchema.js +72 -0
- package/dist/browser/history/HistoricalCaptureDecoder.js +186 -0
- package/dist/browser/history/HistoricalCaptureFormatAdapter.js +1 -0
- package/dist/browser/history/MitmproxyCaptureAdapter.js +42 -0
- package/dist/cli/artifactCommands.js +26 -1
- package/dist/cli/binaryDiagnosticsCommands.js +39 -0
- package/dist/cli/coreAnalysisCommands.js +2 -2
- package/dist/cli/coreBinaryCommands.js +8 -8
- package/dist/cli/electronCommands.js +16 -9
- package/dist/cli/evmCommands.js +24 -0
- package/dist/cli/firmwareCommands.js +6 -3
- package/dist/cli/javascriptApplicationAnalysis.js +16 -3
- package/dist/cli/jsonOutput.js +133 -0
- package/dist/cli/nativeCallCommands.js +35 -0
- package/dist/cli/processCommands.js +1 -1
- package/dist/cli/streamedJsonOutput.js +56 -0
- package/dist/cli/utilityCommands.js +2 -0
- package/dist/cli/webNetworkCaptureCommands.js +40 -0
- package/dist/cli.js +9 -3
- package/dist/cliBrowserScenarioCommands.js +2 -2
- package/dist/cliCommandNames.js +6 -0
- package/dist/cliJsonInput.js +30 -1
- package/dist/cliOutput.js +21 -6
- package/dist/composition/binaryDiagnostics.js +8 -0
- package/dist/composition/evm.js +4 -0
- package/dist/composition/webNetworkCaptures.js +6 -0
- package/dist/contracts/artifactToolContracts.js +6 -0
- package/dist/contracts/browserCaptureToolInputSchema.js +43 -0
- package/dist/contracts/browserToolContracts.js +4 -3
- package/dist/contracts/errorSchemas.js +8 -1
- package/dist/contracts/evm/evmToolContracts.js +20 -0
- package/dist/contracts/investigationExamples.js +2 -2
- package/dist/contracts/managed/managedToolContracts.js +7 -3
- package/dist/contracts/native/binaryDiagnosticsToolContracts.js +42 -0
- package/dist/contracts/native/nativeToolContracts.js +17 -1
- package/dist/contracts/officialToolContracts.js +12 -9
- package/dist/contracts/process/processCaptureExample.js +2 -0
- package/dist/contracts/sessionLifecycleInputs.js +22 -5
- package/dist/contracts/sessionStatusContract.js +10 -2
- package/dist/contracts/sessionToolContracts.js +8 -1
- package/dist/contracts/sessionToolSchemas.js +10 -3
- package/dist/contracts/toolContractExamples.js +1 -1
- package/dist/contracts/toolContracts.js +6 -0
- package/dist/contracts/toolEffects.js +30 -0
- package/dist/contracts/toolOutputSchemaGroups.js +5 -1
- package/dist/contracts/toolOutputSchemaPrimitives.js +1 -1
- package/dist/contracts/webNetworkCaptureToolContracts.js +24 -0
- package/dist/domain/analysisErrorBase.js +15 -0
- package/dist/domain/analysisErrorCore.js +59 -5
- package/dist/domain/analysisErrorPresentation.js +29 -1
- package/dist/domain/analysisErrorProjection.js +52 -2
- package/dist/domain/analysisSnapshot.js +166 -2
- package/dist/domain/apple/dyldPaths.js +81 -0
- package/dist/domain/apple/dylibResolution.js +367 -0
- package/dist/domain/apple/dylibResolutionFindings.js +75 -0
- package/dist/domain/artifactPathSyntax.js +1 -1
- package/dist/domain/browserObservation.js +1 -1
- package/dist/domain/browserObservationSchemas.js +13 -0
- package/dist/domain/browserScenarioValues.js +9 -2
- package/dist/domain/callPathSchemas.js +1 -1
- package/dist/domain/canonicalDigest.js +13 -1
- package/dist/domain/changedBehavior.js +3 -3
- package/dist/domain/comparisonStatus.js +2 -30
- package/dist/domain/conformanceTrustGate.js +0 -16
- package/dist/domain/evidence.js +17 -5
- package/dist/domain/evm/evmInterface.js +63 -0
- package/dist/domain/explicitSensitiveFailure.js +92 -0
- package/dist/domain/explicitSensitiveValues.js +10 -0
- package/dist/domain/firmware/firmwareAnalysis.js +8 -1
- package/dist/domain/javascript/electronActiveObservation.js +9 -4
- package/dist/domain/javascript/htmlArtifactReferences.js +40 -0
- package/dist/domain/javascript/javascriptExportShapeComparison.js +21 -5
- package/dist/domain/javascript/javascriptExportShapeVariants.js +0 -10
- package/dist/domain/javascript/javascriptSemanticGraph.js +21 -5
- package/dist/domain/jsonValue.js +4 -0
- package/dist/domain/localPath.js +2 -0
- package/dist/domain/native/binaryLayout.js +357 -0
- package/dist/domain/native/nativeCallObservation.js +164 -0
- package/dist/domain/native/objcSwiftMetadata.js +0 -39
- package/dist/domain/native/recordedCrash.js +266 -0
- package/dist/domain/native/recordedCrashNoteCoverage.js +73 -0
- package/dist/domain/{processCapture.js → process/processCapture.js} +139 -2
- package/dist/domain/{processCaptureExample.js → process/processCaptureExample.js} +5 -0
- package/dist/domain/process/processCaptureParsing.js +54 -0
- package/dist/domain/{processCaptureValidation.js → process/processCaptureValidation.js} +18 -2
- package/dist/domain/{processComparison.js → process/processComparison.js} +1 -1
- package/dist/domain/{processObservation.js → process/processObservation.js} +1 -1
- package/dist/domain/{processTraceSpecification.js → process/processTraceSpecification.js} +1 -1
- package/dist/domain/providerSelectionError.js +4 -2
- package/dist/domain/reconstructionVerification.js +3 -3
- package/dist/domain/reconstructionVerificationSchemas.js +1 -1
- package/dist/domain/residualUnknown.js +3 -2
- package/dist/domain/staticRuntimeCorrelation.js +3 -3
- package/dist/domain/webCaptureDiff.js +1 -0
- package/dist/domain/webNetworkCapture.js +118 -0
- package/dist/domain/webNetworkCaptureCoordinates.js +58 -0
- package/dist/dotnet/ManagedExceptionRegionValidation.js +41 -0
- package/dist/dotnet/ManagedMemberInspector.js +4 -2
- package/dist/dotnet/ManagedMemberInspectorCore.js +38 -32
- package/dist/dotnet/ManagedMemberInstructionDecoder.js +23 -7
- package/dist/dotnet/ManagedMemberRows.js +14 -3
- package/dist/dotnet/ManagedMetadataHeaps.js +20 -2
- package/dist/dotnet/ManagedMetadataInventory.js +38 -28
- package/dist/dotnet/ManagedMetadataInventoryRows.js +24 -10
- package/dist/dotnet/ManagedMethodBodyReader.js +7 -1
- package/dist/dotnet/ManagedNativeBoundaryHelpers.js +15 -3
- package/dist/dotnet/ManagedNativeBoundaryInspector.js +3 -1
- package/dist/dotnet/ManagedPeReader.js +11 -0
- package/dist/evm/EvmBytecodeCarrier.js +21 -0
- package/dist/evm/EvmWorkerLimits.js +40 -0
- package/dist/evm/EvmoleFailures.js +126 -0
- package/dist/evm/EvmoleInterfaceAdapter.js +53 -0
- package/dist/evm/EvmoleInterfaceProvider.js +273 -0
- package/dist/evm/EvmoleInterfaceWorker.js +87 -0
- package/dist/evm/EvmoleRelease.js +16 -0
- package/dist/firmware/FirmwareCommand.js +1 -0
- package/dist/generatedPackageMetadata.js +2 -2
- package/dist/ghidra/GhidraLauncher.js +1 -0
- package/dist/ghidra/GhidraProvider.js +2 -2
- package/dist/ghidra/GhidraProviderCapabilities.js +1 -1
- package/dist/hopper/BridgeLauncher.js +2 -0
- package/dist/hopper/LinuxPrivateDisplayProbe.js +4 -2
- package/dist/javascript/recovery/WakaruCommand.js +3 -0
- package/dist/javascript/sourceMaps/SourceMapCommand.js +1 -0
- package/dist/native/CommandRunner.js +2 -1
- package/dist/native/LldbCallTracer.js +278 -0
- package/dist/native/NativeCallObservation.js +135 -0
- package/dist/native/NativeMacOSProvider.js +19 -3
- package/dist/native/NativeMacOSProviderMetadata.js +8 -3
- package/dist/native/pwndbg/PwndbgCoreContext.js +160 -0
- package/dist/native/pwntools/PwntoolsDecoder.js +203 -0
- package/dist/native/pwntools/PwntoolsFailures.js +180 -0
- package/dist/native/pwntools/PwntoolsLayoutProvider.js +36 -0
- package/dist/native/pwntools/PwntoolsRecordedCrashProvider.js +75 -0
- package/dist/native/pwntools/PwntoolsRelease.js +19 -0
- package/dist/native/pwntools/PwntoolsResourceLimits.js +76 -0
- package/dist/native/pwntools/RecordedCrashSourceBindings.js +46 -0
- package/dist/native/pwntools/RecordedCrashStructureBindings.js +101 -0
- package/dist/process/DarwinProcessRunTokenReader.js +294 -0
- package/dist/process/OwnedCommand.js +113 -0
- package/dist/process/ProcessOwnership.js +263 -33
- package/dist/process/ProcessOwnershipObservation.js +161 -24
- package/dist/process/ProviderProcess.js +30 -3
- package/dist/{application → process/capture}/ProcessCaptureCapability.js +15 -0
- package/dist/{application → process/capture}/ProcessCaptureError.js +17 -1
- package/dist/process/capture/ProcessCaptureLifecycle.js +615 -0
- package/dist/{application → process/capture}/ProcessHarness.js +195 -29
- package/dist/{application → process/capture}/ProcessSampling.js +2 -2
- package/dist/server/createServer.js +22 -0
- package/dist/server/promptCompletion.js +10 -3
- package/dist/server/registerBinaryDiagnosticsTools.js +17 -0
- package/dist/server/registerEvidenceTools.js +1 -1
- package/dist/server/registerEvmTools.js +17 -0
- package/dist/server/registerProcessComparisonTool.js +1 -1
- package/dist/server/registerRecordedCrashTools.js +17 -0
- package/dist/server/registerSessionTools.js +2 -2
- package/dist/server/registerWebNetworkCaptureTool.js +28 -0
- package/dist/server/sessionAvailabilityPolicy.js +3 -0
- package/dist/server/sessionToolPolicies.js +1 -1
- package/dist/server/toolResult.js +1 -1
- package/native/windows/build/manifest.json +2 -2
- package/native/windows/build/rea-windows-x64.node +0 -0
- package/package.json +37 -9
- package/scripts/rea.mjs +11 -3
- package/skills/reverse-engineer-anything/SKILL.md +23 -3
- package/skills/reverse-engineer-anything/references/native-and-artifacts.md +29 -0
- package/third_party/README.md +60 -0
- package/third_party/evmole/LICENSE +21 -0
- package/third_party/evmole/README.md +22 -0
- package/third_party/pwndbg/LICENSE.md +21 -0
- package/third_party/pwndbg/README.md +23 -0
- package/third_party/pwntools/README.md +40 -0
- package/dist/application/AuthorizedArtifactInventory.js +0 -7
- package/dist/application/ProcessCaptureLifecycle.js +0 -299
- package/dist/application/StoreFileLock.js +0 -124
- package/dist/contracts/processCaptureExample.js +0 -2
- package/dist/domain/bytecodeProvider.js +0 -178
- package/dist/domain/customProtocolCapture.js +0 -137
- package/dist/domain/eventProcessTree.js +0 -225
- package/dist/domain/mobileApplicationInvestigation.js +0 -159
- package/dist/domain/packageAnalysis.js +0 -123
- package/dist/domain/peInspection.js +0 -197
- package/dist/domain/processCaptureParsing.js +0 -21
- package/dist/domain/protocolCapture.js +0 -217
- package/dist/domain/runtimeIdentification.js +0 -212
- package/dist/generatedMcpToolCatalog.js +0 -37
- /package/dist/application/{ArtifactExtractionDestination.js → artifacts/ArtifactExtractionDestination.js} +0 -0
- /package/dist/{application/ArtifactInventory → artifacts/inventory}/policy.js +0 -0
- /package/dist/{application/ArtifactInventory → artifacts/inventory}/types.js +0 -0
- /package/dist/domain/{processEvidenceProvider.js → process/processEvidenceProvider.js} +0 -0
- /package/dist/domain/{processScenario.js → process/processScenario.js} +0 -0
- /package/dist/domain/{processTraceComparison.js → process/processTraceComparison.js} +0 -0
- /package/dist/domain/{processTraceDimensionProjection.js → process/processTraceDimensionProjection.js} +0 -0
- /package/dist/domain/{processTraceEvaluation.js → process/processTraceEvaluation.js} +0 -0
- /package/dist/{application → process/capture}/FilesystemSnapshot.js +0 -0
- /package/dist/{application → process/capture}/ProcessCaptureEnvironment.js +0 -0
- /package/dist/{application → process/capture}/ProcessCaptureJournal.js +0 -0
- /package/dist/{application → process/capture}/ProcessFilesystemEffects.js +0 -0
- /package/dist/{application → process/capture}/ProcessNormalization.js +0 -0
- /package/dist/{application → process/capture}/ProcessScenarioRuntimeValidation.js +0 -0
- /package/dist/{application → process/capture}/ProcessTimer.js +0 -0
- /package/dist/{application → process/capture}/TerminalRenderer.js +0 -0
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
/** Load commands larger than this are refused instead of buffered. */
|
|
2
|
+
export const MAX_LOAD_COMMAND_BYTES = 16 * 1024 * 1024;
|
|
3
|
+
/** Target opening accepts up to 128 FAT records; Java class files share the FAT magic. */
|
|
4
|
+
const MAX_FAT_ARCHITECTURES = 128;
|
|
5
|
+
/** CPU types of real Mach-O slices (x86, x86_64, arm, arm64, arm64_32, ppc, ppc64). */
|
|
6
|
+
const KNOWN_CPU_TYPES = [
|
|
7
|
+
7, 0x01000007, 12, 0x0100000c, 0x0200000c, 18, 0x01000012,
|
|
8
|
+
];
|
|
9
|
+
const MH_MAGIC = 0xfeedface;
|
|
10
|
+
const MH_MAGIC_64 = 0xfeedfacf;
|
|
11
|
+
const MH_CIGAM = 0xcefaedfe;
|
|
12
|
+
const MH_CIGAM_64 = 0xcffaedfe;
|
|
13
|
+
const FAT_MAGIC = 0xcafebabe;
|
|
14
|
+
const FAT_MAGIC_64 = 0xcafebabf;
|
|
15
|
+
const FAT_CIGAM = 0xbebafeca;
|
|
16
|
+
const FAT_CIGAM_64 = 0xbfbafeca;
|
|
17
|
+
const LC_ID_DYLIB = 0xd;
|
|
18
|
+
const LC_LOAD_DYLIB = 0xc;
|
|
19
|
+
const LC_LOAD_WEAK_DYLIB = 0x80000018;
|
|
20
|
+
const LC_REEXPORT_DYLIB = 0x8000001f;
|
|
21
|
+
const LC_LAZY_LOAD_DYLIB = 0x20;
|
|
22
|
+
const LC_LOAD_UPWARD_DYLIB = 0x80000023;
|
|
23
|
+
const LC_RPATH = 0x8000001c;
|
|
24
|
+
const LC_DYLD_ENVIRONMENT = 0x27;
|
|
25
|
+
const LC_CODE_SIGNATURE = 0x1d;
|
|
26
|
+
/** `mach-o/loader.h` `dylib_use_command`: `nameoff == 28` and this marker. */
|
|
27
|
+
const DYLIB_USE_MARKER = 0x1a741800;
|
|
28
|
+
const DYLIB_USE_WEAK_LINK = 0x1;
|
|
29
|
+
const DYLIB_USE_REEXPORT = 0x2;
|
|
30
|
+
const DYLIB_USE_UPWARD = 0x4;
|
|
31
|
+
const DYLIB_USE_DELAYED_INIT = 0x8;
|
|
32
|
+
const DEPENDENCY_COMMANDS = new Map([
|
|
33
|
+
[LC_LOAD_DYLIB, "LC_LOAD_DYLIB"],
|
|
34
|
+
[LC_LOAD_WEAK_DYLIB, "LC_LOAD_WEAK_DYLIB"],
|
|
35
|
+
[LC_REEXPORT_DYLIB, "LC_REEXPORT_DYLIB"],
|
|
36
|
+
[LC_LAZY_LOAD_DYLIB, "LC_LAZY_LOAD_DYLIB"],
|
|
37
|
+
[LC_LOAD_UPWARD_DYLIB, "LC_LOAD_UPWARD_DYLIB"],
|
|
38
|
+
]);
|
|
39
|
+
/** Malformed or unsupported structure found while decoding one image. */
|
|
40
|
+
class MachoFormatIssue extends Error {
|
|
41
|
+
status;
|
|
42
|
+
constructor(status, message) {
|
|
43
|
+
super(message);
|
|
44
|
+
this.status = status;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/** Report whether the first bytes carry a Mach-O or FAT magic number. */
|
|
48
|
+
export const hasMachoMagic = (head) => {
|
|
49
|
+
if (head.byteLength < 4)
|
|
50
|
+
return false;
|
|
51
|
+
const magic = new DataView(head.buffer, head.byteOffset, head.byteLength).getUint32(0, false);
|
|
52
|
+
return [
|
|
53
|
+
MH_MAGIC,
|
|
54
|
+
MH_MAGIC_64,
|
|
55
|
+
MH_CIGAM,
|
|
56
|
+
MH_CIGAM_64,
|
|
57
|
+
FAT_MAGIC,
|
|
58
|
+
FAT_MAGIC_64,
|
|
59
|
+
FAT_CIGAM,
|
|
60
|
+
FAT_CIGAM_64,
|
|
61
|
+
].includes(magic);
|
|
62
|
+
};
|
|
63
|
+
/** Decode every slice's dylib-loading load commands without reading code. */
|
|
64
|
+
export const readMachoImage = async (readAt, size) => {
|
|
65
|
+
try {
|
|
66
|
+
const head = await readAt(0, Math.min(size, 8));
|
|
67
|
+
if (head.byteLength < 8)
|
|
68
|
+
// A recognized magic with no room for a header is a truncated image.
|
|
69
|
+
return hasMachoMagic(head)
|
|
70
|
+
? { status: "malformed", reason: "Mach-O header is truncated" }
|
|
71
|
+
: { status: "not-mach-o" };
|
|
72
|
+
const view = viewOf(head);
|
|
73
|
+
const magic = view.getUint32(0, false);
|
|
74
|
+
if ([FAT_MAGIC, FAT_MAGIC_64, FAT_CIGAM, FAT_CIGAM_64].includes(magic)) {
|
|
75
|
+
// FAT headers are big-endian; the swapped magics store them little-endian.
|
|
76
|
+
const littleEndian = magic === FAT_CIGAM || magic === FAT_CIGAM_64;
|
|
77
|
+
const count = view.getUint32(4, littleEndian);
|
|
78
|
+
const wide = magic === FAT_MAGIC_64 || magic === FAT_CIGAM_64;
|
|
79
|
+
// A Java class file's nonzero major version reads as the count, so an
|
|
80
|
+
// empty architecture table can only be a malformed universal header.
|
|
81
|
+
if (count === 0)
|
|
82
|
+
return {
|
|
83
|
+
status: "malformed",
|
|
84
|
+
reason: "FAT header declares no architectures",
|
|
85
|
+
};
|
|
86
|
+
if (count > MAX_FAT_ARCHITECTURES || 8 + count * (wide ? 32 : 20) > size)
|
|
87
|
+
return { status: "not-mach-o" };
|
|
88
|
+
const table = viewOf(await readExact(readAt, 8, count * (wide ? 32 : 20), "FAT table"));
|
|
89
|
+
// A Java class file's version fields read as a FAT count; its "table" names no CPU.
|
|
90
|
+
const cpuTypes = Array.from({ length: count }, (_, index) => table.getUint32(index * (wide ? 32 : 20), littleEndian));
|
|
91
|
+
if (!cpuTypes.some((type) => KNOWN_CPU_TYPES.includes(type)))
|
|
92
|
+
return { status: "not-mach-o" };
|
|
93
|
+
return {
|
|
94
|
+
status: "parsed",
|
|
95
|
+
slices: await readFatSlices(readAt, size, {
|
|
96
|
+
wide,
|
|
97
|
+
littleEndian,
|
|
98
|
+
count,
|
|
99
|
+
}),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
if (magic === MH_CIGAM || magic === MH_CIGAM_64)
|
|
103
|
+
return { status: "parsed", slices: [await readSlice(readAt, 0, size)] };
|
|
104
|
+
if (magic === MH_MAGIC || magic === MH_MAGIC_64)
|
|
105
|
+
throw new MachoFormatIssue("unsupported", "big-endian Mach-O images are not supported");
|
|
106
|
+
return { status: "not-mach-o" };
|
|
107
|
+
}
|
|
108
|
+
catch (cause) {
|
|
109
|
+
if (cause instanceof MachoFormatIssue)
|
|
110
|
+
return { status: cause.status, reason: cause.message };
|
|
111
|
+
throw cause;
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
const readFatSlices = async (readAt, size, table) => {
|
|
115
|
+
const { wide, littleEndian, count } = table;
|
|
116
|
+
const entrySize = wide ? 32 : 20;
|
|
117
|
+
const view = viewOf(await readExact(readAt, 8, count * entrySize, "FAT table"));
|
|
118
|
+
const slices = [];
|
|
119
|
+
for (let index = 0; index < count; index++) {
|
|
120
|
+
const base = index * entrySize;
|
|
121
|
+
const offset = wide
|
|
122
|
+
? safeNumber(view.getBigUint64(base + 8, littleEndian))
|
|
123
|
+
: view.getUint32(base + 8, littleEndian);
|
|
124
|
+
const length = wide
|
|
125
|
+
? safeNumber(view.getBigUint64(base + 16, littleEndian))
|
|
126
|
+
: view.getUint32(base + 12, littleEndian);
|
|
127
|
+
if (offset + length > size)
|
|
128
|
+
throw new MachoFormatIssue("malformed", `FAT architecture ${index} extends beyond the file`);
|
|
129
|
+
slices.push(await readSlice(readAt, offset, length));
|
|
130
|
+
}
|
|
131
|
+
return slices;
|
|
132
|
+
};
|
|
133
|
+
const readSlice = async (readAt, base, length) => {
|
|
134
|
+
const header = viewOf(await readExact(readAt, base, 28, "Mach-O header"));
|
|
135
|
+
const magic = header.getUint32(0, true);
|
|
136
|
+
if (magic === MH_MAGIC_64 || magic === MH_MAGIC) {
|
|
137
|
+
const wide = magic === MH_MAGIC_64;
|
|
138
|
+
const headerSize = wide ? 32 : 28;
|
|
139
|
+
const commandCount = header.getUint32(16, true);
|
|
140
|
+
const commandBytes = header.getUint32(20, true);
|
|
141
|
+
if (commandBytes > MAX_LOAD_COMMAND_BYTES)
|
|
142
|
+
throw new MachoFormatIssue("unsupported", `load commands occupy ${commandBytes} bytes, above the ${MAX_LOAD_COMMAND_BYTES}-byte limit`);
|
|
143
|
+
if (headerSize + commandBytes > length)
|
|
144
|
+
throw new MachoFormatIssue("malformed", "load commands extend beyond the Mach-O slice");
|
|
145
|
+
const commands = await readExact(readAt, base + headerSize, commandBytes, "load commands");
|
|
146
|
+
return decodeCommands(commands, {
|
|
147
|
+
wide,
|
|
148
|
+
cpuType: header.getUint32(4, true),
|
|
149
|
+
cpuSubtype: header.getUint32(8, true),
|
|
150
|
+
fileType: header.getUint32(12, true),
|
|
151
|
+
commandCount,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
throw new MachoFormatIssue(magic === swap(MH_MAGIC) || magic === swap(MH_MAGIC_64)
|
|
155
|
+
? "unsupported"
|
|
156
|
+
: "malformed", magic === swap(MH_MAGIC) || magic === swap(MH_MAGIC_64)
|
|
157
|
+
? "big-endian Mach-O slices are not supported"
|
|
158
|
+
: `slice at offset ${base} has no Mach-O header`);
|
|
159
|
+
};
|
|
160
|
+
const decodeCommands = (bytes, header) => {
|
|
161
|
+
// loader.h: load commands are 8-byte aligned in 64-bit images, 4 in 32-bit.
|
|
162
|
+
const alignment = header.wide ? 8 : 4;
|
|
163
|
+
const view = viewOf(bytes);
|
|
164
|
+
const slice = {
|
|
165
|
+
architecture: architectureName(header.cpuType, header.cpuSubtype),
|
|
166
|
+
file_type: fileTypeName(header.fileType),
|
|
167
|
+
install_name: null,
|
|
168
|
+
dependencies: [],
|
|
169
|
+
rpaths: [],
|
|
170
|
+
dyld_environment: [],
|
|
171
|
+
code_signature_present: false,
|
|
172
|
+
};
|
|
173
|
+
let offset = 0;
|
|
174
|
+
for (let index = 0; index < header.commandCount; index++) {
|
|
175
|
+
if (offset + 8 > bytes.byteLength)
|
|
176
|
+
throw new MachoFormatIssue("malformed", `load command ${index} starts beyond sizeofcmds`);
|
|
177
|
+
const command = view.getUint32(offset, true);
|
|
178
|
+
const size = view.getUint32(offset + 4, true);
|
|
179
|
+
if (size < 8 || size % alignment !== 0 || offset + size > bytes.byteLength)
|
|
180
|
+
throw new MachoFormatIssue("malformed", `load command ${index} at offset ${offset} has invalid cmdsize ${size}`);
|
|
181
|
+
const body = bytes.subarray(offset, offset + size);
|
|
182
|
+
decodeCommand(command, body, index, slice);
|
|
183
|
+
offset += size;
|
|
184
|
+
}
|
|
185
|
+
return slice;
|
|
186
|
+
};
|
|
187
|
+
const decodeCommand = (command, body, index, slice) => {
|
|
188
|
+
if (command === LC_ID_DYLIB) {
|
|
189
|
+
slice.install_name = commandString(body, 24, index);
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
if (command === LC_RPATH) {
|
|
193
|
+
slice.rpaths.push(commandString(body, 12, index));
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
if (command === LC_DYLD_ENVIRONMENT) {
|
|
197
|
+
slice.dyld_environment.push(commandString(body, 12, index));
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
if (command === LC_CODE_SIGNATURE) {
|
|
201
|
+
// linkedit_data_command: cmd, cmdsize, dataoff, datasize.
|
|
202
|
+
if (body.byteLength < 16)
|
|
203
|
+
throw new MachoFormatIssue("malformed", `load command ${index} is too short for LC_CODE_SIGNATURE`);
|
|
204
|
+
slice.code_signature_present = true;
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
const name = DEPENDENCY_COMMANDS.get(command);
|
|
208
|
+
if (name !== undefined)
|
|
209
|
+
slice.dependencies.push(decodeDependency(name, body, index));
|
|
210
|
+
};
|
|
211
|
+
const decodeDependency = (command, body, index) => {
|
|
212
|
+
if (body.byteLength < 24)
|
|
213
|
+
throw new MachoFormatIssue("malformed", `load command ${index} is too short for a dylib command`);
|
|
214
|
+
const view = viewOf(body);
|
|
215
|
+
const nameOffset = view.getUint32(8, true);
|
|
216
|
+
const usesFlags = nameOffset === 28 &&
|
|
217
|
+
body.byteLength >= 28 &&
|
|
218
|
+
view.getUint32(12, true) === DYLIB_USE_MARKER;
|
|
219
|
+
const flags = usesFlags ? view.getUint32(24, true) : 0;
|
|
220
|
+
return {
|
|
221
|
+
command,
|
|
222
|
+
encoding: usesFlags ? "dylib_use_command" : "dylib_command",
|
|
223
|
+
install_name: commandString(body, usesFlags ? 28 : 24, index),
|
|
224
|
+
weak: command === "LC_LOAD_WEAK_DYLIB" || (flags & DYLIB_USE_WEAK_LINK) !== 0,
|
|
225
|
+
upward: command === "LC_LOAD_UPWARD_DYLIB" || (flags & DYLIB_USE_UPWARD) !== 0,
|
|
226
|
+
reexport: command === "LC_REEXPORT_DYLIB" || (flags & DYLIB_USE_REEXPORT) !== 0,
|
|
227
|
+
delayed_init: (flags & DYLIB_USE_DELAYED_INIT) !== 0,
|
|
228
|
+
current_version: version(view.getUint32(16, true)),
|
|
229
|
+
compatibility_version: version(view.getUint32(20, true)),
|
|
230
|
+
};
|
|
231
|
+
};
|
|
232
|
+
/** NUL-terminated UTF-8 string at a `lc_str` offset inside one command. */
|
|
233
|
+
const commandString = (body, minimumOffset, index) => {
|
|
234
|
+
if (body.byteLength < 12)
|
|
235
|
+
throw new MachoFormatIssue("malformed", `load command ${index} is too short to hold a string offset`);
|
|
236
|
+
const offset = viewOf(body).getUint32(8, true);
|
|
237
|
+
if (offset < minimumOffset || offset >= body.byteLength)
|
|
238
|
+
throw new MachoFormatIssue("malformed", `load command ${index} has string offset ${offset} outside the command`);
|
|
239
|
+
const end = body.indexOf(0, offset);
|
|
240
|
+
if (end < 0)
|
|
241
|
+
throw new MachoFormatIssue("malformed", `load command ${index} has an unterminated string`);
|
|
242
|
+
try {
|
|
243
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(body.subarray(offset, end));
|
|
244
|
+
}
|
|
245
|
+
catch {
|
|
246
|
+
throw new MachoFormatIssue("malformed", `load command ${index} has a string that is not UTF-8`);
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
const readExact = async (readAt, offset, length, label) => {
|
|
250
|
+
const bytes = await readAt(offset, length);
|
|
251
|
+
if (bytes.byteLength < length)
|
|
252
|
+
throw new MachoFormatIssue("malformed", `${label} is truncated`);
|
|
253
|
+
return bytes;
|
|
254
|
+
};
|
|
255
|
+
const CPU_ARCH_ABI64 = 0x01000000;
|
|
256
|
+
const CPU_ARCH_ABI64_32 = 0x02000000;
|
|
257
|
+
const CPU_TYPE_X86 = 7;
|
|
258
|
+
const CPU_TYPE_ARM = 12;
|
|
259
|
+
const CPU_SUBTYPE_MASK = 0xff000000;
|
|
260
|
+
const CPU_SUBTYPE_ARM64E = 2;
|
|
261
|
+
const CPU_SUBTYPE_X86_64_H = 8;
|
|
262
|
+
const architectureName = (cpuType, cpuSubtype) => {
|
|
263
|
+
const subtype = cpuSubtype & ~CPU_SUBTYPE_MASK;
|
|
264
|
+
if (cpuType === (CPU_TYPE_ARM | CPU_ARCH_ABI64))
|
|
265
|
+
return subtype === CPU_SUBTYPE_ARM64E ? "arm64e" : "arm64";
|
|
266
|
+
if (cpuType === (CPU_TYPE_ARM | CPU_ARCH_ABI64_32))
|
|
267
|
+
return "arm64_32";
|
|
268
|
+
if (cpuType === (CPU_TYPE_X86 | CPU_ARCH_ABI64))
|
|
269
|
+
return subtype === CPU_SUBTYPE_X86_64_H ? "x86_64h" : "x86_64";
|
|
270
|
+
if (cpuType === CPU_TYPE_X86)
|
|
271
|
+
return "i386";
|
|
272
|
+
if (cpuType === CPU_TYPE_ARM)
|
|
273
|
+
return "arm";
|
|
274
|
+
return `cpu-${cpuType.toString(16)}`;
|
|
275
|
+
};
|
|
276
|
+
const fileTypeName = (fileType) => fileType === 2
|
|
277
|
+
? "execute"
|
|
278
|
+
: fileType === 6
|
|
279
|
+
? "dylib"
|
|
280
|
+
: fileType === 8
|
|
281
|
+
? "bundle"
|
|
282
|
+
: "other";
|
|
283
|
+
const version = (value) => `${value >>> 16}.${(value >>> 8) & 0xff}.${value & 0xff}`;
|
|
284
|
+
const swap = (value) => (((value & 0xff) << 24) |
|
|
285
|
+
((value & 0xff00) << 8) |
|
|
286
|
+
((value >>> 8) & 0xff00) |
|
|
287
|
+
(value >>> 24)) >>>
|
|
288
|
+
0;
|
|
289
|
+
const safeNumber = (value) => {
|
|
290
|
+
if (value > BigInt(Number.MAX_SAFE_INTEGER))
|
|
291
|
+
throw new MachoFormatIssue("malformed", "FAT offset exceeds safe range");
|
|
292
|
+
return Number(value);
|
|
293
|
+
};
|
|
294
|
+
const viewOf = (bytes) => new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { lstat, realpath } from "node:fs/promises";
|
|
2
|
-
import { canonicalDigest } from "
|
|
3
|
-
import { AsarArtifactReader } from "../
|
|
4
|
-
import { ArtifactPathRegistry, normalizeArtifactPath, } from "../
|
|
5
|
-
import { ArtifactReaderFailure, } from "../
|
|
6
|
-
import { DirectoryArtifactReader } from "../
|
|
7
|
-
import { SafeOutputTree } from "../
|
|
8
|
-
import { ZipArtifactReader } from "../
|
|
9
|
-
import { MachOSliceArtifactReader } from "../
|
|
10
|
-
import { artifactExtractionResultSchema, } from "
|
|
11
|
-
import { scanArtifactInventory } from "
|
|
2
|
+
import { canonicalDigest } from "../../domain/comparisonSemantics.js";
|
|
3
|
+
import { AsarArtifactReader } from "../AsarArtifactReader.js";
|
|
4
|
+
import { ArtifactPathRegistry, normalizeArtifactPath, } from "../ArtifactPaths.js";
|
|
5
|
+
import { ArtifactReaderFailure, } from "../ArtifactReader.js";
|
|
6
|
+
import { DirectoryArtifactReader } from "../DirectoryArtifactReader.js";
|
|
7
|
+
import { SafeOutputTree } from "../SafeOutputTree.js";
|
|
8
|
+
import { ZipArtifactReader } from "../ZipArtifactReader.js";
|
|
9
|
+
import { MachOSliceArtifactReader } from "../MachOSliceArtifactReader.js";
|
|
10
|
+
import { artifactExtractionResultSchema, } from "../../domain/artifactGraph.js";
|
|
11
|
+
import { scanArtifactInventory } from "../inventory/ArtifactInventory.js";
|
|
12
12
|
/** Extract every regular inventory occurrence into an exclusively owned absent root. */
|
|
13
13
|
export const extractArtifact = async (input, signal) => {
|
|
14
14
|
const sourcePath = await realpath(input.inputPath);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { canonicalDigest } from "
|
|
2
|
-
import { zipPackageFormatForPath } from "
|
|
3
|
-
import { mzWindowsHeaderOffset, parseDosMzHeader } from "
|
|
1
|
+
import { canonicalDigest } from "../../domain/comparisonSemantics.js";
|
|
2
|
+
import { zipPackageFormatForPath } from "../../domain/zipPackageFormat.js";
|
|
3
|
+
import { mzWindowsHeaderOffset, parseDosMzHeader } from "../../domain/dosMz.js";
|
|
4
4
|
/** Index the first occurrence for each canonical logical path. */
|
|
5
5
|
export const indexOccurrencesByPath = (occurrences) => {
|
|
6
6
|
const byPath = new Map();
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { realpath } from "node:fs/promises";
|
|
2
|
-
import { artifactInventoryResultSchema, } from "
|
|
3
|
-
import { abortIfNeeded } from "../
|
|
4
|
-
import { scanCanonicalArtifactInventory } from "./
|
|
5
|
-
export { scanCanonicalArtifactInventory } from "./
|
|
2
|
+
import { artifactInventoryResultSchema, } from "../../domain/artifactGraph.js";
|
|
3
|
+
import { abortIfNeeded } from "../ArtifactHash.js";
|
|
4
|
+
import { scanCanonicalArtifactInventory } from "./scanCanonical.js";
|
|
5
|
+
export { scanCanonicalArtifactInventory } from "./scanCanonical.js";
|
|
6
6
|
/** Inventory one local artifact and return every graph collection inline. */
|
|
7
7
|
export const inventoryArtifact = async (inputPath, options = {}) => {
|
|
8
8
|
const snapshot = await scanArtifactInventory(inputPath, options);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { open } from "node:fs/promises";
|
|
2
|
-
import { classifyArtifactContent } from "
|
|
3
|
-
import { ARTIFACT_CLASSIFICATION_PREFIX_BYTES } from "
|
|
2
|
+
import { classifyArtifactContent } from "./ArtifactGraphConstruction.js";
|
|
3
|
+
import { ARTIFACT_CLASSIFICATION_PREFIX_BYTES } from "../ArtifactHash.js";
|
|
4
4
|
import { hasZipSignature, zipPackageFormatForPath, } from "../../domain/zipPackageFormat.js";
|
|
5
5
|
const classifyContainerExtension = (path) => {
|
|
6
6
|
const lower = path.toLowerCase();
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { AsarArtifactReader } from "
|
|
2
|
-
import { DirectoryArtifactReader } from "
|
|
3
|
-
import { MachOSliceArtifactReader } from "
|
|
4
|
-
import { NativeDmgArtifactReader } from "
|
|
5
|
-
import { ZipArtifactReader } from "
|
|
1
|
+
import { AsarArtifactReader } from "../AsarArtifactReader.js";
|
|
2
|
+
import { DirectoryArtifactReader } from "../DirectoryArtifactReader.js";
|
|
3
|
+
import { MachOSliceArtifactReader } from "../MachOSliceArtifactReader.js";
|
|
4
|
+
import { NativeDmgArtifactReader } from "../NativeDmgArtifactReader.js";
|
|
5
|
+
import { ZipArtifactReader } from "../ZipArtifactReader.js";
|
|
6
6
|
export const createReader = async (path, format, signal) => {
|
|
7
7
|
switch (format) {
|
|
8
8
|
case "directory":
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { createReadStream } from "node:fs";
|
|
2
2
|
import { lstat } from "node:fs/promises";
|
|
3
|
-
import { ArtifactReaderFailure, } from "
|
|
3
|
+
import { ArtifactReaderFailure, } from "../ArtifactReader.js";
|
|
4
4
|
import { artifactInventoryResultSchema, } from "../../domain/artifactGraph.js";
|
|
5
|
-
import { createArtifactEdges, createRootNode, indexOccurrencesByPath, materializeDirectoryNodes, rekeyOccurrences, rootOccurrenceFor, } from "
|
|
5
|
+
import { createArtifactEdges, createRootNode, indexOccurrencesByPath, materializeDirectoryNodes, rekeyOccurrences, rootOccurrenceFor, } from "./ArtifactGraphConstruction.js";
|
|
6
6
|
import { canonicalDigest } from "../../domain/comparisonSemantics.js";
|
|
7
7
|
import { classifyRoot } from "./classify.js";
|
|
8
|
-
import { hashReadable } from "
|
|
8
|
+
import { hashReadable } from "../ArtifactHash.js";
|
|
9
9
|
import { createReader, inventoryLimitations } from "./reader.js";
|
|
10
10
|
import { scanReader, } from "./scanReader.js";
|
|
11
11
|
import { STRICT_INTEGRITY_POLICY, } from "./types.js";
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { isAbsolute } from "node:path";
|
|
2
|
-
import { ArtifactPathRegistry, normalizeArtifactPath, } from "
|
|
3
|
-
import { ArtifactReaderFailure, } from "
|
|
4
|
-
import { AsarArtifactReader } from "
|
|
5
|
-
import { classifyArtifactContent, createArtifactNode, createOccurrence, nearestParent, } from "
|
|
2
|
+
import { ArtifactPathRegistry, normalizeArtifactPath, } from "../ArtifactPaths.js";
|
|
3
|
+
import { ArtifactReaderFailure, } from "../ArtifactReader.js";
|
|
4
|
+
import { AsarArtifactReader } from "../AsarArtifactReader.js";
|
|
5
|
+
import { classifyArtifactContent, createArtifactNode, createOccurrence, nearestParent, } from "./ArtifactGraphConstruction.js";
|
|
6
6
|
import { STRICT_INTEGRITY_POLICY, } from "./types.js";
|
|
7
|
-
import { hashReadable } from "
|
|
7
|
+
import { hashReadable } from "../ArtifactHash.js";
|
|
8
8
|
const UNAVAILABLE_UNPACKED_LIMITATION = "ASAR unpacked companion bytes were unavailable; no content hash or child artifact was produced.";
|
|
9
9
|
const emptyScan = () => ({ nodes: new Map(), occurrences: [], pendingContradictions: [] });
|
|
10
10
|
export const scanReader = async (reader, signal, integrity = STRICT_INTEGRITY_POLICY) => {
|
|
@@ -2,15 +2,31 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import { constants } from "node:fs";
|
|
3
3
|
import { lstat, open } from "node:fs/promises";
|
|
4
4
|
import { ArtifactReaderFailure } from "./ArtifactReader.js";
|
|
5
|
+
const FILE_SYSTEM = {
|
|
6
|
+
lstat: (path) => lstat(path, { bigint: true }),
|
|
7
|
+
open,
|
|
8
|
+
};
|
|
9
|
+
const afterValidation = async (path, stage, read) => {
|
|
10
|
+
try {
|
|
11
|
+
return await read();
|
|
12
|
+
}
|
|
13
|
+
catch (cause) {
|
|
14
|
+
if (cause instanceof Error &&
|
|
15
|
+
"code" in cause &&
|
|
16
|
+
["ENOENT", "ENOTDIR", "ELOOP"].includes(String(cause.code)))
|
|
17
|
+
throw new ArtifactReaderFailure("integrity", `Artifact changed ${stage}: ${path}`, { cause });
|
|
18
|
+
throw cause;
|
|
19
|
+
}
|
|
20
|
+
};
|
|
5
21
|
/** Read one bounded regular file and reject replacement or in-place changes. */
|
|
6
|
-
export const readStableArtifact = async (path, maximumBytes, signal) => {
|
|
22
|
+
export const readStableArtifact = async (path, maximumBytes, signal, fileSystem = FILE_SYSTEM) => {
|
|
7
23
|
signal?.throwIfAborted();
|
|
8
|
-
const before = await lstat(path
|
|
24
|
+
const before = await fileSystem.lstat(path);
|
|
9
25
|
if (!before.isFile() || before.isSymbolicLink())
|
|
10
26
|
throw new ArtifactReaderFailure("path", `Expected a regular file without a symlink: ${path}`);
|
|
11
27
|
if (before.size > BigInt(maximumBytes))
|
|
12
28
|
throw new ArtifactReaderFailure("limit", `Selected artifact exceeds ${String(maximumBytes)} bytes: ${path}`);
|
|
13
|
-
const file = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
|
|
29
|
+
const file = await afterValidation(path, "before open", () => fileSystem.open(path, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK));
|
|
14
30
|
try {
|
|
15
31
|
const initial = await file.stat({ bigint: true });
|
|
16
32
|
if (!initial.isFile() ||
|
|
@@ -34,7 +50,7 @@ export const readStableArtifact = async (path, maximumBytes, signal) => {
|
|
|
34
50
|
chunks.push(bytes);
|
|
35
51
|
}
|
|
36
52
|
const after = await file.stat({ bigint: true });
|
|
37
|
-
const current = await
|
|
53
|
+
const current = await afterValidation(path, "during read", () => fileSystem.lstat(path));
|
|
38
54
|
if ([after, current].some((stat) => !stat.isFile() ||
|
|
39
55
|
stat.dev !== initial.dev ||
|
|
40
56
|
stat.ino !== initial.ino ||
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { inferJsonShape } from "../domain/jsonShape.js";
|
|
2
2
|
import { safeResponseMetadata } from "./CdpSafeMetadata.js";
|
|
3
3
|
import { allowedSanitizedUrl, isHttpUrl, numberValue, recordValue, recordsValue, cdpStringValue, } from "./CdpCaptureValues.js";
|
|
4
|
-
import { requestBodyShape } from "./CdpCaptureEventBodyShapes.js";
|
|
5
|
-
import { consolePrimitive, decodeBase64, exclusionReasonForUrl, executionContextKey, firstCallFrame, initiatorLocation, integerOrNull, isMainFrameNavigation, } from "./CdpCaptureEventHelpers.js";
|
|
4
|
+
import { requestBodyShape, updateResponseBodyShape, } from "./CdpCaptureEventBodyShapes.js";
|
|
5
|
+
import { consolePrimitive, decodeBase64, exclusionReasonForUrl, executionContextKey, firstCallFrame, initiatorLocation, integerOrNull, isJsonMediaType, isMainFrameNavigation, } from "./CdpCaptureEventHelpers.js";
|
|
6
6
|
export const handleExecutionContextCreated = (state, params) => {
|
|
7
7
|
const context = recordValue(params.context);
|
|
8
8
|
const identifier = numberValue(context?.id);
|
|
@@ -74,16 +74,69 @@ export const handleRequestWillBeSent = (state, params) => {
|
|
|
74
74
|
state.completeness.exclude("network_requests", "invalid_protocol_value");
|
|
75
75
|
return;
|
|
76
76
|
}
|
|
77
|
+
if (state.malformedRedirectRequestIds.has(requestId))
|
|
78
|
+
return;
|
|
77
79
|
if (request === undefined) {
|
|
78
80
|
state.completeness.exclude("network_requests", "invalid_protocol_value");
|
|
79
81
|
state.network.delete(requestId);
|
|
82
|
+
state.networkRequestTimestamps.delete(requestId);
|
|
80
83
|
return;
|
|
81
84
|
}
|
|
82
85
|
const sanitized = allowedSanitizedUrl(request.url, state.allowedOrigins);
|
|
83
86
|
if (sanitized === undefined) {
|
|
84
87
|
state.completeness.exclude("network_requests", exclusionReasonForUrl(cdpStringValue(request.url)));
|
|
85
88
|
state.network.delete(requestId);
|
|
89
|
+
state.networkRequestTimestamps.delete(requestId);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const previous = state.network.get(requestId);
|
|
93
|
+
const redirectResponse = recordValue(params.redirectResponse);
|
|
94
|
+
if (Object.hasOwn(params, "redirectResponse") &&
|
|
95
|
+
redirectResponse === undefined &&
|
|
96
|
+
preserveMalformedRedirectEvidence(state, requestId, previous))
|
|
86
97
|
return;
|
|
98
|
+
const redirects = [...(previous?.redirects ?? [])];
|
|
99
|
+
if (redirectResponse !== undefined) {
|
|
100
|
+
const rawResponseUrl = cdpStringValue(redirectResponse.url);
|
|
101
|
+
const responseUrl = rawResponseUrl === undefined || rawResponseUrl === ""
|
|
102
|
+
? undefined
|
|
103
|
+
: allowedSanitizedUrl(rawResponseUrl, state.allowedOrigins);
|
|
104
|
+
const responseUrlReason = redirectResponseUrlReason(rawResponseUrl);
|
|
105
|
+
if (previous === undefined) {
|
|
106
|
+
if (responseUrl === undefined)
|
|
107
|
+
state.completeness.exclude("network_requests", responseUrlReason);
|
|
108
|
+
else
|
|
109
|
+
// CDP may deliver the continuation after its predecessor was excluded
|
|
110
|
+
// by origin policy or before capture began. Keep the final request but
|
|
111
|
+
// report missing prior coverage without calling it malformed protocol.
|
|
112
|
+
state.completeness.attachLimited("network_requests");
|
|
113
|
+
}
|
|
114
|
+
else if (responseUrl === undefined) {
|
|
115
|
+
// redirectResponse belongs to the same request ID and can contain an
|
|
116
|
+
// excluded origin. Drop the entire chain before retaining any details.
|
|
117
|
+
state.completeness.exclude("network_requests", responseUrlReason);
|
|
118
|
+
state.network.delete(requestId);
|
|
119
|
+
state.networkRequestTimestamps.delete(requestId);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
redirects.push({
|
|
124
|
+
url: previous.url,
|
|
125
|
+
response_url: responseUrl.url,
|
|
126
|
+
method: previous.method,
|
|
127
|
+
resource_type: previous.resource_type,
|
|
128
|
+
status: numberValue(redirectResponse.status) ?? null,
|
|
129
|
+
mime_type: cdpStringValue(redirectResponse.mimeType) ?? null,
|
|
130
|
+
encoded_data_length: (() => {
|
|
131
|
+
const length = numberValue(redirectResponse.encodedDataLength);
|
|
132
|
+
return length === undefined ? null : Math.max(0, length);
|
|
133
|
+
})(),
|
|
134
|
+
request_timestamp: state.networkRequestTimestamps.get(requestId) ?? null,
|
|
135
|
+
// CDP reports redirectResponse on the next requestWillBeSent event.
|
|
136
|
+
// This is the event boundary, not a separately observed response time.
|
|
137
|
+
redirect_event_timestamp: numberValue(params.timestamp) ?? null,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
87
140
|
}
|
|
88
141
|
const initiator = recordValue(params.initiator);
|
|
89
142
|
const initiatorFrame = initiatorLocation(initiator);
|
|
@@ -100,6 +153,7 @@ export const handleRequestWillBeSent = (state, params) => {
|
|
|
100
153
|
status: null,
|
|
101
154
|
mime_type: null,
|
|
102
155
|
encoded_data_length: null,
|
|
156
|
+
redirects,
|
|
103
157
|
initiator: {
|
|
104
158
|
type: cdpStringValue(initiator?.type) ?? "other",
|
|
105
159
|
url: initiatorUrl?.url ?? null,
|
|
@@ -108,6 +162,35 @@ export const handleRequestWillBeSent = (state, params) => {
|
|
|
108
162
|
},
|
|
109
163
|
body_shapes: requestBodyShape(state, request),
|
|
110
164
|
});
|
|
165
|
+
const timestamp = numberValue(params.timestamp);
|
|
166
|
+
if (timestamp === undefined)
|
|
167
|
+
state.networkRequestTimestamps.delete(requestId);
|
|
168
|
+
else
|
|
169
|
+
state.networkRequestTimestamps.set(requestId, timestamp);
|
|
170
|
+
};
|
|
171
|
+
const preserveMalformedRedirectEvidence = (state, requestId, previous) => {
|
|
172
|
+
state.completeness.exclude("network_requests", "invalid_protocol_value");
|
|
173
|
+
if (previous === undefined)
|
|
174
|
+
return false;
|
|
175
|
+
state.malformedRedirectRequestIds.add(requestId);
|
|
176
|
+
if (state.input.include_json_body_shapes &&
|
|
177
|
+
isJsonMediaType(previous.mime_type) &&
|
|
178
|
+
previous.body_shapes.response === null) {
|
|
179
|
+
updateResponseBodyShape(state, requestId, null);
|
|
180
|
+
state.completeness.exclude("json_body_shapes", "invalid_protocol_value");
|
|
181
|
+
}
|
|
182
|
+
return true;
|
|
183
|
+
};
|
|
184
|
+
const redirectResponseUrlReason = (value) => {
|
|
185
|
+
if (value === undefined || value === "")
|
|
186
|
+
return "invalid_protocol_value";
|
|
187
|
+
try {
|
|
188
|
+
new URL(value);
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
return "invalid_protocol_value";
|
|
192
|
+
}
|
|
193
|
+
return exclusionReasonForUrl(value);
|
|
111
194
|
};
|
|
112
195
|
export const handleResponseReceived = (state, params) => {
|
|
113
196
|
const requestId = cdpStringValue(params.requestId);
|
|
@@ -115,6 +198,8 @@ export const handleResponseReceived = (state, params) => {
|
|
|
115
198
|
state.completeness.exclude("network_requests", "invalid_protocol_value");
|
|
116
199
|
return;
|
|
117
200
|
}
|
|
201
|
+
if (state.malformedRedirectRequestIds.has(requestId))
|
|
202
|
+
return;
|
|
118
203
|
const current = state.network.get(requestId);
|
|
119
204
|
const response = recordValue(params.response);
|
|
120
205
|
if (current === undefined)
|
|
@@ -125,6 +210,7 @@ export const handleResponseReceived = (state, params) => {
|
|
|
125
210
|
? "invalid_protocol_value"
|
|
126
211
|
: exclusionReasonForUrl(cdpStringValue(response.url)));
|
|
127
212
|
state.network.delete(requestId);
|
|
213
|
+
state.networkRequestTimestamps.delete(requestId);
|
|
128
214
|
return;
|
|
129
215
|
}
|
|
130
216
|
state.network.set(requestId, {
|
|
@@ -140,6 +226,8 @@ export const handleLoadingFinished = (state, params) => {
|
|
|
140
226
|
const requestId = cdpStringValue(params.requestId);
|
|
141
227
|
if (requestId === undefined)
|
|
142
228
|
return;
|
|
229
|
+
if (state.malformedRedirectRequestIds.has(requestId))
|
|
230
|
+
return;
|
|
143
231
|
const current = state.network.get(requestId);
|
|
144
232
|
if (current === undefined)
|
|
145
233
|
return;
|
|
@@ -9,6 +9,8 @@ export class CdpCaptureEvents {
|
|
|
9
9
|
scripts = new Map();
|
|
10
10
|
executionContextFrames = new Map();
|
|
11
11
|
network = new Map();
|
|
12
|
+
networkRequestTimestamps = new Map();
|
|
13
|
+
malformedRedirectRequestIds = new Set();
|
|
12
14
|
allowedWebSockets = new Set();
|
|
13
15
|
console = [];
|
|
14
16
|
websockets = [];
|
|
@@ -39,6 +41,8 @@ export class CdpCaptureEvents {
|
|
|
39
41
|
this.scripts.clear();
|
|
40
42
|
this.executionContextFrames.clear();
|
|
41
43
|
this.network.clear();
|
|
44
|
+
this.networkRequestTimestamps.clear();
|
|
45
|
+
this.malformedRedirectRequestIds.clear();
|
|
42
46
|
this.allowedWebSockets.clear();
|
|
43
47
|
this.console.length = 0;
|
|
44
48
|
this.websockets.length = 0;
|
|
@@ -119,14 +123,19 @@ export class CdpCaptureEvents {
|
|
|
119
123
|
if (!this.input.include_json_body_shapes)
|
|
120
124
|
return [];
|
|
121
125
|
return [...this.network.values()]
|
|
126
|
+
.filter((request) => !this.malformedRedirectRequestIds.has(request.request_id))
|
|
122
127
|
.filter((request) => isJsonMediaType(request.mime_type))
|
|
123
128
|
.map((request) => request.request_id);
|
|
124
129
|
}
|
|
125
130
|
responseBodyUnavailable(requestId) {
|
|
131
|
+
if (this.malformedRedirectRequestIds.has(requestId))
|
|
132
|
+
return;
|
|
126
133
|
bodyShapes.updateResponseBodyShape(this, requestId, null);
|
|
127
134
|
this.completeness.unavailable("json_body_shapes");
|
|
128
135
|
}
|
|
129
136
|
ingestResponseBody(requestId, value) {
|
|
137
|
+
if (this.malformedRedirectRequestIds.has(requestId))
|
|
138
|
+
return;
|
|
130
139
|
bodyShapes.ingestResponseBodyShape(this, requestId, value);
|
|
131
140
|
}
|
|
132
141
|
}
|