Research
Security News
Quasar RAT Disguised as an npm Package for Detecting Vulnerabilities in Ethereum Smart Contracts
Socket researchers uncover a malicious npm package posing as a tool for detecting vulnerabilities in Etherium smart contracts.
@fanoutio/grip
Advanced tools
A GRIP interface library for JavaScript. For use with HTTP reverse proxy servers that support the GRIP interface, such as Pushpin.
Supported GRIP servers include:
Authors: Katsuyuki Omuro komuro@fastly.com, Konstantin Bokarius kon@fanout.io
isWsOverHttp()
and getWebSocketContextFromReq()
functions now work with
Request
objects rather than Node.js's IncomingMessage
objects.
IncomingMessage
are available as isNodeReqWsOverHttp
and getWebSocketContextFromNodeReq
. These are exported from @fanoutio/grip/node
, and are also
exported from @fanoutio/grip
when the condition "node"
is present when
resolving imports.For detailed breaking changes, see the detailed list.
"@fanoutio/grip/node"
.GRIP_URL
now allows key
and verify-key
query parameters to be provided as:
JsonWebKey
base64:
) of Uint8Array
, JSON-stringified JsonWebKey
,
or PEM file (SPKI or PKCS#8).parseGripUri
now accepts a second parameter which can be used to merge parameters into a IGripConfig
.validateGripSig
is now available on Publisher
, allowing you to easily check a
Grip-Sig
header against the publisher clients registered with a Publisher
.Publisher
can now be configured with a custom channel prefix that will be applied
when publishing messages.Publisher
can now be configured with an override fetch()
function that will be
called when publishing messages.npm install @fanoutio/grip
Configure a publisher.
import { Publisher } from '@fanoutio/grip';
const publisher = new Publisher({
control_uri: 'http://pushpin.myproject.com/', // Control URI of your Pushpin instance
});
// or
const publisher = new Publisher(process.env.GRIP_URL); // A GRIP_URL representing your GRIP proxy
// or
const gripURL = process.env.GRIP_URL || 'http://127.0.0.1:5561/';
const gripVerifyKey = process.env.GRIP_VERIFY_KEY;
const gripConfig = parseGripUri(gripURL, { 'verify-key': gripVerifyKey }); // Merge a key into the GRIP_URL
const publisher = new Publisher(gripConfig);
Validate a GRIP signature.
// publisher instantiated above
const gripSig = req.headers.get('Grip-Sig');
const { isProxied, isSigned } = await publisher.validateGripSig(gripSig);
Publish an HTTP streaming message.
// publisher instantiated above
await publisher.publishHttpStream('<channel>', 'Test Publish!');
Publish an HTTP long-polling response message.
// publisher instantiated above
await publisher.publishHttpResponse('<channel>', 'Test Publish!');
If you're familiar with the concepts of GRIP, then it may be beneficial to go browse the Examples at this point, and then come back to this document for reference.
Generic Realtime Intermediary Protocol, otherwise known as GRIP, is a mechanism that allows your origin application to use a GRIP-compatible HTTP proxy server to hold incoming connections open.
GRIP is composed of two parts:
This library includes the Publisher
class, which helps you with these tasks.
To configure the Publisher
class, you'll use a GRIP configuration object.
The GRIP configuration object (IGripConfig
interface in TypeScript) represents the configuration for a single
GRIP proxy and its publishing endpoint. It has the following fields:
control_uri
- string Used for publishing. The Control URI of the GRIP proxy.user
- string Used for authorization during publishing.
pass
- string Used for authorization during publishing.
control_iss
- string Used for authorization during publishing.
iss
claim of the JWT to this value.key
- string or Uint8Array or CryptoKey or
KeyObject Used for authorization during publishing.
control_iss
is also provided, if the GRIP publishing endpoint allows JSON Web Tokens for authorization, the publisher signs the JWT using this key.control_iss
is not provided, then if the GRIP publishing endpoint allows Bearer Tokens for authorization, the publisher uses this value as the Bearer token.verify_key
- Uint8Array or CryptoKey or KeyObject Used for validating an incoming request.
Grip-Sig
header is verified as a JWT using this key.verify_iss
- string Used for validating an incoming request.
verify_key
and this value are set, the Grip-Sig
header is only considered to successfully verify
if its iss
claim of the JWT matches this value.control_uri
is the only required field. The other fields may be required depending on your setup.
key
and verify_key
may also be provided as string
or Uint8Array
that
encodes keys in PEM or JWK formats. See More on Keys.
NOTE: If your origin application is running on Fastly Compute, Fastly Compute does not support PEM-formatted keys.
NOTE: For backwards-compatibility reasons, if JWT authorization is used with a symmetric secret (
control_iss
andkey
are both provided, andkey
is not a private key) andverify_key
is not provided, thenkey
will also be used as theverify_key
value.
If you're using Fastly Fanout, then control_uri
, key
, verify_iss
, and verify_key
are required
and should be set to the following values:
control_uri
- The string value https://api.fastly.com/service/<service-id>
, where <service-id>
is your Fastly service ID,
with Fanout enabled.key
- A Fastly API token that has global
scope access to
your service, as a string value.verify_iss
- The string value fastly:<service-id>
, where <service-id>
is your Fastly service ID.verify_key
- The following object, which is also available from this library as the exported constant
PUBLIC_KEY_FASTLY_FANOUT_JWK
.
{
"kty":"EC",
"crv":"P-256",
"x":"CKo5A1ebyFcnmVV8SE5On-8G81JyBjSvcrx4VLetWCg",
"y":"7gwJqaU6N8TP88--twjkwoB36f-pT3QsmI46nPhjO7M"
}
import { PUBLIC_KEY_FASTLY_FANOUT_JWK } from '@fanoutio/grip/fastly-fanout';
// Replace '<SERVICE_ID>' and '<FASTLY_API_TOKEN>' with appropriate values
const gripConfig = {
control_uri: 'https://api.fastly.com/service/<SERVICE_ID>',
key: '<FASTLY_API_TOKEN>',
verify_iss: 'fastly:<SERVICE_ID>',
verify_key: PUBLIC_KEY_FASTLY_FANOUT_JWK,
};
const publisher = new Publisher(gripConfig);
As a convenience, you can use the buildFanoutGripConfig()
function exported from @fanoutio/grip/fastly-compute
to
build the GRIP configuration object for Fastly Fanout.
import { buildFanoutGripConfig } from '@fanoutio/grip/fastly-compute';
import { Publisher } from '@fanoutio/grip';
const gripConfig = buildFanoutGripConfig({
serviceId: '<service-id>', // Service of GRIP proxy
apiToken: '<fastly-api-token>', // API token that has 'global' scope on above service
});
const publisher = new Publisher(gripConfig);
TIP: It's also possible to configure Fastly Fanout using
GRIP_URL
. See GRIP_URL for details.
TIP: API tokens should be handled with care.
The fields in a GRIP configuration object can be combined into a single compact URL. The URL
is built as the control_uri
with the other values added as query parameters.
This value is often stored in an environment variable or configuration store with the name
GRIP_URL
. As it is a URL, it is easy to move the configuration between environments.
The verify_key
is sometimes large, especially when public keys are used. In this case, it is
stored separately as a GRIP_VERIFY_KEY
, and the values are merged at runtime:
const gripURL = process.env.GRIP_URL || 'http://127.0.0.1:5561/';
const gripVerifyKey = process.env.GRIP_VERIFY_KEY;
const gripConfig = parseGripUri(gripURL, { 'verify-key': gripVerifyKey });
TIP: Because GRIP_URL can contain secrets (API token or private/shared key for signing), it should be handled with care.
TIP:
GRIP_URL
andGRIP_VERIFY_KEY
can be used with Fastly Fanout as well. This can simplify your code by allowing it to be configured through a single code path.To do so, use these values (replace
<SERVICE_ID>
and<FASTLY_API_TOKEN>
with appropriate values):GRIP_URL='https://api.fastly.com/service/<SERVICE_ID>?key=<FASTLY_API_TOKEN>&verify-iss=fastly:<SERVICE_ID>' GRIP_VERIFY_KEY='{"kty":"EC","crv":"P-256","x":"CKo5A1ebyFcnmVV8SE5On-8G81JyBjSvcrx4VLetWCg","y":"7gwJqaU6N8TP88--twjkwoB36f-pT3QsmI46nPhjO7M"}'
Publisher
objectInstantiate the Publisher
object by passing the GRIP configuration object to its constructor.
import { Publisher } from '@fanoutio/grip';
const publisher = new Publisher({
control_uri: 'http://pushpin.myproject.com/', // Control URI of your Pushpin instance
});
// or
const gripURL = process.env.GRIP_URL || 'http://127.0.0.1:5561/';
const gripVerifyKey = process.env.GRIP_VERIFY_KEY;
const gripConfig = parseGripUri(gripURL, { 'verify-key': gripVerifyKey });
const publisher = new Publisher(gripConfig); // You can pass a gripConfig if you've already parsed it
// or
const publisher = new Publisher(process.env.GRIP_URL); // You can even pass a GRIP_URL directly
NOTE: If your origin application is running on Fastly Compute, then you'll need to further configure the
Publisher
. See Overriding fetch below.
When an incoming client request arrives at the GRIP proxy over HTTP, the proxy forwards the request to
your origin application and adds the Grip-Sig
header to the proxied request.
It's highly recommended that your origin application validate this Grip-Sig
to make sure it's coming
from your GRIP proxy. To do this, call publisher.validateGripSig()
:
// publisher instantiated above
const gripSig = req.headers.get('Grip-Sig');
const { isProxied, isSigned } = await publisher.validateGripSig(gripSig);
If your publisher requires validation (i.e., is configured with a verify_key
), then the signature of
Grip-Sig
will be checked with that key. If the key was able to successfully able to verify the signature
(including checking for expiry), then both isSigned
and isProxied
will be true
. Otherwise, they
will both be false
.
If your publisher does not require validation, then the signature is not checked. isSigned
will be false
,
and isProxied
will be true
if Grip-Sig
is present, and false
if it is not present.
NOTE: For backwards-compatibility reasons, if JWT authorization is used with a symmetric secret (
control_iss
andkey
are both provided, andkey
is not a private key) andverify_key
is not provided, thenkey
will be used as theverify_key
value as well.
Once you've verified that your request is proxied behind GRIP, your origin application can, as part of its execution, decide to have the GRIP proxy hold the connection and subscribe it to channels.
With an HTTP transport such as long-polling and streaming, your origin application
includes HTTP headers known as GRIP instructions along with the response.
These instructions indicate the action that the GRIP proxy is to take, and
is abstracted as a GripInstruct
object.
To set GRIP instructions, instantiate GripInstruct
and call its functions.
When it comes time to return the response, include the GRIP instructions with
the response by calling toHeaders()
on them and including them with the
response headers.
const gripInstruct = new GripInstruct();
gripInstruct.addChannel('<channel>');
gripInstruct.setHoldLongPoll();
// To optionally set a timeout value in seconds:
// gripInstruct.setHoldLongPoll(<timeout_value>);
return new Response(
'Body',
{
status: 200,
headers: {
...gripInstruct.toHeaders(),
'Content-Type': 'text/plain',
}
}
);
TIP: If the response status code is 304, some platforms will refuse to send custom HTTP response headers. To work around this issue, you can call
gripInstruct.setStatus()
.
const gripInstruct = new GripInstruct(); gripInstruct.addChannel('<channel>'); gripInstruct.setHoldLongPoll(); // Set 304 here gripInstruct.setStatus(304); // Send 200 to your platform return new Response( 'Body', { status: 200, headers: { ...gripInstruct.toHeaders(), 'Content-Type': 'text/plain', } } );
const gripInstruct = new GripInstruct();
gripInstruct.addChannel('<channel>');
gripInstruct.setHoldStream();
return new Response(
'Body',
{
status: 200,
headers: {
...gripInstruct.toHeaders(),
'Content-Type': 'text/plain',
}
}
);
To subscribe WebSocket-over-HTTP requests to a channel, use a WebSocketContext
object.
See the WebSocket-over-HTTP section below for details.
To publish a message, call one of the publishing methods on the publisher, which depends on the type of GRIP interaction.
To publish an HTTP long-polling response message:
// publisher instantiated above
await publisher.publishHttpResponse('<channel>', 'Test Publish!');
To publish an HTTP streaming message:
// publisher instantiated above
await publisher.publishHttpStream('<channel>', 'Test Publish!');
To publish a WebSocket-over-HTTP message:
// publisher instantiated above
await publisher.publishFormats('<channel>', new WebSocketMessageFormat('Test Publish!'));
See the WebSocket-over-HTTP section below for details.
Publisher
The Publisher
class constructor accepts an optional second parameter that is used
to customize its behavior.
For namespacing reasons, it's sometimes useful to prefix the channel name when publishing.
To do this, set the prefix
property in the configuration parameter when instantiating the Publisher
.
const publisher = new Publisher(process.env.GRIP_URL, { prefix: 'foo_' });
await publisher.publishHttpStream('test', 'Test Publish!'); // Message is sent to channel named 'foo_test'
By default, publishing messages through the Publisher
class uses the global fetch()
function as the underlying mechanism.
Sometimes you may wish to override this behavior. To do this, set the fetch
property in
the configuration parameter to a custom function when instantiating the Publisher
. Once
you do this, publishing messages through the Publisher
instance will call your custom function,
passing it the same parameters as it would when calling fetch()
. You are then free to modify
these values and then call the global fetch()
, or even provide the entire implementation yourself.
TIP: If your origin application is running on Fastly Compute, you'll need to do this to specify the
backend
parameter when performing afetch()
. See the section below.
If your origin application is running on Fastly Compute, a backend
parameter
is usually required when making a fetch()
call. (You won't need to do this if you are using the Dynamic Backends feature.)
One way to accomplish this is by providing an override function for fetch
that
inserts a backend
parameter.
In the following example, the backend
property of the second parameter is
set to 'publisher'
before calling the global fetch()
:
const publisher = new Publisher(gripConfig, {
fetch(input, init) {
return fetch(input, { ...init, backend: 'publisher' });
},
});
It's also possible to instantiate a Publisher
with more than one GRIP proxy.
To do this, simply pass an array of GRIP configurations to the constructor.
When you do this, validating incoming requests works slightly differently:
If all the GRIP configurations require validation (i.e., are configured with verify_key
),
then the signature of Grip-Sig
will be checked. If at least one GRIP configuration's key
was able to successfully able to verify the signature (including checking for expiry),
then both isSigned
and isProxied
will be true
. Otherwise, they will both be false
.
If at least one GRIP configuration does not require validation, then the signature is not
checked. isSigned
will be false
, and isProxied
will be true
if Grip-Sig
is present,
and false
if it is not present.
When publishing messages, each GRIP configuration is published to in parallel. The promise returned
from the publish()
call (or one of its variants) resolves when publishing to all configurations
completes, or rejects when publishing to any of the configurations fails.
WebSocket-Over-HTTP is a feature of Pushpin and Fastly Fanout that is a simple, text-based protocol for acting as a gateway between a WebSocket client (often a web browser) and a conventional HTTP server.
Events from the WebSocket client, including opening, closing, and sending of messages, are transformed by the GRIP proxy into HTTP POST requests and arrive at the origin application.
The origin application can use the isWsOverHttp()
function and pass in a Request
to detect whether
the request is using the WebSocket-over-HTTP protocol. If it is, the origin application can call the getWebSocketContextFromReq()
function to consume the Request
's body and obtain an instance of the WebSocketContext
class.
let wsContext = null;
if (gripStatus.isProxied && isWsOverHttp(request)) {
wsContext = await getWebSocketContextFromReq(request);
}
This object contains a queue of the current batch of incoming WebSocket messages, as well as a queue of outgoing WebSocket messages.
At this point the typical WebSocket-over-HTTP application:
OPEN messages can be checked by calling isOpening()
on the WebSocket context.
If so, the usual course of action is to call accept()
as well as subscribe()
on the WebSocket context. Keep in mind that these actions are simply queued up
as outgoing WebSocket messages at this stage.
if (wsContext.isOpening()) {
wsContext.accept();
wsContext.subscribe('test');
}
For other messages, iterate the queue of incoming messages on the WebSocket context
by checking canRecv()
and recv()
(or recvRaw()
, if the message may include binary data).
while (wsContext.canRecv()) {
const message = wsContext.recv();
// handle message ...
}
recv()
returns null
if the message was CLOSE. In this case, send a CLOSE back
to close the WebSocket cleanly.
if (message == null) {
wsContext.close();
break;
}
Otherwise, recv()
returns the string content of the WebSocket message (if recvRaw()
is used, then BINARY
messages will return a Uint8Array
of the bytes).
It's now up to your application to perform any application logic and handle this message.
Usually, to send messages back to the caller, call one of these functions. Again, keep in mind that these messages are just queued at this point.
send()
sendBinary()
sendControl()
For example, if you are writing an echo server, you may do something like this:
wsContext.send(message);
Finally, the outgoing messages in the WebSocket context need to be sent as the HTTP response. To do this, serialize the outgoing messages and send it, along with any headers that the WebSocket context would represent, in the HTTP response.
const events = wsContext.getOutgoingEvents();
const responseBody = encodeWebSocketEvents(events);
return new Response(
responseBody,
{
status: 200,
headers: wsContext.toHeaders(),
},
);
The package uses standard exports to make functions, classes, and interfaces available.
import { createWebSocketControlMessage, Publisher, Format, Item } from '@fanoutio/grip';
Function | Description |
---|---|
validateSig(token, key, iss) | Validates the specified JWT token with the provided key, and (optionally) validate the iss claim. |
encodeWebSocketEvents(events) | Encodes the specified array of WebSocketEvent instances. |
decodeWebSocketEvents(body) | Decodes the specified HTTP request body into an array of WebSocketEvent instances when using the WebSocket-over-HTTP protocol. |
parseGripUri(uri, additionalParams) | Parses the specified GRIP URI into a config object that can then be used to construct a Publisher instance. |
createWebSocketControlMessage(type, args) | Generates a WebSocket control message with the specified type and optional arguments. |
isWsOverHttp(req) | Detects whether the current request is using the WebSocket-over-HTTP protocol. |
getWebSocketContextFromReq(req, prefix) | Parses the body of the request and return an array of WebSocketEvent instances. |
Class | Description |
---|---|
Publisher | Main object used to publish messages to GRIP proxies. |
GripInstruct | Class used to create the necessary HTTP headers that instruct the GRIP proxy to hold connections. |
WebSocketContext | WebSocket context |
WebSocketEvent | WebSocket event |
WebSocketMessageFormat | Format used to publish messages to Web Socket clients connected to a GRIP proxy. |
Interfaces | Description |
---|---|
IGripConfig | Represents a GRIP client's configuration |
Class GripInstruct
Method | Description |
---|---|
constructor(channels? ) | Create a GripInstruct instance, configuring it with an optional array of channels to bind to. |
addChannel(channels) | Bind to additional channels. |
setHoldLongPoll(timeout?) | Set the Grip-Hold header to the response value, and specify an optional timeout value. |
setHoldStream() | Set the Grip-Hold header to the stream mode. |
setKeepAlive(data, timeout) | Set the Grip-Keep-Alive header to the specified data value and timeout value. The value for data may be provided as either a string or Buffer , and the appropriate encoding will be performed. |
setNextLink(uri, timeout?) | Set the Grip-Link header to the specified uri, with an optional timeout value. |
meta (property) | A property to be set directly on the instance. This is serialized into the Grip-Set-Meta header. |
toHeaders(params) | Turns the current instance into an object that can be sent as HTTP headers. |
Class Publisher
Method | Description |
---|---|
constructor(configs, options ) | Create a Publisher instance, configuring it based on the specified GRIP settings. |
async publishFormats(channel, formats, id?, prevId?) | Publish an item to the specified channel by building it from the provided formats. |
async publishHttpResponse(channel, data, id?, prevId?) | Publish an HTTP response format message to the specified channel, with optional ID and previous ID. |
async publishHttpStream(channel, item) | Publish an HTTP stream format message to the specified channel, with optional ID and previous ID. |
applyConfig(configs) | Advanced: Apply an additional GRIP proxy based on the specified GRIP config. |
applyConfigs(configs) | Advanced: Apply additional clients based on specified GRIP configs. |
addClient(client) | Advanced: Add a IPublisherClient instance that you have configured on your own. |
async publish(channel, item) | Advanced: Publish an item to the specified channel. |
The constructor and applyConfigs
methods accept either a single object, or an array of objects that implement
the IGripConfig
interface.
Interface IGripConfig
Represents the configuration for a GRIP proxy.
Field | Description |
---|---|
control_uri | The Control URI of the GRIP proxy. |
user | (optional) The user to use with the Control ISS, if required by the GRIP client. |
pass | (optional) The pass to use with the Control ISS, if required by the GRIP client. |
control_iss | (optional) The Control ISS, if required by the GRIP client. |
key | (optional) The key to use with the Control ISS, if required by the GRIP client. |
verify_iss | (optional) The ISS to use when validating a GRIP signature. |
verify_key | (optional) The key to use when validating a GRIP signature. |
Class Format
A base class for all publishing formats that are included in the Item class. Examples of format implementations include HttpStreamFormat and HttpResponseFormat.
This package comes with full TypeScript type definitions, so you may use it with TypeScript as well.
import { Publisher, IGripConfig } from '@fanoutio/grip';
const pub = new Publisher({control_uri: "<endpoint_uri>"});
// IGripConfig is a type declaration.
The following apply to the key
and verify_key
fields of the GRIP configuration object.
If present, key
must be a private key or symmetric secret, and verify_key
must be a public key or symmetric secret.
Binary values for key
and verify_key
may be provided as Uint8Array
, but they may also be provided as
base64-encoded strings. To do so, prefix the values with
base64:
, and the values will be converted to Uint8Array
as they are read.
They may also be provided as CryptoKey or KeyObject, which are runtime-specific key representations (CryptoKey in the browser and Web-interoperable runtimes, as well as KeyObject in Node.js). Refer to your platform's documentation to import keys of these types.
They may also be provided as JsonWebKey
objects (or JSON-stringified representations of JsonWebKey
objects, or Uint8Array
encodings of JSON-stringified
representations of JsonWebKey
objects). In these cases, they will be converted to CryptoKey or KeyObject as they are read.
Finally, they may also be provided as PEM-encoded strings (or Uint8Array
encodings of PEM-encoded strings):
key
as a PEM-encoded PKCS#8 private key, and verify_key
as a PEM-encoded SPKI public key.
In these cases, they will be converted to CryptoKey or KeyObject as they are read.
NOTE: If your origin application is running on Fastly Compute, note that Fastly Compute does not support PEM-formatted keys.
NOTE: For backwards-compatibility reasons, if JWT authorization is used with a symmetric secret (
control_iss
andkey
are both provided, andkey
is not a private key) andverify_key
is not provided, thenkey
will also be used as theverify_key
value.
buildHeader()
function to return a Promise
that
resolves to a string
. Previously they returned the string
directly.HttpResponseFormat
now specifically works with string
and Uint8Array
.
Previously the body could be anything that supported a .toString()
function.IWebSocketEvent
now requires the content
field and getContent
accessor to be
string
or Uint8Array
. Previously, this could also be number[]
, but this is no longer
supported.WebSocketContext
now requires the meta
parameter of its constructor to be an object whose
values are string
. Previously, this could be any JavaScript object.WebSocketEvent
now requires the content
parameter of its constructor to be
string
or Uint8Array
. Previously, this could also be number[]
, but this is no longer
supported.createWebSocketControlMessage
now requires the args
parameter to be an object whose
values are string
. Previously, this could be any JavaScript object.GripInstruct
now requires the meta
field, if it's used, to be an object whose
values are string
. Previously, this could be any JavaScript object.IFormat
now requires the export()
function to return an object whose values are JSON-serializable.
Previously, values could be of any type.IItemExport
now requires the formats
field to be an object whose values are JSON-serializable.
Previously, values could be of any type.Auth.Base
Response
NodeApiRequest
NodeApiResponse
PublisherBase
PublisherTransport
Auth.Basic
user
- use getUser()
pass
- use getPass()
Auth.Bearer
token
- use getToken()
Auth.Jwt
claim
- use getClaim()
key
- use getKey()
PublisherClient
auth
- use getAuth()
transport
verifyComponents
- use getVerifyIss()
and getVerifyKey()
setAuthBasic()
setAuthBearer()
setAuthJwt()
setVerifyComponents()
_startPubCall()
_performHttpRequest()
_finishHttpRequest()
Publisher
buildPublisherClient()
parseGripUri()
WebSockeContext
inEvents
readIndex
FetchResponse
IApiRequest
IApiResponse
IExportedResponse
IGripConfigBase
IPublisherTransport
IReqHeaders
flattenHeader()
getWebSocketContextFromApiRequest()
isApiRequestWsOverHttp()
isString()
toBuffer()
parseGripUriCustomParams()
parseQueryString()
(C) 2015, 2020 Fanout, Inc.
(C) 2023 Fastly, Inc.
Licensed under the MIT License, see file LICENSE.md for details.
[4.3.0] - 2024-10-14
@fanoutio/grip/node
are now available on the main @fanoutio/grip
export when the condition "node"
is present when
resolving imports.FAQs
GRIP Interface Library
We found that @fanoutio/grip demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 0 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.
Research
Security News
Socket researchers uncover a malicious npm package posing as a tool for detecting vulnerabilities in Etherium smart contracts.
Security News
Research
A supply chain attack on Rspack's npm packages injected cryptomining malware, potentially impacting thousands of developers.
Research
Security News
Socket researchers discovered a malware campaign on npm delivering the Skuld infostealer via typosquatted packages, exposing sensitive data.