diff --git a/package-lock.json b/package-lock.json index 5ce2c59..d331b8f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", "@mui/icons-material": "^5.15.1", - "@mui/material": "^5.15.1", + "@mui/material": "^6.3.1", "@mui/x-charts": "^7.0.0", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", @@ -1959,9 +1959,9 @@ "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==" }, "node_modules/@babel/runtime": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.1.tgz", - "integrity": "sha512-+BIznRzyqBf+2wCTxcKE3wDjfGeCoVE61KSHGpkzqrLi8qxqFwBeUFyId2cxkTmm55fzDGnm0+yCxaxygrLUnQ==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz", + "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==", "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -2333,21 +2333,31 @@ } }, "node_modules/@emotion/cache": { - "version": "11.11.0", - "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.11.0.tgz", - "integrity": "sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ==", - "dependencies": { - "@emotion/memoize": "^0.8.1", - "@emotion/sheet": "^1.2.2", - "@emotion/utils": "^1.2.1", - "@emotion/weak-memoize": "^0.3.1", + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz", + "integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==", + "dependencies": { + "@emotion/memoize": "^0.9.0", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", "stylis": "4.2.0" } }, + "node_modules/@emotion/cache/node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==" + }, + "node_modules/@emotion/cache/node_modules/@emotion/weak-memoize": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==" + }, "node_modules/@emotion/hash": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.1.tgz", - "integrity": "sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ==" + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==" }, "node_modules/@emotion/is-prop-valid": { "version": "1.2.1", @@ -2386,21 +2396,26 @@ } }, "node_modules/@emotion/serialize": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.2.tgz", - "integrity": "sha512-zR6a/fkFP4EAcCMQtLOhIgpprZOwNmCldtpaISpvz348+DP4Mz8ZoKaGGCQpbzepNIUWbq4w6hNZkwDyKoS+HA==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz", + "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==", "dependencies": { - "@emotion/hash": "^0.9.1", - "@emotion/memoize": "^0.8.1", - "@emotion/unitless": "^0.8.1", - "@emotion/utils": "^1.2.1", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/unitless": "^0.10.0", + "@emotion/utils": "^1.4.2", "csstype": "^3.0.2" } }, + "node_modules/@emotion/serialize/node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==" + }, "node_modules/@emotion/sheet": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.2.tgz", - "integrity": "sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA==" + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", + "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==" }, "node_modules/@emotion/styled": { "version": "11.11.0", @@ -2425,9 +2440,9 @@ } }, "node_modules/@emotion/unitless": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", - "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==" + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==" }, "node_modules/@emotion/use-insertion-effect-with-fallbacks": { "version": "1.0.1", @@ -2438,9 +2453,9 @@ } }, "node_modules/@emotion/utils": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.2.1.tgz", - "integrity": "sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg==" + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz", + "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==" }, "node_modules/@emotion/weak-memoize": { "version": "0.3.1", @@ -3464,9 +3479,9 @@ } }, "node_modules/@mui/core-downloads-tracker": { - "version": "5.15.14", - "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.15.14.tgz", - "integrity": "sha512-on75VMd0XqZfaQW+9pGjSNiqW+ghc5E2ZSLRBXwcXl/C4YzjfyjrLPhrEpKnR9Uym9KXBvxrhoHfPcczYHweyA==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.4.0.tgz", + "integrity": "sha512-6u74wi+9zeNlukrCtYYET8Ed/n9AS27DiaXCZKAD3TRGFaqiyYSsQgN2disW83pI/cM1Q2lJY1JX4YfwvNtlNw==", "funding": { "type": "opencollective", "url": "https://opencollective.com/mui-org" @@ -3498,25 +3513,25 @@ } }, "node_modules/@mui/material": { - "version": "5.15.14", - "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.15.14.tgz", - "integrity": "sha512-kEbRw6fASdQ1SQ7LVdWR5OlWV3y7Y54ZxkLzd6LV5tmz+NpO3MJKZXSfgR0LHMP7meKsPiMm4AuzV0pXDpk/BQ==", - "dependencies": { - "@babel/runtime": "^7.23.9", - "@mui/base": "5.0.0-beta.40", - "@mui/core-downloads-tracker": "^5.15.14", - "@mui/system": "^5.15.14", - "@mui/types": "^7.2.14", - "@mui/utils": "^5.15.14", - "@types/react-transition-group": "^4.4.10", - "clsx": "^2.1.0", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.3.1.tgz", + "integrity": "sha512-ynG9ayhxgCsHJ/dtDcT1v78/r2GwQyP3E0hPz3GdPRl0uFJz/uUTtI5KFYwadXmbC+Uv3bfB8laZ6+Cpzh03gA==", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@mui/core-downloads-tracker": "^6.3.1", + "@mui/system": "^6.3.1", + "@mui/types": "^7.2.21", + "@mui/utils": "^6.3.1", + "@popperjs/core": "^2.11.8", + "@types/react-transition-group": "^4.4.12", + "clsx": "^2.1.1", "csstype": "^3.1.3", "prop-types": "^15.8.1", - "react-is": "^18.2.0", + "react-is": "^19.0.0", "react-transition-group": "^4.4.5" }, "engines": { - "node": ">=12.0.0" + "node": ">=14.0.0" }, "funding": { "type": "opencollective", @@ -3525,9 +3540,10 @@ "peerDependencies": { "@emotion/react": "^11.5.0", "@emotion/styled": "^11.3.0", - "@types/react": "^17.0.0 || ^18.0.0", - "react": "^17.0.0 || ^18.0.0", - "react-dom": "^17.0.0 || ^18.0.0" + "@mui/material-pigment-css": "^6.3.1", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" }, "peerDependenciesMeta": { "@emotion/react": { @@ -3536,15 +3552,145 @@ "@emotion/styled": { "optional": true }, + "@mui/material-pigment-css": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/material/node_modules/@mui/private-theming": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.4.0.tgz", + "integrity": "sha512-rNHci8MP6NOdEWAfZ/RBMO5Rhtp1T6fUDMSmingg9F1T6wiUeodIQ+NuTHh2/pMoUSeP9GdHdgMhMmfsXxOMuw==", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@mui/utils": "^6.4.0", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/material/node_modules/@mui/styled-engine": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.4.0.tgz", + "integrity": "sha512-ek/ZrDujrger12P6o4luQIfRd2IziH7jQod2WMbLqGE03Iy0zUwYmckRTVhRQTLPNccpD8KXGcALJF+uaUQlbg==", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@emotion/cache": "^11.13.5", + "@emotion/serialize": "^1.3.3", + "@emotion/sheet": "^1.4.0", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.4.1", + "@emotion/styled": "^11.3.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } + } + }, + "node_modules/@mui/material/node_modules/@mui/system": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-6.4.0.tgz", + "integrity": "sha512-wTDyfRlaZCo2sW2IuOsrjeE5dl0Usrs6J7DxE3GwNCVFqS5wMplM2YeNiV3DO7s53RfCqbho+gJY6xaB9KThUA==", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@mui/private-theming": "^6.4.0", + "@mui/styled-engine": "^6.4.0", + "@mui/types": "^7.2.21", + "@mui/utils": "^6.4.0", + "clsx": "^2.1.1", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/material/node_modules/@mui/utils": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.4.0.tgz", + "integrity": "sha512-woOTATWNsTNR3YBh2Ixkj3l5RaxSiGoC9G8gOpYoFw1mZM77LWJeuMHFax7iIW4ahK0Cr35TF9DKtrafJmOmNQ==", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@mui/types": "^7.2.21", + "@types/prop-types": "^15.7.14", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-is": "^19.0.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { "@types/react": { "optional": true } } }, "node_modules/@mui/material/node_modules/react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.0.0.tgz", + "integrity": "sha512-H91OHcwjZsbq3ClIDHMzBShc1rotbfACdWENsmEf0IFvZ3FgGPtdHMcsv45bQ1hAbgdfiA8SnxTKfDS+x/8m2g==" }, "node_modules/@mui/private-theming": { "version": "5.15.14", @@ -3643,11 +3789,11 @@ } }, "node_modules/@mui/types": { - "version": "7.2.14", - "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.14.tgz", - "integrity": "sha512-MZsBZ4q4HfzBsywtXgM1Ksj6HDThtiwmOKUXH1pKYISI9gAVXCNHNpo7TlGoGrBaYWZTdNoirIN7JsQcQUjmQQ==", + "version": "7.2.21", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.21.tgz", + "integrity": "sha512-6HstngiUxNqLU+/DPqlUJDIPbzUBxIVHb1MmXP0eTWDIROiCR2viugXpEif0PPe2mLqqakPzzRClWAnK+8UJww==", "peerDependencies": { - "@types/react": "^17.0.0 || ^18.0.0" + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, "peerDependenciesMeta": { "@types/react": { @@ -4999,9 +5145,9 @@ "integrity": "sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==" }, "node_modules/@types/prop-types": { - "version": "15.7.11", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz", - "integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==" + "version": "15.7.14", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz", + "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==" }, "node_modules/@types/q": { "version": "1.5.8", @@ -5043,10 +5189,10 @@ } }, "node_modules/@types/react-transition-group": { - "version": "4.4.10", - "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.10.tgz", - "integrity": "sha512-hT/+s0VQs2ojCX823m60m5f0sL5idt9SO6Tj6Dg+rdphGPIeJbJ6CxvBYkgkGKrYeDjvIpKTR38UzmtHJOGW3Q==", - "dependencies": { + "version": "4.4.12", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz", + "integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==", + "peerDependencies": { "@types/react": "*" } }, @@ -6824,9 +6970,9 @@ } }, "node_modules/clsx": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz", - "integrity": "sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", "engines": { "node": ">=6" } diff --git a/package.json b/package.json index e97dc23..11fe049 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", "@mui/icons-material": "^5.15.1", - "@mui/material": "^5.15.1", + "@mui/material": "^6.3.1", "@mui/x-charts": "^7.0.0", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", diff --git a/src/App.js b/src/App.js index 374f535..f7e3fd1 100644 --- a/src/App.js +++ b/src/App.js @@ -24,11 +24,61 @@ import ListItemText from '@mui/material/ListItemText' import Toolbar from '@mui/material/Toolbar' import Typography from '@mui/material/Typography' -import { ThemeProvider, createTheme } from '@mui/material/styles' -import CssBaseline from '@mui/material/CssBaseline' -import ListSubheader from '@mui/material/ListSubheader' -import CheckIcon from '@mui/icons-material/Check' -import EditIcon from '@mui/icons-material/Edit'; +import { + ThemeProvider, + createTheme, + useColorScheme, +} from "@mui/material/styles"; +import CssBaseline from "@mui/material/CssBaseline"; +import ListSubheader from "@mui/material/ListSubheader"; +import CheckIcon from "@mui/icons-material/Check"; +import EditIcon from "@mui/icons-material/Edit"; +import RadioGroup from "@mui/material/RadioGroup"; +import Radio from "@mui/material/Radio"; +import FormControl from "@mui/material/FormControl"; +import FormControlLabel from "@mui/material/FormControlLabel"; +import FormLabel from "@mui/material/FormLabel"; + +function ModeSwitcher() { + const { mode, setMode } = useColorScheme(); + + if (!mode) { + return null; + } + + return ( + + + + Theme + + setMode(event.target.value)} + > + } label="System" /> + } label="Light" /> + } label="Dark" /> + + + + ); +} function App() { const drawerWidth = 280 @@ -60,164 +110,230 @@ function App() { } const darkTheme = createTheme({ - palette: { - mode: 'dark', - primary: { - main: '#3b512a' + // palette: { + // mode: 'dark', + // primary: { + // main: '#3b512a' + // }, + // secondary: { + // main: '#8278d9' + // } + // }, + colorSchemes: { + // dark: true, + dark: { + palette: { + primary: { + main: "#3b512a", + }, + secondary: { + main: "#8278d9", + }, + }, + }, + light: { + palette: { + primary: { + main: "#FF5733", + }, + // ...other tokens + }, }, - secondary: { - main: '#8278d9' - } }, typography: { h4: { fontFamily: '"Noto Sans Mono", monospace', - color: '#8278d9', - fontWeight: '400', + color: "#8278d9", + fontWeight: "400", }, h5: { fontFamily: '"Noto Sans Mono", monospace', - opacity: '0.6' + opacity: "0.6", }, subtitle: { fontFamily: '"Noto Sans Mono", monospace', - opacity: '0.6' - } - } - }) + opacity: "0.6", + }, + }, + }); const drawer = (
+ sx={{ + backgroundImage: "url(/public/noise4.png)", + }} + > - ThreatFinderAI + ThreatFinderAI - - Stages - - setView('goals')}> + + + + Stages + setView("goals")}> - + - - {['risks', 'model', 'analyze', 'controls'].includes(view) && } - {view === 'goals' && } + + {["risks", "model", "analyze", "controls"].includes(view) && ( + + )} + {view === "goals" && } - - setView('model')}> + + setView("model")} + > - + - - {['risks', 'analyze', 'controls'].includes(view) && } - {view === 'model' && } + + {["risks", "analyze", "controls"].includes(view) && ( + + )} + {view === "model" && } - - setView('analyze')}> + + setView("analyze")} + > - + - - {['risks', 'controls'].includes(view) && } - {view === 'analyze' && } + + {["risks", "controls"].includes(view) && ( + + )} + {view === "analyze" && } - - setView('controls')}> + + setView("controls")} + > - + - - {['risks'].includes(view) && } - {view === 'controls' && } + + {["risks"].includes(view) && } + {view === "controls" && } - - setView('risks')}> + + setView("risks")} + > - + - - {view === 'risks' && } + + {view === "risks" && } - - - - - Instructions - - - - - - + + + + + Instructions + + + + + +
- ) + ); return ( - + - + + sx={{ + width: { sm: drawerWidth }, + flexShrink: { sm: 0 }, + backgroundImage: "url(noise4.png)", + }} + aria-label="mailbox folders" + > + display: { xs: "block", sm: "none" }, + "& .MuiDrawer-paper": { + boxSizing: "border-box", + width: drawerWidth, + }, + }} + > {drawer} + open + > {drawer} - {view === 'goals' && { updateSelectedModel(selectedModel) }}/>} - {view === 'model' && } - {view === 'analyze' && } - {view === 'controls' && } - {view === 'risks' && } + sx={{ + flexGrow: 1, + p: 3, + width: { sm: `calc(100% - ${drawerWidth}px)` }, + height: "100vh", + }} + > + {view === "goals" && ( + { + updateSelectedModel(selectedModel); + }} + /> + )} + {view === "model" && } + {view === "analyze" && } + {view === "controls" && } + {view === "risks" && } - ) + ); } export default App diff --git a/src/pages/Analyze.jsx b/src/pages/Analyze.jsx index e21d268..3909405 100644 --- a/src/pages/Analyze.jsx +++ b/src/pages/Analyze.jsx @@ -11,242 +11,324 @@ import NotesRoundedIcon from '@mui/icons-material/NotesRounded'; import { Typography } from '@mui/material' import TuneRoundedIcon from '@mui/icons-material/TuneRounded'; import NativeSelect from '@mui/material/NativeSelect'; -import { useState } from 'react' +import { useState, useEffect } from "react"; -import { Button, +import { + Button, FormControl, FormLabel, FormGroup, FormControlLabel, FormHelperText, List, - InputLabel } from '@mui/material' -import assetTaxonomy from './AssetTaxonomy.json' -import threatTaxonomy from './ThreatTaxonomy.json' + InputLabel, +} from "@mui/material"; +import assetTaxonomy from "./AssetTaxonomy.json"; +import threatTaxonomy from "./ThreatTaxonomy.json"; - -export default Analyze +export default Analyze; function Analyze({ onNrThreats }) { - var [selectedModel] = useState(localStorage.getItem('selectedModel') || '') + const [jsonData, setJsonData] = useState(threatTaxonomy); + const [selectedThreatTaxonomy, setSelectedThreatTaxonomy] = useState(""); + + var [selectedModel] = useState(localStorage.getItem("selectedModel") || ""); try { - var loaded = JSON.parse(localStorage.getItem('storedModels')) || [] + var loaded = JSON.parse(localStorage.getItem("storedModels")) || []; } catch (e) { - loaded = [] + loaded = []; } - var storedModels = loaded - var [selectedModelInfo] = useState(storedModels.find(m => m.id === selectedModel)) + var storedModels = loaded; + var [selectedModelInfo] = useState( + storedModels.find((m) => m.id === selectedModel) + ); - var diagram = selectedModelInfo.diagram - var [detectedAssets] = useModeledAssets(diagram, assetTaxonomy) + var diagram = selectedModelInfo.diagram; + var [detectedAssets] = useModeledAssets(diagram, assetTaxonomy); - selectedModelInfo.assets = detectedAssets - localStorage.setItem('storedModels', JSON.stringify(storedModels)) + selectedModelInfo.assets = detectedAssets; + localStorage.setItem("storedModels", JSON.stringify(storedModels)); try { - var loadedTM = JSON.parse(localStorage.getItem('threatModels'))[selectedModel] || [] + var loadedTM = + JSON.parse(localStorage.getItem("threatModels"))[selectedModel] || []; } catch (e) { - loadedTM = [] + loadedTM = []; } - var [threatModel, setThreatModel] = useState(loadedTM) - onNrThreats(threatModel.length) + var [threatModel, setThreatModel] = useState(loadedTM); + onNrThreats(threatModel.length); + + function addToThreatModel(newThreat) { + threatModel.push(newThreat); + setThreatModel([...threatModel]); + var storedThreatModels = + JSON.parse(localStorage.getItem("threatModels")) || {}; + storedThreatModels[selectedModel] = threatModel; + localStorage.setItem("threatModels", JSON.stringify(storedThreatModels)); + onNrThreats(threatModel.length); + } - function addToThreatModel (newThreat) { - threatModel.push(newThreat) - setThreatModel([...threatModel]) - var storedThreatModels = JSON.parse(localStorage.getItem('threatModels')) || {} - storedThreatModels[selectedModel] = threatModel - localStorage.setItem('threatModels', JSON.stringify(storedThreatModels)) - onNrThreats(threatModel.length) + var keyProp = selectedModelInfo.keyProp; + const [propFilter, setPropFilter] = useState({ + Confidentiality: keyProp === "Confidentiality", + Integrity: keyProp === "Integrity", + Availability: keyProp === "Availability", + Accountability: keyProp === "Accountability", + "Business Understanding": true, + "Business Goal Definition": true, + "Data Pre-processing": true, + "Model Tuning": true, + "Feature Selection": true, + "Data Exploration": true, + "Data Ingestion": true, + "Model Training": true, + "Model Selection": true, + "Model Building": true, + "Model Deployment": true, + "Model Maintenance": true, + "Transfer Learning": true, + Actor: true, + Data: true, + Model: true, + Artefacts: true, + Processes: true, + "Environment/tools": true, + }); + + function updatePropFilter(newState) { + setPropFilter({ ...newState }); } - var keyProp = selectedModelInfo.keyProp - const [propFilter, setPropFilter] = useState( - { - Confidentiality: keyProp === 'Confidentiality', - Integrity: keyProp === 'Integrity', - Availability: keyProp === 'Availability', - Accountability: keyProp === 'Accountability', - 'Business Understanding': true, - 'Business Goal Definition': true, - 'Data Pre-processing': true, - 'Model Tuning': true, - 'Feature Selection': true, - 'Data Exploration': true, - 'Data Ingestion': true, - 'Model Training': true, - 'Model Selection': true, - 'Model Building': true, - 'Model Deployment': true, - 'Model Maintenance': true, - 'Transfer Learning': true, - 'Actor': true, - 'Data': true, - 'Model': true, - 'Artefacts': true, - 'Processes': true, - 'Environment/tools': true, + window.assetTaxonomy = assetTaxonomy; + + useEffect(() => { + if (!selectedThreatTaxonomy) { + setJsonData(threatTaxonomy); + // setError(null); + return; } - ) - function updatePropFilter(newState) { setPropFilter({ ...newState }) } + const importJsonFile = async (fileName) => { + try { + const response = await import(`./ThreatTaxonomy-${fileName}.json`); + setJsonData(response.default); + // setError(null); + } catch (err) { + // setError(`Error loading ${fileName}.json: ${err.message}`); + setJsonData(threatTaxonomy); + } + }; + + importJsonFile(selectedThreatTaxonomy); + }, [selectedThreatTaxonomy]); - window.assetTaxonomy = assetTaxonomy + const handleThreatTaxonomyChange = (event) => { + setSelectedThreatTaxonomy(event.target.value); + }; function getThreatsforCategory(category) { - var threats = threatTaxonomy.filter(t => t['Affected assets'].includes(category)) - threats.map(t => { - return t.potentialKeyThreat = t['Potential Impact'].includes(selectedModelInfo.keyProp) - }) - return threats + // var threats = threatTaxonomy.filter((t) => + // t["Affected assets"].includes(category) + // ); + var threats = jsonData.filter((t) => + t["Affected assets"].includes(category) + ); + threats.map((t) => { + return (t.potentialKeyThreat = t["Potential Impact"].includes( + selectedModelInfo.keyProp + )); + }); + return threats; } - function assetList(assets, selectedModelInfo) { assets.sort(function sortByStringAttribute(a, b) { - return a.assetDisplayname.localeCompare(b.assetDisplayname) - }) + return a.assetDisplayname.localeCompare(b.assetDisplayname); + }); // TODOD DELETE - var potentialKeyAssets = assets.filter(a => a.assetCategory === selectedModelInfo.keyAsset) + var potentialKeyAssets = assets.filter( + (a) => a.assetCategory === selectedModelInfo.keyAsset + ); - - var nonCriticalAssets = assets.filter(a => a.assetCategory !== selectedModelInfo.keyAsset) + var nonCriticalAssets = assets.filter( + (a) => a.assetCategory !== selectedModelInfo.keyAsset + ); //var notKeyAssetGroups = [...new Set(otherAssets.map(a => a.assetCategory))] - function byCategory({ assetCategory }) { return assetCategory } - var nonCriticalAssetGroups = Object.values(Object.groupBy(nonCriticalAssets, byCategory)) + function byCategory({ assetCategory }) { + return assetCategory; + } + var nonCriticalAssetGroups = Object.values( + Object.groupBy(nonCriticalAssets, byCategory) + ); function ThreatList(asset, category) { - var threats = getThreatsforCategory(category) + var threats = getThreatsforCategory(category); //console.log(`${asset.assetDisplayname} has ${threats.length} threats`, threats) return ( <> - { - threats.map(k => { - var impacts = k['Potential Impact'].split(', ').some(i => { - return propFilter[i] - }) - if (impacts) { - return ( - - } - aria-controls="panel1-content" - id="panel1-header" - sx={{ textTransform: 'capitalize' }}> - - {k.Threat} - ({k['Potential Impact'].split(', ').map(i => i.split('')[0])}) - - - - {k.Description} - - - - - - ) - } return null - }) - } + controls: [], + }); + }} + > + Add to Threat Model + + + + + ); + } + return null; + })} - - ) + ); } function assetGroup(group, groupIsKey) { // should all be the same, check the first - var category = group[0]?.assetCategory - console.log(category) + var category = group[0]?.assetCategory; + console.log(category); if (propFilter[category] && category) { return ( <> - - + + {groupIsKey && } - - {category.replace('/', ' & ')} + + {category.replace("/", " & ")} - {groupIsKey && - + {groupIsKey && ( + These threats may impact or relate to a key asset. - } - {group.map(asset => { - if (asset.assetLifeCycleStage.split(', ').some(function (stage) { - if(asset.assetDisplayname === 'Train Model') debugger - var stageEnabled = propFilter[stage] - return stageEnabled - })) { + )} + {group.map((asset) => { + if ( + asset.assetLifeCycleStage.split(", ").some(function (stage) { + if (asset.assetDisplayname === "Train Model") debugger; + var stageEnabled = propFilter[stage]; + return stageEnabled; + }) + ) { // console.log(asset, 'should be displayed', asset.assetLifeCycleStage) - return ( - } - aria-controls="panel1-content" - id="panel1-header" - sx={{ textTransform: 'capitalize' }}> - {asset.assetDisplayname} - - - - - {asset.assetLifeCycleStage.split(', ').join('—')} - "{asset.assetDescription}" + return ( + + } + aria-controls="panel1-content" + id="panel1-header" + sx={{ textTransform: "capitalize" }} + > + {asset.assetDisplayname} + + + + + + {asset.assetLifeCycleStage.split(", ").join("—")} + + + "{asset.assetDescription}" + + + + + {ThreatList(asset, category)} + - - - {ThreatList(asset, category)} - - - - - ) + + + ); } else { //console.log(asset, 'should NOT be displayed', asset.assetLifeCycleStage) - return null - } + return null; + } })} - ) + ); } } return ( <> {assetGroup(potentialKeyAssets, true)} - {nonCriticalAssetGroups.map(group => { - return assetGroup(group, false) + {nonCriticalAssetGroups.map((group) => { + return assetGroup(group, false); })} - ) + ); } function handlePropChange(event) { - var propChanged = event.target.name - propFilter[propChanged] = !propFilter[propChanged] - updatePropFilter(propFilter) + var propChanged = event.target.name; + propFilter[propChanged] = !propFilter[propChanged]; + updatePropFilter(propFilter); } function handleAssetChange(event) { - var propChanged = event.target.name - propFilter[propChanged] = !propFilter[propChanged] - updatePropFilter(propFilter) + var propChanged = event.target.name; + propFilter[propChanged] = !propFilter[propChanged]; + updatePropFilter(propFilter); } return ( <> - + Threat Analysis @@ -257,124 +339,322 @@ function Analyze({ onNrThreats }) { expandIcon={} aria-controls="panel1-content" id="panel1-header" - sx={{ textTransform: 'capitalize', mt: '1em' }}> + sx={{ textTransform: "capitalize", mt: "1em" }} + > Filters - Automated threat identification easily produces false positives (i.e., irrelevant threats).
Apply filters to investigate threats from different angles — the initial selection reflects the previously defined key asset and security property. + Automated threat identification easily produces false positives ( + i.e., irrelevant threats). +
Apply filters to investigate threats from different angles — + the initial selection reflects the previously defined key asset and + security property. {propFilter.Confidentiality} - - + + - Security Properties + + Security Properties + } - label="Confidentiality" /> + control={ + + } + label="Confidentiality" + /> } - label="Integrity" /> + control={ + + } + label="Integrity" + /> } - label="Availability" /> + control={ + + } + label="Availability" + /> } - label="Accountability" /> + control={ + + } + label="Accountability" + /> - {selectedModelInfo.keyProp} is your key property + + {selectedModelInfo.keyProp} is your key property + - + - Design Stage + + Design Stage + } - label="Business Understanding" /> + control={ + + } + label="Business Understanding" + /> } - label="Business Goal Definition" /> + control={ + + } + label="Business Goal Definition" + /> } - label="Data Pre-processing" /> + control={ + + } + label="Data Pre-processing" + /> } - label="Model Tuning" /> + control={ + + } + label="Model Tuning" + /> } - label="Feature Selection" /> + control={ + + } + label="Feature Selection" + /> } - label="Data Exploration" /> + control={ + + } + label="Data Exploration" + /> } - label="Data Ingestion" /> + control={ + + } + label="Data Ingestion" + /> } - label="Model Training" /> + control={ + + } + label="Model Training" + /> } - label="Model Selection" /> + control={ + + } + label="Model Selection" + /> } - label="Model Building" /> + control={ + + } + label="Model Building" + /> } - label="Model Deployment" /> + control={ + + } + label="Model Deployment" + /> } - label="Model Maintenance" /> + control={ + + } + label="Model Maintenance" + /> } - label="Transfer Learning" /> + control={ + + } + label="Transfer Learning" + /> - - + - + - Asset Type + + Asset Type + } - label="Actor" /> + control={ + + } + label="Actor" + /> } - label="Data" /> + control={ + + } + label="Data" + /> } - label="Model" /> + control={ + + } + label="Model" + /> } - label="Artefacts" /> + control={ + + } + label="Artefacts" + /> } - label="Processes" /> + control={ + + } + label="Processes" + /> } - label="Environment & Tools" /> + control={ + + } + label="Environment & Tools" + /> - - + - + Knowledge Base - - - + name: "age", + id: "uncontrolled-native", + }} + onChange={handleThreatTaxonomyChange} + > + + + + @@ -384,7 +664,7 @@ function Analyze({ onNrThreats }) { {assetList(detectedAssets, selectedModelInfo)} - ) + ); } export function ThreatModel (props) { diff --git a/src/pages/Risks.jsx b/src/pages/Risks.jsx index 1bdcb82..1b5072a 100644 --- a/src/pages/Risks.jsx +++ b/src/pages/Risks.jsx @@ -27,181 +27,464 @@ import ImpactList from './Impacts.json' export default function Controls() { try { - var [selectedModel] = useState(localStorage.getItem('selectedModel') || '') - var loadedTM = JSON.parse(localStorage.getItem('threatModels'))[selectedModel] || [] - console.log(loadedTM) + var [selectedModel] = useState(localStorage.getItem("selectedModel") || ""); + var loadedTM = + JSON.parse(localStorage.getItem("threatModels"))[selectedModel] || []; + // console.log(loadedTM); } catch (e) { - console.warn('error parsing threats from storage', e) - loadedTM = [] + console.warn("error parsing threats from storage", e); + loadedTM = []; } - try { - var loadedRiskScenarios = JSON.parse(localStorage.getItem('riskScenarios'))[selectedModel] || [] - } catch (e) { - console.warn('error parsing risks from storage', e) - loadedRiskScenarios = [] - } + // var loadedRiskScenarios = JSON.parse( + // localStorage.getItem("riskScenarios") + // ).filter((m) => m.model === selectedModel); + const riskScenarios = JSON.parse( + localStorage.getItem("riskScenarios") || "[]" + ); + const loadedRiskScenarios = riskScenarios.filter( + (scenario) => scenario.model === selectedModel + ); + const [riskName, setRiskName] = useState(""); //var [threatModel, _] = useState(loadedTM) - var [risks, setRisks] = useState(loadedRiskScenarios) - - var [selectedThreat, setSelectedThreat] = useState({}) - - function ImpactCard (props) { - var { impact } = props - var [quantified, setQuantified] = useState(false) - var [minInc, setMinInc] = useState(0) - var [maxInc, setMaxInc] = useState() - - var [minLoss, setMinLoss] = useState(0) - var [maxLoss, setMaxLoss] = useState() - - var [confInc, setConfInc] = useState(0.95) - var [confLoss, setConfLoss] = useState(0.95) - - var [quantification, setQuant] = useState({}) - - async function quantify (minInc, maxInc, minLoss, maxLoss, confInc, confLoss) { - console.log('quantification using', arguments) - var quantificationRes = await fetch('https://threatfinderai-quant.comsyslab.xyz/quant?' + new URLSearchParams({ - minInc: minInc, - maxInc: maxInc, - minLoss: minLoss, - maxLoss: maxLoss, - confInc: confInc, - confLoss: confLoss - })) - var quantification = await quantificationRes.json() - console.log(quantification) - setQuant(quantification) - setQuantified(true) + var [risks, setRisks] = useState(loadedRiskScenarios); + + var [selectedThreat, setSelectedThreat] = useState({}); + + function ImpactCard(props) { + var { impact } = props; + var [quantified, setQuantified] = useState(false); + var [minInc, setMinInc] = useState(0); + var [maxInc, setMaxInc] = useState(); + + var [minLoss, setMinLoss] = useState(0); + var [maxLoss, setMaxLoss] = useState(); + + var [confInc, setConfInc] = useState(0.95); + var [confLoss, setConfLoss] = useState(0.95); + + var [quantification, setQuant] = useState({}); + + // async function quantify (minInc, maxInc, minLoss, maxLoss, confInc, confLoss) { + // console.log('quantification using', arguments) + // var quantificationRes = await fetch('https://threatfinderai-quant.comsyslab.xyz/quant?' + new URLSearchParams({ + // minInc: minInc, + // maxInc: maxInc, + // minLoss: minLoss, + // maxLoss: maxLoss, + // confInc: confInc, + // confLoss: confLoss + // })) + // var quantification = await quantificationRes.json() + // console.log(quantification) + // setQuant(quantification) + // setQuantified(true) + // } + + // local quntification function with reduced number of samples + // based code generated using Qwen2.5-Coder-32B-Instruct & ChatGPT + function quantify(minInc, maxInc, minLoss, maxLoss, confInc, confLoss) { + console.log("quantification using", arguments); + + const numSamples = 10000; + const ranges = [ + [minInc, maxInc, confInc], + [minLoss, maxLoss, confLoss], + ]; + + function riskLoss(x) { + const Inc = x[0]; + const Loss = x[1]; + // number of occurances x loss potential, gives risk loss $ number + // confidence for number of occurances and loss potential is already considered + // while generating the random series + return Inc * Loss; + } + + const monteCarloSim = (func, ranges, numSamples = 1000) => { + const samples = Array.from({ length: numSamples }, () => + Array.from({ length: ranges.length }, (_, i) => { + const [low, high, confidence] = ranges[i]; + return (Math.random() * (high - low) + low) * confidence; + }) + ); + // Calcluate risk loss for each sample + const results = samples.map((sample) => func(sample)); + return results; + }; + + const results = monteCarloSim(riskLoss, ranges, numSamples); + + // mean and standard deviation from simulation result + // Approximate normal distribution curve + const mean = results.reduce((a, b) => a + b, 0) / results.length; + const variance = + results.reduce((sum, value) => sum + (value - mean) ** 2, 0) / + results.length; + const stdDev = Math.sqrt(variance); + + // Define the range for the normal distribution + const range = [Math.min(...results), Math.max(...results)]; + const numBins = 30; // Number of bins + + // generate normal distribution values based on mean and standard deviation + // Generate a new set of values that follow a normal distribution within the specified range + + const generateNormalDistribution = (mean, stdDev, numSamples, range) => { + const [low, high] = range; + const samples = []; + while (samples.length < numSamples) { + // Box-Muller transform to generate a normal distribution + let u1 = Math.random(); + let u2 = Math.random(); + let z0 = + Math.sqrt(-2.0 * Math.log(u1)) * Math.cos(2.0 * Math.PI * u2); + let value = mean + stdDev * z0; + + // Truncate values to the specified range + if (value >= low && value <= high) { + samples.push(value); + } + } + return samples; + }; + + const normalDistributedValues = generateNormalDistribution( + mean, + stdDev, + 1000, + range + ); + // const normalDistributedValues = results + + const minResult = Math.min(...normalDistributedValues); + const maxResult = Math.max(...normalDistributedValues); + const binWidth = (maxResult - minResult) / numBins; + + const bins = Array.from({ length: numBins }, (_, i) => ({ + start: (minResult + i * binWidth).toFixed(2), + end: (minResult + (i + 1) * binWidth).toFixed(2), + count: 0, + })); + + // Count the frequency of results in each bin + normalDistributedValues.forEach((result) => { + for (let bin of bins) { + if (result >= bin.start && result < bin.end) { + bin.count++; + break; + } + } + }); + + // Normalize the bin counts + const totalSamples = normalDistributedValues.length; + const normalizedCounts = bins.map( + (bin) => bin.count / totalSamples / binWidth + ); + + // Create chart data for histogram + const histogramData = bins.map((bin) => ({ + value: (parseFloat(bin.start) + parseFloat(bin.end)) / 2, + count: bin.count, + label: `${bin.start} - ${bin.end}`, + normalizedCount: normalizedCounts[bins.indexOf(bin)], + })); + + // descending order + // const sortedResults = histogramData.sort((a, b) => b.value - a.value); + // const midIndex = Math.floor(sortedResults.length / 2); + // const firstHalf = sortedResults.slice(0, midIndex); + // const secondHalf = sortedResults.slice(midIndex); + // // Reverse the second half to make it descending + // const secondHalfDescending = [...secondHalf].reverse(); + // const sortedAndReversedValues = [...firstHalf, ...secondHalfDescending]; + + // const sortedHistogramData = histogramData.sort((a, b) => a.value - b.value); + + // Generate normal distribution curve + // const xValues = Array.from({ length: numSamples }, (_, i) => + // minResult + (maxResult - minResult) * (i / (numSamples - 1)) + // ); + // const normalCurve = xValues.map(x => normalDistribution(x, meanArea, stdArea, range)); + + const quantification = { + hist: histogramData.map((data) => data.count), + bin_edges: histogramData.map((data) => data.value.toFixed(2)), + label: histogramData.map((data) => data.label), + }; + setQuant(quantification); + setQuantified(true); } return ( - + - + {impact} - + Expected incidents - + {setMinInc(e.target.value)}} - startAdornment={min}/> + onChange={(e) => { + setMinInc(e.target.value); + }} + startAdornment={min} + /> {setMaxInc(e.target.value)}} - startAdornment={max}/> + onChange={(e) => { + setMaxInc(e.target.value); + }} + startAdornment={max} + /> - + Expected loss per occurrence - + {setMinLoss(e.target.value)}} - startAdornment={min}/> + onChange={(e) => { + setMinLoss(e.target.value); + }} + startAdornment={ + min + } + /> {setMaxLoss(e.target.value)}} - startAdornment={max}/> + onChange={(e) => { + setMaxLoss(e.target.value); + }} + startAdornment={ + max + } + /> - + Confidence - + {setConfInc(e.target.value)}} - startAdornment={Incidents}/> + onChange={(e) => { + setConfInc(e.target.value); + }} + startAdornment={ + Incidents + } + /> {setConfLoss(e.target.value)}} - startAdornment={Losses}/> + onChange={(e) => { + setConfLoss(e.target.value); + }} + startAdornment={ + Losses + } + /> - { - quantified && - + {quantified && ( + + xAxis={[ + { + scaleType: "band", + barGapRatio: 0.1, + label: "Exposure", + data: quantification.bin_edges, + }, + ]} + series={[ + { + color: "#8278d9", + type: "bar", + label: "Count", + data: quantification.hist, + }, + ]} + height={300} + /> - - } + )} - - + + - ) + ); } - function Impacts (props) { - var { threat } = props - var threatInModel = loadedTM.find(t => t.id === threat) - if(threatInModel) { - var impact = threatInModel.threat['Potential Impact'] - console.log(impact) + function Impacts(props) { + var { threat } = props; + var threatInModel = loadedTM.find((t) => t.id === threat); + + if (threatInModel) { + // var impact = threatInModel.threat['Potential Impact'] + var impacts = threatInModel.threat["Potential Impact"].split(", "); + // console.log(impacts); return ( <> - Impacts potentially related to the {impact} of {threatInModel.targetedAsset.assetDisplayname} - Tangible - - { - ImpactList[impact].tangible.map((impact, i) => { - return ( - - ) - }) - } - - Intangible - - { - ImpactList[impact].intangible.map((impact, i) => { - return ( - - ) - }) - } - + {impacts.map(function renderImpact(impact) { + return ( + <> + + Impacts potentially related to the{" "} + + {impact} + {" "} + of{" "} + + {threatInModel.targetedAsset.assetDisplayname} + + + Tangible + + {ImpactList[impact].tangible.map((impact, i) => { + return ; + })} + + Intangible + + {ImpactList[impact].intangible.map((impact, i) => { + return ; + })} + + + ); + })} - ) + ); } } + function handleSaveRisk() { + var newRisk = { + threat: selectedThreat, + riskname: riskName, + model: selectedModel, + }; + const updatedRiskScenarios = [...risks, newRisk]; + setRisks(updatedRiskScenarios); + localStorage.setItem("riskScenarios", JSON.stringify(updatedRiskScenarios)); + } + return ( <> - + Formulate Risk Scenarios Assess Impacts and Communicate Strategic Risk Exposure - - + + + } + sx={{ textTransform: "capitalize" }} + > + + Existing Risk Scenarios + + {risks.map(function renderThreat(risk) { + // console.log(risk); + var threatInModel = loadedTM.find((t) => t.id === risk.threat); + // console.log(threatInModel); + return ( + + + + + Threat: {threatInModel.threat.Threat} + + ; + Scenario: {risk.riskname} + + + + ); + })} + } - sx={{ textTransform: 'capitalize' }}> - + sx={{ textTransform: "capitalize" }} + > + Create new Risk Scenario @@ -214,46 +497,49 @@ export default function Controls() { label="Threat" value={selectedThreat} color="secondary" - onChange={(event) => {setSelectedThreat(event.target.value)}}> - { - loadedTM.map(function renderThreats (threat) { - return ( - - {threat.threat.Threat} - @ - {threat.targetedAsset.assetDisplayname} - - ) - }) - } + onChange={(event) => { + setSelectedThreat(event.target.value); + }} + > + {loadedTM.map(function renderThreats(threat) { + return ( + + {threat.threat.Threat} + + @ + + {threat.targetedAsset.assetDisplayname} + + ); + })} - The threat for which you wish to model the residual risk + + The threat for which you wish to model the residual risk + - { console.log(e.target.value) }} /> + { + setRiskName(e.target.value); + }} + /> +
- - { risks.map(function renderThreat (threat) { - console.log(threat) - return ( - - } - sx={{ textTransform: 'capitalize' }}> - - - - - - ) - }) - }
- ) + ); } const NumberInput = forwardRef(function CustomInput( diff --git a/src/pages/Scenarios.jsx b/src/pages/Scenarios.jsx index f14d191..11560c2 100644 --- a/src/pages/Scenarios.jsx +++ b/src/pages/Scenarios.jsx @@ -19,6 +19,11 @@ import ArrowRightAltIcon from '@mui/icons-material/ArrowRightAlt' import AddIcon from '@mui/icons-material/Add' import { BarChart } from '@mui/x-charts/BarChart' import { LineChart } from '@mui/x-charts/LineChart' +import Dialog from '@mui/material/Dialog'; +import DialogActions from '@mui/material/DialogActions'; +import DialogContent from '@mui/material/DialogContent'; +import DialogContentText from '@mui/material/DialogContentText'; +import DialogTitle from '@mui/material/DialogTitle'; export default Scenarios @@ -41,6 +46,8 @@ function Scenarios({ onModelSelected }) { const [newDesc, setNewDesc] = useState('') const [newKeyProp, setNewKeyProp] = useState('') const [newKeyAsset, setNewKeyAsset] = useState('') + const [open, setOpen] = useState(false) + const [importModelId, setImportModelId] = useState('') try { var loaded = JSON.parse(localStorage.getItem('storedModels')) || [] @@ -99,6 +106,54 @@ function Scenarios({ onModelSelected }) { setModels(storedModels) } + function importModel() { + if (importModelId) { + const modelToImport = models.find(model => model.id === importModelId); + console.log(modelToImport) + if (modelToImport) { + selectModel(modelToImport.id); + } + } + } + + const handleClickOpen = (id) => { + setOpen(true); + selectModel(id) + }; + + const handleCancel = () => { + setOpen(false); + }; + + function handleDelete() { + // deleting threats + var loadedTMs = JSON.parse(localStorage.getItem("threatModels")); + const updatedTMs = Object.keys(loadedTMs).reduce((obj, key) => { + if (key !== selectedModel) obj[key] = loadedTMs[key]; + return obj; + }, {}); + localStorage.setItem("threatModels", JSON.stringify(updatedTMs)); + // deleting risk scenarios + const riskScenarios = JSON.parse( + localStorage.getItem("riskScenarios") || "[]" + ); + const updatedRiskScenarios = riskScenarios.filter( + (scenario) => scenario.model !== selectedModel + ); + localStorage.setItem("riskScenarios", JSON.stringify(updatedRiskScenarios)); + + var storedModels = JSON.parse(localStorage.getItem("storedModels")) || []; + const updatedModels = storedModels.filter( + (model) => model.id !== selectedModel + ); + localStorage.setItem("storedModels", JSON.stringify(updatedModels)); + setModels(updatedModels); + selectModel(""); + setNewName(""); + setNewDesc(""); + selectModelInfo(""); + setOpen(false); + }; function Saved(props) { var storedModels = props.storedModels.sort((a, b) => a.date < b.date) @@ -124,7 +179,31 @@ function Scenarios({ onModelSelected }) { {model.description} - + + + + + + {"Delete Model"} + + + + This action deletes the threat model diagram and associated risk analysis. Are you sure? + + + + + + + + ) @@ -197,8 +276,31 @@ function Scenarios({ onModelSelected }) { Optional: Define the most critical asset - - + + + + Select Model + + Select a model to import + + + diff --git a/src/pages/ThreatTaxonomy-aws.json b/src/pages/ThreatTaxonomy-aws.json new file mode 100644 index 0000000..cde4633 --- /dev/null +++ b/src/pages/ThreatTaxonomy-aws.json @@ -0,0 +1,81 @@ +[ + { + "Threat Category": "Runtime Application Security Threat", + "Threat": "Compromised open source dependencies to exploit vulnerabilities", + "Description": "A external or internal threat actor who has access to an LLM powered application using compromised upstream open source dependencies can enable exploits through vulnerabilities, resulting in reduced confidentiality, integrity and/or availability of LLM system and connected resources", + "Potential Impact": "Integrity, Availability, Confidentiality", + "Affected assets": ["Model", "Data"], + "Permalink": "https://awslabs.github.io/threat-composer/workspaces/Threat%20Composer/threatPacks/GenAIChatBot" + }, + { + "Threat Category": "Runtime Application Security Threat", + "Threat": "Overly dependent on LLM outputs", + "Description": "A LLM-powered application user who is overly dependent on LLM outputs can make unsupported decisions based on incorrect data or recommendations, resulting in reduced integrity of connected and downstream systems and data", + "Potential Impact": "Integrity", + "Affected assets": ["Model"], + "Permalink": "https://awslabs.github.io/threat-composer/workspaces/Threat%20Composer/threatPacks/GenAIChatBot" + }, + { + "Threat Category": "Runtime Application Security Threat", + "Threat": "Insecure Output Handling resulting in RCE/privilege escalation", + "Description": "A malicious user able to influence LLM outputs can craft malicious payloads, which leads to unchecked to downstream function payloads, resulting in reduced achieving remote code execution or privilege escalation of connected and downstream systems and data", + "Potential Impact": "Confidentiality, Integrity", + "Affected assets": ["Model"] + }, + { + "Threat Category": "Runtime Application Security Threat", + "Threat": "Insecure Output Handling resulting in XSS or code injection", + "Description": "A malicious user able to interact with an LLM system can exploit insufficient output encoding, which leads to achieve XSS or code injection, resulting in reduced confidentiality and/or integrity of user data", + "Potential Impact": "Integrity, Confidentiality", + "Affected assets": ["Model"] + }, + { + "Threat Category": "Runtime Application Security Threat", + "Threat": "Direct Prompt Injection", + "Description": "A malicious user able to submit content to an LLM system can embed malicious prompts in that content, which leads to manipulate the LLM into undertaking harmful actions, resulting in reduced compromising integrity and availability of LLM system and connected resources.", + "Potential Impact": "Integrity", + "Affected assets": ["Model"] + }, + { + "Threat Category": "Runtime Application Security Threat", + "Threat": "Exploiting LLM plugins vulnerabilities for RCE", + "Description": "A malicious user permitted to enable third-party LLM plugins can exploit plugin vulnerabilities, which leads to emote code execution, resulting in reduced confidentiality, integrity and/or availability of connected and downstream systems and data.", + "Potential Impact": "Confidentiality, Integrity, Availability", + "Affected assets": ["Model", "Data"] + }, + { + "Threat Category": "Runtime Application Security Threat", + "Threat": "Exploiting LLM plugins or agents vulnerabilities for unauthorized data access", + "Description": "A malicious user who enables compromised LLM plugins or agents in an LLM system can manipulate it via indirect or direct prompt injection, which leads to access unauthorized functionality or data, resulting in reduced confidentiality and/or integrity of connected and downstream systems and data.", + "Potential Impact": "Confidentiality, Integrity, Availability", + "Affected assets": ["Model", "Data"] + }, + { + "Threat Category": "Runtime Application Security Threat", + "Threat": "Direct System Prompt Injection", + "Description": "A malicious user with ability to interact with an LLM system can overwrite the system prompt with a crafted prompts, which leads to force unintended actions from the LLM, negatively impacting LLM system and connected resources.", + "Potential Impact": "Integrity", + "Affected assets": ["Model"] + }, + { + "Threat Category": "Threat through Use", + "Threat": "Unrestricted LLM API usaged resulting in high financial loss", + "Description": "A malicious user who is able to access an LLM API can submit expensive requests, which leads to high hosting costs, resulting in reduced incurring financial losses of the LLM service provider.", + "Potential Impact": "Integrity", + "Affected assets": ["Model"] + }, + { + "Threat Category": "Threat through Use", + "Threat": "Abuse resources hosting LLM application to impact availability of the system", + "Description": "A malicious user with access to submit LLM requests can abuse request batching systems, which leads to overwhelm resources with queued jobs, resulting in reduced availability of the LLM inference API.", + "Potential Impact": "Availability", + "Affected assets": ["Model", "Data"] + }, + { + "Threat Category": "Development-time Threat", + "Threat": "Data Poisoning", + "Description": "A third-party data supplier may intentionally or unintentionally provide poisoned training data can contain manipulation, bias or malicious content, resulting in reduced integrity and/or effectiveness of the LLM model.", + "Potential Impact": "Integrity", + "Affected assets": ["Model", "Data"] + } +]