- Path traversal: CheckPath now requires separator after prefix match - Store namespace: block reserved '_' prefixed groups - StoreGet: distinguish ErrNotFound from real DB errors via sentinel - Store: add rows.Err() checks in GetAll and Render - gRPC leak: cleanupGRPC on all early-return error paths in OnStartup - DenoClient: fix fmt.Sprint(nil) → type assertions - Socket permissions: 0700 dirs, 0600 sockets (owner-only) - Marketplace: persist SignKey, re-verify manifest on Update - io/local: resolve symlinks in New() (macOS /var → /private/var) - Tests: fix sun_path length overflow on macOS Co-Authored-By: Virgil <virgil@lethean.io>
138 lines
3.3 KiB
Go
138 lines
3.3 KiB
Go
package coredeno
|
|
|
|
import (
|
|
"bufio"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net"
|
|
"sync"
|
|
)
|
|
|
|
// DenoClient communicates with the Deno sidecar's JSON-RPC server over a Unix socket.
|
|
// Thread-safe: uses a mutex to serialize requests (one connection, request/response protocol).
|
|
type DenoClient struct {
|
|
mu sync.Mutex
|
|
conn net.Conn
|
|
reader *bufio.Reader
|
|
}
|
|
|
|
// DialDeno connects to the Deno JSON-RPC server on the given Unix socket path.
|
|
func DialDeno(socketPath string) (*DenoClient, error) {
|
|
conn, err := net.Dial("unix", socketPath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("deno dial: %w", err)
|
|
}
|
|
return &DenoClient{
|
|
conn: conn,
|
|
reader: bufio.NewReader(conn),
|
|
}, nil
|
|
}
|
|
|
|
// Close closes the underlying connection.
|
|
func (c *DenoClient) Close() error {
|
|
return c.conn.Close()
|
|
}
|
|
|
|
func (c *DenoClient) call(req map[string]any) (map[string]any, error) {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
|
|
data, err := json.Marshal(req)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("marshal: %w", err)
|
|
}
|
|
data = append(data, '\n')
|
|
|
|
if _, err := c.conn.Write(data); err != nil {
|
|
return nil, fmt.Errorf("write: %w", err)
|
|
}
|
|
|
|
line, err := c.reader.ReadBytes('\n')
|
|
if err != nil {
|
|
return nil, fmt.Errorf("read: %w", err)
|
|
}
|
|
|
|
var resp map[string]any
|
|
if err := json.Unmarshal(line, &resp); err != nil {
|
|
return nil, fmt.Errorf("unmarshal: %w", err)
|
|
}
|
|
|
|
if errMsg, ok := resp["error"].(string); ok && errMsg != "" {
|
|
return nil, fmt.Errorf("deno: %s", errMsg)
|
|
}
|
|
return resp, nil
|
|
}
|
|
|
|
// ModulePermissions declares per-module permission scopes for Deno Worker sandboxing.
|
|
type ModulePermissions struct {
|
|
Read []string `json:"read,omitempty"`
|
|
Write []string `json:"write,omitempty"`
|
|
Net []string `json:"net,omitempty"`
|
|
Run []string `json:"run,omitempty"`
|
|
}
|
|
|
|
// LoadModuleResponse holds the result of a LoadModule call.
|
|
type LoadModuleResponse struct {
|
|
Ok bool
|
|
Error string
|
|
}
|
|
|
|
// LoadModule tells Deno to load a module with the given permissions.
|
|
func (c *DenoClient) LoadModule(code, entryPoint string, perms ModulePermissions) (*LoadModuleResponse, error) {
|
|
resp, err := c.call(map[string]any{
|
|
"method": "LoadModule",
|
|
"code": code,
|
|
"entry_point": entryPoint,
|
|
"permissions": perms,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
errStr, _ := resp["error"].(string)
|
|
return &LoadModuleResponse{
|
|
Ok: resp["ok"] == true,
|
|
Error: errStr,
|
|
}, nil
|
|
}
|
|
|
|
// UnloadModuleResponse holds the result of an UnloadModule call.
|
|
type UnloadModuleResponse struct {
|
|
Ok bool
|
|
}
|
|
|
|
// UnloadModule tells Deno to unload a module.
|
|
func (c *DenoClient) UnloadModule(code string) (*UnloadModuleResponse, error) {
|
|
resp, err := c.call(map[string]any{
|
|
"method": "UnloadModule",
|
|
"code": code,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &UnloadModuleResponse{
|
|
Ok: resp["ok"] == true,
|
|
}, nil
|
|
}
|
|
|
|
// ModuleStatusResponse holds the result of a ModuleStatus call.
|
|
type ModuleStatusResponse struct {
|
|
Code string
|
|
Status string
|
|
}
|
|
|
|
// ModuleStatus queries the status of a module in the Deno runtime.
|
|
func (c *DenoClient) ModuleStatus(code string) (*ModuleStatusResponse, error) {
|
|
resp, err := c.call(map[string]any{
|
|
"method": "ModuleStatus",
|
|
"code": code,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
respCode, _ := resp["code"].(string)
|
|
sts, _ := resp["status"].(string)
|
|
return &ModuleStatusResponse{
|
|
Code: respCode,
|
|
Status: sts,
|
|
}, nil
|
|
}
|