Security News
Research
Data Theft Repackaged: A Case Study in Malicious Wrapper Packages on npm
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
music-metadata
Advanced tools
Music metadata parser for Node.js, supporting virtual any audio and tag format.
The music-metadata npm package is a powerful tool for parsing and extracting metadata from audio files. It supports a wide range of audio formats and provides detailed information about the audio file, including tags, format, and technical properties.
Extract Basic Metadata
This feature allows you to extract basic metadata from an audio file, such as title, artist, album, and genre. The code sample demonstrates how to use the `parseFile` method to read and log metadata from an MP3 file.
const mm = require('music-metadata');
const fs = require('fs');
async function getMetadata(filePath) {
try {
const metadata = await mm.parseFile(filePath);
console.log(metadata);
} catch (error) {
console.error(error.message);
}
}
getMetadata('path/to/audio/file.mp3');
Extract Format Information
This feature allows you to extract format information from an audio file, such as the container type, codec, sample rate, and bit rate. The code sample demonstrates how to use the `parseFile` method to read and log format information from an MP3 file.
const mm = require('music-metadata');
const fs = require('fs');
async function getFormatInfo(filePath) {
try {
const metadata = await mm.parseFile(filePath);
console.log(metadata.format);
} catch (error) {
console.error(error.message);
}
}
getFormatInfo('path/to/audio/file.mp3');
Extract Technical Properties
This feature allows you to extract technical properties from an audio file, such as duration, number of channels, and bit depth. The code sample demonstrates how to use the `parseFile` method to read and log technical properties from an MP3 file.
const mm = require('music-metadata');
const fs = require('fs');
async function getTechnicalProperties(filePath) {
try {
const metadata = await mm.parseFile(filePath);
console.log(metadata.common);
} catch (error) {
console.error(error.message);
}
}
getTechnicalProperties('path/to/audio/file.mp3');
The node-id3 package is focused on reading and writing ID3 tags in MP3 files. It provides a simpler interface for handling ID3 tags compared to music-metadata, but it is limited to MP3 files and does not support other audio formats.
The musicmetadata package is another tool for extracting metadata from audio files. It supports a variety of audio formats and provides similar functionality to music-metadata. However, it is less actively maintained and may not support the latest audio formats and tags.
The id3js package is a JavaScript library for reading ID3 tags from MP3 files. It is designed to work in both Node.js and browser environments. While it offers similar functionality to music-metadata for MP3 files, it does not support other audio formats.
Key features:
The music-metadata
module is ideal for developers working on media applications, music players, or any project that requires access to detailed audio file metadata.
Module: version 8 migrated from CommonJS to pure ECMAScript Module (ESM). The distributed JavaScript codebase is compliant with the ECMAScript 2020 (11th Edition) standard.
[!NOTE] See also CommonJS backward Compatibility
This module requires a Node.js ≥ 16 engine. It can also be used in a browser environment when bundled with a module bundler.
If you find this project useful and would like to support its development, consider sponsoring or contributing:
Buy me a coffee:
Audio format | Description | Wiki | |
---|---|---|---|
AIFF / AIFF-C | Audio Interchange File Format | :link: | |
AAC | ADTS / Advanced Audio Coding | :link: | |
AMR | Adaptive Multi-Rate audio codec | :link: | |
APE | Monkey's Audio | :link: | |
ASF | Advanced Systems Format | :link: | |
BWF | Broadcast Wave Format | :link: | |
DSDIFF | Philips DSDIFF | :link: | |
DSF | Sony's DSD Stream File | :link: | |
FLAC | Free Lossless Audio Codec | :link: | |
MP2 | MPEG-1 Audio Layer II | :link: | |
Matroska | Matroska (EBML), mka, mkv | :link: | |
MP3 | MPEG-1 / MPEG-2 Audio Layer III | :link: | |
MPC | Musepack SV7 | :link: | |
MPEG 4 | mp4, m4a, m4v | :link: | |
Ogg | Open container format | :link: | |
Opus | :link: | ||
Speex | :link: | ||
Theora | :link: | ||
Vorbis | Vorbis audio compression | :link: | |
WAV | RIFF WAVE | :link: | |
WebM | webm | :link: | |
WV | WavPack | :link: | |
WMA | Windows Media Audio | :link: |
Following tag header formats are supported:
Following lyric formats are supported:
Support for MusicBrainz tags as written by Picard. ReplayGain tags are supported.
Support for encoding / format details:
Install using npm:
npm install music-metadata
or using yarn:
yarn add music-metadata
Node.js specific functions to read an audio file or stream:
Cross-platform functions available to read an audio file or stream:
There are multiple ways to parse (read) audio tracks:
ITokenizer
to parse using the parseFromTokenizer function.[!NOTE] Direct file access in Node.js is generally faster because it can 'jump' to various parts of the file without reading intermediate data.
These functions are tailored for Node.js environments and leverage Node.js-specific APIs, making them incompatible with browser-based JavaScript engines.
parseFile
functionThe parseFile
function is intended for extracting metadata from audio files on the local filesystem in a Node.js environment.
It reads the specified file, parses its audio metadata, and returns a promise that resolves with this information.
parseFile(filePath: string, options?: IOptions): Promise<IAudioMetadata>
filePath
: string
The path to the media file from which metadata should be extracted. This should be a valid path to an audio file on the local filesystem.
options
: IOptions (optional)
An optional configuration object that allows customization of the parsing process. These options can include whether to calculate the file's duration, skip embedded cover art, or other parsing behaviors.
Promise<IAudioMetadata>
:
A promise that resolves to an IAudioMetadata object containing metadata about the audio file. The metadata includes details such as the file format, codec, duration, bit rate, and any embedded tags like album, artist, or track information.
This function is Node.js-only and relies on Node.js-specific APIs to access the filesystem.
For browser environments, consider using the parseBlob to parse File object objects.
The following example demonstrates how to use the parseFile function to read metadata from an audio file:
import { parseFile } from 'music-metadata';
import { inspect } from 'util';
(async () => {
try {
const filePath = '../music-metadata/test/samples/MusicBrainz - Beth Hart - Sinner\'s Prayer [id3v2.3].V2.mp3';
const metadata = await parseFile(filePath);
// Output the parsed metadata to the console in a readable format
console.log(inspect(metadata, { showHidden: false, depth: null }));
} catch (error) {
console.error('Error parsing metadata:', error.message);
}
})();
parseStream
functionThe parseStream function is used to parse metadata from an audio track provided as a Node.js Readable
stream.
This is particularly useful for processing audio data that is being streamed or piped from another source, such as a web server or file system.
parseStream(stream: Readable, fileInfo?: IFileInfo | string, options?: IOptions): Promise<IAudioMetadata>
stream
: Readable
:
The Node.js Readable stream from which the audio data is read. This stream should provide the raw audio data to be analyzed.
fileInfo
: IFileInfo
(optional)
An object containing file-related information or a string representing the MIME-type of the audio stream. The fileInfo parameter can help the parser to correctly identify the audio format and may include:
mimeType
: A string representing the MIME-type (e.g., audio/mpeg
).
If provided, it is assumed the streamed file content is to be the MIME-type. If not provided, the parser will attempt to determine the format based on the content of the stream.
size
: The total size of the audio stream in bytes (useful for streams with a known length).
path
: A string representing the file path or filename, which can also assist in determining the format.
options
: IOptions
(optional)
An optional object containing additional parsing options. These options allow you to customize the parsing process, such as whether to calculate the duration or skip cover art extraction.
Promise<IAudioMetadata>
:
A promise that resolves to an IAudioMetadata
object containing detailed metadata about the audio stream.
This metadata includes information about the format, codec, duration, bitrate, and any embedded tags such as artist, album, or track information.
The following example demonstrates how to use the parseStream
function to read metadata from an audio stream:
import { parseStream } from 'music-metadata';
import { createReadStream } from 'fs';
(async () => {
try {
// Create a readable stream from a file
const audioStream = createReadStream('path/to/audio/file.mp3');
// Parse the metadata from the stream
const metadata = await parseStream(audioStream, { mimeType: 'audio/mpeg'});
// Log the parsed metadata
console.log(metadata);
} catch (error) {
console.error('Error parsing metadata:', error.message);
}
})();
These functions are designed to be cross-platform, meaning it can be used in both Node.js and web browsers.
parseWebStream
functionThe parseWebStream function is used to extract metadata from an audio track provided as a web-compatible ReadableStream. This function is ideal for applications running in web environments, such as browsers, where audio data is streamed over the network or read from other web-based sources.
parseWebStream(webStream: ReadableStream<Uint8Array>, fileInfo?: IFileInfo | string, options?: IOptions): Promise<IAudioMetadata>
webStream
: ReadableStream<Uint8Array>
A ReadableStream that provides the audio data to be parsed. This stream should emit Uint8Array chunks, representing the raw audio data.
fileInfo
: IFileInfo
(optional)
An object containing file-related information or a string representing the MIME-type of the audio stream. The fileInfo parameter can help the parser to correctly identify the audio format and may include:
mimeType
: A string representing the MIME-type (e.g., audio/mpeg
).
If provided, it is assumed the streamed file content is to be the MIME-type. If not provided, the parser will attempt to determine the format based on the content of the stream.
size
: The total size of the audio stream in bytes (useful for streams with a known length).
path
: A string representing the file path or filename, which can also assist in determining the format.
options
: IOptions
(optional)
An optional object containing additional parsing options. These options allow you to customize the parsing process, such as whether to calculate the duration or skip cover art extraction.
Promise<IAudioMetadata>
:
A promise that resolves to an IAudioMetadata
object containing detailed metadata about the audio stream.
This metadata includes information about the format, codec, duration, bitrate, and any embedded tags such as artist, album, or track information.
Here’s an example of how to use the parseWebStream
function to extract metadata from an audio stream in a web application:
import { parseWebStream } from 'music-metadata';
(async () => {
try {
// Assuming you have a ReadableStream of an audio file
const response = await fetch('https://example.com/path/to/audio/file.mp3');
const webStream = response.body;
// Parse the metadata from the web stream
const metadata = await parseWebStream(webStream, 'audio/mpeg');
// Log the parsed metadata
console.log(metadata);
} catch (error) {
console.error('Error parsing metadata:', error.message);
}
})();
The example uses the fetch
API to retrieve an audio file from a URL.
The response.body
provides a ReadableStream
that is then passed to parseWebStream
.
parseBlob
functionParses metadata from an audio file represented as a Blob. This function is suitable for use in environments that support the ReadableStreamBYOBReader, which is available in Node.js 20 and above.
parseBlob(blob: Blob, options?: IOptions = {}): Promise<IAudioMetadata>
blob
: Blob
The Blob object containing the audio data to be parsed. This can be a file or any binary data. If the Blob is an instance of File, its name will be used as the file path in the metadata.
options
: IOptions (optional)
An optional configuration object that specifies parsing options.
Promise<IAudioMetadata>
:
A promise that resolves to the metadata of the audio file.
import { parseBlob } from 'music-metadata';
(async () => {
const fileInput = document.querySelector('input[type="file"]');
const file = fileInput.files[0];
try {
const metadata = await parseBlob(file);
console.log(metadata);
} catch (error) {
console.error('Error parsing metadata:', error.message);
}
})();
parseBuffer
functionParses metadata from an audio file where the audio data is held in a Uint8Array or Buffer. This function is particularly useful when you already have audio data in memory.
parseBuffer(buffer: Uint8Array, fileInfo?: IFileInfo | string, opts?: IOptions = {}): Promise<IAudioMetadata>
uint8Array
: Uint8Array
A Uint8Array containing the audio data to be parsed.
fileInfo
: IFileInfo
| string
(optional)
An object containing file information such as mimeType and size. Alternatively, you can pass a MIME-type string directly. This helps the parser understand the format of the audio data.
options
: IOptions (optional)
An optional configuration object that specifies parsing options.
Promise<IAudioMetadata>
:
A promise that resolves to the metadata of the audio file.
import { parseBuffer } from 'music-metadata';
import fs from 'fs';
(async () => {
const buffer = fs.readFileSync('path/to/audio/file.mp3');
try {
const metadata = await parseBuffer(buffer, { mimeType: 'audio/mpeg' });
console.log(metadata);
} catch (error) {
console.error('Error parsing metadata:', error.message);
}
})();
parseFromTokenizer
functionParses metadata from an audio source that implements the strtok3 ITokenizer interface. This is a low-level function that provides flexibility for advanced use cases, such as parsing metadata from streaming audio or custom data sources.
This also enables special read modules like:
parseFromTokenizer(tokenizer: ITokenizer, options?: IOptions): Promise<IAudioMetadata>
tokenizer: ITokenizer
An instance of an ITokenizer that provides access to the audio data. The tokenizer abstracts the reading process, enabling support for various types of sources, including streams, buffers, or custom data readers.
options
: IOptions (optional)
An optional configuration object that specifies parsing options.
Promise<IAudioMetadata>
:
A promise that resolves to the metadata of the audio source, including information like the title, artist, album, and more.
import { fromNodeProviderChain } from '@aws-sdk/credential-providers';
import { S3Client } from '@aws-sdk/client-s3';
import { makeTokenizer } from '@tokenizer/s3';
import { parseFromTokenizer as mmParseFromTokenizer } from 'music-metadata';
// Configure the S3 client
const s3 = new S3Client({
region: 'eu-west-2',
credentials: fromNodeProviderChain(),
});
// Helper function to create a tokenizer for S3 objects
async function makeS3TestDataTokenizer(key, options) {
return await makeTokenizer(s3, {
Bucket: 'music-metadata',
Key: key,
}, options);
}
// Function to read and log metadata from an S3 object
async function readMetadata() {
try {
// Create a tokenizer for the specified S3 object
const tokenizer = await makeS3TestDataTokenizer('path/to/audio/file.mp3', { disableChunked: false });
// Parse the metadata from the tokenizer
const metadata = await mmParseFromTokenizer(tokenizer);
// Log the retrieved metadata
console.log(metadata);
} catch (error) {
console.error('Error parsing metadata:', error.message);
}
}
// Execute the metadata reading function
readMetadata();
ITokenizer
interface and how to implement it for various use cases.ITokenizer
implementation.music-metadata
provides a robust and extensible error handling system with custom error classes that inherit from the standard JavaScript Error
.
All possible parsing errors are part of a union type UnionOfParseErrors
, ensuring that every error scenario is accounted for in your code.
All parsing errors extend from the base class ParseError
and are included in the UnionOfParseErrors
type:
export type UnionOfParseErrors =
| CouldNotDetermineFileTypeError
| UnsupportedFileTypeError
| UnexpectedFileContentError
| FieldDecodingError
| InternalParserError;
CouldNotDetermineFileTypeError
: Raised when the file type cannot be determined.UnsupportedFileTypeError
: Raised when an unsupported file type is encountered.UnexpectedFileContentError
: Raised when the file content does not match the expected format.FieldDecodingError
: Raised when a specific field in the file cannot be decoded.InternalParserError
: Raised for internal parser errors.orderTags
functionUtility to Converts the native tags to a dictionary index on the tag identifier
orderTags(nativeTags: ITag[]): [tagId: string]: any[]
import { parseFile, orderTags } from 'music-metadata';
import { inspect } from 'util';
(async () => {
try {
const metadata = await parseFile('../test/samples/MusicBrainz - Beth Hart - Sinner\'s Prayer [id3v2.3].V2.mp3');
const orderedTags = orderTags(metadata.native['ID3v2.3']);
console.log(inspect(orderedTags, { showHidden: false, depth: null }));
} catch (error) {
console.error(error.message);
}
})();
ratingToStars
functionCan be used to convert the normalized rating value to the 0..5 stars, where 0 an undefined rating, 1 the star the lowest rating and 5 the highest rating.
ratingToStars(rating: number): number
selectCover
functionSelect cover image based on image type field, otherwise the first picture in file.
export function selectCover(pictures?: IPicture[]): IPicture | null
import { parseFile, selectCover } from 'music-metadata';
(async () => {
const {common} = await parseFile(filePath);
const cover = selectCover(common.picture); // pick the cover image
}
)();
IOptions
Interfaceduration
: boolean
(default: false
)
If set to true
, the parser will analyze the entire media file, if necessary, to determine its duration.
This option ensures accurate duration calculation but may increase processing time for large files.
observer
: (update: MetadataEvent) => void;
:
A callback function that is invoked whenever there is an update to the common (generic) tag or format properties during parsing. This allows for real-time updates on metadata changes.
skipCovers
: boolean
(default: false
)
If set to true
, the parser will skip the extraction of embedded cover art (images) from the media file.
This can be useful to avoid processing unnecessary data if cover images are not required.
mkvUseIndex
: boolean
(default: false
)
If set to true, the parser will use the SeekHead element index to skip segment/cluster elements in Matroska-based files. This is an experimental feature and can significantly impact performance. It may also result in some metadata being skipped if it is not indexed. If the SeekHead element is absent in the Matroska file, this flag has no effect.
[!NOTE]
- The
duration
option is typically included in most cases, but setting it to true ensures that the entire file is parsed if necessary to get an accurate duration.- Using
mkvUseIndex
can improve performance in Matroska files, but be aware of potential side effects, such as missing metadata due to skipped elements.
IAudioMetadata
interfaceIf the returned promise resolves, the metadata (TypeScript IAudioMetadata
interface) contains:
metadata.format
Audio format informationmetadata.common
Is a generic (abstract) way of reading metadata information.metadata.trackInfo
Is a generic (abstract) way of reading metadata information.metadata.native
List of native (original) tags found in the parsed audio file.metadata.format
The questionmark ?
indicates the property is optional.
Audio format information. Defined in the TypeScript IFormat
interface:
format.container?: string
Audio encoding format. e.g.: 'flac'format.codec?
Name of the codec (algorithm used for the audio compression)format.codecProfile?: string
Codec profile / settingsformat.tagTypes?: TagType[]
List of tagging formats found in parsed audio fileformat.duration?: number
Duration in secondsformat.bitrate?: number
Number bits per second of encoded audio fileformat.sampleRate?: number
Sampling rate in Samples per second (S/s)format.bitsPerSample?: number
Audio bit depthformat.lossless?: boolean
True if lossless, false for lossy encodingformat.numberOfChannels?: number
Number of audio channelsformat.creationTime?: Date
Track creation timeformat.modificationTime?: Date
Track modification / tag update timeformat.trackGain?: number
Track gain in dBformat.albumGain?: number
Album gain in dBmetadata.trackInfo
To support advanced containers like Matroska or MPEG-4, which may contain multiple audio and video tracks, the experimental- metadata.trackInfo
has been added,
metadata.trackInfo
is either undefined
or has an array of trackInfo
Audio format information. Defined in the TypeScript IFormat
interface:
trackInfo.type?: TrackType
Track typetrackInfo.codecName?: string
Codec nametrackInfo.codecSettings?: string
Codec settingstrackInfo.flagEnabled?: boolean
Set if the track is usable, default: true
trackInfo.flagDefault?: boolean
Set if that track (audio, video or subs) SHOULD be active if no language found matches the user preference.trackInfo.flagLacing?: boolean
Set if the track may contain blocks using lacingtrackInfo.name?: string
A human-readable track name.trackInfo.language?: string
Specifies the language of the tracktrackInfo.audio?: IAudioTrack
, see trackInfo.audioTrack
trackInfo.video?: IVideoTrack
, see trackInfo.videoTrack
trackInfo.audioTrack
audioTrack.samplingFrequency?: number
audioTrack.outputSamplingFrequency?: number
audioTrack.channels?: number
audioTrack.channelPositions?: Buffer
audioTrack.bitDepth?: number
trackInfo.videoTrack
videoTrack.flagInterlaced?: boolean
videoTrack.stereoMode?: number
videoTrack.pixelWidth?: number
videoTrack.pixelHeight?: number
videoTrack.displayWidth?: number
videoTrack.displayHeight?: number
videoTrack.displayUnit?: number
videoTrack.aspectRatioType?: number
videoTrack.colourSpace?: Buffer
videoTrack.gammaValue?: number
metadata.common
Common tag documentation is automatically generated.
In order to read the duration of a stream (with the exception of file streams), in some cases you should pass the size of the file in bytes.
import { parseStream } from 'music-metadata';
import { inspect } from 'util';
(async () => {
const metadata = await parseStream(someReadStream, {mimeType: 'audio/mpeg', size: 26838}, {duration: true});
console.log(inspect(metadata, {showHidden: false, depth: null}));
someReadStream.close();
}
)();
Via metadata.common.picture
you can access an array of cover art if present.
Each picture has this interface:
/**
* Attached picture, typically used for cover art
*/
export interface IPicture {
/**
* Image mime type
*/
format: string;
/**
* Image data
*/
data: Buffer;
/**
* Optional description
*/
description?: string;
/**
* Picture type
*/
type?: string;
}
To assign img
HTML-object you can do something like:
import {uint8ArrayToBase64} from 'uint8array-extras';
img.src = `data:${picture.format};base64,${uint8ArrayToBase64(picture.data)}`;
Dependency diagram:
graph TD;
MMN("music-metadata (Node.js entry point)")-->MMP
MMN-->FTN
MMP("music-metadata (primary entry point)")-->S(strtok3)
MMP-->TY(token-types)
MMP-->FTP
MMP-->UAE
FTN("file-type (Node.js entry point)")-->FTP
FTP("file-type (primary entry point)")-->S
S(strtok3)-->P(peek-readable)
S(strtok3)-->TO("@tokenizer/token")
TY(token-types)-->TO
TY-->IE("ieee754")
FTP-->TY
NS("node:stream")
FTN-->NS
FTP-->UAE(uint8array-extras)
style NS fill:#F88,stroke:#A44
style IE fill:#CCC,stroke:#888
style FTN fill:#FAA,stroke:#A44
style MMN fill:#FAA,stroke:#A44
Dependency list:
For legacy CommonJS projects needing to load the music-metadata
ESM module, you can use the loadMusicMetadata
function:
const { loadMusicMetadata } = require('music-metadata');
(async () => {
// Dynamically loads the ESM module in a CommonJS project
const mm = await loadMusicMetadata();
const metadata = await mm.parseFile('/path/to/your/file');
})();
[!NOTE] The
loadMusicMetadata
function is experimental.
How can I traverse (a long) list of files?
What is important that file parsing should be done in a sequential manner. In a plain loop, due to the asynchronous character (like most JavaScript functions), it would cause all the files to run in parallel which is will cause your application to hang in no time. There are multiple ways of achieving this:
Using recursion
import { parseFile } from 'music-metadata';
function parseFiles(audioFiles) {
const audioFile = audioFiles.shift();
if (audioFile) {
return parseFile(audioFile).then(metadata => {
// Do great things with the metadata
return parseFiles(audioFiles); // process rest of the files AFTER we are finished
})
}
}
Use async/await
Use async/await
import { parseFile } from 'music-metadata';
// it is required to declare the function 'async' to allow the use of await
async function parseFiles(audioFiles) {
for (const audioFile of audioFiles) {
// await will ensure the metadata parsing is completed before we move on to the next file
const metadata = await parseFile(audioFile);
// Do great things with the metadata
}
}
The MIT License (MIT)
Copyright © 2024 Borewit
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
FAQs
Music metadata parser for Node.js, supporting virtual any audio and tag format.
We found that music-metadata demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer 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
Research
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
Research
Security News
Attackers used a malicious npm package typosquatting a popular ESLint plugin to steal sensitive data, execute commands, and exploit developer systems.
Security News
The Ultralytics' PyPI Package was compromised four times in one weekend through GitHub Actions cache poisoning and failure to rotate previously compromised API tokens.