ostruct-cli 0.8.8__py3-none-any.whl → 1.0.0__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.
Files changed (50) hide show
  1. ostruct/cli/__init__.py +3 -15
  2. ostruct/cli/attachment_processor.py +455 -0
  3. ostruct/cli/attachment_template_bridge.py +973 -0
  4. ostruct/cli/cli.py +187 -33
  5. ostruct/cli/click_options.py +775 -692
  6. ostruct/cli/code_interpreter.py +195 -12
  7. ostruct/cli/commands/__init__.py +0 -3
  8. ostruct/cli/commands/run.py +289 -62
  9. ostruct/cli/config.py +23 -22
  10. ostruct/cli/constants.py +89 -0
  11. ostruct/cli/errors.py +191 -6
  12. ostruct/cli/explicit_file_processor.py +0 -15
  13. ostruct/cli/file_info.py +118 -14
  14. ostruct/cli/file_list.py +82 -1
  15. ostruct/cli/file_search.py +68 -2
  16. ostruct/cli/help_json.py +235 -0
  17. ostruct/cli/mcp_integration.py +13 -16
  18. ostruct/cli/params.py +217 -0
  19. ostruct/cli/plan_assembly.py +335 -0
  20. ostruct/cli/plan_printing.py +385 -0
  21. ostruct/cli/progress_reporting.py +8 -56
  22. ostruct/cli/quick_ref_help.py +128 -0
  23. ostruct/cli/rich_config.py +299 -0
  24. ostruct/cli/runner.py +397 -190
  25. ostruct/cli/security/__init__.py +2 -0
  26. ostruct/cli/security/allowed_checker.py +41 -0
  27. ostruct/cli/security/normalization.py +13 -9
  28. ostruct/cli/security/security_manager.py +558 -17
  29. ostruct/cli/security/types.py +15 -0
  30. ostruct/cli/template_debug.py +283 -261
  31. ostruct/cli/template_debug_help.py +233 -142
  32. ostruct/cli/template_env.py +46 -5
  33. ostruct/cli/template_filters.py +415 -8
  34. ostruct/cli/template_processor.py +240 -619
  35. ostruct/cli/template_rendering.py +49 -73
  36. ostruct/cli/template_validation.py +2 -1
  37. ostruct/cli/token_validation.py +35 -15
  38. ostruct/cli/types.py +15 -19
  39. ostruct/cli/unicode_compat.py +283 -0
  40. ostruct/cli/upload_manager.py +448 -0
  41. ostruct/cli/utils.py +30 -0
  42. ostruct/cli/validators.py +272 -54
  43. {ostruct_cli-0.8.8.dist-info → ostruct_cli-1.0.0.dist-info}/METADATA +292 -126
  44. ostruct_cli-1.0.0.dist-info/RECORD +80 -0
  45. ostruct/cli/commands/quick_ref.py +0 -54
  46. ostruct/cli/template_optimizer.py +0 -478
  47. ostruct_cli-0.8.8.dist-info/RECORD +0 -71
  48. {ostruct_cli-0.8.8.dist-info → ostruct_cli-1.0.0.dist-info}/LICENSE +0 -0
  49. {ostruct_cli-0.8.8.dist-info → ostruct_cli-1.0.0.dist-info}/WHEEL +0 -0
  50. {ostruct_cli-0.8.8.dist-info → ostruct_cli-1.0.0.dist-info}/entry_points.txt +0 -0
