@securecoms/agent-bridge 0.37.0 → 0.38.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 +16 -0
- package/connectors/hermes/__pycache__/securecoms_gate.cpython-310.pyc +0 -0
- package/connectors/hermes/__pycache__/securecoms_transport.cpython-310.pyc +0 -0
- package/connectors/hermes/adapter.py +424 -32
- package/connectors/hermes/securecoms_transport.py +97 -1
- package/connectors/hermes/tests/test_adapter_wiring.py +887 -0
- package/connectors/hermes/tests/test_securecoms_transport.py +127 -0
- package/connectors/openclaw/openclaw-entry.contract.test.ts +96 -0
- package/connectors/openclaw/openclaw-entry.inbound.test.ts +228 -1
- package/connectors/openclaw/openclaw-entry.js +128 -3
- package/dist/attachments.d.ts +33 -1
- package/dist/bin.js +4415 -1014
- package/dist/bridge-agent.d.ts +65 -0
- package/dist/index.js +4387 -986
- package/dist/version.d.ts +1 -1
- package/package.json +1 -1
|
@@ -26,7 +26,9 @@ from __future__ import annotations
|
|
|
26
26
|
|
|
27
27
|
import json
|
|
28
28
|
import os
|
|
29
|
-
|
|
29
|
+
import re
|
|
30
|
+
import stat
|
|
31
|
+
from typing import Awaitable, Callable, List, Mapping, NamedTuple, Optional, Tuple
|
|
30
32
|
|
|
31
33
|
from securecoms_gate import evaluate_gate, resolve_bound_identity
|
|
32
34
|
|
|
@@ -196,6 +198,100 @@ def should_drop_inbound(frame: dict, bound_identity: str) -> bool:
|
|
|
196
198
|
return bool(sender_id) and bool(bound_identity) and sender_id == bound_identity
|
|
197
199
|
|
|
198
200
|
|
|
201
|
+
# Inbound attachments. For an artifact_share the daemon decrypts the file to
|
|
202
|
+
# <attachmentsDir>/<channel_id>/<message_id>-<name> and puts
|
|
203
|
+
# {filename, mime, size, path, sha256} on the frame. Python twin of
|
|
204
|
+
# openclaw-entry.js's scanInboundAttachments. The frame's fields are claims;
|
|
205
|
+
# only a file that passes ALL of these reaches Hermes:
|
|
206
|
+
# - path: a string, absolute and already normalized (normpath collapses any
|
|
207
|
+
# "." or ".." segment and a trailing "/", so any traversal or an empty
|
|
208
|
+
# basename fails the equality), no C0/C1 control or bidi character,
|
|
209
|
+
# parent dir == the frame's channel id inside a dir named "attachments",
|
|
210
|
+
# every kept file in one dir;
|
|
211
|
+
# - filename (when present): no control or bidi character;
|
|
212
|
+
# - the file itself (opened O_NOFOLLOW|O_NONBLOCK): a regular file of at most
|
|
213
|
+
# MAX_INBOUND_ATTACHMENT_BYTES whose first bytes are a PNG, JPEG, GIF or
|
|
214
|
+
# WEBP signature, and the claimed mime equals that sniffed type.
|
|
215
|
+
# Images only. Anything else is dropped; at most MAX_INBOUND_ATTACHMENTS kept.
|
|
216
|
+
MAX_INBOUND_ATTACHMENTS = 8
|
|
217
|
+
MAX_INBOUND_ATTACHMENT_BYTES = 5 * 1024 * 1024
|
|
218
|
+
_FORBIDDEN_CHARS = re.compile("[\u0000-\u001f\u007f-\u009f\u202a-\u202e\u2066-\u2069]")
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def sniff_image_mime(b: bytes) -> Optional[str]:
|
|
222
|
+
"""The image type from a file's leading bytes, or None."""
|
|
223
|
+
if b.startswith(b"\x89PNG\r\n\x1a\n"):
|
|
224
|
+
return "image/png"
|
|
225
|
+
if b.startswith(b"\xff\xd8\xff"):
|
|
226
|
+
return "image/jpeg"
|
|
227
|
+
if b.startswith(b"GIF87a") or b.startswith(b"GIF89a"):
|
|
228
|
+
return "image/gif"
|
|
229
|
+
if len(b) >= 12 and b[:4] == b"RIFF" and b[8:12] == b"WEBP":
|
|
230
|
+
return "image/webp"
|
|
231
|
+
return None
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def _sniff_file(path: str) -> Optional[str]:
|
|
235
|
+
try:
|
|
236
|
+
# O_NONBLOCK: a FIFO planted at the path must not hang the gateway in
|
|
237
|
+
# open(); fstat below then drops it as not a regular file.
|
|
238
|
+
fd = os.open(path, os.O_RDONLY | getattr(os, "O_NOFOLLOW", 0) | getattr(os, "O_NONBLOCK", 0))
|
|
239
|
+
except OSError:
|
|
240
|
+
return None
|
|
241
|
+
try:
|
|
242
|
+
st = os.fstat(fd)
|
|
243
|
+
if not stat.S_ISREG(st.st_mode) or st.st_size > MAX_INBOUND_ATTACHMENT_BYTES:
|
|
244
|
+
return None
|
|
245
|
+
return sniff_image_mime(os.pread(fd, 12, 0))
|
|
246
|
+
except OSError:
|
|
247
|
+
return None
|
|
248
|
+
finally:
|
|
249
|
+
os.close(fd)
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def scan_inbound_attachments(frame: dict) -> Tuple[List[Tuple[str, str]], int]:
|
|
253
|
+
"""(kept (path, sniffed mime) pairs, dropped count) for a daemon frame."""
|
|
254
|
+
raw = frame.get("attachments") if isinstance(frame, dict) else None
|
|
255
|
+
channel_id = frame.get("channel_id") if isinstance(frame, dict) else None
|
|
256
|
+
if not isinstance(raw, list):
|
|
257
|
+
return [], 0
|
|
258
|
+
if not isinstance(channel_id, str) or not channel_id:
|
|
259
|
+
return [], len(raw)
|
|
260
|
+
kept: List[Tuple[str, str]] = []
|
|
261
|
+
first_dir: Optional[str] = None
|
|
262
|
+
for a in raw:
|
|
263
|
+
if len(kept) >= MAX_INBOUND_ATTACHMENTS:
|
|
264
|
+
break
|
|
265
|
+
if not isinstance(a, dict):
|
|
266
|
+
continue
|
|
267
|
+
path, mime, filename = a.get("path"), a.get("mime"), a.get("filename")
|
|
268
|
+
if not isinstance(path, str) or not isinstance(mime, str):
|
|
269
|
+
continue
|
|
270
|
+
if filename is not None and (not isinstance(filename, str) or _FORBIDDEN_CHARS.search(filename)):
|
|
271
|
+
continue
|
|
272
|
+
if _FORBIDDEN_CHARS.search(path) or not os.path.isabs(path) or os.path.normpath(path) != path:
|
|
273
|
+
continue
|
|
274
|
+
parent = os.path.dirname(path)
|
|
275
|
+
if not os.path.basename(path) or os.path.basename(parent) != channel_id:
|
|
276
|
+
continue
|
|
277
|
+
if os.path.basename(os.path.dirname(parent)) != "attachments":
|
|
278
|
+
continue
|
|
279
|
+
if first_dir is not None and parent != first_dir:
|
|
280
|
+
continue
|
|
281
|
+
sniffed = _sniff_file(path)
|
|
282
|
+
if sniffed is None or sniffed != mime.lower():
|
|
283
|
+
continue
|
|
284
|
+
if first_dir is None:
|
|
285
|
+
first_dir = parent
|
|
286
|
+
kept.append((path, sniffed))
|
|
287
|
+
return kept, len(raw) - len(kept)
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def inbound_attachments(frame: dict) -> List[Tuple[str, str]]:
|
|
291
|
+
"""Validated (path, sniffed mime) pairs from a daemon `message` frame."""
|
|
292
|
+
return scan_inbound_attachments(frame)[0]
|
|
293
|
+
|
|
294
|
+
|
|
199
295
|
class ChannelStatusManager:
|
|
200
296
|
"""Per-channel working/idle status emitter, decoupled from any one reply
|
|
201
297
|
turn (unlike openclaw-entry.js's inline reportStatus("working")/("idle")
|