Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions cornucopia.owasp.org/data/website/phrases/en/cards.json
Original file line number Diff line number Diff line change
Expand Up @@ -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 <a rel=\"noopener\" href=\"https://owasp.org/www-project-application-security-verification-standard/\">OWASP Application Security Verification Standard</a>, the <a rel=\"noopener\" href=\"https://owasp.org/www-project-web-security-testing-guide\">OWASP Web Security Testing Guide</a> and David Rook's <a rel=\"noopener\" href=\"/share/OWASP-SecureDevPrinciples-David-Rook.pdf\">Principles of Secure Development</a>. 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 <a rel=\"noopener\" href=\"https://mas.owasp.org/MASVS/\">MASVS categories</a>, 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"
},
Expand Down Expand Up @@ -55,6 +58,12 @@
"2": "Attacks"
}
},
"eopCardTaxonomy": {
"h1": {
"1": "Mappings",
"2": "Attacks"
}
},
"MASVSOverview": {
"p1": "No suitable mappings were found."
},
Expand Down
7 changes: 7 additions & 0 deletions cornucopia.owasp.org/src/domain/card/cardColor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,12 @@ export const cardColor = new Map<string,string>([
["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"],

])
79 changes: 79 additions & 0 deletions cornucopia.owasp.org/src/domain/card/order.ts
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,85 @@ export const cardOrder = new Map<string,number>([
["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],
Expand Down
7 changes: 7 additions & 0 deletions cornucopia.owasp.org/src/domain/suit/order.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,11 @@ export const order = new Map<string,number>([
["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],
]);
5 changes: 3 additions & 2 deletions cornucopia.owasp.org/src/domain/suit/suitController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, { lang: string[] }> = new Map<string, { lang: string[] }>([
['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<string,Suit[]>
Expand Down
20 changes: 12 additions & 8 deletions cornucopia.owasp.org/src/lib/components/cardFound.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, Component<any>> = {
webapp: WebAppCardTaxonomy,
mobileapp: MobileAppCardTaxonomy,
companion: CompanionCardTaxonomy,
eop: EopCardTaxonomy
};

interface Props {
mappingData: any;
Expand Down Expand Up @@ -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]);
</script>
<LanguagePicker
edition={card.edition}
Expand Down Expand Up @@ -74,14 +84,8 @@
<a title="How to play OWASP Cornucopia" class="link" href="/how-to-play">{$t('cards.cardFound.a')}</a>
<Concept card={card}></Concept>
<Explanation card={card}></Explanation>
{#if card.edition == 'webapp'}
<WebAppCardTaxonomy {card} {mappingData} {routes} {capecData} {asvsVersion}></WebAppCardTaxonomy>
{/if}
{#if card.edition == 'mobileapp'}
<MobileAppCardTaxonomy {card} {mappingData} {routes}></MobileAppCardTaxonomy>
{/if}
{#if card.edition == "companion"}
<CompanionCardTaxonomy {card} {mappingData} {routes}></CompanionCardTaxonomy>
{#if Taxonomy}
<Taxonomy {card} {mappingData} {routes} {capecData} {asvsVersion}></Taxonomy>
{/if}
Comment on lines +87 to 89
{#key card}
<ViewSourceOnGithub path={card.githubUrl}></ViewSourceOnGithub>
Expand Down
42 changes: 36 additions & 6 deletions cornucopia.owasp.org/src/lib/components/cardPreview.svelte
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
<script lang="ts">
import type { Component } from "svelte";
import type { Card } from "../../domain/card/card";
import { cardColor } from "../../domain/card/cardColor";
import MobileAppCardMapping from "./mobileAppCardMapping.svelte";
import CompanionCardMapping from "./companionCardMapping.svelte";

import EopCardMapping from "./eopCardMapping.svelte";

const mappingComponents: Record<string, Component<any>> = {
mobileapp: MobileAppCardMapping,
companion: CompanionCardMapping,
eop: EopCardMapping
};

interface Props {
card?: Card;
mapping: Record<string, unknown>;
Expand All @@ -12,6 +20,7 @@

let { card = $bindable(), mapping, style = '' }: Props = $props();
let previewStyle = $derived(style ? ' ' + style : '');
let MappingComponent = $derived(mappingComponents[card?.edition ?? '']);

function getSuitColor(suit : string, id: string)
{
Expand Down Expand Up @@ -57,11 +66,8 @@
{#if mapping}
<span class="property-card-number{previewStyle} {getTextColor(card?.suit, card?.suitId)}-text {getRoyalTextColor(card?.suit, card?.suitId, card?.value)}">{card?.card ?? card?.value}</span>
<p class="property-card-description{previewStyle}">{card?.desc}</p>
{#if card?.edition == 'mobileapp'}
<MobileAppCardMapping {mapping} {style}></MobileAppCardMapping>
{/if}
{#if card?.edition == 'companion'}
<CompanionCardMapping {mapping} {style}></CompanionCardMapping>
{#if MappingComponent}
<MappingComponent {mapping} {style}></MappingComponent>
{/if}
Comment on lines +69 to 71
{:else if card?.suitName == 'WILD CARD'}
<span class="property-card-number {getSuitColor(card.suit, card?.suitId)}">Joker</span>
Expand Down Expand Up @@ -286,6 +292,14 @@
.social-engineering {
background-color: #dbdbdb;
}
.spoofing,
.tampering,
.repudiation,
.information-disclosure,
.denial-of-service,
.elevation-of-privilege {
background-color: #dbdbdb;
}

.agentic-ai-text {
color: #3b2c42;
Expand All @@ -308,6 +322,14 @@
.social-engineering-text {
color: #dbdbdb;
}
.spoofing-text,
.tampering-text,
.repudiation-text,
.information-disclosure-text,
.denial-of-service-text,
.elevation-of-privilege-text {
color: #dbdbdb;
}

.agentic-ai-royal {
background-color: #3b2c4280;
Expand All @@ -330,6 +352,14 @@
.social-engineering-royal {
background-color: #e8e8e8;
}
.spoofing-royal,
.tampering-royal,
.repudiation-royal,
.information-disclosure-royal,
.denial-of-service-royal,
.elevation-of-privilege-royal {
background-color: #e8e8e8;
}

.white {
background-color: white;
Expand Down
57 changes: 57 additions & 0 deletions cornucopia.owasp.org/src/lib/components/eopCardMapping.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<script lang="ts">
interface Props {
mapping: Record<string, unknown>;
style?: string;
}
let { mapping, style = '' }: Props = $props();
let mappingStyle = $derived(style ? ' ' + style : '');
let stride = $derived(String(mapping?.stride ?? '') || '-');
</script>

<p class="mapping-title{mappingStyle}">STRIDE</p>
<p class="mapping-value{mappingStyle}">{stride}</p>

<style>
.mapping-title, .mapping-value {
font-size: 1.1vw;
margin: 0;
margin-left: .25rem;
margin-right: .25rem;
word-wrap: break-word;
white-space: initial;
}
.hero-card-container {
font-size: max(0.3vh, 0.6vw);
}
.mapping-title {
font-weight: bold;
}
.mapping-value {
border-bottom: 1px rgb(192, 192, 192) solid;
}
.mapping-title.browser-card-container,
.mapping-value.browser-card-container {
font-size: 0.7vw;
margin-left: 1vw;
margin-right: 1vw;
}
@media (max-width: 1024px) {
.mapping-title.browser-card-container,
.mapping-value.browser-card-container {
font-size: 2vw;
margin-left: 1vw;
margin-right: 1vw;
}
.mapping-title.browser-card-container:first-of-type {
margin-top: 2vw;
}
}
</style>
47 changes: 47 additions & 0 deletions cornucopia.owasp.org/src/lib/components/eopCardTaxonomy.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<script lang="ts">
import { GetCardAttacks, type Attack } from "$lib/cardAttacks";
import type { Card } from "../../domain/card/card";
import type { Route } from "../../domain/routes/route";
import { MappingController } from "../../domain/mapping/mappingController";
import MappingsList from "$lib/components/mappingsList.svelte";
import Attacks from "./attacks.svelte";
import { readTranslation } from "$lib/stores/stores";

interface Props {
mappingData: Record<string, unknown>;
card: Card;
routes: Map<string, Route[]>;
}

let { mappingData, card = $bindable(), routes: _routes }: Props = $props();

const controller = $derived(new MappingController(mappingData));
let t = readTranslation();

let mappings: Record<string, unknown> = $derived(controller.getCardMappings(card?.id));
let attacks: Attack[] = $derived(GetCardAttacks(card?.id) as Attack[]);

function formatValue(val: unknown): string[] {
if (val === undefined || val === null) return ["-"];
if (Array.isArray(val))
return val.length ? val.map((v) => String(v)) : ["-"];
return [String(val)];
}
</script>

{#if mappings}
<h2 id="mapping" class="title">{$t("cards.eopCardTaxonomy.h1.1")}</h2>
<MappingsList title="STRIDE:" mappings={formatValue(mappings['stride'])} />
<h2 class="title">{$t("cards.eopCardTaxonomy.h1.2")}</h2>
{#if attacks}
<Attacks {attacks} />
{/if}
{/if}

<style>
.title {
background: var(--background);
color: white;
padding: 0.5rem;
}
</style>
Loading
Loading