Assessor_2.0/tests/domain/result-db-disclosure.test.ts

568 lines
37 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { describe, expect, it } from 'vitest';
import type { UnifiedResultProjection } from '../../src/domain/unified-result';
import {
discloseResultDbProjection, discloseResultDbProjectionWithTrace, removeResultDbScopeIdentifiers, RESULT_DB_DISCLOSURE_POLICY_VERSION
} from '../../src/domain/result-db-disclosure';
function unsafeScanner(): UnifiedResultProjection {
return {
resultProjectionId: 'scanner-finding:private-occurrence', sourceKind: 'scanner-finding',
sourceResultId: 'private-occurrence', projectId: 'private-company', assessmentId: 'private-assessment',
collectionRunId: 'private-run', scanRunId: 'private-run', sourceTool: 'maester', sourceRuleId: 'MT.1001',
sourceToolVersion: 'private-build', sourceProfileId: 'maester', canonicalDefinitionId: 'private-definition',
importArtifactId: 'private-artifact', sourceViewId: 'private-view', sourceLocator: { objectPath: 'private/path' },
title: 'Kerberos preauthentication is disabled for person@private-company.example',
description: 'Private Company GmbH tenant permits legacy authentication.',
recommendation: 'Disable legacy authentication and contact Jane Doe.', affectedAsset: 'private-company.example',
severityOriginal: 'Private severity', severityNormalized: 'high', outcomeOriginal: 'Private outcome',
outcomeNormalized: 'fail', technicalExecutionState: 'completed', resultType: 'compliance_check',
isIoe: false, isDeviation: true, lifecycleState: 'unknown', mappingConfidence: 1,
reviewStatus: 'open', reviewOwner: 'Jane Doe', reviewDueDate: '2026-10-01',
sourceObservedAt: '2026-09-22T08:00:00.000Z', importedAt: '2026-09-22T08:00:00.000Z',
createdAt: '2026-09-22T08:00:00.000Z',
frameworkReferences: [],
categories: [{ namespace: 'private-company', categoryCode: 'private', categoryLabel: 'Private Company',
categoryType: 'source', isPrimary: true }]
};
}
describe(`Result DB disclosure ${RESULT_DB_DISCLOSURE_POLICY_VERSION}`, () => {
it('keeps useful known-scanner guidance while redacting customer identifiers', () => {
const disclosed = discloseResultDbProjection(unsafeScanner());
expect(disclosed).toMatchObject({
title: 'Kerberos preauthentication is disabled for [email]', sourceTool: 'maester', sourceRuleId: 'MT.1001',
description: '[organization] tenant permits legacy authentication.',
recommendation: 'Disable legacy authentication and contact [redacted].',
severityOriginal: 'high', outcomeOriginal: 'fail', importedAt: '1970-01-01T00:00:00.000Z',
categories: [{ namespace: 'source-tool', categoryCode: 'maester', categoryLabel: 'Maester' }]
});
const serialized = JSON.stringify(disclosed);
for (const privateValue of ['person@private-company.example', 'Private Company GmbH', 'Jane Doe',
'private-company.example', 'private/path', 'private-build', 'private-definition', 'private-artifact']) {
expect(serialized).not.toContain(privateValue);
}
});
it('withholds review status, disposition and priority from the Result DB projection', () => {
const disclosed = discloseResultDbProjection({
...unsafeScanner(), reviewDisposition: 'accepted', reviewPriority: 'high'
});
for (const key of ['reviewStatus', 'reviewDisposition', 'reviewPriority', 'reviewOwner', 'reviewDueDate',
'sourceToolVersion', 'sourceObservedAt', 'affectedAsset', 'sourceLocator']) {
expect(Object.keys(disclosed)).not.toContain(key);
}
expect(discloseResultDbProjection(disclosed)).toEqual(disclosed);
});
it('replaces environment fingerprints in scanner prose but keeps public technical tokens', () => {
const disclosed = discloseResultDbProjection({
...unsafeScanner(), sourceTool: 'pingcastle', sourceRuleId: 'A-NotEnoughDC',
title: 'The number of DCs is too small to provide redundancy: 1 DC',
description: 'Last AD backup 55 days ago on 2026-08-04 at 13:37; built-in admin used 0 day(s) ago. '
+ '15 accounts and 1.015 objects, 3,250 computers, 42% of users, observed 28.09.2026.',
recommendation: 'Disable NTLMv1 and TLS 1.0 on Windows Server 2016 and Windows 10, see MT.1024, '
+ 'CVE-2021-34527, T1558.003, KB5005413, event ID 4769, port 445, 64-bit and S-1-5-32-544.'
});
expect(disclosed.title).toBe('The number of DCs is too small to provide redundancy: [count] DC');
expect(disclosed.description).toBe('Last AD backup [duration] ago on [date] at [time]; built-in admin used [duration] ago. '
+ '[count] accounts and [count] objects, [count] computers, [count]% of users, observed [date].');
expect(disclosed.recommendation).toBe('Disable NTLMv1 and TLS 1.0 on Windows Server 2016 and Windows 10, see MT.1024, '
+ 'CVE-2021-34527, T1558.003, KB5005413, event ID 4769, port 445, 64-bit and S-1-5-32-544.');
});
it('keeps numbered remediation steps intact', () => {
const disclosed = discloseResultDbProjection({
...unsafeScanner(), sourceTool: 'purple-knight', title: 'Synthetic',
recommendation: '1. From the impacted resources, select 3 accounts.\n2) Remove the delegation.\n 10. Verify.'
});
expect(disclosed.recommendation).toBe('1. From the impacted resources, select [count] accounts.\n2) Remove the delegation.\n10. Verify.');
});
it('keeps inline ordered lists but not a lone count followed by a sentence', () => {
const disclosed = discloseResultDbProjection({
...unsafeScanner(), sourceTool: 'maester', title: 'Synthetic',
description: 'Open the admin center. 1. Select Users. 2. Choose 3 accounts. 3. Fill the form. Found 1. Next steps follow.',
recommendation: 'Enable the rules. 1) Outbound rules. 2) Network Restrictions: apply them.'
});
expect(disclosed.description).toBe('Open the admin center. 1. Select Users. 2. Choose [count] accounts. 3. Fill the form. Found [count]. Next steps follow.');
expect(disclosed.recommendation).toBe('Enable the rules. 1) Outbound rules. 2) Network Restrictions: apply them.');
});
it('handles source quirks: repeated step numbers, markdown links showing their URL, code and sentence joins', () => {
const disclosed = discloseResultDbProjection({
...unsafeScanner(), sourceTool: 'maester', title: 'Navigate to the Microsoft 365 admin center',
description: '1. Navigate to Portal [https://portal.azure.com](https://portal.azure.com). 2. Search Entra. 3. Expand Manage. '
+ '3. On the Properties page continue. 4. Save. Open [blade](https://entra.microsoft.com/#view/Microsoft_AAD_IAM/'
+ 'RecommendationDetails.ReactView/id/x). Run (Get-Policy).keyRestrictions.isEnforced and System.DateTime '
+ 'or Windows.Storage with ASP.NET. It affects any endpoint.The indicator stays. See aka.ms/mfa and '
+ 'test.mysmartlogon.com; contoso.It remains.'
});
expect(disclosed.title).toBe('Navigate to the Microsoft 365 admin center');
expect(disclosed.description).toBe('1. Navigate to Portal [https://portal.azure.com](https://portal.azure.com). 2. Search Entra. 3. Expand Manage. '
+ '3. On the Properties page continue. 4. Save. Open [blade](https://entra.microsoft.com/#view/Microsoft_AAD_IAM/'
+ 'RecommendationDetails.ReactView/id/x). Run (Get-Policy).keyRestrictions.isEnforced and System.DateTime '
+ 'or Windows.Storage with ASP.NET. It affects any endpoint.The indicator stays. See aka.ms/mfa and '
+ '[domain]; [domain] remains.');
});
it('keeps skipped step numbers, attribute flags and CISA section numbers', () => {
const disclosed = discloseResultDbProjection({
...unsafeScanner(), sourceTool: 'purple-knight', title: 'Synthetic',
description: '1. Create a group. 2. Open Entra. 3. Select Security. 3. Select Manage. 5. Under Admin, save. '
+ 'Accounts with adminCount=1 or the adminCount attribute set to 1 are protected; 4 accounts were found. '
+ 'See [CISA 4. Centralized Log Collection - MS.AAD.4.1v1].'
});
expect(disclosed.description).toBe('1. Create a group. 2. Open Entra. 3. Select Security. 3. Select Manage. 5. Under Admin, save. '
+ 'Accounts with adminCount=1 or the adminCount attribute set to 1 are protected; [count] accounts were found. '
+ 'See [CISA 4. Centralized Log Collection - MS.AAD.4.1v1].');
});
it('keeps the space before a generic domain suffix such as .onmicrosoft.com', () => {
const disclosed = discloseResultDbProjection({
...unsafeScanner(), sourceTool: 'maester', sourceRuleId: 'MT.1074',
title: 'No more than 100 outbound mails per day are sent using the .onmicrosoft.com domain',
description: 'Spam from \'.onmicrosoft.com\' addresses , and contoso.onmicrosoft.com stays hidden .'
});
expect(disclosed.title).toBe('No more than [count] outbound mails per day are sent using the .onmicrosoft.com domain');
expect(disclosed.description).toBe('Spam from \'.onmicrosoft.com\' addresses, and [domain] stays hidden.');
});
it('keeps recommended target values in recommendations only', () => {
const disclosed = discloseResultDbProjection({
...unsafeScanner(), sourceTool: 'pingcastle', title: 'Synthetic',
description: 'The value is set to 3 on this system.',
recommendation: 'RequireIntegrity and RequireMutualAuthentication should be set to 1. LDAPOwnerModify configured to 0.'
});
expect(disclosed.description).toBe('The value is set to [count] on this system.');
expect(disclosed.recommendation).toBe('RequireIntegrity and RequireMutualAuthentication should be set to 1. LDAPOwnerModify configured to 0.');
});
it('keeps documented vendor values in recommendations only, never an observed count beside them', () => {
const guidance = 'Set the ms-DS-MachineAccountQuota attribute on the domain NC head to 0 to disable it. '
+ 'It is recommended to set it to 1. Set it to zero (0). The expected new value is 00000000010000000002000000011. '
+ 'Add the flag by adding the number 1048576. Assign 1 as the Value; other values include 2 for Audit mode and 6 for Warn mode. '
+ 'Use get-adobject -ldapfilter "(admincount=1)" and a 20+ length password. It can be done through 2 methods. '
+ 'Note: only 10 will be listed below.';
const disclosed = discloseResultDbProjection({
...unsafeScanner(), sourceTool: 'pingcastle', title: 'Synthetic',
description: guidance, recommendation: `${guidance} Currently 12 accounts are affected; set 4 of them to 0.`
});
expect(disclosed.recommendation).toBe(`${guidance} Currently [count] accounts are affected; set [count] of them to [count].`);
expect(disclosed.description).not.toMatch(/(?<![\w.])(?:0|1|2|6|10|20|1048576)(?![\w.])/);
});
it('keeps public documentation links, file names, policy IDs and Graph permissions but redacts tenant references', () => {
const disclosed = discloseResultDbProjection({
...unsafeScanner(), sourceTool: 'maester', title: 'Synthetic',
recommendation: 'See [Microsoft Learn](https://learn.microsoft.com/en-us/entra/identity/overview) and '
+ 'https://github.com/cisagov/ScubaGear/blob/main/baselines/aad.md for MS.AAD.2.3 in the Entra admin center '
+ 'https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_logging?view=powershell-7 '
+ 'https://www.whitehouse.gov/wp-content/uploads/2022/01/M-22-09.pdf '
+ '(entra.microsoft.com). Grant Directory.ReadWrite.All and Policy.ReadWrite.AuthenticationMethod. '
+ 'Check system32\\logfiles\\firewall\\pfirewall.log. '
+ 'Tenant links https://contoso.sharepoint.com/sites/it, https://login.microsoftonline.com/contoso.onmicrosoft.com/oauth2, '
+ 'https://portal.azure.com/#@contoso.onmicrosoft.com/resource, https://learn.microsoft.com/?tenant=contoso.de, '
+ 'https://portal.azure.com/#blade/subscriptions/0f1e2d3c-4b5a-6978-8a9b-0c1d2e3f4a5b/resourceGroups/rg-contoso-prod/overview, '
+ 'https://entra.microsoft.com/#view/Apps/objectId/0f1e2d3c-4b5a-6978-8a9b-0c1d2e3f4a5b/appId/x '
+ 'and domains contoso.onmicrosoft.com, mail.contoso.de stay hidden.'
});
expect(disclosed.recommendation).toBe('See [Microsoft Learn](https://learn.microsoft.com/en-us/entra/identity/overview) and '
+ 'https://github.com/cisagov/ScubaGear/blob/main/baselines/aad.md for MS.AAD.2.3 in the Entra admin center '
+ 'https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_logging?view=powershell-7 '
+ 'https://www.whitehouse.gov/wp-content/uploads/2022/01/M-22-09.pdf '
+ '(entra.microsoft.com). Grant Directory.ReadWrite.All and Policy.ReadWrite.AuthenticationMethod. '
+ 'Check system32\\logfiles\\firewall\\pfirewall.log. '
+ 'Tenant links [url], [url], [url], [url], [url], [url] '
+ 'and domains [domain], [domain] stay hidden.');
});
it('keeps vendor thresholds in recommendations but not in observed titles or descriptions', () => {
const disclosed = discloseResultDbProjection({
...unsafeScanner(), sourceTool: 'pingcastle', title: 'Password length is at least 7 characters for 15 accounts',
description: 'Observed: at least 7 characters on 15 accounts inactive for more than 55 days.',
recommendation: 'A strong password is at least 8 characters, rotated every 180 days, removed when older than 90 days, '
+ 'with 15 or more characters for service accounts and no more than 5 attempts; 3 accounts are affected.'
});
expect(disclosed.title).toBe('Password length is at least [count] characters for [count] accounts');
expect(disclosed.description).toBe('Observed: at least [count] characters on [count] accounts inactive for more than [duration].');
expect(disclosed.recommendation).toBe('A strong password is at least 8 characters, rotated every 180 days, removed when older than 90 days, '
+ 'with 15 or more characters for service accounts and no more than 5 attempts; [count] accounts are affected.');
});
it('keeps public policy and registry paths and documented authorities instead of turning them into accounts', () => {
const disclosed = discloseResultDbProjection({
...unsafeScanner(), sourceTool: 'cis-cat', sourceRuleId: '2.3.10.7',
recommendation: 'Set the following UI path to Enabled:\n'
+ 'Computer Configuration\\Policies\\Windows Settings\\Security Settings\\Local Policies\\Security Options\\Network access: Remotely accessible registry paths\n'
+ 'The value is stored in HKLM\\SYSTEM\\CurrentControlSet\\Control\\SecurePipeServers\\Winreg\\AllowedExactPaths:Machine and '
+ 'System\\CurrentControlSet\\Control\\ProductOptions.\n'
+ 'Grant BUILTIN\\Administrators, NT SERVICE\\WdiServiceHost and RESTRICTED SERVICES\\PrintSpoolerService.\n'
+ 'Do not grant CONTOSO\\jane or CONTOSO\\svc-backup.'
});
expect(disclosed.recommendation).toBe('Set the following UI path to Enabled:\n'
+ 'Computer Configuration\\Policies\\Windows Settings\\Security Settings\\Local Policies\\Security Options\\Network access: Remotely accessible registry paths\n'
+ 'The value is stored in HKLM\\SYSTEM\\CurrentControlSet\\Control\\SecurePipeServers\\Winreg\\AllowedExactPaths:Machine and '
+ 'System\\CurrentControlSet\\Control\\ProductOptions.\n'
+ 'Grant BUILTIN\\Administrators, NT SERVICE\\WdiServiceHost and RESTRICTED SERVICES\\PrintSpoolerService.\n'
+ 'Do not grant [account] or [account].');
});
it('keeps Group Policy node paths, hard-wrapped registry paths, rule references and documented address ranges', () => {
const disclosed = discloseResultDbProjection({
...unsafeScanner(), sourceTool: 'cis-cat', sourceRuleId: '2.3.10.9',
description: 'Use the built-in security template of Group Policy (e.g. Windows Settings\\Security Settings). '
+ 'It depends on Network security: LAN Manager Authentication Level (Rule 2.3.11.7).',
recommendation: 'System\\CurrentControlSet\\Control\\Terminal Server System\\CurrentControlSet\\Control\\Terminal\n'
+ 'Server\\UserConfig System\\CurrentControlSet\\Control\\ContentIndex.\nCONTOSO\\jane must not be listed.'
});
expect(disclosed.description).toBe('Use the built-in security template of Group Policy (e.g. Windows Settings\\Security Settings). '
+ 'It depends on Network security: LAN Manager Authentication Level (Rule 2.3.11.7).');
expect(disclosed.recommendation).toBe('System\\CurrentControlSet\\Control\\Terminal Server System\\CurrentControlSet\\Control\\Terminal\n'
+ 'Server\\UserConfig System\\CurrentControlSet\\Control\\ContentIndex.\n[account] must not be listed.');
const firewall = discloseResultDbProjection({
...unsafeScanner(), sourceTool: 'pingcastle', sourceRuleId: 'S-FirewallScript', title: 'Synthetic',
recommendation: 'Allow local network access: 10.0.0.0 to 10.255.255.255, 192.168.0.0 to 192.168.255.255; '
+ 'block 0.0.0.0 to 9.255.255.255 11.0.0.0 to 126.255.255.255 128.0.0.1 to 172.15.255.255 172.32.0.0 to 192.167.255.255 '
+ '192.169.0.0 to 255.255.255.255; host 10.20.30.40 and subnet 10.20.30.0 to 10.20.30.255 observed.'
});
expect(firewall.recommendation).toBe('Allow local network access: 10.0.0.0 to 10.255.255.255, 192.168.0.0 to 192.168.255.255; '
+ 'block 0.0.0.0 to 9.255.255.255 11.0.0.0 to 126.255.255.255 128.0.0.1 to 172.15.255.255 172.32.0.0 to 192.167.255.255 '
+ '192.169.0.0 to 255.255.255.255; '
+ 'host [ip] and subnet [ip] to [ip] observed.');
});
it('keeps multi-part CIS numbers in titles and redacts only standalone IPv4 addresses', () => {
const disclosed = discloseResultDbProjection({
...unsafeScanner(), sourceTool: 'cis-cat', sourceRuleId: '18.9.20.1.1',
title: "18.9.20.1.1 (L2) Ensure 'Turn off downloading of print drivers over HTTP' is set to 'Enabled'",
description: 'Rule 18.9.20.1 applies; the server 10.20.30.40 and 999.1.1.1 were observed.'
});
expect(disclosed.title).toBe("18.9.20.1.1 (L2) Ensure 'Turn off downloading of print drivers over HTTP' is set to 'Enabled'");
expect(disclosed.description).toBe('Rule 18.9.20.1 applies; the server [ip] and 999.1.1.1 were observed.');
});
it('replaces compact durations such as 24h or 30d', () => {
const disclosed = discloseResultDbProjection({
...unsafeScanner(), sourceTool: 'pingcastle', title: 'Synthetic', description: 'Rotated 30d ago, checked every 24h, 5min, 2yrs.'
});
expect(disclosed.description).toBe('Rotated [duration] ago, checked every [duration], [duration], [duration].');
});
it('keeps registry-path GPO titles readable instead of treating them as DOMAIN\\account pairs', () => {
const disclosed = discloseResultDbProjection({
...unsafeScanner(), sourceKind: 'policy-evaluation', sourceTool: 'GPOs',
title: 'Software\\Policies\\Microsoft\\Windows NT\\Printers · RestrictDriverInstallationToAdministrators'
});
expect(disclosed.title).toBe('Software\\Policies\\Microsoft\\Windows NT\\Printers · RestrictDriverInstallationToAdministrators');
});
it('keeps documented Microsoft principals in the Soll value readable', () => {
const disclosed = discloseResultDbProjection({
...unsafeScanner(), sourceKind: 'policy-evaluation', sourceTool: 'GPOs', title: 'Privilege Rights · SeServiceLogonRight',
policyBaselineValue: { kind: 'principal-set', principals: ['*S-1-5-113', '*S-1-5-83-0', 'S-1-5-9',
'NT SERVICE\\WdiServiceHost', 'NT VIRTUAL MACHINE\\Virtual Machines', 'Window Manager\\Window Manager Group',
'CONTOSO\\svc-backup', 'S-1-5-21-1-2-3-1105'] }
});
expect(disclosed.policyBaselineValue).toEqual({ kind: 'principal-set', principals: ['*S-1-5-113', '*S-1-5-83-0', 'S-1-5-9',
'NT SERVICE\\WdiServiceHost', 'NT VIRTUAL MACHINE\\Virtual Machines', 'Window Manager\\Window Manager Group',
'[DOMAIN_PRINCIPAL_1]', 'S-1-5-21-[DOMAIN]-1105'] });
});
it('keeps public benchmark values in CIS-CAT prose', () => {
const disclosed = discloseResultDbProjection({
...unsafeScanner(), sourceTool: 'cis-cat', sourceRuleId: '1.1.4',
title: "(L1) Ensure 'Minimum password length' is set to '14 or more character(s)'",
recommendation: "Set 'Account lockout duration' to 15 or more minute(s)."
});
expect(disclosed.title).toBe("(L1) Ensure 'Minimum password length' is set to '14 or more character(s)'");
expect(disclosed.recommendation).toBe("Set 'Account lockout duration' to 15 or more minute(s).");
});
it('never discloses an unprotected number from non-benchmark scanner prose', () => {
const fingerprints = ['7 DC', '15 accounts', '55 days', '0 day(s) ago', '2026-08-04', '13:37', '1.015', '3,250'];
for (const tool of ['pingcastle', 'purple-knight', 'maester']) {
const disclosed = discloseResultDbProjection({
...unsafeScanner(), sourceTool: tool, title: `Synthetic ${fingerprints.join(', ')}`,
description: fingerprints.join(' / '), recommendation: `Review ${fingerprints.join(' and ')}`
});
const prose = [disclosed.title, disclosed.description, disclosed.recommendation].join('\n');
expect(prose.replace(/\[(?:count|duration|date|time)\]/g, '')).not.toMatch(/\d/);
}
});
it('discloses only the baseline family instead of the compared release', () => {
for (const [label, family] of [
['Microsoft Security Baseline Windows Server 2025 (2602)', 'Microsoft Security Baseline - Windows Server'],
['Windows 11 25H2 Security Baseline', 'Microsoft Security Baseline - Windows Client'],
['Microsoft 365 Apps for enterprise 2512', 'Microsoft Security Baseline - Microsoft 365 Apps'],
['Private customer template', 'Baseline recommendation']
]) {
const disclosed = discloseResultDbProjection({
...unsafeScanner(), sourceKind: 'policy-evaluation', sourceTool: 'GPOs', title: 'Synthetic setting',
policyBaselineLabel: label
});
expect(disclosed.policyBaselineLabel).toBe(family);
}
});
it('collapses identical baseline expectations so the compared release count is not disclosed', () => {
const variant = (id: string) => ({ settingId: id, baselineValues: [], comparator: {
version: 1 as const, comparator: { kind: 'equality' as const }, relation: 'different' as const,
actual: { kind: 'text' as const, value: '0' }, baseline: { kind: 'text' as const, value: '1' }, diagnosticCodes: []
} });
const disclosed = discloseResultDbProjection({
...unsafeScanner(), sourceKind: 'policy-evaluation', sourceTool: 'GPOs', title: 'Synthetic setting',
policySourceCount: 4,
policyBaselineVariantAssessment: { version: 1, scope: 'backup-configuration', decision: 'all-fail',
actualValues: [], variants: [variant('server-2019'), variant('server-2022'), variant('server-2025')], diagnosticCodes: [] }
});
expect(disclosed.policyBaselineVariantAssessment?.variants).toHaveLength(1);
expect(disclosed).not.toHaveProperty('policySourceCount');
expect(JSON.stringify(disclosed)).not.toMatch(/server-20\d\d/);
});
it('retains Purple Knight native rule identity and remediation text', () => {
const disclosed = discloseResultDbProjection({
...unsafeScanner(), sourceTool: 'purple-knight', sourceRuleId: 'SI000055',
title: 'Privileged accounts have weak delegation settings',
description: 'Review delegation on PRIVATE\\admin-user and private-company.example.',
recommendation: '<p>Remove unconstrained delegation from the <b>affected account</b>.</p>',
affectedAsset: 'PRIVATE\\admin-user'
});
expect(disclosed).toMatchObject({
sourceTool: 'purple-knight', sourceRuleId: 'SI000055',
title: 'Privileged accounts have weak delegation settings',
description: 'Review delegation on [account] and [domain].',
recommendation: 'Remove unconstrained delegation from the affected account.'
});
});
it('publishes Purple Knight for Entra ID as its own Result DB source scope', () => {
const disclosed = discloseResultDbProjection({
...unsafeScanner(), sourceTool: 'purple-knight', sourceToolScope: 'entra-id', sourceRuleId: 'SI000055',
title: 'Synthetic Entra control', description: 'Synthetic Entra description.'
});
expect(disclosed).toMatchObject({
sourceTool: 'purple-knight-entra', sourceToolScope: 'entra-id', sourceRuleId: 'SI000055',
title: 'Synthetic Entra control',
categories: [{ namespace: 'source-tool', categoryCode: 'purple-knight-entra', categoryLabel: 'Purple Knight for Entra ID' }]
});
});
it('shows a publicly documented PingCastle ID in its native spelling and rejects an unknown ID', () => {
const item = { ...unsafeScanner(), sourceTool: 'pingcastle', sourceRuleId: 'S-oldNtlm',
title: 'The LAN Manager Authentication Level allows the use of NTLMv1 or LM.' };
expect(discloseResultDbProjection(item)).toMatchObject({
sourceRuleId: 'S-oldNtlm', title: item.title
});
expect(discloseResultDbProjection({ ...item, sourceRuleId: 'S-PrivateCustomerRule' }).sourceRuleId)
.toBeUndefined();
});
it('releases documented Maester suite IDs but keeps unknown and instance-specific IDs private', () => {
for (const sourceRuleId of ['CIS.M365.1.1.1', 'CISA.MS.AAD.2.3', 'EIDSCA.AF01', 'ORCA.100', 'MT.1025']) {
expect(discloseResultDbProjection({ ...unsafeScanner(), sourceRuleId }).sourceRuleId)
.toBe(sourceRuleId);
}
for (const sourceRuleId of ['CT001', 'MT.INV.001', 'MT.1024.private-customer-key',
'MT.1024.applicationCredentialExpiry.private-tenant', 'MT.1033.0', 'CIS.M365.99.99.99']) {
expect(discloseResultDbProjection({ ...unsafeScanner(), sourceRuleId }).sourceRuleId)
.toBeUndefined();
}
});
it('keeps a CIS-CAT control number and public policy path while hiding an actual address and account', () => {
const disclosed = discloseResultDbProjection({
...unsafeScanner(), sourceTool: 'cis-cat', sourceRuleId: '18.10.36.1',
title: "18.10.36.1 Ensure 'Turn off location' is set to 'Enabled'",
recommendation: 'Computer Configuration\\Administrative Templates\\System\\Location and Sensors\\Turn off location; CONTOSO\\jane\n'
+ 'Use the public policy file Windows.adml.\n'
+ 'Review CONTOSO\\jane at 10.20.30.40 and jane@contoso.example.'
});
expect(disclosed.sourceRuleId).toBe('18.10.36.1');
expect(disclosed.title).toContain('18.10.36.1');
expect(disclosed.title).not.toContain('[ip]');
expect(disclosed.recommendation).toContain('Computer Configuration\\Administrative Templates\\System\\Location and Sensors\\Turn off location');
expect(disclosed.recommendation).toContain('Turn off location; [account]');
expect(disclosed.recommendation).toContain('Windows.adml');
expect(disclosed.recommendation).toContain('[account] at [ip] and [email]');
expect(disclosed.recommendation).not.toMatch(/CONTOSO|jane|10\.20\.30\.40/);
});
it('keeps only the GPO Soll value and withholds every customer value and principal', () => {
const systemSid = 'S-1-5-82-123456789-234567890-345678901-456789012-567890123';
const disclosed = discloseResultDbProjection({
...unsafeScanner(), sourceKind: 'policy-evaluation', sourceTool: 'GPOs',
title: 'System Access · LockoutBadCount',
policyActualValue: { kind: 'text', value: '5', principals: [systemSid, 'S-1-5-32-544'] },
policyBaselineValue: { kind: 'text', value: '10' },
policyBaselineVariantAssessment: {
version: 1, scope: 'backup-configuration', decision: 'all-fail',
actualValues: [{ kind: 'principal-set', principals: [systemSid] }],
variants: [{ settingId: 'private-setting', baselineValues: [], comparator: {
version: 1, comparator: { kind: 'set', mode: 'exact' }, relation: 'different',
actual: { kind: 'string-set', values: [systemSid] },
baseline: { kind: 'string-set', values: ['S-1-5-32-544'] }, diagnosticCodes: []
}}], diagnosticCodes: []
}
});
expect(disclosed).not.toHaveProperty('policyActualValue');
expect(disclosed.policyBaselineValue).toMatchObject({ kind: 'text', value: '10' });
expect(disclosed.policyBaselineVariantAssessment).toMatchObject({ decision: 'all-fail', actualValues: [],
variants: [{ comparator: { relation: 'different', baseline: { kind: 'string-set', values: ['S-1-5-32-544'] } } }] });
expect(disclosed.policyBaselineVariantAssessment?.variants[0].comparator).not.toHaveProperty('actual');
const serialized = JSON.stringify(disclosed);
expect(serialized).not.toContain(systemSid);
expect(serialized).not.toContain('PRINCIPAL');
expect(serialized).not.toContain('"5"');
});
it('keeps the technical GPO setting, Soll value and relation without customer values or source identity', () => {
const disclosed = discloseResultDbProjection({
...unsafeScanner(), sourceKind: 'policy-evaluation', sourceTool: 'GPOs',
sourceRuleId: 'registry:machine:software\\policies\\microsoft\\windows nt\\printers\\pointandprint:restrictdriverinstallationtoadministrators',
title: 'Restrict driver installation to administrators',
description: 'Private Company GmbH configures this setting for dummy.local.',
recommendation: 'Set RestrictDriverInstallationToAdministrators to 1.',
policyDeliveryMechanism: 'registry-policy', policyClassification: 'misconfigured',
policyActualValue: {
kind: 'registry-policy', operation: 'set-value', key: 'Software\\Policies\\Microsoft\\Windows NT\\Printers\\PointAndPrint',
valueName: 'RestrictDriverInstallationToAdministrators', typeCode: 4, data: { kind: 'uint32', value: 0 },
principals: ['S-1-5-18', 'S-1-5-32-544', 'BUILTIN\\Administrators', 'DUMMY\\Domain Admins',
'DUMMY\\jane.doe', 'S-1-5-21-123456789-234567890-345678901-512'],
note: 'Configured by {A1B2C3D4-1111-2222-3333-444455556666}',
gpoName: 'DUMMY – Domain Controllers Security', gpoId: '{A1B2C3D4-1111-2222-3333-444455556666}',
sourcePath: '\\\\fileserver\\backups\\dummy.local\\gpo.zip'
},
policyBaselineValue: {
kind: 'registry-policy', operation: 'set-value', key: 'Software\\Policies\\Microsoft\\Windows NT\\Printers\\PointAndPrint',
valueName: 'RestrictDriverInstallationToAdministrators', typeCode: 4, data: { kind: 'uint32', value: 1 }
},
policyIdentityKey: 'private-company-policy', policyGpoId: '{A1B2C3D4-1111-2222-3333-444455556666}',
policyNativeBackupId: '{99999999-1111-2222-3333-444455556666}',
policyBaselineId: 'private-baseline-id', policyBaselineLabel: 'Private Company Microsoft Security Baseline Windows Server 2022 v1',
policyDiagnosticCodes: ['policy-safe-code', 'Private Company'], policyComparisonRelation: 'different',
policyBaselineVariantAssessment: {
version: 1, scope: 'backup-configuration', decision: 'all-fail',
actualValues: [{ kind: 'principal-set', principals: ['DUMMY\\named.user'] }],
variants: [{
settingId: 'private-company-baseline-setting',
baselineValues: [{ kind: 'principal-set', principals: ['BUILTIN\\Administrators'] }],
comparator: { version: 1, comparator: { kind: 'set', mode: 'exact' }, relation: 'different',
actual: { kind: 'string-set', values: ['DUMMY\\named.user'] },
baseline: { kind: 'string-set', values: ['BUILTIN\\Administrators'] }, diagnosticCodes: [] }
}], diagnosticCodes: []
}
});
expect(disclosed).toMatchObject({
title: 'Restrict driver installation to administrators', sourceTool: 'GPOs',
sourceRuleId: 'registry:machine:software\\policies\\microsoft\\windows nt\\printers\\pointandprint:restrictdriverinstallationtoadministrators',
policyBaselineValue: { data: { kind: 'uint32', value: 1 } },
policyBaselineLabel: 'Microsoft Security Baseline - Windows Server',
policyComparisonRelation: 'different',
recommendation: 'Set RestrictDriverInstallationToAdministrators to 1.',
policyDiagnosticCodes: ['policy-safe-code'],
policyBaselineVariantAssessment: {
actualValues: [],
variants: [{ settingId: 'baseline-expectation-1', comparator: {
relation: 'different', baseline: { kind: 'string-set', values: ['BUILTIN\\Administrators'] }
}}]
}
});
expect(disclosed).not.toHaveProperty('policyActualValue');
expect(disclosed.policyIdentityKey).toBeUndefined();
expect(disclosed.policyBaselineId).toBeUndefined();
const serialized = JSON.stringify(disclosed);
for (const privateValue of ['Private Company', 'dummy.local', 'DUMMY', 'Domain Admins', 'PRINCIPAL', '2022',
'A1B2C3D4', '99999999', 'fileserver', 'jane.doe', 'named.user', '123456789', '234567890', '345678901']) {
expect(serialized).not.toContain(privateValue);
}
expect(serialized).toContain('Backup comparison; effective endpoint state not verified');
});
it('removes generated GPO identity hashes instead of presenting them as rule IDs', () => {
const disclosed = discloseResultDbProjection({
...unsafeScanner(), sourceKind: 'policy-evaluation', sourceTool: 'GPOs',
sourceRuleId: 'gpo-policy-identity-deadbeef', title: 'Synthetic setting',
policyBaselineLabel: 'Microsoft Security Baseline', policyActualValue: 0, policyBaselineValue: 1
});
expect(disclosed.sourceRuleId).toBeUndefined();
expect(disclosed.policyIdentityKey).toBeUndefined();
});
it('replaces customer scope handles after query filtering', () => {
expect(removeResultDbScopeIdentifiers(discloseResultDbProjection(unsafeScanner()))).toMatchObject({
projectId: 'scoped-project', assessmentId: 'scoped-assessment', collectionRunId: 'scoped-collection',
scanRunId: 'scoped-scan'
});
});
it('keeps the complete seven-level CIS number so sibling recommendations do not collide', () => {
const first = discloseResultDbProjection({ ...unsafeScanner(), sourceTool: 'cis-cat', sourceRuleId: '18.10.42.11.1.1.1' });
const second = discloseResultDbProjection({
...unsafeScanner(), sourceTool: 'cis-cat', sourceRuleId: 'xccdf_org.cisecurity.benchmarks_rule_18.10.42.11.1.1.2_L1_Ensure'
});
expect(first.sourceRuleId).toBe('18.10.42.11.1.1.1');
expect(second.sourceRuleId).toBe('18.10.42.11.1.1.2');
expect(discloseResultDbProjection({ ...unsafeScanner(), sourceTool: 'cis-cat', sourceRuleId: '1.2.3.4.5.6.7.8.9.10' }).sourceRuleId)
.toBeUndefined();
});
it('does not mistake the ordinary word "limited" for an organization but still redacts legal names', () => {
const disclosed = discloseResultDbProjection({
...unsafeScanner(), title: 'Limited number of Global Admins are assigned.',
description: 'External sharing limited to existing guests.', recommendation: 'Contact Contoso Ltd or Fabrikam GmbH.'
});
expect(disclosed.title).toBe('Limited number of Global Admins are assigned.');
expect(disclosed.description).toBe('External sharing limited to existing guests.');
expect(disclosed.recommendation).not.toMatch(/Contoso|Fabrikam/);
expect(disclosed.recommendation).toContain('[organization]');
});
it('keeps public versions, releases and step, option and page references in scanner prose', () => {
const disclosed = discloseResultDbProjection({
...unsafeScanner(), sourceTool: 'pingcastle', sourceRuleId: 'A-DCLdapsProtocolAdvanced',
title: 'At least one DC is using Tls1.0 or Tls1.1.',
description: 'NTLMv2 works starting Windows 95. See Microsoft Graph v1.0. If using a pre 2012-R2 schema, '
+ 'divide the value from step 2 by the value from step 3. Option 1 is described on Page 16. 4 DCs were observed.'
});
expect(disclosed.title).toBe('At least one DC is using Tls1.0 or Tls1.1.');
expect(disclosed.description).toBe('NTLMv2 works starting Windows 95. See Microsoft Graph v1.0. If using a pre 2012-R2 schema, '
+ 'divide the value from step 2 by the value from step 3. Option 1 is described on Page 16. [count] DCs were observed.');
});
it('keeps documented Microsoft GUIDs but redacts every other GUID', () => {
const disclosed = discloseResultDbProjection({
...unsafeScanner(),
description: 'Add Azure DevOps (App ID: 499b84ac-1321-427f-aa17-267ca6975798) and block with ASR rule '
+ 'd3e037e1-3eb8-44c8-a917-57927947596d. Tenant 11111111-2222-4333-8444-555555555555 is affected.'
});
expect(disclosed.description).toBe('Add Azure DevOps (App ID: 499b84ac-1321-427f-aa17-267ca6975798) and block with ASR rule '
+ 'd3e037e1-3eb8-44c8-a917-57927947596d. Tenant [id] is affected.');
});
it('withholds per-finding scanner scores', () => {
const disclosed = discloseResultDbProjection({ ...unsafeScanner(), sourceTool: 'purple-knight', sourceRuleId: 'SI000011', score: 73 });
expect(disclosed).not.toHaveProperty('score');
});
it('lists withheld fields in the trace without revealing client review state', () => {
const reviewed = discloseResultDbProjectionWithTrace({ ...unsafeScanner(), reviewDisposition: 'accepted', reviewPriority: 'high' });
const unreviewed = discloseResultDbProjectionWithTrace({
...unsafeScanner(), reviewStatus: undefined, reviewOwner: undefined, reviewDueDate: undefined
});
expect(reviewed.disclosureTrace?.withheldFields).toEqual(unreviewed.disclosureTrace?.withheldFields);
expect(JSON.stringify(reviewed.disclosureTrace)).not.toMatch(/review/i);
expect(reviewed.disclosureTrace?.withheldFields).toContain('affectedAsset');
});
});