@ginger.io/jay-z
Advanced tools
Comparing version 0.0.3 to 0.0.4
export { DataKey, DataKeyProvider } from "./DataKeyProvider"; | ||
export { ItemWithEncryptedFields, JayZ } from "./JayZ"; | ||
export * from "./Encryptor"; | ||
export { JayZ, JayZConfig } from "./JayZ"; | ||
export { KMSDataKeyProvider } from "./KMSDataKeyProvider"; | ||
export { LibsodiumEncryptor } from "./LibsodiumEncryptor"; | ||
export { StubDataKeyProvider } from "./StubDataKeyProvider"; | ||
export * from "./types"; |
"use strict"; | ||
function __export(m) { | ||
for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; | ||
} | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
@@ -7,4 +10,7 @@ var JayZ_1 = require("./JayZ"); | ||
exports.KMSDataKeyProvider = KMSDataKeyProvider_1.KMSDataKeyProvider; | ||
var LibsodiumEncryptor_1 = require("./LibsodiumEncryptor"); | ||
exports.LibsodiumEncryptor = LibsodiumEncryptor_1.LibsodiumEncryptor; | ||
var StubDataKeyProvider_1 = require("./StubDataKeyProvider"); | ||
exports.StubDataKeyProvider = StubDataKeyProvider_1.StubDataKeyProvider; | ||
__export(require("./types")); | ||
//# sourceMappingURL=index.js.map |
import { DataKeyProvider } from "./DataKeyProvider"; | ||
declare enum EncryptionVersion { | ||
V_0 = 0 | ||
} | ||
declare type EncryptedItemMetadata<T, K extends keyof T> = { | ||
version: EncryptionVersion; | ||
nonce: Uint8Array; | ||
encryptedDataKey: Uint8Array; | ||
encryptedFieldNames: K[]; | ||
import { Encryptor } from "./Encryptor"; | ||
import { EncryptedJayZItem } from "./types"; | ||
export declare type JayZConfig = { | ||
keyProvider: DataKeyProvider; | ||
encryptor?: Encryptor; | ||
maxUsesPerDataKey?: number; | ||
}; | ||
export declare type ItemWithEncryptedFields<T, K extends keyof T> = Omit<T, K> & { | ||
[P in K]: Uint8Array; | ||
} & { | ||
__jayz__metadata: EncryptedItemMetadata<T, K>; | ||
}; | ||
export declare class JayZ { | ||
private keyProvider; | ||
constructor(keyProvider: DataKeyProvider); | ||
encryptItem<T, K extends keyof T>(item: T, fieldsToEncrypt: K[]): Promise<ItemWithEncryptedFields<T, K>>; | ||
decryptItem<T, K extends keyof T>(encryptedItem: ItemWithEncryptedFields<T, K>): Promise<T>; | ||
private toBuffer; | ||
private generateEcryptionKey; | ||
private deriveEncryptionKey; | ||
private encryptor; | ||
private maxUsesPerDataKey; | ||
private timesDataKeyUsed; | ||
private dataKey?; | ||
constructor(config: JayZConfig); | ||
encryptItem<T, K extends keyof T>(item: T, fieldsToEncrypt: K[]): Promise<EncryptedJayZItem<T, K>>; | ||
decryptItem<T, K extends keyof T>(encryptedJayZItem: EncryptedJayZItem<T, K>): Promise<T>; | ||
private getDataKey; | ||
} | ||
export {}; |
@@ -11,80 +11,68 @@ "use strict"; | ||
}; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const fast_json_stable_stringify_1 = __importDefault(require("fast-json-stable-stringify")); | ||
const libsodium_wrappers_1 = require("libsodium-wrappers"); | ||
var KeyType; | ||
(function (KeyType) { | ||
KeyType[KeyType["ENCRYPTION"] = 1] = "ENCRYPTION"; | ||
KeyType[KeyType["SIGNING"] = 2] = "SIGNING"; | ||
})(KeyType || (KeyType = {})); | ||
var EncryptionVersion; | ||
(function (EncryptionVersion) { | ||
EncryptionVersion[EncryptionVersion["V_0"] = 0] = "V_0"; // experimental | ||
})(EncryptionVersion || (EncryptionVersion = {})); | ||
const LibsodiumEncryptor_1 = require("./LibsodiumEncryptor"); | ||
class JayZ { | ||
constructor(keyProvider) { | ||
this.keyProvider = keyProvider; | ||
constructor(config) { | ||
this.encryptor = new LibsodiumEncryptor_1.LibsodiumEncryptor(); | ||
this.timesDataKeyUsed = 0; | ||
this.keyProvider = config.keyProvider; | ||
this.encryptor = | ||
config.encryptor !== undefined | ||
? config.encryptor | ||
: new LibsodiumEncryptor_1.LibsodiumEncryptor(); | ||
this.maxUsesPerDataKey = config.maxUsesPerDataKey || 1; | ||
} | ||
encryptItem(item, fieldsToEncrypt) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const { encryptedDataKey, derivedEncryptionKey, nonce } = yield this.generateEcryptionKey(); | ||
const encryptedFields = {}; | ||
fieldsToEncrypt.forEach(name => { | ||
encryptedFields[name] = libsodium_wrappers_1.crypto_secretbox_easy(this.toBuffer(item[name]), nonce, derivedEncryptionKey); | ||
const { dataKey, encryptedDataKey } = yield this.getDataKey(); | ||
const { encryptedItem, nonce } = yield this.encryptor.encrypt({ | ||
item, | ||
fieldsToEncrypt, | ||
dataKey | ||
}); | ||
libsodium_wrappers_1.memzero(derivedEncryptionKey); | ||
const __jayz__metadata = { | ||
encryptedDataKey, | ||
nonce, | ||
version: EncryptionVersion.V_0, | ||
scheme: this.encryptor.scheme, | ||
encryptedFieldNames: fieldsToEncrypt | ||
}; | ||
return Object.assign(Object.assign(Object.assign({}, item), encryptedFields), { __jayz__metadata }); | ||
return Object.assign(Object.assign({}, encryptedItem), { __jayz__metadata }); | ||
}); | ||
} | ||
decryptItem(encryptedItem) { | ||
decryptItem(encryptedJayZItem) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const { nonce, encryptedDataKey, encryptedFieldNames } = encryptedItem.__jayz__metadata; | ||
const decryptedDataKey = yield this.keyProvider.decryptDataKey(encryptedDataKey); | ||
const decryptionKey = this.deriveEncryptionKey(decryptedDataKey, KeyType.ENCRYPTION); | ||
const data = Object.assign({}, encryptedItem); | ||
delete data.__jayz__metadata; | ||
const decryptedItem = Object.assign({}, data); | ||
encryptedFieldNames.forEach(name => { | ||
const cipherText = encryptedItem[name]; | ||
const json = libsodium_wrappers_1.crypto_secretbox_open_easy(cipherText, nonce, decryptionKey); | ||
decryptedItem[name] = JSON.parse(libsodium_wrappers_1.to_string(json)); | ||
const { nonce, encryptedDataKey, encryptedFieldNames } = encryptedJayZItem.__jayz__metadata; | ||
const encryptedItem = Object.assign({}, encryptedJayZItem); | ||
delete encryptedItem.__jayz__metadata; | ||
const dataKey = yield this.keyProvider.decryptDataKey(encryptedDataKey); | ||
const { decryptedItem } = yield this.encryptor.decrypt({ | ||
encryptedItem, | ||
fieldsToDecrypt: encryptedFieldNames, | ||
dataKey, | ||
nonce | ||
}); | ||
libsodium_wrappers_1.memzero(dataKey); | ||
return decryptedItem; | ||
}); | ||
} | ||
toBuffer(value) { | ||
const json = fast_json_stable_stringify_1.default(value); | ||
return libsodium_wrappers_1.from_string(json); | ||
} | ||
generateEcryptionKey() { | ||
getDataKey() { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
yield libsodium_wrappers_1.ready; | ||
const { dataKey, encryptedDataKey } = yield this.keyProvider.generateDataKey(); | ||
const nonce = libsodium_wrappers_1.randombytes_buf(libsodium_wrappers_1.crypto_secretbox_NONCEBYTES); | ||
const derivedEncryptionKey = this.deriveEncryptionKey(dataKey, KeyType.ENCRYPTION); | ||
// scrub root key from memory since we're done with it | ||
libsodium_wrappers_1.memzero(dataKey); | ||
return { | ||
encryptedDataKey: encryptedDataKey, | ||
derivedEncryptionKey, | ||
nonce | ||
}; | ||
if (this.dataKey === undefined) { | ||
this.timesDataKeyUsed = 1; | ||
this.dataKey = yield this.keyProvider.generateDataKey(); | ||
} | ||
else if (this.timesDataKeyUsed >= this.maxUsesPerDataKey) { | ||
libsodium_wrappers_1.memzero(this.dataKey.dataKey); | ||
this.timesDataKeyUsed = 1; | ||
this.dataKey = yield this.keyProvider.generateDataKey(); | ||
} | ||
else { | ||
this.timesDataKeyUsed += 1; | ||
} | ||
return this.dataKey; | ||
}); | ||
} | ||
deriveEncryptionKey(dataKey, keyType) { | ||
const key = libsodium_wrappers_1.crypto_kdf_derive_from_key(libsodium_wrappers_1.crypto_secretbox_KEYBYTES, keyType, "__jayz__", // encryption context: must be 8 chars, per https://libsodium.gitbook.io/doc/key_derivation | ||
dataKey); | ||
return key; | ||
} | ||
} | ||
exports.JayZ = JayZ; | ||
//# sourceMappingURL=JayZ.js.map |
{ | ||
"name": "@ginger.io/jay-z", | ||
"version": "0.0.3", | ||
"version": "0.0.4", | ||
"description": "Youve got 99 problems, but application-layer encryption aint one", | ||
@@ -5,0 +5,0 @@ "main": "dist/index.js", |
@@ -26,3 +26,3 @@ # Jay-Z | ||
First install beyonce - `npm install @ginger.io/jay-z` | ||
First install jay-z - `npm install @ginger.io/jay-z` | ||
@@ -37,3 +37,3 @@ ### 2. Get yourself a JayZ | ||
const keyProvider = new KMSDataKeyProvider(kmsKeyId, new KMS()) | ||
const jayZ = new JayZ(keyProvider) | ||
const jayZ = new JayZ({ keyProvider }) | ||
``` | ||
@@ -80,4 +80,20 @@ | ||
## Reusing Data Keys | ||
By default, JayZ will request a fresh data key from its `DataKeyProvider` on every encryption operation. If you'd like to trade security for speed and/or cost - you can configure this with the `maxUsesPerDataKey` setting: | ||
```TypeScript | ||
const jayZ = new JayZ({ keyProvider: ..., maxUsesPerDataKey: 100 }) | ||
``` | ||
This would use each data key for 100 `encrypt` operations, before requesting a fresh key from the configured `DataKeyProvider`. | ||
## Design | ||
### Diagram | ||
![diagram.svg](diagram.svg) | ||
### Additional Details | ||
1. Every time you encrypt data, JayZ uses the passed `DataKeyProvider` to generate key material. For example, if you're using the `KMSDataKeyProvider` it will make an API call to [`generateDataKey`](https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/KMS.html#generateDataKey-property). | ||
@@ -84,0 +100,0 @@ |
export { DataKey, DataKeyProvider } from "./DataKeyProvider" | ||
export { ItemWithEncryptedFields, JayZ } from "./JayZ" | ||
export * from "./Encryptor" | ||
export { JayZ, JayZConfig } from "./JayZ" | ||
export { KMSDataKeyProvider } from "./KMSDataKeyProvider" | ||
export { LibsodiumEncryptor } from "./LibsodiumEncryptor" | ||
export { StubDataKeyProvider } from "./StubDataKeyProvider" | ||
export * from "./types" |
@@ -1,74 +0,45 @@ | ||
import stringify from "fast-json-stable-stringify" | ||
import { | ||
crypto_kdf_derive_from_key, | ||
crypto_secretbox_easy, | ||
crypto_secretbox_KEYBYTES, | ||
crypto_secretbox_NONCEBYTES, | ||
crypto_secretbox_open_easy, | ||
from_string, | ||
memzero, | ||
randombytes_buf, | ||
ready, | ||
to_string | ||
} from "libsodium-wrappers" | ||
import { DataKeyProvider } from "./DataKeyProvider" | ||
import { memzero } from "libsodium-wrappers" | ||
import { DataKey, DataKeyProvider } from "./DataKeyProvider" | ||
import { Encryptor } from "./Encryptor" | ||
import { LibsodiumEncryptor } from "./LibsodiumEncryptor" | ||
import { EncryptedItemMetadata, EncryptedJayZItem } from "./types" | ||
enum KeyType { | ||
ENCRYPTION = 1, | ||
SIGNING = 2 | ||
export type JayZConfig = { | ||
keyProvider: DataKeyProvider | ||
encryptor?: Encryptor | ||
maxUsesPerDataKey?: number | ||
} | ||
enum EncryptionVersion { | ||
V_0 // experimental | ||
} | ||
export class JayZ { | ||
private keyProvider: DataKeyProvider | ||
private encryptor: Encryptor = new LibsodiumEncryptor() | ||
type EncryptedItemMetadata<T, K extends keyof T> = { | ||
version: EncryptionVersion | ||
nonce: Uint8Array | ||
encryptedDataKey: Uint8Array | ||
encryptedFieldNames: K[] | ||
} | ||
private maxUsesPerDataKey: number | ||
private timesDataKeyUsed: number = 0 | ||
private dataKey?: DataKey | ||
type EncryptionMaterials = { | ||
encryptedDataKey: Uint8Array | ||
derivedEncryptionKey: Uint8Array | ||
nonce: Uint8Array | ||
} | ||
export type ItemWithEncryptedFields<T, K extends keyof T> = Omit<T, K> & | ||
{ | ||
[P in K]: Uint8Array | ||
} & { | ||
__jayz__metadata: EncryptedItemMetadata<T, K> | ||
constructor(config: JayZConfig) { | ||
this.keyProvider = config.keyProvider | ||
this.encryptor = | ||
config.encryptor !== undefined | ||
? config.encryptor | ||
: new LibsodiumEncryptor() | ||
this.maxUsesPerDataKey = config.maxUsesPerDataKey || 1 | ||
} | ||
export class JayZ { | ||
constructor(private keyProvider: DataKeyProvider) {} | ||
async encryptItem<T, K extends keyof T>( | ||
item: T, | ||
fieldsToEncrypt: K[] | ||
): Promise<ItemWithEncryptedFields<T, K>> { | ||
const { | ||
encryptedDataKey, | ||
derivedEncryptionKey, | ||
nonce | ||
} = await this.generateEcryptionKey() | ||
const encryptedFields: { [P in K]: Uint8Array } = {} as any | ||
fieldsToEncrypt.forEach(name => { | ||
encryptedFields[name] = crypto_secretbox_easy( | ||
this.toBuffer(item[name]), | ||
nonce, | ||
derivedEncryptionKey | ||
) | ||
): Promise<EncryptedJayZItem<T, K>> { | ||
const { dataKey, encryptedDataKey } = await this.getDataKey() | ||
const { encryptedItem, nonce } = await this.encryptor.encrypt({ | ||
item, | ||
fieldsToEncrypt, | ||
dataKey | ||
}) | ||
memzero(derivedEncryptionKey) | ||
const __jayz__metadata: EncryptedItemMetadata<T, K> = { | ||
encryptedDataKey, | ||
nonce, | ||
version: EncryptionVersion.V_0, | ||
scheme: this.encryptor.scheme, | ||
encryptedFieldNames: fieldsToEncrypt | ||
@@ -78,4 +49,3 @@ } | ||
return { | ||
...item, | ||
...encryptedFields, | ||
...encryptedItem, | ||
__jayz__metadata | ||
@@ -86,3 +56,3 @@ } | ||
async decryptItem<T, K extends keyof T>( | ||
encryptedItem: ItemWithEncryptedFields<T, K> | ||
encryptedJayZItem: EncryptedJayZItem<T, K> | ||
): Promise<T> { | ||
@@ -93,71 +63,34 @@ const { | ||
encryptedFieldNames | ||
} = encryptedItem.__jayz__metadata | ||
} = encryptedJayZItem.__jayz__metadata | ||
const decryptedDataKey = await this.keyProvider.decryptDataKey( | ||
encryptedDataKey | ||
) | ||
const encryptedItem = { ...encryptedJayZItem } | ||
delete encryptedItem.__jayz__metadata | ||
const decryptionKey = this.deriveEncryptionKey( | ||
decryptedDataKey, | ||
KeyType.ENCRYPTION | ||
) | ||
const dataKey = await this.keyProvider.decryptDataKey(encryptedDataKey) | ||
const data = { ...encryptedItem } | ||
delete data.__jayz__metadata | ||
const decryptedItem: { [P in keyof T]: T[P] } = { | ||
...data | ||
} as any | ||
encryptedFieldNames.forEach(name => { | ||
const cipherText = encryptedItem[name] | ||
const json = crypto_secretbox_open_easy(cipherText, nonce, decryptionKey) | ||
decryptedItem[name] = JSON.parse(to_string(json)) | ||
const { decryptedItem } = await this.encryptor.decrypt<T, K>({ | ||
encryptedItem, | ||
fieldsToDecrypt: encryptedFieldNames, | ||
dataKey, | ||
nonce | ||
}) | ||
memzero(dataKey) | ||
return decryptedItem | ||
} | ||
private toBuffer(value: any): Uint8Array { | ||
const json = stringify(value) | ||
return from_string(json) | ||
} | ||
private async generateEcryptionKey(): Promise<EncryptionMaterials> { | ||
await ready | ||
const { | ||
dataKey, | ||
encryptedDataKey | ||
} = await this.keyProvider.generateDataKey() | ||
const nonce = randombytes_buf(crypto_secretbox_NONCEBYTES) | ||
const derivedEncryptionKey = this.deriveEncryptionKey( | ||
dataKey, | ||
KeyType.ENCRYPTION | ||
) | ||
// scrub root key from memory since we're done with it | ||
memzero(dataKey) | ||
return { | ||
encryptedDataKey: encryptedDataKey, | ||
derivedEncryptionKey, | ||
nonce | ||
private async getDataKey(): Promise<DataKey> { | ||
if (this.dataKey === undefined) { | ||
this.timesDataKeyUsed = 1 | ||
this.dataKey = await this.keyProvider.generateDataKey() | ||
} else if (this.timesDataKeyUsed >= this.maxUsesPerDataKey) { | ||
memzero(this.dataKey.dataKey) | ||
this.timesDataKeyUsed = 1 | ||
this.dataKey = await this.keyProvider.generateDataKey() | ||
} else { | ||
this.timesDataKeyUsed += 1 | ||
} | ||
} | ||
private deriveEncryptionKey( | ||
dataKey: Uint8Array, | ||
keyType: KeyType | ||
): Uint8Array { | ||
const key = crypto_kdf_derive_from_key( | ||
crypto_secretbox_KEYBYTES, | ||
keyType, | ||
"__jayz__", // encryption context: must be 8 chars, per https://libsodium.gitbook.io/doc/key_derivation | ||
dataKey | ||
) | ||
return key | ||
return this.dataKey | ||
} | ||
} |
@@ -7,27 +7,20 @@ import { | ||
} from "libsodium-wrappers" | ||
import { JayZ } from "../main/JayZ" | ||
import { DataKey, DataKeyProvider } from "../main/DataKeyProvider" | ||
import { JayZ, JayZConfig } from "../main/JayZ" | ||
import { StubDataKeyProvider } from "../main/StubDataKeyProvider" | ||
import { aBankAccount, BankAccount } from "./util" | ||
interface BankAccount { | ||
pk: string // "bank-account-123", | ||
sk: string // "Flava Flav", | ||
accountNumber: string | ||
routingNumber: string | ||
balance: number | ||
notes: { | ||
[key: string]: any | ||
} | ||
} | ||
describe("JayZ", () => { | ||
beforeAll(async () => await ready) | ||
const fieldsToEncrypt: (keyof BankAccount)[] = [ | ||
"accountNumber", | ||
"balance", | ||
"routingNumber", | ||
"notes" | ||
] | ||
it("should encrypt an item", async () => { | ||
const { jayz, bankAccount } = setup() | ||
const encryptedItem = await jayz.encryptItem(bankAccount, [ | ||
"accountNumber", | ||
"balance", | ||
"routingNumber", | ||
"notes" | ||
]) | ||
const encryptedItem = await jayz.encryptItem(bankAccount, fieldsToEncrypt) | ||
@@ -46,34 +39,71 @@ expect(encryptedItem.pk).toEqual("account-123") | ||
const { jayz, bankAccount } = setup() | ||
const encryptedItem = await jayz.encryptItem(bankAccount, [ | ||
"accountNumber", | ||
"balance", | ||
"routingNumber", | ||
"notes" | ||
]) | ||
const encryptedItem = await jayz.encryptItem(bankAccount, fieldsToEncrypt) | ||
const decryptedItem = await jayz.decryptItem(encryptedItem) | ||
expect(decryptedItem).toEqual(bankAccount) | ||
}) | ||
it("should not reuse data keys by default", async () => { | ||
const keyProvider = new CountingKeyProvider() | ||
const { jayz, bankAccount } = setup({ | ||
keyProvider | ||
}) | ||
expect(keyProvider.keysIssued).toEqual(0) | ||
await jayz.encryptItem(bankAccount, fieldsToEncrypt) | ||
expect(keyProvider.keysIssued).toEqual(1) | ||
await jayz.encryptItem(bankAccount, fieldsToEncrypt) | ||
expect(keyProvider.keysIssued).toEqual(2) | ||
}) | ||
it("should reuse data keys when configured to do so", async () => { | ||
const keyProvider = new CountingKeyProvider() | ||
const { jayz, bankAccount } = setup({ | ||
keyProvider, | ||
maxUsesPerDataKey: 2 | ||
}) | ||
const item1 = await jayz.encryptItem(bankAccount, fieldsToEncrypt) | ||
expect(keyProvider.keysIssued).toEqual(1) | ||
const item2 = await jayz.encryptItem(bankAccount, fieldsToEncrypt) | ||
expect(keyProvider.keysIssued).toEqual(1) | ||
const item3 = await jayz.encryptItem(bankAccount, fieldsToEncrypt) | ||
expect(keyProvider.keysIssued).toEqual(2) | ||
expect(await jayz.decryptItem(item1)).toEqual(bankAccount) | ||
expect(await jayz.decryptItem(item2)).toEqual(bankAccount) | ||
expect(await jayz.decryptItem(item3)).toEqual(bankAccount) | ||
}) | ||
}) | ||
function setup(): { bankAccount: BankAccount; jayz: JayZ } { | ||
const key = randombytes_buf(crypto_kdf_KEYBYTES) | ||
const provider = new StubDataKeyProvider(to_base64(key)) | ||
function setup( | ||
config: JayZConfig = { | ||
keyProvider: new StubDataKeyProvider( | ||
to_base64(randombytes_buf(crypto_kdf_KEYBYTES)) | ||
) | ||
} | ||
): { bankAccount: BankAccount; jayz: JayZ } { | ||
const bankAccount = aBankAccount() | ||
const jayz = new JayZ(provider) | ||
const jayz = new JayZ(config) | ||
return { jayz, bankAccount } | ||
} | ||
function aBankAccount(): BankAccount { | ||
return { | ||
pk: "account-123", | ||
sk: "Flava Flav", | ||
accountNumber: "123", | ||
routingNumber: "456", | ||
balance: 100, | ||
notes: { | ||
previousBalances: [0, 50] | ||
class CountingKeyProvider implements DataKeyProvider { | ||
public keysIssued = 0 | ||
async generateDataKey(): Promise<DataKey> { | ||
await ready | ||
const key = randombytes_buf(crypto_kdf_KEYBYTES) | ||
this.keysIssued += 1 | ||
return { | ||
encryptedDataKey: key.slice(0), | ||
dataKey: key.slice(0) | ||
} | ||
} | ||
async decryptDataKey(encryptedDataKey: Uint8Array): Promise<Uint8Array> { | ||
return encryptedDataKey.slice(0) | ||
} | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
112341
45
863
105
1