canvas 0.34.0__py3-none-any.whl → 0.34.1__py3-none-any.whl

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.

Potentially problematic release.


This version of canvas might be problematic. Click here for more details.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: canvas
3
- Version: 0.34.0
3
+ Version: 0.34.1
4
4
  Summary: SDK to customize event-driven actions in your Canvas instance
5
5
  Author-email: Canvas Team <engineering@canvasmedical.com>
6
6
  License-Expression: MIT
@@ -259,14 +259,14 @@ plugin_runner/exceptions.py,sha256=YnRZiQVzbU3HrVlmEXLje_np99009YnhTRVHHyBCtqc,4
259
259
  plugin_runner/installation.py,sha256=LLjtnzPk-w4go3UbXnBItJTKz1ajR_5kGQbFXTaWTFU,7693
260
260
  plugin_runner/load_all_plugins.py,sha256=jXuLxrpfF2OCW1xWaeiKndBHmqjuKIErFkORWMVb86c,5911
261
261
  plugin_runner/plugin_runner.py,sha256=nD-Jr3n2XUiE2bSBlZ2ZWpJ4ZO7UVhk1gpXA6VU-7Mw,21527
262
- plugin_runner/sandbox.py,sha256=jCMZUsgKwu_-nigvz4ZLgudDABN4o_mV7DibOyXwKz8,24954
262
+ plugin_runner/sandbox.py,sha256=q6OX_yoi-9Fbf31i-js9q0Yzs7y4-njMv4X8FcGkwBg,28199
263
263
  protobufs/canvas_generated/messages/effects.proto,sha256=sLP5mNfTffpvWLRTuwe30a9elpsq5LKyPliUg9iVALY,7901
264
264
  protobufs/canvas_generated/messages/events.proto,sha256=x1pzzI4qqbwQBjY_KEmsff5OI50hB5pRt12doGJPZMI,47701
265
265
  protobufs/canvas_generated/messages/plugins.proto,sha256=oNainUPWFYQjgCX7bJEPI9_VnHC5VZduzOqgR4Q7dNM,109
266
266
  protobufs/canvas_generated/services/plugin_runner.proto,sha256=doadBKn5k4xAtOgR-q_pEvW4yzxpUaHNOowMG6CL5GY,304
267
267
  pubsub/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
268
268
  pubsub/pubsub.py,sha256=pyTW0JU8mtaqiAV6g6xjZwel1CVy2EonPMU-_vkmhUM,1044
269
- canvas-0.34.0.dist-info/METADATA,sha256=FCJuNt-7v025Wj2Y8WznNb2306n9aWuWHPHSLisWcXE,4442
270
- canvas-0.34.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
271
- canvas-0.34.0.dist-info/entry_points.txt,sha256=0Vs_9GmTVUNniH6eDBlRPgofmADMV4BES6Ao26M4AbM,47
272
- canvas-0.34.0.dist-info/RECORD,,
269
+ canvas-0.34.1.dist-info/METADATA,sha256=Wqx6oJrGpLwQGhcFj9LoHQn6k_VrY5jNWMvIx0ie0zQ,4442
270
+ canvas-0.34.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
271
+ canvas-0.34.1.dist-info/entry_points.txt,sha256=0Vs_9GmTVUNniH6eDBlRPgofmADMV4BES6Ao26M4AbM,47
272
+ canvas-0.34.1.dist-info/RECORD,,
plugin_runner/sandbox.py CHANGED
@@ -206,6 +206,9 @@ STANDARD_LIBRARY_MODULES = {
206
206
  "Type",
207
207
  "TypedDict",
208
208
  },
