Eop card browser/m2 eop deck browsing#3181
Conversation
…as in source files
…witch as a default card
…d its children ([version]/[lang])
There was a problem hiding this comment.
Pull request overview
This PR adds support for browsing and rendering the “Elevation of Privilege” (EoP) deck in the SvelteKit website (new edition constant, deck loading, UI toggles, taxonomy/mapping rendering, and i18n strings).
Changes:
- Add
VERSION_EOPand wire EoP (eop,5.0) into deck/suit loading and existing browsing routes. - Extend card-code validation lists and update ordering/color mappings for new EoP suits/cards.
- Introduce EoP-specific UI components (taxonomy + mapping) and extend tests to cover the new deck.
Reviewed changes
Copilot reviewed 21 out of 21 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| cornucopia.owasp.org/src/routes/edition/[edition]/+page.svelte | Adds EoP as a selectable edition and renders its description/suits. |
| cornucopia.owasp.org/src/routes/edition/[edition]/[card]/+page.svelte | Extends allowed card IDs to include EoP codes and labels edition name for eop. |
| cornucopia.owasp.org/src/routes/edition/[edition]/[card]/[version]/+page.svelte | Extends allowed card IDs to include EoP codes and labels edition name for eop. |
| cornucopia.owasp.org/src/routes/edition/[edition]/[card]/[version]/[lang]/+page.svelte | Extends allowed card IDs to include EoP codes and labels edition name for eop. |
| cornucopia.owasp.org/src/routes/cards/+page.svelte | Adds EoP suits to the cards browser UI, preview selection, and noscript output. |
| cornucopia.owasp.org/src/routes/cards/+page.server.ts | Loads EoP deck cards server-side and merges into the deck map. |
| cornucopia.owasp.org/src/routes/cards/[card]/+page.svelte | Extends allowed card IDs to include EoP codes and labels edition name for eop. |
| cornucopia.owasp.org/src/routes/cards/[card]/+page.server.ts | Loads EoP deck cards server-side for per-card pages. |
| cornucopia.owasp.org/src/lib/services/decService.integration.test.ts | Updates integration expectations for EoP deck presence and total card counts. |
| cornucopia.owasp.org/src/lib/services/deckServiceConsts.ts | Adds VERSION_EOP. |
| cornucopia.owasp.org/src/lib/services/deckService.ts | Registers EoP deck metadata (latest + available decks). |
| cornucopia.owasp.org/src/lib/services/deckService.test.ts | Adds unit tests for EoP edition/version/language and adjusts deck counts. |
| cornucopia.owasp.org/src/lib/components/eopCardTaxonomy.svelte | New: taxonomy display for EoP cards (mappings + attacks). |
| cornucopia.owasp.org/src/lib/components/eopCardMapping.svelte | New: compact STRIDE mapping renderer for EoP cards in previews. |
| cornucopia.owasp.org/src/lib/components/cardPreview.svelte | Refactors mapping rendering to support EoP mapping component. |
| cornucopia.owasp.org/src/lib/components/cardFound.svelte | Refactors taxonomy rendering to support EoP taxonomy component. |
| cornucopia.owasp.org/src/domain/suit/suitController.ts | Adds EoP deck/language metadata for suit discovery. |
| cornucopia.owasp.org/src/domain/suit/order.ts | Adds suit ordering for STRIDE suit names. |
| cornucopia.owasp.org/src/domain/card/order.ts | Adds per-suit ordering for EoP card IDs. |
| cornucopia.owasp.org/src/domain/card/cardColor.ts | Adds color keys for STRIDE suit names. |
| cornucopia.owasp.org/data/website/phrases/en/cards.json | Adds EoP copy + i18n keys for button/heading and EoP taxonomy headings. |
|
You have the card texts, and the cards are browsable. No styling is applied, but I guess that is what you are working on next? |
|
@ayman-art, this is a great start. I would like you to take this a step further. The css for copi.owasp.org was never really in line with the original graphics of the EoP card game. F.ex. We are lacking the numbers in the background on the cards. Which is evident from looking at https://github.com/adamshostack/eop/blob/master/EoP_Card%20Game%20Images.pdf You should more faithfully try to reproduce the design, as it was initially conceived for the physical cards. |
Thanks @sydseter! I'll rework the EoP card styling in this PR to reproduce the original card design as faithfully as possible, focusing on the card sizing and the background numbers that are missing in copi. I'll also keep the CSS structured to make the later customisation work easier. |
| end | ||
|
|
||
|
|
||
| def update_card_urls(path) do |
There was a problem hiding this comment.
What is the intention behind this new migration method?
What are you trying to do?
Tje existing code alteady handles card migrations.
If you need to import the urls you should simply update the version number of the yaml file.
There was a problem hiding this comment.
I added update_card_urls to fill the urls for cards already in existing database, as the populate_cards method skips existing rows, so only editing the yaml wouldn't update them.
So the standard pattern to follow is adding a new yaml with a higher version: I'll drop the new method and migration, and add the urls in a new eop-cards-5.2-en.yaml.
So should the new version be 5.2 (just above the current highest version "5.1")?
There was a problem hiding this comment.
Spoke to @ayman-art and they will reuse the existing method and clean this one up. @ayman-art go with 5.2 for this migration.
I do wonder if we are now conflating EoP internal versioning with our version for the cards as they move through different iterations? For now, let's treat this as our internal version (assuming that's not what it was already).
|
|
||
| def change do | ||
| update_card_urls(Path.join(:code.priv_dir(:copi), "/repo/cornucopia/eop-cards-5.0-en.yaml")) | ||
| update_card_urls(Path.join(:code.priv_dir(:copi), "/repo/cornucopia/eop-cards-5.1-en.yaml")) |
There was a problem hiding this comment.
You are importing the cards twice. Once with v5.0 and once with v5.1. It is worth considering what is happening here. Pay attention to the version number in the meta data in the card yaml file. Does the meta -> version have the correct version number? Is it higher than tje existing number that has been used previously?
There was a problem hiding this comment.
You shouldn’t need to import the data twice.
This included: - Divided the styles into elements (watermark, artwork, number tab, text block); the watermark only renders on non-royal cards - Royal (J/Q/K) backgrounds come from the card images; the CSS color is just a loading fallback - Used cqw sizing with the original 413/713 ratio, replacing copi's fixed sizes and per-card offsets - Geometry and font sizes referenced from the original design - Sampled suit colors from the original design, fixing copi's Repudiation tab (dark red -> gray) and Information Disclosure tab (yellow -> blue) - Made suit name grow upward on wrap, keeping the description fixed as in the original design Signed-off-by: Ayman Algamal <aymanalgamal22@gmail.com>
…b.com/OWASP/cornucopia into eop-card-browser/m2-eop-deck-browsing
…le for mantainability
| end | ||
|
|
||
|
|
||
| def update_card_urls(path) do |
There was a problem hiding this comment.
Spoke to @ayman-art and they will reuse the existing method and clean this one up. @ayman-art go with 5.2 for this migration.
I do wonder if we are now conflating EoP internal versioning with our version for the cards as they move through different iterations? For now, let's treat this as our internal version (assuming that's not what it was already).
…sting card migration method Signed-off-by: Ayman Algamal <ayman01287985950@gmail.com>
Until now, we have used the official versioning. That said. I have been trying to help Adam with his project for some time without success. I think it is safe to assume that eop will live on separately here. |
|
@rewtd, @sydseter We are close now. That said, there are 4 asset images having a white background instead of transparent (elevation-of-privilege-{2,3,4}.png, tampering-2.png) (See tampering-2 vs tampering 3 below), which hides the watermark. I have several options here. Finding a way to fix those 4 pngs, OR extracting them from the PDF as png, OR handling those cards in css as a workaround. What do you think makes more sense? I won't suggest the last one as we want css to be clean for the later refactoring work. |
@ayman-art First of all, fantastic work. I love it. I believe we could implement a way to handle exceptions, which should be treated as an optional rendering capability. How this should be done is not that important. It could be just a combination of using an image as a background with some css to hold it in place to make it easy to add the card, but it should probably be an option, not the default. |
There was a problem hiding this comment.
Could this be generated by a script?
There was a problem hiding this comment.
Absolutely! I believe a main goal of this project is to provide extensibility and make importing new games into the card browser as easy as possible with minimal additions required. That's why the scaffolding script was probably a good idea. That said, styling was the initial priority for this PR, and yes, some of these additions can be automated as you mentioned. I'll keep this in mind, maybe for a separate PR later, to keep this milestone focused on styling.
There was a problem hiding this comment.
That is fine by me. As I said, it’s up to Grant and you. For now, this is still a great improvement.
There was a problem hiding this comment.
Same here. Could this be generated by a script? Not saying that you need to do this right now. This pr is fine as it is. Just food for thought.
There was a problem hiding this comment.
This as well. Possible to be rendered by a Python script right?
| 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'}]; |
There was a problem hiding this comment.
This, as well, could be moved out and created up front as a configuration that the suiController just consumes.
| } | ||
| </style> | ||
|
|
||
| /* EoP card styles derived from the original PDF design, See |
There was a problem hiding this comment.
This should preferably be externalised and generated as a stylesheet that the cardPreview.svelte uses.
|
|
||
| {#if mappings} | ||
| <h2 id="mapping" class="title">{$t("cards.eopCardTaxonomy.h1.1")}</h2> | ||
| <MappingsList title="STRIDE:" mappings={formatValue(mappings['stride'])} /> |
There was a problem hiding this comment.
This as well. The eopCardTaxonomy.svelte should be generalised so that it can read any type of taxonomy configuration JSON object and create the correct taxonomy based on how it looks. A separate taxonomy attribute in the YAML mapping could be used for this.
| function cardFound() | ||
| function cardFound() | ||
| { | ||
| let cards_options = [ |
| 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])), |
There was a problem hiding this comment.
Instead of ...mobileCards, ...webappCards, ...companionCards, ...eopCards this should simply be: ...decks.
For later. Not now.
| 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"; |
There was a problem hiding this comment.
Should be moved out and made configurable later.
| }); | ||
| let eopSuits = $derived.by(() => { | ||
| const langSuits = suits?.get(`${VERSION_EOP}-${$lang}`); |
| {#if map?.get(suit.name)} | ||
| {#each suit.cards as card (card)} | ||
| <p onmouseenter={()=>{enter(suit.name,cards?.get(card)?.id)}}> | ||
| <a title="OWASP Cornucopia {Text.Format(suit.name).toUpperCase()}, {cards?.get(card)?.id}" href={cards?.get(card)?.url ?? ''}>├── {cards?.get(card)?.id}</a> |
There was a problem hiding this comment.
the OWASP Cornucopia prefix to the title might not make sense any more. How does this look like for the EoP cards?
There was a problem hiding this comment.
Perhaps this could be taken from the incoming mapping or card data?
There was a problem hiding this comment.
Right now the tooltip shows "OWASP Cornucopia SPOOFING, SP2" for the SP2 card, so yes, the prefix is misleading here.
I can take it from the card data as you suggested: adding an editionName field to the card model, populated by deckService from a per-edition name map, and using it in the link titles. The title becomes "Elevation of Privilege SPOOFING, SP2" while the other decks keep their current text.
| <div class="card-buttons"> | ||
| {#each suit.cards as card (card)} | ||
| <p> | ||
| <a title="OWASP Cornucopia card: {cards?.get(card)?.id} from suit: {Text.Format(suit.name).toUpperCase()}" href={cards?.get(card)?.url ?? ''}>├── {cards?.get(card)?.id}</a> |
There was a problem hiding this comment.
here as well. The link title needs to come from the card or mapping data.
| function cardFound() | ||
| function cardFound() | ||
| { | ||
| let cards_options = [ |
| return isKnownCardId(card?.id) | ||
| } | ||
|
|
||
| function getEdition(str: string) : string { |
There was a problem hiding this comment.
Ok for now, but this should also come from the card and mapping data.
| function cardFound() | ||
| function cardFound() | ||
| { | ||
| let cards_options = [ |
There was a problem hiding this comment.
Should be added here by the card browser script.
There was a problem hiding this comment.
same here. Later, this should be copied here by the card browser script. All assets could come from a specific folder containing the yaml, images, css and taxonomy needed to create the card browser scaffolding.
| assert Show.latest_version("webapp") == "3.0" | ||
| assert Show.latest_version("ecommerce") == "1.22" | ||
| assert Show.latest_version("eop") == "5.1" | ||
| assert Show.latest_version("eop") == "5.2" |
There was a problem hiding this comment.
I am not sure this test makes sense. I think we should remove the latest_version assertions.
| assert Show.latest_version("cumulus") == "1.1" | ||
| assert Show.latest_version("masvs") == "1.1" | ||
| assert Show.latest_version("eop") == "5.1" | ||
| assert Show.latest_version("eop") == "5.2" |
There was a problem hiding this comment.
remove all versioning assertions.
| assert Show.latest_version("cumulus") == "1.1" | ||
| assert Show.latest_version("masvs") == "1.1" | ||
| assert Show.latest_version("eop") == "5.1" | ||
| assert Show.latest_version("eop") == "5.2" |
There was a problem hiding this comment.
please remove the versioning assertions. It will make it harder to add new editions and doesn't really do anything important.
I won't require you to make any of these changes. It's up to Grant to prioritize.
|
I would look at the title fields for the links before merging. The OWASP Cornucopia prefix will be misleading for the EoP cards in the card browser. |
| "cumulus" -> "1.1" | ||
| "masvs" -> "1.1" | ||
| "eop" -> "5.1" | ||
| "eop" -> "5.2" |
There was a problem hiding this comment.
This should come from a configuration file that is generated up-front
There was a problem hiding this comment.
Could be copied here by a script
There was a problem hiding this comment.
The migration script could probably be generated as well from config.






Description
Changes
Feature 1: EoP edition in the Card Browser UI
data/website/phrases/en/cards.json: EoP button, header and descriptionFeature 2: EoP deck and suit registration
Registered eop 5.0/en; suits in source order (SP, TA, RE, ID, DS, EP), starting with Ace like the other editions.
src/lib/services/deckService.ts,deckServiceConsts.ts,src/domain/suit/suitController.tssrc/domain/suit/order.ts,src/domain/card/order.tsFeature 3: Card routes for all 78 EoP cards
SP2 is the default card on version switch (as AAI2 for Companion).
src/domain/card/cardIds.ts: shared card id allowlist used by all cardroutes
src/routes/cards/+page.server.ts,+page.svelte,src/routes/cards/[card]/…src/routes/edition/[edition]/+page.svelte,edition/[edition]/[card]/…and its
[version]/[lang]children (cards_options)Feature 4: EoP card styling faithful to the printed deck
The styles were initially ported from copi, then reworked to match the original deck: element
sizes, positions, fonts and colors are derived from the official EoP card
PDF.
src/lib/components/cardPreview.svelte: reworked EoP styles to match theoriginal design instead of copi:
block); the watermark only renders on non-royal cards
just a loading fallback
and per-card offsets
tab (dark red → gray) and Information Disclosure tab (yellow → blue)
original design
static/images/eop-cards/(78 PNGs): per-card images, the files are taken fromcopi;
Feature 5: STRIDE mappings component
src/lib/components/eopCardMapping.svelte: renders the stride fieldcardPreview.svelte: mappings refactored via a component map so that future editions will require changing one line.Feature 6: Card taxonomy
Threat Modeling template from M1, Mappings and Attacks sections, same structure as Companion.
src/lib/components/eopCardTaxonomy.svelte,cardFound.svelte(same component-map refactor)data/website/phrases/en/cards.json: two translation keysFeature 7: Copi cards link to cornucopia card pages
EoP cards in copi games now show a "Need more info?" link that opens the card's
page on cornucopia.owasp.org to help players know more about the card.
priv/repo/cornucopia/eop-cards-5.2-en.yaml: new deck version as a copy of the5.1 deck with
url: "https://cornucopia.owasp.org/cards/<id>"added to all 78cards
priv/repo/migrations/20260717100557_populate_eop_cards52.exs: imports the5.2 deck via the existing
add_cards_to_database/2lib/copi_web/live/game_live/show.ex:latest_version("eop")increased to"5.2" so game sessions use the new deck (tests updated accordingly)
lib/copi_web/controllers/card_html/show.html.heex: shows the link on EoPcards that have a url, same as companion
Feature 8: Tests
deckService.test.ts,decService.integration.test.ts: EoP registration coveredScreenshots:
Notes
elevation-of-privilege-{2,3,4}.png,tampering-2.png)had an opaque white background that hid the watermark
Resolved or fixed issue: none (partial progress on #1322)
AI Tool Disclosure
[e.g. GitHub CoPilot, ChatGPT, JetBrains Junie etc.][e.g. GPT-4.1, Claude Haiku 4.5, Gemini 2.5 Pro etc.][Summarize the key prompts or instructions given to the AI tools]Affirmation