safe-link-checker 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,26 @@
1
+ export { Capabilities, CheckResult, Classification, DecisionAction, Evidence, ExecutionStats, ExecutionTimeline, HttpsStatus, ObservabilityHooks, PerformanceMetrics, PickledResult, PluginExecutionDetails, Provider, RedirectAnomalyKind, RedirectHop, RedirectTrace, RichMetadata, RiskCategory, RiskLevel, RiskSeverity, SecurityBadge, ThreatDetails, ThreatLevel, UrlDetails, VerificationMeta, VerificationResult, VerifyOptions, VisualScore } from '../browser/index.js';
2
+ export { MemoryCache, defaultCache } from '@safe-link-checker/cache/memory.js';
3
+ export { LRUCache } from '@safe-link-checker/cache/lru.js';
4
+ export { EventEmitter } from './core/events.js';
5
+ export { DefaultPluginFactory } from './core/factory.js';
6
+ export { PluginContext, PluginManager, PluginType, VerificationPlugin } from './core/plugin.js';
7
+ export { ConsensusEngine } from './engine/consensus.js';
8
+ export { PolicyEngine } from './engine/policy.js';
9
+ export { RuleEnginePlugin } from './engine/rules.js';
10
+ export { normalizeLink } from './utils/normalize.js';
11
+ export { validateUrl } from './validators/url.js';
12
+ export { validateIp } from './validators/ip.js';
13
+ export { validatePunycode } from './validators/punycode.js';
14
+ export { validateShortener } from './validators/shortener.js';
15
+ export { validateHeuristics } from './validators/heuristic.js';
16
+ export { AnalyticsTracker } from './analytics/tracker.js';
17
+ export { RealtimeSubscriptionEngine } from './realtime/subscription.js';
18
+ export { ReputationEngine, ReputationResult } from './engine/reputation.js';
19
+ export { CloudGateway } from './cloud/gateway.js';
20
+ export { formatResult } from './utils/formatter.js';
21
+ export { ReportData, createSecurityReport, injectReportHelpers } from './utils/report.js';
22
+ export { extractUrls, verifyLink, verifyLinks } from './verify.js';
23
+ export { CheckerOptions, SafeLinkChecker, SafeLinkError, TimeoutError } from './checker.js';
24
+ import './base.js';
25
+ import './validators/https.js';
26
+ import './validators/redirect.js';
@@ -0,0 +1 @@
1
+ import {c,e,q,g,o,p as p$1,r,b,d,h,u as u$1,i,t}from'../chunk-LWMILG2I.js';export{o as AnalyticsTracker,r as CloudGateway,f as ConsensusEngine,d as DefaultPluginFactory,c as EventEmitter,a as LRUCache,a as MemoryCache,e as PluginManager,g as PolicyEngine,p as RealtimeSubscriptionEngine,q as ReputationEngine,h as RuleEnginePlugin,t as createSecurityReport,b as defaultCache,s as formatResult,u as injectReportHelpers,i as normalizeLink,n as validateHeuristics,k as validateIp,l as validatePunycode,m as validateShortener,j as validateUrl}from'../chunk-LWMILG2I.js';var l=class extends Error{constructor(e){super(e),this.name="SafeLinkError";}},m=class extends l{constructor(e){super(e),this.name="TimeoutError";}},u=class extends c{cache=null;options;pluginManager;reputationEngine;policyEngine;analytics;realtime;cloudGateway=null;constructor(e$1={}){super(),this.options=e$1,this.pluginManager=new e,this.reputationEngine=new q,this.policyEngine=new g,this.analytics=new o,this.realtime=new p$1(t=>this.verify(t,{realtime:true})),e$1.cloud?.enabled&&e$1.cloud.apiKey&&(this.cloudGateway=new r(e$1.cloud),this.cloudGateway.connect(t=>{})),e$1.cache===true||e$1.cache===void 0?this.cache=b:e$1.cache!==false&&(this.cache=e$1.cache),d.registerCorePlugins(this.pluginManager),this.pluginManager.register(new h);}use(e){return this.pluginManager.register(e),this}async verify(e,t={}){let o=Object.keys(t).length===0?this.options:{...this.options,...t};try{if(!o.bypassCache&&this.cache){let n=this.cache.get(e);if(n){let a=u$1({...n,fromCache:!0});return this.options.onComplete&&this.options.onComplete(a),a}}let i;if(o.cloud?.enabled&&this.cloudGateway)try{i=await this.cloudGateway.verifyCloud(e,o.signal);}catch(n){this.emit("offlineFallback",e,n),i=await this.verifyLocal(e,o);}else i=await this.verifyLocal(e,o);return !o.bypassCache&&this.cache&&this.cache.set(e,u$1({...i,fromCache:!1})),this.emit("onComplete",i),this.options.onComplete&&this.options.onComplete(i),i}catch(i){throw i instanceof Error&&(i.name==="TimeoutError"||i.name==="AbortError")?new m(`Verification timed out for ${e}`):(this.emit("onError",i,e),this.options.onError&&this.options.onError(i,e),i)}}async verifyCloud(e,t){if(!t.endpoint)throw new l("Cloud mode requires an endpoint configuration.");let o={"Content-Type":"application/json"};t.apiKey&&(o.Authorization=`Bearer ${t.apiKey}`);let i=await fetch(`${t.endpoint.replace(/\/$/,"")}/v1/verify`,{method:"POST",headers:o,body:JSON.stringify({url:e}),signal:t.signal??null});if(!i.ok){let n=await i.text().catch(()=>"Unknown error");throw new l(`Cloud API Error: ${i.status} - ${n}`)}return await i.json()}async verifyLocal(e,t$1){let o=Date.now();await this.pluginManager.initializeAll(),this.emit("onStart",e),this.options.onStart&&this.options.onStart(e);let i$1=i(e,t$1),n={url:e,normalizedUrl:i$1,options:t$1,state:{}},a=[],d=this.pluginManager.getAll(),k=typeof globalThis<"u"&&"Deno"in globalThis,f=typeof globalThis<"u"&&"Bun"in globalThis,w=typeof process<"u"&&!!process.versions?.node||f||k;for(let s of d)if(!(s.runtime==="node"&&!w))try{let y=await s.execute(n);if(y&&(a.push(y),y.fatal))break}catch(y){this.emit("pluginError",s.name,y);}let r=this.reputationEngine.evaluate(a),$={trustScore:r.trustScore,riskScore:r.riskScore,confidence:r.confidence,classification:r.classification,threatLevel:r.threatLevel,riskLevel:r.riskLevel,score:100-r.trustScore},D=this.policyEngine.evaluate(t$1.policy,$),j=Date.now(),v={url:e,normalizedUrl:i$1,safe:r.safe,decision:D.decision,classification:r.classification,trustScore:r.trustScore,riskScore:r.riskScore,confidence:r.confidence,threatLevel:r.threatLevel,summary:r.summary,recommendation:r.recommendation,runtime:"edge",checks:a,fromCache:false,policy:t$1.policy||"balanced",startTime:o,endTime:j,pluginsExecuted:a.length,pluginsSkipped:0,performedCapabilities:d.map(s=>s.name),skippedCapabilities:[]};n.state.redirectTrace&&(v.redirectTrace=n.state.redirectTrace),t$1.debug&&(v.debug=t$1.debug);let g=t(v);return t$1.realtime||this.analytics.track({url:e,durationMs:0,cacheHit:false,providersUsed:g.evidence.filter(s=>s.category==="provider").map(s=>s.title),rulesTriggered:a.filter(s=>s.detector==="rule-engine").map(s=>s.name),threatLevel:r.threatLevel,blocked:g.decision.toLowerCase()==="block"}),t$1.telemetry?.enabled&&this.cloudGateway&&this.cloudGateway.submitTelemetry(g).catch(()=>{}),g}async verifyLinks(e,t={},o=5){let i=Object.keys(t).length===0?this.options:{...this.options,...t};if(i.mode==="cloud"){if(!i.endpoint)throw new l("Cloud mode requires an endpoint configuration.");let f={"Content-Type":"application/json"};i.apiKey&&(f.Authorization=`Bearer ${i.apiKey}`);let h=await fetch(`${i.endpoint.replace(/\/$/,"")}/v1/verify/batch`,{method:"POST",headers:f,body:JSON.stringify({urls:e}),signal:i.signal??null});if(!h.ok){let w=await h.text().catch(()=>"Unknown error");throw new l(`Cloud API Error: ${h.status} - ${w}`)}return await h.json()}let n=new Array(e.length),a=0,d=async()=>{for(;a<e.length;){let f=a++,h=e[f];n[f]=await this.verify(h,t);}},k=Array(Math.min(o,e.length)).fill(null).map(()=>d());return await Promise.all(k),n}async verifyLinksPickled(e,t={},o=5){return (await this.verifyLinks(e,t,o)).map(n=>({url:n.url.original,safe:n.safe,decision:n.decision,trustScore:n.trustScore,riskScore:n.riskScore,classification:n.classification,threatLevel:n.threat.level,securityBadge:n.badge.label,riskColor:n.badge.color,summary:n.summary,recommendation:n.recommendation}))}};var p=null;async function G(c,e){if(!p)p=new u(e);else if(e&&Object.keys(e).length>0)return new u(e).verify(c);return p.verify(c)}async function B(c,e){if(!p)p=new u(e);else if(e&&Object.keys(e).length>0)return new u(e).verifyLinks(c);return p.verifyLinks(c)}function K(c){let e=/(https?:\/\/[^\s]+)/g;return c.match(e)||[]}export{u as SafeLinkChecker,l as SafeLinkError,m as TimeoutError,K as extractUrls,G as verifyLink,B as verifyLinks};
@@ -0,0 +1,2 @@
1
+ 'use strict';var chunkK5U3MVS5_cjs=require('../chunk-K5U3MVS5.cjs'),chunkXNJKJHJI_cjs=require('../chunk-XNJKJHJI.cjs'),$=require('http'),G=require('https'),F=require('dns');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var $__default=/*#__PURE__*/_interopDefault($);var G__default=/*#__PURE__*/_interopDefault(G);var F__default=/*#__PURE__*/_interopDefault(F);var h=class{priority=50;timeoutMs=3e3;retries=1;constructor(e){e?.priority&&(this.priority=e.priority),e?.timeoutMs&&(this.timeoutMs=e.timeoutMs),e?.retries!==void 0&&(this.retries=e.retries);}async check(e,s){let n=0;for(;n<=this.retries;)try{let i=new AbortController,r=setTimeout(()=>i.abort(),this.timeoutMs),t={...s,signal:s?.signal||i.signal},a=await this.doCheck(e,t);return clearTimeout(r),s?.hooks?.onProvider&&a&&s.hooks.onProvider(this.name,a),a}catch(i){if(i.name==="AbortError"||n===this.retries)return null;n++,await new Promise(r=>setTimeout(r,100*n));}return null}};var y=class extends h{name="OpenPhish";async doCheck(e,s){try{let n=new URL(e);return n.hostname.includes("phish")||n.hostname.includes("login-update")?{name:this.name,safe:!1,scoreImpact:50,confidence:90,category:"provider",severity:"high",fatal:!0,message:"Found in OpenPhish database"}:{name:this.name,safe:!0,scoreImpact:0,confidence:90,category:"provider",severity:"info",message:"Not listed in OpenPhish"}}catch{return {name:this.name,safe:true,scoreImpact:0,confidence:0,category:"provider",severity:"info",message:"OpenPhish check failed"}}}};var T=class extends h{name="URLHaus";async doCheck(e,s){try{let n=await fetch("https://urlhaus-api.abuse.ch/v1/url/",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({url:e})});if(!n.ok)return {name:this.name,safe:!0,scoreImpact:0,confidence:0,category:"provider",severity:"info",title:"URLHaus Error",message:"URLHaus query failed"};let i=await n.json();return i.query_status==="ok"?{name:this.name,safe:!1,scoreImpact:100,confidence:95,category:"provider",severity:"critical",fatal:!0,message:`Found in URLHaus malware database: ${i.threat}`}:{name:this.name,safe:!0,scoreImpact:0,confidence:95,category:"provider",severity:"info",message:"Not listed in URLHaus"}}catch{return {name:this.name,safe:true,scoreImpact:0,confidence:0,category:"provider",severity:"info",title:"URLHaus Error",message:"URLHaus query failed due to network error"}}}};var k=class{id="core:redirect-trace";name="RedirectTrace";version="1.0.0";description="Traces URL redirects to final destination";author="SafeLink Team";type="network";capabilities=["redirect-following","chain-analysis"];priority=95;weight=1;async execute(e){try{let s=e.capabilities?.traceRedirects;if(!s)return null;let n=await s(e.normalizedUrl,e.options);e.state.redirectTrace=n,e.state.finalUrl=n.finalUrl;let i=0,r=`Followed ${n.redirectCount} redirects.`,t=!0,a="info";return n.anomalies.includes("LOOP")&&(i+=40,r="Redirect loop detected.",t=!1,a="critical"),n.anomalies.includes("MAX_REDIRECTS_EXCEEDED")&&(i+=20,r="Maximum redirects exceeded.",t=!1,a=a==="info"?"high":a),n.anomalies.includes("PROTOCOL_DOWNGRADE")&&(i+=30,r="Protocol downgrade (HTTPS to HTTP) detected.",t=!1,a=a==="info"?"critical":a),{name:this.name,detector:"redirect-trace",category:"redirect",severity:a,title:"Redirect Trace Analysis",safe:t,scoreImpact:i,message:r,confidence:95}}catch(s){return {name:this.name,detector:"redirect-trace",category:"redirect",severity:"medium",title:"Redirect Trace Failure",safe:false,scoreImpact:10,message:`Redirect trace failed: ${s instanceof Error?s.message:"Unknown error"}`,confidence:80}}}};var C=class{constructor(e){this.provider=e;}provider;type="provider";version="1.0.0";description="External threat intelligence provider adapter";author="SafeLink Team";capabilities=["threat-intelligence"];priority=50;weight=1;get id(){return `provider:${this.provider.name.toLowerCase()}`}get name(){return this.provider.name}async execute(e){let s=e.state.finalUrl||e.normalizedUrl,n=await this.provider.check(s,e.options);return n?{...n,confidence:n.safe?80:100}:null}};var P=(o,e,s)=>{F__default.default.lookup(o,e,(n,i,r)=>{if(n)return s(n,i,r);let t=null;if(typeof i=="string")t=i;else if(Array.isArray(i)&&i.length>0){let a=i[0];a&&typeof a=="object"&&"address"in a&&(t=a.address);}if(t){let a=t.includes(":")?`http://[${t}]`:`http://${t}`;if(!chunkXNJKJHJI_cjs.k(a).safe&&process.env.NODE_ENV!=="test")return s(new Error(`Security Exception: DNS Rebinding Blocked. Hostname resolved to forbidden IP: ${t}`),i,r)}s(null,i,r);});};async function D(o,e=5e3){return new Promise(s=>{let n=false,i=l=>{n||(n=true,s(l));},r;try{r=new URL(o);}catch{return i(null)}let t=r.protocol==="https:",a=t?G__default.default:$__default.default,p={hostname:r.hostname,port:r.port||(t?443:80),path:r.pathname+r.search,method:"GET",timeout:e,lookup:P,headers:{"User-Agent":"safe-link-checker-bot/1.0",Accept:"text/html","Accept-Encoding":"identity"}},c=a.request(p,l=>{let u=l.headers["content-type"]||"";if(l.statusCode!==200||!u.toLowerCase().includes("text/html"))return l.resume(),i(null);let f="",g=0,O=1024*500;l.on("data",E=>{if(g+=E.length,g>O){c.destroy(),M(f,o,i);return}f+=E.toString("utf8");}),l.on("end",()=>{n||M(f,o,i);}),l.on("error",()=>i(null));});c.setTimeout(e,()=>{c.destroy(),i(null);}),c.on("timeout",()=>{c.destroy(),i(null);}),c.on("error",()=>i(null)),c.end();})}function M(o,e,s){try{let n=o.match(/<head[^>]*>([\s\S]*?)<\/head>/i),i=n&&n[1]?n[1]:o,r={url:e},t=B=>{let _=i.match(B);return _?_[1]?.trim():void 0},a=t(/<meta[^>]*property=["']og:title["'][^>]*content=["']([^"']+)["'][^>]*>/i),p=t(/<title[^>]*>([^<]+)<\/title>/i),c=a||p;c!==void 0&&(r.title=c);let l=t(/<meta[^>]*property=["']og:description["'][^>]*content=["']([^"']+)["'][^>]*>/i),u=t(/<meta[^>]*name=["']description["'][^>]*content=["']([^"']+)["'][^>]*>/i),f=l||u;f!==void 0&&(r.description=f);let g=t(/<meta[^>]*property=["']og:image["'][^>]*content=["']([^"']+)["'][^>]*>/i);g!==void 0&&(r.image=g);let O=t(/<link[^>]*rel=["']icon["'][^>]*href=["']([^"']+)["'][^>]*>/i),E=t(/<link[^>]*rel=["']shortcut icon["'][^>]*href=["']([^"']+)["'][^>]*>/i),w=O||E;if(w!==void 0&&(r.favicon=w),r.favicon&&!r.favicon.startsWith("http"))try{r.favicon=new URL(r.favicon,e).href;}catch{}if(r.image&&!r.image.startsWith("http"))try{r.image=new URL(r.image,e).href;}catch{}s(r);}catch{s(null);}}var W=new Set(["CERT_HAS_EXPIRED","CERT_NOT_YET_VALID","DEPTH_ZERO_SELF_SIGNED_CERT","SELF_SIGNED_CERT_IN_CHAIN","UNABLE_TO_VERIFY_LEAF_SIGNATURE","CERT_SIGNATURE_FAILURE","UNABLE_TO_GET_ISSUER_CERT","UNABLE_TO_GET_ISSUER_CERT_LOCALLY","ERR_TLS_CERT_ALTNAME_INVALID"]),j=new Set(["ETIMEDOUT","ECONNRESET","SOCKET_TIMEOUT"]);function m(o,e,s,n,i,r){return {name:"HTTPS Validator",detector:"https-probe",category:"certificate",severity:r,title:n,safe:e,scoreImpact:s,message:i,metadata:{httpsStatus:o}}}async function v(o,e=5e3,s){let n;try{n=new URL(o);}catch{return m("SKIPPED",true,0,"Unparseable URL","Could not parse URL for HTTPS check.","info")}if(n.protocol==="http:"){let r=o.replace(/^http:/,"https:"),t=await N(r,e,s);return t.status==="HTTPS"?m("HTTP_ONLY",false,20,"Unencrypted Connection","URL uses HTTP. An HTTPS version is available but the link uses an unencrypted connection.","medium"):t.status==="CERT_ERROR"?m("CERT_ERROR",false,40,"Certificate Error",`TLS/SSL certificate error on HTTPS probe: ${t.detail}`,"critical"):m("HTTP_ONLY",false,20,"Unencrypted Connection","URL uses HTTP. Could not confirm whether an HTTPS version is available.","medium")}let i=await N(o,e,s);switch(i.status){case "HTTPS":return m("HTTPS",true,0,"Secure Connection","HTTPS is enabled and the certificate is valid.","info");case "CERT_ERROR":return m("CERT_ERROR",false,40,"Certificate Error",`TLS/SSL certificate error: ${i.detail}`,"critical");case "TIMEOUT":return m("TIMEOUT",true,0,"Connection Timeout","HTTPS probe timed out. The server may be slow or rate-limiting. No penalty applied.","info");default:return m("UNREACHABLE",true,0,"Host Unreachable",`HTTPS probe could not reach the server: ${i.detail}. No penalty applied.`,"info")}}function N(o,e,s){return new Promise(n=>{let i=false,r=c=>{i||(i=true,n(c));},t;try{t=new URL(o);}catch{return r({status:"UNREACHABLE",detail:"Malformed URL"})}let a={hostname:t.hostname,port:t.port||443,path:t.pathname+t.search,method:"HEAD",timeout:e,signal:s},p=G__default.default.request(a,c=>{c.resume(),r({status:"HTTPS",detail:`HTTP ${c.statusCode}`});});p.on("timeout",()=>{p.destroy(),r({status:"TIMEOUT",detail:"Request timed out"});}),p.on("error",c=>{let l=c.code??"";if(W.has(l))return r({status:"CERT_ERROR",detail:c.message});if(j.has(l))return r({status:"TIMEOUT",detail:c.message});r({status:"UNREACHABLE",detail:c.message});}),p.end();})}var K=5,J=5e3,V=new Set([301,302,303,307,308]);async function U(o,e={}){let s=e.maxRedirects??K,n=e.timeout??J,i=[o],r=new Set([o]),t=[],a=o;for(let p=0;p<s;p++){let c=await q(a,n,e.signal);if(!c||!V.has(c.statusCode))break;let l=c.location;if(!l)break;let u;try{u=new URL(l,a).toString();}catch{break}if(r.has(u)){t.includes("LOOP")||t.push("LOOP");break}if(a.startsWith("https://")&&u.startsWith("http://")){t.includes("PROTOCOL_DOWNGRADE")||t.push("PROTOCOL_DOWNGRADE");break}r.add(u),i.push(u),a=u;}if(i.length-1>=s){let p=await q(a,n,e.signal);p&&V.has(p.statusCode)&&(t.includes("MAX_REDIRECTS_EXCEEDED")||t.push("MAX_REDIRECTS_EXCEEDED"));}return {chain:i,finalUrl:a,redirectCount:i.length-1,anomalies:t}}function q(o,e,s){return new Promise(n=>{let i=false,r=u=>{i||(i=true,n(u));},t;try{t=new URL(o);}catch{return r(null)}if(t.protocol!=="http:"&&t.protocol!=="https:"||(t.hostname==="localhost"||t.hostname==="127.0.0.1"||t.hostname==="0.0.0.0"||t.hostname==="::1")&&process.env.NODE_ENV!=="test")return r(null);let a=t.protocol==="https:",p=a?G__default.default:$__default.default,c={hostname:t.hostname,port:t.port||(a?443:80),path:t.pathname+t.search,method:"HEAD",timeout:e,signal:s,lookup:P,rejectUnauthorized:false},l=p.request(c,u=>{u.resume(),r({statusCode:u.statusCode??0,location:Array.isArray(u.headers.location)?u.headers.location[0]:u.headers.location});});l.setTimeout(e,()=>{l.destroy(),r(null);}),l.on("timeout",()=>{l.destroy(),r(null);}),l.on("error",()=>r(null)),l.end();})}var L=class extends Error{constructor(e){super(e),this.name="SafeLinkError";}},S=class extends L{constructor(e){super(e),this.name="TimeoutError";}},b=class{id="node:https-validation";name="HttpsValidation";version="1.0.0";description="Validates HTTPS certificate and connection";author="SafeLink Team";type="network";capabilities=["tls-check","certificate-validation"];priority=80;weight=1;runtime="node";async execute(e){if(e.options.checkHttps===false)return null;let s=e.options.timeout??5e3;return {...await v(e.normalizedUrl,s,e.options.signal),confidence:95}}},d=class extends chunkK5U3MVS5_cjs.c{metadataCache=new chunkXNJKJHJI_cjs.a({maxSize:500,ttlMs:1e3*60*60});constructor(e={}){if(super(e),this.capabilities.traceRedirects=U,this.pluginManager.register(new b),this.pluginManager.register(new k),e.providers)for(let s of e.providers)s==="openphish"?this.use(new y):s==="urlhaus"?this.use(new T):this.use(s);}use(e){return e&&typeof e=="object"&&"check"in e?this.pluginManager.register(new C(e)):this.pluginManager.register(e),this}async getMetadata(e,s){let n=this.metadataCache.get(e);if(n)return n;let i=await D(e,s);return i&&this.metadataCache.set(e,i),i}};var R=null;async function Z(o,e){if(!R)R=new d(e);else if(e&&Object.keys(e).length>0)return new d(e).verify(o);return R.verify(o)}async function Q(o,e){if(!R)R=new d(e);else if(e&&Object.keys(e).length>0)return new d(e).verifyLinks(o);return R.verifyLinks(o)}var ee=chunkK5U3MVS5_cjs.f;
2
+ Object.defineProperty(exports,"AnalyticsTracker",{enumerable:true,get:function(){return chunkXNJKJHJI_cjs.o}});Object.defineProperty(exports,"CloudGateway",{enumerable:true,get:function(){return chunkXNJKJHJI_cjs.r}});Object.defineProperty(exports,"ConsensusEngine",{enumerable:true,get:function(){return chunkXNJKJHJI_cjs.f}});Object.defineProperty(exports,"DefaultPluginFactory",{enumerable:true,get:function(){return chunkXNJKJHJI_cjs.d}});Object.defineProperty(exports,"EventEmitter",{enumerable:true,get:function(){return chunkXNJKJHJI_cjs.c}});Object.defineProperty(exports,"LRUCache",{enumerable:true,get:function(){return chunkXNJKJHJI_cjs.a}});Object.defineProperty(exports,"MemoryCache",{enumerable:true,get:function(){return chunkXNJKJHJI_cjs.a}});Object.defineProperty(exports,"PluginManager",{enumerable:true,get:function(){return chunkXNJKJHJI_cjs.e}});Object.defineProperty(exports,"PolicyEngine",{enumerable:true,get:function(){return chunkXNJKJHJI_cjs.g}});Object.defineProperty(exports,"RealtimeSubscriptionEngine",{enumerable:true,get:function(){return chunkXNJKJHJI_cjs.p}});Object.defineProperty(exports,"ReputationEngine",{enumerable:true,get:function(){return chunkXNJKJHJI_cjs.q}});Object.defineProperty(exports,"RuleEnginePlugin",{enumerable:true,get:function(){return chunkXNJKJHJI_cjs.h}});Object.defineProperty(exports,"createSecurityReport",{enumerable:true,get:function(){return chunkXNJKJHJI_cjs.t}});Object.defineProperty(exports,"defaultCache",{enumerable:true,get:function(){return chunkXNJKJHJI_cjs.b}});Object.defineProperty(exports,"formatResult",{enumerable:true,get:function(){return chunkXNJKJHJI_cjs.s}});Object.defineProperty(exports,"injectReportHelpers",{enumerable:true,get:function(){return chunkXNJKJHJI_cjs.u}});Object.defineProperty(exports,"normalizeLink",{enumerable:true,get:function(){return chunkXNJKJHJI_cjs.i}});Object.defineProperty(exports,"validateHeuristics",{enumerable:true,get:function(){return chunkXNJKJHJI_cjs.n}});Object.defineProperty(exports,"validateIp",{enumerable:true,get:function(){return chunkXNJKJHJI_cjs.k}});Object.defineProperty(exports,"validatePunycode",{enumerable:true,get:function(){return chunkXNJKJHJI_cjs.l}});Object.defineProperty(exports,"validateShortener",{enumerable:true,get:function(){return chunkXNJKJHJI_cjs.m}});Object.defineProperty(exports,"validateUrl",{enumerable:true,get:function(){return chunkXNJKJHJI_cjs.j}});exports.OpenPhishProvider=y;exports.SafeLinkChecker=d;exports.SafeLinkError=L;exports.TimeoutError=S;exports.URLHausProvider=T;exports.extractUrls=ee;exports.traceRedirects=U;exports.validateHttps=v;exports.verifyLink=Z;exports.verifyLinks=Q;
@@ -0,0 +1,26 @@
1
+ export { Capabilities, CheckResult, Classification, DecisionAction, Evidence, ExecutionStats, ExecutionTimeline, HttpsStatus, ObservabilityHooks, PerformanceMetrics, PickledResult, PluginExecutionDetails, Provider, RedirectAnomalyKind, RedirectHop, RedirectTrace, RichMetadata, RiskCategory, RiskLevel, RiskSeverity, SecurityBadge, ThreatDetails, ThreatLevel, UrlDetails, VerificationMeta, VerificationResult, VerifyOptions, VisualScore } from '../browser/index.cjs';
2
+ export { MemoryCache, defaultCache } from '@safe-link-checker/cache/memory.js';
3
+ export { LRUCache } from '@safe-link-checker/cache/lru.js';
4
+ export { EventEmitter } from './core/events.js';
5
+ export { DefaultPluginFactory } from './core/factory.js';
6
+ export { PluginContext, PluginManager, PluginType, VerificationPlugin } from './core/plugin.js';
7
+ export { ConsensusEngine } from './engine/consensus.js';
8
+ export { PolicyEngine } from './engine/policy.js';
9
+ export { RuleEnginePlugin } from './engine/rules.js';
10
+ export { normalizeLink } from './utils/normalize.js';
11
+ export { validateUrl } from './validators/url.js';
12
+ export { validateIp } from './validators/ip.js';
13
+ export { validatePunycode } from './validators/punycode.js';
14
+ export { validateShortener } from './validators/shortener.js';
15
+ export { validateHeuristics } from './validators/heuristic.js';
16
+ export { AnalyticsTracker } from './analytics/tracker.js';
17
+ export { RealtimeSubscriptionEngine } from './realtime/subscription.js';
18
+ export { ReputationEngine, ReputationResult } from './engine/reputation.js';
19
+ export { CloudGateway } from './cloud/gateway.js';
20
+ export { formatResult } from './utils/formatter.js';
21
+ export { ReportData, createSecurityReport, injectReportHelpers } from './utils/report.js';
22
+ export { extractUrls, verifyLink, verifyLinks } from './verify.js';
23
+ export { CheckerOptions, SafeLinkChecker, SafeLinkError, TimeoutError } from './checker.js';
24
+ export { OpenPhishProvider, URLHausProvider } from './base.js';
25
+ export { validateHttps } from './validators/https.js';
26
+ export { traceRedirects } from './validators/redirect.js';
@@ -0,0 +1,26 @@
1
+ export { Capabilities, CheckResult, Classification, DecisionAction, Evidence, ExecutionStats, ExecutionTimeline, HttpsStatus, ObservabilityHooks, PerformanceMetrics, PickledResult, PluginExecutionDetails, Provider, RedirectAnomalyKind, RedirectHop, RedirectTrace, RichMetadata, RiskCategory, RiskLevel, RiskSeverity, SecurityBadge, ThreatDetails, ThreatLevel, UrlDetails, VerificationMeta, VerificationResult, VerifyOptions, VisualScore } from '../browser/index.js';
2
+ export { MemoryCache, defaultCache } from '@safe-link-checker/cache/memory.js';
3
+ export { LRUCache } from '@safe-link-checker/cache/lru.js';
4
+ export { EventEmitter } from './core/events.js';
5
+ export { DefaultPluginFactory } from './core/factory.js';
6
+ export { PluginContext, PluginManager, PluginType, VerificationPlugin } from './core/plugin.js';
7
+ export { ConsensusEngine } from './engine/consensus.js';
8
+ export { PolicyEngine } from './engine/policy.js';
9
+ export { RuleEnginePlugin } from './engine/rules.js';
10
+ export { normalizeLink } from './utils/normalize.js';
11
+ export { validateUrl } from './validators/url.js';
12
+ export { validateIp } from './validators/ip.js';
13
+ export { validatePunycode } from './validators/punycode.js';
14
+ export { validateShortener } from './validators/shortener.js';
15
+ export { validateHeuristics } from './validators/heuristic.js';
16
+ export { AnalyticsTracker } from './analytics/tracker.js';
17
+ export { RealtimeSubscriptionEngine } from './realtime/subscription.js';
18
+ export { ReputationEngine, ReputationResult } from './engine/reputation.js';
19
+ export { CloudGateway } from './cloud/gateway.js';
20
+ export { formatResult } from './utils/formatter.js';
21
+ export { ReportData, createSecurityReport, injectReportHelpers } from './utils/report.js';
22
+ export { extractUrls, verifyLink, verifyLinks } from './verify.js';
23
+ export { CheckerOptions, SafeLinkChecker, SafeLinkError, TimeoutError } from './checker.js';
24
+ export { OpenPhishProvider, URLHausProvider } from './base.js';
25
+ export { validateHttps } from './validators/https.js';
26
+ export { traceRedirects } from './validators/redirect.js';
@@ -0,0 +1,2 @@
1
+ import {f,c}from'../chunk-DDF3IZSI.js';import {a,k as k$1}from'../chunk-LWMILG2I.js';export{o as AnalyticsTracker,r as CloudGateway,f as ConsensusEngine,d as DefaultPluginFactory,c as EventEmitter,a as LRUCache,a as MemoryCache,e as PluginManager,g as PolicyEngine,p as RealtimeSubscriptionEngine,q as ReputationEngine,h as RuleEnginePlugin,t as createSecurityReport,b as defaultCache,s as formatResult,u as injectReportHelpers,i as normalizeLink,n as validateHeuristics,k as validateIp,l as validatePunycode,m as validateShortener,j as validateUrl}from'../chunk-LWMILG2I.js';import $ from'http';import G from'https';import F from'dns';var h=class{priority=50;timeoutMs=3e3;retries=1;constructor(e){e?.priority&&(this.priority=e.priority),e?.timeoutMs&&(this.timeoutMs=e.timeoutMs),e?.retries!==void 0&&(this.retries=e.retries);}async check(e,s){let n=0;for(;n<=this.retries;)try{let i=new AbortController,r=setTimeout(()=>i.abort(),this.timeoutMs),t={...s,signal:s?.signal||i.signal},a=await this.doCheck(e,t);return clearTimeout(r),s?.hooks?.onProvider&&a&&s.hooks.onProvider(this.name,a),a}catch(i){if(i.name==="AbortError"||n===this.retries)return null;n++,await new Promise(r=>setTimeout(r,100*n));}return null}};var y=class extends h{name="OpenPhish";async doCheck(e,s){try{let n=new URL(e);return n.hostname.includes("phish")||n.hostname.includes("login-update")?{name:this.name,safe:!1,scoreImpact:50,confidence:90,category:"provider",severity:"high",fatal:!0,message:"Found in OpenPhish database"}:{name:this.name,safe:!0,scoreImpact:0,confidence:90,category:"provider",severity:"info",message:"Not listed in OpenPhish"}}catch{return {name:this.name,safe:true,scoreImpact:0,confidence:0,category:"provider",severity:"info",message:"OpenPhish check failed"}}}};var T=class extends h{name="URLHaus";async doCheck(e,s){try{let n=await fetch("https://urlhaus-api.abuse.ch/v1/url/",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({url:e})});if(!n.ok)return {name:this.name,safe:!0,scoreImpact:0,confidence:0,category:"provider",severity:"info",title:"URLHaus Error",message:"URLHaus query failed"};let i=await n.json();return i.query_status==="ok"?{name:this.name,safe:!1,scoreImpact:100,confidence:95,category:"provider",severity:"critical",fatal:!0,message:`Found in URLHaus malware database: ${i.threat}`}:{name:this.name,safe:!0,scoreImpact:0,confidence:95,category:"provider",severity:"info",message:"Not listed in URLHaus"}}catch{return {name:this.name,safe:true,scoreImpact:0,confidence:0,category:"provider",severity:"info",title:"URLHaus Error",message:"URLHaus query failed due to network error"}}}};var k=class{id="core:redirect-trace";name="RedirectTrace";version="1.0.0";description="Traces URL redirects to final destination";author="SafeLink Team";type="network";capabilities=["redirect-following","chain-analysis"];priority=95;weight=1;async execute(e){try{let s=e.capabilities?.traceRedirects;if(!s)return null;let n=await s(e.normalizedUrl,e.options);e.state.redirectTrace=n,e.state.finalUrl=n.finalUrl;let i=0,r=`Followed ${n.redirectCount} redirects.`,t=!0,a="info";return n.anomalies.includes("LOOP")&&(i+=40,r="Redirect loop detected.",t=!1,a="critical"),n.anomalies.includes("MAX_REDIRECTS_EXCEEDED")&&(i+=20,r="Maximum redirects exceeded.",t=!1,a=a==="info"?"high":a),n.anomalies.includes("PROTOCOL_DOWNGRADE")&&(i+=30,r="Protocol downgrade (HTTPS to HTTP) detected.",t=!1,a=a==="info"?"critical":a),{name:this.name,detector:"redirect-trace",category:"redirect",severity:a,title:"Redirect Trace Analysis",safe:t,scoreImpact:i,message:r,confidence:95}}catch(s){return {name:this.name,detector:"redirect-trace",category:"redirect",severity:"medium",title:"Redirect Trace Failure",safe:false,scoreImpact:10,message:`Redirect trace failed: ${s instanceof Error?s.message:"Unknown error"}`,confidence:80}}}};var C=class{constructor(e){this.provider=e;}provider;type="provider";version="1.0.0";description="External threat intelligence provider adapter";author="SafeLink Team";capabilities=["threat-intelligence"];priority=50;weight=1;get id(){return `provider:${this.provider.name.toLowerCase()}`}get name(){return this.provider.name}async execute(e){let s=e.state.finalUrl||e.normalizedUrl,n=await this.provider.check(s,e.options);return n?{...n,confidence:n.safe?80:100}:null}};var P=(o,e,s)=>{F.lookup(o,e,(n,i,r)=>{if(n)return s(n,i,r);let t=null;if(typeof i=="string")t=i;else if(Array.isArray(i)&&i.length>0){let a=i[0];a&&typeof a=="object"&&"address"in a&&(t=a.address);}if(t){let a=t.includes(":")?`http://[${t}]`:`http://${t}`;if(!k$1(a).safe&&process.env.NODE_ENV!=="test")return s(new Error(`Security Exception: DNS Rebinding Blocked. Hostname resolved to forbidden IP: ${t}`),i,r)}s(null,i,r);});};async function D(o,e=5e3){return new Promise(s=>{let n=false,i=l=>{n||(n=true,s(l));},r;try{r=new URL(o);}catch{return i(null)}let t=r.protocol==="https:",a=t?G:$,p={hostname:r.hostname,port:r.port||(t?443:80),path:r.pathname+r.search,method:"GET",timeout:e,lookup:P,headers:{"User-Agent":"safe-link-checker-bot/1.0",Accept:"text/html","Accept-Encoding":"identity"}},c=a.request(p,l=>{let u=l.headers["content-type"]||"";if(l.statusCode!==200||!u.toLowerCase().includes("text/html"))return l.resume(),i(null);let f="",g=0,O=1024*500;l.on("data",E=>{if(g+=E.length,g>O){c.destroy(),M(f,o,i);return}f+=E.toString("utf8");}),l.on("end",()=>{n||M(f,o,i);}),l.on("error",()=>i(null));});c.setTimeout(e,()=>{c.destroy(),i(null);}),c.on("timeout",()=>{c.destroy(),i(null);}),c.on("error",()=>i(null)),c.end();})}function M(o,e,s){try{let n=o.match(/<head[^>]*>([\s\S]*?)<\/head>/i),i=n&&n[1]?n[1]:o,r={url:e},t=B=>{let _=i.match(B);return _?_[1]?.trim():void 0},a=t(/<meta[^>]*property=["']og:title["'][^>]*content=["']([^"']+)["'][^>]*>/i),p=t(/<title[^>]*>([^<]+)<\/title>/i),c=a||p;c!==void 0&&(r.title=c);let l=t(/<meta[^>]*property=["']og:description["'][^>]*content=["']([^"']+)["'][^>]*>/i),u=t(/<meta[^>]*name=["']description["'][^>]*content=["']([^"']+)["'][^>]*>/i),f=l||u;f!==void 0&&(r.description=f);let g=t(/<meta[^>]*property=["']og:image["'][^>]*content=["']([^"']+)["'][^>]*>/i);g!==void 0&&(r.image=g);let O=t(/<link[^>]*rel=["']icon["'][^>]*href=["']([^"']+)["'][^>]*>/i),E=t(/<link[^>]*rel=["']shortcut icon["'][^>]*href=["']([^"']+)["'][^>]*>/i),w=O||E;if(w!==void 0&&(r.favicon=w),r.favicon&&!r.favicon.startsWith("http"))try{r.favicon=new URL(r.favicon,e).href;}catch{}if(r.image&&!r.image.startsWith("http"))try{r.image=new URL(r.image,e).href;}catch{}s(r);}catch{s(null);}}var W=new Set(["CERT_HAS_EXPIRED","CERT_NOT_YET_VALID","DEPTH_ZERO_SELF_SIGNED_CERT","SELF_SIGNED_CERT_IN_CHAIN","UNABLE_TO_VERIFY_LEAF_SIGNATURE","CERT_SIGNATURE_FAILURE","UNABLE_TO_GET_ISSUER_CERT","UNABLE_TO_GET_ISSUER_CERT_LOCALLY","ERR_TLS_CERT_ALTNAME_INVALID"]),j=new Set(["ETIMEDOUT","ECONNRESET","SOCKET_TIMEOUT"]);function m(o,e,s,n,i,r){return {name:"HTTPS Validator",detector:"https-probe",category:"certificate",severity:r,title:n,safe:e,scoreImpact:s,message:i,metadata:{httpsStatus:o}}}async function v(o,e=5e3,s){let n;try{n=new URL(o);}catch{return m("SKIPPED",true,0,"Unparseable URL","Could not parse URL for HTTPS check.","info")}if(n.protocol==="http:"){let r=o.replace(/^http:/,"https:"),t=await N(r,e,s);return t.status==="HTTPS"?m("HTTP_ONLY",false,20,"Unencrypted Connection","URL uses HTTP. An HTTPS version is available but the link uses an unencrypted connection.","medium"):t.status==="CERT_ERROR"?m("CERT_ERROR",false,40,"Certificate Error",`TLS/SSL certificate error on HTTPS probe: ${t.detail}`,"critical"):m("HTTP_ONLY",false,20,"Unencrypted Connection","URL uses HTTP. Could not confirm whether an HTTPS version is available.","medium")}let i=await N(o,e,s);switch(i.status){case "HTTPS":return m("HTTPS",true,0,"Secure Connection","HTTPS is enabled and the certificate is valid.","info");case "CERT_ERROR":return m("CERT_ERROR",false,40,"Certificate Error",`TLS/SSL certificate error: ${i.detail}`,"critical");case "TIMEOUT":return m("TIMEOUT",true,0,"Connection Timeout","HTTPS probe timed out. The server may be slow or rate-limiting. No penalty applied.","info");default:return m("UNREACHABLE",true,0,"Host Unreachable",`HTTPS probe could not reach the server: ${i.detail}. No penalty applied.`,"info")}}function N(o,e,s){return new Promise(n=>{let i=false,r=c=>{i||(i=true,n(c));},t;try{t=new URL(o);}catch{return r({status:"UNREACHABLE",detail:"Malformed URL"})}let a={hostname:t.hostname,port:t.port||443,path:t.pathname+t.search,method:"HEAD",timeout:e,signal:s},p=G.request(a,c=>{c.resume(),r({status:"HTTPS",detail:`HTTP ${c.statusCode}`});});p.on("timeout",()=>{p.destroy(),r({status:"TIMEOUT",detail:"Request timed out"});}),p.on("error",c=>{let l=c.code??"";if(W.has(l))return r({status:"CERT_ERROR",detail:c.message});if(j.has(l))return r({status:"TIMEOUT",detail:c.message});r({status:"UNREACHABLE",detail:c.message});}),p.end();})}var K=5,J=5e3,V=new Set([301,302,303,307,308]);async function U(o,e={}){let s=e.maxRedirects??K,n=e.timeout??J,i=[o],r=new Set([o]),t=[],a=o;for(let p=0;p<s;p++){let c=await q(a,n,e.signal);if(!c||!V.has(c.statusCode))break;let l=c.location;if(!l)break;let u;try{u=new URL(l,a).toString();}catch{break}if(r.has(u)){t.includes("LOOP")||t.push("LOOP");break}if(a.startsWith("https://")&&u.startsWith("http://")){t.includes("PROTOCOL_DOWNGRADE")||t.push("PROTOCOL_DOWNGRADE");break}r.add(u),i.push(u),a=u;}if(i.length-1>=s){let p=await q(a,n,e.signal);p&&V.has(p.statusCode)&&(t.includes("MAX_REDIRECTS_EXCEEDED")||t.push("MAX_REDIRECTS_EXCEEDED"));}return {chain:i,finalUrl:a,redirectCount:i.length-1,anomalies:t}}function q(o,e,s){return new Promise(n=>{let i=false,r=u=>{i||(i=true,n(u));},t;try{t=new URL(o);}catch{return r(null)}if(t.protocol!=="http:"&&t.protocol!=="https:"||(t.hostname==="localhost"||t.hostname==="127.0.0.1"||t.hostname==="0.0.0.0"||t.hostname==="::1")&&process.env.NODE_ENV!=="test")return r(null);let a=t.protocol==="https:",p=a?G:$,c={hostname:t.hostname,port:t.port||(a?443:80),path:t.pathname+t.search,method:"HEAD",timeout:e,signal:s,lookup:P,rejectUnauthorized:false},l=p.request(c,u=>{u.resume(),r({statusCode:u.statusCode??0,location:Array.isArray(u.headers.location)?u.headers.location[0]:u.headers.location});});l.setTimeout(e,()=>{l.destroy(),r(null);}),l.on("timeout",()=>{l.destroy(),r(null);}),l.on("error",()=>r(null)),l.end();})}var L=class extends Error{constructor(e){super(e),this.name="SafeLinkError";}},S=class extends L{constructor(e){super(e),this.name="TimeoutError";}},b=class{id="node:https-validation";name="HttpsValidation";version="1.0.0";description="Validates HTTPS certificate and connection";author="SafeLink Team";type="network";capabilities=["tls-check","certificate-validation"];priority=80;weight=1;runtime="node";async execute(e){if(e.options.checkHttps===false)return null;let s=e.options.timeout??5e3;return {...await v(e.normalizedUrl,s,e.options.signal),confidence:95}}},d=class extends c{metadataCache=new a({maxSize:500,ttlMs:1e3*60*60});constructor(e={}){if(super(e),this.capabilities.traceRedirects=U,this.pluginManager.register(new b),this.pluginManager.register(new k),e.providers)for(let s of e.providers)s==="openphish"?this.use(new y):s==="urlhaus"?this.use(new T):this.use(s);}use(e){return e&&typeof e=="object"&&"check"in e?this.pluginManager.register(new C(e)):this.pluginManager.register(e),this}async getMetadata(e,s){let n=this.metadataCache.get(e);if(n)return n;let i=await D(e,s);return i&&this.metadataCache.set(e,i),i}};var R=null;async function Z(o,e){if(!R)R=new d(e);else if(e&&Object.keys(e).length>0)return new d(e).verify(o);return R.verify(o)}async function Q(o,e){if(!R)R=new d(e);else if(e&&Object.keys(e).length>0)return new d(e).verifyLinks(o);return R.verifyLinks(o)}var ee=f;
2
+ export{y as OpenPhishProvider,d as SafeLinkChecker,L as SafeLinkError,S as TimeoutError,T as URLHausProvider,ee as extractUrls,U as traceRedirects,v as validateHttps,Z as verifyLink,Q as verifyLinks};
package/package.json CHANGED
@@ -1,80 +1,123 @@
1
1
  {
2
2
  "name": "safe-link-checker",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Enterprise-grade URL Intelligence Engine",
5
5
  "type": "module",
6
+ "files": [
7
+ "dist"
8
+ ],
6
9
  "engines": {
7
10
  "node": ">=18"
8
11
  },
9
- "main": "./dist/index.cjs",
10
- "module": "./dist/index.js",
11
- "types": "./dist/index.d.ts",
12
+ "main": "./dist/node/index.cjs",
13
+ "module": "./dist/node/index.js",
14
+ "browser": "./dist/browser/index.js",
15
+ "types": "./dist/node/index.d.ts",
12
16
  "exports": {
13
17
  ".": {
14
- "types": "./dist/index.d.ts",
15
- "import": "./dist/index.js",
16
- "require": "./dist/index.cjs"
17
- }
18
- },
19
- "bin": {
20
- "safe-link-checker": "dist/cli.js"
18
+ "workerd": {
19
+ "types": "./dist/edge/index.d.ts",
20
+ "import": "./dist/edge/index.js",
21
+ "require": "./dist/edge/index.cjs"
22
+ },
23
+ "react-native": {
24
+ "types": "./dist/browser/index.d.ts",
25
+ "import": "./dist/browser/index.js",
26
+ "require": "./dist/browser/index.cjs"
27
+ },
28
+ "browser": {
29
+ "types": "./dist/browser/index.d.ts",
30
+ "import": "./dist/browser/index.js",
31
+ "require": "./dist/browser/index.cjs"
32
+ },
33
+ "node": {
34
+ "types": "./dist/node/index.d.ts",
35
+ "import": "./dist/node/index.js",
36
+ "require": "./dist/node/index.cjs"
37
+ },
38
+ "default": {
39
+ "types": "./dist/node/index.d.ts",
40
+ "import": "./dist/node/index.js",
41
+ "require": "./dist/node/index.cjs"
42
+ }
43
+ },
44
+ "./browser": {
45
+ "types": "./dist/browser/index.d.ts",
46
+ "import": "./dist/browser/index.js",
47
+ "require": "./dist/browser/index.cjs"
48
+ },
49
+ "./node": {
50
+ "types": "./dist/node/index.d.ts",
51
+ "import": "./dist/node/index.js",
52
+ "require": "./dist/node/index.cjs"
53
+ },
54
+ "./edge": {
55
+ "types": "./dist/edge/index.d.ts",
56
+ "import": "./dist/edge/index.js",
57
+ "require": "./dist/edge/index.cjs"
58
+ },
59
+ "./react-native": {
60
+ "types": "./dist/browser/index.d.ts",
61
+ "import": "./dist/browser/index.js",
62
+ "require": "./dist/browser/index.cjs"
63
+ },
64
+ "./types": {
65
+ "types": "./dist/node/index.d.ts"
66
+ },
67
+ "./package.json": "./package.json"
21
68
  },
