-
Notifications
You must be signed in to change notification settings - Fork 342
[comp] Production Deploy #3356
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
[comp] Production Deploy #3356
Changes from all commits
Commits
Show all changes
27 commits
Select commit
Hold shift + click to select a range
545bc74
fix(api): add isActive filter to member query
chasprowebdev 13aa3ef
fix(app): sort assignee list alphabetically in assignee dropdown
chasprowebdev 98401f4
fix(risk-treatment): ensure live tasks filter before ranking in draft…
tofikwest c51b17c
fix(cloud-security): exclude per-task runs from latest scan selection
tofikwest 70c393c
fix: address follow-up
tofikwest b4a5924
Merge branch 'main' into tofik/cs-702-bug-aws-cloud-security-tests
tofikwest cc493f0
fix(api): tie trust portal access link expiry to the grant duration
chasprowebdev 1816d95
chore: merge release v3.97.0 back to main [skip ci]
github-actions[bot] e1b6c0b
Merge branch 'main' into tofik/cs-702-bug-aws-cloud-security-tests
tofikwest f89c323
feat(trust): add setting to show/hide the Security Questionnaire on t…
tofikwest 1cef7fd
Merge pull request #3341 from trycompai/tofik/cs-702-bug-aws-cloud-se…
tofikwest a3eddac
Merge branch 'main' into chas/trust-portal-access-expiration
tofikwest a402487
Merge pull request #3342 from trycompai/chas/trust-portal-access-expi…
tofikwest 01a86c3
Merge branch 'main' into chas/task-assignee
tofikwest 7a5286b
Merge pull request #3320 from trycompai/chas/task-assignee
tofikwest 1d3c3bd
Merge branch 'main' into tofik/trust-questionnaire-toggle
tofikwest 662a487
Merge branch 'main' into tofik/cs-681-bug-draft-plan-suggest-links
tofikwest 6e71c3f
fix(trust): address cubic review on the questionnaire toggle
tofikwest 2a10daa
Merge remote-tracking branch 'origin/tofik/trust-questionnaire-toggle…
tofikwest eb3c097
fix(risk-treatment): harden orphan task-vector prune (CS-681 review)
tofikwest f555185
refactor(trust): share the public trust lookup + clarify a test name
tofikwest 3e5ad6f
Merge pull request #3353 from trycompai/tofik/trust-questionnaire-toggle
tofikwest 3a2bc95
Merge branch 'main' into tofik/cs-681-bug-draft-plan-suggest-links
tofikwest e2a66b0
Merge pull request #3340 from trycompai/tofik/cs-681-bug-draft-plan-s…
tofikwest dd2ea84
fix(trust-portal): derive public vendor badges from cert data (CS-688…
tofikwest 692db72
Merge branch 'release' into main
tofikwest e8cb123
fix(trust-portal): restore soc3/pipeda/ccpa badge mappings (CS-688) (…
tofikwest File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,102 @@ | ||
| import { | ||
| extractComplianceBadges, | ||
| mapCertificationToBadgeType, | ||
| } from './cert-badge-mapper'; | ||
|
|
||
| describe('mapCertificationToBadgeType', () => { | ||
| // CS-688: the "IEC" infix and ":2022" suffix must not stop ISO 27001 from | ||
| // being recognized. | ||
| it('maps "ISO/IEC 27001:2022" to iso27001', () => { | ||
| expect(mapCertificationToBadgeType('ISO/IEC 27001:2022')).toBe('iso27001'); | ||
| }); | ||
|
|
||
| it('maps common certifications to their canonical badge types', () => { | ||
| expect(mapCertificationToBadgeType('SOC 2 Type II')).toBe('soc2'); | ||
| expect(mapCertificationToBadgeType('ISO 9001:2015')).toBe('iso9001'); | ||
| expect(mapCertificationToBadgeType('ISO/IEC 42001:2023')).toBe('iso42001'); | ||
| expect(mapCertificationToBadgeType('GDPR Compliance')).toBe('gdpr'); | ||
| expect(mapCertificationToBadgeType('HIPAA')).toBe('hipaa'); | ||
| expect(mapCertificationToBadgeType('PCI DSS')).toBe('pci_dss'); | ||
| expect(mapCertificationToBadgeType('NEN 7510')).toBe('nen7510'); | ||
| }); | ||
|
|
||
| // The fully spelled-out PCI name must map too — the scan-time mappers already | ||
| // recognize it, so the shared display mapper must not drop it. | ||
| it('maps the spelled-out "Payment Card Industry Data Security Standard"', () => { | ||
| expect( | ||
| mapCertificationToBadgeType('Payment Card Industry Data Security Standard'), | ||
| ).toBe('pci_dss'); | ||
| }); | ||
|
|
||
| // Regression: soc3 / pipeda / ccpa were supported by the public portal's | ||
| // original mapper. Consolidation must not drop them or affected vendors lose | ||
| // their public Trust Centre badges. | ||
| it('maps soc3 / pipeda / ccpa (restored after consolidation)', () => { | ||
| expect(mapCertificationToBadgeType('SOC 3')).toBe('soc3'); | ||
| expect(mapCertificationToBadgeType('PIPEDA')).toBe('pipeda'); | ||
| expect(mapCertificationToBadgeType('CCPA')).toBe('ccpa'); | ||
| }); | ||
|
|
||
| // The digits alone must not classify an unrelated identifier. | ||
| it('does not misclassify ids that merely contain the standard digits', () => { | ||
| expect(mapCertificationToBadgeType('Catalog 19001')).toBeNull(); | ||
| expect(mapCertificationToBadgeType('ISO 90010')).toBeNull(); | ||
| }); | ||
|
|
||
| // Distinct 2701x standards must not earn the 27001 badge. | ||
| it('does not read ISO/IEC 27017 or 27018 as iso27001', () => { | ||
| expect(mapCertificationToBadgeType('ISO/IEC 27017:2015')).toBeNull(); | ||
| expect(mapCertificationToBadgeType('ISO/IEC 27018:2019')).toBeNull(); | ||
| }); | ||
|
|
||
| it('returns null for certifications with no badge', () => { | ||
| expect(mapCertificationToBadgeType('HDS')).toBeNull(); | ||
| expect(mapCertificationToBadgeType('')).toBeNull(); | ||
| }); | ||
| }); | ||
|
|
||
| describe('extractComplianceBadges', () => { | ||
| // CS-688 regression: Scaleway's verified certs must yield iso27001 + gdpr. | ||
| it('extracts verified badges and skips unrecognized certs', () => { | ||
| const badges = extractComplianceBadges({ | ||
| certifications: [ | ||
| { type: 'ISO/IEC 27001:2022', status: 'verified' }, | ||
| { type: 'HDS', status: 'verified' }, | ||
| { type: 'GDPR Compliance', status: 'verified' }, | ||
| ], | ||
| }); | ||
|
|
||
| const types = badges.map((b) => b.type); | ||
| expect(types).toContain('iso27001'); | ||
| expect(types).toContain('gdpr'); | ||
| expect(types).not.toContain('HDS'); | ||
| }); | ||
|
|
||
| it('ignores certifications that are not verified', () => { | ||
| const badges = extractComplianceBadges({ | ||
| certifications: [ | ||
| { type: 'ISO/IEC 27001:2022', status: 'expired' }, | ||
| { type: 'GDPR Compliance', status: 'verified' }, | ||
| ], | ||
| }); | ||
|
|
||
| expect(badges.map((b) => b.type)).toEqual(['gdpr']); | ||
| }); | ||
|
|
||
| it('de-duplicates repeated badge types', () => { | ||
| const badges = extractComplianceBadges({ | ||
| certifications: [ | ||
| { type: 'ISO/IEC 27001:2022', status: 'verified' }, | ||
| { type: 'ISO 27001:2013', status: 'verified' }, | ||
| ], | ||
| }); | ||
|
|
||
| expect(badges).toEqual([{ type: 'iso27001', verified: true }]); | ||
| }); | ||
|
|
||
| it('returns an empty array for missing or malformed data', () => { | ||
| expect(extractComplianceBadges(null)).toEqual([]); | ||
| expect(extractComplianceBadges({})).toEqual([]); | ||
| expect(extractComplianceBadges({ certifications: 'nope' })).toEqual([]); | ||
| }); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,101 @@ | ||
| /** | ||
| * Single source of truth for turning AI-extracted certification names | ||
| * (`GlobalVendors.riskAssessmentData.certifications[].type`) into Trust Portal | ||
| * compliance badge types. | ||
| * | ||
| * Both the admin vendor sync (`trust-portal.service.ts`) and the public, | ||
| * visitor-facing portal (`trust-access.service.ts`) derive badges through this | ||
| * module so the two surfaces can never disagree — the mismatch that caused | ||
| * CS-688 (Scaleway showed ISO 27001 in the admin Vendors tab but only GDPR on | ||
| * the public Trust Centre). | ||
| */ | ||
|
|
||
| export type ComplianceBadge = { | ||
| type: string; | ||
| verified: boolean; | ||
| }; | ||
|
|
||
| /** | ||
| * Whether a normalized cert string (lowercased, alphanumerics only) names the | ||
| * given ISO standard number. | ||
| * | ||
| * - Requires an "iso" / "iso iec" prefix, so unrelated ids that merely contain | ||
| * the digits ("19001", "127001") are not misclassified. | ||
| * - The optional "iec" handles joint ISO/IEC standards whose "IEC" infix would | ||
| * otherwise break the match ("ISO/IEC 27001:2022" -> "isoiec270012022"). | ||
| * - Allows an optional trailing 4-digit year ("ISO 9001:2015" -> "iso90012015") | ||
| * but forbids any other trailing digit, so a longer number is not read as a | ||
| * shorter standard ("ISO 90010" is not "ISO 9001", "ISO 27017" is not 27001). | ||
| * | ||
| * `standardNumber` is always a hard-coded digit literal — never user input — | ||
| * so building the RegExp from it carries no injection risk. | ||
| */ | ||
| function matchesIsoStandard(normalized: string, standardNumber: string): boolean { | ||
| return new RegExp(`iso(?:iec)?${standardNumber}(?:\\d{4})?(?!\\d)`).test( | ||
| normalized, | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * Map a single certification name to its canonical badge type, or `null` when | ||
| * the certification is not one we render a badge for. | ||
| */ | ||
| export function mapCertificationToBadgeType(certType: string): string | null { | ||
| // Strip every non-alphanumeric char (spaces, slashes, colons, underscores) | ||
| // so separator variants collapse to one form — "PCI DSS", "PCI-DSS" and | ||
| // "pci_dss" all become "pcidss". Matches below therefore never need to spell | ||
| // out separator variants. | ||
| const normalized = certType.toLowerCase().replace(/[^a-z0-9]/g, ''); | ||
|
|
||
| if (normalized.includes('soc2')) return 'soc2'; | ||
| if (normalized.includes('soc3')) return 'soc3'; | ||
| if (matchesIsoStandard(normalized, '27001')) return 'iso27001'; | ||
| if (matchesIsoStandard(normalized, '42001')) return 'iso42001'; | ||
| if (normalized.includes('gdpr')) return 'gdpr'; | ||
| if (normalized.includes('hipaa')) return 'hipaa'; | ||
| // "pcidss" covers "PCI DSS"; "paymentcard" covers the fully spelled-out | ||
| // "Payment Card Industry Data Security Standard" (kept in sync with the | ||
| // scan-time mappers in trigger/vendor/vendor-risk-assessment*). | ||
| if (normalized.includes('pcidss') || normalized.includes('paymentcard')) | ||
| return 'pci_dss'; | ||
| if (normalized.includes('nen7510')) return 'nen7510'; | ||
| if (matchesIsoStandard(normalized, '9001')) return 'iso9001'; | ||
| // soc3 / pipeda / ccpa were supported by the public portal's original mapper | ||
| // before consolidation; keep them so those vendors don't lose their public | ||
| // Trust Centre badges. The public portal renders them via label text and the | ||
| // admin UI safely skips badge types it has no icon for. | ||
| if (normalized.includes('pipeda')) return 'pipeda'; | ||
| if (normalized.includes('ccpa')) return 'ccpa'; | ||
|
|
||
| return null; | ||
|
cubic-dev-ai[bot] marked this conversation as resolved.
|
||
| } | ||
|
|
||
| /** | ||
| * Extract the deduplicated set of verified compliance badges from a | ||
| * `GlobalVendors.riskAssessmentData` object. Unrecognized certifications are | ||
| * skipped. Returns an empty array when there is nothing to map. | ||
| */ | ||
| export function extractComplianceBadges(data: unknown): ComplianceBadge[] { | ||
| if (!data || typeof data !== 'object') return []; | ||
|
|
||
| const certifications = (data as { certifications?: unknown }).certifications; | ||
| if (!Array.isArray(certifications)) return []; | ||
|
|
||
| const badges: ComplianceBadge[] = []; | ||
| const seenTypes = new Set<string>(); | ||
|
|
||
| for (const cert of certifications) { | ||
| if (!cert || typeof cert !== 'object') continue; | ||
|
|
||
| const { type, status } = cert as { type?: unknown; status?: unknown }; | ||
| if (status !== 'verified' || typeof type !== 'string') continue; | ||
|
|
||
| const badgeType = mapCertificationToBadgeType(type); | ||
| if (badgeType && !seenTypes.has(badgeType)) { | ||
| seenTypes.add(badgeType); | ||
| badges.push({ type: badgeType, verified: true }); | ||
| } | ||
| } | ||
|
|
||
| return badges; | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.