Rogue Agents Watch: a static, client-side dashboard for the Agentic Attack Index, MLSecOpsHub's source-linked dataset of real-world cyberattacks executed or orchestrated by AI agents, and of rogue-agent incidents (an agent wiping a production database, an agent hijacked through indirect prompt injection, AI-orchestrated espionage, agentic supply-chain compromise).
This repository is the presentation layer. It renders the dataset and adds no facts of its own. The dataset, its grading, its sources, and its governance live upstream.
Screenshot placeholder:
docs/screenshot.png(overview with the status / AI-role honesty split, the map, and a record page).
| Route | View |
|---|---|
#/ |
Overview: one hero figure bound to a live map teaser with replay, the verification-status / AI-role honesty split, latest disclosures and what changed, then category, model family, autonomy level, and year |
#/map |
World map of records that carry stated coordinates, replayed by disclosure date, with a field log of every record beside it; colour is AI role, ring is evidence, size is severity, a soft disc marks a country-level centroid and a pin a stated location; every point carries the basis it rests on and the publisher that stated it (upstream geo.points[]), shown in the tooltip and the drawer; click opens the record in a drawer; "what changed" strip below |
#/timeline |
Records by disclosure date, lane per category, outline by status, filterable |
#/table |
Sortable, filterable by every enum field, full-text search, CSV/JSON download of the filtered view (the CSV carries the upstream geo_points column: role:basis:country per map point), and ATT&CK / ATLAS Navigator layers built from the filtered view |
#/actors, #/actors/<slug> |
Every distinct actor string as stated by sources, with records, verification and AI-role counts, techniques, models, guardrail bypass, stated origin points and the publishers behind the attribution; nothing merged or inferred |
#/incident/<id> |
Every field, grade badges with upstream definitions, lifecycle strip, map points with their basis and the publisher that stated them, mappings linked to MITRE ATLAS / ATT&CK / OWASP / NVD / AIID, every source with archive link, related records, record-status banner, citation box, "report a correction" |
#/techniques |
The technique lens: MITRE ATLAS and ATT&CK ids with the records behind each, OWASP and CVE tables, Navigator layer downloads, model family × guardrail bypass and × AI role crosstabs, sourcing per family, lifecycle × category |
#/stats |
All rollups from the upstream summary.json, plus a dataset-gaps table (field coverage), archive coverage, and map coverage (geo_coverage: points by role and by basis) |
#/about |
What counts as an incident, the grading scales, the illustrative-geo rule, corrections, licences |
Every card, row, and page shows status, confidence, and ai_role. Nulls
render as "not stated", never as zero. Retracted and superseded records are
flagged and excluded from headline counts by default, with a toggle.
Built at build time from the snapshot, no server involved:
incident/<id>/— a real page per record: the full record (grades with their upstream definitions, summary, facts as stated, mappings, map points, every source) in static HTML, Open Graph and Twitter tags, a 1200×630 card (og/<id>.png),ArticleandBreadcrumbListJSON-LD, and the app booting in place (no redirect, so search engines index the page itself). Every record page and the map drawer have "Copy share link" and "Copy caption".map/,timeline/,table/,techniques/,stats/,about/— the same for every view: a hash-free canonical URL whose static HTML carries the view's content for crawlers that do not run JavaScript, then the live view.about/also carries the grading vocabularies asDefinedTermSetJSON-LD.llms.txtandllms-full.txt— an index of the site and the full text of every record in Markdown, for AI assistants and agents.embed.html#/map— the map and field log alone, for iframes. Filters and replay position travel in the hash:embed.html#/map?ai_role=load-bearing&t=2025-08.feed.atom— Atom feed of records, newest additions first.changes.json— latest additions, revisions, and record-status changes.navigator/attack-layer.json,navigator/atlas-layer.json— MITRE ATT&CK (layer format 4.5) and ATLAS (4.3) Navigator layers; score = number of records carrying the technique, comment = record ids.misp/— a static MISP feed (manifest.json, one event per record,hashes.csv). In MISP add a feed with the URL…/misp/(the directory). Tags carry the grades and technique ids; attributes carry the record links, summary, actor as stated, source URLs, and CVEs.sitemap.xml(every page, with the card images),robots.txt(crawling allowed, the AI crawlers named explicitly), and on the landing page one schema.org graph:WebSite,Organization,Dataset(with every record ashasPart) and the orderedItemList, for search engines, AI answer engines and Google Dataset Search.- The upstream
dist/stix/bundle.json(STIX 2.1) imports into OpenCTI.
Design and product research notes are kept outside the repository.
agentic-attack-index (upstream, CC BY-SA 4.0)
data/incidents/*.yml ─ build ─▶ dist/incidents.json, dist/summary.json,
schema/incident.schema.json, taxonomy/*.yml
│
scripts/sync-data.mjs │ (weekly workflow or `npm run sync:data`)
▼
rogue-agents-dashboard data/snapshot/{incidents,summary,incident.schema,taxonomy,SNAPSHOT}.json
│
scripts/validate-data.mjs │ ajv 2020-12 + ajv-formats; build fails on drift
▼
vite build ─▶ dist/ ─▶ Vercel (www.rogueagentswatch.com)
- The site makes no runtime network request for data. Everything is bundled from the vendored snapshot. The footer's "check upstream for a newer dataset" button is the only fetch that leaves the origin, and only when you click it.
SNAPSHOT.jsonrecords the upstream ref, commit, dataset version, and fetch date; the footer shows the dataset version and archive coverage as a trust signal.- The data contract is upstream schema 0.3.0: map points live in
geo.points[], each withrole,basis(taxonomy/geo-basis.yml),attributed_by,country,lat,lng,label, andillustrative; the oldgeo.target/geo.originslots are gone.scripts/validate-data.mjsmirrors the upstream point rules (basis allowed for the role,attributed_byis a cited publisher, centroids are illustrative) and checkssummary.geo_coverageagainst the records, so a contract change fails the sync instead of rendering a wrong point. - A scheduled workflow (
sync-data.yml) refreshes the snapshot weekly and opens (or updates) a single PR onchore/sync-datawhen it changed. If the new snapshot fails validation it opens one tracked issue instead.
nvm use # Node 20.19
npm ci
npm run dev # http://localhost:5173/
npm test # typecheck + lint + snapshot validation + unit tests incl. axe-core checks (hermetic)
npm run build # validates, then builds dist/ (reproducible)
npm run preview # serves dist/
npm run size # gzipped bundle report; core must stay under 500 KB
npm run sync:data # refresh data/snapshot from the latest tagged upstream release (network)Open the record page and use Report a correction. It opens an issue in the upstream repository pre-filled with the record id, using the data-correction template. Add the affected field, why it is wrong, and supporting sources. Corrections without sources can only remove claims, not change them. Fixes reach this site on the next snapshot sync. Rendering bugs belong in this repository's issues.
The site is served by Vercel at https://www.rogueagentswatch.com/
(rogueagentswatch.com redirects to www). Vercel's GitHub integration
builds npm run build on every push: main deploys to production, every
pull request gets a preview. vercel.json sets trailingSlash, the CORS
header the Navigator layers and MISP feed need, and long-lived caching for
hashed assets. There is no GitHub Pages deployment: a second host serving
the same pages under a different canonical splits search signals.
One-time setup for the daily sync-data workflow, which opens a pull
request when the upstream snapshot changes: add a repository secret
SYNC_PR_TOKEN holding a fine-grained personal access token scoped to
this repository with Contents: write and Pull requests: write. The
built-in GITHUB_TOKEN cannot be used for this: the organisation does not
allow Actions to create pull requests with it, and a branch pushed with it
never triggers CI on the resulting PR. Without the secret the workflow still
fetches, validates and pushes the snapshot to chore/sync-data, then opens
a tracked issue with the compare link instead of a PR.
The asset base path and the canonical URL (share links, Open Graph tags,
feed, sitemap) are resolved together in scripts/site-env.mjs:
- Local (
npm run dev, a plainnpm run build): served at/, and the canonical URL is the preview addresshttp://localhost:4173/. A build with no deploy environment is a local one, so its share links say so rather than pointing at a host it was not built for. - Vercel: no configuration. The build detects Vercel's environment,
serves at
/, and uses the production host (the custom domain) or the preview deployment's host as the canonical URL. - GitHub Actions (the CI build, not a deployment): served under
/<repo>/withhttps://<owner>.github.io/<repo>/as the canonical, so the Lighthouse job audits the same layout a Pages deployment would have. Re-enabling GitHub Pages would need only a deploy workflow; the resolver already handles it. - Custom domain anywhere else: set
VITE_BASE_PATH=/andVITE_SITE_URL=https://your.domain/in the build step; explicit values always win.
- Code in this repository: MIT.
- Data displayed by this dashboard and vendored under
data/snapshot/: the Agentic Attack Index by MLSecOpsHub, licensed CC BY-SA 4.0. Reuse, including screenshots and exports, must attribute "Agentic Attack Index (MLSecOpsHub)" with a link and be shared alike. - Map geometry under
data/geo/: Natural Earth (public domain) via world-atlas (ISC). - Fonts under
assets/fonts/: Barlow, Barlow Condensed, IBM Plex Mono, SIL Open Font License 1.1, used only at build time to render share cards.
See CONTRIBUTING.md for the non-negotiables and SECURITY.md for the security and content policy.