Security News
Weekly Downloads Now Available in npm Package Search Results
Socket's package search now displays weekly downloads for npm packages, helping developers quickly assess popularity and make more informed decisions.
@integromat/imap
Advanced tools
An IMAP module for node.js that makes communicating with IMAP servers easy
node-imap is an IMAP client module for node.js.
This module does not perform any magic such as auto-decoding of messages/attachments or parsing of email addresses (node-imap leaves all mail header values as-is).
An upgrade guide from node-imap v0.7.x to v0.8.x can be found here.
node.js -- v0.8.0 or newer
An IMAP server to connect to -- tested with gmail
npm install imap
var Imap = require('imap'),
inspect = require('util').inspect;
var imap = new Imap({
user: 'mygmailname@gmail.com',
password: 'mygmailpassword',
host: 'imap.gmail.com',
port: 993,
tls: true
});
function openInbox(cb) {
imap.openBox('INBOX', true, cb);
}
imap.once('ready', function() {
openInbox(function(err, box) {
if (err) throw err;
var f = imap.seq.fetch('1:3', {
bodies: 'HEADER.FIELDS (FROM TO SUBJECT DATE)',
struct: true
});
f.on('message', function(msg, seqno) {
console.log('Message #%d', seqno);
var prefix = '(#' + seqno + ') ';
msg.on('body', function(stream, info) {
var buffer = '';
stream.on('data', function(chunk) {
buffer += chunk.toString('utf8');
});
stream.once('end', function() {
console.log(prefix + 'Parsed header: %s', inspect(Imap.parseHeader(buffer)));
});
});
msg.once('attributes', function(attrs) {
console.log(prefix + 'Attributes: %s', inspect(attrs, false, 8));
});
msg.once('end', function() {
console.log(prefix + 'Finished');
});
});
f.once('error', function(err) {
console.log('Fetch error: ' + err);
});
f.once('end', function() {
console.log('Done fetching all messages!');
imap.end();
});
});
});
imap.once('error', function(err) {
console.log(err);
});
imap.once('end', function() {
console.log('Connection ended');
});
imap.connect();
// using the functions and variables already defined in the first example ...
openInbox(function(err, box) {
if (err) throw err;
var f = imap.seq.fetch(box.messages.total + ':*', { bodies: ['HEADER.FIELDS (FROM)','TEXT'] });
f.on('message', function(msg, seqno) {
console.log('Message #%d', seqno);
var prefix = '(#' + seqno + ') ';
msg.on('body', function(stream, info) {
if (info.which === 'TEXT')
console.log(prefix + 'Body [%s] found, %d total bytes', inspect(info.which), info.size);
var buffer = '', count = 0;
stream.on('data', function(chunk) {
count += chunk.length;
buffer += chunk.toString('utf8');
if (info.which === 'TEXT')
console.log(prefix + 'Body [%s] (%d/%d)', inspect(info.which), count, info.size);
});
stream.once('end', function() {
if (info.which !== 'TEXT')
console.log(prefix + 'Parsed header: %s', inspect(Imap.parseHeader(buffer)));
else
console.log(prefix + 'Body [%s] Finished', inspect(info.which));
});
});
msg.once('attributes', function(attrs) {
console.log(prefix + 'Attributes: %s', inspect(attrs, false, 8));
});
msg.once('end', function() {
console.log(prefix + 'Finished');
});
});
f.once('error', function(err) {
console.log('Fetch error: ' + err);
});
f.once('end', function() {
console.log('Done fetching all messages!');
imap.end();
});
});
// using the functions and variables already defined in the first example ...
var fs = require('fs'), fileStream;
openInbox(function(err, box) {
if (err) throw err;
imap.search([ 'UNSEEN', ['SINCE', 'May 20, 2010'] ], function(err, results) {
if (err) throw err;
var f = imap.fetch(results, { bodies: '' });
f.on('message', function(msg, seqno) {
console.log('Message #%d', seqno);
var prefix = '(#' + seqno + ') ';
msg.on('body', function(stream, info) {
console.log(prefix + 'Body');
stream.pipe(fs.createWriteStream('msg-' + seqno + '-body.txt'));
});
msg.once('attributes', function(attrs) {
console.log(prefix + 'Attributes: %s', inspect(attrs, false, 8));
});
msg.once('end', function() {
console.log(prefix + 'Finished');
});
});
f.once('error', function(err) {
console.log('Fetch error: ' + err);
});
f.once('end', function() {
console.log('Done fetching all messages!');
imap.end();
});
});
});
MessageSource can be a single message identifier, a message identifier range (e.g. '2504:2507'
or '*'
or '2504:*'
), an array of message identifiers, or an array of message identifier ranges.
Box is an object representing the currently open mailbox, and has the following properties:
permFlags
may be stored for the current session only. Additional server implementation-specific flags may also be available.ImapMessage is an object representing an email message. It consists of:
info
properties:
attrs
properties:
ImapFetch is an object representing a fetch() request. It consists of:
seqno
is the message's sequence number.A message structure with multiple parts might look something like the following:
[ { type: 'mixed',
params: { boundary: '000e0cd294e80dc84c0475bf339d' },
disposition: null,
language: null,
location: null
},
[ { type: 'alternative',
params: { boundary: '000e0cd294e80dc83c0475bf339b' },
disposition: null,
language: null
},
[ { partID: '1.1',
type: 'text',
subtype: 'plain',
params: { charset: 'ISO-8859-1' },
id: null,
description: null,
encoding: '7BIT',
size: 935,
lines: 46,
md5: null,
disposition: null,
language: null
}
],
[ { partID: '1.2',
type: 'text',
subtype: 'html',
params: { charset: 'ISO-8859-1' },
id: null,
description: null,
encoding: 'QUOTED-PRINTABLE',
size: 1962,
lines: 33,
md5: null,
disposition: null,
language: null
}
]
],
[ { partID: '2',
type: 'application',
subtype: 'octet-stream',
params: { name: 'somefile' },
id: null,
description: null,
encoding: 'BASE64',
size: 98,
lines: null,
md5: null,
disposition:
{ type: 'attachment',
params: { filename: 'somefile' }
},
language: null,
location: null
}
]
]
The above structure describes a message having both an attachment and two forms of the message body (plain text and HTML). Each message part is identified by a partID which is used when you want to fetch the content of that part (see fetch()).
The structure of a message with only one part will simply look something like this:
[ { partID: '1',
type: 'text',
subtype: 'plain',
params: { charset: 'ISO-8859-1' },
id: null,
description: null,
encoding: '7BIT',
size: 935,
lines: 46,
md5: null,
disposition: null,
language: null
}
]
Therefore, an easy way to check for a multipart message is to check if the structure length is >1.
Lastly, here are the system flags defined by RFC3501 that may be added/removed:
It should be noted however that the IMAP server can limit which flags can be permanently modified for any given message. If in doubt, check the mailbox's permFlags first. Additional custom flags may be provided by the server. If available, these will also be listed in the mailbox's permFlags.
require('imap') returns one object: Connection.
ready() - Emitted when a connection to the server has been made and authentication was successful.
alert(< string >message) - Emitted when the server issues an alert (e.g. "the server is going down for maintenance").
mail(< integer >numNewMsgs) - Emitted when new mail arrives in the currently open mailbox.
expunge(< integer >seqno) - Emitted when a message was expunged externally. seqno
is the sequence number (instead of the unique UID) of the message that was expunged. If you are caching sequence numbers, all sequence numbers higher than this value MUST be decremented by 1 in order to stay synchronized with the server and to keep correct continuity.
uidvalidity(< integer >uidvalidity) - Emitted if the UID validity value for the currently open mailbox changes during the current session.
update(< integer >seqno, < object >info) - Emitted when message metadata (e.g. flags) changes externally.
error(< Error >err) - Emitted when an error occurs. The 'source' property will be set to indicate where the error originated from.
close(< boolean >hadError) - Emitted when the connection has completely closed.
end() - Emitted when the connection has ended.
state - string - The current state of the connection (e.g. 'disconnected', 'connected', 'authenticated').
delimiter - string - The (top-level) mailbox hierarchy delimiter. If the server does not support mailbox hierarchies and only a flat list, this value will be falsey.
namespaces - object - Contains information about each namespace type (if supported by the server) with the following properties:
There should always be at least one entry (although the IMAP spec allows for more, it doesn't seem to be very common) in the personal namespace list, with a blank namespace prefix. Each property's array contains objects of the following format (with example values):
{ prefix: '', // A string containing the prefix to use to access mailboxes in this namespace
delimiter: '/', // A string containing the hierarchy delimiter for this namespace, or boolean false
// for a flat namespace with no hierarchy
extensions: [ // An array of namespace extensions supported by this namespace, or null if none
// are specified
{ name: 'X-FOO-BAR', // A string indicating the extension name
params: [ 'BAZ' ] // An array of strings containing the parameters for this extension,
// or null if none are specified
}
]
}
disableAutoDecode
to true to disable automatic decoding of MIME encoded-words that may exist in header field values.Note: Message UID ranges are not guaranteed to be contiguous.
(constructor)([< object >config]) - Connection - Creates and returns a new instance of Connection using the specified configuration object. Valid config properties are:
true
to enable keepalive with defaults or set to object to enable and configure keepalive behavior: Default: true
idleInterval
is checked. Default: 10000true
to force use of NOOP keepalive on servers also support IDLE. Default: falseconnect() - (void) - Attempts to connect and authenticate with the IMAP server.
end() - (void) - Closes the connection to the server after all requests in the queue have been sent.
destroy() - (void) - Immediately destroys the connection to the server.
openBox(< string >mailboxName[, < boolean >openReadOnly=false[, < object >modifiers]], < function >callback) - (void) - Opens a specific mailbox that exists on the server. mailboxName
should include any necessary prefix/path. modifiers
is used by IMAP extensions. callback
has 2 parameters: < Error >err, < Box >mailbox.
closeBox([< boolean >autoExpunge=true, ]< function >callback) - (void) - Closes the currently open mailbox. If autoExpunge
is true, any messages marked as Deleted in the currently open mailbox will be removed if the mailbox was NOT opened in read-only mode. If autoExpunge
is false, you disconnect, or you open another mailbox, messages marked as Deleted will NOT be removed from the currently open mailbox. callback
has 1 parameter: < Error >err.
addBox(< string >mailboxName, < function >callback) - (void) - Creates a new mailbox on the server. mailboxName
should include any necessary prefix/path. callback
has 1 parameter: < Error >err.
delBox(< string >mailboxName, < function >callback) - (void) - Removes a specific mailbox that exists on the server. mailboxName
should including any necessary prefix/path. callback
has 1 parameter: < Error >err.
renameBox(< string >oldMailboxName, < string >newMailboxName, < function >callback) - (void) - Renames a specific mailbox that exists on the server. Both oldMailboxName
and newMailboxName
should include any necessary prefix/path. callback
has 2 parameters: < Error >err, < Box >mailbox. Note: Renaming the 'INBOX' mailbox will instead cause all messages in 'INBOX' to be moved to the new mailbox.
subscribeBox(< string >mailboxName, < function >callback) - (void) - Subscribes to a specific mailbox that exists on the server. mailboxName
should include any necessary prefix/path. callback
has 1 parameter: < Error >err.
unsubscribeBox(< string >mailboxName, < function >callback) - (void) - Unsubscribes from a specific mailbox that exists on the server. mailboxName
should include any necessary prefix/path. callback
has 1 parameter: < Error >err.
status(< string >mailboxName, < function >callback) - (void) - Fetches information about a mailbox other than the one currently open. callback
has 2 parameters: < Error >err, < Box >mailbox. Note: There is no guarantee that this will be a fast operation on the server. Also, do not call this on the currently open mailbox.
getBoxes([< string >nsPrefix, ]< function >callback) - (void) - Obtains the full list of mailboxes. If nsPrefix
is not specified, the main personal namespace is used. callback
has 2 parameters: < Error >err, < object >boxes. boxes
has the following format (with example values):
{ INBOX: // mailbox name
{ attribs: [], // mailbox attributes. An attribute of 'NOSELECT' indicates the mailbox cannot
// be opened
delimiter: '/', // hierarchy delimiter for accessing this mailbox's direct children.
children: null, // an object containing another structure similar in format to this top level,
// otherwise null if no children
parent: null // pointer to parent mailbox, null if at the top level
},
Work:
{ attribs: [],
delimiter: '/',
children: null,
parent: null
},
'[Gmail]':
{ attribs: [ '\\NOSELECT' ],
delimiter: '/',
children:
{ 'All Mail':
{ attribs: [ '\\All' ],
delimiter: '/',
children: null,
parent: [Circular]
},
Drafts:
{ attribs: [ '\\Drafts' ],
delimiter: '/',
children: null,
parent: [Circular]
},
Important:
{ attribs: [ '\\Important' ],
delimiter: '/',
children: null,
parent: [Circular]
},
'Sent Mail':
{ attribs: [ '\\Sent' ],
delimiter: '/',
children: null,
parent: [Circular]
},
Spam:
{ attribs: [ '\\Junk' ],
delimiter: '/',
children: null,
parent: [Circular]
},
Starred:
{ attribs: [ '\\Flagged' ],
delimiter: '/',
children: null,
parent: [Circular]
},
Trash:
{ attribs: [ '\\Trash' ],
delimiter: '/',
children: null,
parent: [Circular]
}
},
parent: null
}
}
getSubscribedBoxes([< string >nsPrefix, ]< function >callback) - (void) - Obtains the full list of subscribed mailboxes. If nsPrefix
is not specified, the main personal namespace is used. callback
has 2 parameters: < Error >err, < object >boxes. boxes
has the same format as getBoxes above.
expunge([< MessageSource >uids, ]< function >callback) - (void) - Permanently removes all messages flagged as Deleted in the currently open mailbox. If the server supports the 'UIDPLUS' capability, uids
can be supplied to only remove messages that both have their uid in uids
and have the \Deleted flag set. callback
has 1 parameter: < Error >err. Note: At least on Gmail, performing this operation with any currently open mailbox that is not the Spam or Trash mailbox will merely archive any messages marked as Deleted (by moving them to the 'All Mail' mailbox).
append(< mixed >msgData, [< object >options, ]< function >callback) - (void) - Appends a message to selected mailbox. msgData
is a string or Buffer containing an RFC-822 compatible MIME message. Valid options
properties are:
['Seen', 'Flagged']
) to append to the message. Default: (no flags)callback
has 1 parameter: < Error >err.
All functions below have sequence number-based counterparts that can be accessed by using the 'seq' namespace of the imap connection's instance (e.g. conn.seq.search() returns sequence number(s) instead of UIDs, conn.seq.fetch() fetches by sequence number(s) instead of UIDs, etc):
search(< array >criteria, < function >callback) - (void) - Searches the currently open mailbox for messages using given criteria. criteria
is a list describing what you want to find. For criteria types that require arguments, use an array instead of just the string criteria type name (e.g. ['FROM', 'foo@bar.com']). Prefix criteria types with an "!" to negate.
The following message flags are valid types that do not have arguments:
The following are valid types that require string value(s):
The following are valid types that require a string parseable by JavaScript's Date object OR a Date instance:
The following are valid types that require one Integer value:
The following are valid criterion that require one or more Integer values:
Note 1: For the UID-based search (i.e. "conn.search()"), you can retrieve the UIDs for sequence numbers by just supplying an array of sequence numbers and/or ranges as a criteria (e.g. [ '24:29', 19, '66:*' ]).
Note 2: By default, all criterion are ANDed together. You can use the special 'OR' on two criterion to find messages matching either search criteria (see example below).
criteria
examples:
callback
has 2 parameters: < Error >err, < array >UIDs.
fetch(< MessageSource >source, [< object >options]) - ImapFetch - Fetches message(s) in the currently open mailbox.
Valid options
properties are:
* **markSeen** - _boolean_ - Mark message(s) as read when fetched. **Default:** false
* **struct** - _boolean_ - Fetch the message structure. **Default:** false
* **envelope** - _boolean_ - Fetch the message envelope. **Default:** false
* **size** - _boolean_ - Fetch the RFC822 size. **Default:** false
* **modifiers** - _object_ - Fetch modifiers defined by IMAP extensions. **Default:** (none)
* **extensions** - _array_ - Fetch custom fields defined by IMAP extensions, e.g. ['X-MAILBOX', 'X-REAL-UID']. **Default:** (none)
* **bodies** - _mixed_ - A string or Array of strings containing the body part section to fetch. **Default:** (none) Example sections:
* 'HEADER' - The message header
* 'HEADER.FIELDS (TO FROM SUBJECT)' - Specific header fields only
* 'HEADER.FIELDS.NOT (TO FROM SUBJECT)' - Header fields only that do not match the fields given
* 'TEXT' - The message body
* '' - The entire message (header + body)
* 'MIME' - MIME-related header fields only (e.g. 'Content-Type')
**Note:** You can also prefix `bodies` strings (i.e. 'TEXT', 'HEADER', 'HEADER.FIELDS', and 'HEADER.FIELDS.NOT' for `message/rfc822` messages and 'MIME' for any kind of message) with part ids. For example: '1.TEXT', '1.2.HEADER', '2.MIME', etc.
**Note 2:** 'HEADER*' sections are only valid for parts whose content type is `message/rfc822`, including the root part (no part id).
copy(< MessageSource >source, < string >mailboxName, < function >callback) - (void) - Copies message(s) in the currently open mailbox to another mailbox. callback
has 1 parameter: < Error >err.
move(< MessageSource >source, < string >mailboxName, < function >callback) - (void) - Moves message(s) in the currently open mailbox to another mailbox. callback
has 1 parameter: < Error >err. Note: The message(s) in the destination mailbox will have a new message UID.
addFlags(< MessageSource >source, < mixed >flags, < function >callback) - (void) - Adds flag(s) to message(s). callback
has 1 parameter: < Error >err.
delFlags(< MessageSource >source, < mixed >flags, < function >callback) - (void) - Removes flag(s) from message(s). callback
has 1 parameter: < Error >err.
setFlags(< MessageSource >source, < mixed >flags, < function >callback) - (void) - Sets the flag(s) for message(s). callback
has 1 parameter: < Error >err.
addKeywords(< MessageSource >source, < mixed >keywords, < function >callback) - (void) - Adds keyword(s) to message(s). keywords
is either a single keyword or an array of keywords. callback
has 1 parameter: < Error >err.
delKeywords(< MessageSource >source, < mixed >keywords, < function >callback) - (void) - Removes keyword(s) from message(s). keywords
is either a single keyword or an array of keywords. callback
has 1 parameter: < Error >err.
setKeywords(< MessageSource >source, < mixed >keywords, < function >callback) - (void) - Sets keyword(s) for message(s). keywords
is either a single keyword or an array of keywords. callback
has 1 parameter: < Error >err.
serverSupports(< string >capability) - boolean - Checks if the server supports the specified capability.
Gmail
Server capability: X-GM-EXT-1
search() criteria extensions:
fetch() will automatically retrieve the thread id, unique message id, and labels (named 'x-gm-thrid', 'x-gm-msgid', 'x-gm-labels' respectively)
Additional Connection instance methods (seqno-based counterparts exist):
setLabels(< MessageSource >source, < mixed >labels, < function >callback) - (void) - Replaces labels of message(s) with labels
. labels
is either a single label or an array of labels. callback
has 1 parameter: < Error >err.
addLabels(< MessageSource >source, < mixed >labels, < function >callback) - (void) - Adds labels
to message(s). labels
is either a single label or an array of labels. callback
has 1 parameter: < Error >err.
delLabels(< MessageSource >source, < mixed >labels, < function >callback) - (void) - Removes labels
from message(s). labels
is either a single label or an array of labels. callback
has 1 parameter: < Error >err.
RFC2087
Server capability: QUOTA
Additional Connection instance methods:
setQuota(< string >quotaRoot, < object >quotas, < function >callback) - (void) - Sets the resource limits for quotaRoot
using the limits in quotas
. callback
has 2 parameters: < Error >err, < object >limits. limits
has the same format as limits
passed to getQuota()'s callback. Example quotas
properties (taken from RFC2087):
getQuota(< string >quotaRoot, < function >callback) - (void) - Gets the resource usage and limits for quotaRoot
. callback
has 2 parameters: < Error >err, < object >limits. limits
is keyed on the resource name, where the values are objects with the following properties:
getQuotaRoot(< string >mailbox, < function >callback) - (void) - Gets the list of quota roots for mailbox
and the resource usage and limits for each. callback
has 2 parameters: < Error >err, < object >info. info
is keyed on the quota root name, where the values are objects structured like limits
given by getQuota(). Example info
:
{
'': {
storage: { usage: 20480, limit: 102400 }
},
foo: {
storage: { usage: 1024, limit: 4096 },
message: { usage: 14, limit: 9001 }
}
}
RFC4315
Server capability: UIDPLUS
The callback passed to append() will receive an additional argument (the UID of the appended message): < integer >appendedUID.
The callback passed to copy(), move(), seq.copy(), seq.move() will receive an additional argument (the UID(s) of the copied message(s) in the destination mailbox): < mixed >newUIDs. newUIDs
can be an integer if just one message was copied, or a string for multiple messages (e.g. '100:103' or '100,125,130' or '100,200:201').
RFC4551
Server capability: CONDSTORE
Connection event 'update' info may contain the additional property:
search() criteria extensions:
modseq
callback parameter is the highest modification sequence value of all the messages identified in the search results.fetch() will automatically retrieve the modification sequence value (named 'modseq') for each message.
fetch() modifier:
The Box type can now have the following property when using openBox() or status():
Additional Connection instance methods (seqno-based counterparts exist):
addFlagsSince(< MessageSource >source, < mixed >flags, < string >modseq, < function >callback) - (void) - Adds flag(s) to message(s) that have not changed since modseq
. flags
is either a single flag or an array of flags. callback
has 1 parameter: < Error >err.
delFlagsSince(< MessageSource >source, < mixed >flags, < string >modseq, < function >callback) - (void) - Removes flag(s) from message(s) that have not changed since modseq
. flags
is either a single flag or an array of flags. callback
has 1 parameter: < Error >err.
setFlagsSince(< MessageSource >source, < mixed >flags, < string >modseq, < function >callback) - (void) - Sets the flag(s) for message(s) that have not changed since modseq
. flags
is either a single flag or an array of flags. callback
has 1 parameter: < Error >err.
addKeywordsSince(< MessageSource >source, < mixed >keywords, < string >modseq, < function >callback) - (void) - Adds keyword(s) to message(s) that have not changed since modseq
. keywords
is either a single keyword or an array of keywords. callback
has 1 parameter: < Error >err.
delKeywordsSince(< MessageSource >source, < mixed >keywords, < string >modseq, < function >callback) - (void) - Removes keyword(s) from message(s) that have not changed since modseq
. keywords
is either a single keyword or an array of keywords. callback
has 1 parameter: < Error >err.
setKeywordsSince(< MessageSource >source, < mixed >keywords, < string >modseq, < function >callback) - (void) - Sets keyword(s) for message(s) that have not changed since modseq
. keywords
is either a single keyword or an array of keywords. callback
has 1 parameter: < Error >err.
RFC4731
Server capability: ESEARCH
Additional Connection instance methods (seqno-based counterpart exists):
esearch(< array >criteria, < array >options, < function >callback) - (void) - A variant of search() that can return metadata about results. callback
has 2 parameters: < Error >err, < object >info. info
has possible keys: 'all', 'min', 'max', 'count'. Valid options
:
Note: specifying no options
or [] for options
is the same as ['ALL']
RFC5256
Server capability: SORT
Additional Connection instance methods (seqno-based counterpart exists):
sort(< array >sortCriteria, < array >searchCriteria, < function >callback) - (void) - Performs a sorted search(). A seqno-based counterpart also exists for this function. callback
has 2 parameters: < Error >err, < array >UIDs. Valid sortCriteria
are (reverse sorting of individual criteria is done by prefixing the criteria with '-'):
Server capability: THREAD=REFERENCES, THREAD=ORDEREDSUBJECT
Additional Connection instance methods (seqno-based counterpart exists):
searchCriteria
and groups the resulting search results using the given algorithm
(e.g. 'references', 'orderedsubject'). callback
has 2 parameters: < Error >err, < array >UIDs. UIDs
is a nested array.Several things not yet implemented in no particular order:
FAQs
An IMAP module for node.js that makes communicating with IMAP servers easy
The npm package @integromat/imap receives a total of 103 weekly downloads. As such, @integromat/imap popularity was classified as not popular.
We found that @integromat/imap demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 10 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Socket's package search now displays weekly downloads for npm packages, helping developers quickly assess popularity and make more informed decisions.
Security News
A Stanford study reveals 9.5% of engineers contribute almost nothing, costing tech $90B annually, with remote work fueling the rise of "ghost engineers."
Research
Security News
Socket’s threat research team has detected six malicious npm packages typosquatting popular libraries to insert SSH backdoors.