@eyeauras/insight-cli 0.0.0-stage → 0.2.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.
Files changed (174) hide show
  1. package/.cli-package-build.json +1 -0
  2. package/LICENSE +21 -0
  3. package/README.md +60 -2
  4. package/SKILL.md +58 -0
  5. package/dist/src/administration-results.d.ts +1 -0
  6. package/dist/src/administration-results.js +84 -0
  7. package/dist/src/administration.d.ts +14 -0
  8. package/dist/src/administration.js +134 -0
  9. package/dist/src/archive-contract.d.ts +6 -0
  10. package/dist/src/archive-contract.js +108 -0
  11. package/dist/src/archive.d.ts +12 -0
  12. package/dist/src/archive.js +116 -0
  13. package/dist/src/arguments.d.ts +14 -0
  14. package/dist/src/arguments.js +62 -0
  15. package/dist/src/artifacts.d.ts +17 -0
  16. package/dist/src/artifacts.js +115 -0
  17. package/dist/src/bin.d.ts +2 -0
  18. package/dist/src/bin.js +4 -0
  19. package/dist/src/cli.d.ts +10 -0
  20. package/dist/src/cli.js +419 -0
  21. package/dist/src/comment-effects.d.ts +3 -0
  22. package/dist/src/comment-effects.js +55 -0
  23. package/dist/src/contexts.d.ts +115 -0
  24. package/dist/src/contexts.js +1078 -0
  25. package/dist/src/coordinator.d.ts +41 -0
  26. package/dist/src/coordinator.js +160 -0
  27. package/dist/src/cross-results.d.ts +14 -0
  28. package/dist/src/cross-results.js +71 -0
  29. package/dist/src/cross.d.ts +3 -0
  30. package/dist/src/cross.js +43 -0
  31. package/dist/src/data-effects.d.ts +3 -0
  32. package/dist/src/data-effects.js +103 -0
  33. package/dist/src/debugger-results.d.ts +4 -0
  34. package/dist/src/debugger-results.js +151 -0
  35. package/dist/src/debugger-supervisor.d.ts +1 -0
  36. package/dist/src/debugger-supervisor.js +136 -0
  37. package/dist/src/debugger.d.ts +21 -0
  38. package/dist/src/debugger.js +267 -0
  39. package/dist/src/documentation-effects.d.ts +5 -0
  40. package/dist/src/documentation-effects.js +56 -0
  41. package/dist/src/extensions.d.ts +64 -0
  42. package/dist/src/extensions.js +289 -0
  43. package/dist/src/function-effects.d.ts +4 -0
  44. package/dist/src/function-effects.js +112 -0
  45. package/dist/src/ghidra.d.ts +50 -0
  46. package/dist/src/ghidra.js +468 -0
  47. package/dist/src/index.d.ts +2 -0
  48. package/dist/src/index.js +1 -0
  49. package/dist/src/initial-model.d.ts +6 -0
  50. package/dist/src/initial-model.js +85 -0
  51. package/dist/src/json.d.ts +2 -0
  52. package/dist/src/json.js +8 -0
  53. package/dist/src/lifecycle.d.ts +49 -0
  54. package/dist/src/lifecycle.js +154 -0
  55. package/dist/src/memory-inputs.d.ts +8 -0
  56. package/dist/src/memory-inputs.js +56 -0
  57. package/dist/src/merge.d.ts +29 -0
  58. package/dist/src/merge.js +66 -0
  59. package/dist/src/model.d.ts +74 -0
  60. package/dist/src/model.js +67 -0
  61. package/dist/src/mutation-effects.d.ts +4 -0
  62. package/dist/src/mutation-effects.js +321 -0
  63. package/dist/src/mutation-inputs.d.ts +2 -0
  64. package/dist/src/mutation-inputs.js +71 -0
  65. package/dist/src/mutations.d.ts +4 -0
  66. package/dist/src/mutations.js +53 -0
  67. package/dist/src/native-inputs.d.ts +28 -0
  68. package/dist/src/native-inputs.js +182 -0
  69. package/dist/src/native-queries.d.ts +2 -0
  70. package/dist/src/native-queries.js +29 -0
  71. package/dist/src/projects.d.ts +31 -0
  72. package/dist/src/projects.js +301 -0
  73. package/dist/src/prototype-cli.d.ts +8 -0
  74. package/dist/src/prototype-cli.js +155 -0
  75. package/dist/src/queries.d.ts +5 -0
  76. package/dist/src/queries.js +84 -0
  77. package/dist/src/query-policy.d.ts +6 -0
  78. package/dist/src/query-policy.js +72 -0
  79. package/dist/src/query-results.d.ts +2 -0
  80. package/dist/src/query-results.js +224 -0
  81. package/dist/src/research.d.ts +184 -0
  82. package/dist/src/research.js +808 -0
  83. package/dist/src/runtime-archives.d.ts +5 -0
  84. package/dist/src/runtime-archives.js +127 -0
  85. package/dist/src/runtime-catalog.d.ts +18 -0
  86. package/dist/src/runtime-catalog.js +27 -0
  87. package/dist/src/runtime-manager.d.ts +101 -0
  88. package/dist/src/runtime-manager.js +444 -0
  89. package/dist/src/runtime-selection.d.ts +13 -0
  90. package/dist/src/runtime-selection.js +25 -0
  91. package/dist/src/saved-materials.d.ts +12 -0
  92. package/dist/src/saved-materials.js +196 -0
  93. package/dist/src/scripts.d.ts +19 -0
  94. package/dist/src/scripts.js +79 -0
  95. package/dist/src/sdk-contracts.d.ts +13 -0
  96. package/dist/src/sdk-contracts.js +37 -0
  97. package/dist/src/sdk-files.d.ts +29 -0
  98. package/dist/src/sdk-files.js +249 -0
  99. package/dist/src/sdk.d.ts +4 -0
  100. package/dist/src/sdk.js +125 -0
  101. package/dist/src/server.d.ts +46 -0
  102. package/dist/src/server.js +460 -0
  103. package/dist/src/store.d.ts +33 -0
  104. package/dist/src/store.js +178 -0
  105. package/dist/src/sync-policy.d.ts +22 -0
  106. package/dist/src/sync-policy.js +123 -0
  107. package/dist/src/transport.d.ts +23 -0
  108. package/dist/src/transport.js +135 -0
  109. package/dist/src/ui-results.d.ts +2 -0
  110. package/dist/src/ui-results.js +80 -0
  111. package/dist/src/ui.d.ts +17 -0
  112. package/dist/src/ui.js +88 -0
  113. package/dist/src/utility.d.ts +14 -0
  114. package/dist/src/utility.js +43 -0
  115. package/dist/src/version-tracking.d.ts +57 -0
  116. package/dist/src/version-tracking.js +200 -0
  117. package/dist/src/workspace.d.ts +199 -0
  118. package/dist/src/workspace.js +564 -0
  119. package/package.json +47 -4
  120. package/references/InsightRepositoryProbe.java +55 -0
  121. package/references/administration.md +34 -0
  122. package/references/archive.md +32 -0
  123. package/references/artifacts.md +47 -0
  124. package/references/debugger.md +40 -0
  125. package/references/extensions-and-sync.md +124 -0
  126. package/references/managed-runtime.md +116 -0
  127. package/references/mutations.json +1424 -0
  128. package/references/native/InsightApplyDocumentation.java +48 -0
  129. package/references/native/InsightArchivePayloads.java +36 -0
  130. package/references/native/InsightBlockTransfer.java +57 -0
  131. package/references/native/InsightEmulation.java +93 -0
  132. package/references/native/InsightExtensions.java +36 -0
  133. package/references/native/InsightFunctionDatabases.java +98 -0
  134. package/references/native/InsightGuiMain.java +32 -0
  135. package/references/native/InsightHeadlessMain.java +10 -0
  136. package/references/native/InsightInitialImport.java +42 -0
  137. package/references/native/InsightInitialJournal.java +48 -0
  138. package/references/native/InsightInitialPublication.java +142 -0
  139. package/references/native/InsightKeytool.java +16 -0
  140. package/references/native/InsightNativeReceipt.java +31 -0
  141. package/references/native/InsightPlugins.java +34 -0
  142. package/references/native/InsightProjectFiles.java +92 -0
  143. package/references/native/InsightPublicationRecovery.java +63 -0
  144. package/references/native/InsightPythonWheels.java +31 -0
  145. package/references/native/InsightResearchQueries.java +188 -0
  146. package/references/native/InsightRuntimeDoctor.java +33 -0
  147. package/references/native/InsightSdkFiles.java +142 -0
  148. package/references/native/InsightSdkScript.java +31 -0
  149. package/references/native/InsightSdkTools.java +338 -0
  150. package/references/native/InsightSdkTypes.java +132 -0
  151. package/references/native/InsightServerAdministration.java +82 -0
  152. package/references/native/InsightServerInit.java +62 -0
  153. package/references/native/InsightServerMain.java +43 -0
  154. package/references/native/InsightServerProbe.java +29 -0
  155. package/references/native/InsightSnapshotFields.java +68 -0
  156. package/references/native/InsightSnapshotInspect.java +189 -0
  157. package/references/native/InsightSnapshotPublisher.java +123 -0
  158. package/references/native/InsightStartDebugger.java +28 -0
  159. package/references/native/InsightStopDebugger.java +20 -0
  160. package/references/native/InsightTraceInspect.java +24 -0
  161. package/references/native/InsightTraceState.java +130 -0
  162. package/references/native/InsightTraceTools.java +114 -0
  163. package/references/native/InsightVersionTracking.java +91 -0
  164. package/references/native-workflows.md +112 -0
  165. package/references/queries.json +1761 -0
  166. package/references/query-semantics.md +96 -0
  167. package/references/recovery.md +53 -0
  168. package/references/research.md +101 -0
  169. package/references/setup.md +49 -0
  170. package/runtime/GhidraMCP-7.0.0.jar +0 -0
  171. package/runtime/GhidraMCP-LICENSE +202 -0
  172. package/runtime/GhidraMCP-NOTICE +10 -0
  173. package/runtime/provenance.json +11 -0
  174. package/skills/insight-re/SKILL.md +108 -0
