From e2c2ec1892975797d4e6bc719c5dcc46f4a1199e Mon Sep 17 00:00:00 2001 From: Marc Kelechava Date: Tue, 9 Dec 2025 11:38:09 -0800 Subject: [PATCH] Masking secrets after templating creds in HttpRequest Block (#4248) --- skyvern/forge/sdk/workflow/context_manager.py | 27 +++++++++++++++++++ skyvern/forge/sdk/workflow/models/block.py | 3 +++ 2 files changed, 30 insertions(+) diff --git a/skyvern/forge/sdk/workflow/context_manager.py b/skyvern/forge/sdk/workflow/context_manager.py index b12d85a5..9e9a7c75 100644 --- a/skyvern/forge/sdk/workflow/context_manager.py +++ b/skyvern/forge/sdk/workflow/context_manager.py @@ -235,6 +235,33 @@ class WorkflowRunContext: return self.secrets.get(secret_id_or_value) return None + def mask_secrets_in_data(self, data: Any, mask: str = "*****") -> Any: + """ + Recursively replace any real secret values in data with a mask. + Used to sanitize HttpRequestBlock output before storing. + + Only masks values that exist in self.secrets (registered credentials). + """ + if not self.secrets: + return data + + # Collect all non-empty string secret values + secret_values = {v for v in self.secrets.values() if isinstance(v, str) and v} + + if not secret_values: + return data + + if isinstance(data, str): + result = data + for secret in secret_values: + result = result.replace(secret, mask) + return result + elif isinstance(data, dict): + return {k: self.mask_secrets_in_data(v, mask) for k, v in data.items()} + elif isinstance(data, list): + return [self.mask_secrets_in_data(item, mask) for item in data] + return data + async def get_secrets_from_password_manager(self) -> dict[str, Any]: """ Get the secrets from the password manager. The secrets dict will contain the actual secret values. diff --git a/skyvern/forge/sdk/workflow/models/block.py b/skyvern/forge/sdk/workflow/models/block.py index f17881ab..7a879fcd 100644 --- a/skyvern/forge/sdk/workflow/models/block.py +++ b/skyvern/forge/sdk/workflow/models/block.py @@ -3913,6 +3913,9 @@ class HttpRequestBlock(Block): "url": self.url, } + # Mask secrets in output to prevent credential exposure in DB/UI + response_data = workflow_run_context.mask_secrets_in_data(response_data) + LOG.info( "HTTP request completed", status_code=status_code,