@cloudbase/cloud-api
Advanced tools
+2
-2
| module.exports = { | ||
| extends: ['alloy', 'alloy/typescript'], | ||
| rules: { | ||
| semi: ['error'], | ||
| indent: ['error', 4], | ||
| semi: ['error', 'never'], | ||
| complexity: ['error', { max: 40 }], | ||
@@ -26,2 +26,2 @@ 'no-useless-constructor': 'off', | ||
| ] | ||
| } | ||
| }; |
+2
-3
@@ -8,4 +8,3 @@ module.exports = { | ||
| useTabs: false, | ||
| // 行尾需要有分号 | ||
| semi: false, | ||
| semi: true, | ||
| // 使用单引号 | ||
@@ -30,2 +29,2 @@ singleQuote: true, | ||
| endOfLine: 'lf' | ||
| } | ||
| }; |
+1
-1
@@ -17,2 +17,2 @@ module.exports = { | ||
| coverageReporters: ['json', 'lcov', 'clover', 'text-summary'] | ||
| } | ||
| }; |
+19
-4
@@ -111,4 +111,19 @@ "use strict"; | ||
| } | ||
| async request(action, data = {}, method = 'POST') { | ||
| async request(actionOrOptions, assignData = {}, assignMethod = 'POST') { | ||
| var _a; | ||
| let action; | ||
| let data; | ||
| let method; | ||
| let region; | ||
| if (typeof actionOrOptions === 'string') { | ||
| action = actionOrOptions; | ||
| data = assignData; | ||
| method = assignMethod; | ||
| } | ||
| else { | ||
| action = actionOrOptions === null || actionOrOptions === void 0 ? void 0 : actionOrOptions.action; | ||
| data = (actionOrOptions === null || actionOrOptions === void 0 ? void 0 : actionOrOptions.data) || {}; | ||
| method = (actionOrOptions === null || actionOrOptions === void 0 ? void 0 : actionOrOptions.method) || 'POST'; | ||
| region = actionOrOptions === null || actionOrOptions === void 0 ? void 0 : actionOrOptions.region; | ||
| } | ||
| this.action = action; | ||
@@ -132,3 +147,3 @@ this.data = deepRemoveVoid(Object.assign(Object.assign({}, data), this.baseParams)); | ||
| try { | ||
| const data = await this.requestWithSign(); | ||
| const data = await this.requestWithSign(region); | ||
| if (data.Response.Error) { | ||
@@ -160,3 +175,3 @@ const tcError = new error_1.CloudBaseError(data.Response.Error.Message, { | ||
| } | ||
| async requestWithSign() { | ||
| async requestWithSign(region) { | ||
| const timestamp = Math.floor(Date.now() / 1000); | ||
@@ -183,3 +198,3 @@ const { method, timeout, data } = this; | ||
| 'X-TC-Action': this.action, | ||
| 'X-TC-Region': this.region, | ||
| 'X-TC-Region': region || this.region, | ||
| 'X-TC-Timestamp': timestamp, | ||
@@ -186,0 +201,0 @@ 'X-TC-Version': this.version |
+1
-1
| { | ||
| "name": "@cloudbase/cloud-api", | ||
| "version": "0.3.1", | ||
| "version": "0.3.2", | ||
| "description": "The cloud api request package.", | ||
@@ -5,0 +5,0 @@ "main": "lib/index.js", |
+22
-22
| interface Options { | ||
| exit?: number | ||
| original?: Error | undefined | ||
| code?: string | number | ||
| requestId?: string | ||
| action?: string | ||
| type?: string | ||
| exit?: number; | ||
| original?: Error | undefined; | ||
| code?: string | number; | ||
| requestId?: string; | ||
| action?: string; | ||
| type?: string; | ||
| } | ||
| export class CloudBaseError extends Error { | ||
| readonly exit: number | ||
| readonly message: string | ||
| readonly name = 'CloudBaseError' | ||
| readonly original: Error | undefined | ||
| readonly code: string | number | ||
| readonly requestId: string | ||
| readonly action: string | ||
| readonly type: string | ||
| readonly exit: number; | ||
| readonly message: string; | ||
| readonly name = 'CloudBaseError'; | ||
| readonly original: Error | undefined; | ||
| readonly code: string | number; | ||
| readonly requestId: string; | ||
| readonly action: string; | ||
| readonly type: string; | ||
| constructor(message: string, options: Options = {}) { | ||
| super() | ||
| const { code = '', action = '', original = null, requestId = '', type } = options | ||
| this.message = `[${action}]\nRequestId:${requestId}\n${message}` | ||
| this.original = original | ||
| this.code = code | ||
| this.requestId = requestId | ||
| this.action = action | ||
| this.type = type | ||
| super(); | ||
| const { code = '', action = '', original = null, requestId = '', type } = options; | ||
| this.message = `[${action}]\nRequestId:${requestId}\n${message}`; | ||
| this.original = original; | ||
| this.code = code; | ||
| this.requestId = requestId; | ||
| this.action = action; | ||
| this.type = type; | ||
| } | ||
| } |
+141
-141
@@ -1,10 +0,10 @@ | ||
| import crypto from 'crypto' | ||
| import { URL } from 'url' | ||
| import QueryString from 'query-string' | ||
| import crypto from 'crypto'; | ||
| import { URL } from 'url'; | ||
| import QueryString from 'query-string'; | ||
| import { fetch as _fetch, fetchStream as _fetchStream, nodeFetch as _nodeFetch } from './request' | ||
| import { CloudBaseError } from './error' | ||
| import { fetch as _fetch, fetchStream as _fetchStream, nodeFetch as _nodeFetch } from './request'; | ||
| import { CloudBaseError } from './error'; | ||
| function isObject(x) { | ||
| return typeof x === 'object' && !Array.isArray(x) && x !== null | ||
| return typeof x === 'object' && !Array.isArray(x) && x !== null; | ||
| } | ||
@@ -15,38 +15,38 @@ | ||
| if (Array.isArray(obj)) { | ||
| return obj.map(deepRemoveVoid) | ||
| return obj.map(deepRemoveVoid); | ||
| } else if (isObject(obj)) { | ||
| let result = {} | ||
| let result = {}; | ||
| for (const key in obj) { | ||
| if (Object.prototype.hasOwnProperty.call(obj, key)) { | ||
| const value = obj[key] | ||
| const value = obj[key]; | ||
| if (typeof value !== 'undefined' && value !== null) { | ||
| result[key] = deepRemoveVoid(value) | ||
| result[key] = deepRemoveVoid(value); | ||
| } | ||
| } | ||
| } | ||
| return result | ||
| return result; | ||
| } else { | ||
| return obj | ||
| return obj; | ||
| } | ||
| } | ||
| type HexBase64Latin1Encoding = 'latin1' | 'hex' | 'base64' | ||
| type HexBase64Latin1Encoding = 'latin1' | 'hex' | 'base64'; | ||
| function sha256(message: string, secret: string, encoding?: HexBase64Latin1Encoding) { | ||
| const hmac = crypto.createHmac('sha256', secret) | ||
| return hmac.update(message).digest(encoding) | ||
| const hmac = crypto.createHmac('sha256', secret); | ||
| return hmac.update(message).digest(encoding); | ||
| } | ||
| function getHash(message: string): string { | ||
| const hash = crypto.createHash('sha256') | ||
| return hash.update(message).digest('hex') | ||
| const hash = crypto.createHash('sha256'); | ||
| return hash.update(message).digest('hex'); | ||
| } | ||
| function getDate(timestamp: number): string { | ||
| const date = new Date(timestamp * 1000) | ||
| const year = date.getUTCFullYear() | ||
| const month = ('0' + (date.getUTCMonth() + 1)).slice(-2) | ||
| const date = new Date(timestamp * 1000); | ||
| const year = date.getUTCFullYear(); | ||
| const month = ('0' + (date.getUTCMonth() + 1)).slice(-2); | ||
| // UTC 日期,非本地时间 | ||
| const day = ('0' + date.getUTCDate()).slice(-2) | ||
| return `${year}-${month}-${day}` | ||
| const day = ('0' + date.getUTCDate()).slice(-2); | ||
| return `${year}-${month}-${day}`; | ||
| } | ||
@@ -61,69 +61,69 @@ | ||
| ssl: '2019-12-05' | ||
| } | ||
| }; | ||
| export interface ServiceOptions { | ||
| service: string | ||
| version?: string | ||
| proxy?: string | ||
| timeout?: number | ||
| region?: string | ||
| baseParams?: Record<string, any> | ||
| credential?: Credential | ||
| getCredential?: () => Promise<Credential> | Credential | ||
| service: string; | ||
| version?: string; | ||
| proxy?: string; | ||
| timeout?: number; | ||
| region?: string; | ||
| baseParams?: Record<string, any>; | ||
| credential?: Credential; | ||
| getCredential?: () => Promise<Credential> | Credential; | ||
| } | ||
| export interface Credential { | ||
| secretId: string | ||
| secretKey: string | ||
| token?: string | ||
| secretId: string; | ||
| secretKey: string; | ||
| token?: string; | ||
| } | ||
| export interface RequestOptions { | ||
| action: string | ||
| data?: Record<string, any> | ||
| method?: 'POST' | 'GET' | ||
| region?: string | ||
| action: string; | ||
| data?: Record<string, any>; | ||
| method?: 'POST' | 'GET'; | ||
| region?: string; | ||
| } | ||
| export const fetch = _fetch | ||
| export const fetchStream = _fetchStream | ||
| export const nodeFetch = _nodeFetch | ||
| export const fetch = _fetch; | ||
| export const fetchStream = _fetchStream; | ||
| export const nodeFetch = _nodeFetch; | ||
| export class CloudApiService { | ||
| // 缓存请求实例 | ||
| static serviceCacheMap: Record<string, CloudApiService> = {} | ||
| static serviceCacheMap: Record<string, CloudApiService> = {}; | ||
| static getInstance(options: ServiceOptions) { | ||
| const { service } = options | ||
| const { service } = options; | ||
| if (CloudApiService.serviceCacheMap?.[service]) { | ||
| return CloudApiService.serviceCacheMap[service] | ||
| return CloudApiService.serviceCacheMap[service]; | ||
| } | ||
| const apiService = new CloudApiService(options) | ||
| const apiService = new CloudApiService(options); | ||
| // 预防 serviceCacheMap 被置空导致的错误 | ||
| CloudApiService.serviceCacheMap = { | ||
| ...CloudApiService.serviceCacheMap | ||
| } | ||
| CloudApiService.serviceCacheMap[service] = apiService | ||
| return apiService | ||
| }; | ||
| CloudApiService.serviceCacheMap[service] = apiService; | ||
| return apiService; | ||
| } | ||
| service: string | ||
| version: string | ||
| proxy: string | ||
| timeout: number | ||
| region: string | ||
| credential: Credential | ||
| baseParams: Record<string, any> | ||
| getCredential: () => Promise<Credential> | Credential | ||
| service: string; | ||
| version: string; | ||
| proxy: string; | ||
| timeout: number; | ||
| region: string; | ||
| credential: Credential; | ||
| baseParams: Record<string, any>; | ||
| getCredential: () => Promise<Credential> | Credential; | ||
| url: string | ||
| host: string | ||
| action: string | ||
| method: 'POST' | 'GET' | ||
| data: Record<string, any> | ||
| payload: Record<string, any> | ||
| url: string; | ||
| host: string; | ||
| action: string; | ||
| method: 'POST' | 'GET'; | ||
| data: Record<string, any>; | ||
| payload: Record<string, any>; | ||
| constructor(options: ServiceOptions) { | ||
| if (!options) { | ||
| throw new CloudBaseError('Options cloud not be empty!') | ||
| throw new CloudBaseError('Options cloud not be empty!'); | ||
| } | ||
@@ -139,23 +139,23 @@ const { | ||
| timeout = 60000 | ||
| } = options | ||
| } = options; | ||
| this.service = service | ||
| this.timeout = timeout | ||
| this.service = service; | ||
| this.timeout = timeout; | ||
| if (this.service === 'tcb' && process.env.CLOUDBASE_TCB_CLOUDAPI_PROXY) { | ||
| this.proxy = process.env.CLOUDBASE_TCB_CLOUDAPI_PROXY | ||
| this.proxy = process.env.CLOUDBASE_TCB_CLOUDAPI_PROXY; | ||
| } else { | ||
| this.proxy = proxy | ||
| this.proxy = proxy; | ||
| } | ||
| if (this.service === 'tcb' && process.env.CLOUDBASE_TCB_CLOUDAPI_REGION) { | ||
| this.region = process.env.CLOUDBASE_TCB_CLOUDAPI_REGION | ||
| this.region = process.env.CLOUDBASE_TCB_CLOUDAPI_REGION; | ||
| } else { | ||
| this.region = region || process.env.TENCENTCLOUD_REGION || 'ap-shanghai' | ||
| this.region = region || process.env.TENCENTCLOUD_REGION || 'ap-shanghai'; | ||
| } | ||
| this.credential = credential | ||
| this.baseParams = baseParams || {} | ||
| this.getCredential = getCredential | ||
| this.version = ServiceVersionMap[service] || version | ||
| this.credential = credential; | ||
| this.baseParams = baseParams || {}; | ||
| this.getCredential = getCredential; | ||
| this.version = ServiceVersionMap[service] || version; | ||
| } | ||
@@ -167,12 +167,12 @@ | ||
| flexdb: 'https://flexdb.tencentcloudapi.com' | ||
| } | ||
| }; | ||
| if (this.service === 'tcb' && process.env.CLOUDBASE_TCB_CLOUDAPI_HOST) { | ||
| return `http://${process.env.CLOUDBASE_TCB_CLOUDAPI_HOST}` | ||
| return `http://${process.env.CLOUDBASE_TCB_CLOUDAPI_HOST}`; | ||
| } | ||
| if (urlMap[this.service]) { | ||
| return urlMap[this.service] | ||
| return urlMap[this.service]; | ||
| } else { | ||
| return `https://${this.service}.tencentcloudapi.com` | ||
| return `https://${this.service}.tencentcloudapi.com`; | ||
| } | ||
@@ -182,4 +182,4 @@ } | ||
| // overload | ||
| async request(options: RequestOptions) | ||
| async request(action: string, data?: Record<string, any>, method?: 'POST' | 'GET') | ||
| async request(options: RequestOptions); | ||
| async request(action: string, data?: Record<string, any>, method?: 'POST' | 'GET'); | ||
@@ -192,42 +192,42 @@ async request( | ||
| // 增加 region 参数,兼容之前的入参形式 | ||
| let action | ||
| let data | ||
| let method | ||
| let region | ||
| let action; | ||
| let data; | ||
| let method; | ||
| let region; | ||
| if (typeof actionOrOptions === 'string') { | ||
| action = actionOrOptions | ||
| data = assignData | ||
| method = assignMethod | ||
| action = actionOrOptions; | ||
| data = assignData; | ||
| method = assignMethod; | ||
| } else { | ||
| action = actionOrOptions?.action | ||
| data = actionOrOptions?.data || {} | ||
| method = actionOrOptions?.method || 'POST' | ||
| region = actionOrOptions?.region | ||
| action = actionOrOptions?.action; | ||
| data = actionOrOptions?.data || {}; | ||
| method = actionOrOptions?.method || 'POST'; | ||
| region = actionOrOptions?.region; | ||
| } | ||
| this.action = action | ||
| this.data = deepRemoveVoid({ ...data, ...this.baseParams }) | ||
| this.method = method | ||
| this.action = action; | ||
| this.data = deepRemoveVoid({ ...data, ...this.baseParams }); | ||
| this.method = method; | ||
| this.url = this.baseUrl | ||
| this.url = this.baseUrl; | ||
| if (!this.credential?.secretId) { | ||
| if (!this.getCredential) { | ||
| throw new CloudBaseError('You must provide credential info!') | ||
| throw new CloudBaseError('You must provide credential info!'); | ||
| } | ||
| if (typeof this.getCredential !== 'function') { | ||
| throw new CloudBaseError('The getCredential option must be a function!') | ||
| throw new CloudBaseError('The getCredential option must be a function!'); | ||
| } | ||
| const credential = await this.getCredential() | ||
| const credential = await this.getCredential(); | ||
| if (!credential) { | ||
| throw new CloudBaseError('Calling getCredential function get no credential info!') | ||
| throw new CloudBaseError('Calling getCredential function get no credential info!'); | ||
| } | ||
| this.credential = credential | ||
| this.credential = credential; | ||
| } | ||
| try { | ||
| const data: Record<string, any> = await this.requestWithSign(region) | ||
| const data: Record<string, any> = await this.requestWithSign(region); | ||
@@ -240,6 +240,6 @@ if (data.Response.Error) { | ||
| original: data.Response.Error | ||
| }) | ||
| throw tcError | ||
| }); | ||
| throw tcError; | ||
| } else { | ||
| return data.Response | ||
| return data.Response; | ||
| } | ||
@@ -249,3 +249,3 @@ } catch (e) { | ||
| if (e.name === 'CloudBaseError') { | ||
| throw e | ||
| throw e; | ||
| } else { | ||
@@ -256,3 +256,3 @@ throw new CloudBaseError(e.message, { | ||
| type: e.type | ||
| }) | ||
| }); | ||
| } | ||
@@ -266,20 +266,20 @@ } | ||
| // await convertReadStreamToBuffer(data) | ||
| const timestamp = Math.floor(Date.now() / 1000) | ||
| const timestamp = Math.floor(Date.now() / 1000); | ||
| const { method, timeout, data } = this | ||
| const { method, timeout, data } = this; | ||
| if (method === 'GET') { | ||
| this.url += '?' + QueryString.stringify(data) | ||
| this.url += '?' + QueryString.stringify(data); | ||
| } | ||
| if (method === 'POST') { | ||
| this.payload = data | ||
| this.payload = data; | ||
| } | ||
| const { CLOUDBASE_TCB_CLOUDAPI_HOST } = process.env | ||
| const { CLOUDBASE_TCB_CLOUDAPI_HOST } = process.env; | ||
| if (this.service === 'tcb' && CLOUDBASE_TCB_CLOUDAPI_HOST) { | ||
| this.host = CLOUDBASE_TCB_CLOUDAPI_HOST | ||
| this.host = CLOUDBASE_TCB_CLOUDAPI_HOST; | ||
| } else { | ||
| this.host = new URL(this.url).host | ||
| this.host = new URL(this.url).host; | ||
| } | ||
@@ -297,65 +297,65 @@ | ||
| } | ||
| } | ||
| }; | ||
| if (this.credential.token) { | ||
| config.headers['X-TC-Token'] = this.credential.token | ||
| config.headers['X-TC-Token'] = this.credential.token; | ||
| } | ||
| if (method === 'GET') { | ||
| config.headers['Content-Type'] = 'application/x-www-form-urlencoded' | ||
| config.headers['Content-Type'] = 'application/x-www-form-urlencoded'; | ||
| } | ||
| if (method === 'POST') { | ||
| config.body = JSON.stringify(data) | ||
| config.headers['Content-Type'] = 'application/json' | ||
| config.body = JSON.stringify(data); | ||
| config.headers['Content-Type'] = 'application/json'; | ||
| } | ||
| const sign = this.getRequestSign(timestamp) | ||
| const sign = this.getRequestSign(timestamp); | ||
| config.headers['Authorization'] = sign | ||
| config.headers['Authorization'] = sign; | ||
| return fetch(this.url, config, this.proxy) | ||
| return fetch(this.url, config, this.proxy); | ||
| } | ||
| getRequestSign(timestamp: number) { | ||
| const { method, url, service } = this | ||
| const { secretId, secretKey } = this.credential | ||
| const urlObj = new URL(url) | ||
| const { method, url, service } = this; | ||
| const { secretId, secretKey } = this.credential; | ||
| const urlObj = new URL(url); | ||
| // 通用头部 | ||
| let headers = '' | ||
| const signedHeaders = 'content-type;host' | ||
| let headers = ''; | ||
| const signedHeaders = 'content-type;host'; | ||
| if (method === 'GET') { | ||
| headers = 'content-type:application/x-www-form-urlencoded\n' | ||
| headers = 'content-type:application/x-www-form-urlencoded\n'; | ||
| } | ||
| if (method === 'POST') { | ||
| headers = 'content-type:application/json\n' | ||
| headers = 'content-type:application/json\n'; | ||
| } | ||
| headers += `host:${this.host}\n` | ||
| headers += `host:${this.host}\n`; | ||
| const path = urlObj.pathname | ||
| const querystring = urlObj.search.slice(1) | ||
| const path = urlObj.pathname; | ||
| const querystring = urlObj.search.slice(1); | ||
| const payloadHash = this.payload ? getHash(JSON.stringify(this.payload)) : getHash('') | ||
| const payloadHash = this.payload ? getHash(JSON.stringify(this.payload)) : getHash(''); | ||
| const canonicalRequest = `${method}\n${path}\n${querystring}\n${headers}\n${signedHeaders}\n${payloadHash}` | ||
| const canonicalRequest = `${method}\n${path}\n${querystring}\n${headers}\n${signedHeaders}\n${payloadHash}`; | ||
| const date = getDate(timestamp) | ||
| const date = getDate(timestamp); | ||
| const StringToSign = `TC3-HMAC-SHA256\n${timestamp}\n${date}/${service}/tc3_request\n${getHash( | ||
| canonicalRequest | ||
| )}` | ||
| )}`; | ||
| const kDate = sha256(date, `TC3${secretKey}`) | ||
| const kService = sha256(service, kDate) | ||
| const kSigning = sha256('tc3_request', kService) | ||
| const signature = sha256(StringToSign, kSigning, 'hex') | ||
| const kDate = sha256(date, `TC3${secretKey}`); | ||
| const kService = sha256(service, kDate); | ||
| const kSigning = sha256('tc3_request', kService); | ||
| const signature = sha256(StringToSign, kSigning, 'hex'); | ||
| return `TC3-HMAC-SHA256 Credential=${secretId}/${date}/${service}/tc3_request, SignedHeaders=${signedHeaders}, Signature=${signature}` | ||
| return `TC3-HMAC-SHA256 Credential=${secretId}/${date}/${service}/tc3_request, SignedHeaders=${signedHeaders}, Signature=${signature}`; | ||
| } | ||
| clearCredentialCache() { | ||
| this.credential = null | ||
| this.credential = null; | ||
| } | ||
| } |
+11
-11
@@ -1,6 +0,6 @@ | ||
| import { URL } from 'url' | ||
| import _fetch, { RequestInit } from 'node-fetch' | ||
| import HttpsProxyAgent from 'https-proxy-agent' | ||
| import { URL } from 'url'; | ||
| import _fetch, { RequestInit } from 'node-fetch'; | ||
| import HttpsProxyAgent from 'https-proxy-agent'; | ||
| export const nodeFetch = _fetch | ||
| export const nodeFetch = _fetch; | ||
@@ -10,10 +10,10 @@ // 使用 fetch + 代理 | ||
| if (proxy) { | ||
| config.agent = HttpsProxyAgent(proxy) | ||
| config.agent = HttpsProxyAgent(proxy); | ||
| } | ||
| // 解决中文编码问题 | ||
| const escapeUrl = new URL(url).toString() | ||
| const escapeUrl = new URL(url).toString(); | ||
| const res = await _fetch(escapeUrl, config) | ||
| return res.json() | ||
| const res = await _fetch(escapeUrl, config); | ||
| return res.json(); | ||
| } | ||
@@ -23,8 +23,8 @@ | ||
| if (proxy) { | ||
| config.agent = HttpsProxyAgent(proxy) | ||
| config.agent = HttpsProxyAgent(proxy); | ||
| } | ||
| const escapeUrl = new URL(url).toString() | ||
| const escapeUrl = new URL(url).toString(); | ||
| return _fetch(escapeUrl, config) | ||
| return _fetch(escapeUrl, config); | ||
| } |
+9
-2
@@ -17,2 +17,8 @@ import { fetch as _fetch, fetchStream as _fetchStream } from './request'; | ||
| } | ||
| export interface RequestOptions { | ||
| action: string; | ||
| data?: Record<string, any>; | ||
| method?: 'POST' | 'GET'; | ||
| region?: string; | ||
| } | ||
| export declare const fetch: typeof _fetch; | ||
@@ -40,6 +46,7 @@ export declare const fetchStream: typeof _fetchStream; | ||
| get baseUrl(): any; | ||
| request(action: string, data?: Record<string, any>, method?: 'POST' | 'GET'): Promise<any>; | ||
| requestWithSign(): Promise<any>; | ||
| request(options: RequestOptions): any; | ||
| request(action: string, data?: Record<string, any>, method?: 'POST' | 'GET'): any; | ||
| requestWithSign(region: any): Promise<any>; | ||
| getRequestSign(timestamp: number): string; | ||
| clearCredentialCache(): void; | ||
| } |
Network access
Supply chain riskThis module accesses the network.
Found 2 instances in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 5 instances in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 2 instances in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 5 instances in 1 package
33929
3.33%803
2.69%