@vercel/python
Advanced tools
+2
-2
| { | ||
| "name": "@vercel/python", | ||
| "version": "6.0.0", | ||
| "version": "6.0.1", | ||
| "main": "./dist/index.js", | ||
@@ -24,3 +24,3 @@ "license": "Apache-2.0", | ||
| "@types/which": "3.0.0", | ||
| "@vercel/build-utils": "12.2.3", | ||
| "@vercel/build-utils": "12.2.4", | ||
| "cross-env": "7.0.3", | ||
@@ -27,0 +27,0 @@ "execa": "^1.0.0", |
+165
-79
@@ -0,1 +1,2 @@ | ||
| from __future__ import annotations | ||
| import sys | ||
@@ -8,6 +9,16 @@ import os | ||
| import inspect | ||
| import asyncio | ||
| import http | ||
| import time | ||
| from importlib import util | ||
| from http.server import BaseHTTPRequestHandler | ||
| from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer | ||
| import socket | ||
| import functools | ||
| import logging | ||
| import builtins | ||
| from typing import Callable, Literal, TextIO | ||
| import contextvars | ||
| import contextlib | ||
| _here = os.path.dirname(__file__) | ||
@@ -34,2 +45,149 @@ _vendor_rel = '__VC_HANDLER_VENDOR_DIR' | ||
| def setup_logging(send_message: Callable[[dict], None], storage: contextvars.ContextVar[dict | None]): | ||
| # Override logging.Handler to send logs to the platform when a request context is available. | ||
| class VCLogHandler(logging.Handler): | ||
| def emit(self, record: logging.LogRecord): | ||
| try: | ||
| message = record.getMessage() | ||
| except Exception: | ||
| message = repr(getattr(record, "msg", "")) | ||
| if record.levelno >= logging.CRITICAL: | ||
| level = "fatal" | ||
| elif record.levelno >= logging.ERROR: | ||
| level = "error" | ||
| elif record.levelno >= logging.WARNING: | ||
| level = "warn" | ||
| elif record.levelno >= logging.INFO: | ||
| level = "info" | ||
| else: | ||
| level = "debug" | ||
| context = storage.get() | ||
| if context is not None: | ||
| send_message({ | ||
| "type": "log", | ||
| "payload": { | ||
| "context": { | ||
| "invocationId": context['invocationId'], | ||
| "requestId": context['requestId'], | ||
| }, | ||
| "message": base64.b64encode(message.encode()).decode(), | ||
| "level": level, | ||
| } | ||
| }) | ||
| else: | ||
| # If IPC is not ready, enqueue the message to be sent later. | ||
| enqueue_or_send_message({ | ||
| "type": "log", | ||
| "payload": { | ||
| "context": {"invocationId": "0", "requestId": 0}, | ||
| "message": base64.b64encode(message.encode()).decode(), | ||
| "level": level, | ||
| } | ||
| }) | ||
| # Override sys.stdout and sys.stderr to map logs to the correct request | ||
| class StreamWrapper: | ||
| def __init__(self, stream: TextIO, stream_name: Literal["stdout", "stderr"]): | ||
| self.stream = stream | ||
| self.stream_name = stream_name | ||
| def write(self, message: str): | ||
| context = storage.get() | ||
| if context is not None: | ||
| send_message({ | ||
| "type": "log", | ||
| "payload": { | ||
| "context": { | ||
| "invocationId": context['invocationId'], | ||
| "requestId": context['requestId'], | ||
| }, | ||
| "message": base64.b64encode(message.encode()).decode(), | ||
| "stream": self.stream_name, | ||
| } | ||
| }) | ||
| else: | ||
| enqueue_or_send_message({ | ||
| "type": "log", | ||
| "payload": { | ||
| "context": {"invocationId": "0", "requestId": 0}, | ||
| "message": base64.b64encode(message.encode()).decode(), | ||
| "stream": self.stream_name, | ||
| } | ||
| }) | ||
| def __getattr__(self, name): | ||
| return getattr(self.stream, name) | ||
| sys.stdout = StreamWrapper(sys.stdout, "stdout") | ||
| sys.stderr = StreamWrapper(sys.stderr, "stderr") | ||
| logging.basicConfig(level=logging.INFO, handlers=[VCLogHandler()], force=True) | ||
| # Ensure built-in print funnels through stdout wrapper so prints are | ||
| # attributed to the current request context. | ||
| def print_wrapper(func: Callable[..., None]) -> Callable[..., None]: | ||
| @functools.wraps(func) | ||
| def wrapper(*args, sep=' ', end='\n', file=None, flush=False): | ||
| if file is None: | ||
| file = sys.stdout | ||
| if file in (sys.stdout, sys.stderr): | ||
| file.write(sep.join(map(str, args)) + end) | ||
| if flush: | ||
| file.flush() | ||
| else: | ||
| # User specified a different file, use original print behavior | ||
| func(*args, sep=sep, end=end, file=file, flush=flush) | ||
| return wrapper | ||
| builtins.print = print_wrapper(builtins.print) | ||
| # If running in the platform (IPC present), logging must be setup before importing user code so that | ||
| # logs happening outside the request context are emitted correctly. | ||
| ipc_sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) | ||
| storage: contextvars.ContextVar[dict | None] = contextvars.ContextVar('storage', default=None) | ||
| send_message = lambda m: None | ||
| _original_stderr = sys.stderr | ||
| # Buffer for pre-handshake logs (to avoid blocking IPC on startup) | ||
| _ipc_ready = False | ||
| _init_log_buf: list[dict] = [] | ||
| _INIT_LOG_BUF_MAX_BYTES = 1_000_000 | ||
| _init_log_buf_bytes = 0 | ||
| def enqueue_or_send_message(msg: dict): | ||
| global _init_log_buf_bytes | ||
| if _ipc_ready: | ||
| send_message(msg) | ||
| return | ||
| enc_len = len(json.dumps(msg)) | ||
| if _init_log_buf_bytes + enc_len <= _INIT_LOG_BUF_MAX_BYTES: | ||
| _init_log_buf.append(msg) | ||
| _init_log_buf_bytes += enc_len | ||
| else: | ||
| # Fallback so message is not lost if buffer is full | ||
| with contextlib.suppress(Exception): | ||
| payload = msg.get("payload", {}) | ||
| decoded = base64.b64decode(payload.get("message", "")).decode(errors="ignore") | ||
| _original_stderr.write(decoded + "\n") | ||
| if 'VERCEL_IPC_PATH' in os.environ: | ||
| with contextlib.suppress(Exception): | ||
| ipc_sock.connect(os.getenv("VERCEL_IPC_PATH", "")) | ||
| def send_message(message: dict): | ||
| with contextlib.suppress(Exception): | ||
| ipc_sock.sendall((json.dumps(message) + '\0').encode()) | ||
| setup_logging(send_message, storage) | ||
| # Import relative path https://docs.python.org/3/library/importlib.html#importing-a-source-file-directly | ||
@@ -56,18 +214,6 @@ user_mod_path = os.path.join(_here, "__VC_HANDLER_ENTRYPOINT") # absolute | ||
| if 'VERCEL_IPC_PATH' in os.environ: | ||
| from http.server import ThreadingHTTPServer | ||
| import http | ||
| import time | ||
| import contextvars | ||
| import functools | ||
| import builtins | ||
| import logging | ||
| start_time = time.time() | ||
| sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) | ||
| sock.connect(os.getenv("VERCEL_IPC_PATH", "")) | ||
| send_message = lambda message: sock.sendall((json.dumps(message) + '\0').encode()) | ||
| storage = contextvars.ContextVar('storage', default=None) | ||
| # Override urlopen from urllib3 (& requests) to send Request Metrics | ||
@@ -116,67 +262,2 @@ try: | ||
| # Override sys.stdout and sys.stderr to map logs to the correct request | ||
| class StreamWrapper: | ||
| def __init__(self, stream, stream_name): | ||
| self.stream = stream | ||
| self.stream_name = stream_name | ||
| def write(self, message): | ||
| context = storage.get() | ||
| if context is not None: | ||
| send_message({ | ||
| "type": "log", | ||
| "payload": { | ||
| "context": { | ||
| "invocationId": context['invocationId'], | ||
| "requestId": context['requestId'], | ||
| }, | ||
| "message": base64.b64encode(message.encode()).decode(), | ||
| "stream": self.stream_name, | ||
| } | ||
| }) | ||
| else: | ||
| self.stream.write(message) | ||
| def __getattr__(self, name): | ||
| return getattr(self.stream, name) | ||
| sys.stdout = StreamWrapper(sys.stdout, "stdout") | ||
| sys.stderr = StreamWrapper(sys.stderr, "stderr") | ||
| # Override the global print to log to stdout | ||
| def print_wrapper(func): | ||
| @functools.wraps(func) | ||
| def wrapper(*args, **kwargs): | ||
| sys.stdout.write(' '.join(map(str, args)) + '\n') | ||
| return wrapper | ||
| builtins.print = print_wrapper(builtins.print) | ||
| # Override logging to maps logs to the correct request | ||
| def logging_wrapper(func, level="info"): | ||
| @functools.wraps(func) | ||
| def wrapper(*args, **kwargs): | ||
| context = storage.get() | ||
| if context is not None: | ||
| send_message({ | ||
| "type": "log", | ||
| "payload": { | ||
| "context": { | ||
| "invocationId": context['invocationId'], | ||
| "requestId": context['requestId'], | ||
| }, | ||
| "message": base64.b64encode(f"{args[0]}".encode()).decode(), | ||
| "level": level, | ||
| } | ||
| }) | ||
| else: | ||
| func(*args, **kwargs) | ||
| return wrapper | ||
| logging.basicConfig(level=logging.INFO) | ||
| logging.debug = logging_wrapper(logging.debug) | ||
| logging.info = logging_wrapper(logging.info) | ||
| logging.warning = logging_wrapper(logging.warning, "warn") | ||
| logging.error = logging_wrapper(logging.error, "error") | ||
| logging.critical = logging_wrapper(logging.critical, "error") | ||
| class BaseHandler(BaseHTTPRequestHandler): | ||
@@ -413,2 +494,7 @@ # Re-implementation of BaseHTTPRequestHandler's log_message method to | ||
| }) | ||
| # Mark IPC as ready and flush any buffered init logs | ||
| _ipc_ready = True | ||
| for m in _init_log_buf: | ||
| send_message(m) | ||
| _init_log_buf.clear() | ||
| server.serve_forever() | ||
@@ -415,0 +501,0 @@ |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 9 instances in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 9 instances in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
326017
1.85%9006
1.36%