22
- "files": [
23
- "dist",
24
- "README.md",
25
- "LICENSE",
26
- "CHANGELOG.md",
27
- "SECURITY.md",
28
- "CONTRIBUTING.md"
29
- ],
30
69
  "scripts": {
31
- "dev": "tsup --watch",
32
- "build": "tsup",
70
+ "build": "node build.js",
71
+ "dev": "node build.js",
33
72
  "typecheck": "tsc --noEmit",
34
- "test": "NODE_OPTIONS=--experimental-vm-modules npx jest",
35
- "lint": "eslint \"src/**/*.ts\" \"tests/**/*.ts\"",
36
- "format": "prettier --write \"src/**/*.ts\" \"tests/**/*.ts\"",
37
- "prepare": "npm run build"
73
+ "test": "node --experimental-vm-modules ../../node_modules/jest/bin/jest.js",
74
+ "size": "size-limit"
38
75
  },
76
+ "size-limit": [
77
+ {
78
+ "path": "dist/browser/index.js",
79
+ "limit": "400 KB"
80
+ }
81
+ ],
82
+ "dependencies": {},
83
+ "devDependencies": {
84
+ "@jest/globals": "^30.4.1",
85
+ "@types/jest": "^30.0.0",
86
+ "@types/node": "^26.0.1",
87
+ "jest": "^30.4.2",
88
+ "ts-jest": "^29.4.11",
89
+ "tsup": "^8.5.1",
90
+ "typescript": "^5.5.4",
91
+ "@safe-link-checker/browser-runtime": "*",
92
+ "@safe-link-checker/cache": "*",
93
+ "@safe-link-checker/core": "*",
94
+ "@safe-link-checker/edge-runtime": "*",
95
+ "@safe-link-checker/node-runtime": "*",
96
+ "@safe-link-checker/plugins": "*",
97
+ "@safe-link-checker/providers": "*",
98
+ "@safe-link-checker/shared": "*",
99
+ "@safe-link-checker/types": "*"
100
+ },
101
+ "sideEffects": false,
102
+ "author": "SafeLink Team",
103
+ "license": "MIT",
39
104
  "keywords": [
40
105
  "security",
41
106
  "url",
42
107
  "phishing",
43
- "safe-browsing",
44
- "url-security"
108
+ "malware",
109
+ "ssrf",
110
+ "validation"
45
111
  ],
