IncludeCPP 4.6.0__tar.gz → 4.6.4__tar.gz

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.
Files changed (77) hide show
  1. {includecpp-4.6.0 → includecpp-4.6.4}/IncludeCPP.egg-info/PKG-INFO +1 -1
  2. {includecpp-4.6.0 → includecpp-4.6.4}/PKG-INFO +1 -1
  3. {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/__init__.py +1 -1
  4. includecpp-4.6.4/includecpp/core/cssl/cpp/build/api.pyd +0 -0
  5. {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/core/cssl/cssl_builtins.py +171 -49
  6. {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/core/cssl/cssl_parser.py +204 -8
  7. {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/core/cssl/cssl_runtime.py +199 -15
  8. includecpp-4.6.4/includecpp/core/cssl/cssl_syntax.py +661 -0
  9. {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/core/cssl/cssl_types.py +1225 -346
  10. {includecpp-4.6.0 → includecpp-4.6.4}/pyproject.toml +1 -1
  11. includecpp-4.6.0/includecpp/core/cssl/cpp/build/api.pyd +0 -0
  12. includecpp-4.6.0/includecpp/core/cssl/cssl_syntax.py +0 -589
  13. {includecpp-4.6.0 → includecpp-4.6.4}/IncludeCPP.egg-info/SOURCES.txt +0 -0
  14. {includecpp-4.6.0 → includecpp-4.6.4}/IncludeCPP.egg-info/dependency_links.txt +0 -0
  15. {includecpp-4.6.0 → includecpp-4.6.4}/IncludeCPP.egg-info/entry_points.txt +0 -0
  16. {includecpp-4.6.0 → includecpp-4.6.4}/IncludeCPP.egg-info/requires.txt +0 -0
  17. {includecpp-4.6.0 → includecpp-4.6.4}/IncludeCPP.egg-info/top_level.txt +0 -0
  18. {includecpp-4.6.0 → includecpp-4.6.4}/LICENSE +0 -0
  19. {includecpp-4.6.0 → includecpp-4.6.4}/MANIFEST.in +0 -0
  20. {includecpp-4.6.0 → includecpp-4.6.4}/README.md +0 -0
  21. {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/CHANGELOG.md +0 -0
  22. {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/DOCUMENTATION.md +0 -0
  23. {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/__init__.pyi +0 -0
  24. {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/__main__.py +0 -0
  25. {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/cli/__init__.py +0 -0
  26. {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/cli/commands.py +0 -0
  27. {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/cli/config_parser.py +0 -0
  28. {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/core/__init__.py +0 -0
  29. {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/core/ai_integration.py +0 -0
  30. {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/core/build_manager.py +0 -0
  31. {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/core/cpp_api.py +0 -0
  32. {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/core/cpp_api.pyi +0 -0
  33. {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/core/cpp_api_extensions.pyi +0 -0
  34. {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/core/cppy_converter.py +0 -0
  35. {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/core/cssl/CSSL_DOCUMENTATION.md +0 -0
  36. {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/core/cssl/CSSL_DOCUMENTATION_NEW.md +0 -0
  37. {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/core/cssl/__init__.py +0 -0
  38. {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/core/cssl/cpp/build/cssl_core.pyi +0 -0
  39. {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/core/cssl/cpp/build/libgcc_s_seh-1.dll +0 -0
  40. {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/core/cssl/cpp/build/libstdc++-6.dll +0 -0
  41. {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/core/cssl/cpp/build/libwinpthread-1.dll +0 -0
  42. {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/core/cssl/cpp/cssl_core.cp +0 -0
  43. {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/core/cssl/cpp/cssl_lexer.hpp +0 -0
  44. {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/core/cssl/cssl_builtins.pyi +0 -0
  45. {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/core/cssl/cssl_compiler.py +0 -0
  46. {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/core/cssl/cssl_events.py +0 -0
  47. {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/core/cssl/cssl_languages.py +0 -0
  48. {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/core/cssl/cssl_modules.py +0 -0
  49. {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/core/cssl/cssl_optimizer.py +0 -0
  50. {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/core/cssl_bridge.py +0 -0
  51. {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/core/cssl_bridge.pyi +0 -0
  52. {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/core/error_catalog.py +0 -0
  53. {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/core/error_formatter.py +0 -0
  54. {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/core/exceptions.py +0 -0
  55. {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/core/path_discovery.py +0 -0
  56. {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/core/project_ui.py +0 -0
  57. {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/core/settings_ui.py +0 -0
  58. {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/generator/__init__.py +0 -0
  59. {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/generator/parser.cpp +0 -0
  60. {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/generator/parser.h +0 -0
  61. {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/generator/type_resolver.cpp +0 -0
  62. {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/generator/type_resolver.h +0 -0
  63. {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/py.typed +0 -0
  64. {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/templates/cpp.proj.template +0 -0
  65. {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/vscode/__init__.py +0 -0
  66. {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/vscode/cssl/__init__.py +0 -0
  67. {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/vscode/cssl/extension.js +0 -0
  68. {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/vscode/cssl/images/cssl.png +0 -0
  69. {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/vscode/cssl/images/cssl_pl.png +0 -0
  70. {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/vscode/cssl/language-configuration.json +0 -0
  71. {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/vscode/cssl/package.json +0 -0
  72. {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/vscode/cssl/snippets/cssl.snippets.json +0 -0
  73. {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/vscode/cssl/syntaxes/cssl.tmLanguage.json +0 -0
  74. {includecpp-4.6.0 → includecpp-4.6.4}/requirements.txt +0 -0
  75. {includecpp-4.6.0 → includecpp-4.6.4}/setup.cfg +0 -0
  76. {includecpp-4.6.0 → includecpp-4.6.4}/setup.py +0 -0
  77. {includecpp-4.6.0 → includecpp-4.6.4}/tests/test_multilang.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: IncludeCPP
3
- Version: 4.6.0
3
+ Version: 4.6.4
4
4
  Summary: Professional C++ Python bindings with type-generic templates, pystubs and native threading
5
5
  Home-page: https://github.com/liliassg/IncludeCPP
6
6
  Author: Lilias Hatterscheidt
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: IncludeCPP
3
- Version: 4.6.0
3
+ Version: 4.6.4
4
4
  Summary: Professional C++ Python bindings with type-generic templates, pystubs and native threading
5
5
  Home-page: https://github.com/liliassg/IncludeCPP
6
6
  Author: Lilias Hatterscheidt
@@ -2,7 +2,7 @@ from .core.cpp_api import CppApi
2
2
  from .core import cssl_bridge as CSSL
3
3
  import warnings
4
4
 
5
- __version__ = "4.5.2"
5
+ __version__ = "4.6.4"
6
6
  __all__ = ["CppApi", "CSSL"]
7
7
 
8
8
  # Module-level cache for C++ modules
@@ -1017,18 +1017,53 @@ class CSSLBuiltins:
1017
1017
 
1018
1018
  # ============= File I/O Functions =============
1019
1019
 
1020
+ # v4.7.1: Path validation helper to prevent directory traversal attacks
1021
+ def _validate_path(self, path: str, allow_write: bool = False) -> str:
1022
+ """Validate file path for security.
1023
+
1024
+ v4.7.1: Prevents directory traversal attacks by checking for '..' sequences
1025
+ and ensuring paths are within reasonable bounds.
1026
+
1027
+ Args:
1028
+ path: The file path to validate
1029
+ allow_write: If True, allows write operations
1030
+
1031
+ Returns:
1032
+ The normalized absolute path
1033
+
1034
+ Raises:
1035
+ CSSLBuiltinError: If path is invalid or attempts traversal
1036
+ """
1037
+ if not isinstance(path, str) or not path:
1038
+ raise CSSLBuiltinError("Invalid path: path must be a non-empty string")
1039
+
1040
+ # Normalize the path
1041
+ normalized = os.path.normpath(path)
1042
+
1043
+ # Check for directory traversal attempts
1044
+ if '..' in normalized:
1045
+ raise CSSLBuiltinError("Directory traversal not allowed in path")
1046
+
1047
+ return normalized
1048
+
1020
1049
  def builtin_read(self, path: str, encoding: str = 'utf-8') -> str:
1021
1050
  """Read entire file content.
1022
1051
  Usage: read('/path/to/file.txt')
1052
+ v4.7.1: Added path validation
1023
1053
  """
1024
- with open(path, 'r', encoding=encoding) as f:
1054
+ validated_path = self._validate_path(path)
1055
+ with open(validated_path, 'r', encoding=encoding) as f:
1025
1056
  return f.read()
1026
1057
 
1027
1058
  def builtin_readline(self, line: int, path: str, encoding: str = 'utf-8') -> str:
1028
1059
  """Read specific line from file (1-indexed).
1029
1060
  Usage: readline(5, '/path/to/file.txt') -> returns line 5
1061
+ v4.7.1: Added path validation and line number validation
1030
1062
  """
1031
- with open(path, 'r', encoding=encoding) as f:
1063
+ if not isinstance(line, int) or line < 1:
1064
+ raise CSSLBuiltinError("Line number must be a positive integer")
1065
+ validated_path = self._validate_path(path)
1066
+ with open(validated_path, 'r', encoding=encoding) as f:
1032
1067
  for i, file_line in enumerate(f, 1):
1033
1068
  if i == line:
1034
1069
  return file_line.rstrip('\n\r')
@@ -1037,18 +1072,25 @@ class CSSLBuiltins:
1037
1072
  def builtin_write(self, path: str, content: str, encoding: str = 'utf-8') -> int:
1038
1073
  """Write content to file, returns chars written.
1039
1074
  Usage: write('/path/to/file.txt', 'Hello World')
1075
+ v4.7.1: Added path validation
1040
1076
  """
1041
- with open(path, 'w', encoding=encoding) as f:
1042
- return f.write(content)
1077
+ validated_path = self._validate_path(path, allow_write=True)
1078
+ with open(validated_path, 'w', encoding=encoding) as f:
1079
+ return f.write(str(content) if content is not None else '')
1043
1080
 
1044
1081
  def builtin_writeline(self, line: int, content: str, path: str, encoding: str = 'utf-8') -> bool:
1045
1082
  """Write/replace specific line in file (1-indexed).
1046
1083
  Usage: writeline(5, 'New content', '/path/to/file.txt')
1084
+ v4.7.1: Added path and line number validation
1047
1085
  """
1086
+ if not isinstance(line, int) or line < 1:
1087
+ raise CSSLBuiltinError("Line number must be a positive integer")
1088
+ validated_path = self._validate_path(path, allow_write=True)
1089
+
1048
1090
  # Read all lines
1049
1091
  lines = []
1050
- if os.path.exists(path):
1051
- with open(path, 'r', encoding=encoding) as f:
1092
+ if os.path.exists(validated_path):
1093
+ with open(validated_path, 'r', encoding=encoding) as f:
1052
1094
  lines = f.readlines()
1053
1095
 
1054
1096
  # Ensure we have enough lines
@@ -1056,67 +1098,81 @@ class CSSLBuiltins:
1056
1098
  lines.append('\n')
1057
1099
 
1058
1100
  # Replace the specific line (1-indexed)
1059
- if not content.endswith('\n'):
1060
- content = content + '\n'
1061
- lines[line - 1] = content
1101
+ content_str = str(content) if content is not None else ''
1102
+ if not content_str.endswith('\n'):
1103
+ content_str = content_str + '\n'
1104
+ lines[line - 1] = content_str
1062
1105
 
1063
1106
  # Write back
1064
- with open(path, 'w', encoding=encoding) as f:
1107
+ with open(validated_path, 'w', encoding=encoding) as f:
1065
1108
  f.writelines(lines)
1066
1109
  return True
1067
1110
 
1068
1111
  def builtin_readfile(self, path: str, encoding: str = 'utf-8') -> str:
1069
- """Read entire file content"""
1070
- with open(path, 'r', encoding=encoding) as f:
1112
+ """Read entire file content. v4.7.1: Added path validation"""
1113
+ validated_path = self._validate_path(path)
1114
+ with open(validated_path, 'r', encoding=encoding) as f:
1071
1115
  return f.read()
1072
1116
 
1073
1117
  def builtin_writefile(self, path: str, content: str, encoding: str = 'utf-8') -> int:
1074
- """Write content to file, returns bytes written"""
1075
- with open(path, 'w', encoding=encoding) as f:
1076
- return f.write(content)
1118
+ """Write content to file, returns bytes written. v4.7.1: Added path validation"""
1119
+ validated_path = self._validate_path(path, allow_write=True)
1120
+ with open(validated_path, 'w', encoding=encoding) as f:
1121
+ return f.write(str(content) if content is not None else '')
1077
1122
 
1078
1123
  def builtin_appendfile(self, path: str, content: str, encoding: str = 'utf-8') -> int:
1079
- """Append content to file, returns bytes written"""
1080
- with open(path, 'a', encoding=encoding) as f:
1081
- return f.write(content)
1124
+ """Append content to file, returns bytes written. v4.7.1: Added path validation"""
1125
+ validated_path = self._validate_path(path, allow_write=True)
1126
+ with open(validated_path, 'a', encoding=encoding) as f:
1127
+ return f.write(str(content) if content is not None else '')
1082
1128
 
1083
1129
  def builtin_readlines(self, path: str, encoding: str = 'utf-8') -> list:
1084
- """Read file lines into list"""
1085
- with open(path, 'r', encoding=encoding) as f:
1130
+ """Read file lines into list. v4.7.1: Added path validation"""
1131
+ validated_path = self._validate_path(path)
1132
+ with open(validated_path, 'r', encoding=encoding) as f:
1086
1133
  return f.readlines()
1087
1134
 
1088
1135
  def builtin_listdir(self, path: str = '.') -> list:
1089
- """List directory contents"""
1090
- return os.listdir(path)
1136
+ """List directory contents. v4.7.1: Added path validation"""
1137
+ validated_path = self._validate_path(path) if path != '.' else '.'
1138
+ return os.listdir(validated_path)
1091
1139
 
1092
1140
  def builtin_makedirs(self, path: str, exist_ok: bool = True) -> bool:
1093
- """Create directories recursively"""
1094
- os.makedirs(path, exist_ok=exist_ok)
1141
+ """Create directories recursively. v4.7.1: Added path validation"""
1142
+ validated_path = self._validate_path(path, allow_write=True)
1143
+ os.makedirs(validated_path, exist_ok=exist_ok)
1095
1144
  return True
1096
1145
 
1097
1146
  def builtin_removefile(self, path: str) -> bool:
1098
- """Remove a file"""
1099
- os.remove(path)
1147
+ """Remove a file. v4.7.1: Added path validation"""
1148
+ validated_path = self._validate_path(path, allow_write=True)
1149
+ os.remove(validated_path)
1100
1150
  return True
1101
1151
 
1102
1152
  def builtin_removedir(self, path: str) -> bool:
1103
- """Remove an empty directory"""
1104
- os.rmdir(path)
1153
+ """Remove an empty directory. v4.7.1: Added path validation"""
1154
+ validated_path = self._validate_path(path, allow_write=True)
1155
+ os.rmdir(validated_path)
1105
1156
  return True
1106
1157
 
1107
1158
  def builtin_copyfile(self, src: str, dst: str) -> str:
1108
- """Copy a file, returns destination path"""
1159
+ """Copy a file, returns destination path. v4.7.1: Added path validation"""
1109
1160
  import shutil
1110
- return shutil.copy2(src, dst)
1161
+ validated_src = self._validate_path(src)
1162
+ validated_dst = self._validate_path(dst, allow_write=True)
1163
+ return shutil.copy2(validated_src, validated_dst)
1111
1164
 
1112
1165
  def builtin_movefile(self, src: str, dst: str) -> str:
1113
- """Move a file, returns destination path"""
1166
+ """Move a file, returns destination path. v4.7.1: Added path validation"""
1114
1167
  import shutil
1115
- return shutil.move(src, dst)
1168
+ validated_src = self._validate_path(src, allow_write=True)
1169
+ validated_dst = self._validate_path(dst, allow_write=True)
1170
+ return shutil.move(validated_src, validated_dst)
1116
1171
 
1117
1172
  def builtin_filesize(self, path: str) -> int:
1118
- """Get file size in bytes"""
1119
- return os.path.getsize(path)
1173
+ """Get file size in bytes. v4.7.1: Added path validation"""
1174
+ validated_path = self._validate_path(path)
1175
+ return os.path.getsize(validated_path)
1120
1176
 
1121
1177
  # ============= JSON Functions =============
1122
1178
 
@@ -1558,26 +1614,57 @@ class CSSLBuiltins:
1558
1614
  """Delay execution by milliseconds"""
1559
1615
  time.sleep(ms / 1000.0)
1560
1616
 
1617
+ # v4.7.1: Safe modules whitelist for pyimport
1618
+ SAFE_MODULES = {
1619
+ 'math', 'json', 'datetime', 'random', 're', 'collections', 'itertools',
1620
+ 'functools', 'operator', 'string', 'textwrap', 'unicodedata', 'difflib',
1621
+ 'time', 'calendar', 'decimal', 'fractions', 'statistics', 'copy',
1622
+ 'pprint', 'enum', 'dataclasses', 'typing', 'abc', 'contextlib',
1623
+ 'base64', 'binascii', 'hashlib', 'hmac', 'secrets',
1624
+ 'html', 'urllib.parse', 'uuid', 'pathlib',
1625
+ }
1626
+
1561
1627
  def builtin_pyimport(self, module_name: str) -> Any:
1562
1628
  """
1563
1629
  Import a Python module for use in CSSL.
1564
1630
 
1631
+ v4.7.1: Restricted to safe modules only for security.
1565
1632
  v4.1.1: Fixed to use importlib.import_module for proper nested module support.
1566
1633
 
1634
+ Safe modules: math, json, datetime, random, re, collections, itertools,
1635
+ functools, operator, string, time, calendar, decimal, etc.
1636
+
1567
1637
  Usage:
1568
- module = pyimport("plugins.app")
1569
- result = module.my_function()
1638
+ @math = pyimport("math");
1639
+ result = math.sqrt(16);
1570
1640
 
1571
- os = pyimport("os")
1572
- path = os.path.join("a", "b")
1641
+ @json = pyimport("json");
1642
+ data = json.loads('{"key": "value"}');
1573
1643
  """
1574
1644
  import importlib
1645
+ # Check if module is in safe list
1646
+ base_module = module_name.split('.')[0]
1647
+ if base_module not in self.SAFE_MODULES and module_name not in self.SAFE_MODULES:
1648
+ raise RuntimeError(
1649
+ f"Module '{module_name}' is not allowed. "
1650
+ f"Safe modules: {', '.join(sorted(self.SAFE_MODULES))}"
1651
+ )
1575
1652
  return importlib.import_module(module_name)
1576
1653
 
1577
1654
  # ============= Extended String Functions =============
1578
1655
 
1579
1656
  def builtin_sprintf(self, fmt: str, *args) -> str:
1580
- """C-style format string"""
1657
+ """C-style format string with validation.
1658
+
1659
+ v4.7.1: Added format specifier validation for security.
1660
+ """
1661
+ import re
1662
+ # Count format specifiers (excluding %%)
1663
+ specifiers = re.findall(r'%(?!%)[#0\- +]*\d*\.?\d*[hlL]?[diouxXeEfFgGcrsab]', fmt)
1664
+ if len(specifiers) != len(args):
1665
+ raise ValueError(
1666
+ f"Format string has {len(specifiers)} specifiers but {len(args)} arguments provided"
1667
+ )
1581
1668
  return fmt % args
1582
1669
 
1583
1670
  def builtin_chars(self, s: str) -> list:
@@ -1849,27 +1936,60 @@ class CSSLBuiltins:
1849
1936
 
1850
1937
  def builtin_initsh(self, path: str, *args) -> int:
1851
1938
  """
1852
- Execute a shell script
1853
- Usage: initsh('/path/to/script.sh')
1939
+ Execute a shell script from the 'scripts' directory only.
1940
+
1941
+ v4.7.1: Restricted to 'scripts/' directory for security.
1942
+
1943
+ Usage: initsh('myscript.sh') // Runs scripts/myscript.sh
1854
1944
  """
1855
1945
  import subprocess
1856
1946
 
1857
- if not os.path.isabs(path) and self.runtime and self.runtime.service_engine:
1858
- path = os.path.join(self.runtime.service_engine.KernelClient.RootDirectory, path)
1947
+ # v4.7.1: Security - prevent directory traversal
1948
+ if '..' in path or path.startswith('/') or path.startswith('\\'):
1949
+ raise CSSLBuiltinError(
1950
+ f"Invalid script path: '{path}'. "
1951
+ "Scripts must be relative paths without '..' and must be in 'scripts/' directory."
1952
+ )
1859
1953
 
1860
- if not os.path.exists(path):
1861
- raise CSSLBuiltinError(f"Shell script not found: {path}")
1954
+ # Determine base directory
1955
+ if self.runtime and self.runtime.service_engine:
1956
+ base_dir = self.runtime.service_engine.KernelClient.RootDirectory
1957
+ else:
1958
+ base_dir = os.getcwd()
1959
+
1960
+ # Force scripts to be in 'scripts' subdirectory
1961
+ scripts_dir = os.path.join(base_dir, 'scripts')
1962
+ full_path = os.path.normpath(os.path.join(scripts_dir, path))
1963
+
1964
+ # Verify path is within scripts directory (prevent traversal)
1965
+ if not full_path.startswith(os.path.normpath(scripts_dir)):
1966
+ raise CSSLBuiltinError(
1967
+ f"Security: Script path '{path}' escapes the scripts directory."
1968
+ )
1969
+
1970
+ if not os.path.exists(full_path):
1971
+ raise CSSLBuiltinError(f"Shell script not found: {full_path}")
1972
+
1973
+ # Validate file extension
1974
+ valid_extensions = {'.sh', '.bat', '.cmd', '.ps1'}
1975
+ _, ext = os.path.splitext(full_path)
1976
+ if ext.lower() not in valid_extensions:
1977
+ raise CSSLBuiltinError(
1978
+ f"Invalid script type: '{ext}'. Allowed: {', '.join(valid_extensions)}"
1979
+ )
1862
1980
 
1863
1981
  try:
1864
1982
  # Determine shell based on platform
1865
1983
  import platform
1866
1984
  if platform.system() == 'Windows':
1867
- # Use cmd or powershell on Windows
1868
- cmd = ['cmd', '/c', path] + list(args)
1985
+ if ext.lower() == '.ps1':
1986
+ cmd = ['powershell', '-ExecutionPolicy', 'Bypass', '-File', full_path] + list(args)
1987
+ else:
1988
+ cmd = ['cmd', '/c', full_path] + list(args)
1869
1989
  else:
1870
- cmd = ['bash', path] + list(args)
1990
+ cmd = ['bash', full_path] + list(args)
1871
1991
 
1872
- result = subprocess.run(cmd, capture_output=True, text=True)
1992
+ result = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
1873
1993
 
1874
1994
  if result.stdout:
1875
1995
  print(result.stdout)
@@ -1878,6 +1998,8 @@ class CSSLBuiltins:
1878
1998
 
1879
1999
  return result.returncode
1880
2000
 
2001
+ except subprocess.TimeoutExpired:
2002
+ raise CSSLBuiltinError(f"Script execution timeout (60s): {path}")
1881
2003
  except Exception as e:
1882
2004
  print(f"Shell execution error [{path}]: {e}")
1883
2005
  raise CSSLBuiltinError(f"initsh failed: {e}")