Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

haraka-plugin-karma

Package Overview
Dependencies
Maintainers
3
Versions
26
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

haraka-plugin-karma - npm Package Compare versions

Comparing version 1.0.8 to 1.0.9

appveyor.yml

7

.eslintrc.json
{
"env": {
"es6": true,
"node": true
},
"plugins": [

@@ -8,4 +12,5 @@ "haraka"

"rules": {
"indent": [2, 2, {"SwitchCase": 1}]
"indent": [2, 2, {"SwitchCase": 1}],
"no-unused-vars": ["error", { "vars": "all", "args": "none" } ]
}
}
## 1.0.9 - 2017-07-29
- splash on some es6
- add AppVeyor CI testing
## 1.0.8 - 2017-06-26

@@ -3,0 +8,0 @@

286

index.js
'use strict';
// karma - reward good and penalize bad mail senders
var constants = require('haraka-constants');
var utils = require('haraka-utils');
const constants = require('haraka-constants');
const utils = require('haraka-utils');
var phase_prefixes = utils.to_object([
let phase_prefixes = utils.to_object([
'connect','helo','mail_from','rcpt_to','data'

@@ -12,3 +12,4 @@ ]);

exports.register = function () {
var plugin = this;
let plugin = this;
plugin.inherits('haraka-plugin-redis');

@@ -33,6 +34,6 @@

plugin.register_hook('connect_init', 'ip_history_from_redis');
};
}
exports.load_karma_ini = function () {
var plugin = this;
let plugin = this;

@@ -49,3 +50,3 @@ plugin.cfg = plugin.config.get('karma.ini', {

var cfg = plugin.cfg;
let cfg = plugin.cfg;
if (cfg.deny && cfg.deny.hooks) {

@@ -55,3 +56,3 @@ plugin.deny_hooks = utils.to_object(cfg.deny.hooks);

var e = cfg.deny_excludes;
let e = cfg.deny_excludes;
if (e && e.hooks) {

@@ -76,6 +77,6 @@ plugin.deny_exclude_hooks = utils.to_object(e.hooks);

}
};
}
exports.results_init = function (next, connection) {
var plugin = this;
let plugin = this;

@@ -90,9 +91,12 @@ if (connection.results.get('karma')) {

// When discovered, apply the awards value
var todo = {};
for (var key in plugin.cfg.awards) {
var award = plugin.cfg.awards[key].toString();
let todo = {};
for (let key in plugin.cfg.awards) {
let award = plugin.cfg.awards[key].toString();
todo[key] = award;
}
connection.results.add(plugin, { score:0, todo: todo });
}
connection.results.add(plugin, { score:0, todo: todo });
else {
connection.results.add(plugin, { score:0 });
}

@@ -107,12 +111,12 @@ if (!connection.server.notes.redis) {

// subscribe to result_store publish messages
plugin.redis_subscribe(connection, function () {
connection.notes.redis.on('pmessage', function (pattern, channel, message) {
plugin.redis_subscribe(connection, () => {
connection.notes.redis.on('pmessage', (pattern, channel, message) => {
plugin.check_result(connection, message);
});
next();
});
};
})
}
exports.preparse_result_awards = function () {
var plugin = this;
let plugin = this;
if (!plugin.result_awards) plugin.result_awards = {};

@@ -122,7 +126,7 @@

// ex: karma.result_awards.clamd.fail = { .... }
Object.keys(plugin.cfg.result_awards).forEach(function (anum) {
Object.keys(plugin.cfg.result_awards).forEach(anum => {
// plugin, property, operator, value, award, reason, resolution
var parts = plugin.cfg.result_awards[anum].split(/(?:\s*\|\s*)/);
var pi_name = parts[0];
var property = parts[1];
let parts = plugin.cfg.result_awards[anum].split(/(?:\s*\|\s*)/);
let pi_name = parts[0];
let property = parts[1];
if (!plugin.result_awards[pi_name]) {

@@ -146,3 +150,3 @@ plugin.result_awards[pi_name] = {};

exports.check_result = function (connection, message) {
var plugin = this;
let plugin = this;
// connection.loginfo(plugin, message);

@@ -152,3 +156,3 @@ // {"plugin":"karma","result":{"fail":"spamassassin.hits"}}

var m = JSON.parse(message);
let m = JSON.parse(message);
if (m && m.result && m.result.asn) {

@@ -159,9 +163,9 @@ plugin.check_result_asn(m.result.asn, connection);

Object.keys(m.result).forEach(function (r) { // foreach result in mess
Object.keys(m.result).forEach(r => { // foreach result in mess
if (r === 'emit') return; // r: pass, fail, skip, err, ...
var pi_prop = plugin.result_awards[m.plugin][r];
let pi_prop = plugin.result_awards[m.plugin][r];
if (!pi_prop) return; // no award for this plugin property
var thisResult = m.result[r];
let thisResult = m.result[r];
// ignore empty arrays, objects, and strings

@@ -175,6 +179,6 @@ if (Array.isArray(thisResult) && thisResult.length === 0) return;

// do any award conditions match this result?
for (var i=0; i < pi_prop.length; i++) { // each award...
var thisAward = pi_prop[i];
for (let i=0; i < pi_prop.length; i++) { // each award...
let thisAward = pi_prop[i];
// { id: '011', operator: 'equals', value: 'all_bad', award: '-2'}
var thisResArr = plugin.result_as_array(thisResult);
let thisResArr = plugin.result_as_array(thisResult);
switch (thisAward.operator) {

@@ -210,4 +214,4 @@ case 'eq':

if (typeof result === 'object') {
var array = [];
Object.keys(result).forEach(function (tr) {
let array = [];
Object.keys(result).forEach(tr => {
array.push(result[tr]);

@@ -222,3 +226,3 @@ });

exports.check_result_asn = function (asn, conn) {
var plugin = this;
let plugin = this;
if (!plugin.cfg.asn_awards) return;

@@ -232,6 +236,6 @@ if (!plugin.cfg.asn_awards[asn]) return;

exports.check_result_lt = function (thisResult, thisAward, conn) {
var plugin = this;
let plugin = this;
for (var j=0; j < thisResult.length; j++) {
var tr = parseFloat(thisResult[j]);
for (let j=0; j < thisResult.length; j++) {
let tr = parseFloat(thisResult[j]);
if (tr >= parseFloat(thisAward.value)) continue;

@@ -246,6 +250,6 @@ if (conn.results.has('karma', 'awards', thisAward.id)) continue;

exports.check_result_gt = function (thisResult, thisAward, conn) {
var plugin = this;
let plugin = this;
for (var j=0; j < thisResult.length; j++) {
var tr = parseFloat(thisResult[j]);
for (let j=0; j < thisResult.length; j++) {
let tr = parseFloat(thisResult[j]);
if (tr <= parseFloat(thisAward.value)) continue;

@@ -260,5 +264,5 @@ if (conn.results.has('karma', 'awards', thisAward.id)) continue;

exports.check_result_equal = function (thisResult, thisAward, conn) {
var plugin = this;
let plugin = this;
for (var j=0; j < thisResult.length; j++) {
for (let j=0; j < thisResult.length; j++) {
if (thisAward.value === 'true') {

@@ -281,6 +285,6 @@ if (!thisResult[j]) continue;

exports.check_result_match = function (thisResult, thisAward, conn) {
var plugin = this;
var re = new RegExp(thisAward.value, 'i');
let plugin = this;
let re = new RegExp(thisAward.value, 'i');
for (var i=0; i < thisResult.length; i++) {
for (let i=0; i < thisResult.length; i++) {
if (!re.test(thisResult[i])) continue;

@@ -295,9 +299,9 @@ if (conn.results.has('karma', 'awards', thisAward.id)) continue;

exports.check_result_length = function (thisResult, thisAward, conn) {
var plugin = this;
let plugin = this;
for (let j=0; j < thisResult.length; j++) {
// var [operator, qty] = thisAward.value.split(/\s+/); // requires node 6
var matches = thisAward.value.split(/\s+/);
var operator = matches[0];
var qty = matches[1];
// let [operator, qty] = thisAward.value.split(/\s+/); // requires node 6
let matches = thisAward.value.split(/\s+/);
let operator = matches[0];
let qty = matches[1];

@@ -327,3 +331,3 @@ switch (operator) {

exports.apply_tarpit = function (connection, hook, score, next) {
var plugin = this;
let plugin = this;
if (!plugin.cfg.tarpit) { return next(); } // tarpit disabled in config

@@ -336,3 +340,3 @@

// no delay for senders with good karma
var k = connection.results.get('karma');
let k = connection.results.get('karma');
if (score === undefined) { score = parseFloat(k.score); }

@@ -342,7 +346,7 @@ if (score >= 0) { return next(); }

// how long to delay?
var delay = plugin.tarpit_delay(score, connection, hook, k);
let delay = plugin.tarpit_delay(score, connection, hook, k);
if (!delay) return next();
connection.logdebug(plugin, 'tarpitting '+hook+' for ' + delay + 's');
setTimeout(function () {
setTimeout(() => {
connection.logdebug(plugin, 'tarpit '+hook+' end');

@@ -354,3 +358,3 @@ next();

exports.tarpit_delay = function (score, connection, hook, k) {
var plugin = this;
let plugin = this;

@@ -362,3 +366,3 @@ if (plugin.cfg.tarpit.delay && parseFloat(plugin.cfg.tarpit.delay)) {

var delay = score * -1; // progressive tarpit
let delay = score * -1; // progressive tarpit

@@ -371,3 +375,3 @@ // detect roaming users based on MSA ports that require auth

var max = plugin.cfg.tarpit.max || 5;
let max = plugin.cfg.tarpit.max || 5;
if (delay > max) {

@@ -382,4 +386,4 @@ connection.logdebug(plugin, 'tarpit capped to: ' + max);

exports.tarpit_delay_msa = function (connection, delay, k) {
var plugin = this;
var trg = 'tarpit reduced for good';
let plugin = this;
let trg = 'tarpit reduced for good';

@@ -389,3 +393,3 @@ delay = parseFloat(delay);

// Reduce delay for good history
var history = ((k.good || 0) - (k.bad || 0));
let history = ((k.good || 0) - (k.bad || 0));
if (history > 0) {

@@ -397,3 +401,3 @@ delay = delay - 2;

// Reduce delay for good ASN history
var asn = connection.results.get('asn');
let asn = connection.results.get('asn');
if (!asn) { asn = connection.results.get('geoip'); }

@@ -405,3 +409,3 @@ if (asn && asn.asn && k.neighbors > 0) {

var max = plugin.cfg.tarpit.max_msa || 2;
let max = plugin.cfg.tarpit.max_msa || 2;
if (delay > max) {

@@ -416,5 +420,5 @@ connection.logdebug(plugin, 'tarpit capped at: ' + delay);

exports.should_we_deny = function (next, connection, hook) {
var plugin = this;
let plugin = this;
var r = connection.results.get('karma');
let r = connection.results.get('karma');
if (!r) { return next(); }

@@ -424,3 +428,3 @@

var score = parseFloat(r.score);
let score = parseFloat(r.score);
if (isNaN(score)) {

@@ -432,3 +436,3 @@ connection.logerror(plugin, 'score is NaN');

var negative_limit = -5;
let negative_limit = -5;
if (plugin.cfg.thresholds && plugin.cfg.thresholds.negative) {

@@ -445,3 +449,3 @@ negative_limit = parseFloat(plugin.cfg.thresholds.negative);

var rejectMsg = 'very bad karma score: {score}';
let rejectMsg = 'very bad karma score: {score}';
if (plugin.cfg.deny && plugin.cfg.deny.message) {

@@ -456,3 +460,3 @@ rejectMsg = plugin.cfg.deny.message;

return plugin.apply_tarpit(connection, hook, score, function () {
return plugin.apply_tarpit(connection, hook, score, () => {
next(constants.DENY, rejectMsg);

@@ -463,9 +467,9 @@ });

exports.hook_deny = function (next, connection, params) {
var plugin = this;
// var pi_deny = params[0]; // (constants.deny, denysoft, ok)
// var pi_message = params[1];
var pi_name = params[2];
// var pi_function = params[3];
// var pi_params = params[4];
var pi_hook = params[5];
let plugin = this;
// let pi_deny = params[0]; // (constants.deny, denysoft, ok)
// let pi_message = params[1];
let pi_name = params[2];
// let pi_function = params[3];
// let pi_params = params[4];
let pi_hook = params[5];

@@ -493,4 +497,4 @@ // exceptions, whose 'DENY' should not be captured

exports.hook_connect = function (next, connection) {
var plugin = this;
var asnkey = plugin.get_asn_key(connection);
let plugin = this;
let asnkey = plugin.get_asn_key(connection);
if (asnkey) {

@@ -520,3 +524,3 @@ plugin.check_asn(connection, asnkey);

exports.hook_reset_transaction = function (next, connection) {
var plugin = this;
let plugin = this;
connection.results.add(plugin, {emit: true});

@@ -527,3 +531,3 @@ plugin.should_we_deny(next, connection, 'reset_transaction');

exports.hook_unrecognized_command = function (next, connection, cmd) {
var plugin = this;
let plugin = this;

@@ -537,6 +541,6 @@ connection.results.incr(plugin, {score: -1});

exports.ip_history_from_redis = function (next, connection) {
var plugin = this;
let plugin = this;
var expire = (plugin.cfg.redis.expire_days || 60) * 86400; // to days
var dbkey = 'karma|' + connection.remote.ip;
let expire = (plugin.cfg.redis.expire_days || 60) * 86400; // to days
let dbkey = 'karma|' + connection.remote.ip;

@@ -546,3 +550,3 @@ // redis plugin is emitting errors, no need to here

plugin.db.hgetall(dbkey, function (err, dbr) {
plugin.db.hgetall(dbkey, (err, dbr) => {
if (err) {

@@ -561,7 +565,7 @@ connection.results.add(plugin, {err: err});

.expire(dbkey, expire) // extend expiration
.exec(function (err2, replies) {
.exec((err2, replies) => {
if (err2) connection.results.add(plugin, {err: err2});
});
var results = {
let results = {
good: dbr.good,

@@ -590,3 +594,3 @@ bad: dbr.bad,

exports.hook_mail = function (next, connection, params) {
var plugin = this;
let plugin = this;

@@ -596,3 +600,3 @@ plugin.check_spammy_tld(params[0], connection);

// look for invalid (RFC 5321,(2)821) space in envelope from
var full_from = connection.current_line;
let full_from = connection.current_line;
if (full_from.toUpperCase().substring(0,11) !== 'MAIL FROM:<') {

@@ -607,4 +611,4 @@ connection.loginfo(plugin, 'RFC ignorant env addr format: ' + full_from);

exports.hook_rcpt = function (next, connection, params) {
var plugin = this;
var rcpt = params[0];
let plugin = this;
let rcpt = params[0];

@@ -616,3 +620,3 @@ // hook_rcpt catches recipients that no rcpt_to plugin permitted

// 2015-05 30-day sample: 84% spam correlation
var txn = connection.transaction;
let txn = connection.transaction;
if (txn && txn.mail_from && txn.mail_from.user === rcpt.user) {

@@ -630,5 +634,5 @@ connection.results.add(plugin, {fail: 'env_user_match'});

exports.hook_rcpt_ok = function (next, connection, rcpt) {
var plugin = this;
let plugin = this;
var txn = connection.transaction;
let txn = connection.transaction;
if (txn && txn.mail_from && txn.mail_from.user === rcpt.user) {

@@ -645,7 +649,7 @@ connection.results.add(plugin, {fail: 'env_user_match'});

// goal: prevent delivery of spam before queue
var plugin = this;
let plugin = this;
plugin.check_awards(connection); // update awards
var results = connection.results.collate(plugin);
let results = connection.results.collate(plugin);
connection.logdebug(plugin, 'adding header: ' + results);

@@ -658,3 +662,3 @@ connection.transaction.add_header('X-Haraka-Karma', results);

exports.increment = function (connection, key, val) {
var plugin = this;
let plugin = this;
if (!plugin.db) return;

@@ -664,3 +668,3 @@

var asnkey = plugin.get_asn_key(connection);
let asnkey = plugin.get_asn_key(connection);
if (asnkey) plugin.db.hincrby(asnkey, key, 1);

@@ -670,7 +674,7 @@ };

exports.hook_disconnect = function (next, connection) {
var plugin = this;
let plugin = this;
plugin.redis_unsubscribe(connection);
var k = connection.results.get('karma');
let k = connection.results.get('karma');
if (!k || k.score === undefined) {

@@ -699,6 +703,6 @@ connection.results.add(plugin, {err: 'karma results missing'});

exports.get_award_loc_from_note = function (connection, award) {
var plugin = this;
let plugin = this;
if (connection.transaction) {
var obj = plugin.assemble_note_obj(connection.transaction, award);
let obj = plugin.assemble_note_obj(connection.transaction, award);
if (obj) { return obj; }

@@ -708,4 +712,4 @@ }

// connection.logdebug(plugin, 'no txn note: ' + award);
obj = plugin.assemble_note_obj(connection, award);
if (obj) { return obj; }
let obj = plugin.assemble_note_obj(connection, award);
if (obj) return obj;

@@ -718,4 +722,4 @@ // connection.logdebug(plugin, 'no conn note: ' + award);

var pi_name = loc_bits[1];
var notekey = loc_bits[2];
let pi_name = loc_bits[1];
let notekey = loc_bits[2];

@@ -747,5 +751,5 @@ if (phase_prefixes[pi_name]) {

// based on award key, find the requested note or result
var plugin = this;
var bits = award_key.split('@');
var loc_bits = bits[0].split('.');
let plugin = this;
let bits = award_key.split('@');
let loc_bits = bits[0].split('.');
if (loc_bits.length === 1) { // ex: relaying

@@ -775,7 +779,7 @@ return connection[bits[0]];

exports.get_award_condition = function (note_key, note_val) {
var wants;
var keybits = note_key.split('@');
let wants;
let keybits = note_key.split('@');
if (keybits[1]) { wants = keybits[1]; }
var valbits = note_val.split(/\s+/);
let valbits = note_val.split(/\s+/);
if (!valbits[1]) { return wants; }

@@ -791,8 +795,8 @@ if (valbits[1] !== 'if') { return wants; } // no if condition

exports.check_awards = function (connection) {
var plugin = this;
var karma = connection.results.get('karma');
let plugin = this;
let karma = connection.results.get('karma');
if (!karma ) return;
if (!karma.todo) return;
for (var key in karma.todo) {
for (let key in karma.todo) {
// loc = terms

@@ -802,11 +806,11 @@ // note_location [@wants] = award [conditions]

// results.geoip.distance@4000 = -1 if gt 4000
var award_terms = karma.todo[key];
let award_terms = karma.todo[key];
var note = plugin.get_award_location(connection, key);
let note = plugin.get_award_location(connection, key);
if (note === undefined) { continue; }
var wants = plugin.get_award_condition(key, award_terms);
let wants = plugin.get_award_condition(key, award_terms);
// test the desired condition
var bits = award_terms.split(/\s+/);
var award = parseFloat(bits[0]);
let bits = award_terms.split(/\s+/);
let award = parseFloat(bits[0]);
if (!bits[1] || bits[1] !== 'if') { // no if conditions

@@ -827,3 +831,3 @@ if (!note) { continue; } // failed truth test

// Matches fall through (break) to the apply_award below.
var condition = bits[2];
let condition = bits[2];
switch (condition) {

@@ -866,3 +870,3 @@ case 'equals':

case 'in': // if in pass whitelisted
// var list = bits[3];
// let list = bits[3];
if (bits[4]) { wants = bits[4]; }

@@ -882,3 +886,3 @@ if (!Array.isArray(note)) { continue; }

exports.apply_award = function (connection, nl, award) {
var plugin = this;
let plugin = this;
if (!award) { return; }

@@ -891,3 +895,3 @@ if (isNaN(award)) { // garbage in config

var bits = nl.split('@'); nl = bits[0]; // strip off @... if present
let bits = nl.split('@'); nl = bits[0]; // strip off @... if present

@@ -897,3 +901,3 @@ connection.results.incr(plugin, {score: award});

var trimmed = nl.substring(0, 5) === 'notes' ? nl.substring(6) :
let trimmed = nl.substring(0, 5) === 'notes' ? nl.substring(6) :
nl.substring(0, 7) === 'results' ? nl.substring(8) :

@@ -913,10 +917,10 @@ nl.substring(0,19) === 'transaction.results' ?

exports.check_spammy_tld = function (mail_from, connection) {
var plugin = this;
let plugin = this;
if (!plugin.cfg.spammy_tlds) { return; }
if (mail_from.isNull()) { return; } // null sender (bounce)
var from_tld = mail_from.host.split('.').pop();
let from_tld = mail_from.host.split('.').pop();
// connection.logdebug(plugin, 'from_tld: ' + from_tld);
var tld_penalty = parseFloat(plugin.cfg.spammy_tlds[from_tld] || 0);
let tld_penalty = parseFloat(plugin.cfg.spammy_tlds[from_tld] || 0);
if (tld_penalty === 0) { return; }

@@ -929,6 +933,6 @@

exports.check_syntax_RcptTo = function (connection) {
var plugin = this;
let plugin = this;
// look for an illegal (RFC 5321,(2)821) space in envelope recipient
var full_rcpt = connection.current_line;
let full_rcpt = connection.current_line;
if (full_rcpt.toUpperCase().substring(0,9) === 'RCPT TO:<') { return; }

@@ -942,6 +946,6 @@

exports.assemble_note_obj = function (prefix, key) {
var note = prefix;
var parts = key.split('.');
let note = prefix;
let parts = key.split('.');
while (parts.length > 0) {
var next = parts.shift();
let next = parts.shift();
if (phase_prefixes[next]) {

@@ -957,6 +961,6 @@ next = next + '.' + parts.shift();

exports.check_asn = function (connection, asnkey) {
var plugin = this;
let plugin = this;
if (!plugin.db) return;
var report_as = { name: plugin.name };
let report_as = { name: plugin.name };

@@ -967,3 +971,3 @@ if (plugin.cfg.asn.report_as) {

plugin.db.hgetall(asnkey, function (err, res) {
plugin.db.hgetall(asnkey, (err, res) => {
if (err) {

@@ -975,3 +979,3 @@ connection.results.add(plugin, {err: err});

if (res === null) {
var expire = (plugin.cfg.redis.expire_days || 60) * 86400; // days
let expire = (plugin.cfg.redis.expire_days || 60) * 86400; // days
plugin.init_asn(asnkey, expire);

@@ -982,4 +986,4 @@ return;

plugin.db.hincrby(asnkey, 'connections', 1);
var asn_score = parseInt(res.good || 0) - (res.bad || 0);
var asn_results = {
let asn_score = parseInt(res.good || 0) - (res.bad || 0);
let asn_results = {
asn_score: asn_score,

@@ -1013,3 +1017,3 @@ asn_connections: res.connections,

exports.init_ip = function (dbkey, rip, expire) {
var plugin = this;
let plugin = this;
if (!plugin.db) return;

@@ -1023,5 +1027,5 @@ plugin.db.multi()

exports.get_asn_key = function (connection) {
var plugin = this;
let plugin = this;
if (!plugin.cfg.asn.enable) { return; }
var asn = connection.results.get('asn');
let asn = connection.results.get('asn');
if (!asn || !asn.asn) {

@@ -1035,3 +1039,3 @@ asn = connection.results.get('geoip');

exports.init_asn = function (asnkey, expire) {
var plugin = this;
let plugin = this;
if (!plugin.db) return;

@@ -1038,0 +1042,0 @@ plugin.db.multi()

{
"name": "haraka-plugin-karma",
"version": "1.0.8",
"version": "1.0.9",
"description": "Watch live SMTP traffic in a web interface",
"main": "index.js",
"scripts": {
"test": "./run_tests",
"test": "node run_tests",
"lint": "./node_modules/.bin/eslint *.js test/*.js",

@@ -9,0 +9,0 @@ "lintfix": "./node_modules/.bin/eslint --fix *.js test/*.js",

[![Build Status][ci-img]][ci-url]
[![Windows Build Status][ci-win-img]][ci-win-url]
[![Code Climate][clim-img]][clim-url]

@@ -207,2 +208,4 @@ [![Greenkeeper badge][gk-img]][gk-url]

[ci-url]: https://travis-ci.org/haraka/haraka-plugin-karma
[ci-win-img]: https://ci.appveyor.com/api/projects/status/eut008hijj7gt995?svg=true
[ci-win-url]: https://ci.appveyor.com/project/msimerson/haraka-plugin-karma
[cov-img]: https://codecov.io/github/haraka/haraka-plugin-karma/coverage.svg

@@ -209,0 +212,0 @@ [cov-url]: https://codecov.io/github/haraka/haraka-plugin-karma

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc