diff --git a/.config/rollup.test.config.mjs b/.config/rollup.test.config.mjs index c29d9a4ef..5bf4339ea 100644 --- a/.config/rollup.test.config.mjs +++ b/.config/rollup.test.config.mjs @@ -18,7 +18,6 @@ export default () => { const { rootSrcPath } = constants return baseConfig({ input: { - 'alert-rules': `${rootSrcPath}/utils/alert/rules.ts`, errors: `${rootSrcPath}/utils/errors.ts`, 'path-resolve': `${rootSrcPath}/utils/path-resolve.ts` }, diff --git a/.dep-stats.json b/.dep-stats.json index 52e7e5536..2d2fd89c3 100644 --- a/.dep-stats.json +++ b/.dep-stats.json @@ -10,7 +10,7 @@ "@socketregistry/packageurl-js": "1.0.4", "@socketsecurity/config": "2.1.3", "@socketsecurity/registry": "1.0.135", - "@socketsecurity/sdk": "1.4.16", + "@socketsecurity/sdk": "1.4.17", "blessed": "0.1.81", "blessed-contrib": "4.11.0", "browserslist": "4.24.4", diff --git a/package-lock.json b/package-lock.json index 6401d74e3..e532bbf21 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,7 +19,7 @@ "@socketregistry/packageurl-js": "1.0.4", "@socketsecurity/config": "2.1.3", "@socketsecurity/registry": "1.0.135", - "@socketsecurity/sdk": "1.4.16", + "@socketsecurity/sdk": "1.4.17", "blessed": "0.1.81", "blessed-contrib": "4.11.0", "browserslist": "4.24.4", @@ -5214,9 +5214,9 @@ } }, "node_modules/@socketsecurity/sdk": { - "version": "1.4.16", - "resolved": "https://registry.npmjs.org/@socketsecurity/sdk/-/sdk-1.4.16.tgz", - "integrity": "sha512-Wd0wbwQUe63s8Y+4meYkeO8rFXzSqVpFTm0YNb4fimtHOWhVC+24a7oLoxRmgjRaQMqhDgQIcj60s1Gr+5MFPg==", + "version": "1.4.17", + "resolved": "https://registry.npmjs.org/@socketsecurity/sdk/-/sdk-1.4.17.tgz", + "integrity": "sha512-hiSSDTVLOfeMLUH+hgwG5c/N3e17gPJlrgRkHjucmsLd+1I0B2bwnhrR4yr/NeiJGKkaCRjf1ng714JnoFZsKA==", "license": "MIT", "dependencies": { "@socketsecurity/registry": "1.0.135" diff --git a/package.json b/package.json index 13d59d299..8c7d8aa69 100644 --- a/package.json +++ b/package.json @@ -81,7 +81,7 @@ "@socketregistry/packageurl-js": "1.0.4", "@socketsecurity/config": "2.1.3", "@socketsecurity/registry": "1.0.135", - "@socketsecurity/sdk": "1.4.16", + "@socketsecurity/sdk": "1.4.17", "blessed": "0.1.81", "blessed-contrib": "4.11.0", "browserslist": "4.24.4", diff --git a/src/utils/alert/artifact.ts b/src/utils/alert/artifact.ts index 3d745f2ab..febaec8d9 100755 --- a/src/utils/alert/artifact.ts +++ b/src/utils/alert/artifact.ts @@ -29,10 +29,7 @@ export type ArtifactAlertUpgrade = Remap< export type CveAlertType = 'cve' | 'mediumCVE' | 'mildCVE' | 'criticalCVE' export type CompactSocketArtifactAlert = Remap< - Omit< - SocketArtifactAlert, - 'action' | 'actionPolicyIndex' | 'category' | 'end' | 'file' | 'start' - > + Omit > export type CompactSocketArtifact = Remap< diff --git a/src/utils/alert/rules.ts b/src/utils/alert/rules.ts deleted file mode 100644 index 85d6cb8bc..000000000 --- a/src/utils/alert/rules.ts +++ /dev/null @@ -1,262 +0,0 @@ -import { isObject } from '@socketsecurity/registry/lib/objects' - -import { findSocketYmlSync, getConfigValue } from '../config' -import { isErrnoException } from '../errors' -import { getPublicToken, setupSdk } from '../sdk' - -import type { SocketSdkResultType } from '@socketsecurity/sdk' - -type AlertUxLookup = ReturnType - -type AlertUxLookupSettings = Parameters[0] - -type AlertUxLookupResult = ReturnType - -type NonNormalizedRule = - | NonNullable< - NonNullable< - NonNullable< - (SocketSdkResultType<'postSettings'> & { - success: true - })['data']['entries'][number]['settings'][string] - >['issueRules'] - > - >[string] - | boolean - -type NonNormalizedResolvedRule = - | (NonNullable< - NonNullable< - (SocketSdkResultType<'postSettings'> & { - success: true - })['data']['defaults']['issueRules'] - >[string] - > & { action: string }) - | boolean - -type RuleActionUX = { block: boolean; display: boolean } - -const ERROR_UX: RuleActionUX = { - block: true, - display: true -} - -const IGNORE_UX: RuleActionUX = { - block: false, - display: false -} - -const WARN_UX: RuleActionUX = { - block: false, - display: true -} - -// Iterates over all entries with ordered issue rule for deferral. Iterates over -// all issue rules and finds the first defined value that does not defer otherwise -// uses the defaultValue. Takes the value and converts into a UX workflow. -function resolveAlertRuleUX( - orderedRulesCollection: Iterable>, - defaultValue: NonNormalizedResolvedRule -): RuleActionUX { - if ( - defaultValue === true || - defaultValue === null || - defaultValue === undefined - ) { - defaultValue = { action: 'error' } - } else if (defaultValue === false) { - defaultValue = { action: 'ignore' } - } - let block = false - let display = false - let needDefault = true - iterate_entries: for (const rules of orderedRulesCollection) { - for (const rule of rules) { - if (ruleValueDoesNotDefer(rule)) { - needDefault = false - const narrowingFilter = uxForDefinedNonDeferValue(rule) - block = block || narrowingFilter.block - display = display || narrowingFilter.display - continue iterate_entries - } - } - const narrowingFilter = uxForDefinedNonDeferValue(defaultValue) - block = block || narrowingFilter.block - display = display || narrowingFilter.display - } - if (needDefault) { - const narrowingFilter = uxForDefinedNonDeferValue(defaultValue) - block = block || narrowingFilter.block - display = display || narrowingFilter.display - } - return { block, display } -} - -// Negative form because it is narrowing the type. -function ruleValueDoesNotDefer( - rule: NonNormalizedRule -): rule is NonNormalizedResolvedRule { - if (rule === undefined) { - return false - } - if (isObject(rule)) { - const { action } = rule - if (action === undefined || action === 'defer') { - return false - } - } - return true -} - -// Handles booleans for backwards compatibility. -function uxForDefinedNonDeferValue( - ruleValue: NonNormalizedResolvedRule -): RuleActionUX { - if (typeof ruleValue === 'boolean') { - return ruleValue ? ERROR_UX : IGNORE_UX - } - const { action } = ruleValue - if (action === 'warn') { - return WARN_UX - } else if (action === 'ignore') { - return IGNORE_UX - } - return ERROR_UX -} - -type SettingsType = (SocketSdkResultType<'postSettings'> & { - success: true -})['data'] - -export function createAlertUXLookup(settings: SettingsType): (context: { - package: { name: string; version: string } - alert: { type: string } -}) => RuleActionUX { - const cachedUX: Map = - new Map() - return context => { - const { type } = context.alert - let ux = cachedUX.get(type) - if (ux) { - return ux - } - const orderedRulesCollection: NonNormalizedRule[][] = [] - for (const settingsEntry of settings.entries) { - const orderedRules: NonNormalizedRule[] = [] - let target = settingsEntry.start - while (target !== null) { - const resolvedTarget = settingsEntry.settings[target] - if (!resolvedTarget) { - break - } - const issueRuleValue = resolvedTarget.issueRules?.[type] - if (typeof issueRuleValue !== 'undefined') { - orderedRules.push(issueRuleValue) - } - target = resolvedTarget.deferTo ?? null - } - orderedRulesCollection.push(orderedRules) - } - const defaultValue = settings.defaults.issueRules[type] as - | { action: 'error' | 'ignore' | 'warn' } - | boolean - | undefined - let resolvedDefaultValue: NonNormalizedResolvedRule = { - action: 'error' - } - if (defaultValue === false) { - resolvedDefaultValue = { action: 'ignore' } - } else if (defaultValue && defaultValue !== true) { - resolvedDefaultValue = { action: defaultValue.action ?? 'error' } - } - ux = resolveAlertRuleUX(orderedRulesCollection, resolvedDefaultValue) - cachedUX.set(type, ux) - return ux - } -} - -let _uxLookup: AlertUxLookup | undefined -export async function uxLookup( - settings: AlertUxLookupSettings -): Promise { - if (_uxLookup === undefined) { - const { orgs, settings } = await (async () => { - try { - const sockSdk = await setupSdk(getPublicToken()) - const orgResult = await sockSdk.getOrganizations() - if (!orgResult.success) { - if (orgResult.status === 429) { - throw new Error(`API token quota exceeded: ${orgResult.error}`) - } - throw new Error( - `Failed to fetch Socket organization info: ${orgResult.error}` - ) - } - const { organizations } = orgResult.data - const orgs: Array> = - [] - for (const org of Object.values(organizations)) { - if (org) { - orgs.push(org) - } - } - const settingsResult = await sockSdk.postSettings( - orgs.map(org => ({ organization: org.id })) - ) - if (!settingsResult.success) { - throw new Error( - `Failed to fetch API key settings: ${settingsResult.error}` - ) - } - return { - orgs, - settings: settingsResult.data - } - } catch (e) { - const cause = isObject(e) && 'cause' in e ? e['cause'] : undefined - if ( - isErrnoException(cause) && - (cause.code === 'ENOTFOUND' || cause.code === 'ECONNREFUSED') - ) { - throw new Error( - 'Unable to connect to socket.dev, ensure internet connectivity before retrying', - { - cause: e - } - ) - } - throw e - } - })() - // Remove any organizations not being enforced. - const enforcedOrgs = getConfigValue('enforcedOrgs') ?? [] - for (const { 0: i, 1: org } of orgs.entries()) { - if (!enforcedOrgs.includes(org.id)) { - settings.entries.splice(i, 1) - } - } - const socketYml = findSocketYmlSync() - if (socketYml) { - settings.entries.push({ - start: socketYml.path, - settings: { - [socketYml.path]: { - deferTo: null, - // TODO: TypeScript complains about the type not matching. We should - // figure out why are providing - // issueRules: { [issueName: string]: boolean } - // but expecting - // issueRules: { [issueName: string]: { action: 'defer' | 'error' | 'ignore' | 'monitor' | 'warn' } } - issueRules: socketYml.parsed.issueRules as unknown as { - [key: string]: { - action: 'defer' | 'error' | 'ignore' | 'monitor' | 'warn' - } - } - } - } - }) - } - _uxLookup = createAlertUXLookup(settings) - } - return _uxLookup(settings) -} diff --git a/test/alert-rules.test.ts b/test/alert-rules.test.ts deleted file mode 100644 index 32da0ba93..000000000 --- a/test/alert-rules.test.ts +++ /dev/null @@ -1,384 +0,0 @@ -import { describe, expect, it } from 'vitest' - -import { createAlertUXLookup } from './dist/alert-rules' - -describe('Alert Rule UX', () => { - it('should properly defer', () => { - const noEntriesLookup = createAlertUXLookup({ - defaults: { - issueRules: { - fromDeferString: { - action: 'warn' - }, - fromUndefinedAction: { - action: 'warn' - }, - fromUndefinedRule: { - action: 'warn' - }, - willError: { - action: 'error' - }, - willIgnore: { - action: 'ignore' - }, - willWarn: { - action: 'warn' - } - } - }, - entries: [ - { - start: 'organization', - settings: { - organization: { - deferTo: 'repository', - issueRules: { - fromDeferString: { action: 'defer' }, - // @ts-ignore paranoia - fromUndefinedAction: {} - } - }, - repository: { - deferTo: null, - issueRules: { - fromMiddleConfig: { - action: 'warn' - } - } - } - } - } - ] - }) - expect( - noEntriesLookup({ - package: { - name: 'bar', - version: '0.0.0' - }, - alert: { type: 'willError' } - }) - ).toMatchInlineSnapshot(` - { - "block": true, - "display": true, - } - `) - expect( - noEntriesLookup({ - package: { - name: 'bar', - version: '0.0.0' - }, - alert: { type: 'willIgnore' } - }) - ).toMatchInlineSnapshot(` - { - "block": false, - "display": false, - } - `) - expect( - noEntriesLookup({ - package: { - name: 'bar', - version: '0.0.0' - }, - alert: { type: 'willWarn' } - }) - ).toMatchInlineSnapshot(` - { - "block": false, - "display": true, - } - `) - expect( - noEntriesLookup({ - package: { - name: 'bar', - version: '0.0.0' - }, - alert: { type: 'fromDeferString' } - }) - ).toMatchInlineSnapshot(` - { - "block": false, - "display": true, - } - `) - expect( - noEntriesLookup({ - package: { - name: 'bar', - version: '0.0.0' - }, - alert: { type: 'fromUndefinedAction' } - }) - ).toMatchInlineSnapshot(` - { - "block": false, - "display": true, - } - `) - expect( - noEntriesLookup({ - package: { - name: 'bar', - version: '0.0.0' - }, - alert: { type: 'fromUndefinedRule' } - }) - ).toMatchInlineSnapshot(` - { - "block": false, - "display": true, - } - `) - expect( - noEntriesLookup({ - package: { - name: 'bar', - version: '0.0.0' - }, - alert: { type: 'fromMiddleConfig' } - }) - ).toMatchInlineSnapshot(` - { - "block": false, - "display": true, - } - `) - }) - it('should use error UX when missing keys', () => { - const emptyLookup = createAlertUXLookup({ - defaults: { - issueRules: {} - }, - entries: [] - }) - expect( - emptyLookup({ - package: { - name: 'bar', - version: '0.0.0' - }, - alert: { type: '404' } - }) - ).toMatchInlineSnapshot(` - { - "block": true, - "display": true, - } - `) - }) - it('should use error/ignore UX when having boolean values instead of config', () => { - const booleanLookup = createAlertUXLookup({ - defaults: { - issueRules: { - // @ts-ignore backcompat - defaultTrue: true, - // @ts-ignore backcompat - defaultFalse: false - } - }, - entries: [ - { - start: 'organization', - settings: { - organization: { - issueRules: { - // @ts-ignore backcompat - orgTrue: true, - // @ts-ignore backcompat - orgFalse: false - } - } - } - } - ] - }) - expect( - booleanLookup({ - package: { - name: 'bar', - version: '0.0.0' - }, - alert: { type: 'defaultTrue' } - }) - ).toMatchInlineSnapshot(` - { - "block": true, - "display": true, - } - `) - expect( - booleanLookup({ - package: { - name: 'bar', - version: '0.0.0' - }, - alert: { type: 'orgTrue' } - }) - ).toMatchInlineSnapshot(` - { - "block": true, - "display": true, - } - `) - expect( - booleanLookup({ - package: { - name: 'bar', - version: '0.0.0' - }, - alert: { type: 'defaultFalse' } - }) - ).toMatchInlineSnapshot(` - { - "block": false, - "display": false, - } - `) - expect( - booleanLookup({ - package: { - name: 'bar', - version: '0.0.0' - }, - alert: { type: 'orgFalse' } - }) - ).toMatchInlineSnapshot(` - { - "block": false, - "display": false, - } - `) - }) - it('should use the maximal strength on multiple settings entries', () => { - const multiSettings = createAlertUXLookup({ - defaults: { - issueRules: {} - }, - entries: [ - { - start: 'start', - settings: { - start: { - deferTo: null, - issueRules: { - warn_then_error: { - action: 'warn' - }, - ignore_then_missing: { - action: 'ignore' - }, - ignore_then_defer: { - action: 'ignore' - } - } - } - } - }, - { - start: 'start', - settings: { - start: { - deferTo: null, - issueRules: { - warn_then_error: { - action: 'error' - }, - ignore_then_defer: { - action: 'defer' - } - } - } - } - } - ] - }) - expect( - multiSettings({ - package: { - name: 'bar', - version: '0.0.0' - }, - alert: { type: 'warn_then_error' } - }) - ).toMatchInlineSnapshot(` - { - "block": true, - "display": true, - } - `) - expect( - multiSettings({ - package: { - name: 'bar', - version: '0.0.0' - }, - alert: { type: 'ignore_then_missing' } - }) - ).toMatchInlineSnapshot(` - { - "block": true, - "display": true, - } - `) - expect( - multiSettings({ - package: { - name: 'bar', - version: '0.0.0' - }, - alert: { type: 'ignore_then_defer' } - }) - ).toMatchInlineSnapshot(` - { - "block": true, - "display": true, - } - `) - }) - it('should shadow defaults', () => { - const shadowedLookup = createAlertUXLookup({ - defaults: { - issueRules: { - willWarn: { - action: 'warn' - } - } - }, - entries: [ - { - start: 'organization', - settings: { - organization: { - deferTo: null, - issueRules: { - willWarn: { - action: 'ignore' - } - } - } - } - } - ] - }) - expect( - shadowedLookup({ - package: { - name: 'bar', - version: '0.0.0' - }, - alert: { type: 'willWarn' } - }) - ).toMatchInlineSnapshot(` - { - "block": false, - "display": false, - } - `) - }) -})