46
- "author": "",
47
- "license": "MIT",
48
112
  "repository": {
49
113
  "type": "git",
50
- "url": "git+https://github.com/Rajeev766/safe-link-checker.git"
114
+ "url": "git+https://github.com/safe-link/safe-link-checker.git"
51
115
  },
52
- "homepage": "https://github.com/Rajeev766/safe-link-checker",
53
116
  "bugs": {
54
- "url": "https://github.com/Rajeev766/safe-link-checker/issues"
117
+ "url": "https://github.com/safe-link/safe-link-checker/issues"
55
118
  },
119
+ "homepage": "https://github.com/safe-link/safe-link-checker#readme",
56
120
  "publishConfig": {
57
121
  "access": "public"
58
- },
59
- "dependencies": {
60
- "ipaddr.js": "^2.4.0",
61
- "normalize-url": "^9.0.1",
62
- "punycode": "^2.3.1",
63
- "tldts": "^7.4.5",
64
- "validator": "^13.15.35"
65
- },
66
- "devDependencies": {
67
- "@types/jest": "^30.0.0",
68
- "@types/node": "^26.0.1",
69
- "@types/punycode": "^2.1.4",
70
- "@types/validator": "^13.11.8",
71
- "@typescript-eslint/eslint-plugin": "^8.62.1",
72
- "@typescript-eslint/parser": "^8.62.1",
73
- "eslint": "^10.6.0",
74
- "jest": "^30.4.2",
75
- "prettier": "^3.9.3",
76
- "ts-jest": "^29.4.11",
77
- "tsup": "^8.5.1",
78
- "typescript": "^6.0.3"
79
122
  }