@@ -0,0 +1,142 @@
1
+ package db;
2
+
3
+ import com.google.gson.*;
4
+ import com.xebyte.headless.GhidraServerManager;
5
+ import db.buffers.*;
6
+ import ghidra.GhidraApplicationLayout;
7
+ import ghidra.framework.*;
8
+ import ghidra.framework.client.RepositoryAdapter;
9
+ import ghidra.framework.data.OpenMode;
10
+ import ghidra.framework.store.db.PackedDatabase;
11
+ import ghidra.program.database.ProgramDB;
12
+ import ghidra.util.task.TaskMonitor;
13
+ import insight.InsightInitialJournal;
14
+ import java.nio.file.*;
15
+ import java.nio.channels.*;
16
+ import java.util.*;
17
+
18
+ /** Prepare under a unique staging name; only promote independently verified complete buffers.
19
+ * No checkout/check-in, overwrite or buffer write exists for the canonical target path. */
20
+ class InsightInitialPublication {
21
+ static String text(JsonObject p,String k){return InsightInitialJournal.text(p,k);}
22
+ static void authority(RepositoryAdapter repo,boolean reader)throws Exception{
23
+ var user=repo.getUser();if(user==null||(reader?!user.isReadOnly():!user.hasWritePermission()))throw new SecurityException();
24
+ }
25
+ static void binding(Path root,JsonObject journal,JsonObject saved)throws Exception{
26
+ if(!text(journal,"intentHash").equals(InsightInitialJournal.hash(root.resolve("intent.json")))||!text(journal,"snapshotSha256").equals(text(saved,"snapshotSha256")))throw new SecurityException("Initial publication binding changed");
27
+ }
28
+ static boolean recovered(RepositoryAdapter repo,String name)throws Exception{
29
+ var versions=repo.getVersions("/",name);
30
+ return versions.length==1&&versions[0].getVersion()==1&&versions[0].getCreateTime()==0&&"<Recovered>".equals(versions[0].getComment());
31
+ }
32
+ static void verify(RepositoryAdapter repo,String name,String fileId,Path snapshot,String sha,String marker)throws Exception{
33
+ var item=repo.getItem("/",name);
34
+ if(item==null||!fileId.equals(item.getFileID())||item.getVersion()!=1||repo.isCheckinActive("/",name))throw new IllegalStateException("Initial item is not quiescent at version one");
35
+ var versions=repo.getVersions("/",name);
36
+ if(versions.length!=1||versions[0].getVersion()!=1||!marker.equals(versions[0].getComment())&&!recovered(repo,name))throw new SecurityException("Initial history changed");
37
+ DBHandle expected=PackedDatabase.getPackedDatabase(snapshot.toFile(),true,TaskMonitor.DUMMY).open(TaskMonitor.DUMMY);
38
+ try{
39
+ DBHandle observed=new DBHandle(repo.openDatabase("/",name,1,-1));
40
+ try{
41
+ if(expected.getDatabaseId()!=observed.getDatabaseId()||!InsightBlockTransfer.fingerprint(expected.getBufferMgr().getSourceFile()).equals(InsightBlockTransfer.fingerprint(observed.getBufferMgr().getSourceFile())))throw new SecurityException("Initial buffers differ");
42
+ Object consumer=new Object();ProgramDB program=new ProgramDB(observed,OpenMode.IMMUTABLE,TaskMonitor.DUMMY,consumer);
43
+ try{if(!sha.equals(program.getExecutableSHA256()))throw new SecurityException("Initial source changed");}finally{program.release(consumer);}
44
+ }finally{observed.close();}
45
+ }finally{expected.close();}
46
+ }
47
+ static void release(RepositoryAdapter repo,String name,JsonObject j)throws Exception{
48
+ if(!j.has("checkoutId"))return;
49
+ for(var held:repo.getCheckouts("/",name))if(held.getCheckoutId()==j.get("checkoutId").getAsLong()){
50
+ if(!held.getUser().equals(repo.getUser().getName())||held.getCheckoutVersion()>1||repo.isCheckinActive("/",name))throw new SecurityException("Initial checkout remains active or foreign");
51
+ repo.terminateCheckout("/",name,held.getCheckoutId(),false);
52
+ }
53
+ }
54
+ public static void main(String[] args){var out=System.out;System.setOut(System.err);
55
+ try{
56
+ Path root=Path.of(args[0]);String operation=args[1];boolean readOnly=operation.startsWith("verify");JsonObject p=InsightInitialJournal.intent(root);
57
+ try(var channel=FileChannel.open(root.resolve("lock"),StandardOpenOption.CREATE,StandardOpenOption.WRITE);var lock=channel.tryLock()){
58
+ if(lock==null){out.println("{\"state\":\"busy\"}");System.exit(0);}
59
+ JsonObject saved=InsightInitialJournal.prepared(root);Path snapshot=root.resolve(text(saved,"snapshot")),journalPath=root.resolve("publication.json");
60
+ JsonObject j=Files.exists(journalPath)?InsightInitialJournal.read(journalPath):null;
61
+ if(j!=null)binding(root,j,saved);
62
+ Application.initializeApplication(new GhidraApplicationLayout(),new HeadlessGhidraApplicationConfiguration());
63
+ String name=text(p,"target")+".bin",marker="insight target "+text(p,"target"),sha=text(p,"sha256");
64
+ var manager=new GhidraServerManager();manager.connect();var repo=manager.server().getRepository(text(p,"repository"));repo.connect();authority(repo,readOnly);
65
+ if(!repo.getUser().getName().equals(System.getenv("GHIDRA_SERVER_USER")))throw new SecurityException();
66
+ RepositoryAdapter observed=repo;
67
+ if(operation.equals("verify-stage")){
68
+ if(j==null||!j.has("stage"))throw new SecurityException();
69
+ verify(observed,text(j,"stage"),text(j,"fileId"),snapshot,sha,marker);
70
+ JsonObject proof=j.deepCopy();proof.addProperty("verified",true);InsightInitialJournal.write(root.resolve("stage-verified.json"),proof);
71
+ out.println("{\"state\":\"stage_verified\",\"version\":1,\"snapshotBuffersVerified\":true}");System.exit(0);
72
+ }
73
+ var canonical=observed.getItem("/",name);
74
+ if(canonical!=null){
75
+ if(j==null)throw new SecurityException("Canonical item has no retained publication intent");
76
+ String id=j.has("fileId")?text(j,"fileId"):text(p,"id");
77
+ if(!id.equals(canonical.getFileID()))throw new SecurityException("Foreign canonical initial model");
78
+ try{verify(observed,name,id,snapshot,sha,marker);}
79
+ catch(Exception invalid){
80
+ // Compatibility with an older interrupted direct create: preserve the
81
+ // proven-owned, uncommitted <Recovered> object under quarantine. No delete.
82
+ if(readOnly||j.has("stage")||!recovered(observed,name)||repo.isCheckinActive("/",name))throw invalid;
83
+ release(repo,name,j);repo.moveItem("/","/",name,"__initial_abandoned_"+UUID.randomUUID().toString().replace("-",""));canonical=null;
84
+ j.addProperty("phase","quarantined");InsightInitialJournal.write(root.resolve("legacy-quarantined.json"),j);
85
+ }
86
+ if(canonical!=null&&!readOnly)release(repo,name,j);
87
+ }
88
+ if(canonical==null){
89
+ if(readOnly){out.println("{\"state\":\"absent\"}");System.exit(0);}
90
+ if(repo.fileExists("/",name))throw new SecurityException("Canonical path is reserved");
91
+ if(operation.equals("stage")){
92
+ boolean staged=false;
93
+ if(j!=null&&j.has("stage")){
94
+ String stage=text(j,"stage");if(!stage.matches("__initial_[a-f0-9]{32}"))throw new SecurityException();
95
+ var item=observed.getItem("/",stage);
96
+ if(item!=null){
97
+ if(!item.getFileID().equals(text(j,"fileId")))throw new SecurityException("Foreign staged initial model");
98
+ try{verify(observed,stage,text(j,"fileId"),snapshot,sha,marker);staged=true;}
99
+ catch(Exception incomplete){
100
+ // Interrupted staging is unpublished. Keep its bytes and receipt;
101
+ // start another uniquely named stage instead of overwriting it.
102
+ if(!repo.isCheckinActive("/",stage))release(repo,stage,j);
103
+ }
104
+ }
105
+ }
106
+ if(!staged){
107
+ int calls=j!=null&&j.has("createCalls")?j.get("createCalls").getAsInt():0;
108
+ j=new JsonObject();j.addProperty("intentHash",InsightInitialJournal.hash(root.resolve("intent.json")));j.addProperty("snapshotSha256",text(saved,"snapshotSha256"));
109
+ j.addProperty("stage","__initial_"+UUID.randomUUID().toString().replace("-",""));j.addProperty("fileId",UUID.randomUUID().toString());
110
+ j.addProperty("pid",ProcessHandle.current().pid());j.addProperty("createCalls",calls+1);j.addProperty("phase","create_intent");
111
+ Path attempt=root.resolve("create-"+UUID.randomUUID()+".json");InsightInitialJournal.write(attempt,j);InsightInitialJournal.write(journalPath,j);
112
+ DBHandle expected=PackedDatabase.getPackedDatabase(snapshot.toFile(),true,TaskMonitor.DUMMY).open(TaskMonitor.DUMMY);
113
+ try{
114
+ var remote=repo.createDatabase("/",text(j,"stage"),expected.getBufferSize(),"Program",text(j,"fileId"),root.toString());
115
+ j.addProperty("checkoutId",remote.getCheckinID());j.addProperty("phase","created");InsightInitialJournal.write(attempt,j);InsightInitialJournal.write(journalPath,j);
116
+ try{remote.setVersionComment(marker);InsightBlockTransfer.copy((LocalBufferFile)expected.getBufferMgr().getSourceFile(),remote);if(!remote.setReadOnly())throw new IllegalStateException("Stage save not acknowledged");}
117
+ finally{remote.dispose();}
118
+ }finally{expected.close();}
119
+ j.addProperty("phase","staged");InsightInitialJournal.write(attempt,j);InsightInitialJournal.write(journalPath,j);
120
+ }
121
+ String stage=text(j,"stage");verify(observed,stage,text(j,"fileId"),snapshot,sha,marker);release(repo,stage,j);
122
+ if(repo.getCheckouts("/",stage).length!=0)throw new SecurityException("Stage retains foreign checkout");
123
+ out.println("{\"state\":\"staged\",\"version\":1,\"snapshotBuffersVerified\":true}");System.exit(0);
124
+ }
125
+ if(!operation.equals("promote")||j==null)throw new SecurityException();
126
+ JsonObject proof=InsightInitialJournal.read(root.resolve("stage-verified.json"));binding(root,proof,saved);
127
+ if(!proof.get("verified").getAsBoolean()||!text(proof,"fileId").equals(text(j,"fileId"))||!text(proof,"stage").equals(text(j,"stage")))throw new SecurityException("Independent stage proof changed");
128
+ String stage=text(j,"stage");verify(observed,stage,text(j,"fileId"),snapshot,sha,marker);
129
+ if(repo.getCheckouts("/",stage).length!=0)throw new SecurityException("Stage retains checkout");
130
+ j.addProperty("phase","promote_intent");InsightInitialJournal.write(journalPath,j);
131
+ // Ghidra's server move rejects an existing destination; never overwrite it.
132
+ try{repo.moveItem("/","/",stage,name);}catch(Exception lost){try{verify(observed,name,text(j,"fileId"),snapshot,sha,marker);}catch(Exception unproved){unproved.addSuppressed(lost);throw unproved;}}
133
+ j.addProperty("phase","committed");InsightInitialJournal.write(journalPath,j);
134
+ verify(observed,name,text(j,"fileId"),snapshot,sha,marker);
135
+ }
136
+ if(repo.getCheckouts("/",name).length!=0)throw new IllegalStateException("Initial checkout not released");
137
+ repo.disconnect();manager.server().disconnect();out.println(new Gson().toJson(Map.of("state",readOnly?"verified":"published","version",1,"snapshotBuffersVerified",true)));
138
+ }
139
+ System.exit(0);
140
+ }catch(Throwable failure){failure.printStackTrace(System.err);out.println("{\"state\":\"failed\"}");System.exit(1);}
141
+ }
142
+ }
@@ -0,0 +1,16 @@
1
+ import java.nio.file.*;
2
+ import java.util.*;
3
+
4
+ /** Fixed certificate preparation; the secret remains in the child environment. */
5
+ class InsightKeytool {
6
+ public static void main(String[] args) throws Exception {
7
+ if(args.length!=1 || System.getenv("INSIGHT_KEYSTORE_PASSWORD")==null) throw new IllegalArgumentException();
8
+ var executable=Path.of(System.getProperty("java.home"),"bin",System.getProperty("os.name").startsWith("Windows")?"keytool.exe":"keytool");
9
+ var command=new ArrayList<String>(List.of(executable.toString(),"-genkeypair","-alias","insight","-keyalg","RSA","-keysize","2048",
10
+ "-dname","CN=GhidraServer","-ext","SAN=IP:127.0.0.1","-ext","KU=digitalSignature,keyEncipherment","-validity","3650",
11
+ "-keystore",args[0],"-storetype","PKCS12","-storepass:env","INSIGHT_KEYSTORE_PASSWORD"));
12
+ var process=new ProcessBuilder(command).redirectError(ProcessBuilder.Redirect.INHERIT).redirectOutput(ProcessBuilder.Redirect.DISCARD).start();
13
+ int code=process.waitFor(); if(code!=0) System.exit(code);
14
+ System.out.println("{\"state\":\"ready\"}");
15
+ }
16
+ }
@@ -0,0 +1,31 @@
1
+ import com.google.gson.*;
2
+ import java.nio.file.*;
3
+ import java.nio.channels.FileChannel;
4
+ import java.security.MessageDigest;
5
+ import java.util.*;
6
+
7
+ /** Domain-effect receipt: close/sync native outputs before acknowledging a file/database result. */
8
+ class InsightNativeReceipt {
9
+ static void write(JsonObject request,Object value)throws Exception{
10
+ if(!request.has("receipt"))return;
11
+ Map<String,Object> result=new LinkedHashMap<>();result.put("request",request.get("request_id").getAsString());result.put("operation",request.get("operation").getAsString());result.put("value",value);
12
+ if(request.has("output")){
13
+ Path directory=Path.of(request.get("output").getAsString()).getParent();List<Object> files=new ArrayList<>();
14
+ try(var entries=Files.list(directory)){
15
+ for(Path file:entries.sorted().toList()){
16
+ if(files.size()>=64||!Files.isRegularFile(file,LinkOption.NOFOLLOW_LINKS))throw new IllegalArgumentException("Native output manifest requires at most 64 regular files");
17
+ long size=Files.size(file);if(size<1||size>4294967296L)throw new IllegalArgumentException("Native output exceeds artifact limits");
18
+ MessageDigest hash=MessageDigest.getInstance("SHA-256");try(var input=Files.newInputStream(file)){byte[] buffer=new byte[65536];for(int count;(count=input.read(buffer))>=0;)if(count>0)hash.update(buffer,0,count);}
19
+ try(FileChannel channel=FileChannel.open(file,StandardOpenOption.WRITE)){channel.force(true);}
20
+ files.add(Map.of("name",file.getFileName().toString(),"size",size,"sha256",HexFormat.of().formatHex(hash.digest())));
21
+ }
22
+ }
23
+ if(files.isEmpty())throw new IllegalStateException("Native output is empty");result.put("files",files);
24
+ }
25
+ Path receipt=Path.of(request.get("receipt").getAsString()),temporary=receipt.resolveSibling("receipt-"+UUID.randomUUID()+".new");
26
+ Files.writeString(temporary,new GsonBuilder().serializeNulls().create().toJson(result),StandardOpenOption.CREATE_NEW);
27
+ try(FileChannel channel=FileChannel.open(temporary,StandardOpenOption.WRITE)){channel.force(true);}
28
+ Files.move(temporary,receipt,StandardCopyOption.ATOMIC_MOVE);
29
+ if(!System.getProperty("os.name").toLowerCase().contains("win"))try(FileChannel channel=FileChannel.open(receipt.getParent(),StandardOpenOption.READ)){channel.force(true);}
30
+ }
31
+ }
@@ -0,0 +1,34 @@
1
+ import insight.InsightExtensions;
2
+ import com.google.gson.*;
3
+ import ghidra.app.script.GhidraScript;
4
+ import ghidra.framework.plugintool.*;
5
+ import ghidra.util.classfinder.ClassSearcher;
6
+ import java.util.*;
7
+ import javax.swing.SwingUtilities;
8
+
9
+ /** Enable/disable only plugins from explicitly consumed managed modules, in this GUI tool. */
10
+ public class InsightPlugins extends GhidraScript {
11
+ public void run() throws Exception {
12
+ JsonObject p=JsonParser.parseString(new String(Base64.getDecoder().decode(getScriptArgs()[0]),java.nio.charset.StandardCharsets.UTF_8)).getAsJsonObject();
13
+ var tool=state.getTool();if(tool==null)throw new IllegalStateException("Owned GUI required");
14
+ final Object[] value={null};final Exception[] failure={null};
15
+ SwingUtilities.invokeAndWait(()->{try{
16
+ String operation=p.get("operation").getAsString();
17
+ if(operation.equals("set")){
18
+ String name=p.get("plugin").getAsString();Class<? extends Plugin> type=null;
19
+ for(var c:ClassSearcher.getClasses(Plugin.class))if(c.getName().equals(name)&&InsightExtensions.managed(c)){type=c;break;}
20
+ if(type==null)throw new IllegalArgumentException("Choose a plugin from an enabled managed module");
21
+ Plugin existing=tool.getManagedPlugins().stream().filter(x->x.getClass().getName().equals(name)).findFirst().orElse(null);
22
+ boolean enabled=p.get("enabled").getAsBoolean();
23
+ if(enabled&&existing==null)tool.addPlugin(name);else if(!enabled&&existing!=null)tool.removePlugins(List.of(existing));
24
+ if(tool.getManagedPlugins().stream().anyMatch(x->x.getClass().getName().equals(name))!=enabled)throw new IllegalStateException("Plugin enablement readback differs");
25
+ value[0]=Map.of("plugin",name,"enabled",enabled,"lifetime","current-gui-tool");
26
+ }else if(operation.equals("list")){
27
+ List<Object> rows=new ArrayList<>();for(Plugin plugin:tool.getManagedPlugins())if(InsightExtensions.managed(plugin.getClass()))rows.add(Map.of("id",plugin.getClass().getName(),"name",plugin.getName(),"enabled",true));
28
+ rows.sort(Comparator.comparing(r->((Map<?,?>)r).get("id").toString()));
29
+ if(rows.size()>100)throw new IllegalStateException("Managed GUI plugin inventory exceeds 100 entries");value[0]=Map.of("items",rows,"lifetime","current-gui-tool");
30
+ }else throw new IllegalArgumentException();
31
+ }catch(Exception e){failure[0]=e;}});
32
+ if(failure[0]!=null)throw failure[0];println("INSIGHT_PLUGINS:"+new Gson().toJson(value[0]));
33
+ }
34
+ }
@@ -0,0 +1,92 @@
1
+ import com.google.gson.*;
2
+ import ghidra.GhidraApplicationLayout;
3
+ import ghidra.framework.Application;
4
+ import ghidra.framework.HeadlessGhidraApplicationConfiguration;
5
+ import ghidra.framework.model.*;
6
+ import ghidra.base.project.GhidraProject;
7
+ import ghidra.app.plugin.core.archive.ArchiveBridge;
8
+ import ghidra.util.task.TaskMonitor;
9
+ import java.nio.file.*;
10
+ import java.util.*;
11
+ import java.util.zip.*;
12
+ import java.nio.channels.FileChannel;
13
+ import java.security.MessageDigest;
14
+
15
+ /** Operations are confined to a facade-owned project, never a research worker project. */
16
+ class InsightProjectFiles {
17
+ static String string(JsonObject p,String key){return p.get(key).getAsString();}
18
+ static String path(JsonObject p,String key){String s=string(p,key);
19
+ if(!s.startsWith("/")||s.contains("\\")||s.contains("//")||Arrays.stream(s.split("/")).anyMatch(x->x.equals(".")||x.equals("..")))throw new IllegalArgumentException();return s;}
20
+ static DomainFolder folder(ProjectData data,String path){DomainFolder f=data.getFolder(path);if(f==null)throw new IllegalArgumentException("Folder not found");return f;}
21
+ static DomainFile file(ProjectData data,String path){DomainFile f=data.getFile(path);if(f==null)throw new IllegalArgumentException("File not found");return f;}
22
+ static Map<String,Object> describe(DomainFile f){return Map.of("name",f.getName(),"path",f.getPathname(),"type",f.getContentType());}
23
+ static String parent(String path){int at=path.lastIndexOf('/');return at==0?"/":path.substring(0,at);}
24
+ static String name(String path){String n=path.substring(path.lastIndexOf('/')+1);if(n.isEmpty()||n.startsWith("."))throw new IllegalArgumentException();return n;}
25
+ public static void main(String[] args){var out=System.out;System.setOut(System.err);GhidraProject project=null;
26
+ try{
27
+ if(args.length!=1)throw new IllegalArgumentException();
28
+ Path requestPath=Path.of(args[0]).toRealPath();String requestJson=Files.readString(requestPath);
29
+ JsonObject p=JsonParser.parseString(requestJson).getAsJsonObject();
30
+ Path root=Path.of(string(p,"root")).toRealPath();
31
+ JsonObject owner=JsonParser.parseString(Files.readString(root.resolve("owner.json"))).getAsJsonObject();
32
+ if(!string(owner,"project").equals(string(p,"project"))||!string(owner,"profile").equals(string(p,"profile")))throw new IllegalArgumentException("Ownership mismatch");
33
+ Application.initializeApplication(new GhidraApplicationLayout(),new HeadlessGhidraApplicationConfiguration());
34
+ String op=string(p,"operation");Map<String,Object> result=new LinkedHashMap<>();result.put("project",string(p,"project"));
35
+ if(op.equals("restore")){
36
+ try(ZipFile zip=new ZipFile(string(p,"input"))){int count=0;long total=0;var entries=zip.entries();
37
+ while(entries.hasMoreElements()){ZipEntry entry=entries.nextElement();String n=entry.getName();Path relative=Path.of(n);
38
+ if(++count>100000||relative.isAbsolute()||n.contains("\\")||Arrays.stream(n.split("/")).anyMatch(x->x.equals(".."))||!root.resolve(relative).normalize().startsWith(root))throw new IllegalArgumentException("Archive entry escapes owned project");
39
+ if(entry.getSize()<0||entry.getSize()>4294967296L||(total+=entry.getSize())>4294967296L)throw new IllegalArgumentException("Archive exceeds restore limit");
40
+ }
41
+ }
42
+ ProjectLocator locator=new ProjectLocator(root.toString(),"project");
43
+ if(locator.getProjectDir().exists()||locator.getMarkerFile().exists())throw new IllegalArgumentException("Destination exists");
44
+ ArchiveBridge.restore(Path.of(string(p,"input")).toFile(),locator,TaskMonitor.DUMMY);
45
+ }
46
+ project=op.equals("create")?GhidraProject.createProject(root.toString(),"project",false):GhidraProject.openProject(root.toString(),"project",false);
47
+ ProjectData data=project.getProjectData();
48
+ if(op.equals("create")||op.equals("restore")||op.equals("open")||op.equals("close"))result.put("state",op.equals("close")?"closed":"ready");
49
+ else if(op.equals("info"))result.put("state","ready");
50
+ else if(op.equals("files")){
51
+ String selected=path(p,"path");DomainFolder f=folder(data,selected);
52
+ List<Map<String,Object>> entries=new ArrayList<>();
53
+ for(DomainFolder child:f.getFolders())entries.add(Map.of("name",child.getName(),"path",child.getPathname(),"type","Folder"));
54
+ for(DomainFile child:f.getFiles())entries.add(describe(child));
55
+ entries.sort(Comparator.comparing(row->row.get("path").toString()));
56
+ int offset=p.get("offset").getAsInt(),limit=p.get("limit").getAsInt();
57
+ result.put("path",selected);result.put("total",entries.size());result.put("offset",offset);result.put("limit",limit);
58
+ result.put("items",entries.subList(Math.min(offset,entries.size()),Math.min(entries.size(),offset+limit)));
59
+ }else if(op.equals("create_folder")){
60
+ String selected=path(p,"path");DomainFolder f=folder(data,parent(selected)).createFolder(name(selected));
61
+ if(data.getFolder(selected)==null)throw new IllegalStateException();result.put("path",f.getPathname());result.put("state","created");
62
+ }else if(op.equals("move_file")||op.equals("move_folder")){
63
+ String source=path(p,"path"),destination=path(p,"destination");DomainFolder to=folder(data,destination);
64
+ if(op.equals("move_file")){DomainFile f=file(data,source);String expected=destination.equals("/")?"/"+f.getName():destination+"/"+f.getName();
65
+ f=f.moveTo(to);if(data.getFile(source)!=null||data.getFile(expected)==null)throw new IllegalStateException();result.put("path",f.getPathname());}
66
+ else{if(source.equals("/"))throw new IllegalArgumentException();DomainFolder f=folder(data,source);if(f.isSameOrAncestor(to))throw new IllegalArgumentException();
67
+ String expected=destination.equals("/")?"/"+f.getName():destination+"/"+f.getName();f=f.moveTo(to);
68
+ if(data.getFolder(source)!=null||data.getFolder(expected)==null)throw new IllegalStateException();result.put("path",f.getPathname());}
69
+ result.put("state","moved");
70
+ }else if(op.equals("delete_file")){
71
+ String selected=path(p,"path");file(data,selected).delete();if(data.getFile(selected)!=null)throw new IllegalStateException();result.put("path",selected);result.put("state","deleted");
72
+ }else if(op.equals("import")){
73
+ String selected=path(p,"path");DomainFile imported=folder(data,parent(selected)).createFile(name(selected),Path.of(string(p,"input")).toFile(),TaskMonitor.DUMMY);
74
+ if(!imported.getPathname().equals(selected)||data.getFile(selected)==null)throw new IllegalStateException();result.put("file",describe(imported));result.put("state","imported");
75
+ }else if(op.equals("archive")){
76
+ Path output=Path.of(string(p,"output"));if(Files.exists(output))throw new IllegalArgumentException("Output exists");
77
+ ArchiveBridge.archive(project.getProject(),output.toFile(),TaskMonitor.DUMMY);
78
+ if(!Files.isRegularFile(output)||Files.size(output)==0)throw new IllegalStateException();result.put("state","archived");result.put("size",Files.size(output));
79
+ }else if(op.equals("delete")){project.setDeleteOnClose(true);project.close();project=null;
80
+ if(Files.exists(root.resolve("project.gpr"))||Files.exists(root.resolve("project.rep")))throw new IllegalStateException();result.put("state","deleted");}
81
+ else throw new IllegalArgumentException("Unknown project operation");
82
+ if(project!=null){project.close();project=null;}
83
+ Path journal=requestPath.getParent().resolve("result.json"),temporary=requestPath.getParent().resolve("result.new");
84
+ String hash=HexFormat.of().formatHex(MessageDigest.getInstance("SHA-256").digest(requestJson.getBytes(java.nio.charset.StandardCharsets.UTF_8)));
85
+ Files.writeString(temporary,new Gson().toJson(Map.of("schema",1,"request",string(p,"request_id"),"hash",hash,"result",result)),StandardOpenOption.CREATE_NEW);
86
+ try(FileChannel channel=FileChannel.open(temporary,StandardOpenOption.WRITE)){channel.force(true);}
87
+ Files.move(temporary,journal,StandardCopyOption.ATOMIC_MOVE);
88
+ if(!System.getProperty("os.name").toLowerCase().contains("win"))try(FileChannel channel=FileChannel.open(requestPath.getParent(),StandardOpenOption.READ)){channel.force(true);}
89
+ out.println(new Gson().toJson(result));System.exit(0);
90
+ }catch(Throwable failure){if(project!=null)project.close();failure.printStackTrace(System.err);out.println("{\"error\":\"project_operation_failed\"}");System.exit(1);}
91
+ }
92
+ }
@@ -0,0 +1,63 @@
1
+ package db;
2
+
3
+ import com.google.gson.*;
4
+ import com.xebyte.headless.GhidraServerManager;
5
+ import db.buffers.InsightBlockTransfer;
6
+ import ghidra.GhidraApplicationLayout;
7
+ import ghidra.framework.Application;
8
+ import ghidra.framework.HeadlessGhidraApplicationConfiguration;
9
+ import ghidra.framework.data.OpenMode;
10
+ import ghidra.framework.store.db.PackedDatabase;
11
+ import ghidra.program.database.ProgramDB;
12
+ import ghidra.util.task.TaskMonitor;
13
+ import java.nio.file.*;
14
+ import java.util.*;
15
+
16
+ /** Verify an immutable exact version; release only the receipt-identified owned checkout. */
17
+ class InsightPublicationRecovery {
18
+ public static void main(String[] args) {
19
+ var output=System.out;System.setOut(System.err);
20
+ try {
21
+ if(args.length!=7)throw new IllegalArgumentException();
22
+ String repo=args[0],path=args[1],marker=args[4],sha=args[6];int expected=Integer.parseInt(args[3]);
23
+ if(!path.startsWith("/")||path.indexOf('/',1)>=0||!marker.matches("insight merge q_[a-f0-9-]{36}"))throw new IllegalArgumentException();
24
+ Application.initializeApplication(new GhidraApplicationLayout(),new HeadlessGhidraApplicationConfiguration());
25
+ var reader=new GhidraServerManager();reader.connect();var repository=reader.server().getRepository(repo);repository.connect();
26
+ var user=repository.getUser();if(user==null||!user.isReadOnly()||!user.getName().equals(System.getenv("GHIDRA_SERVER_USER")))throw new SecurityException();
27
+ String folder="/",name=path.substring(1);
28
+ var history=repository.getVersions(folder,name);var matches=Arrays.stream(history).filter(item->marker.equals(item.getComment())).toList();
29
+ if(matches.size()!=1||matches.getFirst().getVersion()!=expected+1){output.println("{\"state\":\"unknown\"}");System.exit(0);}
30
+ int actual=matches.getFirst().getVersion();
31
+ DBHandle snapshot=PackedDatabase.getPackedDatabase(Path.of(args[2]).toFile(),true,TaskMonitor.DUMMY).open(TaskMonitor.DUMMY);
32
+ DBHandle observed=new DBHandle(repository.openDatabase(folder,name,actual,-1));
33
+ try {
34
+ if(snapshot.getDatabaseId()!=observed.getDatabaseId()||!InsightBlockTransfer.fingerprint(snapshot.getBufferMgr().getSourceFile()).equals(
35
+ InsightBlockTransfer.fingerprint(observed.getBufferMgr().getSourceFile())))throw new IllegalStateException("Exact published buffers differ");
36
+ Object consumer=new Object();ProgramDB program=new ProgramDB(observed,OpenMode.IMMUTABLE,TaskMonitor.DUMMY,consumer);
37
+ try{if(!sha.equals(program.getExecutableSHA256()))throw new SecurityException("Published source identity changed");}
38
+ finally{program.release(consumer);}
39
+ }finally{snapshot.close();observed.close();}
40
+ repository.disconnect();reader.server().disconnect();
41
+ JsonObject intent=JsonParser.parseString(Files.readString(Path.of(args[5]))).getAsJsonObject();
42
+ if(!repo.equals(intent.get("repository").getAsString())||!path.equals(intent.get("path").getAsString())||
43
+ !marker.equals(intent.get("marker").getAsString())||intent.get("baseline").getAsInt()!=expected)throw new SecurityException();
44
+ long checkout=intent.get("checkoutId").getAsLong();
45
+ var writer=new GhidraServerManager(System.getenv("GHIDRA_SERVER_HOST"),Integer.parseInt(System.getenv("GHIDRA_SERVER_PORT")),
46
+ System.getenv("INSIGHT_PUBLISHER_USER"),System.getenv("INSIGHT_PUBLISHER_PASSWORD"));
47
+ writer.connect();var owned=writer.server().getRepository(repo);owned.connect();
48
+ var authority=owned.getUser();if(authority==null||!authority.hasWritePermission()||!authority.getName().equals(System.getenv("INSIGHT_PUBLISHER_USER")))throw new SecurityException();
49
+ var held=Arrays.stream(owned.getCheckouts(folder,name)).filter(item->item.getCheckoutId()==checkout).toList();
50
+ if(held.size()>1)throw new IllegalStateException();
51
+ if(!held.isEmpty()) {
52
+ var item=held.getFirst();
53
+ if(!authority.getName().equals(item.getUser())||item.getCheckoutVersion()!=expected||owned.isCheckinActive(folder,name))
54
+ throw new IllegalStateException("Owned checkout cannot be safely released yet");
55
+ owned.terminateCheckout(folder,name,checkout,false);
56
+ }
57
+ if(Arrays.stream(owned.getCheckouts(folder,name)).anyMatch(item->item.getCheckoutId()==checkout))throw new IllegalStateException();
58
+ owned.disconnect();writer.server().disconnect();
59
+ output.println(new Gson().toJson(Map.of("state","verified","version",actual,"snapshotBuffersVerified",true,"ownedCheckoutReleased",true)));
60
+ System.exit(0);
61
+ }catch(Throwable failure){failure.printStackTrace(System.err);output.println("{\"state\":\"unknown\"}");System.exit(0);}
62
+ }
63
+ }
@@ -0,0 +1,31 @@
1
+ import java.nio.file.*;
2
+ import java.util.zip.*;
3
+ import java.util.*;
4
+
5
+ /** Extract only the verified SDK's pure Python wheels into a fresh owned directory. */
6
+ class InsightPythonWheels {
7
+ public static void main(String[] args)throws Exception{
8
+ if(args.length!=4)throw new IllegalArgumentException();Path root=Path.of(args[0]).toRealPath();int count=0;long total=0;
9
+ for(int i=1;i<args.length;i++)try(ZipFile zip=new ZipFile(args[i])){
10
+ var entries=zip.entries();while(entries.hasMoreElements()){
11
+ ZipEntry entry=entries.nextElement();String name=entry.getName();Path relative=Path.of(name),output=root.resolve(relative).normalize();
12
+ if(++count>10000||relative.isAbsolute()||name.contains("\\")||Arrays.stream(name.split("/")).anyMatch(x->x.equals(".."))||!output.startsWith(root))throw new IllegalArgumentException();
13
+ if(entry.isDirectory()){Files.createDirectories(output);continue;}
14
+ if(entry.getSize()<0||entry.getSize()>16777216||(total+=entry.getSize())>67108864)throw new IllegalArgumentException();
15
+ Files.createDirectories(output.getParent());try(var in=zip.getInputStream(entry);var out=Files.newOutputStream(output,StandardOpenOption.CREATE_NEW)){
16
+ byte[] buffer=new byte[65536];long actual=0;int length;
17
+ while((length=in.read(buffer))!=-1){actual+=length;if(actual>entry.getSize())throw new IllegalArgumentException();out.write(buffer,0,length);}
18
+ if(actual!=entry.getSize())throw new IllegalArgumentException();
19
+ }
20
+ }
21
+ }
22
+ // Ghidra 12.1.2's LLDB adapter calls a nonexistent SBProcess.write_memory method.
23
+ // Adapt only the newly extracted owned copy; refuse a different upstream implementation.
24
+ Path methods=root.resolve("ghidralldb/methods.py");String source=Files.readString(methods);
25
+ String original=" proc.write_memory(offset, data)";
26
+ if(source.indexOf(original)<0||source.indexOf(original)!=source.lastIndexOf(original))throw new IllegalArgumentException("Unexpected pinned LLDB memory writer");
27
+ String replacement=" error = lldb.SBError()\n written = proc.WriteMemory(offset, data, error)\n if error.Fail() or written != len(data):\n raise RuntimeError(\"LLDB memory write failed: \" + error.GetCString())";
28
+ Files.writeString(methods,source.replace(original,replacement),StandardOpenOption.TRUNCATE_EXISTING);
29
+ System.out.println("{\"state\":\"prepared\",\"lldb_memory_adapter\":\"insight-sbprocess-v1\"}");
30
+ }
31
+ }