From 3ea9cdd611364aa714ed76aae8580897fa107097 Mon Sep 17 00:00:00 2001 From: "Torsten Schulz (local)" Date: Wed, 11 Mar 2026 12:25:45 +0100 Subject: [PATCH] feat(clickTtHttpPageRoutes): add sensitive data masking and form body summarization - Introduced functions to mask sensitive values in form submissions and summarize form body data, enhancing security and logging capabilities. - Updated the proxy POST route to log detailed information about submitted forms, including field counts and redacted values for sensitive keys. - Enhanced error handling for URL-encoded body parsing, improving robustness in form data processing. --- backend/routes/clickTtHttpPageRoutes.js | 63 +++++++++++++++++++++++-- 1 file changed, 60 insertions(+), 3 deletions(-) diff --git a/backend/routes/clickTtHttpPageRoutes.js b/backend/routes/clickTtHttpPageRoutes.js index 3aa55367..da46269e 100644 --- a/backend/routes/clickTtHttpPageRoutes.js +++ b/backend/routes/clickTtHttpPageRoutes.js @@ -56,6 +56,41 @@ function serializeFormBody(req) { return null; } +function maskSensitiveValue(key, value) { + if (!key) return value; + return /(pass|password|passwd|pwd|token|secret)/i.test(String(key)) ? '[redacted]' : value; +} + +function summarizeFormBody(body) { + if (!body || typeof body !== 'string') { + return { fieldCount: 0, fields: [] }; + } + + try { + const params = new URLSearchParams(body); + const fields = []; + + for (const [key, value] of params.entries()) { + fields.push({ + key, + value: maskSensitiveValue(key, value), + }); + } + + return { + fieldCount: fields.length, + fields, + parseError: null, + }; + } catch { + return { + fieldCount: 0, + fields: [], + parseError: 'body is not URL-encoded', + }; + } +} + /** Domains, deren Links durch den Proxy umgeleitet werden (für Folge-Logs) */ const PROXY_DOMAINS = ['click-tt.de', 'httv.de', 'liga.nu']; @@ -287,6 +322,7 @@ function injectProxyNavigationScript(html, proxyBaseUrl, pageBaseUrl, sid) { 'if(!anchor||event.defaultPrevented)return;', "var targetUrl=normalizeUrl(anchor.getAttribute('href'),PAGE_BASE_URL);", 'if(!targetUrl||!shouldProxyUrl(targetUrl))return;', + "try{console.log('[ClickTT Proxy] anchor click',{href:anchor.getAttribute('href'),targetUrl:targetUrl,text:(anchor.textContent||'').trim().slice(0,120)});}catch(e){}", 'event.preventDefault();', 'navigateViaProxy(targetUrl);', '},false);', @@ -296,6 +332,17 @@ function injectProxyNavigationScript(html, proxyBaseUrl, pageBaseUrl, sid) { 'var submitter=event.submitter||null;', 'var targetUrl=getSubmitTarget(form,submitter);', 'if(!targetUrl||!shouldProxyUrl(targetUrl))return;', + 'try{', + 'var formDataEntries=[];', + 'if(window.FormData){', + 'var formData=new FormData(form);', + 'formData.forEach(function(value,key){', + "var safeValue=/(pass|password|passwd|pwd|token|secret)/i.test(String(key))?'[redacted]':String(value);", + 'formDataEntries.push({key:key,value:safeValue});', + '});', + '}', + "console.log('[ClickTT Proxy] form submit',{action:form.getAttribute('action'),targetUrl:targetUrl,submitterName:submitter&&submitter.name?submitter.name:null,submitterValue:submitter&&submitter.value?submitter.value:null,fieldCount:formDataEntries.length,fields:formDataEntries.slice(0,40)});", + '}catch(e){}', "form.setAttribute('action',buildProxyUrl(targetUrl));", '},true);', 'if(window.fetch){', @@ -321,6 +368,7 @@ function injectProxyNavigationScript(html, proxyBaseUrl, pageBaseUrl, sid) { 'var nativeSubmit=window.HTMLFormElement.prototype.submit;', 'window.HTMLFormElement.prototype.submit=function patchedSubmit(){', 'var targetUrl=getSubmitTarget(this,null);', + 'try{console.log("[ClickTT Proxy] direct form.submit()",{action:this.getAttribute("action"),targetUrl:targetUrl});}catch(e){}', "if(targetUrl&&shouldProxyUrl(targetUrl)){this.setAttribute('action',buildProxyUrl(targetUrl));}", 'return nativeSubmit.apply(this,arguments);', '};', @@ -469,6 +517,16 @@ router.post('/proxy', async (req, res, next) => { const contentType = req.get('content-type') || 'application/x-www-form-urlencoded'; const body = serializeFormBody(req); + const bodySummary = summarizeFormBody(body); + + console.log('[ClickTT Proxy POST]', JSON.stringify({ + sid, + targetUrl, + contentType, + fieldCount: bodySummary.fieldCount, + fields: bodySummary.fields, + parseError: bodySummary.parseError, + })); const result = await clickTtHttpPageService.fetchWithLogging({ url: targetUrl, @@ -658,10 +716,9 @@ router.get('/fetch', authenticate, async (req, res, next) => { return res.status(400).json({ error: 'url ist erforderlich' }); } - // Nur click-tt.de und httv.de erlauben - if (!url.includes('click-tt.de') && !url.includes('httv.de')) { + if (!isAllowedProxyUrl(url)) { return res.status(400).json({ - error: 'Nur URLs von click-tt.de oder httv.de sind erlaubt', + error: 'Nur URLs von click-tt.de, httv.de oder liga.nu sind erlaubt', }); }