209
+ "urllib": {
210
+ "parse",
211
+ },
209
212
  "urllib.parse": {
210
213
  "urlencode",
211
214
  "quote",
@@ -309,6 +312,22 @@ def _find_folder_in_path(file_path: Path, target_folder_name: str) -> Path | Non
309
312
  return _find_folder_in_path(file_path.parent, target_folder_name)
310
313
 
311
314
 
315
+ def node_name(node: ast.AST) -> str:
316
+ """
317
+ Given an AST node, return its name.
318
+ """
319
+ if isinstance(node, ast.Call):
320
+ return ".".join(node_name(arg) for arg in node.args)
321
+
322
+ if isinstance(node, ast.Constant):
323
+ return str(node.value)
324
+
325
+ if isinstance(node, ast.Name):
326
+ return str(node.id)
327
+
328
+ return "__unknown__"
329
+
330
+
312
331
  class Sandbox:
313
332
  """A restricted sandbox for safely executing arbitrary Python code."""
314
333
 
@@ -483,7 +502,7 @@ class Sandbox:
483
502
  func=ast.Name("_write_", ast.Load()),
484
503
  args=[
485
504
  node.value,
486
- ast.Constant(node.value.id if isinstance(node.value, ast.Name) else None),
505
+ ast.Constant(node_name(node.value)),
487
506
  ast.Constant(node.attr),
488
507
  ],
489
508
  keywords=[],
@@ -497,6 +516,55 @@ class Sandbox:
497
516
  # Impossible Case only ctx Load, Store and Del are defined in ast.
498
517
  raise NotImplementedError(f"Unknown ctx type: {type(node.ctx)}")
499
518
 
519
+ def visit_Subscript(self, node: ast.Subscript) -> ast.AST:
520
+ """Transforms all kinds of subscripts.
521
+
522
+ 'foo[bar]' becomes '_getitem_(foo, bar)'
523
+ 'foo[:ab]' becomes '_getitem_(foo, slice(None, ab, None))'
524
+ 'foo[ab:]' becomes '_getitem_(foo, slice(ab, None, None))'
525
+ 'foo[a:b]' becomes '_getitem_(foo, slice(a, b, None))'
526
+ 'foo[a:b:c]' becomes '_getitem_(foo, slice(a, b, c))'
527
+ 'foo[a, b:c] becomes '_getitem_(foo, (a, slice(b, c, None)))'
528
+ 'foo[a] = c' becomes '_write_(foo)[a] = c'
529
+ 'del foo[a]' becomes 'del _write_(foo)[a]'
530
+
531
+ The _write_ function should return a security proxy.
532
+ """
533
+ node = self.node_contents_visit(node)
534
+
535
+ # 'AugStore' and 'AugLoad' are defined in 'Python.asdl' as possible
536
+ # 'expr_context'. However, according to Python/ast.c
537
+ # they are NOT used by the implementation => No need to worry here.
538
+ # Instead ast.c creates 'AugAssign' nodes, which can be visited.
539
+ if isinstance(node.ctx, ast.Load):
540
+ new_node = ast.Call(
541
+ func=ast.Name("_getitem_", ast.Load()),
542
+ args=[node.value, self.transform_slice(node.slice)],
543
+ keywords=[],
544
+ )
545
+
546
+ copy_locations(new_node, node)
547
+
548
+ return new_node
549
+ elif isinstance(node.ctx, ast.Del | ast.Store):
550
+ new_value = ast.Call(
551
+ func=ast.Name("_write_", ast.Load()),
552
+ args=[
553
+ node.value,
554
+ ast.Constant(node_name(node.value)),
555
+ ast.Constant(node_name(node.slice)),
556
+ ],
557
+ keywords=[],
558
+ )
559
+
560
+ copy_locations(new_value, node)
561
+ node.value = new_value
562
+
563
+ return node
564
+ else: # pragma: no cover
565
+ # Impossible Case only ctx Load, Store and Del are defined in ast.
566
+ raise NotImplementedError(f"Unknown ctx type: {type(node.ctx)}")
567
+
500
568
  def __init__(
501
569
  self,
502
570
  source_code: Path,
@@ -655,7 +723,12 @@ class Sandbox:
655
723
  """
656
724
  return bool(self.base_path) and module.split(".")[0] == self.package_name
657
725
 
658
- def _safe_write(self, _ob: Any, name: str | None = None, attribute: str | None = None) -> Any:
726
+ def _safe_write(
727
+ self,
728
+ _ob: Any,
729
+ name: str | None = None,
730
+ attribute: str | int | None = None,
731
+ ) -> Any:
659
732
  """Check if the given obj belongs to a protected resource."""
660
733
  is_module = isinstance(_ob, types.ModuleType)
661
734
 
@@ -664,14 +737,30 @@ class Sandbox:
664
737
  raise AttributeError(f"Forbidden assignment to a module attribute: {_ob.__name__}.")
665
738
  elif isinstance(_ob, type):
666
739
  full_name = f"{_ob.__module__}.{_ob.__qualname__}"
740
+ module_name = _ob.__module__
741
+ else:
742
+ full_name = f"{_ob.__class__.__module__}.{_ob.__class__.__qualname__}"
743
+ module_name = _ob.__class__.__module__
744
+
745
+ if attribute is not None:
746
+ if isinstance(_ob, dict):
747
+ value = dict.get(_ob, attribute)
748
+ elif isinstance(_ob, list | tuple) and isinstance(attribute, int):
749
+ value = _ob.__getitem__(attribute)
750
+ elif isinstance(attribute, str):
751
+ value = getattr(_ob, attribute, None)
752
+ else:
753
+ value = None
667
754
  else:
668
- full_name = f"{_ob.__module__}.{_ob.__class__.__qualname__}"
755
+ value = None
669
756
 
670
- if not self._same_module(_ob.__module__) and (
757
+ if not self._same_module(module_name) and (
671
758
  # deny if it was anything imported
672
- name in self.imported_names["names"]
759
+ (name and name.split(".")[0] in self.imported_names["names"])
673
760
  # deny if it's anything callable
674
- or (attribute is not None and callable(getattr(_ob, attribute)))
761
+ or callable(value)
762
+ # deny writes to dictionary underscore keys
763
+ or (isinstance(_ob, dict) and isinstance(attribute, str) and attribute.startswith("_"))
675
764
  ):
676
765
  raise AttributeError(
677
766
  f"Forbidden assignment to a non-module attribute: {full_name} "
@@ -704,7 +793,7 @@ class Sandbox:
704
793
  is_module = isinstance(_ob, types.ModuleType)
705
794
 
706
795
  if is_module:
707
- module = _ob.__name__.split(".")[0]
796
+ module = _ob.__name__
708
797
  elif isinstance(_ob, type):
709
798
  module = _ob.__module__.split(".")[0]
710
799
  else:
@@ -743,7 +832,9 @@ class Sandbox:
743
832
  if name not in exports:
744
833
  raise AttributeError(f'"{name}" is an invalid attribute name (not in __exports__)')
745
834
  elif is_module and (module not in ALLOWED_MODULES or name not in ALLOWED_MODULES[module]):
746
- raise AttributeError(f'"{name}" is an invalid attribute name (not in ALLOWED_MODULES)')
835
+ raise AttributeError(
836
+ f'"{module}.{name}" is an invalid attribute name (not in ALLOWED_MODULES)'
837
+ )
747
838
 
748
839
  return getattr(_ob, name, default)
749
840