@@ -19,6 +19,7 @@ from .errors import (
19
19
  from .normalization import normalize_path
20
20
  from .safe_joiner import safe_join
21
21
  from .security_manager import SecurityManager
22
+ from .types import PathSecurity
22
23
 
23
24
  __all__ = [
24
25
  "normalize_path",
@@ -29,4 +30,5 @@ __all__ = [
29
30
  "DirectoryNotFoundError",
30
31
  "SecurityErrorReasons",
31
32
  "SecurityManager",
33
+ "PathSecurity",
32
34
  ]
@@ -4,11 +4,14 @@ This module provides functionality to verify that a given path is within
4
4
  one of a set of allowed directories.
5
5
  """
6
6
 
7
+ import logging
7
8
  from pathlib import Path
8
9
  from typing import List, Union
9
10
 
10
11
  from .normalization import normalize_path
11
12
 
13
+ logger = logging.getLogger(__name__)
14
+
12
15
 
13
16
  def is_path_in_allowed_dirs(
14
17
  path: Union[str, Path], allowed_dirs: List[Path]
@@ -17,6 +20,8 @@ def is_path_in_allowed_dirs(
17
20
 
18
21
  This function normalizes both the input path and allowed directories
19
22
  before comparison to ensure consistent results across platforms.
23
+ It also handles symlink resolution to ensure that resolved paths
24
+ can be compared correctly with allowed directories.
20
25
 
21
26
  Args:
22
27
  path: The path to check.
@@ -43,6 +48,7 @@ def is_path_in_allowed_dirs(
43
48
  norm_path = normalize_path(path)
44
49
  norm_allowed = [normalize_path(d) for d in allowed_dirs]
45
50
 
51
+ # Check with normalized paths first
46
52
  for allowed in norm_allowed:
47
53
  try:
48
54
  # If path.relative_to(allowed) does not raise an error,
@@ -52,4 +58,39 @@ def is_path_in_allowed_dirs(
52
58
  except ValueError:
53
59
  continue
54
60
 
61
+ # If normalized comparison failed, try with resolved paths
62
+ # This handles the case where the input path is already resolved (e.g., from validate_file_access)
63
+ # but the allowed directories are not resolved
64
+ try:
65
+ # Check if the input path might already be resolved by comparing it with its own resolution
66
+ path_as_path = Path(path) if isinstance(path, str) else path
67
+
68
+ # If the input path is already resolved, resolve allowed dirs to match
69
+ # We determine this by checking if the path starts with common temp resolved prefixes
70
+ path_str = str(path_as_path)
71
+ if path_str.startswith("/var/folders/") or path_str.startswith(
72
+ "/private/"
73
+ ):
74
+ # This looks like an already-resolved path on macOS, resolve allowed dirs
75
+ resolved_allowed = [d.resolve() for d in norm_allowed]
76
+ for allowed in resolved_allowed:
77
+ try:
78
+ path_as_path.relative_to(allowed)
79
+ return True
80
+ except ValueError:
81
+ continue
82
+ else:
83
+ # Try resolving both path and allowed dirs
84
+ resolved_path = norm_path.resolve()
85
+ resolved_allowed = [d.resolve() for d in norm_allowed]
86
+ for allowed in resolved_allowed:
87
+ try:
88
+ resolved_path.relative_to(allowed)
89
+ return True
90
+ except ValueError:
91
+ continue
92
+ except (OSError, RuntimeError):
93
+ # If resolution fails, fall back to normalized comparison result
94
+ pass
95
+
55
96
  return False
@@ -68,7 +68,9 @@ _BACKSLASH_PATTERN = re.compile(r"\\")
68
68
  _MULTIPLE_SLASH_PATTERN = re.compile(r"/+")
69
69
 
70
70
 
71
- def normalize_path(path: Union[str, Path]) -> Path:
71
+ def normalize_path(
72
+ path: Union[str, Path], allow_traversal: bool = False
73
+ ) -> Path:
72
74
  """Normalize a path string with security checks.
73
75
 
74
76
  This function:
@@ -94,6 +96,7 @@ def normalize_path(path: Union[str, Path]) -> Path:
94
96
 
95
97
  Args:
96
98
  path: A string or Path object representing a file path.
99
+ allow_traversal: If True, allow ".." components in paths (for explicitly allowed files).
97
100
 
98
101
  Returns:
99
102
  A pathlib.Path object for the normalized absolute path.
@@ -132,14 +135,15 @@ def normalize_path(path: Union[str, Path]) -> Path:
132
135
  if match := _UNICODE_SAFETY_PATTERN.search(normalized):
133
136
  matched_text = match.group(0)
134
137
  if ".." in matched_text:
135
- raise PathSecurityError(
136
- "Directory traversal not allowed",
137
- path=path_str,
138
- context={
139
- "reason": SecurityErrorReasons.PATH_TRAVERSAL,
140
- "matched": matched_text,
141
- },
142
- )
138
+ if not allow_traversal:
139
+ raise PathSecurityError(
140
+ "Directory traversal not allowed",
141
+ path=path_str,
142
+ context={
143
+ "reason": SecurityErrorReasons.PATH_TRAVERSAL,
144
+ "matched": matched_text,
145
+ },
146
+ )
143
147
  else:
144
148
  raise PathSecurityError(
145
149
  "Path contains unsafe characters",