fix(security): stop credential/PII leaks at capture, persistence and replay - #169
fix(security): stop credential/PII leaks at capture, persistence and replay#169Sangaibisi wants to merge 2 commits into
Conversation
…replay Secrets were mishandled at every stage; this closes the P0 set: Capture (extension): - semanticInfo.value carried the RAW value of every field - real passwords leaked into stored events and to the recorder server even while the step's own value was masked. Sensitive values are masked inside extractSemanticInfo now. - masking covered only input[type=password]; OTP (autocomplete= one-time-code), credit-card/CVV, SSN/TCKN/IBAN and password-named text/tel/number fields were recorded in cleartext. New isSensitiveField() checks type, autocomplete and name/id/label/ placeholder conventions (EN+TR). - per-event console.log payload dumps (input/click/select/key data objects, typed values included) reduced to event-type-only lines. Persistence (variable identifier): - high-confidence sensitive matches (SSN, credit card - confidence >= 0.95) deliberately set suggested_default=None, but _generate_input_schema overrode that intent and always persisted the raw recorded value as a plaintext 'default' in the saved .workflow.yaml. Sensitive defaults are now omitted end-to-end. Replay (engine): - input values were logged at INFO and embedded in error reports verbatim on both executor paths. New workflow_use/workflow/redaction module masks values whose field hints look sensitive; wired into the semantic executor's input log, ErrorContext.input_value and the deterministic controller's input action. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
2 issues found across 5 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="workflows/workflow_use/controller/service.py">
<violation number="1" location="workflows/workflow_use/controller/service.py:142">
P1: Sensitive replay inputs can still be logged verbatim when the selector is generic or uses standard credit-card autocomplete hints, because redaction receives only the action params and its heuristic misses those cases. Passing the matched element's type/autocomplete/name metadata into redaction (and covering the standard hints) would preserve masking for these fields.</violation>
</file>
<file name="workflows/workflow_use/workflow/semantic_executor.py">
<violation number="1" location="workflows/workflow_use/workflow/semantic_executor.py:1535">
P1: Replay of a recorded telephone or `cc-*` credit-card input can still write its raw value to INFO logs and error reports. This call only uses a hint regex that does not inspect the recorded input type/semantic metadata or recognize `tel` and `cc-number`/`cc-csc`/`cc-exp`, so the shared redactor should be extended before relying on it here.</violation>
</file>
Tip: instead of fixing issues one by one fix them all with cubic
Re-trigger cubic
| await asyncio.sleep(0.5) | ||
|
|
||
| msg = f'⌨️ Input "{params.value}" into element with CSS selector: {truncate_selector(selector_used)} (original: {truncate_selector(original_selector)})' | ||
| logged_value = redact_step_value(params, params.value) |
There was a problem hiding this comment.
P1: Sensitive replay inputs can still be logged verbatim when the selector is generic or uses standard credit-card autocomplete hints, because redaction receives only the action params and its heuristic misses those cases. Passing the matched element's type/autocomplete/name metadata into redaction (and covering the standard hints) would preserve masking for these fields.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At workflows/workflow_use/controller/service.py, line 142:
<comment>Sensitive replay inputs can still be logged verbatim when the selector is generic or uses standard credit-card autocomplete hints, because redaction receives only the action params and its heuristic misses those cases. Passing the matched element's type/autocomplete/name metadata into redaction (and covering the standard hints) would preserve masking for these fields.</comment>
<file context>
@@ -138,7 +139,8 @@ async def input(
await asyncio.sleep(0.5)
- msg = f'⌨️ Input "{params.value}" into element with CSS selector: {truncate_selector(selector_used)} (original: {truncate_selector(original_selector)})'
+ logged_value = redact_step_value(params, params.value)
+ msg = f'⌨️ Input "{logged_value}" into element with CSS selector: {truncate_selector(selector_used)} (original: {truncate_selector(original_selector)})'
logger.info(msg)
</file context>
| await asyncio.sleep(0.5) | ||
|
|
||
| msg = f"⌨️ Input '{step.value}' into: {target_identifier or step.description or selector_to_use}" | ||
| msg = f"⌨️ Input '{redact_step_value(step, step.value)}' into: {target_identifier or step.description or selector_to_use}" |
There was a problem hiding this comment.
P1: Replay of a recorded telephone or cc-* credit-card input can still write its raw value to INFO logs and error reports. This call only uses a hint regex that does not inspect the recorded input type/semantic metadata or recognize tel and cc-number/cc-csc/cc-exp, so the shared redactor should be extended before relying on it here.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At workflows/workflow_use/workflow/semantic_executor.py, line 1535:
<comment>Replay of a recorded telephone or `cc-*` credit-card input can still write its raw value to INFO logs and error reports. This call only uses a hint regex that does not inspect the recorded input type/semantic metadata or recognize `tel` and `cc-number`/`cc-csc`/`cc-exp`, so the shared redactor should be extended before relying on it here.</comment>
<file context>
@@ -1531,7 +1532,7 @@ async def input_executor():
await asyncio.sleep(0.5)
- msg = f"⌨️ Input '{step.value}' into: {target_identifier or step.description or selector_to_use}"
+ msg = f"⌨️ Input '{redact_step_value(step, step.value)}' into: {target_identifier or step.description or selector_to_use}"
logger.info(msg)
return ActionResult(extracted_content=msg, include_in_memory=True)
</file context>
…se#169 (16 issues) Replay engine (PR browser-use#168 feedback): - text fallback (_find_by_tag_and_text) rewritten as one page-side pass in the compat layer: scans ALL candidates (no 40-element cap), matches rendered innerText instead of hidden textContent plus aria-label/ title/placeholder/value, and POLLS until timeout_ms (the old version ignored its timeout and queried once) - XPath/text marker attributes are now per-lookup tokens (secrets.token_hex) so overlapping lookups can't fetch each other's element; marked-element fetch/cleanup extracted to one helper - XPath click path decoded: page.evaluate returns a JSON string, and .get() on it raised AFTER the click already fired in the page (click-then-report-failure trap) - element_finder._node_attr also reads browser-use's nested {'attributes': {...}} dict shape for dict-form nodes - removed unused compat element_inner_text; semantic_executor._set_checked_by_selector now reuses cdp.set_checkbox_state instead of duplicating it Security (PR browser-use#169 feedback): - extension: type=tel and phone/telefon/gsm naming count as sensitive (a real phone number leaked into a saved workflow exactly this way); rrweb now masks ALL inputs (nothing downstream consumes rrweb input data, so defense-in-depth is free) - redaction: hints now include elementText/elementTag/inputType, and the pattern covers cc-number/cc-csc/cc-exp autocomplete tokens plus tel/phone terminology (EN+TR) - variable identifier: default omission is type-based (password/card/ SSN/email/phone), not confidence-based - a context-detected password at 0.85 confidence was still persisted as a plaintext default; only an already-masked capture may surface as a default 13 new tests (tests/test_redaction.py); full suite 140 passed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
️✅ There are no secrets present in this pull request anymore.If these secrets were true positive and are still valid, we highly recommend you to revoke them. 🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request. |
|
Thanks @cubic-dev-ai — all six addressed in aacf1b7:
|
@Sangaibisi All six fixes line up with what I flagged, and the code matches your descriptions:
One thing I want to flag on the last item: the code is now both type-based and confidence-based, not purely type-based as the message implies. The Also heads-up: GitGuardian flagged a "Generic Password" at The 13 tests look well-scoped and cover both the masking and default-omission behavior. Nothing blocking from me. Tip: get faster answers by chatting with cubic’s review copilot in the review UI |
There was a problem hiding this comment.
1 existing issue remains and no new issues found across 4 files (changes from recent commits).
Requires human review: Auto-approval blocked because this review re-detected 1 unresolved issue already reported by Cubic.
Fix all with cubic | Re-trigger cubic
…t discovery Actions from cubic-dev-ai re-review of PRs browser-use#168/browser-use#169, applied to develop (same defects exist here), plus real bugs the widened test net caught: Security (PR browser-use#169 findings, all confirmed valid): - variable_identifier: sensitivity is now decided by TYPE or NAME/CONTEXT hints (shared redaction matcher). A field named "password"/"iban" that pattern-matching classified as plain STRING was still getting its raw recorded value written to input_schema.default. - variable_identifier: a masked capture no longer becomes a '********' default - defaults are typed verbatim on replay, so it would literally enter eight asterisks into the login field. Sensitive inputs get no default at all and are forced required. - redaction + extension isSensitiveField: phone vocabulary now covers mobile/mobil/cell(ular)/msisdn/cep naming (fields that are not type="tel"); \bcep\b bounded so "Recep" doesn't mask. Extension also learns secret/token. New is_sensitive_hint() shared helper. Real bugs caught by un-hiding the nested suites: - healing/variable_extractor: VAR:name:value markers embedded in larger text (e.g. a URL query ?q=VAR:term:laptop) clobbered the ENTIRE field with {term}, destroying the URL; multiple markers kept only the last. Markers now replace their own span; a marker owning the whole field still consumes it (value may contain spaces: VAR:user_name:John Doe). - semantic_extractor safeGetLabelText: the previous-sibling fallback accepted ANY element with textContent as a "label", so a Cancel button adopted the neighboring "Submit Form" text and links adopted the whole form's text as their key. Non-LABEL siblings now only label true form fields, and only with short text. Verified against a live browser (8/8 with RUN_BROWSER_TESTS=1). Test infrastructure (PR browser-use#168 findings): - pyproject testpaths widened to tests + workflow_use + the top-level regression file: workflow_use/{mcp,workflow,healing,builder}/tests were silently undiscovered (and hid the two bugs above). - Live-LLM suites (test_extract, test_exploration_agent, test_workflow_creation, test_generate_workflow) gated behind RUN_LLM_TESTS - they crashed collection without API keys. - test_semantic_extractor rewritten for the CDP surface: set_content does not exist; markup loads via data: URLs. Browser classes gated behind RUN_BROWSER_TESTS; _normalize_text contract test fixed to the real behavior (whitespace collapse, case preserved - lookup folds). - Password-looking fixture strings replaced with inert values so secret scanners don't flag the test file itself. Suite: 191 passed, 13 skipped (was 159/8 before widening). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Round 1 findings: - rrweb events now mask ALL input values (maskAllInputs) - the parallel custom-event masking alone still persisted OTP/card values through RRWEB_EVENT. - redaction hint fields extended with elementText (legacy steps may carry the only usable hint there). - semantic replay input logging goes through the shared redactor. Round 2 findings: - Sensitivity is decided by TYPE or NAME/CONTEXT hints: a field named "password"/"iban" whose value pattern-matched as plain STRING was still getting its raw recorded value persisted as a default (variable_identifier now consults the shared hint matcher). - A masked capture no longer becomes a '********' default - defaults are typed verbatim on replay, so it would literally enter eight asterisks. Sensitive inputs get no default and are forced required. - Phone vocabulary covers mobile/mobil/cell(ular)/msisdn/cep naming on both capture (extension) and replay (redaction) sides - fields that are not type="tel"; \bcep\b bounded so "Recep" doesn't mask. - Test fixture strings replaced with inert non-password-like values so secret scanners don't flag the test file itself. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
aacf1b7 to
c075b6d
Compare
|
Round-2 review response (force-pushed c075b6d, replacing aacf1b7):
Branch checks: |
Problem
Recorded credentials and PII currently leak on three independent paths:
semanticInfo.valuecarries the RAW value of every field — including passwords. The step's ownvalueis masked to********, but thesemanticInfoobject embedded in the same event still contains the cleartext, which is stored in the session log, dumped to the page console (console.log("Sending CUSTOM_INPUT_EVENT:", inputData)), and shipped to the local recorder server.input[type=password]. OTP fields (type=text/tel+autocomplete=one-time-code), credit-card/CVV fields (type=text/number), SSN/IBAN and password-named fields are recorded in cleartext. We verified this with a real recording where a phone number typed into atelfield landed verbatim in the saved.workflow.yaml.variable_identifierdeliberately setssuggested_default=Nonefor SSN/credit-card/password matches (confidence ≥ 0.95), but_generate_input_schemaoverrides that intent —entry['default'] = candidate.value— so the recorded secret is written into the saved workflow file anyway, taggedformat: ssn/credit-card.On replay, input values (including
********-masked and unmasked ones) are logged at INFO and embedded in error reports verbatim on both executor paths.Fix
Extension (capture):
isSensitiveField(): checkstype,autocomplete(one-time-code,cc-number,cc-csc,cc-exp,new-password,current-password) and name/id/aria-label/placeholder conventionsextractSemanticInfomasks the value of sensitive fields — the raw value never enters the event streamhandleInputmasks byisSensitiveFieldinstead oftype === 'password'onlyPython (persistence + replay):
_generate_input_schemarespectssuggested_default=None: sensitive defaults are omitted instead of falling back to the raw recorded valueworkflow_use/workflow/redaction.py:redact_step_value()masks values whose field hints look sensitive; wired into the semantic executor's input log,ErrorContext.input_value, and the deterministic controller's input logNo behavior change for non-sensitive fields.
Related: #165 (audit findings), #166, #168.
🤖 Generated with Claude Code
Summary by cubic
Prevents credential and PII leaks by masking sensitive values at capture, persistence, and replay. rrweb input events now use
maskAllInputs, and logs/error reports no longer expose raw secrets; non-sensitive inputs behave the same.maskAllInputs; broaden sensitive field detection (type, autocomplete, and name/label hints incl. tel/phone, EN/TR); masksemanticInfo.valueand input values; console logs omit payloads._generate_input_schemaomits defaults for sensitive types and for sensitive name/context hints; no'********'defaults; these inputs are forcedrequired.redact_step_valueredacts inputs in INFO logs and error reports using hints incl.elementText,elementTag,inputType; applied in the semantic executor, deterministic controller, and error context.Written for commit c075b6d. Summary will update on new commits.