Skip to content

Eop card browser/m2 eop deck browsing#3181

Open
ayman-art wants to merge 39 commits into
masterfrom
eop-card-browser/m2-eop-deck-browsing
Open

Eop card browser/m2 eop deck browsing#3181
ayman-art wants to merge 39 commits into
masterfrom
eop-card-browser/m2-eop-deck-browsing

Conversation

@ayman-art

@ayman-art ayman-art commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

Description

Changes

Feature 1: EoP edition in the Card Browser UI

  • data/website/phrases/en/cards.json: EoP button, header and description

Feature 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.ts
  • src/domain/suit/order.ts, src/domain/card/order.ts

Feature 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 card
    routes
  • 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 the
    original design instead of copi:
    • 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
    • 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
    • suit colors sampled from the original design, fixing copi's Repudiation
      tab (dark red → gray) and Information Disclosure tab (yellow → blue)
    • suit name grows upward on wrap, keeping the description fixed as in the
      original design
    • static/images/eop-cards/ (78 PNGs): per-card images, the files are taken from
      copi;

Feature 5: STRIDE mappings component

  • src/lib/components/eopCardMapping.svelte: renders the stride field
  • cardPreview.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 keys

Feature 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 the
    5.1 deck with url: "https://cornucopia.owasp.org/cards/<id>" added to all 78
    cards
  • priv/repo/migrations/20260717100557_populate_eop_cards52.exs: imports the
    5.2 deck via the existing add_cards_to_database/2
  • lib/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 EoP
    cards that have a url, same as companion

Feature 8: Tests

  • deckService.test.ts, decService.integration.test.ts: EoP registration covered

  • Screenshots:

image image image image image image image image

Notes

  • Ace-first order: all source yamls list the Ace last, but the browser shows Ace first for every edition.
  • four files (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

  • My contribution does not include any AI-generated content
  • My contribution includes AI-generated content, as disclosed below:
    • AI Tools: [e.g. GitHub CoPilot, ChatGPT, JetBrains Junie etc.]
    • LLMs and versions: [e.g. GPT-4.1, Claude Haiku 4.5, Gemini 2.5 Pro etc.]
    • Prompts: [Summarize the key prompts or instructions given to the AI tools]

Affirmation

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_EOP and 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.

Comment thread cornucopia.owasp.org/src/lib/components/cardPreview.svelte
Comment thread cornucopia.owasp.org/src/lib/components/cardFound.svelte
@sydseter

sydseter commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

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
ayman-art marked this pull request as ready for review July 9, 2026 04:24
@ayman-art

Copy link
Copy Markdown
Collaborator Author

@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.
Noting that in the original design, the shapes are always stacking on watermarks (see information-disclosure-10 stacking below), that is why reversing the current stacking wouldn't be a perfect solution.

image image
image

@ayman-art
ayman-art requested review from rewtd and sydseter July 17, 2026 17:52
@sydseter

Copy link
Copy Markdown
Collaborator

@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. Noting that in the original design, the shapes are always stacking on watermarks (see information-disclosure-10 stacking below), that is why reversing the current stacking wouldn't be a perfect solution.

image image image

@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.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this be generated by a script?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is fine by me. As I said, it’s up to Grant and you. For now, this is still a great improvement.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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'}];

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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'])} />

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 = [

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very good.

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])),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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";

@sydseter sydseter Jul 18, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be moved out and made configurable later.

});

let eopSuits = $derived.by(() => {
const langSuits = suits?.get(`${VERSION_EOP}-${$lang}`);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here.

{#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 ?? ''}>&#9500;&#9472;&#9472; {cards?.get(card)?.id}</a>

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the OWASP Cornucopia prefix to the title might not make sense any more. How does this look like for the EoP cards?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps this could be taken from the incoming mapping or card data?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great!

<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 ?? ''}>&#9500;&#9472;&#9472; {cards?.get(card)?.id}</a>

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here as well. The link title needs to come from the card or mapping data.

function cardFound()
function cardFound()
{
let cards_options = [

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great!

return isKnownCardId(card?.id)
}

function getEdition(str: string) : string {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok for now, but this should also come from the card and mapping data.

function cardFound()
function cardFound()
{
let cards_options = [

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perfect.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be added here by the card browser script.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please remove the versioning assertions. It will make it harder to add new editions and doesn't really do anything important.

@sydseter
sydseter self-requested a review July 18, 2026 11:47
@sydseter
sydseter dismissed their stale review July 18, 2026 11:48

I won't require you to make any of these changes. It's up to Grant to prioritize.

@sydseter

Copy link
Copy Markdown
Collaborator

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"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should come from a configuration file that is generated up-front

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could be copied here by a script

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The migration script could probably be generated as well from config.

coming from card data

Signed-off-by: Ayman Algamal <ayman01287985950@gmail.com>
Signed-off-by: Ayman Algamal <ayman01287985950@gmail.com>
@sydseter

Copy link
Copy Markdown
Collaborator

@ayman-art you have a build failure, but the important issues seems to be resolved.

@ayman-art

ayman-art commented Jul 19, 2026

Copy link
Copy Markdown
Collaborator Author

@ayman-art you have a build failure, but the important issues seems to be resolved.

yes, it seems removing the unnecessary tests of latest versions values has reduced the test coverage to 94.7%
I will try to increase it again.
Edit: Now it is 95% as I added a test to verify only their type

Signed-off-by: Ayman Algamal <ayman01287985950@gmail.com>
sydseter
sydseter previously approved these changes Jul 19, 2026
@sydseter

Copy link
Copy Markdown
Collaborator

@ayman-art You have a conflict you need to resolve. Keep in mind the ordering of the cards has now changed.

… order

Signed-off-by: Ayman Algamal <ayman01287985950@gmail.com>
@ayman-art

Copy link
Copy Markdown
Collaborator Author

@ayman-art You have a conflict you need to resolve. Keep in mind the ordering of the cards has now changed.

Resolved. I merged master branch and updated the EoP card order to follow the new Ace-last convention.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants