10xgraph-api 0.7.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 (203) hide show
  1. 10xgraph_api-0.7.0.dist-info/METADATA +618 -0
  2. 10xgraph_api-0.7.0.dist-info/RECORD +203 -0
  3. 10xgraph_api-0.7.0.dist-info/WHEEL +5 -0
  4. 10xgraph_api-0.7.0.dist-info/entry_points.txt +3 -0
  5. 10xgraph_api-0.7.0.dist-info/licenses/LICENSE +21 -0
  6. 10xgraph_api-0.7.0.dist-info/top_level.txt +2 -0
  7. agentflow_cli/__init__.py +69 -0
  8. tenxgraph_api/__init__.py +23 -0
  9. tenxgraph_api/cli/__init__.py +6 -0
  10. tenxgraph_api/cli/capabilities.py +127 -0
  11. tenxgraph_api/cli/commands/__init__.py +49 -0
  12. tenxgraph_api/cli/commands/api.py +262 -0
  13. tenxgraph_api/cli/commands/audit.py +213 -0
  14. tenxgraph_api/cli/commands/build.py +305 -0
  15. tenxgraph_api/cli/commands/config.py +71 -0
  16. tenxgraph_api/cli/commands/demo.py +93 -0
  17. tenxgraph_api/cli/commands/eval.py +909 -0
  18. tenxgraph_api/cli/commands/init.py +531 -0
  19. tenxgraph_api/cli/commands/skills.py +532 -0
  20. tenxgraph_api/cli/commands/test.py +113 -0
  21. tenxgraph_api/cli/commands/version.py +47 -0
  22. tenxgraph_api/cli/config_editor/__init__.py +15 -0
  23. tenxgraph_api/cli/config_editor/schema.py +566 -0
  24. tenxgraph_api/cli/config_editor/server.py +240 -0
  25. tenxgraph_api/cli/config_editor/static/app.css +2 -0
  26. tenxgraph_api/cli/config_editor/static/app.js +3 -0
  27. tenxgraph_api/cli/config_editor/static/favicon.svg +11 -0
  28. tenxgraph_api/cli/config_editor/static/index.html +20 -0
  29. tenxgraph_api/cli/config_editor/store.py +120 -0
  30. tenxgraph_api/cli/config_editor/validation.py +377 -0
  31. tenxgraph_api/cli/constants.py +112 -0
  32. tenxgraph_api/cli/context.py +46 -0
  33. tenxgraph_api/cli/core/__init__.py +0 -0
  34. tenxgraph_api/cli/core/animations.py +550 -0
  35. tenxgraph_api/cli/core/config.py +279 -0
  36. tenxgraph_api/cli/core/output.py +625 -0
  37. tenxgraph_api/cli/core/prompts.py +220 -0
  38. tenxgraph_api/cli/core/screen.py +287 -0
  39. tenxgraph_api/cli/core/steps.py +621 -0
  40. tenxgraph_api/cli/core/theme.py +242 -0
  41. tenxgraph_api/cli/core/validation.py +251 -0
  42. tenxgraph_api/cli/exceptions.py +129 -0
  43. tenxgraph_api/cli/logger.py +84 -0
  44. tenxgraph_api/cli/main.py +936 -0
  45. tenxgraph_api/cli/templates/__init__.py +0 -0
  46. tenxgraph_api/cli/templates/defaults.py +844 -0
  47. tenxgraph_api/cli/templates/dev/.env.example +1 -0
  48. tenxgraph_api/cli/templates/dev/10xgraph.json +16 -0
  49. tenxgraph_api/cli/templates/dev/graph/__init__.py +0 -0
  50. tenxgraph_api/cli/templates/dev/graph/agent.py +63 -0
  51. tenxgraph_api/cli/templates/prod/.env.example +75 -0
  52. tenxgraph_api/cli/templates/prod/.pre-commit-config.yaml +50 -0
  53. tenxgraph_api/cli/templates/prod/.python-version +1 -0
  54. tenxgraph_api/cli/templates/prod/10xgraph.json +42 -0
  55. tenxgraph_api/cli/templates/prod/auth/__init__.py +0 -0
  56. tenxgraph_api/cli/templates/prod/auth/agent_auth.py +46 -0
  57. tenxgraph_api/cli/templates/prod/evals/__init__.py +0 -0
  58. tenxgraph_api/cli/templates/prod/evals/user_simulator_eval.py +76 -0
  59. tenxgraph_api/cli/templates/prod/evals/weather_agents_eval.py +53 -0
  60. tenxgraph_api/cli/templates/prod/graph/__init__.py +0 -0
  61. tenxgraph_api/cli/templates/prod/graph/agent.py +50 -0
  62. tenxgraph_api/cli/templates/prod/graph/state.py +5 -0
  63. tenxgraph_api/cli/templates/prod/graph/thread_name_generator.py +7 -0
  64. tenxgraph_api/cli/templates/prod/graph/tools/__init__.py +0 -0
  65. tenxgraph_api/cli/templates/prod/graph/tools/weather_tool.py +43 -0
  66. tenxgraph_api/cli/templates/prod/graph/validators/__init__.py +0 -0
  67. tenxgraph_api/cli/templates/prod/graph/validators/lifecyle.py +107 -0
  68. tenxgraph_api/cli/templates/prod/graph/validators/manager.py +9 -0
  69. tenxgraph_api/cli/templates/prod/graph/validators/validators.py +21 -0
  70. tenxgraph_api/cli/templates/prod/pyproject.toml +79 -0
  71. tenxgraph_api/cli/templates/prod/tests/__init__.py +0 -0
  72. tenxgraph_api/cli/templates/prod/tests/conftest.py +6 -0
  73. tenxgraph_api/cli/templates/prod/tests/test_agent_eval.py +78 -0
  74. tenxgraph_api/cli/templates/prod/tests/test_catalog_tools.py +47 -0
  75. tenxgraph_api/cli/templates/prod/tests/test_graph_nodes.py +57 -0
  76. tenxgraph_api/cli/templates/skills/10xgraph/SKILL.md +92 -0
  77. tenxgraph_api/cli/templates/skills/10xgraph/references/agents-and-tools.md +389 -0
  78. tenxgraph_api/cli/templates/skills/10xgraph/references/api-client.md +61 -0
  79. tenxgraph_api/cli/templates/skills/10xgraph/references/api-configuration.md +75 -0
  80. tenxgraph_api/cli/templates/skills/10xgraph/references/api-settings-and-middleware.md +89 -0
  81. tenxgraph_api/cli/templates/skills/10xgraph/references/architecture.md +91 -0
  82. tenxgraph_api/cli/templates/skills/10xgraph/references/auth-and-authorization.md +149 -0
  83. tenxgraph_api/cli/templates/skills/10xgraph/references/callbacks-and-command.md +283 -0
  84. tenxgraph_api/cli/templates/skills/10xgraph/references/checkpointing-and-threads.md +63 -0
  85. tenxgraph_api/cli/templates/skills/10xgraph/references/cli-commands.md +73 -0
  86. tenxgraph_api/cli/templates/skills/10xgraph/references/client-auth-and-errors.md +94 -0
  87. tenxgraph_api/cli/templates/skills/10xgraph/references/client-messages-invoke-stream.md +115 -0
  88. tenxgraph_api/cli/templates/skills/10xgraph/references/client-threads-memory-files.md +80 -0
  89. tenxgraph_api/cli/templates/skills/10xgraph/references/context-id-background.md +80 -0
  90. tenxgraph_api/cli/templates/skills/10xgraph/references/dependency-injection.md +58 -0
  91. tenxgraph_api/cli/templates/skills/10xgraph/references/evaluation.md +500 -0
  92. tenxgraph_api/cli/templates/skills/10xgraph/references/id-and-thread-name-generators.md +69 -0
  93. tenxgraph_api/cli/templates/skills/10xgraph/references/media-and-files.md +66 -0
  94. tenxgraph_api/cli/templates/skills/10xgraph/references/memory-and-store.md +70 -0
  95. tenxgraph_api/cli/templates/skills/10xgraph/references/prebuilt-agents-and-tools.md +394 -0
  96. tenxgraph_api/cli/templates/skills/10xgraph/references/production-runtime.md +56 -0
  97. tenxgraph_api/cli/templates/skills/10xgraph/references/providers-and-adapters.md +89 -0
  98. tenxgraph_api/cli/templates/skills/10xgraph/references/publishers-and-runtime-protocols.md +84 -0
  99. tenxgraph_api/cli/templates/skills/10xgraph/references/rate-limiting.md +194 -0
  100. tenxgraph_api/cli/templates/skills/10xgraph/references/realtime.md +172 -0
  101. tenxgraph_api/cli/templates/skills/10xgraph/references/remote-tools.md +55 -0
  102. tenxgraph_api/cli/templates/skills/10xgraph/references/rest-api-and-errors.md +97 -0
  103. tenxgraph_api/cli/templates/skills/10xgraph/references/security-and-validators.md +86 -0
  104. tenxgraph_api/cli/templates/skills/10xgraph/references/state-and-messages.md +68 -0
  105. tenxgraph_api/cli/templates/skills/10xgraph/references/state-graph.md +232 -0
  106. tenxgraph_api/cli/templates/skills/10xgraph/references/stream-emitter.md +144 -0
  107. tenxgraph_api/cli/templates/skills/10xgraph/references/streaming.md +53 -0
  108. tenxgraph_api/cli/templates/skills/10xgraph/references/testing-and-evaluation.md +64 -0
  109. tenxgraph_api/cli/templates/skills/10xgraph/references/unit-testing.md +202 -0
  110. tenxgraph_api/cli/templates/skills/copilot/10xgraph.instructions.md +108 -0
  111. tenxgraph_api/py.typed +0 -0
  112. tenxgraph_api/src/__init__.py +0 -0
  113. tenxgraph_api/src/app/__init__.py +13 -0
  114. tenxgraph_api/src/app/core/__init__.py +15 -0
  115. tenxgraph_api/src/app/core/auth/__init__.py +0 -0
  116. tenxgraph_api/src/app/core/auth/auth_backend.py +38 -0
  117. tenxgraph_api/src/app/core/auth/authorization.py +379 -0
  118. tenxgraph_api/src/app/core/auth/base_auth.py +27 -0
  119. tenxgraph_api/src/app/core/auth/jwt_auth.py +122 -0
  120. tenxgraph_api/src/app/core/auth/ownership_resolver.py +170 -0
  121. tenxgraph_api/src/app/core/auth/permissions.py +354 -0
  122. tenxgraph_api/src/app/core/auth/request_config.py +130 -0
  123. tenxgraph_api/src/app/core/auth/route_guard.py +105 -0
  124. tenxgraph_api/src/app/core/config/__init__.py +0 -0
  125. tenxgraph_api/src/app/core/config/graph_config.py +532 -0
  126. tenxgraph_api/src/app/core/config/media_settings.py +72 -0
  127. tenxgraph_api/src/app/core/config/sentry_config.py +60 -0
  128. tenxgraph_api/src/app/core/config/settings.py +202 -0
  129. tenxgraph_api/src/app/core/config/setup_logs.py +65 -0
  130. tenxgraph_api/src/app/core/config/setup_middleware.py +483 -0
  131. tenxgraph_api/src/app/core/config/worker_middleware.py +99 -0
  132. tenxgraph_api/src/app/core/exceptions/__init__.py +17 -0
  133. tenxgraph_api/src/app/core/exceptions/general_exception.py +58 -0
  134. tenxgraph_api/src/app/core/exceptions/handle_errors.py +363 -0
  135. tenxgraph_api/src/app/core/exceptions/resources_exceptions.py +72 -0
  136. tenxgraph_api/src/app/core/exceptions/user_exception.py +58 -0
  137. tenxgraph_api/src/app/core/middleware/__init__.py +25 -0
  138. tenxgraph_api/src/app/core/middleware/rate_limit/__init__.py +15 -0
  139. tenxgraph_api/src/app/core/middleware/rate_limit/base.py +27 -0
  140. tenxgraph_api/src/app/core/middleware/rate_limit/factory.py +98 -0
  141. tenxgraph_api/src/app/core/middleware/rate_limit/keying.py +133 -0
  142. tenxgraph_api/src/app/core/middleware/rate_limit/memory.py +98 -0
  143. tenxgraph_api/src/app/core/middleware/rate_limit/middleware.py +90 -0
  144. tenxgraph_api/src/app/core/middleware/rate_limit/redis.py +115 -0
  145. tenxgraph_api/src/app/core/middleware/request_limits.py +154 -0
  146. tenxgraph_api/src/app/core/middleware/security_headers.py +239 -0
  147. tenxgraph_api/src/app/core/utils/__init__.py +14 -0
  148. tenxgraph_api/src/app/core/utils/log_sanitizer.py +216 -0
  149. tenxgraph_api/src/app/loader.py +516 -0
  150. tenxgraph_api/src/app/main.py +180 -0
  151. tenxgraph_api/src/app/routers/__init__.py +4 -0
  152. tenxgraph_api/src/app/routers/ag_ui/__init__.py +10 -0
  153. tenxgraph_api/src/app/routers/ag_ui/converter.py +195 -0
  154. tenxgraph_api/src/app/routers/ag_ui/event_mapper.py +353 -0
  155. tenxgraph_api/src/app/routers/ag_ui/router.py +90 -0
  156. tenxgraph_api/src/app/routers/ag_ui/service.py +193 -0
  157. tenxgraph_api/src/app/routers/checkpointer/__init__.py +1 -0
  158. tenxgraph_api/src/app/routers/checkpointer/router.py +482 -0
  159. tenxgraph_api/src/app/routers/checkpointer/schemas/__init__.py +1 -0
  160. tenxgraph_api/src/app/routers/checkpointer/schemas/checkpointer_schemas.py +121 -0
  161. tenxgraph_api/src/app/routers/checkpointer/services/__init__.py +1 -0
  162. tenxgraph_api/src/app/routers/checkpointer/services/checkpointer_service.py +300 -0
  163. tenxgraph_api/src/app/routers/evals/__init__.py +6 -0
  164. tenxgraph_api/src/app/routers/evals/router.py +38 -0
  165. tenxgraph_api/src/app/routers/evals/services/__init__.py +1 -0
  166. tenxgraph_api/src/app/routers/evals/services/eval_report_service.py +354 -0
  167. tenxgraph_api/src/app/routers/graph/__init__.py +6 -0
  168. tenxgraph_api/src/app/routers/graph/realtime_guard.py +161 -0
  169. tenxgraph_api/src/app/routers/graph/router.py +821 -0
  170. tenxgraph_api/src/app/routers/graph/schemas/__init__.py +1 -0
  171. tenxgraph_api/src/app/routers/graph/schemas/graph_schemas.py +386 -0
  172. tenxgraph_api/src/app/routers/graph/services/__init__.py +1 -0
  173. tenxgraph_api/src/app/routers/graph/services/graph_service.py +1219 -0
  174. tenxgraph_api/src/app/routers/graph/services/multimodal_preprocessor.py +164 -0
  175. tenxgraph_api/src/app/routers/media/__init__.py +506 -0
  176. tenxgraph_api/src/app/routers/media/router.py +242 -0
  177. tenxgraph_api/src/app/routers/media/schemas.py +39 -0
  178. tenxgraph_api/src/app/routers/ping/__init__.py +4 -0
  179. tenxgraph_api/src/app/routers/ping/router.py +28 -0
  180. tenxgraph_api/src/app/routers/setup_router.py +60 -0
  181. tenxgraph_api/src/app/routers/store/__init__.py +4 -0
  182. tenxgraph_api/src/app/routers/store/router.py +200 -0
  183. tenxgraph_api/src/app/routers/store/schemas/__init__.py +28 -0
  184. tenxgraph_api/src/app/routers/store/schemas/store_schemas.py +200 -0
  185. tenxgraph_api/src/app/routers/store/services/__init__.py +4 -0
  186. tenxgraph_api/src/app/routers/store/services/store_service.py +203 -0
  187. tenxgraph_api/src/app/tasks/__init__.py +0 -0
  188. tenxgraph_api/src/app/tasks/user_tasks.py +38 -0
  189. tenxgraph_api/src/app/utils/__init__.py +12 -0
  190. tenxgraph_api/src/app/utils/callable_helper.py +26 -0
  191. tenxgraph_api/src/app/utils/media/__init__.py +15 -0
  192. tenxgraph_api/src/app/utils/media/extractor.py +172 -0
  193. tenxgraph_api/src/app/utils/media/pipeline.py +83 -0
  194. tenxgraph_api/src/app/utils/parse_output.py +17 -0
  195. tenxgraph_api/src/app/utils/response_helper.py +129 -0
  196. tenxgraph_api/src/app/utils/schemas/__init__.py +11 -0
  197. tenxgraph_api/src/app/utils/schemas/output_schemas.py +28 -0
  198. tenxgraph_api/src/app/utils/schemas/user_schemas.py +13 -0
  199. tenxgraph_api/src/app/utils/snowflake_id_generator.py +90 -0
  200. tenxgraph_api/src/app/utils/swagger_helper.py +160 -0
  201. tenxgraph_api/src/app/utils/telemetry_store.py +123 -0
  202. tenxgraph_api/src/app/utils/thread_name_generator.py +304 -0
  203. tenxgraph_api/src/app/worker.py +91 -0
