diff --git a/docs/banner.svg b/docs/banner.svg index b36ee38..9716b88 100644 --- a/docs/banner.svg +++ b/docs/banner.svg @@ -1,4 +1,5 @@ + @@ -32,9 +33,10 @@ OWASP SOURCE LISTS - LLM01-10 Prompt Injection... - ASI01-10 Agent Goal Hijack... - DSGAI01-21 Data Leakage... + LLM01-10 Prompt Injection... + ASI01-10 Agent Goal Hijack... + DSGAI01-21 Data Leakage... + AST01-10 Malicious Skills... @@ -43,9 +45,9 @@ CROSSWALK ENGINE - 67 mapping files - BGE + cross-encoder classifier - 25 framework registries + 77 mapping files + scripts/generate.js + 27 framework registries @@ -67,31 +69,31 @@ - 25 + 26 FRAMEWORKS - 1,514 - CONTROLS + 4 + SOURCE LISTS - 41 + 51 ENTRIES - 3,210 + 3,771 MAPPINGS - 114 + 147 INCIDENTS - 67 + 77 MAPPING FILES - CC BY-SA 4.0 · genai-security-project.github.io/GenAI-Data-Security-Initiative - Created by Emmanuel Guilherme Junior · OWASP GenAI Data Security Initiative + CC BY-SA 4.0 · genai-security-project.github.io/crosswalk + OWASP GenAI Data Security Initiative \ No newline at end of file diff --git a/docs/index.html b/docs/index.html index cd15cf7..b3df0cb 100644 --- a/docs/index.html +++ b/docs/index.html @@ -1859,7 +1859,8 @@

AI Security Standards Crosswalk

