@tencent-ai/agent-sdk 0.3.206 → 0.3.209
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/cli/CHANGELOG.md +53 -0
- package/cli/builtin/workflows/deep-research.workflow.js +77 -20
- package/cli/dist/codebuddy-headless.js +293 -245
- package/cli/dist/web-ui/assets/index-CH6e4vvI.css +32 -0
- package/cli/dist/web-ui/assets/index-GcU4pZms.js +1102 -0
- package/cli/dist/web-ui/index.html +2 -2
- package/cli/dist/web-ui/sw.js +1 -1
- package/cli/package.json +4 -4
- package/cli/product.cloudhosted.json +2 -2
- package/cli/product.internal.json +2 -2
- package/cli/product.ioa.json +2 -2
- package/cli/product.json +3 -3
- package/cli/product.selfhosted.json +2 -2
- package/cli/vendor/native-builds.json +18 -0
- package/cli/vendor/shim/broker-ipc-client.cjs +158 -0
- package/cli/vendor/shim/brokered-bin/codebuddy-toybox-dispatch +381 -0
- package/cli/vendor/shim/brokered-sandbox-bash-env.sh +10 -0
- package/cli/vendor/shim/genie-safe-delete.cjs +4 -781
- package/cli/vendor/shim/native/binding.gyp +13 -0
- package/cli/vendor/shim/native/brokered_sandbox_native.c +293 -0
- package/cli/vendor/shim/native/darwin-arm64/brokered-sandbox-native.node +0 -0
- package/cli/vendor/shim/native/darwin-x64/brokered-sandbox-native.node +0 -0
- package/cli/vendor/shim/node-brokered-fs-shim.cjs +932 -0
- package/cli/vendor/shim/node-language-shim.cjs +35 -0
- package/cli/vendor/shim/node-safe-delete-shim.cjs +810 -0
- package/cli/vendor/shim/safe-bin/safe-delete-bash-env.sh +1 -1
- package/cli/vendor/shim/safe-bin/safe-delete-common.sh +95 -34
- package/cli/vendor/shim/safe-delete-broker-delete.cjs +166 -0
- package/cli/vendor/shim/shell-runtime-bash-env.sh +8 -0
- package/cli/vendor/shim/sitecustomize.py +551 -22
- package/cli/vendor/toybox-macos/LICENSE +12 -0
- package/cli/vendor/toybox-macos/toybox +0 -0
- package/cli/vendor/toybox-macos/toybox.sb +19 -0
- package/cli/vendor/zsh-macos/LICENSE +37 -0
- package/cli/vendor/zsh-macos/bin/zsh +0 -0
- package/package.json +1 -1
- package/cli/dist/web-ui/assets/index-BZjhFKbY.js +0 -1051
- package/cli/dist/web-ui/assets/index-C5EhSt3C.css +0 -32
|
@@ -13,8 +13,11 @@ genie-safe-delete — Python 运行时安全删除 shim
|
|
|
13
13
|
"""
|
|
14
14
|
|
|
15
15
|
import errno
|
|
16
|
+
import builtins
|
|
17
|
+
import io
|
|
16
18
|
import json
|
|
17
19
|
import os
|
|
20
|
+
import socket
|
|
18
21
|
import stat
|
|
19
22
|
import sys
|
|
20
23
|
import tempfile
|
|
@@ -29,6 +32,11 @@ _SESSION_ID = (
|
|
|
29
32
|
or os.environ.get("CLAUDE_SESSION_ID")
|
|
30
33
|
)
|
|
31
34
|
_IN_SANDBOX = os.environ.get("CODEBUDDY_SAFE_DELETE_SANDBOX") == "1"
|
|
35
|
+
_SAFE_DELETE_ENABLED = os.environ.get("CODEBUDDY_SAFE_DELETE_ENABLED") != "0"
|
|
36
|
+
_BROKERED_FS_HOOK_ENABLED = (
|
|
37
|
+
os.environ.get("CODEBUDDY_BROKERED_FS_HOOK_ENABLED") == "1"
|
|
38
|
+
or _IN_SANDBOX
|
|
39
|
+
)
|
|
32
40
|
|
|
33
41
|
if _SESSION_ID:
|
|
34
42
|
# __file__ 在 sitecustomize 加载时指向本文件路径,dirname 即 vendor/shim/
|
|
@@ -268,14 +276,484 @@ if _SESSION_ID:
|
|
|
268
276
|
import pathlib
|
|
269
277
|
import platform
|
|
270
278
|
|
|
279
|
+
_orig_builtin_open = builtins.open
|
|
280
|
+
_orig_io_open = io.open
|
|
281
|
+
_orig_os_open = os.open
|
|
271
282
|
_orig_remove = os.remove
|
|
272
283
|
_orig_unlink = os.unlink
|
|
273
284
|
_orig_rmdir = os.rmdir
|
|
285
|
+
_orig_mkdir = os.mkdir
|
|
286
|
+
_orig_makedirs = os.makedirs
|
|
287
|
+
_orig_rename = os.rename
|
|
288
|
+
_orig_replace = os.replace
|
|
289
|
+
_orig_chmod = os.chmod
|
|
290
|
+
_orig_link = os.link
|
|
291
|
+
_orig_symlink = os.symlink
|
|
274
292
|
_orig_shutil_rmtree = shutil.rmtree
|
|
293
|
+
_orig_shutil_copyfile = shutil.copyfile
|
|
294
|
+
_orig_shutil_copy2 = shutil.copy2
|
|
295
|
+
_orig_shutil_copytree = shutil.copytree
|
|
275
296
|
_orig_path_unlink = pathlib.Path.unlink
|
|
276
297
|
_orig_path_rmdir = pathlib.Path.rmdir
|
|
298
|
+
_orig_path_mkdir = pathlib.Path.mkdir
|
|
299
|
+
_orig_path_touch = pathlib.Path.touch
|
|
277
300
|
_report_path = os.environ.get("CODEBUDDY_SAFE_DELETE_REPORT_PATH")
|
|
278
301
|
|
|
302
|
+
# -----------------------------------------------------------------------
|
|
303
|
+
# Brokered sandbox file-token support.
|
|
304
|
+
#
|
|
305
|
+
# Mirrors the shell/toybox broker model, but the interception point is
|
|
306
|
+
# Python's runtime file APIs. The host still owns policy and token issuing.
|
|
307
|
+
# -----------------------------------------------------------------------
|
|
308
|
+
|
|
309
|
+
_broker_socket_path = os.environ.get("CODEBUDDY_SANDBOX_BROKER_IPC_ADDRESS")
|
|
310
|
+
_broker_session_id = os.environ.get("CODEBUDDY_SANDBOX_BROKER_SESSION_ID") or _SESSION_ID
|
|
311
|
+
_broker_tool_call_id = (
|
|
312
|
+
os.environ.get("CODEBUDDY_SANDBOX_BROKER_TOOL_CALL_ID")
|
|
313
|
+
or os.environ.get("CODEBUDDY_TOOL_CALL_ID")
|
|
314
|
+
)
|
|
315
|
+
_broker_trace_id = os.environ.get("CODEBUDDY_SANDBOX_BROKER_TRACE_ID")
|
|
316
|
+
_broker_file_token_command = (
|
|
317
|
+
os.environ.get("CODEBUDDY_SANDBOX_FILE_TOKEN_OPERATION_COMMAND")
|
|
318
|
+
or "FileTokenRequest"
|
|
319
|
+
)
|
|
320
|
+
_broker_host_operation_command = os.environ.get(
|
|
321
|
+
"CODEBUDDY_SANDBOX_HOST_FILE_OPERATION_COMMAND"
|
|
322
|
+
)
|
|
323
|
+
_broker_request_seq = 0
|
|
324
|
+
_broker_host_operation_depth = 0
|
|
325
|
+
_COPYTREE_DEFAULT = object()
|
|
326
|
+
|
|
327
|
+
def _broker_enabled():
|
|
328
|
+
return bool(sys.platform == "darwin" and _BROKERED_FS_HOOK_ENABLED and _broker_socket_path and _broker_session_id and _broker_file_token_command)
|
|
329
|
+
|
|
330
|
+
def _broker_write_completed_enabled():
|
|
331
|
+
return bool(sys.platform == "darwin" and _BROKERED_FS_HOOK_ENABLED and _broker_socket_path and _broker_session_id)
|
|
332
|
+
|
|
333
|
+
def _broker_host_ops_enabled():
|
|
334
|
+
return bool(
|
|
335
|
+
sys.platform == "darwin"
|
|
336
|
+
and _BROKERED_FS_HOOK_ENABLED
|
|
337
|
+
and _broker_socket_path
|
|
338
|
+
and _broker_session_id
|
|
339
|
+
and _broker_host_operation_command
|
|
340
|
+
and _broker_host_operation_depth == 0
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
def _broker_abs_path(file_path, dir_fd=None):
|
|
344
|
+
if isinstance(file_path, int):
|
|
345
|
+
return None
|
|
346
|
+
try:
|
|
347
|
+
raw_path = os.fspath(file_path)
|
|
348
|
+
except TypeError:
|
|
349
|
+
return None
|
|
350
|
+
if isinstance(raw_path, bytes):
|
|
351
|
+
try:
|
|
352
|
+
raw_path = os.fsdecode(raw_path)
|
|
353
|
+
except UnicodeError:
|
|
354
|
+
return None
|
|
355
|
+
if not raw_path:
|
|
356
|
+
return None
|
|
357
|
+
if dir_fd is not None and not os.path.isabs(raw_path):
|
|
358
|
+
return None
|
|
359
|
+
return os.path.abspath(raw_path)
|
|
360
|
+
|
|
361
|
+
def _broker_readline(sock):
|
|
362
|
+
chunks = []
|
|
363
|
+
while True:
|
|
364
|
+
chunk = sock.recv(1)
|
|
365
|
+
if not chunk:
|
|
366
|
+
break
|
|
367
|
+
if chunk == b"\n":
|
|
368
|
+
break
|
|
369
|
+
chunks.append(chunk)
|
|
370
|
+
return b"".join(chunks).decode("utf-8", errors="replace")
|
|
371
|
+
|
|
372
|
+
def _broker_consume_token(token):
|
|
373
|
+
if sys.platform != "darwin":
|
|
374
|
+
return
|
|
375
|
+
import ctypes
|
|
376
|
+
|
|
377
|
+
lib = ctypes.CDLL(None, use_errno=True)
|
|
378
|
+
fn = lib.sandbox_extension_consume
|
|
379
|
+
fn.argtypes = [ctypes.c_char_p]
|
|
380
|
+
fn.restype = ctypes.c_int64
|
|
381
|
+
handle = fn(token.encode("utf-8"))
|
|
382
|
+
if handle <= 0:
|
|
383
|
+
err = ctypes.get_errno() or errno.EINVAL
|
|
384
|
+
raise OSError(err, os.strerror(err))
|
|
385
|
+
|
|
386
|
+
def _broker_send_request(request):
|
|
387
|
+
try:
|
|
388
|
+
with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as sock:
|
|
389
|
+
sock.settimeout(5)
|
|
390
|
+
sock.connect(_broker_socket_path)
|
|
391
|
+
sock.sendall((json.dumps(request, separators=(",", ":")) + "\n").encode("utf-8"))
|
|
392
|
+
line = _broker_readline(sock)
|
|
393
|
+
except Exception:
|
|
394
|
+
return None
|
|
395
|
+
|
|
396
|
+
try:
|
|
397
|
+
response = json.loads(line)
|
|
398
|
+
except Exception:
|
|
399
|
+
return None
|
|
400
|
+
return response if isinstance(response, dict) else None
|
|
401
|
+
|
|
402
|
+
def _broker_request_file_token(abs_path, operation):
|
|
403
|
+
global _broker_request_seq
|
|
404
|
+
if not _broker_enabled():
|
|
405
|
+
return
|
|
406
|
+
|
|
407
|
+
_broker_request_seq += 1
|
|
408
|
+
request = {
|
|
409
|
+
"id": "python-%d" % _broker_request_seq,
|
|
410
|
+
"command": _broker_file_token_command,
|
|
411
|
+
"runtime": "python",
|
|
412
|
+
"operation": operation,
|
|
413
|
+
"path": abs_path,
|
|
414
|
+
"sessionId": _broker_session_id,
|
|
415
|
+
}
|
|
416
|
+
if _broker_tool_call_id:
|
|
417
|
+
request["toolCallId"] = _broker_tool_call_id
|
|
418
|
+
if _broker_trace_id:
|
|
419
|
+
request["brokerTraceId"] = _broker_trace_id
|
|
420
|
+
|
|
421
|
+
response = _broker_send_request(request)
|
|
422
|
+
if response is None:
|
|
423
|
+
# Match toybox's fail-to-sandbox behavior: if IPC is unavailable,
|
|
424
|
+
# do not grant anything here; let the native sandbox decide.
|
|
425
|
+
return
|
|
426
|
+
|
|
427
|
+
token = response.get("token") if response.get("ok") is True else None
|
|
428
|
+
if isinstance(token, str) and token:
|
|
429
|
+
_broker_consume_token(token)
|
|
430
|
+
return
|
|
431
|
+
if response.get("decision") == "sandbox":
|
|
432
|
+
return
|
|
433
|
+
|
|
434
|
+
raise PermissionError(
|
|
435
|
+
response.get("error")
|
|
436
|
+
or "Brokered sandbox denied %s access to %s" % (operation, abs_path)
|
|
437
|
+
)
|
|
438
|
+
|
|
439
|
+
def _broker_notify_file_write_completed(abs_path, existed_before):
|
|
440
|
+
global _broker_request_seq
|
|
441
|
+
if existed_before:
|
|
442
|
+
return
|
|
443
|
+
if not _broker_write_completed_enabled():
|
|
444
|
+
return
|
|
445
|
+
|
|
446
|
+
_broker_request_seq += 1
|
|
447
|
+
request = {
|
|
448
|
+
"id": "python-%d" % _broker_request_seq,
|
|
449
|
+
"command": "FileWriteCompleted",
|
|
450
|
+
"runtime": "python",
|
|
451
|
+
"operation": "write",
|
|
452
|
+
"path": abs_path,
|
|
453
|
+
"sessionId": _broker_session_id,
|
|
454
|
+
}
|
|
455
|
+
if _broker_tool_call_id:
|
|
456
|
+
request["toolCallId"] = _broker_tool_call_id
|
|
457
|
+
if _broker_trace_id:
|
|
458
|
+
request["brokerTraceId"] = _broker_trace_id
|
|
459
|
+
_broker_send_request(request)
|
|
460
|
+
|
|
461
|
+
def _broker_request_host_operation(operation, primary_path, **fields):
|
|
462
|
+
global _broker_request_seq
|
|
463
|
+
if not _broker_host_ops_enabled():
|
|
464
|
+
return False
|
|
465
|
+
|
|
466
|
+
_broker_request_seq += 1
|
|
467
|
+
request = {
|
|
468
|
+
"id": "python-%d" % _broker_request_seq,
|
|
469
|
+
"command": _broker_host_operation_command,
|
|
470
|
+
"runtime": "python",
|
|
471
|
+
"operation": operation,
|
|
472
|
+
"sessionId": _broker_session_id,
|
|
473
|
+
}
|
|
474
|
+
request.update(fields)
|
|
475
|
+
if _broker_tool_call_id:
|
|
476
|
+
request["toolCallId"] = _broker_tool_call_id
|
|
477
|
+
if _broker_trace_id:
|
|
478
|
+
request["brokerTraceId"] = _broker_trace_id
|
|
479
|
+
|
|
480
|
+
response = _broker_send_request(request)
|
|
481
|
+
if response is None:
|
|
482
|
+
return False
|
|
483
|
+
if response.get("ok") is True:
|
|
484
|
+
return True
|
|
485
|
+
if response.get("decision") == "sandbox":
|
|
486
|
+
return False
|
|
487
|
+
raise PermissionError(
|
|
488
|
+
response.get("error")
|
|
489
|
+
or "Brokered sandbox denied host %s for %s" % (operation, primary_path)
|
|
490
|
+
)
|
|
491
|
+
|
|
492
|
+
def _broker_call_native_host_operation(fn, *args, **kwargs):
|
|
493
|
+
global _broker_host_operation_depth
|
|
494
|
+
_broker_host_operation_depth += 1
|
|
495
|
+
try:
|
|
496
|
+
return fn(*args, **kwargs)
|
|
497
|
+
finally:
|
|
498
|
+
_broker_host_operation_depth -= 1
|
|
499
|
+
|
|
500
|
+
def _broker_operation_for_mode(mode):
|
|
501
|
+
mode = mode or "r"
|
|
502
|
+
return "write" if any(ch in mode for ch in "wax+") else "read"
|
|
503
|
+
|
|
504
|
+
def _broker_operation_for_flags(flags):
|
|
505
|
+
write_flags = (
|
|
506
|
+
os.O_WRONLY
|
|
507
|
+
| os.O_RDWR
|
|
508
|
+
| os.O_CREAT
|
|
509
|
+
| os.O_TRUNC
|
|
510
|
+
| os.O_APPEND
|
|
511
|
+
| getattr(os, "O_TMPFILE", 0)
|
|
512
|
+
)
|
|
513
|
+
return "write" if flags & write_flags else "read"
|
|
514
|
+
|
|
515
|
+
def _broker_gate(file_path, operation, dir_fd=None):
|
|
516
|
+
abs_path = _broker_abs_path(file_path, dir_fd=dir_fd)
|
|
517
|
+
if abs_path:
|
|
518
|
+
_broker_request_file_token(abs_path, operation)
|
|
519
|
+
|
|
520
|
+
def _brokered_open(
|
|
521
|
+
open_fn,
|
|
522
|
+
file,
|
|
523
|
+
mode="r",
|
|
524
|
+
buffering=-1,
|
|
525
|
+
encoding=None,
|
|
526
|
+
errors=None,
|
|
527
|
+
newline=None,
|
|
528
|
+
closefd=True,
|
|
529
|
+
opener=None,
|
|
530
|
+
):
|
|
531
|
+
operation = _broker_operation_for_mode(mode)
|
|
532
|
+
abs_path = _broker_abs_path(file)
|
|
533
|
+
existed_before = bool(abs_path and os.path.exists(abs_path))
|
|
534
|
+
if abs_path:
|
|
535
|
+
_broker_request_file_token(abs_path, operation)
|
|
536
|
+
file_obj = open_fn(file, mode, buffering, encoding, errors, newline, closefd, opener)
|
|
537
|
+
if operation == "write" and abs_path and os.path.exists(abs_path):
|
|
538
|
+
_broker_notify_file_write_completed(abs_path, existed_before)
|
|
539
|
+
return file_obj
|
|
540
|
+
|
|
541
|
+
def _brokered_builtin_open(
|
|
542
|
+
file,
|
|
543
|
+
mode="r",
|
|
544
|
+
buffering=-1,
|
|
545
|
+
encoding=None,
|
|
546
|
+
errors=None,
|
|
547
|
+
newline=None,
|
|
548
|
+
closefd=True,
|
|
549
|
+
opener=None,
|
|
550
|
+
):
|
|
551
|
+
return _brokered_open(
|
|
552
|
+
_orig_builtin_open,
|
|
553
|
+
file, mode, buffering, encoding, errors, newline, closefd, opener,
|
|
554
|
+
)
|
|
555
|
+
|
|
556
|
+
def _brokered_io_open(
|
|
557
|
+
file,
|
|
558
|
+
mode="r",
|
|
559
|
+
buffering=-1,
|
|
560
|
+
encoding=None,
|
|
561
|
+
errors=None,
|
|
562
|
+
newline=None,
|
|
563
|
+
closefd=True,
|
|
564
|
+
opener=None,
|
|
565
|
+
):
|
|
566
|
+
return _brokered_open(
|
|
567
|
+
_orig_io_open,
|
|
568
|
+
file, mode, buffering, encoding, errors, newline, closefd, opener,
|
|
569
|
+
)
|
|
570
|
+
|
|
571
|
+
def _brokered_os_open(path, flags, mode=0o777, *, dir_fd=None):
|
|
572
|
+
_broker_gate(path, _broker_operation_for_flags(flags), dir_fd=dir_fd)
|
|
573
|
+
if dir_fd is not None:
|
|
574
|
+
return _orig_os_open(path, flags, mode, dir_fd=dir_fd)
|
|
575
|
+
return _orig_os_open(path, flags, mode)
|
|
576
|
+
|
|
577
|
+
def _brokered_os_mkdir(path, mode=0o777, *, dir_fd=None):
|
|
578
|
+
abs_path = _broker_abs_path(path, dir_fd=dir_fd)
|
|
579
|
+
if abs_path and isinstance(mode, int) and _broker_request_host_operation(
|
|
580
|
+
"mkdir", abs_path, path=abs_path, mode=mode
|
|
581
|
+
):
|
|
582
|
+
return None
|
|
583
|
+
if dir_fd is not None:
|
|
584
|
+
return _broker_call_native_host_operation(_orig_mkdir, path, mode, dir_fd=dir_fd)
|
|
585
|
+
return _broker_call_native_host_operation(_orig_mkdir, path, mode)
|
|
586
|
+
|
|
587
|
+
def _brokered_os_makedirs(name, mode=0o777, exist_ok=False):
|
|
588
|
+
abs_path = _broker_abs_path(name)
|
|
589
|
+
if abs_path and isinstance(mode, int):
|
|
590
|
+
if not exist_ok and os.path.exists(abs_path):
|
|
591
|
+
return _broker_call_native_host_operation(
|
|
592
|
+
_orig_makedirs, name, mode=mode, exist_ok=exist_ok
|
|
593
|
+
)
|
|
594
|
+
if _broker_request_host_operation(
|
|
595
|
+
"mkdir", abs_path, path=abs_path, recursive=True, mode=mode
|
|
596
|
+
):
|
|
597
|
+
return None
|
|
598
|
+
return _broker_call_native_host_operation(
|
|
599
|
+
_orig_makedirs, name, mode=mode, exist_ok=exist_ok
|
|
600
|
+
)
|
|
601
|
+
|
|
602
|
+
def _brokered_os_rename(src, dst, *, src_dir_fd=None, dst_dir_fd=None):
|
|
603
|
+
from_path = _broker_abs_path(src, dir_fd=src_dir_fd)
|
|
604
|
+
to_path = _broker_abs_path(dst, dir_fd=dst_dir_fd)
|
|
605
|
+
if from_path and to_path and _broker_request_host_operation(
|
|
606
|
+
"rename", from_path, **{"from": from_path, "to": to_path}
|
|
607
|
+
):
|
|
608
|
+
return None
|
|
609
|
+
if src_dir_fd is not None or dst_dir_fd is not None:
|
|
610
|
+
return _broker_call_native_host_operation(
|
|
611
|
+
_orig_rename, src, dst, src_dir_fd=src_dir_fd, dst_dir_fd=dst_dir_fd
|
|
612
|
+
)
|
|
613
|
+
return _broker_call_native_host_operation(_orig_rename, src, dst)
|
|
614
|
+
|
|
615
|
+
def _brokered_os_replace(src, dst, *, src_dir_fd=None, dst_dir_fd=None):
|
|
616
|
+
from_path = _broker_abs_path(src, dir_fd=src_dir_fd)
|
|
617
|
+
to_path = _broker_abs_path(dst, dir_fd=dst_dir_fd)
|
|
618
|
+
if from_path and to_path and _broker_request_host_operation(
|
|
619
|
+
"rename", from_path, **{"from": from_path, "to": to_path}
|
|
620
|
+
):
|
|
621
|
+
return None
|
|
622
|
+
if src_dir_fd is not None or dst_dir_fd is not None:
|
|
623
|
+
return _broker_call_native_host_operation(
|
|
624
|
+
_orig_replace, src, dst, src_dir_fd=src_dir_fd, dst_dir_fd=dst_dir_fd
|
|
625
|
+
)
|
|
626
|
+
return _broker_call_native_host_operation(_orig_replace, src, dst)
|
|
627
|
+
|
|
628
|
+
def _brokered_os_chmod(path, mode, *, dir_fd=None, follow_symlinks=True):
|
|
629
|
+
abs_path = _broker_abs_path(path, dir_fd=dir_fd)
|
|
630
|
+
if abs_path and isinstance(mode, int) and follow_symlinks and _broker_request_host_operation(
|
|
631
|
+
"chmod", abs_path, path=abs_path, mode=mode
|
|
632
|
+
):
|
|
633
|
+
return None
|
|
634
|
+
if dir_fd is not None:
|
|
635
|
+
return _broker_call_native_host_operation(
|
|
636
|
+
_orig_chmod, path, mode, dir_fd=dir_fd, follow_symlinks=follow_symlinks
|
|
637
|
+
)
|
|
638
|
+
return _broker_call_native_host_operation(
|
|
639
|
+
_orig_chmod, path, mode, follow_symlinks=follow_symlinks
|
|
640
|
+
)
|
|
641
|
+
|
|
642
|
+
def _brokered_os_link(src, dst, *, src_dir_fd=None, dst_dir_fd=None, follow_symlinks=True):
|
|
643
|
+
from_path = _broker_abs_path(src, dir_fd=src_dir_fd)
|
|
644
|
+
to_path = _broker_abs_path(dst, dir_fd=dst_dir_fd)
|
|
645
|
+
should_broker = follow_symlinks and from_path and to_path
|
|
646
|
+
try:
|
|
647
|
+
should_broker = should_broker and not os.path.islink(from_path)
|
|
648
|
+
except Exception:
|
|
649
|
+
should_broker = False
|
|
650
|
+
if should_broker and _broker_request_host_operation(
|
|
651
|
+
"link", from_path, **{"from": from_path, "to": to_path, "symbolic": False}
|
|
652
|
+
):
|
|
653
|
+
return None
|
|
654
|
+
if src_dir_fd is not None or dst_dir_fd is not None:
|
|
655
|
+
return _broker_call_native_host_operation(
|
|
656
|
+
_orig_link, src, dst, src_dir_fd=src_dir_fd, dst_dir_fd=dst_dir_fd,
|
|
657
|
+
follow_symlinks=follow_symlinks
|
|
658
|
+
)
|
|
659
|
+
return _broker_call_native_host_operation(
|
|
660
|
+
_orig_link, src, dst, follow_symlinks=follow_symlinks
|
|
661
|
+
)
|
|
662
|
+
|
|
663
|
+
def _brokered_os_symlink(src, dst, target_is_directory=False, *, dir_fd=None):
|
|
664
|
+
source = None
|
|
665
|
+
try:
|
|
666
|
+
source = os.fspath(src)
|
|
667
|
+
if isinstance(source, bytes):
|
|
668
|
+
source = os.fsdecode(source)
|
|
669
|
+
except (TypeError, UnicodeError):
|
|
670
|
+
source = None
|
|
671
|
+
to_path = _broker_abs_path(dst, dir_fd=dir_fd)
|
|
672
|
+
if source and to_path and _broker_request_host_operation(
|
|
673
|
+
"link", to_path, **{"from": source, "to": to_path, "symbolic": True}
|
|
674
|
+
):
|
|
675
|
+
return None
|
|
676
|
+
if dir_fd is not None:
|
|
677
|
+
return _broker_call_native_host_operation(
|
|
678
|
+
_orig_symlink, src, dst, target_is_directory, dir_fd=dir_fd
|
|
679
|
+
)
|
|
680
|
+
return _broker_call_native_host_operation(
|
|
681
|
+
_orig_symlink, src, dst, target_is_directory
|
|
682
|
+
)
|
|
683
|
+
|
|
684
|
+
def _brokered_shutil_copyfile(src, dst, *, follow_symlinks=True):
|
|
685
|
+
from_path = _broker_abs_path(src)
|
|
686
|
+
to_path = _broker_abs_path(dst)
|
|
687
|
+
if follow_symlinks and from_path and to_path and _broker_request_host_operation(
|
|
688
|
+
"copy", from_path, **{"from": from_path, "to": to_path, "recursive": False}
|
|
689
|
+
):
|
|
690
|
+
return dst
|
|
691
|
+
return _broker_call_native_host_operation(
|
|
692
|
+
_orig_shutil_copyfile, src, dst, follow_symlinks=follow_symlinks
|
|
693
|
+
)
|
|
694
|
+
|
|
695
|
+
def _brokered_shutil_copytree(
|
|
696
|
+
src,
|
|
697
|
+
dst,
|
|
698
|
+
symlinks=False,
|
|
699
|
+
ignore=None,
|
|
700
|
+
copy_function=_COPYTREE_DEFAULT,
|
|
701
|
+
ignore_dangling_symlinks=False,
|
|
702
|
+
dirs_exist_ok=False,
|
|
703
|
+
):
|
|
704
|
+
native_copy_function = (
|
|
705
|
+
_orig_shutil_copy2
|
|
706
|
+
if copy_function is _COPYTREE_DEFAULT
|
|
707
|
+
else copy_function
|
|
708
|
+
)
|
|
709
|
+
can_broker = (
|
|
710
|
+
symlinks is False
|
|
711
|
+
and ignore is None
|
|
712
|
+
and native_copy_function is _orig_shutil_copy2
|
|
713
|
+
and ignore_dangling_symlinks is False
|
|
714
|
+
and dirs_exist_ok is False
|
|
715
|
+
)
|
|
716
|
+
from_path = _broker_abs_path(src) if can_broker else None
|
|
717
|
+
to_path = _broker_abs_path(dst) if can_broker else None
|
|
718
|
+
if from_path and to_path and _broker_request_host_operation(
|
|
719
|
+
"copy", from_path, **{"from": from_path, "to": to_path, "recursive": True}
|
|
720
|
+
):
|
|
721
|
+
return dst
|
|
722
|
+
return _broker_call_native_host_operation(
|
|
723
|
+
_orig_shutil_copytree,
|
|
724
|
+
src,
|
|
725
|
+
dst,
|
|
726
|
+
symlinks=symlinks,
|
|
727
|
+
ignore=ignore,
|
|
728
|
+
copy_function=native_copy_function,
|
|
729
|
+
ignore_dangling_symlinks=ignore_dangling_symlinks,
|
|
730
|
+
dirs_exist_ok=dirs_exist_ok,
|
|
731
|
+
)
|
|
732
|
+
|
|
733
|
+
def _brokered_path_mkdir(self, mode=0o777, parents=False, exist_ok=False):
|
|
734
|
+
abs_path = _broker_abs_path(self)
|
|
735
|
+
if abs_path and isinstance(mode, int):
|
|
736
|
+
if not exist_ok and os.path.exists(abs_path):
|
|
737
|
+
return _broker_call_native_host_operation(
|
|
738
|
+
_orig_path_mkdir, self, mode=mode, parents=parents, exist_ok=exist_ok
|
|
739
|
+
)
|
|
740
|
+
if _broker_request_host_operation(
|
|
741
|
+
"mkdir", abs_path, path=abs_path, recursive=parents, mode=mode
|
|
742
|
+
):
|
|
743
|
+
return None
|
|
744
|
+
return _broker_call_native_host_operation(
|
|
745
|
+
_orig_path_mkdir, self, mode=mode, parents=parents, exist_ok=exist_ok
|
|
746
|
+
)
|
|
747
|
+
|
|
748
|
+
def _brokered_path_touch(self, mode=0o666, exist_ok=True):
|
|
749
|
+
abs_path = _broker_abs_path(self)
|
|
750
|
+
can_broker = abs_path and mode == 0o666 and (exist_ok or not os.path.exists(abs_path))
|
|
751
|
+
if can_broker and _broker_request_host_operation("touch", abs_path, path=abs_path):
|
|
752
|
+
return None
|
|
753
|
+
return _broker_call_native_host_operation(
|
|
754
|
+
_orig_path_touch, self, mode=mode, exist_ok=exist_ok
|
|
755
|
+
)
|
|
756
|
+
|
|
279
757
|
def _path_for_compare(path):
|
|
280
758
|
return os.path.normcase(os.path.realpath(os.path.abspath(path)))
|
|
281
759
|
|
|
@@ -314,7 +792,7 @@ if _SESSION_ID:
|
|
|
314
792
|
if not _report_path:
|
|
315
793
|
return
|
|
316
794
|
try:
|
|
317
|
-
with
|
|
795
|
+
with _orig_builtin_open(_report_path, "a", encoding="utf-8") as f:
|
|
318
796
|
f.write(json.dumps({
|
|
319
797
|
"operation": "trash",
|
|
320
798
|
"runtime": "python",
|
|
@@ -419,11 +897,30 @@ if _SESSION_ID:
|
|
|
419
897
|
return False
|
|
420
898
|
|
|
421
899
|
# -----------------------------------------------------------------------
|
|
422
|
-
#
|
|
900
|
+
# 核心:移入回收站。
|
|
423
901
|
# 返回 True=已移入回收站;抛错=fail-closed,调用方不得真删。
|
|
424
902
|
# -----------------------------------------------------------------------
|
|
425
903
|
|
|
426
|
-
def
|
|
904
|
+
def _try_broker_delete(abs_path, recursive=False, force=False):
|
|
905
|
+
if not _broker_host_ops_enabled():
|
|
906
|
+
return "unavailable"
|
|
907
|
+
try:
|
|
908
|
+
if _broker_request_host_operation(
|
|
909
|
+
"delete",
|
|
910
|
+
abs_path,
|
|
911
|
+
path=abs_path,
|
|
912
|
+
deleteMode="trash",
|
|
913
|
+
recursive=bool(recursive),
|
|
914
|
+
force=bool(force),
|
|
915
|
+
):
|
|
916
|
+
return "ok"
|
|
917
|
+
return "unavailable"
|
|
918
|
+
except PermissionError:
|
|
919
|
+
return "denied"
|
|
920
|
+
except Exception:
|
|
921
|
+
return "unavailable"
|
|
922
|
+
|
|
923
|
+
def _try_trash(abs_path, recursive=False, force=False):
|
|
427
924
|
if sys.platform == "win32" and _IN_SANDBOX:
|
|
428
925
|
message = (
|
|
429
926
|
"[safe-delete][SAFE_DELETE_FAIL_CLOSED] "
|
|
@@ -439,6 +936,15 @@ if _SESSION_ID:
|
|
|
439
936
|
pass
|
|
440
937
|
raise OSError(message)
|
|
441
938
|
_check_bulk_delete_guard(abs_path)
|
|
939
|
+
|
|
940
|
+
broker_result = _try_broker_delete(abs_path, recursive=recursive, force=force)
|
|
941
|
+
if broker_result == "ok":
|
|
942
|
+
return True
|
|
943
|
+
if broker_result == "denied":
|
|
944
|
+
raise PermissionError(
|
|
945
|
+
"[safe-delete] broker denied delete: path=%s" % abs_path
|
|
946
|
+
)
|
|
947
|
+
|
|
442
948
|
if _try_trash_via_binary(abs_path):
|
|
443
949
|
_record_trash(abs_path)
|
|
444
950
|
return True
|
|
@@ -461,17 +967,18 @@ if _SESSION_ID:
|
|
|
461
967
|
except OSError:
|
|
462
968
|
return False
|
|
463
969
|
|
|
464
|
-
def
|
|
970
|
+
def _is_under_root(root, abs_path):
|
|
465
971
|
try:
|
|
466
972
|
target = _path_for_compare(os.fsdecode(abs_path))
|
|
467
|
-
|
|
973
|
+
root_path = _path_for_compare(root)
|
|
974
|
+
rel = os.path.relpath(target, root_path)
|
|
975
|
+
except (TypeError, UnicodeError, ValueError):
|
|
468
976
|
return False
|
|
977
|
+
return rel not in ("", ".") and rel != ".." and not rel.startswith(".." + os.sep) and not os.path.isabs(rel)
|
|
978
|
+
|
|
979
|
+
def _is_under_os_tmp_dir(abs_path):
|
|
469
980
|
for tmp_dir in _os_tmp_dirs:
|
|
470
|
-
|
|
471
|
-
rel = os.path.relpath(target, tmp_dir)
|
|
472
|
-
except ValueError:
|
|
473
|
-
continue
|
|
474
|
-
if rel not in ("", ".") and rel != ".." and not rel.startswith(".." + os.sep) and not os.path.isabs(rel):
|
|
981
|
+
if _is_under_root(tmp_dir, abs_path):
|
|
475
982
|
return True
|
|
476
983
|
return False
|
|
477
984
|
|
|
@@ -489,6 +996,9 @@ if _SESSION_ID:
|
|
|
489
996
|
return True
|
|
490
997
|
return False
|
|
491
998
|
|
|
999
|
+
def _should_bypass_safe_delete(abs_path):
|
|
1000
|
+
return _is_under_os_tmp_dir(abs_path) or _is_pip_site_packages_temp_dir(abs_path)
|
|
1001
|
+
|
|
492
1002
|
# -----------------------------------------------------------------------
|
|
493
1003
|
# 包装:os.remove / os.unlink (文件,不能是目录)
|
|
494
1004
|
# -----------------------------------------------------------------------
|
|
@@ -498,7 +1008,7 @@ if _SESSION_ID:
|
|
|
498
1008
|
if dir_fd is not None:
|
|
499
1009
|
return _orig_remove(path, dir_fd=dir_fd)
|
|
500
1010
|
abs_path = os.path.abspath(os.fspath(path))
|
|
501
|
-
if
|
|
1011
|
+
if _should_bypass_safe_delete(abs_path):
|
|
502
1012
|
return _orig_remove(path)
|
|
503
1013
|
st = _safe_lstat(abs_path)
|
|
504
1014
|
if not st:
|
|
@@ -515,7 +1025,7 @@ if _SESSION_ID:
|
|
|
515
1025
|
if dir_fd is not None:
|
|
516
1026
|
return _orig_rmdir(path, dir_fd=dir_fd)
|
|
517
1027
|
abs_path = os.path.abspath(os.fspath(path))
|
|
518
|
-
if
|
|
1028
|
+
if _should_bypass_safe_delete(abs_path):
|
|
519
1029
|
return _orig_rmdir(path)
|
|
520
1030
|
st = _safe_lstat(abs_path)
|
|
521
1031
|
if not st:
|
|
@@ -539,7 +1049,7 @@ if _SESSION_ID:
|
|
|
539
1049
|
path, ignore_errors=ignore_errors, onerror=onerror, **kwargs
|
|
540
1050
|
)
|
|
541
1051
|
|
|
542
|
-
if
|
|
1052
|
+
if _should_bypass_safe_delete(abs_path):
|
|
543
1053
|
return _orig_shutil_rmtree(
|
|
544
1054
|
path, ignore_errors=ignore_errors, onerror=onerror, **kwargs
|
|
545
1055
|
)
|
|
@@ -556,7 +1066,7 @@ if _SESSION_ID:
|
|
|
556
1066
|
)
|
|
557
1067
|
|
|
558
1068
|
try:
|
|
559
|
-
_try_trash(abs_path)
|
|
1069
|
+
_try_trash(abs_path, recursive=True)
|
|
560
1070
|
except Exception as e:
|
|
561
1071
|
if ignore_errors:
|
|
562
1072
|
return
|
|
@@ -571,7 +1081,7 @@ if _SESSION_ID:
|
|
|
571
1081
|
|
|
572
1082
|
def _safe_path_unlink(self, missing_ok=False):
|
|
573
1083
|
abs_path = os.path.abspath(str(self))
|
|
574
|
-
if
|
|
1084
|
+
if _should_bypass_safe_delete(abs_path):
|
|
575
1085
|
return _orig_path_unlink(self, missing_ok=missing_ok)
|
|
576
1086
|
st = _safe_lstat(abs_path)
|
|
577
1087
|
if not st:
|
|
@@ -584,7 +1094,7 @@ if _SESSION_ID:
|
|
|
584
1094
|
|
|
585
1095
|
def _safe_path_rmdir(self):
|
|
586
1096
|
abs_path = os.path.abspath(str(self))
|
|
587
|
-
if
|
|
1097
|
+
if _should_bypass_safe_delete(abs_path):
|
|
588
1098
|
return _orig_path_rmdir(self)
|
|
589
1099
|
st = _safe_lstat(abs_path)
|
|
590
1100
|
if not st:
|
|
@@ -599,9 +1109,28 @@ if _SESSION_ID:
|
|
|
599
1109
|
# 安装 patch
|
|
600
1110
|
# -----------------------------------------------------------------------
|
|
601
1111
|
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
1112
|
+
if _broker_enabled():
|
|
1113
|
+
builtins.open = _brokered_builtin_open
|
|
1114
|
+
io.open = _brokered_io_open
|
|
1115
|
+
os.open = _brokered_os_open
|
|
1116
|
+
|
|
1117
|
+
if _broker_host_ops_enabled():
|
|
1118
|
+
os.mkdir = _brokered_os_mkdir
|
|
1119
|
+
os.makedirs = _brokered_os_makedirs
|
|
1120
|
+
os.rename = _brokered_os_rename
|
|
1121
|
+
os.replace = _brokered_os_replace
|
|
1122
|
+
os.chmod = _brokered_os_chmod
|
|
1123
|
+
os.link = _brokered_os_link
|
|
1124
|
+
os.symlink = _brokered_os_symlink
|
|
1125
|
+
shutil.copyfile = _brokered_shutil_copyfile
|
|
1126
|
+
shutil.copytree = _brokered_shutil_copytree
|
|
1127
|
+
pathlib.Path.mkdir = _brokered_path_mkdir
|
|
1128
|
+
pathlib.Path.touch = _brokered_path_touch
|
|
1129
|
+
|
|
1130
|
+
if _SAFE_DELETE_ENABLED:
|
|
1131
|
+
os.remove = _safe_remove
|
|
1132
|
+
os.unlink = _safe_remove # CPython 里 os.unlink is os.remove,显式 patch 兼容其它实现
|
|
1133
|
+
os.rmdir = _safe_rmdir
|
|
1134
|
+
shutil.rmtree = _safe_shutil_rmtree
|
|
1135
|
+
pathlib.Path.unlink = _safe_path_unlink
|
|
1136
|
+
pathlib.Path.rmdir = _safe_path_rmdir
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
Copyright (C) 2006, 2019 by Rob Landley <rob@landley.net>
|
|
2
|
+
|
|
3
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
4
|
+
purpose with or without fee is hereby granted.
|
|
5
|
+
|
|
6
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
7
|
+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
8
|
+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
9
|
+
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
10
|
+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
11
|
+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
12
|
+
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
Binary file
|