@@ -0,0 +1,239 @@
1
+ """
2
+ Security Headers Middleware
3
+
4
+ This middleware adds standard security headers to HTTP responses to protect
5
+ against common web vulnerabilities.
6
+
7
+ Headers Added:
8
+ - X-Content-Type-Options: Prevents MIME-type sniffing
9
+ - X-Frame-Options: Prevents clickjacking attacks
10
+ - X-XSS-Protection: Enables XSS filtering (legacy browsers)
11
+ - Strict-Transport-Security: Enforces HTTPS (if HTTPS is detected)
12
+ - Content-Security-Policy: Controls resource loading
13
+ - Referrer-Policy: Controls referrer information
14
+ - Permissions-Policy: Controls browser features
15
+
16
+ Configuration:
17
+ Configure via environment variables or settings:
18
+ - SECURITY_HEADERS_ENABLED: Enable/disable middleware (default: true)
19
+ - HSTS_MAX_AGE: HSTS max-age in seconds (default: 31536000 = 1 year)
20
+ - CSP_POLICY: Custom CSP policy (default: strict policy)
21
+ """
22
+
23
+ from collections.abc import Callable
24
+
25
+ from starlette.middleware.base import BaseHTTPMiddleware
26
+ from starlette.requests import Request
27
+ from starlette.responses import Response
28
+
29
+
30
+ class SecurityHeadersMiddleware(BaseHTTPMiddleware):
31
+ """
32
+ Middleware to add security headers to responses.
33
+
34
+ This middleware enhances security by adding standard security headers
35
+ that protect against common web vulnerabilities.
36
+ """
37
+
38
+ def __init__( # noqa: PLR0913
39
+ self,
40
+ app,
41
+ enable_hsts: bool = True,
42
+ hsts_max_age: int = 31536000, # 1 year
43
+ hsts_include_subdomains: bool = True,
44
+ hsts_preload: bool = False,
45
+ frame_options: str = "DENY",
46
+ content_type_options: str = "nosniff",
47
+ xss_protection: str = "1; mode=block",
48
+ referrer_policy: str = "strict-origin-when-cross-origin",
49
+ permissions_policy: str | None = None,
50
+ csp_policy: str | None = None,
51
+ ):
52
+ """
53
+ Initialize security headers middleware.
54
+
55
+ Args:
56
+ app: ASGI application
57
+ enable_hsts: Enable Strict-Transport-Security header
58
+ hsts_max_age: HSTS max-age in seconds (default: 1 year)
59
+ hsts_include_subdomains: Include subdomains in HSTS
60
+ hsts_preload: Enable HSTS preload
61
+ frame_options: X-Frame-Options value (DENY, SAMEORIGIN, or ALLOW-FROM)
62
+ content_type_options: X-Content-Type-Options value
63
+ xss_protection: X-XSS-Protection value
64
+ referrer_policy: Referrer-Policy value
65
+ permissions_policy: Permissions-Policy value (optional)
66
+ csp_policy: Content-Security-Policy value (optional)
67
+ """
68
+ super().__init__(app)
69
+ self.enable_hsts = enable_hsts
70
+ self.hsts_max_age = hsts_max_age
71
+ self.hsts_include_subdomains = hsts_include_subdomains
72
+ self.hsts_preload = hsts_preload
73
+ self.frame_options = frame_options
74
+ self.content_type_options = content_type_options
75
+ self.xss_protection = xss_protection
76
+ self.referrer_policy = referrer_policy
77
+ self.permissions_policy = permissions_policy or self._default_permissions_policy()
78
+ self.csp_policy = csp_policy or self._default_csp_policy()
79
+
80
+ def _default_permissions_policy(self) -> str:
81
+ """
82
+ Get default Permissions-Policy header value.
83
+
84
+ Returns:
85
+ Default permissions policy string
86
+ """
87
+ return (
88
+ "geolocation=(), microphone=(), camera=(), payment=(), usb=(), "
89
+ "magnetometer=(), gyroscope=(), accelerometer=()"
90
+ )
91
+
92
+ def _default_csp_policy(self) -> str:
93
+ """
94
+ Get default Content-Security-Policy header value.
95
+
96
+ Returns:
97
+ Default CSP policy string
98
+ """
99
+ return (
100
+ "default-src 'self'; "
101
+ "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://unpkg.com; "
102
+ "style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://fonts.googleapis.com;"
103
+ "img-src 'self' data: https:; "
104
+ "font-src 'self' data: https://fonts.gstatic.com; "
105
+ "connect-src 'self'; "
106
+ "frame-ancestors 'none'; "
107
+ "base-uri 'self'; "
108
+ "form-action 'self'"
109
+ )
110
+
111
+ def _build_hsts_header(self) -> str:
112
+ """
113
+ Build HSTS header value.
114
+
115
+ Returns:
116
+ HSTS header string
117
+ """
118
+ hsts_parts = [f"max-age={self.hsts_max_age}"]
119
+
120
+ if self.hsts_include_subdomains:
121
+ hsts_parts.append("includeSubDomains")
122
+
123
+ if self.hsts_preload:
124
+ hsts_parts.append("preload")
125
+
126
+ return "; ".join(hsts_parts)
127
+
128
+ def _is_https(self, request: Request) -> bool:
129
+ """
130
+ Check if request is using HTTPS.
131
+
132
+ Checks both the request scheme and X-Forwarded-Proto header
133
+ (for proxied requests).
134
+
135
+ Args:
136
+ request: Starlette request object
137
+
138
+ Returns:
139
+ True if HTTPS, False otherwise
140
+ """
141
+ # Check request scheme
142
+ if request.url.scheme == "https":
143
+ return True
144
+
145
+ # Check X-Forwarded-Proto header (for proxied requests)
146
+ forwarded_proto = request.headers.get("X-Forwarded-Proto", "")
147
+ return forwarded_proto.lower() == "https"
148
+
149
+ async def dispatch(self, request: Request, call_next: Callable) -> Response:
150
+ """
151
+ Process request and add security headers to response.
152
+
153
+ Args:
154
+ request: Incoming request
155
+ call_next: Next middleware/handler
156
+
157
+ Returns:
158
+ Response with security headers
159
+ """
160
+ # Process request
161
+ response = await call_next(request)
162
+
163
+ # Add X-Content-Type-Options header
164
+ response.headers["X-Content-Type-Options"] = self.content_type_options
165
+
166
+ # Add X-Frame-Options header
167
+ response.headers["X-Frame-Options"] = self.frame_options
168
+
169
+ # Add X-XSS-Protection header (for legacy browser support)
170
+ response.headers["X-XSS-Protection"] = self.xss_protection
171
+
172
+ # Add Referrer-Policy header
173
+ response.headers["Referrer-Policy"] = self.referrer_policy
174
+
175
+ # Add Permissions-Policy header
176
+ if self.permissions_policy:
177
+ response.headers["Permissions-Policy"] = self.permissions_policy
178
+
179
+ # Add Content-Security-Policy header
180
+ if self.csp_policy:
181
+ response.headers["Content-Security-Policy"] = self.csp_policy
182
+
183
+ # Add Strict-Transport-Security header (only for HTTPS)
184
+ if self.enable_hsts and self._is_https(request):
185
+ response.headers["Strict-Transport-Security"] = self._build_hsts_header()
186
+
187
+ return response
188
+
189
+
190
+ def create_security_headers_middleware(
191
+ enable_hsts: bool = True,
192
+ hsts_max_age: int = 31536000,
193
+ hsts_include_subdomains: bool = True,
194
+ hsts_preload: bool = False,
195
+ frame_options: str = "DENY",
196
+ content_type_options: str = "nosniff",
197
+ xss_protection: str = "1; mode=block",
198
+ referrer_policy: str = "strict-origin-when-cross-origin",
199
+ permissions_policy: str | None = None,
200
+ csp_policy: str | None = None,
201
+ ) -> type[SecurityHeadersMiddleware]:
202
+ """
203
+ Factory function to create SecurityHeadersMiddleware with configuration.
204
+
205
+ This is a convenience function for creating middleware with custom settings.
206
+
207
+ Args:
208
+ enable_hsts: Enable HSTS header
209
+ hsts_max_age: HSTS max-age in seconds
210
+ hsts_include_subdomains: Include subdomains in HSTS
211
+ hsts_preload: Enable HSTS preload
212
+ frame_options: X-Frame-Options value
213
+ content_type_options: X-Content-Type-Options value
214
+ xss_protection: X-XSS-Protection value
215
+ referrer_policy: Referrer-Policy value
216
+ permissions_policy: Permissions-Policy value
217
+ csp_policy: Content-Security-Policy value
218
+
219
+ Returns:
220
+ Configured SecurityHeadersMiddleware class
221
+ """
222
+
223
+ class ConfiguredSecurityHeadersMiddleware(SecurityHeadersMiddleware):
224
+ def __init__(self, app):
225
+ super().__init__(
226
+ app,
227
+ enable_hsts=enable_hsts,
228
+ hsts_max_age=hsts_max_age,
229
+ hsts_include_subdomains=hsts_include_subdomains,
230
+ hsts_preload=hsts_preload,
231
+ frame_options=frame_options,
232
+ content_type_options=content_type_options,
233
+ xss_protection=xss_protection,
234
+ referrer_policy=referrer_policy,
235
+ permissions_policy=permissions_policy,
236
+ csp_policy=csp_policy,
237
+ )
238
+
239
+ return ConfiguredSecurityHeadersMiddleware
@@ -0,0 +1,14 @@
1
+ """Core utilities for 10xGraph CLI."""
2
+
3
+ from tenxgraph_api.src.app.core.utils.log_sanitizer import (
4
+ SanitizingFormatter,
5
+ sanitize_for_logging,
6
+ sanitize_log_message,
7
+ )
8
+
9
+
10
+ __all__ = [
11
+ "sanitize_for_logging",
12
+ "sanitize_log_message",
13
+ "SanitizingFormatter",
14
+ ]
@@ -0,0 +1,216 @@
1
+ """
2
+ Log sanitization utilities for 10xGraph CLI.
3
+
4
+ This module provides utilities to sanitize sensitive data before logging,
5
+ preventing tokens, passwords, and other credentials from appearing in logs.
6
+ """
7
+
8
+ import logging
9
+ import re
10
+ from typing import Any
11
+
12
+
13
+ # Patterns for detecting sensitive field names
14
+ SENSITIVE_PATTERNS = {
15
+ "token",
16
+ "password",
17
+ "secret",
18
+ "key",
19
+ "credential",
20
+ "authorization",
21
+ "api_key",
22
+ "access_token",
23
+ "refresh_token",
24
+ "auth",
25
+ "bearer",
26
+ "jwt",
27
+ "session",
28
+ "cookie",
29
+ "private_key",
30
+ "passphrase",
31
+ "pin",
32
+ "ssn",
33
+ "credit_card",
34
+ }
35
+
36
+ # Regex pattern to detect JWT tokens (three base64url parts separated by dots)
37
+ JWT_PATTERN = re.compile(r"^[A-Za-z0-9-_=]+\.[A-Za-z0-9-_=]+\.?[A-Za-z0-9-_.+/=]*$")
38
+
39
+ # Regex pattern to detect bearer tokens
40
+ BEARER_PATTERN = re.compile(r"^Bearer\s+[A-Za-z0-9\-_.~+/]+=*$", re.IGNORECASE)
41
+
42
+ # Tokens embedded anywhere in a message (f-string logs carry no args to sanitize).
43
+ _INLINE_SECRET_PATTERNS = (
44
+ (re.compile(r"Bearer\s+[A-Za-z0-9\-_.~+/]+=*", re.IGNORECASE), "Bearer ***REDACTED***"),
45
+ (re.compile(r"\beyJ[A-Za-z0-9_-]{5,}\.[A-Za-z0-9_-]{5,}\.[A-Za-z0-9_-]*"), "***JWT_TOKEN***"),
46
+ )
47
+
48
+
49
+ def redact_text(text: str) -> str:
50
+ """Redact bearer tokens and JWTs that appear anywhere inside ``text``."""
51
+ for pattern, replacement in _INLINE_SECRET_PATTERNS:
52
+ text = pattern.sub(replacement, text)
53
+ return text
54
+
55
+
56
+ def sanitize_for_logging(data: Any, max_depth: int = 10, _current_depth: int = 0) -> Any:
57
+ """
58
+ Recursively sanitize sensitive data for safe logging.
59
+
60
+ This function walks through data structures and replaces sensitive values
61
+ with redaction markers. It detects:
62
+ - Dictionary keys containing sensitive patterns
63
+ - JWT tokens (by structure)
64
+ - Bearer tokens
65
+ - Authorization headers
66
+
67
+ Args:
68
+ data: The data to sanitize (dict, list, str, or any other type)
69
+ max_depth: Maximum recursion depth to prevent infinite loops
70
+ _current_depth: Internal parameter tracking current recursion depth
71
+
72
+ Returns:
73
+ Sanitized copy of the data with sensitive values redacted
74
+
75
+ Examples:
76
+ >>> sanitize_for_logging({"user_id": "123", "token": "abc123"})
77
+ {'user_id': '123', 'token': '***REDACTED***'}
78
+
79
+ >>> sanitize_for_logging({"Authorization": "Bearer eyJhbGc..."})
80
+ {'Authorization': '***REDACTED***'}
81
+ """
82
+ # Prevent excessive recursion
83
+ if _current_depth >= max_depth:
84
+ return "***MAX_DEPTH_REACHED***"
85
+
86
+ if isinstance(data, dict):
87
+ return {k: _sanitize_value(k, v, max_depth, _current_depth + 1) for k, v in data.items()}
88
+ if isinstance(data, list):
89
+ return [sanitize_for_logging(item, max_depth, _current_depth + 1) for item in data]
90
+ if isinstance(data, tuple):
91
+ return tuple(sanitize_for_logging(item, max_depth, _current_depth + 1) for item in data)
92
+ if isinstance(data, str):
93
+ return _sanitize_string(data)
94
+ return data
95
+
96
+
97
+ def _sanitize_value(key: str, value: Any, max_depth: int, current_depth: int) -> Any:
98
+ """
99
+ Sanitize a value based on its key name and content.
100
+
101
+ Args:
102
+ key: The dictionary key
103
+ value: The value to sanitize
104
+ max_depth: Maximum recursion depth
105
+ current_depth: Current recursion depth
106
+
107
+ Returns:
108
+ Sanitized value
109
+ """
110
+ # Check if key name contains sensitive patterns
111
+ key_lower = key.lower()
112
+ if any(pattern in key_lower for pattern in SENSITIVE_PATTERNS):
113
+ return "***REDACTED***"
114
+
115
+ # Recursively sanitize the value
116
+ return sanitize_for_logging(value, max_depth, current_depth)
117
+
118
+
119
+ def _sanitize_string(value: str) -> str:
120
+ """
121
+ Sanitize a string value if it appears to be sensitive.
122
+
123
+ Args:
124
+ value: String to check and potentially sanitize
125
+
126
+ Returns:
127
+ Original string or redaction marker
128
+ """
129
+ max_value = 32
130
+ # Check for JWT token pattern
131
+ if len(value) > max_value and JWT_PATTERN.match(value):
132
+ return "***JWT_TOKEN***"
133
+
134
+ # Check for Bearer token pattern
135
+ if BEARER_PATTERN.match(value):
136
+ return "***BEARER_TOKEN***"
137
+
138
+ # Check if string looks like a long random token (>32 chars, alphanumeric)
139
+ if len(value) > max_value and value.replace("-", "").replace("_", "").isalnum():
140
+ # Could be an API key or token
141
+ return f"{value[:4]}...{value[-4:]}"
142
+
143
+ return value
144
+
145
+
146
+ def sanitize_log_message(message: str, *args: Any, **kwargs: Any) -> tuple[str, tuple, dict]:
147
+ """
148
+ Sanitize log message arguments.
149
+
150
+ This is useful for sanitizing arguments passed to logger.debug(), logger.info(), etc.
151
+
152
+ Args:
153
+ message: Log message format string
154
+ *args: Positional arguments for the log message
155
+ **kwargs: Keyword arguments for the log message
156
+
157
+ Returns:
158
+ Tuple of (message, sanitized_args, sanitized_kwargs)
159
+
160
+ Examples:
161
+ >>> msg, args, kwargs = sanitize_log_message(
162
+ ... "User %s logged in", {"user_id": "123", "token": "secret"}
163
+ ... )
164
+ >>> # args will have sanitized data
165
+ """
166
+ sanitized_args = tuple(sanitize_for_logging(arg) for arg in args)
167
+ sanitized_kwargs = {k: sanitize_for_logging(v) for k, v in kwargs.items()}
168
+ return message, sanitized_args, sanitized_kwargs
169
+
170
+
171
+ class SanitizingFormatter(logging.Formatter):
172
+ """
173
+ A wrapper for log formatters that sanitizes sensitive data.
174
+
175
+ This can be used to wrap existing formatters to add sanitization. It is a
176
+ proper ``logging.Formatter`` subclass so it can be passed to
177
+ ``Handler.setFormatter`` without type errors, while delegating the actual
178
+ formatting to the wrapped base formatter.
179
+
180
+ Example:
181
+ import logging
182
+
183
+ formatter = logging.Formatter('%(asctime)s - %(message)s')
184
+ sanitizing_formatter = SanitizingFormatter(formatter)
185
+ handler.setFormatter(sanitizing_formatter)
186
+ """
187
+
188
+ def __init__(self, base_formatter: logging.Formatter) -> None:
189
+ """
190
+ Initialize the sanitizing formatter.
191
+
192
+ Args:
193
+ base_formatter: The underlying formatter to wrap
194
+ """
195
+ super().__init__()
196
+ self.base_formatter = base_formatter
197
+
198
+ def format(self, record: logging.LogRecord) -> str:
199
+ """
200
+ Format the log record with sanitization.
201
+
202
+ Args:
203
+ record: LogRecord to format
204
+
205
+ Returns:
206
+ Formatted and sanitized log string
207
+ """
208
+ # Sanitize the message arguments
209
+ if isinstance(record.args, tuple) and record.args:
210
+ record.args = tuple(sanitize_for_logging(arg) for arg in record.args)
211
+ elif isinstance(record.args, dict):
212
+ record.args = sanitize_for_logging(record.args)
213
+
214
+ # Format using the base formatter, then catch tokens already baked into the text
215
+ # (f-string messages, exception text, tracebacks).
216
+ return redact_text(self.base_formatter.format(record))