
Research
Security News
Lazarus Strikes npm Again with New Wave of Malicious Packages
The Socket Research Team has discovered six new malicious npm packages linked to North Korea’s Lazarus Group, designed to steal credentials and deploy backdoors.
The 'grant' npm package is a middleware for OAuth that allows you to easily integrate with various OAuth providers. It supports multiple frameworks like Express, Koa, Hapi, and more. It simplifies the process of setting up OAuth flows for authentication and authorization.
Express Integration
This code demonstrates how to integrate the 'grant' package with an Express application to handle OAuth authentication with Google. It sets up the necessary middleware and routes to handle the OAuth flow.
const express = require('express');
const session = require('express-session');
const Grant = require('grant-express');
const app = express();
app.use(session({secret: 'very secret'}));
app.use(new Grant({
defaults: {
protocol: 'http',
host: 'localhost:3000',
transport: 'session',
state: true
},
google: {
key: 'GOOGLE_CLIENT_ID',
secret: 'GOOGLE_CLIENT_SECRET',
scope: ['profile', 'email'],
callback: '/handle_google_callback'
}
}));
app.get('/handle_google_callback', (req, res) => {
res.json(req.session.grant.response);
});
app.listen(3000, () => {
console.log('Server listening on http://localhost:3000');
});
Koa Integration
This code demonstrates how to integrate the 'grant' package with a Koa application to handle OAuth authentication with GitHub. It sets up the necessary middleware and routes to handle the OAuth flow.
const Koa = require('koa');
const session = require('koa-session');
const Grant = require('grant-koa');
const app = new Koa();
app.keys = ['very secret'];
app.use(session(app));
app.use(new Grant({
defaults: {
protocol: 'http',
host: 'localhost:3000',
transport: 'session',
state: true
},
github: {
key: 'GITHUB_CLIENT_ID',
secret: 'GITHUB_CLIENT_SECRET',
scope: ['user'],
callback: '/handle_github_callback'
}
}));
app.use(async (ctx) => {
if (ctx.path === '/handle_github_callback') {
ctx.body = ctx.session.grant.response;
}
});
app.listen(3000, () => {
console.log('Server listening on http://localhost:3000');
});
Hapi Integration
This code demonstrates how to integrate the 'grant' package with a Hapi application to handle OAuth authentication with Twitter. It sets up the necessary middleware and routes to handle the OAuth flow.
const Hapi = require('@hapi/hapi');
const Grant = require('grant-hapi');
const start = async () => {
const server = Hapi.server({
port: 3000,
host: 'localhost'
});
await server.register({
plugin: Grant,
options: {
defaults: {
protocol: 'http',
host: 'localhost:3000',
transport: 'session',
state: true
},
twitter: {
key: 'TWITTER_CONSUMER_KEY',
secret: 'TWITTER_CONSUMER_SECRET',
callback: '/handle_twitter_callback'
}
}
});
server.route({
method: 'GET',
path: '/handle_twitter_callback',
handler: (request, h) => {
return request.yar.get('grant').response;
}
});
await server.start();
console.log('Server running on %s', server.info.uri);
};
start();
Passport is a popular authentication middleware for Node.js. It supports a wide range of authentication strategies, including OAuth, OAuth2, OpenID, and more. Compared to 'grant', Passport offers more flexibility and a larger ecosystem of strategies, but it can be more complex to set up.
Simple OAuth2 is a library for integrating OAuth2 authentication in Node.js applications. It provides a straightforward API for obtaining access tokens and refreshing them. Compared to 'grant', Simple OAuth2 is more focused on OAuth2 and does not support as many frameworks out of the box.
Bell is a Hapi plugin for third-party authentication using OAuth, OAuth2, and more. It is specifically designed for Hapi applications and provides a seamless integration with the Hapi framework. Compared to 'grant', Bell is more specialized for Hapi but does not support other frameworks.
23andme
| 500px
| acton
| acuityscheduling
| aha
| amazon
| angellist
| arcgis
| asana
| assembla
| auth0
| authentiq
| axosoft
| baidu
| basecamp
| battlenet
| beatport
| bitbucket
| bitly
| box
| buffer
| campaignmonitor
| cheddar
| clio
| coinbase
| concur
| constantcontact
| coursera
| dailymile
| dailymotion
| deezer
| delivery
| deputy
| deviantart
| digitalocean
| discogs
| discord
| disqus
| docusign
| dribbble
| dropbox
| ebay
| echosign
| ecwid
| edmodo
| egnyte
| etsy
| eventbrite
| evernote
| everyplay
| eyeem
| facebook
| familysearch
| feedly
| fitbit
| flattr
| flickr
| flowdock
| fluidsurveys
| formstack
| foursquare
| freeagent
| freelancer
| freshbooks
| geeklist
| genius
| getbase
| getpocket
| gitbook
| github
| gitlab
| gitter
| goodreads
| google
| groove
| gumroad
| harvest
| hellosign
| heroku
| homeaway
| hootsuite
| ibm
| iconfinder
| idme
| idonethis
| imgur
| infusionsoft
| instagram
| intuit
| jamendo
| jumplead
| kakao
| letsfreckle
| linkedin
| live
| lyft
| mailchimp
| mailup
| mapmyfitness
| mastodon
| medium
| meetup
| mention
| microsoft
| mixcloud
| mixer
| moves
| moxtra
| mydigipass
| myob
| nest
| nylas
| okta
| onelogin
| openstreetmap
| optimizely
| patreon
| paypal
| pinterest
| plurk
| podio
| producteev
| producthunt
| projectplace
| pushbullet
| qq
| ravelry
| redbooth
| reddit
| runkeeper
| salesforce
| shoeboxed
| shopify
| skyrock
| slack
| slice
| smartsheet
| smugmug
| socialpilot
| socrata
| soundcloud
| spotify
| square
| stackexchange
| stocktwits
| stormz
| strava
| stripe
| surveygizmo
| surveymonkey
| thingiverse
| ticketbud
| timelyapp
| todoist
| trakt
| traxo
| trello
| tripit
| tumblr
| twitch
| twitter
| typeform
| uber
| underarmour
| unsplash
| upwork
| uservoice
| vend
| venmo
| verticalresponse
| viadeo
| vimeo
| visualstudio
| vk
| weekdone
| weibo
| withings
| wordpress
| wrike
| xero
| xing
| yahoo
| yammer
| yandex
| zendesk
npm install grant-express
var express = require('express')
var session = require('express-session')
var grant = require('grant-express')
var app = express()
// REQUIRED: any session store - see /examples/express-session-stores
app.use(session({secret: 'grant'}))
// mount grant
app.use(grant({/*configuration - see below*/}))
npm install grant-koa
var Koa = require('koa')
var session = require('koa-session')
var mount = require('koa-mount')
var grant = require('grant-koa')
var app = new Koa()
// REQUIRED: any session store - see /examples/koa-session-stores
app.keys = ['grant']
app.use(session(app))
// mount grant
app.use(mount(grant({/*configuration - see below*/})))
npm install grant-hapi
var Hapi = require('hapi')
var yar = require('yar')
var grant = require('grant-hapi')
var server = new Hapi.Server()
server.register([
// REQUIRED: any session store - see /examples/hapi-session-stores
{plugin: yar, options: {cookieOptions: {password: 'grant', isSecure: false}}},
// mount grant
{plugin: grant(), options: {/*configuration - see below*/}}
])
{
"defaults": {
"protocol": "http",
"host": "localhost:3000",
"transport": "session",
"state": true
},
"google": {
"key": "...",
"secret": "...",
"scope": ["openid"],
"nonce": true,
"custom_params": {"access_type": "offline"},
"callback": "/hello"
},
"twitter": {
"key": "...",
"secret": "...",
"callback": "/hi"
}
}
server
)
http
or https
localhost:3000
| dummy.com:5000
| awesome.com
...querystring
| session
(defaults to querystring if omitted)true
| false
(OAuth2 only, defaults to false if omitted)google
| twitter
...
consumer_key
or client_id
of your OAuth appconsumer_secret
or client_secret
of your OAuth apptrue
| false
(OpenID Connect only, defaults to false if omitted)/callback
| /done
...List of all available options.
Grant operates on the following two routes:
/connect/:provider/:override?
/connect/:provider/callback
You login by navigating to the /connect/:provider
route where :provider
is a key in your configuration, usually one of the officially supported providers. Additionally you can login through a static override key defined for that provider, in your configuration, by navigating to the /connect/:provider/:override?
route.
You should always use the following format for the redirect_uri
of your OAuth App:
[protocol]://[host]/connect/[provider]/callback
The protocol
and the host
are the options you specify inside the defaults
key of your configuration, and this is where your Grant server is listening. The provider
is a key in your configuration, usually one of the officially supported providers.
Note that the /connect/:provider/callback
route is used internally by Grant! You will receive the OAuth response data inside the callback
route specified in your configuration.
You can mount Grant under specific path prefix:
// Express
app.use('/path/prefix', grant(config))
// Koa
app.use(mount('/path/prefix', grant(config)))
// Hapi
server.register([{routes: {prefix: '/path/prefix'}, plugin: grant(config)}])
In this case it is required to specify that path
prefix in your configuration:
{
"defaults": {
"protocol": "...",
"host": "...",
"path": "/path/prefix"
}
}
That path prefix should also be specified in your OAuth App Redirect URL:
[protocol]://[host][path]/connect/[provider]/callback
Optionally you can prefix your callback
routes as well:
{
"github": {
"callback": "/path/prefix/hello"
}
}
The nonce
option is recommended when requesting the openid
scope:
{
"google": {
"scope": ["openid"],
"nonce": true
}
}
Grant does not verify the signature of the returned id_token
because that requires discovery, caching, and expiration of the provider's public keys.
However, Grant tries to decode the id_token
and verifies the following two claims (returns error respectively):
aud
- is the token intended for my OAuth app?nonce
- does it tie to a request of my own?For convenience the response data contains the decoded id_token
Take a look at the OpenID Connect example.
Some providers may employ custom authorization parameters, that you can configure using the custom_params
option:
{
"google": {
"custom_params": {"access_type": "offline"}
},
"reddit": {
"custom_params": {"duration": "permanent"}
},
"trello": {
"custom_params": {"name": "my app", "expiration": "never"}
}
}
You can specify provider sub configurations using the overrides
key:
{
"github": {
"key": "...", "secret": "...",
"scope": ["public_repo"],
"callback": "/hello",
"overrides": {
"notifications": {
"key": "...", "secret": "...",
"scope": ["notifications"]
},
"all": {
"scope": ["repo", "gist", "user"],
"callback": "/awesome"
}
}
}
}
Navigate to:
/connect/github
to request the public_repo scope
/connect/github/notifications
to request the notifications scope
using another OAuth App (key
and secret
)/connect/github/all
to request a bunch of scope
s and also receive the response data in another callback
routeIn some cases you may want certain parts of your configuration to be set dynamically.
For example for shopify
you have to embed the user's shop name into the OAuth URLs, so it makes sense to allow the subdomain
option to be set dynamically:
{
"shopify": {
"dynamic": ["subdomain"]
}
}
Then you can have a form on your website allowing the user to specify the shop name:
<form action="/connect/shopify" method="POST" accept-charset="utf-8">
<input type="text" name="subdomain" value="" />
<button>Login</button>
</form>
Keep in mind that when making a POST
request to the /connect/:provider/:override?
route you have to mount the body-parser
middleware for Express and Koa before mounting Grant:
// express
var parser = require('body-parser')
app.use(parser.urlencoded({extended: true}))
app.use(grant(config))
// koa
var parser = require('koa-bodyparser')
app.use(parser())
app.use(mount(grant(config)))
Alternatively you can make a GET
request to the /connect/:provider/:override?
route:
https://awesome.com/connect/shopify?subdomain=usershop
The OAuth response data is returned in your final callback
route.
By default the OAuth response data will be encoded as querystring.
You can instruct Grant to return the response data in the session instead, by using the transport
option.
You can limit the amount of data returned by using the response
option.
{
id_token: {header: {...}, payload: {...}, signature: '...'},
access_token: '...',
refresh_token: '...',
raw: {
id_token: '...',
access_token: '...',
refresh_token: '...',
some: 'other data'
}
}
The
refresh_token
is optional. Theid_token
is returned only for OpenID Connect providers requesting the openid scope.
{
access_token: '...',
access_secret: '...',
raw: {
oauth_token: '...',
oauth_token_secret: '...',
some: 'other data'
}
}
{
error: {
some: 'error data'
}
}
In case of an error, the
error
key will contain the error data.
By default Grant will encode the OAuth response data as querystring in your final callback
route:
{
"github": {
"callback": "/hello"
}
}
This final /hello?access_token=...
redirect can potentially leak private data in your server logs, especially if you are behind reverse proxy.
It is recommended to use the session transport
instead:
{
"defaults": {
"transport": "session"
},
"github": {
"callback": "/hello"
}
}
That way the result will no longer be encoded as querystring, and you will receive the response data inside the session instead.
By default Grant will return all available response data in your final callback
route:
{
id_token: {header: {...}, payload: {...}, signature: '...'},
access_token: '...',
refresh_token: '...',
raw: {
id_token: '...',
access_token: '...',
refresh_token: '...',
some: 'other data'
}
}
However, encoding potentially large amounts of data as querystring can lead to incompatibility issues with some servers and browsers, and generally is considered a bad practice.
It can also cause problems when the session transport is used, and the particular session store implementation encodes the entire session in a cookie, not just the session ID. In this case some servers may reject the HTTP request because of too big HTTP headers in size.
The response
option can be used to limit the response data:
{
"defaults": {
"response": "tokens"
}
}
This will return only the tokens, without the raw
key.
In case you want to include the decoded id_token
as well:
{
"google": {
"response": ["tokens", "jwt"]
}
}
This will make the decoded id_token
available as id_token_jwt
in the response object.
Grant uses session to persist state between HTTP redirects occurring during the OAuth flow. This session, however, was never meant to be used as persistent storage, even if that's totally possible.
Once you receive the response data in your final callback
route you are free to destroy that session, and do whatever you want with the returned data.
However, there are a few session keys returned in your final callback
route, that you may find useful:
Key | Availability | Description |
---|---|---|
provider | Always | The provider name this authorization was called for |
override | Depends on URL | The static override name used for this authorization |
dynamic | Depends on request type | The dynamic override configuration passed for this authorization |
state | OAuth 2.0 only | OAuth 2.0 state string that was generated |
nonce | OpenID Connect only | OpenID Connect nonce string that was generated |
response | Depends on transport used | The final response data |
request | OAuth 1.0a only | Data returned from the first request of the OAuth 1.0a flow |
Key | Location | Description |
---|---|---|
request_url | oauth.json | OAuth1/step1 |
authorize_url | oauth.json | OAuth1/step2 or OAuth2/step1 |
access_url | oauth.json | OAuth1/step3 or OAuth2/step2 |
oauth | oauth.json | OAuth version number |
scope_delimiter | oauth.json | string delimiter used for concatenating multiple scopes |
custom_parameters | oauth.json | list of known custom authorization parameters |
protocol, host, path | defaults | used to generate redirect_uri |
transport | defaults | transport to use to deliver the response data in your final callback route |
state | defaults | toggle random state string generation for OAuth2 |
key | [provider] | OAuth app key, reserved aliases: consumer_key and client_id |
secret | [provider] | OAuth app secret, reserved aliases: consumer_secret and client_secret |
scope | [provider] | list of scopes to request |
custom_params | [provider] | custom authorization parameters and their values |
subdomain | [provider] | string to be embedded in request_url , authorize_url and access_url |
nonce | [provider] | toggle random nonce string generation for OpenID Connect providers |
callback | [provider] | final callback route on your server to receive the response data |
dynamic | [provider] | allow dynamic override of configuration |
overrides | [provider] | static overrides for a provider |
response | [provider] | limit the response data |
name | generated | provider's name, used to generate redirect_uri |
[provider] | generated | provider's name as key |
redirect_uri | generated | OAuth app redirect URI, generated using protocol , host , path and name |
Grant relies on configuration gathered from 5 different places:
The first place Grant looks for configuration is the built-in oauth.json file located in the config folder.
The second place Grant looks for configuration is the defaults
key, specified in the user's configuration. These defaults are applied for every provider in the user's configuration.
The third place for configuration is the provider itself. All providers in the user's configuration inherit every option defined for them in the oauth.json file, and all options defined inside the defaults
key. Having oauth.json file and a defaults
configuration is only a convenience. You can define all available options directly for a provider.
The fourth place for configuration is the provider's overrides
. The static overrides inherit their parent provider, essentially creating a sub provider of the same type.
The fifth place for configuration, that potentially can override all of the above, and make all of the above optional, is the dynamic override.
You can define your own provider by adding a key for it in your configuration. In this case you'll have to supply all of the required options by yourself:
{
"defaults": {
"protocol": "https",
"host": "awesome.com"
},
"awesome": {
"authorize_url": "https://awesome.com/authorize",
"access_url": "https://awesome.com/token",
"oauth": 2,
"key": "APP_ID",
"secret": "APP_SECRET",
"scope": ["read", "write"]
}
}
Take a look at the oauth.json file to see how various providers are configured.
You can easily configure different development environments:
{
"development": {
"defaults": {"protocol": "http", "host": "localhost:3000"},
"github": {
"key": "development OAuth app credentials",
"secret": "development OAuth app credentials"
}
},
"staging": {
"defaults": {"protocol": "https", "host": "staging.awesome.com"},
"github": {
"key": "staging OAuth app credentials",
"secret": "staging OAuth app credentials"
}
},
"production": {
"defaults": {"protocol": "https", "host": "awesome.com"},
"github": {
"key": "production OAuth app credentials",
"secret": "production OAuth app credentials"
}
}
}
Then you can pass the environment flag:
NODE_ENV=production node app.js
And use it in your application:
var config = require('./config.json')
var grant = Grant(config[process.env.NODE_ENV || 'development'])
In case you really want to, you can allow dynamic override of every option for a provider:
{
"github": {
"dynamic": true
}
}
And the most extreme case is allowing even non preconfigured providers to be used dynamically:
{
"defaults": {
"dynamic": true
}
}
Essentially Grant is a completely transparent OAuth Proxy.
The protocol
, the host
(and optionally the path
) options are used to generate the correct redirect_uri
for each provider:
{
"defaults": {
"protocol": "https",
"host": "awesome.com"
},
"google": {},
"twitter": {}
}
The above configuration is identical to:
{
"google": {
"redirect_uri": "https://awesome.com/connect/google/callback"
},
"twitter": {
"redirect_uri": "https://awesome.com/connect/twitter/callback"
}
}
Note that the
redirect_uri
option would override theprotocol
and thehost
even if they were specified.
You can document your configuration by adding custom keys to it:
{
"google": {
"app": "My Awesome OAuth App",
"owner": "my_email@gmail.com",
"url": "https://url/to/manage/oauth/app"
}
}
These custom keys cannot be reserved ones.
Some providers have dynamic URLs containing bits of user information embedded in them.
The subdomain
option can be used to specify your company name, server region or whatever else is required:
"shopify": {
"subdomain": "mycompany"
},
"battlenet": {
"subdomain": "us"
}
Then Grant will generate the correct OAuth URLs:
"shopify": {
"authorize_url": "https://mycompany.myshopify.com/admin/oauth/authorize",
"access_url": "https://mycompany.myshopify.com/admin/oauth/access_token"
},
"battlenet": {
"authorize_url": "https://us.battle.net/oauth/authorize",
"access_url": "https://us.battle.net/oauth/token"
}
Alternatively you can override the entire
authorize_url
andaccess_url
in your configuration.
Some providers may have Sandbox URLs to use while developing your app. To use them just override the entire request_url
, authorize_url
and access_url
in your configuration (notice the sandbox
bits):
"paypal": {
"authorize_url": "https://www.sandbox.paypal.com/webapps/auth/protocol/openidconnect/v1/authorize",
"access_url": "https://api.sandbox.paypal.com/v1/identity/openidconnect/tokenservice"
},
"evernote": {
"request_url": "https://sandbox.evernote.com/oauth",
"authorize_url": "https://sandbox.evernote.com/OAuth.action",
"access_url": "https://sandbox.evernote.com/oauth"
}
Very rarely you may need to override the redirect_uri
that Grant generates for you.
For example Feedly supports only http://localhost
as redirect URL of their Sandbox OAuth application, and it won't allow the correct http://localhost/connect/feedly/callback
URL:
"feedly": {
"redirect_uri": "http://localhost"
}
In this case you'll have to redirect the user to the [protocol]://[host]/connect/[provider]/callback
route that Grant uses to execute the last step of the OAuth flow:
var qs = require('querystring')
app.get('/', (req, res) => {
if (process.env.NODE_ENV === 'development' &&
req.session.grant &&
req.session.grant.provider === 'feedly' &&
req.query.code
) {
res.redirect(`/connect/${req.session.grant.provider}/callback?${qs.stringify(req.query)}`)
}
})
As usual you will receive the response data in your final
callback
route.
Ebay
Set the Redirect URL of your OAuth app as usual [protocol]://[host]/connect/ebay/callback
. Then Ebay will generate a special string called RuName (eBay Redirect URL name) that you need to set as redirect_uri
in Grant:
"ebay": {
"redirect_uri": "RUNAME"
}
Flickr, Freelancer, Optimizely
Some providers are using custom authorization parameter to pass the requested scopes - Flickr perms
, Freelancer advanced_scopes
, Optimizely scopes
, but you can use the regular scope
option instead:
"flickr": {
"scope": ["write"]
},
"freelancer": {
"scope": ["1", "2"]
},
"optimizely": {
"scope": ["all"]
}
Mastodon
Mastodon requires the entire domain of your server to be embedded in the OAuth URLs. However you should use the subdomain
option:
"mastodon": {
"subdomain": "mastodon.cloud"
}
SurveyMonkey
Set your Mashery user name as key
and your application key as api_key
:
"surveymonkey": {
"key": "MASHERY_USER_NAME",
"secret": "CLIENT_SECRET",
"custom_params": {"api_key": "CLIENT_ID"}
}
Fitbit, LinkedIn, ProjectPlace
Initially these were OAuth1 providers, so the fitbit
, linkedin
and projectplace
names are used for that. To use their OAuth2 flow append 2
at the end of their names:
"fitbit2": {
// navigate to /connect/fitbit2
},
"linkedin2": {
// navigate to /connect/linkedin2
},
"projectplace2": {
// navigate to /connect/projectplace2
}
VisualStudio
Set your Client Secret as secret
not the App Secret:
"visualstudio": {
"key": "APP_ID",
"secret": "CLIENT_SECRET instead of APP_SECRET"
}
Alternatively you can require any of the middlewares directly from grant
(each pair is identical):
// Express
var Grant = require('grant-express')
var Grant = require('grant').express()
// Koa
var Grant = require('grant-koa')
var Grant = require('grant').koa()
// Hapi
var Grant = require('grant-hapi')
var Grant = require('grant').hapi()
Grant can be instantiated with or without using the new
keyword:
var Grant = require('grant-express|koa|hapi')
var grant = Grant(config)
// identical to:
var grant = new Grant(config)
Additionally Hapi can accept the configuration in two different ways:
server.register([{plugin: grant(config)}])
// identical to:
server.register([{plugin: grant(), options: config}])
Every Grant instance have a config
property attached to it:
var grant = Grant(require('./config'))
console.log(grant.config)
It contains the generated configuration data that Grant uses internally.
You can use the config
property to alter the Grant's behavior during runtime. Keep in mind that this affects the entire Grant instance! Use dynamic override instead, to alter configuration per authorization attempt.
Once you have your access tokens secured, you can start making authorized requests on behalf of your users.
For example, you may want to get the user's profile after the OAuth flow has completed:
var express = require('express')
var session = require('express-session')
var grant = require('grant-express')
var request = require('request-compose').client
var config = {
"defaults": {
"protocol": "http",
"host": "localhost:3000"
},
"facebook": {
"key": "APP_ID",
"secret": "APP_SECRET",
"callback": "/hello"
}
}
express()
.use(session({secret: 'grant', saveUninitialized: true, resave: true}))
.use(grant(config))
.get('/hello', async (req, res) => {
var {body} = await request({
url: 'https://graph.facebook.com/me',
headers: {authorization: `Bearer ${req.query.access_token}`}
})
res.end(JSON.stringify({oauth: req.query, profile: body}, null, 2))
})
.listen(3000)
v4.4.1 (2019/01/25)
aud
claims when validating an id_token
qq
FAQs
OAuth Proxy
The npm package grant receives a total of 181,919 weekly downloads. As such, grant popularity was classified as popular.
We found that grant 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.
Research
Security News
The Socket Research Team has discovered six new malicious npm packages linked to North Korea’s Lazarus Group, designed to steal credentials and deploy backdoors.
Security News
Socket CEO Feross Aboukhadijeh discusses the open web, open source security, and how Socket tackles software supply chain attacks on The Pair Program podcast.
Security News
Opengrep continues building momentum with the alpha release of its Playground tool, demonstrating the project's rapid evolution just two months after its initial launch.