{ icon: '\uD83D\uDD0D', title: 'Explore risks & controls', - desc: 'Search 41 OWASP entries, filter by severity, click any entry to see all 25 framework mappings.', + desc: 'Search ' + DATA.length + ' OWASP entries, filter by severity, click any entry to see its mappings across all ' + + FRAMEWORKS.length + ' frameworks.', link: '#/explorer', cta: 'Open explorer \u2192', who: 'Security engineers, architects, auditors' @@ -5654,7 +5655,7 @@

AI Security Standards Crosswalk

page.appendChild(el('h2', {}, 'Submit a Standard')); page.appendChild(el('p', { style: 'color:var(--text-secondary);margin-bottom:20px;' }, - 'Submit a security framework for automated mapping to all 41 OWASP GenAI entries. ' + + 'Submit a security framework for automated mapping to all ' + DATA.length + ' OWASP GenAI entries. ' + 'Paste your framework controls as JSON below. Our classifier will propose mappings and open a PR for review.')); // Example template diff --git a/docs/og-image.svg b/docs/og-image.svg index e8143e3..6137dc2 100644 --- a/docs/og-image.svg +++ b/docs/og-image.svg @@ -1,4 +1,5 @@ + @@ -17,33 +18,33 @@ - 25 + 26 FRAMEWORKS - 1,514 - CONTROLS + 3,771 + MAPPINGS - 41 + 51 ENTRIES - 67 + 77 MAPPING FILES - 114 + 147 INCIDENTS - LLM Top 10 + Agentic Top 10 + DSGAI + 4 OWASP source lists → - ML Classifier Pipeline + Mapping files + registries → @@ -51,9 +52,9 @@ Gap Reports + OSCAL + GRC Export Score your AI security coverage in 60 seconds - EU AI Act · FedRAMP · DORA · NIST · ISO 27001 · SOC 2 · PCI DSS · and 18 more + EU AI Act · FedRAMP · DORA · NIST · ISO 27001 · SOC 2 · PCI DSS · and more - genai-security-project.github.io/GenAI-Data-Security-Initiative - Created by Emmanuel Guilherme Junior · OWASP GenAI Data Security Initiative - \ No newline at end of file + genai-security-project.github.io/crosswalk + OWASP GenAI Data Security Initiative · CC BY-SA 4.0 + diff --git a/scripts/render-stats.mjs b/scripts/render-stats.mjs index 8f9b791..7f5c2dd 100644 --- a/scripts/render-stats.mjs +++ b/scripts/render-stats.mjs @@ -1,21 +1,23 @@ #!/usr/bin/env node /** - * render-stats.mjs — Render generated counts into README marker regions. + * render-stats.mjs — Render generated counts into marker regions. * - * Every headline number in the README lives between a pair of HTML comments: + * Every headline number in the README, and in the two brand images that repeat + * those numbers (the social card and the banner), lives between a pair of + * comments. HTML and SVG share the comment syntax: * * 23 * * The text between the markers is replaced from `data/stats.json`; everything - * else in the file is left byte-identical. `npm run stats:check` re-renders and - * asserts `git diff --exit-code`, so a hand-edited count fails CI. + * else in each file is left byte-identical. `npm run stats:check` re-renders and + * fails if any target would change, so a hand-edited count fails CI. * - * Adding a number to the README means wrapping it in a marker and adding the - * key to KEYS below — never hand-maintaining the digits. + * Adding a number means wrapping it in a marker and adding the key to KEYS + * below — never hand-maintaining the digits. * * Usage: - * node scripts/render-stats.mjs # rewrite README.md in place - * node scripts/render-stats.mjs --check # exit 1 if the README is stale + * node scripts/render-stats.mjs # rewrite every target in place + * node scripts/render-stats.mjs --check # exit 1 if any target is stale */ import fs from 'node:fs'; @@ -23,7 +25,8 @@ import path from 'node:path'; import { fileURLToPath } from 'node:url'; const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); -const README = path.join(ROOT, 'README.md'); +// The badge block is Markdown, so it belongs in README.md only. +const TARGETS = ['README.md', 'docs/og-image.svg', 'docs/banner.svg']; const STATS = path.join(ROOT, 'data', 'stats.json'); const CHECK = process.argv.includes('--check'); @@ -107,26 +110,33 @@ function render(src) { * working copy as stale. The repo is LF-canonical in the index either way, so all * work happens in LF and git converts on checkout. */ -const before = fs.readFileSync(README, 'utf8').replace(/\r\n/g, '\n'); -const { out, seen, unknown } = render(before); +let failed = false; -if (unknown.length) { - console.error(`✗ unknown stats key(s) in README: ${[...new Set(unknown)].join(', ')}`); - console.error(` known keys: ${Object.keys(KEYS).join(', ')}`); - process.exit(1); -} +for (const rel of TARGETS) { + const file = path.join(ROOT, rel); + const before = fs.readFileSync(file, 'utf8').replace(/\r\n/g, '\n'); + const { out, seen, unknown } = render(before); -if (CHECK) { - if (out !== before) { - console.error('✗ README.md is stale — run `npm run stats`'); - process.exit(1); + if (unknown.length) { + console.error(`✗ unknown stats key(s) in ${rel}: ${[...new Set(unknown)].join(', ')}`); + console.error(` known keys: ${Object.keys(KEYS).join(', ')}`); + failed = true; + continue; } - console.log(`✓ README.md is current (${seen.size} marker keys in use)`); -} else { - if (out !== before) { - fs.writeFileSync(README, out, 'utf8'); - console.log(`Rewritten README.md (${seen.size} marker keys)`); + + if (CHECK) { + if (out !== before) { + console.error(`✗ ${rel} is stale — run \`npm run stats\``); + failed = true; + } else { + console.log(`✓ ${rel} is current (${seen.size} marker keys in use)`); + } + } else if (out !== before) { + fs.writeFileSync(file, out, 'utf8'); + console.log(`Rewritten ${rel} (${seen.size} marker keys)`); } else { - console.log(`README.md already current (${seen.size} marker keys)`); + console.log(`${rel} already current (${seen.size} marker keys)`); } } + +if (failed) process.exit(1); diff --git a/scripts/validate.js b/scripts/validate.js index 941ff64..93ddaed 100644 --- a/scripts/validate.js +++ b/scripts/validate.js @@ -671,9 +671,6 @@ const ATTRIBUTION_EXEMPT = [ 'GOVERNANCE.md', // Historical record; past releases are not rewritten. 'CHANGELOG.md', - // Brand assets are frozen (C2) — the webapp's logo/social images. - 'docs/og-image.svg', - 'docs/banner.svg', ]; const ATTRIBUTION_SKIP_DIRS = ['node_modules', '.git', 'reports', 'dist', 'coverage']; diff --git a/scripts/webapp.test.mjs b/scripts/webapp.test.mjs index 370ef7d..9439918 100644 --- a/scripts/webapp.test.mjs +++ b/scripts/webapp.test.mjs @@ -161,6 +161,20 @@ test('the home page counts mapped frameworks only', () => { assert.equal(cards['Registry Controls'], String(controls)); }); +// The landing and Submit pages used to hard-code "41 entries" and "25 +// frameworks". Counts in page copy must come from the loaded data. +test('page copy states the live entry and framework counts', () => { + const { window } = dom; + const entries = window.CROSSWALK_DATA.length; + const frameworks = new Set(window.CROSSWALK_DATA.flatMap((e) => e.mappings.map((m) => m.framework))).size; + + const home = visit('#/').textContent; + assert.match(home, new RegExp(`Search ${entries} OWASP entries`)); + assert.match(home, new RegExp(`across all ${frameworks} frameworks`)); + + assert.match(visit('#/submit').textContent, new RegExp(`all ${entries} OWASP GenAI entries`)); +}); + // The About page is the one place in the webapp allowed to carry the creator // credit, and this file is not — so the test checks that the section renders // with content, never the name itself.