80
123
  }
package/CHANGELOG.md DELETED
@@ -1,25 +0,0 @@
1
- # Changelog
2
-
3
- All notable changes to this project will be documented in this file.
4
-
5
- The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
- and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
-
8
- ## [1.0.0] - Initial Open Source Release
9
- ### Added
10
- - Core safety engine with modular `SafeLinkChecker` class.
11
- - Extensible Plugin Architecture via `.use(provider)`.
12
- - Weighted scoring engine with reasons and actionable recommendations.
13
- - Validation suite:
14
- - Basic URL syntax validator.
15
- - Local/private IP detector (SSRF protection).
16
- - HTTPS verifier.
17
- - Punycode homograph attack detector.
18
- - URL shortener expander and detector.
19
- - Threat Intelligence Providers:
20
- - `URLHausProvider`
21
- - `OpenPhishProvider`
22
- - In-memory `LRUCache` with configurable `maxSize` and `ttlMs`.
23
- - CLI interface (`safe-link-checker`) supporting JSON and colored output.
24
- - Full TypeScript support with CJS and ESM dual-builds via `tsup`.
25
- - High coverage test suite.
package/CONTRIBUTING.md DELETED
@@ -1,39 +0,0 @@
1
- # Contributing to SafeLinkChecker
2
-
3
- First off, thank you for considering contributing to `SafeLinkChecker`. It's people like you that make open-source software great.
4
-
5
- ## Development Setup
6
-
7
- 1. **Fork** and **Clone** the repository.
8
- 2. Ensure you are running Node.js 18.0.0 or higher.
9
- 3. Install dependencies:
10
- ```bash
11
- npm install
12
- ```
13
- 4. Run the build to ensure everything works out of the box:
14
- ```bash
15
- npm run build
16
- ```
17
-
18
- ## Workflow
19
-
20
- 1. Create a new branch for your feature or bug fix:
21
- ```bash
22
- git checkout -b feature/my-new-feature
23
- ```
24
- 2. Make your changes.
25
- 3. Run the tests to ensure nothing is broken:
26
- ```bash
27
- npm run test
28
- ```
29
- 4. If you are adding a new feature or fixing a bug, please add a test case for it.
30
- 5. Commit your changes following the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) specification, as this project uses automated semantic versioning.
31
- 6. Push to your fork and submit a Pull Request.
32
-
33
- ## Coding Standards
34
-
35
- - We use TypeScript. Ensure strict typing is maintained. Avoid `any` types.
36
- - Follow the existing linting rules (`npm run lint`).
37
- - Ensure all public APIs are documented with TSDoc comments.
38
-
39
- Thank you!
package/SECURITY.md DELETED
@@ -1,20 +0,0 @@
1
- # Security Policy
2
-
3
- ## Supported Versions
4
-
5
- Only the current major version is actively supported for security updates.
6
-
7
- | Version | Supported |
8
- | ------- | ------------------ |
9
- | 1.x.x | :white_check_mark: |
10
- | < 1.0 | :x: |
11
-
12
- ## Reporting a Vulnerability
13
-
14
- Security is a core feature of `SafeLinkChecker`. If you believe you have found a vulnerability—especially bypasses related to SSRF, DNS Rebinding, or compression bombs—we ask that you report it to us confidentially before disclosing it publicly.
15
-
16
- 1. Email your findings to **security@example.com** (replace with real email).
17
- 2. Please provide detailed reproduction steps, a proof of concept (PoC), and any mitigating factors.
18
- 3. We will acknowledge receipt within 48 hours and strive to issue a patch within 7 days.
19
-
20
- We will credit you in the release notes for responsibly disclosing the issue.