Files
2026-10-13 10:34:48 +02:00

34 lines
3.6 KiB
JavaScript

import { inspect } from './policy.mjs';
const blocked=ruleId=>({decision:'block',ruleId});
const allowed=()=>({decision:'allow',ruleId:null});
const normalize=text=>text.normalize('NFKC').toLowerCase().replace(/[\u200b-\u200f\u202a-\u202e\u2060-\u206f\ufeff]/g,'').replace(/[`^]/g,'');
// Deterministic indicators of remote/obfuscated shell execution, not a command parser or malware proof.
export function detectClickFix(text){
if(typeof text!=='string'||Buffer.byteLength(text)>1048576||text.includes('\0'))return blocked('clipboard-uninspectable');
const values=[normalize(text)];try{values.push(normalize(decodeURIComponent(text)));}catch{}
for(const value of values){
const command=value.replace(/["']/g,'');
if(/\b(?:powershell|pwsh)(?:\.exe)?\b[\s\S]{0,512}?\s-(?:e|ec|en|enc|enco|encod|encode|encoded|encodedc|encodedco|encodedcom|encodedcomm|encodedcomma|encodedcomman|encodedcommand)(?:\s+|:|=)[a-z0-9+/]+={0,2}/.test(command))return blocked('clickfix-encoded-powershell');
const remote=/https?:\/\//.test(command),download=/\b(?:invoke-webrequest|invoke-restmethod|iwr|irm)\b|\.download(?:string|file)\s*\(/.test(command),execute=/\b(?:invoke-expression|iex)\b/.test(command);
if(remote&&download&&execute)return blocked('clickfix-powershell-remote-execution');
if(/\bmshta(?:\.exe)?\s+(?:https?:\/\/|(?:vbscript|javascript):)/.test(command))return blocked('clickfix-mshta-execution');
if(/\brundll32(?:\.exe)?\b[\s\S]{0,512}?javascript:[\s\S]{0,512}?(?:runhtmlapplication|getobject)/.test(command))return blocked('clickfix-rundll32-script');
if(/\bregsvr32(?:\.exe)?\b[\s\S]{0,512}?\/i:https?:\/\//.test(command))return blocked('clickfix-regsvr32-remote-script');
if(remote&&/\b(?:curl|wget)(?:\.exe)?\b[^\n;]{0,1024}\|\s*(?:sudo\s+)?(?:sh|bash|zsh|pwsh|powershell)(?:\.exe)?\b/.test(command))return blocked('clickfix-download-pipe-shell');
if(remote&&/\b(?:sh|bash|zsh)\s+-c\s+\$\([^\n]{0,1024}\b(?:curl|wget)\b/.test(command))return blocked('clickfix-shell-download-substitution');
if(remote&&(download||/\b(?:curl|wget|certutil|bitsadmin)(?:\.exe)?\b/.test(command))&&/(?:&&|;|\n)\s*(?:(?:start|start-process|sh|bash|\.\/|\.\\)\s*)?[^\s;\n]{1,200}\.(?:exe|ps1|bat|cmd|sh)\b/.test(command))return blocked('clickfix-download-then-execute');
if(remote&&/\b(?:python|python3)(?:\.exe)?\s+-c\b/.test(command)&&/\b(?:exec|eval)\s*\(/.test(command)&&/\b(?:urllib|requests)\b/.test(command))return blocked('clickfix-python-remote-execution');
}
return allowed();
}
export function inspectClipboard(bytes,policy,operation){
if(!['copy','cut','paste'].includes(operation))return blocked('clipboard-operation-denied');
if(!policy||!Number.isFinite(Date.parse(policy.expiresAt))||Date.parse(policy.expiresAt)<=Date.now())return blocked('clipboard-policy-expired');
const settings=policy.security?.clipboard;if(!settings||settings.failClosed!==true)return blocked('clipboard-policy-unavailable');
if(settings.enabled&&!settings[`allow${operation[0].toUpperCase()}${operation.slice(1)}`])return blocked(`clipboard-${operation}-disabled`);
let data,text;try{if(!Buffer.isBuffer(bytes)&&!(bytes instanceof Uint8Array)&&typeof bytes!=='string')throw Error();data=Buffer.from(bytes);if(data.length>Math.min(settings.maxTextBytes,policy.dlp.maxBytes))return blocked('clipboard-size-limit');text=new TextDecoder('utf-8',{fatal:true}).decode(data);if(text.includes('\0'))throw Error();}catch{return blocked('clipboard-uninspectable');}
const verdict=inspect(data,policy);if(verdict.decision!=='allow')return verdict;
return settings.enabled&&settings.blockSuspiciousCommands?detectClickFix(text):allowed();
}