diff --git a/cornucopia.owasp.org/data/website/phrases/en/cards.json b/cornucopia.owasp.org/data/website/phrases/en/cards.json
index 76da6cdf7..a57289308 100644
--- a/cornucopia.owasp.org/data/website/phrases/en/cards.json
+++ b/cornucopia.owasp.org/data/website/phrases/en/cards.json
@@ -10,16 +10,19 @@
"button": {
"1": "Website App version",
"2": "Mobile App version",
- "3": "Companion version"
+ "3": "Companion version",
+ "4": "Elevation of Privilege version"
},
"h2": {
"1": "Website App version",
"2": "Mobile App version",
- "3": "Companion version"
+ "3": "Companion version",
+ "4": "Elevation of Privilege version"
},
"p2": "For the previously called Ecommerce Website Edition. Instead of EoP’s STRIDE suits, the suits were selected based on the structure of the OWASP Secure Coding Practices - Quick Reference Guide (SCP) which have been migrated to the OWASP Developer Guide Web Application Checklist. The content was mainly drawn from the SCP but with additional consideration of sections in the OWASP Application Security Verification Standard, the OWASP Web Security Testing Guide and David Rook's Principles of Secure Development. These provided five suits, and a sixth called “Cornucopia” was created for everything else:",
"p3": "The second Cornucopia deck, the “Mobile App Edition”, follows the same principles and game rules as the original OWASP Cornucopia, but has different suits based on the MASVS categories, in addition to the Cornucopia suit that contains threats related to mobile malware and privacy issues:",
"p4": "The third Cornucopia deck, the “Companion Edition”, is a companion to the other two decks:",
+ "p5": "The fourth Cornucopia deck, the “Elevation of Privilege Edition” (EoP), is a threat modeling game developed by Microsoft. Instead of the security concepts featured in the first editions, EoP card suits are structured around the STRIDE threat categories (Spoofing, Tampering, Repudiation, Information Disclosure, Denial of Service, and Elevation of Privilege):",
"card": {
"p1": "Back to overview"
},
@@ -55,6 +58,12 @@
"2": "Attacks"
}
},
+ "eopCardTaxonomy": {
+ "h1": {
+ "1": "Mappings",
+ "2": "Attacks"
+ }
+ },
"MASVSOverview": {
"p1": "No suitable mappings were found."
},
diff --git a/cornucopia.owasp.org/src/domain/card/cardColor.ts b/cornucopia.owasp.org/src/domain/card/cardColor.ts
index d7d58a4eb..a9ea785ab 100644
--- a/cornucopia.owasp.org/src/domain/card/cardColor.ts
+++ b/cornucopia.owasp.org/src/domain/card/cardColor.ts
@@ -24,5 +24,12 @@ export const cardColor = new Map([
["frontend", "frontend"],
["large-language-models", "large-language-models"],
["social-engineering", "social-engineering"],
+ // EoP cards
+ ["spoofing", "spoofing"],
+ ["tampering", "tampering"],
+ ["repudiation", "repudiation"],
+ ["information-disclosure", "information-disclosure"],
+ ["denial-of-service", "denial-of-service"],
+ ["elevation-of-privilege", "elevation-of-privilege"],
])
\ No newline at end of file
diff --git a/cornucopia.owasp.org/src/domain/card/order.ts b/cornucopia.owasp.org/src/domain/card/order.ts
index 6e33a81eb..9ff5f3900 100644
--- a/cornucopia.owasp.org/src/domain/card/order.ts
+++ b/cornucopia.owasp.org/src/domain/card/order.ts
@@ -247,6 +247,85 @@ export const cardOrder = new Map([
["BOTJ", 10],
["BOTQ", 11],
["BOTK", 12],
+ // EoP cards (ordered as in eop-cards-5.0-en.yaml: 2..K, Ace last)
+ ["SP2", 0],
+ ["SP3", 1],
+ ["SP4", 2],
+ ["SP5", 3],
+ ["SP6", 4],
+ ["SP7", 5],
+ ["SP8", 6],
+ ["SP9", 7],
+ ["SPX", 8],
+ ["SPJ", 9],
+ ["SPQ", 10],
+ ["SPK", 11],
+ ["SPA", 12],
+ ["TA2", 0],
+ ["TA3", 1],
+ ["TA4", 2],
+ ["TA5", 3],
+ ["TA6", 4],
+ ["TA7", 5],
+ ["TA8", 6],
+ ["TA9", 7],
+ ["TAX", 8],
+ ["TAJ", 9],
+ ["TAQ", 10],
+ ["TAK", 11],
+ ["TAA", 12],
+ ["RE2", 0],
+ ["RE3", 1],
+ ["RE4", 2],
+ ["RE5", 3],
+ ["RE6", 4],
+ ["RE7", 5],
+ ["RE8", 6],
+ ["RE9", 7],
+ ["REX", 8],
+ ["REJ", 9],
+ ["REQ", 10],
+ ["REK", 11],
+ ["REA", 12],
+ ["ID2", 0],
+ ["ID3", 1],
+ ["ID4", 2],
+ ["ID5", 3],
+ ["ID6", 4],
+ ["ID7", 5],
+ ["ID8", 6],
+ ["ID9", 7],
+ ["IDX", 8],
+ ["IDJ", 9],
+ ["IDQ", 10],
+ ["IDK", 11],
+ ["IDA", 12],
+ ["DS2", 0],
+ ["DS3", 1],
+ ["DS4", 2],
+ ["DS5", 3],
+ ["DS6", 4],
+ ["DS7", 5],
+ ["DS8", 6],
+ ["DS9", 7],
+ ["DSX", 8],
+ ["DSJ", 9],
+ ["DSQ", 10],
+ ["DSK", 11],
+ ["DSA", 12],
+ ["EP2", 0],
+ ["EP3", 1],
+ ["EP4", 2],
+ ["EP5", 3],
+ ["EP6", 4],
+ ["EP7", 5],
+ ["EP8", 6],
+ ["EP9", 7],
+ ["EPX", 8],
+ ["EPJ", 9],
+ ["EPQ", 10],
+ ["EPK", 11],
+ ["EPA", 12],
// Special cards
["JOA", 13],
["JOB", 14],
diff --git a/cornucopia.owasp.org/src/domain/suit/order.ts b/cornucopia.owasp.org/src/domain/suit/order.ts
index adbe9e113..a18cca922 100644
--- a/cornucopia.owasp.org/src/domain/suit/order.ts
+++ b/cornucopia.owasp.org/src/domain/suit/order.ts
@@ -20,4 +20,11 @@ export const order = new Map([
["devops", 17],
["social-engineering", 18],
["automated-threats", 19],
+ // EoP suits
+ ["spoofing", 20],
+ ["tampering", 21],
+ ["repudiation", 22],
+ ["information-disclosure", 23],
+ ["denial-of-service", 24],
+ ["elevation-of-privilege", 25],
]);
diff --git a/cornucopia.owasp.org/src/domain/suit/suitController.ts b/cornucopia.owasp.org/src/domain/suit/suitController.ts
index 8488ebf65..c8335b21b 100644
--- a/cornucopia.owasp.org/src/domain/suit/suitController.ts
+++ b/cornucopia.owasp.org/src/domain/suit/suitController.ts
@@ -5,11 +5,12 @@ import { cardOrder } from "$domain/card/order";
export class SuitController {
- private static readonly decks = [{edition: 'mobileapp', version: '1.1'}, {edition: 'webapp', version: '3.0'}, {edition: 'companion', version: '1.0'}];
+ private static readonly decks = [{edition: 'mobileapp', version: '1.1'}, {edition: 'webapp', version: '3.0'}, {edition: 'companion', version: '1.0'}, {edition: 'eop', version: '5.0'}];
private static languages : Map = new Map([
['mobileapp', {lang: ['en']}],
['webapp', {lang: ['en', 'es', 'fr', 'nl', 'no_nb', 'pt_br', 'pt_pt', 'ru', 'it']}],
- ['companion', {lang: ['en']}]
+ ['companion', {lang: ['en']}],
+ ['eop', {lang: ['en']}]
]);
public static getSuits() : Map
diff --git a/cornucopia.owasp.org/src/lib/components/cardFound.svelte b/cornucopia.owasp.org/src/lib/components/cardFound.svelte
index 9b1ff7e98..9c887198d 100644
--- a/cornucopia.owasp.org/src/lib/components/cardFound.svelte
+++ b/cornucopia.owasp.org/src/lib/components/cardFound.svelte
@@ -13,6 +13,15 @@
import { readTranslation } from "$lib/stores/stores";
import Concept from './concept.svelte';
import CompanionCardTaxonomy from './companionCardTaxonomy.svelte';
+ import EopCardTaxonomy from './eopCardTaxonomy.svelte';
+ import type { Component } from "svelte";
+
+ const taxonomies: Record> = {
+ webapp: WebAppCardTaxonomy,
+ mobileapp: MobileAppCardTaxonomy,
+ companion: CompanionCardTaxonomy,
+ eop: EopCardTaxonomy
+ };
interface Props {
mappingData: any;
@@ -41,6 +50,7 @@
let mappings = $derived(controller.getCardMappings(card.id));
let _attacks: Attack[] = $derived(GetCardAttacks(card.id));
const asvsVersion = $derived(card.version < '3.0' ? '4.0.3' : '5.0');
+ let Taxonomy = $derived(taxonomies[card.edition]);
{$t('cards.cardFound.a')}
- {#if card.edition == 'webapp'}
-
- {/if}
- {#if card.edition == 'mobileapp'}
-
- {/if}
- {#if card.edition == "companion"}
-
+ {#if Taxonomy}
+
{/if}
{#key card}
diff --git a/cornucopia.owasp.org/src/lib/components/cardPreview.svelte b/cornucopia.owasp.org/src/lib/components/cardPreview.svelte
index c5b3dc941..6a9846281 100644
--- a/cornucopia.owasp.org/src/lib/components/cardPreview.svelte
+++ b/cornucopia.owasp.org/src/lib/components/cardPreview.svelte
@@ -1,9 +1,17 @@
+
+STRIDE
+{stride}
+
+
diff --git a/cornucopia.owasp.org/src/lib/components/eopCardTaxonomy.svelte b/cornucopia.owasp.org/src/lib/components/eopCardTaxonomy.svelte
new file mode 100644
index 000000000..0632890fa
--- /dev/null
+++ b/cornucopia.owasp.org/src/lib/components/eopCardTaxonomy.svelte
@@ -0,0 +1,47 @@
+
+
+{#if mappings}
+ {$t("cards.eopCardTaxonomy.h1.1")}
+
+ {$t("cards.eopCardTaxonomy.h1.2")}
+ {#if attacks}
+
+ {/if}
+{/if}
+
+
diff --git a/cornucopia.owasp.org/src/lib/services/decService.integration.test.ts b/cornucopia.owasp.org/src/lib/services/decService.integration.test.ts
index 714a56475..095a497cb 100644
--- a/cornucopia.owasp.org/src/lib/services/decService.integration.test.ts
+++ b/cornucopia.owasp.org/src/lib/services/decService.integration.test.ts
@@ -39,6 +39,8 @@ describe('DeckService integration tests', () => {
expect((new DeckService()).getCardDataForEditionVersionLang('mobileapp', '1.1', 'en')).toBeDefined();
expect((new DeckService()).getCardDataForEditionVersionLang('companion', '1.0', 'en')).toBeDefined();
+ expect((new DeckService()).getCardDataForEditionVersionLang('eop', '5.0', 'en')).toBeDefined();
+ expect((new DeckService()).getCardDataForEditionVersionLang('eop', '5.0', 'en').size).toBe(78);
expect((new DeckService()).getCardDataForEditionVersionLang('webapp', '2.2', 'en').size).toBe(80);
expect((new DeckService()).getCardDataForEditionVersionLang('webapp', '3.0', 'ru').size).toBe(80);
@@ -53,8 +55,8 @@ describe('DeckService integration tests', () => {
expect((new DeckService()).getCardDataForEditionVersionLang('webapp', '2.2', 'pt_pt').size).toBe(80);
}, 30000);
- it("should return 238 cards.", async () => {
+ it("should return 316 cards.", async () => {
const cards = (new DeckService()).getCards('en');
- expect(cards.size).toBe(238);
+ expect(cards.size).toBe(316);
});
});
diff --git a/cornucopia.owasp.org/src/lib/services/deckService.test.ts b/cornucopia.owasp.org/src/lib/services/deckService.test.ts
index b6466098d..9c8c6c2ed 100644
--- a/cornucopia.owasp.org/src/lib/services/deckService.test.ts
+++ b/cornucopia.owasp.org/src/lib/services/deckService.test.ts
@@ -33,6 +33,10 @@ describe('DeckService tests', () => {
expect(DeckService.hasEdition('companion')).toBe(true);
});
+ it('should return true for eop edition', () => {
+ expect(DeckService.hasEdition('eop')).toBe(true);
+ });
+
it('should return false for unknown edition', () => {
expect(DeckService.hasEdition('unknown')).toBe(false);
});
@@ -59,6 +63,10 @@ describe('DeckService tests', () => {
expect(DeckService.hasVersion('companion', '1.0')).toBe(true);
});
+ it('should return true for eop version 5.0', () => {
+ expect(DeckService.hasVersion('eop', '5.0')).toBe(true);
+ });
+
it('should return false for invalid version', () => {
expect(DeckService.hasVersion('webapp', '1.0')).toBe(false);
});
@@ -113,6 +121,10 @@ describe('DeckService tests', () => {
expect(DeckService.hasLanguage('companion', 'en')).toBe(true);
});
+ it('should return true for eop with en', () => {
+ expect(DeckService.hasLanguage('eop', 'en')).toBe(true);
+ });
+
it('should return false for mobileapp with es', () => {
expect(DeckService.hasLanguage('mobileapp', 'es')).toBe(false);
});
@@ -129,9 +141,10 @@ describe('DeckService tests', () => {
describe('getDecks', () => {
it('should return all available decks', () => {
const decks = DeckService.getDecks();
- expect(decks).toHaveLength(5);
+ expect(decks).toHaveLength(6);
expect(decks).toContainEqual({ edition: 'mobileapp', version: '1.1', lang: ['en', 'hi', 'uk'] });
expect(decks).toContainEqual({ edition: 'companion', version: '1.0', lang: ['en'] });
+ expect(decks).toContainEqual({ edition: 'eop', version: '5.0', lang: ['en'] });
expect(decks).toContainEqual({ edition: 'dbd', version: '1.0', lang: ['en'] });
expect(decks).toContainEqual({
edition: 'webapp',
@@ -155,6 +168,10 @@ describe('DeckService tests', () => {
expect(DeckService.getLatestVersion('companion')).toBe('1.0');
});
+ it('should return 5.0 for eop', () => {
+ expect(DeckService.getLatestVersion('eop')).toBe('5.0');
+ });
+
it('should return 3.0 as default for unknown edition', () => {
expect(DeckService.getLatestVersion('unknown')).toBe('3.0');
});
@@ -163,10 +180,11 @@ describe('DeckService tests', () => {
describe('getLatestEditions', () => {
it('should return array of latest editions', () => {
const editions = DeckService.getLatestEditions();
- expect(editions).toHaveLength(3);
+ expect(editions).toHaveLength(4);
expect(editions).toContain('webapp');
expect(editions).toContain('mobileapp');
expect(editions).toContain('companion');
+ expect(editions).toContain('eop');
});
}, 10000);
@@ -194,6 +212,11 @@ describe('DeckService tests', () => {
expect(languages).toContain('en');
});
+ it('should return en for eop', () => {
+ const languages = DeckService.getLanguages('eop');
+ expect(languages).toContain('en');
+ });
+
it('should return en for dbd', () => {
const languages = DeckService.getLanguages('dbd');
expect(languages).toContain('en');
@@ -225,6 +248,11 @@ describe('DeckService tests', () => {
expect(languages).toEqual(['en']);
});
+ it('should return only en for eop version 5.0', () => {
+ const languages = DeckService.getLanguagesForEditionVersion('eop', '5.0');
+ expect(languages).toEqual(['en']);
+ });
+
it('should return empty array for unknown version', () => {
const languages = DeckService.getLanguagesForEditionVersion('webapp', '1.0');
expect(languages).toEqual([]);
@@ -254,6 +282,11 @@ describe('DeckService tests', () => {
expect(versions).toEqual(['1.0']);
});
+ it('should return version for eop', () => {
+ const versions = DeckService.getVersions('eop');
+ expect(versions).toEqual(['5.0']);
+ });
+
it('should return empty array for unknown edition', () => {
const versions = DeckService.getVersions('unknown');
expect(versions).toEqual([]);
diff --git a/cornucopia.owasp.org/src/lib/services/deckService.ts b/cornucopia.owasp.org/src/lib/services/deckService.ts
index 4f43721a6..059bb9c2d 100644
--- a/cornucopia.owasp.org/src/lib/services/deckService.ts
+++ b/cornucopia.owasp.org/src/lib/services/deckService.ts
@@ -16,13 +16,15 @@ export class DeckService {
private static readonly latests: Deck[] = [
{ lang: ['en', 'hi', 'uk'], edition: 'mobileapp', version: '1.1' },
{ lang: ['en', 'es', 'fr', 'nl', 'no_nb', 'pt_br', 'pt_pt', 'ru', 'it', 'hi', 'uk'], edition: 'webapp', version: '3.0' },
- { lang: ['en'], edition: 'companion', version: '1.0' }
+ { lang: ['en'], edition: 'companion', version: '1.0' },
+ { lang: ['en'], edition: 'eop', version: '5.0' }
];
private static readonly decks: Deck[] = [
{ edition: 'mobileapp', version: '1.1', lang: ['en', 'hi', 'uk'] },
{ edition: 'webapp', version: '3.0', lang: ['en', 'es', 'fr', 'nl', 'no_nb', 'pt_br', 'pt_pt', 'ru', 'it', 'hi', 'uk'] },
{ edition: 'webapp', version: '2.2', lang: ['en', 'es', 'fr', 'nl', 'no_nb', 'pt_br', 'pt_pt', 'ru', 'it'] },
{ edition: 'companion', version: '1.0', lang: ['en'] },
+ { edition: 'eop', version: '5.0', lang: ['en'] },
{ edition: 'dbd', version: '1.0', lang: ['en'] }
];
diff --git a/cornucopia.owasp.org/src/lib/services/deckServiceConsts.ts b/cornucopia.owasp.org/src/lib/services/deckServiceConsts.ts
index 7b9ddc695..f37bcce68 100644
--- a/cornucopia.owasp.org/src/lib/services/deckServiceConsts.ts
+++ b/cornucopia.owasp.org/src/lib/services/deckServiceConsts.ts
@@ -4,4 +4,5 @@
export const VERSION_WEBAPP = "webapp";
export const VERSION_MOBILEAPP = "mobileapp";
export const VERSION_COMPANION = "companion";
-export const VERSION_DBD = "dbd";
\ No newline at end of file
+export const VERSION_DBD = "dbd";
+export const VERSION_EOP = "eop";
\ No newline at end of file
diff --git a/cornucopia.owasp.org/src/routes/cards/+page.server.ts b/cornucopia.owasp.org/src/routes/cards/+page.server.ts
index cd75ea94c..7cf78e3d4 100644
--- a/cornucopia.owasp.org/src/routes/cards/+page.server.ts
+++ b/cornucopia.owasp.org/src/routes/cards/+page.server.ts
@@ -2,21 +2,23 @@ import { DeckService } from '$lib/services/deckService';
import { SuitController } from '../../domain/suit/suitController';
import { FileSystemHelper } from '$lib/filesystem/fileSystemHelper';
import { MappingService } from '$lib/services/mappingService';
-
+
export const load = (({ params: _params }) => {
const deckService = new DeckService();
const decks = new Map>();
const lang = 'en';
const mobileCards = deckService.getCardDataForEditionVersionLang(
- 'mobileapp', DeckService.getLatestVersion('mobileapp'), lang);
+ 'mobileapp', DeckService.getLatestVersion('mobileapp'), lang);
const webappCards = deckService.getCardDataForEditionVersionLang(
'webapp', DeckService.getLatestVersion('webapp'), lang);
const companionCards = deckService.getCardDataForEditionVersionLang(
'companion', DeckService.getLatestVersion('companion'), lang);
-
+ const eopCards = deckService.getCardDataForEditionVersionLang(
+ 'eop', DeckService.getLatestVersion('eop'), lang);
+
return {
- suits : SuitController.getSuits(),
- decks : decks.set(lang, new Map([...mobileCards, ...webappCards, ...companionCards])),
+ suits: SuitController.getSuits(),
+ decks: decks.set(lang, new Map([...mobileCards, ...webappCards, ...companionCards, ...eopCards])),
mappingData: (new MappingService()).getCardMappingForLatestEdtions(),
content: FileSystemHelper.getDataFromPath('data/website/pages/cards')
};
diff --git a/cornucopia.owasp.org/src/routes/cards/+page.svelte b/cornucopia.owasp.org/src/routes/cards/+page.svelte
index ac1b258b9..deea05955 100644
--- a/cornucopia.owasp.org/src/routes/cards/+page.svelte
+++ b/cornucopia.owasp.org/src/routes/cards/+page.svelte
@@ -9,7 +9,7 @@
import { readLang, readTranslation } from '$lib/stores/stores';
import type { Suit } from "../../domain/suit/suit.js";
import { SvelteMap } from 'svelte/reactivity';
- import { VERSION_WEBAPP, VERSION_MOBILEAPP, VERSION_COMPANION } from "$lib/services/deckServiceConsts";
+ import { VERSION_WEBAPP, VERSION_MOBILEAPP, VERSION_COMPANION, VERSION_EOP } from "$lib/services/deckServiceConsts";
interface Props {
data: PageData;
@@ -40,6 +40,11 @@
return langSuits || suits?.get(`${VERSION_COMPANION}-en`) as Suit[];
});
+ let eopSuits = $derived.by(() => {
+ const langSuits = suits?.get(`${VERSION_EOP}-${$lang}`);
+ return langSuits || suits?.get(`${VERSION_EOP}-en`) as Suit[];
+ });
+
let version : string = $state(VERSION_WEBAPP);
let _suit : string;
let card : Card = $derived(cards?.get('VE2') as Card);
@@ -68,6 +73,11 @@
{
if (companionSuits !== undefined && typeof companionSuits[i] !== 'undefined') map.set(companionSuits[i]?.name,expand);
}
+
+ for(let i = 0 ; i < eopSuits?.length ; i++)
+ {
+ if (eopSuits !== undefined && typeof eopSuits[i] !== 'undefined') map.set(eopSuits[i]?.name,expand);
+ }
}
function toggle(suit : string)
@@ -91,6 +101,9 @@
if(version == VERSION_COMPANION)
card = cards?.get('AAI2') as Card;
+
+ if(version == VERSION_EOP)
+ card = cards?.get('SP2') as Card;
}
@@ -120,6 +133,7 @@
+
@@ -150,6 +164,13 @@
{@html $t('cards.p4')}
{/if}
+ {#if version == VERSION_EOP}
+
{$t('cards.h2.4')}
+
+
+ {@html $t('cards.p5')}
+
+ {/if}
@@ -195,6 +216,20 @@
{/if}
{/each}
{/if}
+
+ {#if version == VERSION_EOP}
+ {#each eopSuits as suit (suit.name)}
+
+
toggle(suit.name)} onclick={()=>toggle(suit.name)}>└── {Text.Format(suit.name).toUpperCase()}
+ {#if map?.get(suit.name)}
+ {#each suit.cards as card (card)}
+
{enter(suit.name,cards?.get(card)?.id)}}>
+ ├── {cards?.get(card)?.id}
+
+ {/each}
+ {/if}
+ {/each}
+ {/if}
@@ -260,7 +295,28 @@
{/each}
{/each}
-
-
+
+
+
+
+
{$t('cards.h2.4')}
+
+
+ {@html $t('cards.p5')}
+
+ {#each eopSuits as suit (suit.name)}
+
+
+
+ {/each}
+
+