Socket
Socket
Sign inDemoInstall

@sap/hdi-deploy

Package Overview
Dependencies
Maintainers
1
Versions
60
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@sap/hdi-deploy - npm Package Compare versions

Comparing version 4.3.3 to 4.4.1

11

CHANGELOG.md

@@ -0,1 +1,12 @@

## 4.4.1
Features:
- use @sap/hana-client@2.13.13
- use hdb@0.19.3
- use @sap/xsenv@3.3.2
## 4.4.0
Features:
- added option --use-hdb to enable using hdb client instead of @sap/hana-client for connecting to the database
- use @sap/hana-client@2.12.25
## 4.3.3

@@ -2,0 +13,0 @@ Features:

@@ -169,2 +169,4 @@ 'use strict';

args.translateJSONEnvBooleanOptionToOption(logger, options, name, option, process.argv);
} else if (option === 'use_hdb') {
args.translateJSONEnvBooleanOptionToOption(logger, options, name, option, process.argv);
} else if (option === 'liveness_ping') {

@@ -226,2 +228,3 @@ args.translateJSONEnvBooleanOptionToOption(logger, options, name, option, process.argv);

' --version print version and exit',
' --use-hdb enable usage of hdb instead of @sap/hana-client',
' -t, --trace enable tracing',

@@ -402,2 +405,4 @@ ' --hana-client-trace enable tracing for the SAP HANA client; all interactions with the SAP HANA server will be traced which can lead to a large amount of trace information to be written',

logger.setHanaClientTrace(true);
} else if (arg === '--use-hdb') {
logger.setHDBValue(true);
} else if (arg === '--hana-client-packet-trace') {

@@ -630,2 +635,9 @@ logger.setHanaClientPacketTrace(true);

logger.log(`Deployment started at ${utils.currentDateTime()}`);
// prints client information
if (logger.getHDBValue()) {
logger.log('Using hdb client for connection');
} else {
logger.log('Using hana-client for connection');
}
// log that we couldn't get the version from the server (usually we don't have privileges for SYS.M_DATABASE)

@@ -632,0 +644,0 @@ if (serverVersion.error) {

36

lib/client-info.js
'use strict';
const application_id = process.env.APPLICATION_ID || 'SAP_HDI';
const hana_client = require('@sap/hana-client');
const logger = require('./logger');

@@ -13,2 +12,9 @@ const async = require('async');

let org_name = '';
const is_hdb_enabled = logger.getHDBValue();
let hana_client;
if (is_hdb_enabled) {
hana_client = require('hdb');
} else {
hana_client = require('@sap/hana-client');
}

@@ -40,4 +46,3 @@ if (process.env.VCAP_APPLICATION) {

}
const client = hana_client.createConnection();
connections.push({client, file: __filename});
let client;

@@ -48,6 +53,23 @@ const sql = 'select * from sys.m_session_context where CONNECTION_ID=CURRENT_CONNECTION';

async.series([
(cb) => client.connect(hdiCreds, cb),
(cb) => client.exec(sql, cb)
], (error, [, session_context]) => {
if (is_hdb_enabled) {
client = hana_client.createClient(hdiCreds);
} else {
client = hana_client.createConnection();
}
connections.push({client, file: __filename});
let connection;
if (is_hdb_enabled) {
connection = [
(cb) => client.connect(cb)
];
} else {
connection = [
(cb) => client.connect(hdiCreds, cb)
];
}
connection.push((cb) => client.exec(sql, cb));
async.series(connection, (error, [, session_context]) => {
if (error) {

@@ -54,0 +76,0 @@ logger.log(`Could not check if session variable APPLICATION is set correctly: ${error.message ? error.message : error}`);

94

lib/hana-helper.js

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

const async = require('async');
const hana_client = require('@sap/hana-client');
const hana_util = require('@sap/hana-client/extension/Stream.js');

@@ -12,5 +11,13 @@ const {enrich_credentials_with_session_variables} = require('./client-info');

const tls = require('tls');
const identifier = require('./utils.js').identifier;
const dot_quoted_identifier = require('./utils.js').dot_quoted_identifier;
const censor_string = require('./utils').censor_string;
const is_hdb_enabled = logger.getHDBValue();
let hana_client;
if (is_hdb_enabled) {
hana_client = require('hdb');
} else {
hana_client = require('@sap/hana-client');
}

@@ -262,5 +269,23 @@ /**

// hana-client@2.7.16 requires this to be a string.
credentials.port = `${port}`;
if (is_hdb_enabled) {
credentials.port = port;
} else {
credentials.port = `${port}`;
}
}
/**
* custom logic to validate the server's hostname against the certificate
* @param {string} hostname hostname for the connection
* @param {Object} cert certificate
* @returns
*/
credentials.checkServerIdentity = (hostname, cert) => {
if (credentials.sslValidateCertificate === false) {
return undefined;
}
const host = credentials.sslHostNameInCertificate ? credentials.sslHostNameInCertificate : hostname;
return tls.checkServerIdentity(host, cert);
};
if (client_key) {

@@ -293,2 +318,3 @@ credentials.key = client_key;

credentials.encrypt = encrypt;
credentials.useTLS = credentials.encrypt;
logger.trace('credentials.encrypt set to', credentials.encrypt);

@@ -301,4 +327,9 @@ }

let client;
if (is_hdb_enabled) {
client = hana_client.createClient(credentials);
} else {
client = hana_client.createConnection();
}
const client = hana_client.createConnection();

@@ -327,14 +358,19 @@ /**

return function (cb) {
if (logger.getHanaClientTrace()) {
const traceCB = function (buf) {
logger.log(buf.toString());
};
const traceOptions = logger.getHanaClientPacketTrace() ?
'DEBUG,SQL,PACKET=32K,FLUSH,OutBufferSize=128K' :
'DEBUG,SQL,PACKET=OFF,FLUSH,OutBufferSize=64K';
logger.log(`hana-helper SAP HANA client trace options: ${traceOptions}`);
client.onTrace(traceOptions, traceCB);
if (is_hdb_enabled) {
logger.trace('client connect: ', credentials);
client.connect(cb);
} else {
if (logger.getHanaClientTrace()) {
const traceCB = function (buf) {
logger.log(buf.toString());
};
const traceOptions = logger.getHanaClientPacketTrace() ?
'DEBUG,SQL,PACKET=32K,FLUSH,OutBufferSize=128K' :
'DEBUG,SQL,PACKET=OFF,FLUSH,OutBufferSize=64K';
logger.log(`hana-helper SAP HANA client trace options: ${traceOptions}`);
client.onTrace(traceOptions, traceCB);
}
logger.trace('client connect: ', credentials);
client.connect(credentials, cb);
}
logger.trace('hana_client connect: ', credentials);
client.connect(credentials, cb);
};

@@ -349,3 +385,3 @@ }

return function (cb) {
logger.trace('hana_client disconnect');
logger.trace('client disconnect');
client.close();

@@ -363,3 +399,3 @@ cb();

return function (cb) {
logger.trace('hana_client exec', sql);
logger.trace('client exec', sql);
client.exec(sql, function (err, rows) {

@@ -387,3 +423,3 @@ cb(formatSqlError(sql, err), rows);

// logger.trace('hana_client insert', sql, data);
logger.trace('hana_client insert', sql);
logger.trace('client insert', sql);
async.waterfall([

@@ -412,8 +448,12 @@ function (callback) {

function callproc1 (sql, input, fct) {
input = input || {}; // if undefined - hana client driver exception
input = input || {}; // if undefined - hana client / hdb driver exception
return function (cb) {
logger.trace('hana client call proc prepare: %s', sql);
logger.trace('client call proc prepare: %s', sql);
async.waterfall([
function (callback) {
hana_util.createProcStatement(client, sql, callback);
if (is_hdb_enabled) {
client.prepare(sql, callback);
} else {
hana_util.createProcStatement(client, sql, callback);
}
},

@@ -446,9 +486,13 @@ function (stmt, callback) {

function callproc2 (sql, input, fct) {
input = input || {}; // if undefined - hana_client driver exception
input = input || {}; // if undefined - hana_client / hdb driver exception
return function (cb) {
logger.trace('hana_client call proc prepare: %s', sql);
logger.trace('client call proc prepare: %s', sql);
async.waterfall([
function (callback) {
hana_util.createProcStatement(client, sql, callback);
if (is_hdb_enabled) {
client.prepare(sql, callback);
} else {
hana_util.createProcStatement(client, sql, callback);
}
},

@@ -480,4 +524,4 @@ function (stmt, callback) {

* workaround - check in which schema the creation of local temporary tables is possible
* - use schema in GoBroker / hana client 0.9
* - use schema + #DI in JavaBroker / hana client 0.10
* - use schema in GoBroker / hana client 0.9 / hdb 0.10
* - use schema + #DI in JavaBroker / hana client 0.10 / hdb 0.10
*/

@@ -484,0 +528,0 @@ return function (cb) {

@@ -17,3 +17,3 @@ 'use strict';

const schema = `${creds.schema}#DI`;
const container = new Container(creds.schema, hdiCreds, schema);
const container = new Container(creds.schema, hdiCreds, schema, logger.getHDBValue());

@@ -20,0 +20,0 @@ const deployTask = new DeployTask(container, hdiCreds, content, options, logger, creds.schema);

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

let isHanaClientPacketTraceEnabled = false;
let isUseHDBEnabled = false;
const Liveness_Ping = require('./liveness-ping');

@@ -141,2 +142,9 @@

exports.setHDBValue = function (value) {
isUseHDBEnabled = value;
};
exports.getHDBValue = function () {
return isUseHDBEnabled;
};
const client_private_key_1_regexp = new RegExp('"client_authentication_private_key":.*', 'ig');

@@ -143,0 +151,0 @@ const client_private_key_2_regexp = new RegExp('client_authentication_private_key:.*', 'ig');

'use strict';
const hana_client = require('@sap/hana-client');
const hana_helper = require('./hana-helper.js');
const logger = require('./logger');
let hana_client;
if (logger.getHDBValue()) {
hana_client = require('hdb');
} else {
hana_client = require('@sap/hana-client');
}
const connections = require('./connections');

@@ -108,4 +113,7 @@

}
return setTimeout(() => statement.exec([request_id, row_id], poll_callback), 1000);
if (logger.getHDBValue()) {
return setTimeout(() => statement.exec([request_id, row_id], {}, poll_callback), 9000000);
} else {
return setTimeout(() => statement.exec([request_id, row_id], poll_callback), 1000);
}
};

@@ -129,17 +137,36 @@

try {
const db = hana_client.createConnection();
connections.push({client:db, file: __filename});
db.connect(db_credentials, (e) => {
if (e) {
this.stop_flag = true;
logger.warn('Encountered an error during polling for messages. Polling will be stopped.');
} else {
this.db = db;
poll_request_id(connection_id, schema, db, (request_id) => {
if (request_id) {
poll_messages(request_id, schema, db);
}
});
}
});
let db;
if (logger.getHDBValue()) {
db = hana_client.createClient(db_credentials);
connections.push({client:db, file: __filename});
db.connect((e) => {
if (e) {
this.stop_flag = true;
logger.warn('Encountered an error during polling for messages. Polling will be stopped.');
} else {
this.db = db;
poll_request_id(connection_id, schema, db, (request_id) => {
if (request_id) {
poll_messages(request_id, schema, db);
}
});
}
});
} else {
db = hana_client.createConnection();
connections.push({client:db, file: __filename});
db.connect(db_credentials, (e) => {
if (e) {
this.stop_flag = true;
logger.warn('Encountered an error during polling for messages. Polling will be stopped.');
} else {
this.db = db;
poll_request_id(connection_id, schema, db, (request_id) => {
if (request_id) {
poll_messages(request_id, schema, db);
}
});
}
});
}
} catch (e) {

@@ -146,0 +173,0 @@ this.stop_flag = true;

'use strict';
// Use let to allow testing using rewire
// eslint-disable-next-line prefer-const
let hana_client = require('@sap/hana-client');
const {enrich_credentials_with_session_variables} = require('./client-info');
const {prepareCredentials} = require('./utils');
const logger = require('./logger');
let hana_client;
if (logger.getHDBValue()) {
hana_client = require('hdb');
} else {
hana_client = require('@sap/hana-client');
}
const connections = require('./connections');

@@ -23,2 +27,26 @@

function executeSql (client, target_credentials, e, callback) {
if (e) {
return could_not_check_ownership(e, callback);
}
client.exec(`SELECT OBJECT_NAME, OBJECT_TYPE, PATH, OWNER_NAME FROM ${target_credentials.schema}#DI.M_OBJECTS WHERE PATH IS NOT NULL AND DEPENDENT_OBJECT_NAME IS NULL AND CONTAINER_NAME = '${target_credentials.schema}' AND OWNER_NAME != '${target_credentials.schema}#OO'`, (err, r) => {
client.disconnect();
if (err) {
return could_not_check_ownership(err, callback);
}
if (r.length > 0) {
logger.log('Ownership check found the following issues:');
const messages = r.map(({OBJECT_NAME, OBJECT_TYPE, PATH, OWNER_NAME}) => {
return ` ${PATH}: Object '${OBJECT_NAME}' of type '${OBJECT_TYPE}' owned by '${OWNER_NAME}'.`;
});
messages.forEach(m => logger.log(m));
return callback(new Error(`Some objects in the container have the wrong owner! Found instances: ${r.length}`));
}
logger.logfnTimerDelta('check-ownership', 'Checking ownership... ok')(() => {});
return callback();
});
}
/**

@@ -34,32 +62,24 @@ * Check if ownership of the objects in the container is correct, should belong to #OO

const hdiCreds = enrich_credentials_with_session_variables(prepareCredentials(target_credentials, options, logger));
const client = hana_client.createConnection();
let client;
if (logger.getHDBValue()) {
client = hana_client.createClient(hdiCreds);
} else {
client= hana_client.createConnection();
}
connections.push({client, file: __filename});
logger.logfnTimerInit('check-ownership', 'Checking ownership...')(() => {});
client.connect(hdiCreds, (e) => {
if (e) {
return could_not_check_ownership(e, callback);
}
client.exec(`SELECT OBJECT_NAME, OBJECT_TYPE, PATH, OWNER_NAME FROM ${target_credentials.schema}#DI.M_OBJECTS WHERE PATH IS NOT NULL AND DEPENDENT_OBJECT_NAME IS NULL AND CONTAINER_NAME = '${target_credentials.schema}' AND OWNER_NAME != '${target_credentials.schema}#OO'`, (err, r) => {
client.disconnect();
if (logger.getHDBValue()) {
client.connect((e) => {
executeSql(client, target_credentials, e, callback);
});
if (err) {
return could_not_check_ownership(err, callback);
}
} else {
client.connect(hdiCreds, (e) => {
executeSql(client, target_credentials, e, callback);
});
}
if (r.length > 0) {
logger.log('Ownership check found the following issues:');
const messages = r.map(({OBJECT_NAME, OBJECT_TYPE, PATH, OWNER_NAME}) => {
return ` ${PATH}: Object '${OBJECT_NAME}' of type '${OBJECT_TYPE}' owned by '${OWNER_NAME}'.`;
});
messages.forEach(m => logger.log(m));
return callback(new Error(`Some objects in the container have the wrong owner! Found instances: ${r.length}`));
}
logger.logfnTimerDelta('check-ownership', 'Checking ownership... ok')(() => {});
return callback();
});
});
}
module.exports = check_ownership;
'use strict';
// Use let to allow testing using rewire
// eslint-disable-next-line prefer-const
let hana_client = require('@sap/hana-client');
const logger = require('./logger');
let hana_client;
if (logger.getHDBValue()) {
hana_client = require('hdb');
} else {
hana_client = require('@sap/hana-client');
}
const {prepareCredentials} = require('./utils');

@@ -49,2 +53,38 @@ const {isArray} = require('./utils');

function executeSql (client, targetCreds, e, callback) {
if (e) {
return could_not_get_status(e, callback);
}
client.exec(`SELECT TOP 1 * from ${targetCreds.schema}#DI.M_JOBS ORDER BY JOB_START_TIMESTAMP_UTC DESC`, (err, r) => {
if (err) {
client.disconnect();
return could_not_get_status(err, callback);
}
if (r.length > 0) {
const latest_request_id = r[0].REQUEST_ID;
client.exec(`SELECT * from ${targetCreds.schema}#DI.M_MESSAGES where REQUEST_ID = '${latest_request_id}' AND TYPE = 'SUMMARY'`, (error, rows) => {
if (error) {
client.disconnect();
return could_not_get_status(error, callback);
}
client.disconnect();
if (rows.length > 0) {
print_build_status(r, rows);
return callback();
} else {
return could_not_get_status('Could not find any information about the previous deployment.', callback);
}
});
} else {
client.disconnect();
return could_not_get_status('Could not find any information about the previous deployment.', callback);
}
});
}
/** */
/**

@@ -60,38 +100,16 @@ * Get the status of the last build.

const hdiCreds = enrich_credentials_with_session_variables(prepareCredentials(targetCreds, options, logger));
const client = hana_client.createConnection();
connections.push({client, file: __filename});
client.connect(hdiCreds, (e) => {
if (e) {
return could_not_get_status(e, callback);
}
client.exec(`SELECT TOP 1 * from ${targetCreds.schema}#DI.M_JOBS ORDER BY JOB_START_TIMESTAMP_UTC DESC`, (err, r) => {
if (err) {
client.disconnect();
return could_not_get_status(err, callback);
}
if (r.length > 0) {
const latest_request_id = r[0].REQUEST_ID;
client.exec(`SELECT * from ${targetCreds.schema}#DI.M_MESSAGES where REQUEST_ID = '${latest_request_id}' AND TYPE = 'SUMMARY'`, (error, rows) => {
if (error) {
client.disconnect();
return could_not_get_status(error, callback);
}
client.disconnect();
if (rows.length > 0) {
print_build_status(r, rows);
return callback();
} else {
return could_not_get_status('Could not find any information about the previous deployment.', callback);
}
});
} else {
client.disconnect();
return could_not_get_status('Could not find any information about the previous deployment.', callback);
}
let client;
if (logger.getHDBValue()) {
client = hana_client.createClient(hdiCreds);
connections.push({client, file: __filename});
client.connect((e) => {
executeSql(client, targetCreds, e, callback);
});
});
} else {
client = hana_client.createConnection();
connections.push({client, file: __filename});
client.connect(hdiCreds, (e) => {
executeSql(client, targetCreds, e, callback);
});
}
};

@@ -227,3 +227,3 @@ 'use strict';

tasks.push((cb) => this.container.connect(cb));
if (logger.getHanaClientTrace()) {
if (!logger.getHDBValue() && logger.getHanaClientTrace()) {
const traceCB = function (buf) {

@@ -230,0 +230,0 @@ logger.log(buf.toString());

@@ -7,2 +7,5 @@ 'use strict';

const strip_json_comments = require('./json-comment-stripper');
const tls = require('tls');
const log = require('./logger');
const is_hdb_enabled = log.getHDBValue();

@@ -258,3 +261,3 @@ /**

* @param {any} options Options, that can contain credentials as well.
* @returns {Object} An object containing the credentials for a HANA connection.
* @returns {Object} An object containing the credentials for a HANA / hdb connection.
*/

@@ -289,5 +292,24 @@ function prepareCredentials (creds, options, logger) {

// hana-client@2.7.16 requires this to be a string.
hdiCreds.port = `${creds.port}`;
if (is_hdb_enabled) {
hdiCreds.port = creds.port;
} else {
hdiCreds.port = `${creds.port}`;
}
}
/**
* custom logic to validate the server's hostname against the certificate
* @param {string} hostname hostname for the connection
* @param {Object} cert certificate
* @returns
*/
hdiCreds.checkServerIdentity = (hostname, cert) => {
if (creds.sslValidateCertificate === false) {
return undefined;
}
const host = creds.hostname_in_certificate ? creds.hostname_in_certificate : hostname;
return tls.checkServerIdentity(host, cert);
};
if (creds.certificate) {

@@ -310,3 +332,8 @@ hdiCreds.ca = Array.isArray(creds.certificate) ? creds.certificate : [creds.certificate];

hdiCreds.encrypt = creds.encrypt;
logger.trace('hdiCreds.encrypt set to', hdiCreds.encrypt);
hdiCreds.useTLS = creds.encrypt;
if (is_hdb_enabled) {
logger.trace('hdiCreds.useTLS set to', hdiCreds.encrypt);
} else {
logger.trace('hdiCreds.encrypt set to', hdiCreds.encrypt);
}
}

@@ -313,0 +340,0 @@

{
"name": "@sap/hdi-deploy",
"version": "4.3.3",
"version": "4.4.1",
"lockfileVersion": 1,

@@ -8,3 +8,3 @@ "requires": true,

"@sap/hana-client": {
"version": "2.12.20",
"version": "2.13.13",
"requires": {

@@ -32,5 +32,6 @@ "debug": "3.1.0"

"@sap/xsenv": {
"version": "3.1.0",
"version": "3.3.2",
"requires": {
"debug": "3.1.0",
"debug": "4.3.3",
"node-cache": "^5.1.0",
"verror": "1.10.0"

@@ -42,2 +43,5 @@ },

},
"clone": {
"version": "2.1.2"
},
"core-util-is": {

@@ -47,13 +51,19 @@ "version": "1.0.2"

"debug": {
"version": "3.1.0",
"version": "4.3.3",
"requires": {
"ms": "2.0.0"
"ms": "2.1.2"
}
},
"extsprintf": {
"version": "1.4.0"
"version": "1.4.1"
},
"ms": {
"version": "2.0.0"
"version": "2.1.2"
},
"node-cache": {
"version": "5.1.2",
"requires": {
"clone": "2.x"
}
},
"verror": {

@@ -97,2 +107,14 @@ "version": "1.10.0",

},
"hdb": {
"version": "0.19.3",
"requires": {
"iconv-lite": "^0.4.18"
}
},
"iconv-lite": {
"version": "0.4.24",
"requires": {
"safer-buffer": ">= 2.1.2 < 3"
}
},
"is-number": {

@@ -117,2 +139,5 @@ "version": "7.0.0"

},
"safer-buffer": {
"version": "2.1.2"
},
"source-map": {

@@ -128,3 +153,3 @@ "version": "0.6.1"

"uglify-js": {
"version": "3.15.3",
"version": "3.16.1",
"optional": true

@@ -131,0 +156,0 @@ },

{
"name": "@sap/hdi-deploy",
"description": "HDI content deployment",
"version": "4.3.3",
"version": "4.4.1",
"license": "See LICENSE file",

@@ -9,8 +9,9 @@ "repository": {},

"dependencies": {
"@sap/hana-client": "2.12.20",
"@sap/hana-client": "2.13.13",
"@sap/hdi": "4.3.1",
"@sap/xsenv": "3.1.0",
"@sap/xsenv": "3.3.2",
"async": "3.2.3",
"dotenv": "10.0.0",
"handlebars": "4.7.7",
"hdb": "0.19.3",
"micromatch": "4.0.4"

@@ -17,0 +18,0 @@ },

@@ -104,3 +104,3 @@ @sap/hdi-deploy

"dependencies": {
"@sap/hdi-deploy": "4.3.3"
"@sap/hdi-deploy": "4.4.1"
},

@@ -547,3 +547,3 @@ "scripts": {

"dependencies": {
"@sap/hdi-deploy": "4.3.3",
"@sap/hdi-deploy": "4.4.1",
"module1": "1.3.1",

@@ -771,4 +771,4 @@ "module2": "1.7.0"

- `hdi`: an HDI container with access to the container's GRANT APIs
- `sql`: a technical database user with GRANT privileges for the required object privileges, roles, system privileges, etc.
- `hdi`: an HDI container with access to the container's GRANT APIs. An HDI container can only grant roles from the container, without admin option.
- `procedure`: a technical database user with EXECUTE privileges on a stored procedure which has GRANT privileges for the required object privileges, roles, system privileges, etc.

@@ -1021,2 +1021,3 @@ - `ignore`: grants were already given at the database-level and the HDI Deployer will ignore the content of the `.hdbgrants` file.

- `-t, --trace`: enable tracing
- `--use-hdb`: enable the "hdb" client to connect to the database instead of "@sap/hana-client"; by default "@sap/hana-client" is used
- `--hana-client-trace`: enable tracing for the SAP HANA client; All interactions with the SAP HANA server will be traced which can lead to a large amount of trace information to be written

@@ -1081,3 +1082,3 @@ - `--hana-client-packet-trace`: enable PACKET tracing for the SAP HANA client; Must only be used in combination with --hana-client-trace

"name": "@sap/hdi-deploy",
"version": "4.3.3",
"version": "4.4.1",
"features": {

@@ -1084,0 +1085,0 @@ "info": 2,

SocketSocket SOC 2 Logo

Product

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

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc