Socket
Socket
Sign inDemoInstall

@root/acme

Package Overview
Dependencies
6
Maintainers
3
Versions
15
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 3.0.0 to 3.0.1

32

account.js

@@ -49,6 +49,6 @@ 'use strict';

function agree(tosUrl) {
function agree(agreed) {
var err;
if (me._tos !== tosUrl) {
err = new Error("must agree to '" + tosUrl + "'");
if (!agreed) {
err = new Error("must agree to '" + me._tos + "'");
err.code = 'E_AGREE_TOS';

@@ -140,8 +140,26 @@ throw err;

var agreeToTerms = options.agreeToTerms;
if (true === agreeToTerms) {
agreeToTerms = function(tos) {
return tos;
if (!agreeToTerms) {
agreeToTerms = function(terms) {
console.log(
'By using this software you accept this Subscriber Agreement and Terms of Service:'
);
console.info(
'ACME Subscriber Agreement:',
terms.acmeSubscriberTermsUrl
);
console.info(
'Greenlock/ACME.js Terms of Use:',
terms.terms.acmeJsTermsUrl
);
return true;
};
} else if (true === agreeToTerms) {
agreeToTerms = function(terms) {
return terms && true;
};
}
return agreeToTerms(me._tos);
return agreeToTerms({
acmeSubscriberTosUrl: me._tos,
acmeJsTosUrl: 'https://rootprojects.org/legal/#terms'
});
})

@@ -148,0 +166,0 @@ .then(agree)

@@ -113,3 +113,3 @@ // Copyright 2018-present AJ ONeal. All rights reserved

return A._getAccountKid(me, options).then(function(kid) {
ACME._normalizePresenters(options, options.challenges);
ACME._normalizePresenters(me, options, options.challenges);
return ACME._orderCert(me, options, kid).then(function(order) {

@@ -123,3 +123,3 @@ return order.claims;

return A._getAccountKid(me, options).then(function(kid) {
ACME._normalizePresenters(options, options.challenges);
ACME._normalizePresenters(me, options, options.challenges);
return ACME._finalizeOrder(me, options, kid, options.order);

@@ -133,3 +133,3 @@ });

return A._getAccountKid(me, options).then(function(kid) {
ACME._normalizePresenters(options, options.challenges);
ACME._normalizePresenters(me, options, options.challenges);
return ACME._getCertificate(me, options, kid);

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

};
ACME._normalizePresenters = function(options, presenters) {
ACME._normalizePresenters = function(me, options, presenters) {
// Prefer this order for efficiency:

@@ -235,2 +235,18 @@ // * http-01 is the fasest

);
if (
presenters['dns-01'] &&
'number' !== typeof presenters['dns-01'].propagationDelay
) {
if (!ACME._propagationDelayWarning) {
var err = new Error(
"dns-01 challenge's `propagationDelay` not set, defaulting to 5000ms"
);
err.code = 'E_NO_DNS_DELAY';
err.description =
"Each dns-01 challenge should specify challenges['dns-01'].propagationDelay as an estimate of how long DNS propagation will take.";
ACME._notify(me, options, 'warning', err);
presenters['dns-01'].propagationDelay = 5000;
ACME._propagationDelayWarning = true;
}
}
Object.keys(presenters || {}).forEach(function(k) {

@@ -916,9 +932,2 @@ var ch = presenters[k];

if (!DNS_DELAY || DNS_DELAY <= 0) {
if (!ACME._propagationDelayWarning) {
console.warn(
'warn: the given dns-01 challenge did not specify `propagationDelay`'
);
console.warn('warn: the default of 5000ms will be used');
ACME._propagationDelayWarning = true;
}
DNS_DELAY = 5000;

@@ -925,0 +934,0 @@ }

@@ -58,2 +58,3 @@ 'use strict';

maintainer: me.maintainerEmail,
package: me.packageAgent,
tz: tz,

@@ -60,0 +61,0 @@ locale: locale

{
"name": "@root/acme",
"version": "3.0.0",
"version": "3.0.1",
"description": "Free SSL certificates for Node.js and Browsers. Issued via Let's Encrypt",

@@ -5,0 +5,0 @@ "homepage": "https://rootprojects.org/acme/",

@@ -1,9 +0,23 @@

# [ACME.js](https://git.rootprojects.org/root/acme.js) (RFC 8555 / November 2019)
# Let's Encrypt&trade; + JavaScript = [ACME.js](https://git.rootprojects.org/root/acme.js)
| Built by [Root](https://therootcompany.com) for [Greenlock](https://greenlock.domains)
| Built by [Root](https://therootcompany.com) for [Hub](https://rootprojects.org/hub)
Free SSL Certificates from Let's Encrypt, for Node.js and Web Browsers
ACME.js is a _low-level_ client for Let's Encrypt.
Lightweight. Fast. Modern Crypto. Zero external dependecies.
Looking for an **easy**, _high-level_ client? Check out [Greenlock.js](https://git.rootprojects.org/root/greenlock.js).
# Online Demo
See https://greenlock.domains
<!--
We expect that our hosted versions will meet all of yours needs.
If they don't, please open an issue to let us know why.
We'd much rather improve the app than have a hundred different versions running in the wild.
However, in keeping to our values we've made the source visible for others to inspect, improve, and modify.
-->
# Features

@@ -13,46 +27,62 @@

The primary goal of this library is to make it easy to
get Accounts and Certificates through Let's Encrypt.
Supports the latest (Nov 2019) release of Let's Encrypt in a small, lightweight, Vanilla JS package.
- [x] Let's Encrypt v2 / ACME RFC 8555 (November 2019)
- [x] POST-as-GET support
- [x] Secure support for EC and RSA for account and server keys
- [x] Simple and lightweight PEM, DER, ASN1, X509, and CSR implementations
- [ ] (in-progress) StartTLS Everywhere&trade;
- [x] Supports International Domain Names (i.e. `.中国`)
- [x] Works with any [generic ACME challenge handler](https://git.rootprojects.org/root/acme-challenge-test.js)
- [x] **http-01** for single or multiple domains per certificate
- [x] **dns-01** for wildcards, localhost, private networks, etc
- [x] VanillaJS, Zero External Dependencies
- [x] Let's Encrypt v2
- [x] ACME RFC 8555
- [x] November 2019
- [x] POST-as-GET
- [ ] StartTLS Everywhere&trade; (in-progress)
- [x] IDN (i.e. `.中国`)
- [x] ECDSA and RSA keypairs
- [x] JWK
- [x] PEM
- [x] DER
- [x] Native Crypto in Node.js
- [x] WebCrypto in Browsers
- [x] Domain Validation Plugins
- [x] tls-alpn-01
- [x] http-01
- [x] dns-01
- [x] **Wildcards**
- [x] **Localhost**
- [x] Private Networks
- [x] [Create your own](https://git.rootprojects.org/root/acme-challenge-test.js)
- [x] Vanilla JS\*
- [x] No Transpiling Necessary!
- [x] Node.js
- [x] Browsers
- [x] WebPack
- [x] Zero External Dependencies
- [x] Commercial Support
- [x] Safe, Efficient, Maintained
- [x] Node.js\* (v6+)
- [x] WebPack
- [x] Online Demo
- See https://greenlock.domains
\* Although we use `async/await` in the examples, the code is written in CommonJS,
with Promises, so you can use it in Node.js and Browsers without transpiling.
\* Although we use `async/await` in the examples,
the codebase is written entirely in Common JS.
# Want Quick and Easy?
# Use Cases
ACME.js is a low-level tool for building Let's Encrypt clients in Node and Browsers.
- Home Servers
- IoT
- Enterprise On-Prem
- Web Hosting
- Cloud Services
- Localhost Development
If you're looking for maximum convenience, try
[Greenlock.js](https://git.rootprojects.org/root/greenlock-express.js).
# API
- <https://git.rootprojects.org/root/greenlock-express.js>
The public API encapsulates the three high-level steps of the ACME protocol:
# Online Demos
1. API Discovery
2. Account Creation
- Subscriber Agreement
3. Certificate Issuance
- Certificate Request
- Authorization Challenges
- Challenge Presentation
- Certificate Redemption
- Greenlock for the Web <https://greenlock.domains>
- ACME.js Demo <https://rootprojects.org/acme/>
## Overview
We expect that our hosted versions will meet all of yours needs.
If they don't, please open an issue to let us know why.
The core API can be show in just four functions:
We'd much rather improve the app than have a hundred different versions running in the wild.
However, in keeping to our values we've made the source visible for others to inspect, improve, and modify.
# API Overview
```js

@@ -72,17 +102,2 @@ ACME.create({ maintainerEmail, packageAgent, notify });

| Parameter | Description |
| --------------- | ----------------------------------------------------------------------------------------------------------- |
| account | an object containing the Let's Encrypt Account ID as "kid" (misnomer, not actually a key id/thumbprint) |
| accountKey | an RSA or EC public/private keypair in JWK format |
| agreeToTerms | set to `true` to agree to the Let's Encrypt Subscriber Agreement |
| challenges | the 'http-01', 'alpn-01', and/or 'dns-01' challenge plugins (`get`, `set`, and `remove` callbacks) to use |
| csr | a Certificate Signing Request (CSR), which may be generated with csr.js, openssl, or another |
| customerEmail | Don't use this. Given as an example to differentiate between Maintainer, Subscriber, and End-User |
| directoryUrl | should be the Let's Encrypt Directory URL<br>`https://acme-staging-v02.api.letsencrypt.org/directory` |
| domains | the list of altnames (subject first) that are listed in the CSR and will be listed on the certificate |
| notify | all callback for logging events and errors in the form `function (ev, args) { ... }` |
| maintainerEmail | should be a contact for the author of the code to receive critical bug and security notices |
| packageAgent | should be an RFC72321-style user-agent string to append to the ACME client (ex: mypackage/v1.1.1) |
| subscriberEmail | should be a contact for the service provider to receive renewal failure notices and manage the ACME account |
Helper Functions

@@ -92,3 +107,3 @@

ACME.computeChallenge({
accountKey: jwk,
accountKey,
hostname: 'example.com',

@@ -99,2 +114,77 @@ challenge: { type: 'dns-01', token: 'xxxx' }

| Parameter | Description |
| ------------------ | ----------------------------------------------------------------------------------------------------------- |
| account | an object containing the Let's Encrypt Account ID as "kid" (misnomer, not actually a key id/thumbprint) |
| accountKey | an RSA or EC public/private keypair in JWK format |
| agreeToTerms | set to `true` to agree to the Let's Encrypt Subscriber Agreement |
| challenges | the 'http-01', 'alpn-01', and/or 'dns-01' challenge plugins (`get`, `set`, and `remove` callbacks) to use |
| csr | a Certificate Signing Request (CSR), which may be generated with `@root/csr`, openssl, or another |
| customerEmail | Don't use this. Given as an example to differentiate between Maintainer, Subscriber, and End-User |
| directoryUrl | should be the Let's Encrypt Directory URL<br>`https://acme-staging-v02.api.letsencrypt.org/directory` |
| domains | the list of altnames (subject first) that are listed in the CSR and will be listed on the certificate |
| maintainerEmail | should be a contact for the author of the code to receive critical bug and security notices |
| notify | all callback for logging events and errors in the form `function (ev, args) { ... }` |
| packageAgent | should be an RFC72321-style user-agent string to append to the ACME client (ex: mypackage/v1.1.1) |
| skipChallengeTests | do not do a self-check that the ACME-issued challenges will pass (not recommended) |
| skipDryRun: false | do not do a self-check with self-issued challenges (not recommended) |
| subscriberEmail | should be a contact for the service provider to receive renewal failure notices and manage the ACME account |
**Maintainer vs Subscriber vs Customer**
- `maintainerEmail` should be the email address of the **author of the code**.
This person will receive critical security and API change notifications.
- `subscriberEmail` should be the email of the **admin of the hosting service**.
This person agrees to the Let's Encrypt Terms of Service and will be notified
when a certificate fails to renew.
- `customerEmail` should be the email of individual who owns the domain.
This is optional (not currently implemented).
Generally speaking **YOU** are the _maintainer_ and you **or your employer** is the _subscriber_.
If you (or your employer) is running any type of service
you **SHOULD NOT** pass the _customer_ email as the subscriber email.
If you are not running a service (you may be building a CLI, for example),
then you should prompt the user for their email address, and they are the subscriber.
## Events
These `notify` events are intended for _logging_ and debugging, NOT as a data API.
| Event Name | Example Message |
| -------------------- | --------------------------------------------------------------------------------- |
| `certificate_order` | `{ subject: 'example.com', altnames: ['...'], account: { key: { kid: '...' } } }` |
| `challenge_select` | `{ altname: '*.example.com', type: 'dns-01' }` |
| `challenge_status` | `{ altname: '*.example.com', type: 'dns-01', status: 'pending' }` |
| `challenge_remove` | `{ altname: '*.example.com', type: 'dns-01' }` |
| `certificate_status` | `{ subject: 'example.com', status: 'valid' }` |
| `warning` | `{ message: 'what went wrong', description: 'what action to take about it' }` |
| `error` | `{ message: 'a background process failed, and it may have side-effects' }` |
Note: DO NOT rely on **undocumented properties**. They are experimental and **will break**.
If you have a use case for a particular property **open an issue** - we can lock it down and document it.
# Example
A basic example includes the following:
1. Initialization
- maintainer contact
- package user-agent
- log events
2. Discover API
- retrieves Terms of Service and API endpoints
3. Get Subscriber Account
- create an ECDSA (or RSA) Account key in JWK format
- agree to terms
- register account by the key
4. Prepare a Certificate Signing Request
- create a RSA (or ECDSA) Server key in PEM format
- select domains
- choose challenges
- sign CSR
- order certificate
See [examples/README.md](https://git.rootprojects.org/root/acme.js/src/branch/master/examples/README.md)
# Install

@@ -106,3 +196,4 @@

## Node.js
<detail>
<summary>Node.js</summary>

@@ -117,4 +208,7 @@ ```js

## WebPack
</detail>
<detail>
<summary>WebPack</summary>
```html

@@ -130,4 +224,7 @@ <meta charset="UTF-8" />

## Vanilla JS
</detail>
<detail>
<summary>Vanilla JS</summary>
```html

@@ -155,180 +252,38 @@ <meta charset="UTF-8" />

## Usage Examples
</detail>
You can see `tests/index.js`, `examples/index.html`, `examples/app.js` in the repo for full example usage.
# Challenge Callbacks
### Emails: Maintainer vs Subscriber vs Customer
The challenge callbacks are documented in the [test suite](https://git.rootprojects.org/root/acme-dns-01-test.js),
essentially:
- `maintainerEmail` should be the email address of the **author of the code**.
This person will receive critical security and API change notifications.
- `subscriberEmail` should be the email of the **admin of the hosting service**.
This person agrees to the Let's Encrypt Terms of Service and will be notified
when a certificate fails to renew.
- `customerEmail` should be the email of individual who owns the domain.
This is optional (not currently implemented).
Generally speaking **YOU** are the _maintainer_ and you **or your employer** is the _subscriber_.
If you (or your employer) is running any type of service
you **SHOULD NOT** pass the _customer_ email as the subscriber email.
If you are not running a service (you may be building a CLI, for example),
then you should prompt the user for their email address, and they are the subscriber.
### Overview
1. Create an instance of ACME.js
2. Create and SAVE a Subscriber Account private key
3. Retrieve the Let's Encrypt Subscriber account (with the key)
- the account will be created if it doesn't exist
4. Create a Server Key
- this should be per-server, or perhaps per-end-user
5. Create a Certificate Signing Request
- International Domain Names must be converted with `punycode`
6. Create an ACME Order
- use a challenge plugin for HTTP-01 or DNS-01 challenges
### Instantiate ACME.js
Although built for Let's Encrypt, ACME.js will work with any server
that supports draft-15 of the ACME spec (includes POST-as-GET support).
The `init()` method takes a _directory url_ and initializes internal state according to its response.
```js
var acme = ACME.create({
maintainerEmail: 'jon@example.com'
});
acme.init('https://acme-staging-v02.api.letsencrypt.org/directory').then(
function() {
// Ready to use, show page
$('body').hidden = false;
}
);
function create(options) {
var plugin = {
init: async function(deps) {
// for http requests
plugin.request = deps.request;
},
zones: async function(args) {
// list zones relevant to the altnames
},
set: async function(args) {
// set TXT record
},
get: async function(args) {
// get TXT records
},
remove: async function(args) {
// remove TXT record
},
// how long to wait after *all* TXT records are set
// before presenting them for validation
propagationDelay: 5000
};
return plugin;
}
```
### Create ACME Account with Let's Encrypt
The `http-01` plugin is similar, but without `zones` or `propagationDelay`.
ACME Accounts are key and device based, with an email address as a backup identifier.
A public account key must be registered before an SSL certificate can be requested.
```js
var accountPrivateJwk;
var account;
Keypairs.generate({ kty: 'EC' }).then(function(pair) {
accountPrivateJwk = pair.private;
return acme.accounts
.create({
agreeToTerms: function(tos) {
if (
window.confirm(
"Do you agree to the ACME.js and Let's Encrypt Terms of Service?"
)
) {
return Promise.resolve(tos);
}
},
accountKey: pair.private,
subscriberEmail: $('.js-email-input').value
})
.then(function(_account) {
account = _account;
});
});
```
### Generate a Certificate Private Key
```js
var certKeypair = await Keypairs.generate({ kty: 'RSA' });
var pem = await Keypairs.export({
jwk: certKeypair.private,
encoding: 'pem'
});
// This should be saved as `privkey.pem`
console.log(pem);
```
### Generate a CSR
The easiest way to generate a Certificate Signing Request will be either with `openssl` or with `@root/CSR`.
```js
var CSR = require('@root/csr');
var Enc = require('@root/encoding');
// 'subject' should be first in list
// the domains may be in any order, but it should be consistent
var sortedDomains = ['example.com', 'www.example.com'];
var csr = await CSR.csr({
jwk: certKeypair.private,
domains: sortedDomains,
encoding: 'der'
}).then(function(der) {
return Enc.bufToUrlBase64(der);
});
```
### Get Free 90-day SSL Certificate
Creating an ACME "order" for a 90-day SSL certificate requires use of the account private key,
the names of domains to be secured, and a distinctly separate server private key.
A domain ownership verification "challenge" (uploading a file to an unsecured HTTP url or setting a DNS record)
is a required part of the process, which requires `set` and `remove` callbacks/promises.
```js
var certinfo = await acme.certificates.create({
account: account,
accountKey: accountPrivateJwk,
csr: csr,
domains: sortedDomains,
challenges: challenges, // must be implemented
customerEmail: null,
skipChallengeTests: false,
skipDryRun: false
});
console.log('Got SSL Certificate:');
console.log(results.expires);
// This should be saved as `fullchain.pem`
console.log([results.cert, results.chain].join('\n'));
```
### Example "Challenge" Implementation
Typically here you're just presenting some sort of dialog to the user to ask them to
upload a file or set a DNS record.
It may be possible to do something fancy like using OAuth2 to login to Google Domanis
to set a DNS address, etc, but it seems like that sort of fanciness is probably best
reserved for server-side plugins.
```js
var challenges = {
'http-01': {
set: function(opts) {
console.info('http-01 set challenge:');
console.info(opts.challengeUrl);
console.info(opts.keyAuthorization);
while (
!window.confirm('Upload the challenge file before continuing.')
) {
// spin and wait for the user to upload the challenge file
}
return Promise.resolve();
},
remove: function(opts) {
console.log('http-01 remove challenge:', opts.challengeUrl);
return Promise.resolve();
}
}
};
```
Many challenge plugins are already available for popular platforms.

@@ -338,40 +293,29 @@

- [x] DNS-01 Challenges
- CloudFlare
- [Digital Ocean](https://git.rootprojects.org/root/acme-dns-01-digitalocean.js)
- [DNSimple](https://git.rootprojects.org/root/acme-dns-01-dnsimple.js)
- [DuckDNS](https://git.rootprojects.org/root/acme-dns-01-duckdns.js)
- [GoDaddy](https://git.rootprojects.org/root/acme-dns-01-godaddy.js)
- [Gandi](https://git.rootprojects.org/root/acme-dns-01-gandi.js)
- [NameCheap](https://git.rootprojects.org/root/acme-dns-01-namecheap.js)
- [Name&#46;com](https://git.rootprojects.org/root/acme-dns-01-namedotcom.js)
- Route53 (AWS)
- [Vultr](https://git.rootprojects.org/root/acme-dns-01-vultr.js)
- Build your own
- [x] HTTP-01 Challenges
- [In-Memory](https://git.rootprojects.org/root/acme-http-01-standalone.js) (Standalone)
- [FileSystem](https://git.rootprojects.org/root/acme-http-01-webroot.js) (WebRoot)
- S3 (AWS, Digital Ocean, etc)
- [x] TLS-ALPN-01 Challenges
- Contact us to learn about Greenlock Pro
| Type | Service | Plugin |
| ----------- | ----------------------------------------------------------------------------------- | ------------------------ |
| dns-01 | CloudFlare | acme-dns-01-cloudflare |
| dns-01 | [Digital Ocean](https://git.rootprojects.org/root/acme-dns-01-digitalocean.js) | acme-dns-01-digitalocean |
| dns-01 | [DNSimple](https://git.rootprojects.org/root/acme-dns-01-dnsimple.js) | acme-dns-01-dnsimple |
| dns-01 | [DuckDNS](https://git.rootprojects.org/root/acme-dns-01-duckdns.js) | acme-dns-01-duckdns |
| http-01 | File System / [Web Root](https://git.rootprojects.org/root/acme-http-01-webroot.js) | acme-http-01-webroot |
| dns-01 | [GoDaddy](https://git.rootprojects.org/root/acme-dns-01-godaddy.js) | acme-dns-01-godaddy |
| dns-01 | [Gandi](https://git.rootprojects.org/root/acme-dns-01-gandi.js) | acme-dns-01-gandi |
| dns-01 | [NameCheap](https://git.rootprojects.org/root/acme-dns-01-namecheap.js) | acme-dns-01-namecheap |
| dns-01 | [Name&#46;com](https://git.rootprojects.org/root/acme-dns-01-namedotcom.js) | acme-dns-01-namedotcom |
| dns-01 | Route53 (AWS) | acme-dns-01-route53 |
| http-01 | S3 (AWS, Digital Ocean, Scaleway) | acme-http-01-s3 |
| dns-01 | [Vultr](https://git.rootprojects.org/root/acme-dns-01-vultr.js) | acme-dns-01-vultr |
| dns-01 | [Build your own](https://git.rootprojects.org/root/acme-dns-01-test.js) | acme-dns-01-test |
| http-01 | [Build your own](https://git.rootprojects.org/root/acme-http-01-test.js) | acme-http-01-test |
| tls-alpn-01 | [Contact us](mailto:support@therootcompany.com) | - |
# IDN - International Domain Names
# Running the Tests
Convert domain names to `punycode` before creating the certificate:
```js
var punycode = require('punycode');
acme.certificates.create({
// ...
domains: ['example.com', 'www.example.com'].map(function(name) {
return punycode.toASCII(name);
})
});
```bash
npm test
```
The punycode library itself is lightweight and dependency-free.
It is available both in node and for browsers.
## Usa a dns-01 challenge
# Testing
Although you can run the tests from a public facing server, its easiest to do so using a dns-01 challenge.

@@ -381,6 +325,5 @@ You will need to use one of the [`acme-dns-01-*` plugins](https://www.npmjs.com/search?q=acme-dns-01-)

You'll also need a `.env` that looks something like the one in `examples/example.env`:
```bash
ENV=DEV
MAINTAINER_EMAIL=letsencrypt+staging@example.com
SUBSCRIBER_EMAIL=letsencrypt+staging@example.com

@@ -393,3 +336,3 @@ BASE_DOMAIN=test.example.com

For example:
### For Example

@@ -403,3 +346,9 @@ ```bash

npm install --save-dev acme-dns-01-digitalocean
```
## Create a `.env` config
You'll need a `.env` in the project root that looks something like the one in `examples/example.env`:
```bash
# Copy the sample .env file

@@ -418,5 +367,2 @@ rsync -av examples/example.env .env

You can see `<script>` tags in the `index.html` in the repo, which references the original
source files.
Join `@rootprojects` `#general` on [Keybase](https://keybase.io) if you'd like to chat with us.

@@ -435,3 +381,3 @@

Greenlock&trade; is a [trademark](https://rootprojects.org/legal/#trademark) of AJ ONeal
ACME.jsk&trade; is a [trademark](https://rootprojects.org/legal/#trademark) of AJ ONeal

@@ -438,0 +384,0 @@ The rule of thumb is "attribute, but don't confuse". For example:

SocketSocket SOC 2 Logo

Product

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

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc