rea-agents 4.0.0 → 4.1.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 +93 -30
- package/bridge/ghidra/ReaGhidraBridge.java +358 -15
- package/bridge/ghidra/ReaGhidraExtensions.java +113 -0
- package/bridge/ghidra/ReaGhidraPrepareCom.java +32 -0
- package/dist/android/AndroidTargetSnapshot.js +34 -0
- package/dist/android/JadxAnalysis.js +191 -0
- package/dist/android/JadxConfiguration.js +41 -0
- package/dist/android/JadxMcpTransport.js +145 -0
- package/dist/android/JadxProtocol.js +101 -0
- package/dist/android/JadxProvider.js +170 -0
- package/dist/android/JadxRelease.js +19 -0
- package/dist/android/JadxSession.js +97 -0
- package/dist/application/AnalysisContextQueries.js +2 -1
- package/dist/application/AnalysisProviderEvaluation.js +5 -3
- package/dist/application/AnalysisProviderRegistry.js +2 -1
- package/dist/application/AnalysisSnapshotCache.js +4 -1
- package/dist/application/AnalysisSnapshotFiles.js +1 -1
- package/dist/application/AndroidAnalysisPort.js +1 -0
- package/dist/application/AndroidAnalysisService.js +37 -0
- package/dist/application/ArtifactExtraction.js +7 -4
- package/dist/application/ArtifactGraphConstruction.js +10 -17
- package/dist/application/ArtifactInventory/scanCanonical.js +8 -7
- package/dist/application/BinarySession.js +16 -3
- package/dist/application/BinarySessionExecution.js +4 -1
- package/dist/application/BinarySessionOpen.js +3 -2
- package/dist/application/BinarySessionRecords.js +9 -3
- package/dist/application/BinaryTargetResolver.js +25 -6
- package/dist/application/BrowserObservationService.js +2 -1
- package/dist/application/BrowserScenarioCaptureService.js +2 -1
- package/dist/application/BrowserScenarioEvidence.js +2 -2
- package/dist/application/CallPathTracing.js +2 -1
- package/dist/application/CapabilityInventory.js +18 -0
- package/dist/application/ClientConfigPath.js +3 -1
- package/dist/application/ClientConfigurationDocument.js +11 -1
- package/dist/application/ClientRegistrationStatus.js +8 -1
- package/dist/application/CompositeProvider.js +1 -1
- package/dist/application/DirectAnalysis.js +23 -7
- package/dist/application/DirectAnalysisStatus.js +3 -3
- package/dist/application/Doctor.js +48 -8
- package/dist/application/DoctorDiagnostics.js +7 -5
- package/dist/application/DoctorScope.js +1 -1
- package/dist/application/ElectronActiveEvidence.js +2 -2
- package/dist/application/ElectronActiveObservationService.js +2 -1
- package/dist/application/ElectronBoundaryAnalysis.js +1 -1
- package/dist/application/ElectronObservationService.js +2 -1
- package/dist/application/EnhancedLiteralTracing.js +2 -1
- package/dist/application/EnhancedToolTypes.js +2 -1
- package/dist/application/EnhancedTools.js +3 -1
- package/dist/application/EvidenceBundleCommands.js +2 -1
- package/dist/application/EvidenceBundleFiles.js +1 -1
- package/dist/application/EvidenceInputResolver.js +30 -0
- package/dist/application/EvidenceLedger.js +2 -1
- package/dist/application/FirmwareAnalysisPort.js +1 -0
- package/dist/application/FirmwareAnalysisService.js +35 -0
- package/dist/application/FunctionAnalysisEvidence.js +32 -0
- package/dist/application/InventoryProjectionEvidence.js +2 -1
- package/dist/application/JavaScriptApplicationService.js +50 -3
- package/dist/application/JavaScriptApplicationWorkflowService.js +2 -1
- package/dist/application/JavaScriptArtifactAnalysis.js +4 -2
- package/dist/application/JavaScriptArtifactFiles.js +4 -2
- package/dist/application/JavaScriptArtifactPathResolution.js +6 -2
- package/dist/application/JavaScriptJsonModules.js +3 -2
- package/dist/application/JavaScriptModuleRelationships.js +4 -2
- package/dist/application/JavaScriptRuntimeObservationService.js +2 -1
- package/dist/application/JavaScriptRuntimeReconciliationService.js +2 -1
- package/dist/application/JavaScriptSemanticGraphProjection.js +1 -1
- package/dist/application/JavaScriptSemanticTraceService.js +2 -1
- package/dist/application/JsonFiles.js +1 -1
- package/dist/application/LazyAnalysisProvider.js +3 -1
- package/dist/application/LinuxHopper.js +12 -4
- package/dist/application/MacHopper.js +20 -7
- package/dist/application/ManagedApplicationGraphService.js +2 -1
- package/dist/application/ManagedMemberComparisonService.js +3 -1
- package/dist/application/ManagedNativeVerificationService.js +2 -1
- package/dist/application/ManagedReconstructionService.js +2 -1
- package/dist/application/NativeCallRoutes.js +1 -1
- package/dist/application/NativeDispatchMetadataInspection.js +1 -1
- package/dist/application/NativeUiActionTrace.js +2 -1
- package/dist/application/NativeValueTrace.js +1 -1
- package/dist/application/ProcessCaptureError.js +1 -1
- package/dist/application/ProcessCaptureJournal.js +3 -1
- package/dist/application/ProcessCaptureLifecycle.js +6 -3
- package/dist/application/ProcessCli.js +9 -5
- package/dist/application/ProcessEvidence.js +2 -6
- package/dist/application/ProcessHarness.js +3 -2
- package/dist/application/ProcessNormalization.js +12 -5
- package/dist/application/ProcessSampling.js +3 -1
- package/dist/application/ReconstructionCoverageService.js +2 -1
- package/dist/application/ReconstructionObligationLedgerService.js +2 -1
- package/dist/application/ReferenceSourceImportEntries.js +2 -0
- package/dist/application/ReferenceSourceImportPolicy.js +4 -2
- package/dist/application/ReferenceSourceVcsAdapter.js +35 -4
- package/dist/application/SessionProviderRouter.js +4 -1
- package/dist/application/Setup.js +1 -128
- package/dist/application/SetupClientConfiguration.js +17 -6
- package/dist/application/SetupHost.js +111 -0
- package/dist/application/SetupRegistrationEnvironment.js +1 -2
- package/dist/application/SetupSkill.js +9 -3
- package/dist/application/SetupTypes.js +17 -0
- package/dist/application/StoreFileLock.js +7 -2
- package/dist/application/SupportedClients.js +8 -0
- package/dist/application/Uninstall.js +9 -4
- package/dist/application/UpdateRuntime.js +5 -2
- package/dist/application/WindowsCapabilities.js +7 -7
- package/dist/application/runtime.js +3 -1
- package/dist/artifacts/ArtifactProvider.js +3 -1
- package/dist/artifacts/DirectoryArtifactReader.js +2 -0
- package/dist/artifacts/NativeDmgArtifactReader.js +10 -3
- package/dist/artifacts/NibArchive.js +4 -2
- package/dist/artifacts/SafeOutputTree.js +26 -10
- package/dist/artifacts/ZipArtifactReader.js +1 -0
- package/dist/browser/BrowserProcessStartup.js +3 -1
- package/dist/browser/CdpAuthorizedMainFrame.js +1 -1
- package/dist/browser/CdpBrowserProvider.js +4 -1
- package/dist/browser/CdpCaptureDocuments.js +3 -1
- package/dist/browser/CdpCaptureEventHandlers.js +6 -2
- package/dist/browser/CdpCaptureStorageFingerprints.js +1 -1
- package/dist/browser/CdpCaptureValues.js +6 -2
- package/dist/browser/CdpConnection.js +8 -6
- package/dist/browser/CdpElectronInspection.js +3 -3
- package/dist/browser/CdpElectronProvider.js +4 -1
- package/dist/browser/CdpElectronScripts.js +2 -2
- package/dist/browser/CdpEndpoint.js +22 -10
- package/dist/browser/CdpObservationSession.js +2 -1
- package/dist/browser/CdpOptionalCommand.js +1 -1
- package/dist/browser/CdpPageCapture.js +2 -1
- package/dist/browser/CdpSafeMetadata.js +53 -8
- package/dist/browser/CdpScreenshot.js +1 -1
- package/dist/browser/CdpTargetSession.js +8 -5
- package/dist/browser/CdpWebMcpDiscovery.js +1 -1
- package/dist/browser/ElectronFileScope.js +9 -3
- package/dist/browser/JavaScriptRuntimeScope.js +3 -1
- package/dist/browser/PlaywrightBrowserScenarioProvider.js +3 -1
- package/dist/browser/PlaywrightElectronActiveActions.js +1 -1
- package/dist/browser/PlaywrightElectronActiveProvider.js +6 -2
- package/dist/browser/PlaywrightExecutionBoundary.js +1 -1
- package/dist/browser/PlaywrightScenarioArtifacts.js +10 -5
- package/dist/browser/PlaywrightScenarioBrowser.js +3 -1
- package/dist/browser/PlaywrightScenarioEvents.js +6 -2
- package/dist/browser/PlaywrightScenarioSession.js +5 -2
- package/dist/browser/V8InspectorEndpoint.js +2 -1
- package/dist/browser/V8InspectorProvider.js +6 -1
- package/dist/browser/WebSourceMapFetcher.js +24 -11
- package/dist/cli/androidCommands.js +81 -0
- package/dist/{cliApplicationCommands.js → cli/applicationCommands.js} +18 -16
- package/dist/{cliBrowserAdvancedCommands.js → cli/browserAdvancedCommands.js} +13 -16
- package/dist/{cliBrowserCommands.js → cli/browserCommands.js} +9 -9
- package/dist/cli/commandCancellation.js +24 -0
- package/dist/cli/coreAnalysisCommands.js +110 -17
- package/dist/{cliElectronCommands.js → cli/electronCommands.js} +14 -13
- package/dist/cli/firmwareCommands.js +75 -0
- package/dist/{cliSetup.js → cli/interactiveSetup.js} +1 -1
- package/dist/cli/javascriptApplicationAnalysis.js +1 -1
- package/dist/cli/managedCommands.js +1 -1
- package/dist/cli/managedProjectGraphCommand.js +1 -1
- package/dist/cli/options.js +7 -1
- package/dist/{cliProcessCommands.js → cli/processCommands.js} +3 -3
- package/dist/cli/referenceSourceImportStatus.js +10 -0
- package/dist/cli/setupCommands.js +4 -2
- package/dist/cli/utilityCommands.js +3 -2
- package/dist/cli.js +10 -15
- package/dist/cliBrowserContext.js +1 -1
- package/dist/cliBrowserScenarioCommands.js +2 -1
- package/dist/cliCommandNames.js +11 -2
- package/dist/cliEvidenceCommands.js +2 -1
- package/dist/cliJavaScriptRuntimeCommands.js +2 -1
- package/dist/cliJsonInput.js +26 -11
- package/dist/cliLogging.js +2 -1
- package/dist/cliOutput.js +15 -16
- package/dist/config/environment.js +11 -1
- package/dist/config/parseConfig.js +7 -1
- package/dist/config/parsers.js +1 -1
- package/dist/contracts/androidToolContracts.js +77 -0
- package/dist/contracts/applicationToolContracts.js +32 -5
- package/dist/contracts/applicationWorkflowInputContracts.js +13 -13
- package/dist/contracts/artifactComparisonExample.js +7 -14
- package/dist/contracts/enhancedToolContracts.js +31 -0
- package/dist/contracts/errorSchemas.js +4 -2
- package/dist/contracts/evidenceInputContracts.js +13 -0
- package/dist/contracts/firmwareToolContracts.js +38 -0
- package/dist/contracts/officialToolContracts.js +66 -0
- package/dist/contracts/promptContracts.js +1 -1
- package/dist/contracts/sessionLifecycleInputs.js +4 -0
- package/dist/contracts/sessionToolContracts.js +43 -0
- package/dist/contracts/sessionToolSchemas.js +1 -1
- package/dist/contracts/toolContractExamples.js +5 -0
- package/dist/contracts/toolContractTypes.js +2 -0
- package/dist/contracts/toolContracts.js +7 -130
- package/dist/contracts/toolEffects.js +43 -0
- package/dist/contracts/toolOutputSchemaGroups.js +4 -0
- package/dist/contracts/toolOutputSchemaPrimitives.js +1 -0
- package/dist/doctorRuntime.js +5 -1
- package/dist/domain/analysisErrorBase.js +33 -0
- package/dist/domain/analysisErrorCore.js +60 -0
- package/dist/domain/analysisErrorPresentation.js +22 -2
- package/dist/domain/analysisErrorProjection.js +24 -3
- package/dist/domain/analysisSnapshot.js +1 -0
- package/dist/domain/androidAnalysis.js +141 -0
- package/dist/domain/appleAssetCatalog.js +1 -1
- package/dist/domain/artifactGraph.js +1 -0
- package/dist/domain/artifactInspection.js +19 -17
- package/dist/domain/artifactInventoryEvidence.js +20 -25
- package/dist/domain/artifactOperationError.js +16 -0
- package/dist/domain/browserObservation.js +12 -4
- package/dist/domain/browserObservationError.js +23 -0
- package/dist/domain/browserScenario.js +1 -1
- package/dist/domain/browserScenarioCapture.js +1 -1
- package/dist/domain/browserScenarioDiff.js +1 -1
- package/dist/domain/browserScenarioNormalization.js +6 -11
- package/dist/domain/browserScenarioValues.js +3 -1
- package/dist/domain/callPath.js +47 -7
- package/dist/domain/canonicalDigest.js +65 -0
- package/dist/domain/canonicalOrdering.js +11 -0
- package/dist/domain/changedBehavior.js +8 -7
- package/dist/domain/comparisonSemantics.js +2 -4
- package/dist/domain/configurationErrors.js +25 -0
- package/dist/domain/dosCom.js +8 -0
- package/dist/domain/electronActiveObservation.js +3 -1
- package/dist/domain/electronStaticAnalysisBrowser.js +13 -23
- package/dist/domain/electronStaticAnalysisNative.js +2 -1
- package/dist/domain/electronStaticAnalysisValues.js +23 -3
- package/dist/domain/evidence.js +3 -11
- package/dist/domain/evidenceErrors.js +30 -0
- package/dist/domain/firmwareAnalysis.js +121 -0
- package/dist/domain/functionComparison.js +4 -2
- package/dist/domain/functionComparisonDimensions.js +6 -4
- package/dist/domain/functionComparisonNormalization.js +8 -1
- package/dist/domain/functionComparisonResults.js +1 -4
- package/dist/domain/hopperErrors.js +78 -0
- package/dist/domain/hopperValues.js +2 -1
- package/dist/domain/interfaceBuilderGraph.js +3 -1
- package/dist/domain/javascriptApplicationChangeGraph.js +2 -2
- package/dist/domain/javascriptApplicationGraph.js +13 -22
- package/dist/domain/javascriptApplicationVersionComparison.js +3 -11
- package/dist/domain/javascriptApplicationVersionItems.js +3 -3
- package/dist/domain/javascriptApplicationVersionKeys.js +1 -1
- package/dist/domain/javascriptAstFingerprint.js +1 -1
- package/dist/domain/javascriptExportShapeComparison.js +1 -2
- package/dist/domain/javascriptExportShapeSelection.js +1 -1
- package/dist/domain/javascriptExportShapeVariants.js +7 -3
- package/dist/domain/javascriptFeatureSeed.js +29 -9
- package/dist/domain/javascriptFeatureTrace.js +9 -13
- package/dist/domain/javascriptFeatureTraversal.js +2 -1
- package/dist/domain/javascriptNativeHandoff.js +2 -1
- package/dist/domain/javascriptRuntimeLoadState.js +2 -2
- package/dist/domain/javascriptRuntimeReconciliationActive.js +3 -10
- package/dist/domain/javascriptRuntimeReconciliationGraph.js +2 -2
- package/dist/domain/javascriptRuntimeReconciliationMatching.js +4 -3
- package/dist/domain/javascriptRuntimeReconciliationParsing.js +8 -14
- package/dist/domain/javascriptRuntimeReconciliationResult.js +4 -4
- package/dist/domain/javascriptRuntimeReconciliationSchemas.js +5 -2
- package/dist/domain/javascriptRuntimeStaticCandidates.js +8 -4
- package/dist/domain/javascriptSemanticAnalysis.js +40 -3
- package/dist/domain/javascriptSemanticCallResolution.js +2 -1
- package/dist/domain/javascriptSemanticChildProcesses.js +2 -1
- package/dist/domain/javascriptSemanticDataEffects.js +2 -1
- package/dist/domain/javascriptSemanticFingerprints.js +1 -1
- package/dist/domain/javascriptSemanticGraph.js +23 -28
- package/dist/domain/javascriptSemanticPrimitives.js +9 -2
- package/dist/domain/javascriptSemanticProjection.js +2 -1
- package/dist/domain/javascriptSemanticPromises.js +2 -1
- package/dist/domain/javascriptSemanticProvenance.js +1 -1
- package/dist/domain/javascriptSemanticQuery.js +7 -8
- package/dist/domain/javascriptSemanticQueryAssessment.js +1 -2
- package/dist/domain/javascriptSemanticQueryIdentity.js +1 -2
- package/dist/domain/javascriptSemanticReturns.js +2 -1
- package/dist/domain/javascriptSemanticValues.js +50 -16
- package/dist/domain/javascriptSourceParser.js +4 -2
- package/dist/domain/javascriptStaticAnalysis.js +1 -1
- package/dist/domain/javascriptStaticAnalysisBundler.js +2 -1
- package/dist/domain/javascriptStaticAnalysisCalls.js +14 -6
- package/dist/domain/javascriptStaticAnalysisHelpers.js +31 -4
- package/dist/domain/jsonShape.js +6 -2
- package/dist/domain/managedMemberComparison.js +1 -0
- package/dist/domain/managedMemberComparisonItems.js +2 -0
- package/dist/domain/managedNativeVerificationMatch.js +5 -3
- package/dist/domain/nativeDataType.js +2 -0
- package/dist/domain/nativeFunctionAnnotations.js +43 -0
- package/dist/domain/nativeLoadImage.js +95 -0
- package/dist/domain/nativeMetadataRecovery.js +55 -0
- package/dist/domain/processCapture.js +0 -1
- package/dist/domain/processEvidenceProvider.js +11 -0
- package/dist/domain/processTraceComparison.js +1 -1
- package/dist/domain/providerAdapterError.js +17 -0
- package/dist/domain/providerCleanupError.js +1 -1
- package/dist/domain/providerSelectionError.js +44 -0
- package/dist/domain/reconstructionUnknowns.js +1 -1
- package/dist/domain/reconstructionVerification.js +22 -8
- package/dist/domain/referenceSourceClassification.js +5 -6
- package/dist/domain/referenceSourceGraph.js +9 -17
- package/dist/domain/referenceSourceImportParsing.js +38 -23
- package/dist/domain/runtimeIdentification.js +1 -1
- package/dist/domain/runtimeVersion.js +4 -7
- package/dist/domain/safeJson.js +13 -0
- package/dist/domain/sourceToBundleComparison.js +5 -7
- package/dist/domain/sourceToBundleSignals.js +6 -6
- package/dist/domain/staticRuntimeCorrelation.js +9 -7
- package/dist/domain/unknownRegistryError.js +10 -0
- package/dist/domain/webBundleAnalyzerAst.js +22 -5
- package/dist/domain/webBundleAnalyzerInspection.js +20 -14
- package/dist/dotnet/ManagedMemberRows.js +1 -1
- package/dist/dotnet/ManagedMetadataHeaps.js +3 -2
- package/dist/dotnet/ManagedMetadataInventoryRows.js +4 -2
- package/dist/dotnet/ManagedMethodBodyReader.js +8 -1
- package/dist/dotnet/ManagedStaticProvider.js +3 -1
- package/dist/firmware/FirmwareCommand.js +163 -0
- package/dist/firmware/FirmwareFiles.js +190 -0
- package/dist/firmware/FirmwareProvider.js +258 -0
- package/dist/firmware/FirmwarePublication.js +84 -0
- package/dist/firmware/FirmwareRelease.js +32 -0
- package/dist/firmware/FirmwareReports.js +223 -0
- package/dist/generatedMcpToolCatalog.js +1 -1
- package/dist/generatedPackageMetadata.js +5 -5
- package/dist/ghidra/GhidraAnalysisProfile.js +37 -8
- package/dist/ghidra/GhidraClient.js +52 -6
- package/dist/ghidra/GhidraDiagnostics.js +3 -0
- package/dist/ghidra/GhidraDoctor.js +38 -14
- package/dist/ghidra/GhidraFunctionValues.js +10 -4
- package/dist/ghidra/GhidraInstallation.js +10 -4
- package/dist/ghidra/GhidraInventoryValues.js +39 -2
- package/dist/ghidra/GhidraLauncher.js +91 -26
- package/dist/ghidra/GhidraLoadImageValues.js +199 -0
- package/dist/ghidra/GhidraProvider.js +129 -13
- package/dist/ghidra/GhidraProviderCapabilities.js +52 -15
- package/dist/ghidra/GhidraRequestQueue.js +2 -2
- package/dist/ghidra/GhidraSessionError.js +31 -4
- package/dist/ghidra/GhidraSessionValues.js +10 -7
- package/dist/ghidra/GhidraTargetSnapshot.js +16 -2
- package/dist/ghidra/GhidraTransport.js +12 -0
- package/dist/ghidra/extensions/GhidraExtensionFailures.js +20 -0
- package/dist/ghidra/extensions/GhidraExtensions.js +170 -0
- package/dist/ghidra/extensions/nativeaot/NativeAotAdapter.js +21 -0
- package/dist/ghidra/extensions/nativeaot/NativeAotReport.js +84 -0
- package/dist/hopper/BridgeLauncher.js +4 -2
- package/dist/hopper/HopperAnalysisProfile.js +7 -2
- package/dist/hopper/HopperCleanup.js +55 -9
- package/dist/hopper/HopperClient.js +14 -4
- package/dist/hopper/HopperProcessDiagnostic.js +1 -1
- package/dist/hopper/HopperProvider.js +8 -4
- package/dist/hopper/HopperRequestQueue.js +7 -3
- package/dist/hopper/HopperSessionValues.js +1 -1
- package/dist/hopper/HopperSocketConnection.js +1 -1
- package/dist/hopper/HopperTargetLease.js +23 -15
- package/dist/hopper/LinuxPrivateDisplayDiagnostic.js +7 -8
- package/dist/hopper/LinuxPrivateDisplayProbe.js +6 -2
- package/dist/hopper/protocol.js +8 -8
- package/dist/ida/IdaConfiguration.js +56 -0
- package/dist/ida/IdaDoctor.js +53 -0
- package/dist/ida/IdaInput.js +62 -0
- package/dist/ida/IdaMcpConnection.js +111 -0
- package/dist/ida/IdaOperationRunner.js +299 -0
- package/dist/ida/IdaProtocolValues.js +178 -0
- package/dist/ida/IdaProvider.js +126 -0
- package/dist/ida/IdaProviderCapabilities.js +60 -0
- package/dist/ida/IdaSessionClient.js +306 -0
- package/dist/ida/IdaWorkspace.js +54 -0
- package/dist/main/shutdown.js +21 -1
- package/dist/main/startup.js +1 -1
- package/dist/main/transport.js +8 -4
- package/dist/main.js +5 -2
- package/dist/native/AppleDispatchMetadata.js +31 -10
- package/dist/native/NativeMacOSProvider.js +87 -15
- package/dist/native/NativeMachoInspection.js +42 -12
- package/dist/native/NativeUiHelperRuntime.js +7 -1
- package/dist/native/NativeUiObservation.js +6 -2
- package/dist/native/parsers/demangle.js +1 -1
- package/dist/native/parsers/dyldInfo.js +3 -3
- package/dist/native/parsers/otool.js +4 -1
- package/dist/native/parsers/plist.js +11 -3
- package/dist/process/PrivateRuntimeRoot.js +13 -3
- package/dist/process/ProcessOwnership.js +5 -73
- package/dist/process/ProcessOwnershipObservation.js +62 -2
- package/dist/process/ProcessOwnershipProcessTree.js +13 -0
- package/dist/process/ProviderProcess.js +24 -7
- package/dist/process/ProviderRunLineage.js +1 -1
- package/dist/process/WindowsAuthority.js +20 -1
- package/dist/reference/ReferenceSourceReaderEntries.js +5 -1
- package/dist/reference/ReferenceSourceReaderFile.js +1 -0
- package/dist/server/createServer.js +25 -2
- package/dist/server/mcpProgress.js +2 -1
- package/dist/server/promptCompletion.js +3 -3
- package/dist/server/registerAndroidTools.js +30 -0
- package/dist/server/registerApplicationTools/compareExportShapes.js +5 -1
- package/dist/server/registerApplicationTools/compareSourceToBundle.js +7 -2
- package/dist/server/registerApplicationTools/compareVersions.js +5 -1
- package/dist/server/registerApplicationTools/traceFeature.js +7 -2
- package/dist/server/registerApplicationTools/traceSemantics.js +7 -2
- package/dist/server/registerEnhancedTools.js +28 -1
- package/dist/server/registerFirmwareTools.js +22 -0
- package/dist/server/registerFunctionComparisonTool.js +1 -1
- package/dist/server/registerInvestigationTools/helpers.js +2 -1
- package/dist/server/registerManagedTools.js +1 -1
- package/dist/server/registerManagedWorkflowTools/evidence.js +1 -1
- package/dist/server/registerOfficialTools.js +4 -1
- package/dist/server/registerProcessComparisonTool.js +5 -2
- package/dist/server/registerSessionTools.js +3 -1
- package/dist/server/runDerivedOperation.js +1 -1
- package/dist/server/sessionAvailabilityPolicy.js +3 -0
- package/dist/server/sessionEvidence.js +1 -1
- package/dist/server/toolResult.js +2 -1
- package/dist/windows/WindowsNativeLoader.js +108 -0
- package/dist/windows/WindowsOwnedProcess.js +168 -0
- package/dist/windows/WindowsPrivateRuntime.js +137 -0
- package/install.sh +4 -6
- package/native/windows/README.md +102 -0
- package/native/windows/build/manifest.json +9 -0
- package/native/windows/build/rea-windows-x64.node +0 -0
- package/package.json +38 -24
- package/scripts/rea.mjs +1 -1
- package/scripts/verify-windows-native-artifact.mjs +39 -0
- package/skills/reverse-engineer-anything/SKILL.md +92 -24
- package/skills/reverse-engineer-anything/references/android-applications.md +31 -0
- package/skills/reverse-engineer-anything/references/javascript-applications.md +19 -2
- package/skills/reverse-engineer-anything/references/native-and-artifacts.md +52 -4
- package/dist/browser/CdpCanonicalJsonDigest.js +0 -9
- package/dist/domain/errors.js +0 -324
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { access, realpath, stat } from "node:fs/promises";
|
|
3
|
+
import { constants } from "node:fs";
|
|
4
|
+
import { isAbsolute } from "node:path";
|
|
5
|
+
import { AnalysisCapabilityUnavailableError, AnalysisCancelledError, AnalysisTimeoutError, AnalysisOutputError, } from "../domain/analysisErrorCore.js";
|
|
6
|
+
import { ProviderAdapterError } from "../domain/providerAdapterError.js";
|
|
7
|
+
import { ProviderCleanupError } from "../domain/providerCleanupError.js";
|
|
8
|
+
import { spawnOwnedProviderProcess, ProviderProcessSupervisor, } from "../process/ProviderProcess.js";
|
|
9
|
+
import { cleanupOwnedProcessGroup } from "../process/ProcessOwnership.js";
|
|
10
|
+
import { FIRMWARE_LIMITS } from "./FirmwareRelease.js";
|
|
11
|
+
import { hashFirmwareFile, inventoryFirmwareOutput, readFirmwareReport, } from "./FirmwareFiles.js";
|
|
12
|
+
/** Resolve explicitly supplied executable paths; never install or search for engines. */
|
|
13
|
+
export const resolveFirmwareCommand = async (environment, engine, operation) => {
|
|
14
|
+
const variable = engine === "binwalk" ? "REA_BINWALK_COMMAND" : "REA_UNBLOB_COMMAND";
|
|
15
|
+
const configured = environment[variable];
|
|
16
|
+
if (process.platform !== "linux" ||
|
|
17
|
+
configured === undefined ||
|
|
18
|
+
!isAbsolute(configured))
|
|
19
|
+
throw new AnalysisCapabilityUnavailableError(engine, operation, `This integration requires Linux and an absolute ${variable} path to the caller-supplied tool`);
|
|
20
|
+
const command = await realpath(configured).catch((cause) => {
|
|
21
|
+
throw new AnalysisCapabilityUnavailableError(engine, operation, `${variable} is unavailable: ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
22
|
+
});
|
|
23
|
+
let sha256;
|
|
24
|
+
try {
|
|
25
|
+
await access(command, constants.R_OK | constants.X_OK);
|
|
26
|
+
if (!(await stat(command)).isFile())
|
|
27
|
+
throw new TypeError("Configured path is not a regular file");
|
|
28
|
+
sha256 = await hashFirmwareFile(command);
|
|
29
|
+
}
|
|
30
|
+
catch (cause) {
|
|
31
|
+
throw new AnalysisCapabilityUnavailableError(engine, operation, `${variable} is not a readable executable regular file: ${command}: ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
32
|
+
}
|
|
33
|
+
const limiter = environment.REA_FIRMWARE_PRLIMIT_COMMAND ?? "/usr/bin/prlimit";
|
|
34
|
+
if (!isAbsolute(limiter))
|
|
35
|
+
throw new AnalysisCapabilityUnavailableError(engine, operation, "REA_FIRMWARE_PRLIMIT_COMMAND must be absolute");
|
|
36
|
+
try {
|
|
37
|
+
await access(limiter, constants.X_OK);
|
|
38
|
+
if (!(await stat(limiter)).isFile())
|
|
39
|
+
throw new TypeError("Configured path is not a regular file");
|
|
40
|
+
}
|
|
41
|
+
catch (cause) {
|
|
42
|
+
throw new AnalysisCapabilityUnavailableError(engine, operation, `Resource limiter is not an executable regular file: ${limiter}: ${cause instanceof Error ? cause.message : String(cause)}; provide util-linux prlimit`);
|
|
43
|
+
}
|
|
44
|
+
return { command, limiter, sha256 };
|
|
45
|
+
};
|
|
46
|
+
/** Execute one bounded provider command and verify its owned descendants have stopped. */
|
|
47
|
+
export const runFirmwareCommand = async (context) => {
|
|
48
|
+
const { engine, operation, signal } = context;
|
|
49
|
+
signal.throwIfAborted();
|
|
50
|
+
const runId = `rea-firmware-${randomUUID()}`;
|
|
51
|
+
const spawned = await (context.launcher ?? spawnOwnedProviderProcess)({
|
|
52
|
+
command: context.limiter,
|
|
53
|
+
arguments: [
|
|
54
|
+
`--as=${FIRMWARE_LIMITS.addressSpaceBytes}`,
|
|
55
|
+
`--fsize=${context.outputBudget?.bytes ?? FIRMWARE_LIMITS.reportBytes}`,
|
|
56
|
+
"--core=0",
|
|
57
|
+
"--cpu=120",
|
|
58
|
+
"--",
|
|
59
|
+
context.command,
|
|
60
|
+
...context.args,
|
|
61
|
+
],
|
|
62
|
+
expectedCommand: null,
|
|
63
|
+
runId,
|
|
64
|
+
cwd: context.cwd,
|
|
65
|
+
hostEnvironment: context.environment,
|
|
66
|
+
env: {
|
|
67
|
+
TMPDIR: context.cwd,
|
|
68
|
+
OMP_NUM_THREADS: "1",
|
|
69
|
+
OPENBLAS_NUM_THREADS: "1",
|
|
70
|
+
RAYON_NUM_THREADS: "1",
|
|
71
|
+
NO_COLOR: "1",
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
const supervisor = new ProviderProcessSupervisor({
|
|
75
|
+
...spawned,
|
|
76
|
+
ownsProcessLifetime: true,
|
|
77
|
+
cleanup: spawned.cleanup ?? (() => cleanupOwnedProcessGroup(spawned.ownership)),
|
|
78
|
+
});
|
|
79
|
+
let failure;
|
|
80
|
+
try {
|
|
81
|
+
const deadline = Date.now() + FIRMWARE_LIMITS.timeoutMs;
|
|
82
|
+
while (!(await supervisor.waitForExit(100))) {
|
|
83
|
+
if (signal.aborted)
|
|
84
|
+
throw new AnalysisCancelledError(operation);
|
|
85
|
+
if (Date.now() >= deadline)
|
|
86
|
+
throw new AnalysisTimeoutError(operation, FIRMWARE_LIMITS.timeoutMs);
|
|
87
|
+
if (context.outputBudget !== undefined)
|
|
88
|
+
await inventoryFirmwareOutput(context.outputBudget.root, context.outputBudget.bytes, context.outputBudget.entries, operation, signal);
|
|
89
|
+
}
|
|
90
|
+
if (signal.aborted)
|
|
91
|
+
throw new AnalysisCancelledError(operation);
|
|
92
|
+
}
|
|
93
|
+
catch (cause) {
|
|
94
|
+
failure = cause;
|
|
95
|
+
}
|
|
96
|
+
const stopped = await supervisor.stop();
|
|
97
|
+
const snapshot = supervisor.snapshot();
|
|
98
|
+
supervisor.dispose();
|
|
99
|
+
if (stopped.status === "incomplete")
|
|
100
|
+
throw new ProviderCleanupError(engine, [runId, context.cwd], {
|
|
101
|
+
reason: stopped.reason,
|
|
102
|
+
leader_pid: spawned.ownership.leaderPid,
|
|
103
|
+
process_group_id: spawned.ownership.processGroupId,
|
|
104
|
+
exit_code: snapshot.exitCode ?? null,
|
|
105
|
+
signal: snapshot.signal ?? null,
|
|
106
|
+
stdout: snapshot.stdout.text,
|
|
107
|
+
stderr: snapshot.stderr.text,
|
|
108
|
+
stdout_bytes: snapshot.stdout.bytes,
|
|
109
|
+
stderr_bytes: snapshot.stderr.bytes,
|
|
110
|
+
previous_error: failure instanceof Error ? failure.message : null,
|
|
111
|
+
});
|
|
112
|
+
if (failure !== undefined)
|
|
113
|
+
throw failure;
|
|
114
|
+
if (snapshot.exitCode !== 0 &&
|
|
115
|
+
!(context.acceptReportedExtractionFailure === true &&
|
|
116
|
+
snapshot.exitCode === 1))
|
|
117
|
+
throw new ProviderAdapterError(engine, operation, {
|
|
118
|
+
diagnostics: {
|
|
119
|
+
reason: "Tool failed; inspect exit status and stderr for format, dependency, sandbox or resource-limit failures",
|
|
120
|
+
exit_code: snapshot.exitCode ?? null,
|
|
121
|
+
signal: snapshot.signal ?? null,
|
|
122
|
+
stdout: snapshot.stdout.text,
|
|
123
|
+
stderr: snapshot.stderr.text,
|
|
124
|
+
stdout_bytes: snapshot.stdout.bytes,
|
|
125
|
+
stderr_bytes: snapshot.stderr.bytes,
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
return {
|
|
129
|
+
stdout: snapshot.stdout,
|
|
130
|
+
stderr: snapshot.stderr,
|
|
131
|
+
exit_code: snapshot.exitCode,
|
|
132
|
+
signal: snapshot.signal ?? null,
|
|
133
|
+
command: context.command,
|
|
134
|
+
args: [...context.args],
|
|
135
|
+
resource_limiter: context.limiter,
|
|
136
|
+
};
|
|
137
|
+
};
|
|
138
|
+
/** Preserve tool failure diagnostics when an accepted exit 1 has no usable report. */
|
|
139
|
+
export const readFirmwareCommandReport = async (context) => {
|
|
140
|
+
try {
|
|
141
|
+
return await readFirmwareReport(context.path, context.operation);
|
|
142
|
+
}
|
|
143
|
+
catch (cause) {
|
|
144
|
+
if (context.execution.exit_code === 1)
|
|
145
|
+
throw new ProviderAdapterError(context.engine, context.operation, {
|
|
146
|
+
cause,
|
|
147
|
+
diagnostics: {
|
|
148
|
+
reason: "Tool exited 1 without a usable analysis report; inspect stderr for startup, sandbox or dependency failures",
|
|
149
|
+
report_failure: cause instanceof Error ? cause.message : String(cause),
|
|
150
|
+
exit_code: 1,
|
|
151
|
+
signal: context.execution.signal,
|
|
152
|
+
stdout: context.execution.stdout.text,
|
|
153
|
+
stderr: context.execution.stderr.text,
|
|
154
|
+
stdout_bytes: context.execution.stdout.bytes,
|
|
155
|
+
stderr_bytes: context.execution.stderr.bytes,
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
const diagnostics = `stdout (${context.execution.stdout.bytes} bytes): ${context.execution.stdout.text}; stderr (${context.execution.stderr.bytes} bytes): ${context.execution.stderr.text}`;
|
|
159
|
+
if (cause instanceof AnalysisOutputError)
|
|
160
|
+
throw new AnalysisOutputError(context.operation, `${cause.reason}; ${diagnostics}`, { cause });
|
|
161
|
+
throw new AnalysisOutputError(context.operation, `Tool did not produce the expected complete report: ${context.path}; ${diagnostics}`, { cause });
|
|
162
|
+
}
|
|
163
|
+
};
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { constants, createReadStream, createWriteStream } from "node:fs";
|
|
3
|
+
import { chmod, lstat, open, readdir, readlink, realpath, } from "node:fs/promises";
|
|
4
|
+
import { isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
5
|
+
import { Transform } from "node:stream";
|
|
6
|
+
import { pipeline } from "node:stream/promises";
|
|
7
|
+
import { AnalysisCancelledError, AnalysisInputError, AnalysisOutputError, } from "../domain/analysisErrorCore.js";
|
|
8
|
+
import { readBoundedFileBytes } from "../process/BoundedFileBytes.js";
|
|
9
|
+
import { FIRMWARE_LIMITS } from "./FirmwareRelease.js";
|
|
10
|
+
/** Hash a regular tool or extracted file without retaining its bytes in memory. */
|
|
11
|
+
export const hashFirmwareFile = async (path, signal) => {
|
|
12
|
+
const handle = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
13
|
+
const hash = createHash("sha256");
|
|
14
|
+
try {
|
|
15
|
+
if (!(await handle.stat()).isFile())
|
|
16
|
+
throw new Error(`Not a regular file: ${path}`);
|
|
17
|
+
for await (const chunk of handle.createReadStream({ autoClose: false })) {
|
|
18
|
+
signal?.throwIfAborted();
|
|
19
|
+
hash.update(chunk);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
finally {
|
|
23
|
+
await handle.close();
|
|
24
|
+
}
|
|
25
|
+
return hash.digest("hex");
|
|
26
|
+
};
|
|
27
|
+
/** Copy and identify the exact bytes consumed by firmware providers. */
|
|
28
|
+
export const snapshotFirmware = async (request, root, signal) => {
|
|
29
|
+
const sourcePath = resolve(request.input.path);
|
|
30
|
+
const source = await realpath(sourcePath);
|
|
31
|
+
const handle = await open(source, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
32
|
+
const fullPath = join(root, "firmware.bin");
|
|
33
|
+
const hash = createHash("sha256");
|
|
34
|
+
let size = 0;
|
|
35
|
+
try {
|
|
36
|
+
const stat = await handle.stat();
|
|
37
|
+
if (!stat.isFile() ||
|
|
38
|
+
stat.size === 0 ||
|
|
39
|
+
stat.size > FIRMWARE_LIMITS.inputBytes)
|
|
40
|
+
throw new AnalysisInputError(request.operation, undefined, [
|
|
41
|
+
{
|
|
42
|
+
path: ["path"],
|
|
43
|
+
reason: "out_of_range",
|
|
44
|
+
message: `Firmware must be a nonempty regular file no larger than ${FIRMWARE_LIMITS.inputBytes} bytes`,
|
|
45
|
+
},
|
|
46
|
+
]);
|
|
47
|
+
const meter = new Transform({
|
|
48
|
+
transform(chunk, _encoding, callback) {
|
|
49
|
+
size += chunk.length;
|
|
50
|
+
if (size > FIRMWARE_LIMITS.inputBytes) {
|
|
51
|
+
callback(new Error("Firmware grew beyond the input byte budget"));
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
hash.update(chunk);
|
|
55
|
+
callback(null, chunk);
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
await pipeline(handle.createReadStream({ autoClose: false }), meter, createWriteStream(fullPath, { flags: "wx", mode: 0o600 }), { signal });
|
|
59
|
+
}
|
|
60
|
+
finally {
|
|
61
|
+
await handle.close();
|
|
62
|
+
}
|
|
63
|
+
await chmod(fullPath, 0o400);
|
|
64
|
+
const sha256 = hash.digest("hex");
|
|
65
|
+
const selected = request.operation === "extract_firmware" ? request.input.range : undefined;
|
|
66
|
+
const offset = selected?.offset ?? 0;
|
|
67
|
+
const length = selected?.length ?? size;
|
|
68
|
+
if (length === 0 || offset > size || length > size - offset)
|
|
69
|
+
throw new AnalysisInputError(request.operation, undefined, [
|
|
70
|
+
{
|
|
71
|
+
path: ["range"],
|
|
72
|
+
reason: "out_of_range",
|
|
73
|
+
message: `Selected range must be within the ${size}-byte firmware file`,
|
|
74
|
+
},
|
|
75
|
+
]);
|
|
76
|
+
let inputPath = fullPath;
|
|
77
|
+
if (selected !== undefined) {
|
|
78
|
+
inputPath = join(root, "selected.bin");
|
|
79
|
+
await pipeline(createReadStream(fullPath, { start: offset, end: offset + length - 1 }), createWriteStream(inputPath, { flags: "wx", mode: 0o600 }), { signal });
|
|
80
|
+
await chmod(inputPath, 0o400);
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
subject: { path: sourcePath, sha256, format: "file" },
|
|
84
|
+
size,
|
|
85
|
+
inputPath,
|
|
86
|
+
selection: {
|
|
87
|
+
offset,
|
|
88
|
+
length,
|
|
89
|
+
sha256: selected === undefined
|
|
90
|
+
? sha256
|
|
91
|
+
: await hashFirmwareFile(inputPath, signal),
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
};
|
|
95
|
+
/** Read a complete bounded report; oversized data is never reported as complete. */
|
|
96
|
+
export const readFirmwareReport = async (path, operation) => {
|
|
97
|
+
const handle = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
98
|
+
try {
|
|
99
|
+
if (!(await handle.stat()).isFile())
|
|
100
|
+
throw new AnalysisOutputError(operation, "Report is not a regular file");
|
|
101
|
+
const bytes = await readBoundedFileBytes(handle, FIRMWARE_LIMITS.reportBytes);
|
|
102
|
+
if (bytes === undefined)
|
|
103
|
+
throw new AnalysisOutputError(operation, `Report exceeded ${FIRMWARE_LIMITS.reportBytes} bytes`);
|
|
104
|
+
try {
|
|
105
|
+
const value = JSON.parse(bytes.toString("utf8"));
|
|
106
|
+
return value;
|
|
107
|
+
}
|
|
108
|
+
catch (cause) {
|
|
109
|
+
throw new AnalysisOutputError(operation, "Tool report is not valid complete JSON", { cause });
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
finally {
|
|
113
|
+
await handle.close();
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
/** Inventory private output without following links, enforcing aggregate staging budgets. */
|
|
117
|
+
export const inventoryFirmwareOutput = async (root, maxBytes, maxEntries, operation, signal) => {
|
|
118
|
+
const entries = [];
|
|
119
|
+
const pending = [root];
|
|
120
|
+
let bytes = 0;
|
|
121
|
+
let count = 0;
|
|
122
|
+
while (pending.length > 0) {
|
|
123
|
+
if (signal?.aborted === true)
|
|
124
|
+
throw new AnalysisCancelledError(operation);
|
|
125
|
+
const directory = pending.pop();
|
|
126
|
+
if (directory === undefined)
|
|
127
|
+
break;
|
|
128
|
+
const directoryStat = await lstat(directory).catch((cause) => {
|
|
129
|
+
if (cause instanceof Error && "code" in cause && cause.code === "ENOENT")
|
|
130
|
+
return undefined;
|
|
131
|
+
throw cause;
|
|
132
|
+
});
|
|
133
|
+
if (directoryStat === undefined ||
|
|
134
|
+
!directoryStat.isDirectory() ||
|
|
135
|
+
directoryStat.isSymbolicLink())
|
|
136
|
+
continue;
|
|
137
|
+
const names = await readdir(directory).catch((cause) => {
|
|
138
|
+
if (cause instanceof Error && "code" in cause && cause.code === "ENOENT")
|
|
139
|
+
return [];
|
|
140
|
+
throw cause;
|
|
141
|
+
});
|
|
142
|
+
for (const name of names.sort()) {
|
|
143
|
+
count++;
|
|
144
|
+
if (count > maxEntries)
|
|
145
|
+
throw new AnalysisOutputError(operation, `Staging entry budget exceeded (${maxEntries})`);
|
|
146
|
+
const path = join(directory, name);
|
|
147
|
+
const stat = await lstat(path).catch((cause) => {
|
|
148
|
+
if (cause instanceof Error &&
|
|
149
|
+
"code" in cause &&
|
|
150
|
+
cause.code === "ENOENT")
|
|
151
|
+
return undefined;
|
|
152
|
+
throw cause;
|
|
153
|
+
});
|
|
154
|
+
if (stat === undefined)
|
|
155
|
+
continue;
|
|
156
|
+
if (stat.isDirectory()) {
|
|
157
|
+
pending.push(path);
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
if (stat.isFile())
|
|
161
|
+
bytes += stat.size;
|
|
162
|
+
if (bytes > maxBytes)
|
|
163
|
+
throw new AnalysisOutputError(operation, `Staging byte budget exceeded (${maxBytes})`);
|
|
164
|
+
entries.push({
|
|
165
|
+
path,
|
|
166
|
+
relativePath: relative(root, path).split(sep).join("/"),
|
|
167
|
+
size: stat.size,
|
|
168
|
+
kind: stat.isSymbolicLink()
|
|
169
|
+
? "symlink"
|
|
170
|
+
: stat.isFile()
|
|
171
|
+
? "file"
|
|
172
|
+
: "special",
|
|
173
|
+
linkTarget: stat.isSymbolicLink() ? await readlink(path) : null,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return entries.sort((a, b) => a.relativePath.localeCompare(b.relativePath));
|
|
178
|
+
};
|
|
179
|
+
/** Bind producer paths to the selected input or private extraction tree. */
|
|
180
|
+
export const firmwareLogicalPath = (path, inputPath, outputRoot) => {
|
|
181
|
+
if (path === inputPath)
|
|
182
|
+
return "$input";
|
|
183
|
+
const rel = relative(outputRoot, path);
|
|
184
|
+
if (!isAbsolute(path) ||
|
|
185
|
+
rel === ".." ||
|
|
186
|
+
rel.startsWith(`..${sep}`) ||
|
|
187
|
+
isAbsolute(rel))
|
|
188
|
+
throw new AnalysisOutputError("extract_firmware", `Provider reported a path outside its owned output: ${path}`);
|
|
189
|
+
return rel === "" ? "$output" : `$output/${rel.split(sep).join("/")}`;
|
|
190
|
+
};
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import { join, isAbsolute } from "node:path";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { createAnalysisExecution, } from "../application/AnalysisProvider.js";
|
|
4
|
+
import { firmwareResultSchemas, } from "../domain/firmwareAnalysis.js";
|
|
5
|
+
import { AnalysisError } from "../domain/analysisErrorBase.js";
|
|
6
|
+
import { AnalysisCancelledError, AnalysisCapabilityUnavailableError, AnalysisInputError, AnalysisOutputError, AnalysisTimeoutError, } from "../domain/analysisErrorCore.js";
|
|
7
|
+
import { ProviderAdapterError } from "../domain/providerAdapterError.js";
|
|
8
|
+
import { ProviderCleanupError } from "../domain/providerCleanupError.js";
|
|
9
|
+
import { err, ok } from "../domain/result.js";
|
|
10
|
+
import { PrivateRuntimeRoot } from "../process/PrivateRuntimeRoot.js";
|
|
11
|
+
import { publishFirmwareExtraction } from "./FirmwarePublication.js";
|
|
12
|
+
import { FIRMWARE_LIMITS, FIRMWARE_RELEASES, BINWALK_PROVIDER_IDENTITY, UNBLOB_PROVIDER_IDENTITY, } from "./FirmwareRelease.js";
|
|
13
|
+
import { resolveFirmwareCommand, runFirmwareCommand, readFirmwareCommandReport, } from "./FirmwareCommand.js";
|
|
14
|
+
import { inventoryFirmwareOutput, snapshotFirmware } from "./FirmwareFiles.js";
|
|
15
|
+
import { normalizeBinwalkReport, normalizeUnblobReport, } from "./FirmwareReports.js";
|
|
16
|
+
const waitForPredecessor = (predecessor, operation, signal) => new Promise((resolve, reject) => {
|
|
17
|
+
const abort = () => reject(new AnalysisCancelledError(operation));
|
|
18
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
19
|
+
if (signal?.aborted === true)
|
|
20
|
+
abort();
|
|
21
|
+
void predecessor.then(() => {
|
|
22
|
+
signal?.removeEventListener("abort", abort);
|
|
23
|
+
resolve();
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
const limitations = [
|
|
27
|
+
"Signature matches and reported sizes do not establish partition validity, encryption or runtime mappings. Binwalk may infer unknown lengths before emitting its report.",
|
|
28
|
+
"Only Linux is supported by this integration. One worker, inherited per-process address-space/file-size/CPU limits and a deadline constrain execution; staging byte and entry budgets are polled and may transiently overshoot. Limits are not aggregate process-memory or filesystem quotas.",
|
|
29
|
+
"Unblob's default sandbox and skip rules remain enabled. Missing external extractors, unknown chunks, encrypted chunks and depth limits yield partial coverage. Extracted code is never executed; only regular files are published, without executable permission bits.",
|
|
30
|
+
"The actual launcher executable is hashed. Interpreter, shared libraries and installed Python package dependencies are not independently attested; source revision remains unknown for caller-supplied builds.",
|
|
31
|
+
];
|
|
32
|
+
/** Queued bring-your-own firmware adapters with private snapshots and owned cleanup. */
|
|
33
|
+
export class FirmwareProvider {
|
|
34
|
+
environment;
|
|
35
|
+
launcher;
|
|
36
|
+
#tail = Promise.resolve();
|
|
37
|
+
#cleanupFailure;
|
|
38
|
+
constructor(environment = process.env, launcher) {
|
|
39
|
+
this.environment = environment;
|
|
40
|
+
this.launcher = launcher;
|
|
41
|
+
}
|
|
42
|
+
/** Inspect regions or explicitly extract selected bytes without changing the source. */
|
|
43
|
+
async execute(request, options) {
|
|
44
|
+
const predecessor = this.#tail;
|
|
45
|
+
let release = () => { };
|
|
46
|
+
this.#tail = new Promise((resolve) => {
|
|
47
|
+
release = resolve;
|
|
48
|
+
});
|
|
49
|
+
// Cancel the caller promptly without letting a later request bypass its predecessor.
|
|
50
|
+
try {
|
|
51
|
+
await waitForPredecessor(predecessor, request.operation, options?.signal);
|
|
52
|
+
}
|
|
53
|
+
catch (cause) {
|
|
54
|
+
void predecessor.then(release);
|
|
55
|
+
return err(cause instanceof AnalysisError
|
|
56
|
+
? cause
|
|
57
|
+
: new AnalysisCancelledError(request.operation));
|
|
58
|
+
}
|
|
59
|
+
try {
|
|
60
|
+
return await this.#execute(request, options);
|
|
61
|
+
}
|
|
62
|
+
finally {
|
|
63
|
+
release();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
async #execute(request, options) {
|
|
67
|
+
let root;
|
|
68
|
+
const engineName = request.operation === "inspect_firmware_regions" ? "binwalk" : "unblob";
|
|
69
|
+
const deadline = AbortSignal.timeout(FIRMWARE_LIMITS.timeoutMs);
|
|
70
|
+
const signal = options?.signal === undefined
|
|
71
|
+
? deadline
|
|
72
|
+
: AbortSignal.any([options.signal, deadline]);
|
|
73
|
+
let outcome;
|
|
74
|
+
try {
|
|
75
|
+
if (this.#cleanupFailure !== undefined)
|
|
76
|
+
return err(this.#cleanupFailure);
|
|
77
|
+
if (signal.aborted)
|
|
78
|
+
throw new AnalysisCancelledError(request.operation);
|
|
79
|
+
const command = await resolveFirmwareCommand(this.environment, engineName, request.operation);
|
|
80
|
+
root = await PrivateRuntimeRoot.create({ prefix: "rea-firmware-" });
|
|
81
|
+
const target = await snapshotFirmware(request, root.path, signal);
|
|
82
|
+
const run = (args, outputBudget) => runFirmwareCommand({
|
|
83
|
+
engine: engineName,
|
|
84
|
+
operation: request.operation,
|
|
85
|
+
...command,
|
|
86
|
+
args,
|
|
87
|
+
cwd: root?.path ?? "",
|
|
88
|
+
environment: this.environment,
|
|
89
|
+
signal,
|
|
90
|
+
...(this.launcher === undefined ? {} : { launcher: this.launcher }),
|
|
91
|
+
...(outputBudget === undefined
|
|
92
|
+
? {}
|
|
93
|
+
: { outputBudget, acceptReportedExtractionFailure: true }),
|
|
94
|
+
});
|
|
95
|
+
const versionRun = await run(["--version"]);
|
|
96
|
+
const version = versionRun.stdout.text.trim();
|
|
97
|
+
const expected = FIRMWARE_RELEASES[engineName].version;
|
|
98
|
+
if (versionRun.stdout.bytes > 1024 ||
|
|
99
|
+
(engineName === "binwalk"
|
|
100
|
+
? version !== `binwalk ${expected}`
|
|
101
|
+
: version !== expected))
|
|
102
|
+
throw new AnalysisCapabilityUnavailableError(engineName, request.operation, `Unsupported tool version ${version}; this adapter is verified against ${expected}`);
|
|
103
|
+
const engine = {
|
|
104
|
+
name: engineName,
|
|
105
|
+
version: expected,
|
|
106
|
+
executable_path: command.command,
|
|
107
|
+
executable_sha256: command.sha256,
|
|
108
|
+
source_revision: null,
|
|
109
|
+
worker_count: 1,
|
|
110
|
+
};
|
|
111
|
+
const reportPath = join(root.path, "report.json");
|
|
112
|
+
let result;
|
|
113
|
+
let raw;
|
|
114
|
+
if (request.operation === "inspect_firmware_regions") {
|
|
115
|
+
const processRun = await run([
|
|
116
|
+
"--quiet",
|
|
117
|
+
"--search-all",
|
|
118
|
+
"--threads",
|
|
119
|
+
"1",
|
|
120
|
+
"--log",
|
|
121
|
+
reportPath,
|
|
122
|
+
target.inputPath,
|
|
123
|
+
]);
|
|
124
|
+
const report = await readFirmwareCommandReport({
|
|
125
|
+
path: reportPath,
|
|
126
|
+
engine: engineName,
|
|
127
|
+
operation: request.operation,
|
|
128
|
+
execution: processRun,
|
|
129
|
+
});
|
|
130
|
+
result = firmwareResultSchemas.inspect_firmware_regions.parse({
|
|
131
|
+
engine,
|
|
132
|
+
input_size: target.size,
|
|
133
|
+
regions: normalizeBinwalkReport(report, target.inputPath, target.size),
|
|
134
|
+
coverage: "signature_scan",
|
|
135
|
+
});
|
|
136
|
+
raw = { report, execution: processRun, version_execution: versionRun };
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
if (!isAbsolute(request.input.output_directory))
|
|
140
|
+
throw new AnalysisInputError(request.operation, undefined, [
|
|
141
|
+
{
|
|
142
|
+
path: ["output_directory"],
|
|
143
|
+
reason: "invalid_format",
|
|
144
|
+
message: "Extraction output directory must be absolute",
|
|
145
|
+
},
|
|
146
|
+
]);
|
|
147
|
+
const outputRoot = join(root.path, "extracted");
|
|
148
|
+
const processRun = await run([
|
|
149
|
+
"--process-num",
|
|
150
|
+
"1",
|
|
151
|
+
"--depth",
|
|
152
|
+
String(request.input.max_depth),
|
|
153
|
+
"--randomness-depth",
|
|
154
|
+
"0",
|
|
155
|
+
"--report",
|
|
156
|
+
reportPath,
|
|
157
|
+
"--log",
|
|
158
|
+
join(root.path, "unblob.log"),
|
|
159
|
+
"--extract-dir",
|
|
160
|
+
outputRoot,
|
|
161
|
+
target.inputPath,
|
|
162
|
+
], {
|
|
163
|
+
root: outputRoot,
|
|
164
|
+
bytes: request.input.max_output_bytes,
|
|
165
|
+
entries: request.input.max_output_files,
|
|
166
|
+
});
|
|
167
|
+
const report = await readFirmwareCommandReport({
|
|
168
|
+
path: reportPath,
|
|
169
|
+
engine: engineName,
|
|
170
|
+
operation: request.operation,
|
|
171
|
+
execution: processRun,
|
|
172
|
+
});
|
|
173
|
+
const normalized = normalizeUnblobReport(report, {
|
|
174
|
+
inputPath: target.inputPath,
|
|
175
|
+
outputRoot,
|
|
176
|
+
...target.selection,
|
|
177
|
+
maxDepth: request.input.max_depth,
|
|
178
|
+
});
|
|
179
|
+
if (processRun.exit_code === 1 && normalized.diagnostics.length === 0)
|
|
180
|
+
throw new AnalysisOutputError(request.operation, "Unblob failed without a reported extraction diagnostic");
|
|
181
|
+
const entries = await inventoryFirmwareOutput(outputRoot, request.input.max_output_bytes, request.input.max_output_files, request.operation, signal);
|
|
182
|
+
result = await publishFirmwareExtraction({
|
|
183
|
+
outputDirectory: request.input.output_directory,
|
|
184
|
+
entries,
|
|
185
|
+
normalized,
|
|
186
|
+
selection: target.selection,
|
|
187
|
+
engine,
|
|
188
|
+
signal,
|
|
189
|
+
});
|
|
190
|
+
raw = { report, execution: processRun, version_execution: versionRun };
|
|
191
|
+
}
|
|
192
|
+
outcome = ok(createAnalysisExecution(result, engineName === "binwalk"
|
|
193
|
+
? BINWALK_PROVIDER_IDENTITY
|
|
194
|
+
: UNBLOB_PROVIDER_IDENTITY, {
|
|
195
|
+
subject: target.subject,
|
|
196
|
+
rawResult: raw,
|
|
197
|
+
limitations,
|
|
198
|
+
locations: [
|
|
199
|
+
{
|
|
200
|
+
kind: "file-offset-range",
|
|
201
|
+
start: target.selection.offset,
|
|
202
|
+
end: target.selection.offset + target.selection.length,
|
|
203
|
+
},
|
|
204
|
+
],
|
|
205
|
+
}));
|
|
206
|
+
}
|
|
207
|
+
catch (cause) {
|
|
208
|
+
outcome = err(cause instanceof ProviderCleanupError
|
|
209
|
+
? cause
|
|
210
|
+
: options?.signal?.aborted === true
|
|
211
|
+
? new AnalysisCancelledError(request.operation)
|
|
212
|
+
: deadline.aborted
|
|
213
|
+
? new AnalysisTimeoutError(request.operation, FIRMWARE_LIMITS.timeoutMs)
|
|
214
|
+
: cause instanceof AnalysisError
|
|
215
|
+
? cause
|
|
216
|
+
: cause instanceof z.ZodError
|
|
217
|
+
? new AnalysisOutputError(request.operation, "Tool report violated the pinned producer protocol", { cause })
|
|
218
|
+
: new ProviderAdapterError(engineName, request.operation, {
|
|
219
|
+
cause,
|
|
220
|
+
diagnostics: {
|
|
221
|
+
reason: cause instanceof Error
|
|
222
|
+
? cause.message
|
|
223
|
+
: String(cause),
|
|
224
|
+
},
|
|
225
|
+
}));
|
|
226
|
+
if (!outcome.ok && outcome.error.cleanupIncomplete) {
|
|
227
|
+
this.#cleanupFailure =
|
|
228
|
+
root === undefined ||
|
|
229
|
+
outcome.error.cleanupResources.includes(root.path)
|
|
230
|
+
? outcome.error
|
|
231
|
+
: new ProviderCleanupError(engineName, [...outcome.error.cleanupResources, root.path], outcome.error instanceof ProviderAdapterError
|
|
232
|
+
? (outcome.error.diagnostics ?? {
|
|
233
|
+
reason: outcome.error.message,
|
|
234
|
+
})
|
|
235
|
+
: { reason: outcome.error.message }, { cause: outcome.error });
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
// Never remove a workspace while process ownership remains uncertain.
|
|
239
|
+
if (this.#cleanupFailure !== undefined)
|
|
240
|
+
return err(this.#cleanupFailure);
|
|
241
|
+
try {
|
|
242
|
+
await root?.close();
|
|
243
|
+
}
|
|
244
|
+
catch (cause) {
|
|
245
|
+
this.#cleanupFailure = new ProviderCleanupError(engineName, [
|
|
246
|
+
root?.path ?? "unknown",
|
|
247
|
+
...(outcome.ok && request.operation === "extract_firmware"
|
|
248
|
+
? [request.input.output_directory]
|
|
249
|
+
: []),
|
|
250
|
+
], {
|
|
251
|
+
reason: cause instanceof Error ? cause.message : String(cause),
|
|
252
|
+
previous_result: outcome.ok ? outcome.value.result : null,
|
|
253
|
+
});
|
|
254
|
+
return err(this.#cleanupFailure);
|
|
255
|
+
}
|
|
256
|
+
return outcome;
|
|
257
|
+
}
|
|
258
|
+
}
|