From 4ee09aa9e462ec2f9fe1cfdeea53479ab38a0d9b Mon Sep 17 00:00:00 2001 From: prakashar11 <prakasha.ramachandra@udemy.com> Date: Wed, 8 Jan 2025 17:32:14 +0530 Subject: [PATCH 1/7] add threat model delete button --- src/App.js | 6 ++--- src/pages/Scenarios.jsx | 55 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 57 insertions(+), 4 deletions(-) diff --git a/src/App.js b/src/App.js index 374f535..48fe56c 100644 --- a/src/App.js +++ b/src/App.js @@ -135,7 +135,7 @@ function App() { </ListItemButton> </ListItem> <ListItem key={'analyze'} disablePadding> - <ListItemButton disabled={!nrAssets} onClick={() => setView('analyze')}> + <ListItemButton disabled={!nrAssets | !selectedModel} onClick={() => setView('analyze')}> <ListItemIcon> <FunctionsOutlinedIcon color="secondary"/> </ListItemIcon> @@ -145,7 +145,7 @@ function App() { </ListItemButton> </ListItem> <ListItem key={'controls'} disablePadding> - <ListItemButton disabled={!nrThreats} onClick={() => setView('controls')}> + <ListItemButton disabled={!nrThreats | !selectedModel} onClick={() => setView('controls')}> <ListItemIcon> <SecurityOutlinedIcon color="secondary"/> </ListItemIcon> @@ -155,7 +155,7 @@ function App() { </ListItemButton> </ListItem> <ListItem key={'Risks'} disablePadding> - <ListItemButton disabled={!nrThreats} onClick={() => setView('risks')}> + <ListItemButton disabled={!nrThreats | !selectedModel} onClick={() => setView('risks')}> <ListItemIcon> <CandlestickChartRoundedIcon color="secondary"/> </ListItemIcon> diff --git a/src/pages/Scenarios.jsx b/src/pages/Scenarios.jsx index f14d191..a12889a 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,7 @@ function Scenarios({ onModelSelected }) { const [newDesc, setNewDesc] = useState('') const [newKeyProp, setNewKeyProp] = useState('') const [newKeyAsset, setNewKeyAsset] = useState('') + const [open, setOpen] = useState(false) try { var loaded = JSON.parse(localStorage.getItem('storedModels')) || [] @@ -99,6 +105,27 @@ function Scenarios({ onModelSelected }) { setModels(storedModels) } + const handleClickOpen = (id) => { + setOpen(true); + selectModel(id) + }; + + const handleCancel = () => { + setOpen(false); + }; + + function handleDelete() { + console.log(selectedModel) + 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 +151,33 @@ function Scenarios({ onModelSelected }) { <Typography> {model.description} </Typography> - <AccordionActions><Button variant="contained" disabled={selectedModel === model.id} onClick={() => { selectModel(model.id) }}>Load</Button></AccordionActions> + <AccordionActions> + <Button variant="contained" disabled={selectedModel === model.id} onClick={() => { selectModel(model.id) }}>Load</Button> + <div> + <Button variant="contained" onClick={() => handleClickOpen(model.id)}>Delete</Button> + <Dialog + open={open} + onClose={handleCancel} + aria-labelledby="alert-dialog-title" + aria-describedby="alert-dialog-description" + > + <DialogTitle id="alert-dialog-title"> + {"Delete Model"} + </DialogTitle> + <DialogContent> + <DialogContentText id="alert-dialog-description"> + This action deletes the threat model diagram and associated risk analysis. Are you sure? + </DialogContentText> + </DialogContent> + <DialogActions> + <Button variant="contained" onClick={handleCancel} autoFocus>Cancel</Button> + <Button variant="contained" onClick={handleDelete} > + Delete + </Button> + </DialogActions> + </Dialog> + </div> + </AccordionActions> </AccordionDetails> </Accordion> ) From 002f94e1f1a1ba8172c73de0104d7a44ced7ec10 Mon Sep 17 00:00:00 2001 From: prakashar11 <prakasha.ramachandra@udemy.com> Date: Wed, 8 Jan 2025 21:00:15 +0530 Subject: [PATCH 2/7] update --- src/pages/Scenarios.jsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/pages/Scenarios.jsx b/src/pages/Scenarios.jsx index a12889a..6433143 100644 --- a/src/pages/Scenarios.jsx +++ b/src/pages/Scenarios.jsx @@ -153,8 +153,7 @@ function Scenarios({ onModelSelected }) { </Typography> <AccordionActions> <Button variant="contained" disabled={selectedModel === model.id} onClick={() => { selectModel(model.id) }}>Load</Button> - <div> - <Button variant="contained" onClick={() => handleClickOpen(model.id)}>Delete</Button> + <Button variant="contained" onClick={() => handleClickOpen(model.id)}>Delete</Button> <Dialog open={open} onClose={handleCancel} @@ -176,7 +175,6 @@ function Scenarios({ onModelSelected }) { </Button> </DialogActions> </Dialog> - </div> </AccordionActions> </AccordionDetails> </Accordion> From 6d9926ac53502251807961224047bf8da5430186 Mon Sep 17 00:00:00 2001 From: prakashar11 <prakasha.ramachandra@udemy.com> Date: Mon, 13 Jan 2025 10:14:22 +0530 Subject: [PATCH 3/7] local simulator --- src/pages/Risks.jsx | 157 ++++++++++++++++++++++++++++++++++++---- src/pages/Scenarios.jsx | 39 +++++++++- 2 files changed, 179 insertions(+), 17 deletions(-) diff --git a/src/pages/Risks.jsx b/src/pages/Risks.jsx index 1bdcb82..89cdafd 100644 --- a/src/pages/Risks.jsx +++ b/src/pages/Risks.jsx @@ -61,18 +61,145 @@ export default function Controls() { var [quantification, setQuant] = useState({}) - async function quantify (minInc, maxInc, minLoss, maxLoss, confInc, confLoss) { + // 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) - 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) + + 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) } @@ -135,9 +262,11 @@ export default function Controls() { quantified && <Box sx={{ display: 'flex' }}> <BarChart - xAxis={[{ scaleType: 'band', barGapRatio: 0.1, data: quantification.bin_edges }]} - series={[{ color: '#8278d9', label: 'Exposure', data: quantification.hist }]} - height={300} /> + xAxis={[{ scaleType: 'band', barGapRatio: 0.1, label: 'Exposure', data: quantification.bin_edges }]} + series={[ + { color: '#8278d9', type: 'bar', label: 'Count', data: quantification.hist}, + ]} + height={300}/> </Box> } diff --git a/src/pages/Scenarios.jsx b/src/pages/Scenarios.jsx index 6433143..ddb21fe 100644 --- a/src/pages/Scenarios.jsx +++ b/src/pages/Scenarios.jsx @@ -47,6 +47,7 @@ function Scenarios({ onModelSelected }) { 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')) || [] @@ -105,6 +106,16 @@ 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) @@ -115,7 +126,6 @@ function Scenarios({ onModelSelected }) { }; function handleDelete() { - console.log(selectedModel) var storedModels = JSON.parse(localStorage.getItem('storedModels')) || [] const updatedModels = storedModels.filter((model) => model.id !== selectedModel) localStorage.setItem('storedModels', JSON.stringify(updatedModels)) @@ -248,8 +258,31 @@ function Scenarios({ onModelSelected }) { <FormHelperText>Optional: Define the most critical asset</FormHelperText> </FormControl> </Box> - - <Button variant="contained" onClick={createModel} >Create</Button> + <Box sx={{ display: 'flex', gap: '1em' }}> + <Button variant="contained" onClick={createModel} autoFocus>Create</Button> + <FormControl sx={{ m: 0, minWidth: 120, flexGrow: 1 }}> + <InputLabel id="import-model"><em>Select Model</em></InputLabel> + <Select + labelId="import-model" + id="import-model-helper" + value={importModelId} + onChange={(e) => { setImportModelId(e.target.value) }} + label="Import Model"> + <MenuItem value=""> + <em>None</em> + </MenuItem> + {models.map(model => ( + <MenuItem key={model.id} value={model.id}> + {model.name} + </MenuItem> + ))} + </Select> + <FormHelperText>Select a model to import</FormHelperText> + </FormControl> + <Button variant="contained" onClick={importModel}> + Import + </Button> + </Box> </Box> </AccordionDetails> </Accordion> From 5ee68862d7a13d40244d218495af3de7154d3531 Mon Sep 17 00:00:00 2001 From: prakashar11 <prakasha.ramachandra@udemy.com> Date: Wed, 22 Jan 2025 16:02:35 +0530 Subject: [PATCH 4/7] dark theme, AWS LLM chat threat pack, delete model --- package-lock.json | 282 +++++++++--- package.json | 2 +- src/App.js | 292 ++++++++---- src/pages/Analyze.jsx | 724 +++++++++++++++++++++--------- src/pages/Risks.jsx | 465 ++++++++++++------- src/pages/Scenarios.jsx | 34 +- src/pages/ThreatTaxonomy-aws.json | 81 ++++ 7 files changed, 1341 insertions(+), 539 deletions(-) create mode 100644 src/pages/ThreatTaxonomy-aws.json 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 48fe56c..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 ( + <Box + sx={{ + display: "flex", + width: "100%", + alignItems: "center", + justifyContent: "center", + bgcolor: "background.default", + color: "text.primary", + borderRadius: 1, + p: 3, + width: 1, + }} + > + <FormControl> + <FormLabel id="theme-toggle" style={{ lineHeight: "24px" }}> + Theme + </FormLabel> + <RadioGroup + aria-labelledby="theme-toggle" + name="theme-toggle" + row + value={mode} + onChange={(event) => setMode(event.target.value)} + > + <FormControlLabel value="system" control={<Radio />} label="System" /> + <FormControlLabel value="light" control={<Radio />} label="Light" /> + <FormControlLabel value="dark" control={<Radio />} label="Dark" /> + </RadioGroup> + </FormControl> + </Box> + ); +} 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 = ( <div> <Toolbar - sx={{ - backgroundImage: 'url(/public/noise4.png)', - }}> + sx={{ + backgroundImage: "url(/public/noise4.png)", + }} + > <Typography variant="h6" noWrap component="a" sx={{ mr: 2, - display: { xs: 'none', md: 'flex' }, - fontFamily: 'monospace', + display: { xs: "none", md: "flex" }, + fontFamily: "monospace", fontWeight: 700, - letterSpacing: '.1rem', - color: 'inherit', - textDecoration: 'none', + letterSpacing: ".1rem", + color: "inherit", + textDecoration: "none", }} > - ThreatFinder<span style={{color: 'hsl(95, 28%, 44%)'}}>AI</span> + ThreatFinder<span style={{ color: "hsl(95, 28%, 44%)" }}>AI</span> </Typography> </Toolbar> <Divider /> <List> - <ListSubheader style={{lineHeight: '24px'}}> - Stages - </ListSubheader> - <ListItem key={'goals'} disablePadding onClick={() => setView('goals')}> + <ModeSwitcher /> + </List> + <List> + <ListSubheader style={{ lineHeight: "24px" }}>Stages</ListSubheader> + <ListItem key={"goals"} disablePadding onClick={() => setView("goals")}> <ListItemButton> <ListItemIcon> - <FlagIcon color="secondary"/> + <FlagIcon color="secondary" /> </ListItemIcon> - <ListItemText primary={'Scenarios'} /> - {['risks', 'model', 'analyze', 'controls'].includes(view) && <CheckIcon sx={{ fontSize: 15 }} />} - {view === 'goals' && <EditIcon sx={{ fontSize: 15 }} />} + <ListItemText primary={"Scenarios"} /> + {["risks", "model", "analyze", "controls"].includes(view) && ( + <CheckIcon sx={{ fontSize: 15 }} /> + )} + {view === "goals" && <EditIcon sx={{ fontSize: 15 }} />} </ListItemButton> </ListItem> - <ListItem key={'model'} disablePadding > - <ListItemButton disabled={!selectedModel} onClick={() => setView('model')}> + <ListItem key={"model"} disablePadding> + <ListItemButton + disabled={!selectedModel} + onClick={() => setView("model")} + > <ListItemIcon> - <ArchitectureIcon color="secondary"/> + <ArchitectureIcon color="secondary" /> </ListItemIcon> - <ListItemText primary={'Model'} /> - {['risks', 'analyze', 'controls'].includes(view) && <CheckIcon sx={{ fontSize: 15 }} />} - {view === 'model' && <EditIcon sx={{ fontSize: 15 }} />} + <ListItemText primary={"Model"} /> + {["risks", "analyze", "controls"].includes(view) && ( + <CheckIcon sx={{ fontSize: 15 }} /> + )} + {view === "model" && <EditIcon sx={{ fontSize: 15 }} />} </ListItemButton> </ListItem> - <ListItem key={'analyze'} disablePadding> - <ListItemButton disabled={!nrAssets | !selectedModel} onClick={() => setView('analyze')}> + <ListItem key={"analyze"} disablePadding> + <ListItemButton + disabled={!nrAssets | !selectedModel} + onClick={() => setView("analyze")} + > <ListItemIcon> - <FunctionsOutlinedIcon color="secondary"/> + <FunctionsOutlinedIcon color="secondary" /> </ListItemIcon> - <ListItemText primary={'Analyze'} /> - {['risks', 'controls'].includes(view) && <CheckIcon sx={{ fontSize: 15 }} />} - {view === 'analyze' && <EditIcon sx={{ fontSize: 15 }} />} + <ListItemText primary={"Analyze"} /> + {["risks", "controls"].includes(view) && ( + <CheckIcon sx={{ fontSize: 15 }} /> + )} + {view === "analyze" && <EditIcon sx={{ fontSize: 15 }} />} </ListItemButton> </ListItem> - <ListItem key={'controls'} disablePadding> - <ListItemButton disabled={!nrThreats | !selectedModel} onClick={() => setView('controls')}> + <ListItem key={"controls"} disablePadding> + <ListItemButton + disabled={!nrThreats | !selectedModel} + onClick={() => setView("controls")} + > <ListItemIcon> - <SecurityOutlinedIcon color="secondary"/> + <SecurityOutlinedIcon color="secondary" /> </ListItemIcon> - <ListItemText primary={'Controls'} /> - {['risks'].includes(view) && <CheckIcon sx={{ fontSize: 15 }} />} - {view === 'controls' && <EditIcon sx={{ fontSize: 15 }} />} + <ListItemText primary={"Controls"} /> + {["risks"].includes(view) && <CheckIcon sx={{ fontSize: 15 }} />} + {view === "controls" && <EditIcon sx={{ fontSize: 15 }} />} </ListItemButton> </ListItem> - <ListItem key={'Risks'} disablePadding> - <ListItemButton disabled={!nrThreats | !selectedModel} onClick={() => setView('risks')}> + <ListItem key={"Risks"} disablePadding> + <ListItemButton + disabled={!nrThreats | !selectedModel} + onClick={() => setView("risks")} + > <ListItemIcon> - <CandlestickChartRoundedIcon color="secondary"/> + <CandlestickChartRoundedIcon color="secondary" /> </ListItemIcon> - <ListItemText primary={'Risks'} /> - {view === 'risks' && <EditIcon sx={{ fontSize: 15 }} />} + <ListItemText primary={"Risks"} /> + {view === "risks" && <EditIcon sx={{ fontSize: 15 }} />} </ListItemButton> </ListItem> - </List> - <Divider /> - <List> - <ListSubheader style={{lineHeight: '24px'}}> - Instructions - </ListSubheader> - <ListItem> - <Instructions/> - </ListItem> - </List> - <Stats diagram={diagram} nrAssets={updateNrAssets}/> + </List> + <Divider /> + <List> + <ListSubheader style={{ lineHeight: "24px" }}> + Instructions + </ListSubheader> + <ListItem> + <Instructions /> + </ListItem> + </List> + <Stats diagram={diagram} nrAssets={updateNrAssets} /> </div> - ) + ); return ( - <ThemeProvider theme={darkTheme}> + <ThemeProvider theme={darkTheme} defaultMode="dark"> <CssBaseline /> - <Box sx={{ display: 'flex' }}> + <Box sx={{ display: "flex" }}> <Box component="nav" - sx={{ width: { sm: drawerWidth }, flexShrink: { sm: 0 }, backgroundImage: 'url(noise4.png)' }} - aria-label="mailbox folders"> + sx={{ + width: { sm: drawerWidth }, + flexShrink: { sm: 0 }, + backgroundImage: "url(noise4.png)", + }} + aria-label="mailbox folders" + > <Drawer variant="temporary" ModalProps={{ keepMounted: true, // Better open performance on mobile. }} sx={{ - display: { xs: 'block', sm: 'none' }, - '& .MuiDrawer-paper': { boxSizing: 'border-box', width: drawerWidth }, - }}> + display: { xs: "block", sm: "none" }, + "& .MuiDrawer-paper": { + boxSizing: "border-box", + width: drawerWidth, + }, + }} + > {drawer} </Drawer> <Drawer variant="permanent" sx={{ - display: { xs: 'none', sm: 'block' }, - '& .MuiDrawer-paper': { boxSizing: 'border-box', width: drawerWidth }, + display: { xs: "none", sm: "block" }, + "& .MuiDrawer-paper": { + boxSizing: "border-box", + width: drawerWidth, + }, }} - open> + open + > {drawer} </Drawer> </Box> <Box component="main" - sx={{ flexGrow: 1, p: 3, width: { sm: `calc(100% - ${drawerWidth}px)` }, height: '100vh' }}> - {view === 'goals' && <Scenarios onModelSelected={(selectedModel) => { updateSelectedModel(selectedModel) }}/>} - {view === 'model' && <DrawIO sendDiagram={receiveDiagram}/>} - {view === 'analyze' && <Analyze onNrThreats={updateNrThreats}/>} - {view === 'controls' && <Controls />} - {view === 'risks' && <Risks />} + sx={{ + flexGrow: 1, + p: 3, + width: { sm: `calc(100% - ${drawerWidth}px)` }, + height: "100vh", + }} + > + {view === "goals" && ( + <Scenarios + onModelSelected={(selectedModel) => { + updateSelectedModel(selectedModel); + }} + /> + )} + {view === "model" && <DrawIO sendDiagram={receiveDiagram} />} + {view === "analyze" && <Analyze onNrThreats={updateNrThreats} />} + {view === "controls" && <Controls />} + {view === "risks" && <Risks />} </Box> </Box> </ThemeProvider> - ) + ); } 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 ( - <Accordion> - <AccordionSummary - expandIcon={<ExpandMoreIcon />} - aria-controls="panel1-content" - id="panel1-header" - sx={{ textTransform: 'capitalize' }}> - <Box sx={{ display: 'flex', flexDirection: 'row', width: '100%', justifyContent: 'space-between' }}> - <span>{k.Threat}</span> - <span>({k['Potential Impact'].split(', ').map(i => i.split('')[0])})</span> - </Box> - </AccordionSummary> - <AccordionDetails> - {k.Description} - <AccordionActions> - <Button color="secondary" onClick={(e) => { + {threats.map((k) => { + var impacts = k["Potential Impact"].split(", ").some((i) => { + return propFilter[i]; + }); + if (impacts) { + return ( + <Accordion> + <AccordionSummary + expandIcon={<ExpandMoreIcon />} + aria-controls="panel1-content" + id="panel1-header" + sx={{ textTransform: "capitalize" }} + > + <Box + sx={{ + display: "flex", + flexDirection: "row", + width: "100%", + justifyContent: "space-between", + }} + > + <span>{k.Threat}</span> + <span> + ( + {k["Potential Impact"] + .split(", ") + .map((i) => i.split("")[0])} + ) + </span> + </Box> + </AccordionSummary> + <AccordionDetails> + {k.Description} + <AccordionActions> + <Button + color="secondary" + onClick={(e) => { addToThreatModel({ - id: Math.random()*1e17, + id: Math.random() * 1e17, threat: k, targetedAsset: asset, - controls: []}) - }}>Add to Threat Model</Button> - </AccordionActions> - </AccordionDetails> - </Accordion> - ) - } return null - }) - } + controls: [], + }); + }} + > + Add to Threat Model + </Button> + </AccordionActions> + </AccordionDetails> + </Accordion> + ); + } + 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 ( <> - <Paper sx={{ p: '1em' }} elevation={groupIsKey ? 0 : 0}> - <Box sx={{ display: 'flex', alignItems: 'center', flexDirection: 'row', mb: '1em' }}> + <Paper sx={{ p: "1em" }} elevation={groupIsKey ? 0 : 0}> + <Box + sx={{ + display: "flex", + alignItems: "center", + flexDirection: "row", + mb: "1em", + }} + > {groupIsKey && <ArrowRightAltIcon color="secondary" />} - <Typography variant="h5" sx={{ fontStyle: 'italic', textTransform: 'capitalize' }}> - {category.replace('/', ' & ')} + <Typography + variant="h5" + sx={{ fontStyle: "italic", textTransform: "capitalize" }} + > + {category.replace("/", " & ")} </Typography> </Box> - {groupIsKey && - <Typography > + {groupIsKey && ( + <Typography> These threats may impact or relate to a key asset. </Typography> - } - {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 (<Accordion> - <AccordionSummary - expandIcon={<ExpandMoreIcon />} - aria-controls="panel1-content" - id="panel1-header" - sx={{ textTransform: 'capitalize' }}> - {asset.assetDisplayname} - </AccordionSummary> - <AccordionDetails> - <Box sx={{ display: 'flex' }}> - <Box sx={{ minWidth: '35ch', maxWidth: '35ch' }}> - <Typography sx={{ opacity: '.6', mb: '1em' }}>{asset.assetLifeCycleStage.split(', ').join('—')}</Typography> - <Typography sx={{ fontStyle: 'italic', mb: '1em' }}>"{asset.assetDescription}"</Typography> + return ( + <Accordion> + <AccordionSummary + expandIcon={<ExpandMoreIcon />} + aria-controls="panel1-content" + id="panel1-header" + sx={{ textTransform: "capitalize" }} + > + {asset.assetDisplayname} + </AccordionSummary> + <AccordionDetails> + <Box sx={{ display: "flex" }}> + <Box sx={{ minWidth: "35ch", maxWidth: "35ch" }}> + <Typography sx={{ opacity: ".6", mb: "1em" }}> + {asset.assetLifeCycleStage.split(", ").join("—")} + </Typography> + <Typography sx={{ fontStyle: "italic", mb: "1em" }}> + "{asset.assetDescription}" + </Typography> + </Box> + + <Box sx={{ flexGrow: 1 }}> + {ThreatList(asset, category)} + </Box> </Box> - - <Box sx={{ flexGrow: 1 }}> - {ThreatList(asset, category)} - </Box> - </Box> - - </AccordionDetails> - </Accordion>) + </AccordionDetails> + </Accordion> + ); } else { //console.log(asset, 'should NOT be displayed', asset.assetLifeCycleStage) - return null - } + return null; + } })} </Paper> </> - ) + ); } } 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 ( <> - <Typography level="title-lg" variant='h4'> + <Typography level="title-lg" variant="h4"> Threat Analysis </Typography> <Typography variant="subtitle"> @@ -257,124 +339,322 @@ function Analyze({ onNrThreats }) { expandIcon={<TuneRoundedIcon />} aria-controls="panel1-content" id="panel1-header" - sx={{ textTransform: 'capitalize', mt: '1em' }}> + sx={{ textTransform: "capitalize", mt: "1em" }} + > Filters </AccordionSummary> <AccordionDetails> - Automated threat identification easily produces false positives (<i>i.e.,</i> irrelevant threats).<br /> 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>i.e.,</i> irrelevant threats). + <br /> Apply filters to investigate threats from different angles — + the initial selection reflects the previously defined key asset and + security property. {propFilter.Confidentiality} - <Box sx={{ display: 'flex' }}> - <Box sx={{ maxHeight: '13em', p: '1em' }}> + <Box sx={{ display: "flex" }}> + <Box sx={{ maxHeight: "13em", p: "1em" }}> <FormControl component="fieldset"> - <FormLabel component="legend" color="secondary">Security Properties</FormLabel> + <FormLabel component="legend" color="secondary"> + Security Properties + </FormLabel> <FormGroup> <FormControlLabel - control={<Switch checked={propFilter.Confidentiality} onChange={handlePropChange} name="Confidentiality" color="secondary" />} - label="Confidentiality" /> + control={ + <Switch + checked={propFilter.Confidentiality} + onChange={handlePropChange} + name="Confidentiality" + color="secondary" + /> + } + label="Confidentiality" + /> <FormControlLabel - control={<Switch checked={propFilter.Integrity} onChange={handlePropChange} name="Integrity" color="secondary" />} - label="Integrity" /> + control={ + <Switch + checked={propFilter.Integrity} + onChange={handlePropChange} + name="Integrity" + color="secondary" + /> + } + label="Integrity" + /> <FormControlLabel - control={<Switch checked={propFilter.Availability} onChange={handlePropChange} name="Availability" color="secondary" />} - label="Availability" /> + control={ + <Switch + checked={propFilter.Availability} + onChange={handlePropChange} + name="Availability" + color="secondary" + /> + } + label="Availability" + /> <FormControlLabel - control={<Switch checked={propFilter.Accountability} onChange={handlePropChange} name="Accountability" color="secondary" />} - label="Accountability" /> + control={ + <Switch + checked={propFilter.Accountability} + onChange={handlePropChange} + name="Accountability" + color="secondary" + /> + } + label="Accountability" + /> </FormGroup> <FormHelperText> - <span style={{ textTransform: 'capitalize' }}>{selectedModelInfo.keyProp} is your key property</span> + <span style={{ textTransform: "capitalize" }}> + {selectedModelInfo.keyProp} is your key property + </span> </FormHelperText> </FormControl> </Box> - <Box sx={{ maxHeight: '12em', p: '1em', overflow: 'scroll' }}> + <Box sx={{ maxHeight: "12em", p: "1em", overflow: "scroll" }}> <FormControl component="fieldset"> - <FormLabel component="legend" color="secondary">Design Stage</FormLabel> + <FormLabel component="legend" color="secondary"> + Design Stage + </FormLabel> <FormGroup> <FormControlLabel - control={<Switch checked={propFilter['Business Understanding']} onChange={handlePropChange} name="Business Understanding" color="secondary" />} - label="Business Understanding" /> + control={ + <Switch + checked={propFilter["Business Understanding"]} + onChange={handlePropChange} + name="Business Understanding" + color="secondary" + /> + } + label="Business Understanding" + /> <FormControlLabel - control={<Switch checked={propFilter['Business Goal Definition']} onChange={handlePropChange} name="Business Goal Definition" color="secondary" />} - label="Business Goal Definition" /> + control={ + <Switch + checked={propFilter["Business Goal Definition"]} + onChange={handlePropChange} + name="Business Goal Definition" + color="secondary" + /> + } + label="Business Goal Definition" + /> <FormControlLabel - control={<Switch checked={propFilter['Data Pre-processing']} onChange={handlePropChange} name="Data Pre-processing" color="secondary" />} - label="Data Pre-processing" /> + control={ + <Switch + checked={propFilter["Data Pre-processing"]} + onChange={handlePropChange} + name="Data Pre-processing" + color="secondary" + /> + } + label="Data Pre-processing" + /> <FormControlLabel - control={<Switch checked={propFilter['Model Tuning']} onChange={handlePropChange} name="Model Tuning" color="secondary" />} - label="Model Tuning" /> + control={ + <Switch + checked={propFilter["Model Tuning"]} + onChange={handlePropChange} + name="Model Tuning" + color="secondary" + /> + } + label="Model Tuning" + /> <FormControlLabel - control={<Switch checked={propFilter['Feature Selection']} onChange={handlePropChange} name="Feature Selection" color="secondary" />} - label="Feature Selection" /> + control={ + <Switch + checked={propFilter["Feature Selection"]} + onChange={handlePropChange} + name="Feature Selection" + color="secondary" + /> + } + label="Feature Selection" + /> <FormControlLabel - control={<Switch checked={propFilter['Data Exploration']} onChange={handlePropChange} name="Data Exploration" color="secondary" />} - label="Data Exploration" /> + control={ + <Switch + checked={propFilter["Data Exploration"]} + onChange={handlePropChange} + name="Data Exploration" + color="secondary" + /> + } + label="Data Exploration" + /> <FormControlLabel - control={<Switch checked={propFilter['Data Ingestion']} onChange={handlePropChange} name="Data Ingestion" color="secondary" />} - label="Data Ingestion" /> + control={ + <Switch + checked={propFilter["Data Ingestion"]} + onChange={handlePropChange} + name="Data Ingestion" + color="secondary" + /> + } + label="Data Ingestion" + /> <FormControlLabel - control={<Switch checked={propFilter['Model Training']} onChange={handlePropChange} name="Model Training" color="secondary" />} - label="Model Training" /> + control={ + <Switch + checked={propFilter["Model Training"]} + onChange={handlePropChange} + name="Model Training" + color="secondary" + /> + } + label="Model Training" + /> <FormControlLabel - control={<Switch checked={propFilter['Model Selection']} onChange={handlePropChange} name="Model Selection" color="secondary" />} - label="Model Selection" /> + control={ + <Switch + checked={propFilter["Model Selection"]} + onChange={handlePropChange} + name="Model Selection" + color="secondary" + /> + } + label="Model Selection" + /> <FormControlLabel - control={<Switch checked={propFilter['Model Building']} onChange={handlePropChange} name="Model Building" color="secondary" />} - label="Model Building" /> + control={ + <Switch + checked={propFilter["Model Building"]} + onChange={handlePropChange} + name="Model Building" + color="secondary" + /> + } + label="Model Building" + /> <FormControlLabel - control={<Switch checked={propFilter['Model Deployment']} onChange={handlePropChange} name="Model Deployment" color="secondary" />} - label="Model Deployment" /> + control={ + <Switch + checked={propFilter["Model Deployment"]} + onChange={handlePropChange} + name="Model Deployment" + color="secondary" + /> + } + label="Model Deployment" + /> <FormControlLabel - control={<Switch checked={propFilter['Model Maintenance']} onChange={handlePropChange} name="Model Maintenance" color="secondary" />} - label="Model Maintenance" /> + control={ + <Switch + checked={propFilter["Model Maintenance"]} + onChange={handlePropChange} + name="Model Maintenance" + color="secondary" + /> + } + label="Model Maintenance" + /> <FormControlLabel - control={<Switch checked={propFilter['Transfer Learning']} onChange={handlePropChange} name="Transfer Learning" color="secondary" />} - label="Transfer Learning" /> + control={ + <Switch + checked={propFilter["Transfer Learning"]} + onChange={handlePropChange} + name="Transfer Learning" + color="secondary" + /> + } + label="Transfer Learning" + /> </FormGroup> - <FormHelperText> - </FormHelperText> + <FormHelperText></FormHelperText> </FormControl> </Box> - <Box sx={{ maxHeight: '12em', p: '1em', overflow: 'scroll' }}> + <Box sx={{ maxHeight: "12em", p: "1em", overflow: "scroll" }}> <FormControl component="fieldset"> - <FormLabel component="legend" color="secondary">Asset Type</FormLabel> + <FormLabel component="legend" color="secondary"> + Asset Type + </FormLabel> <FormGroup> <FormControlLabel - control={<Switch checked={propFilter['Actor']} onChange={handleAssetChange} name="Actor" color="secondary" />} - label="Actor" /> + control={ + <Switch + checked={propFilter["Actor"]} + onChange={handleAssetChange} + name="Actor" + color="secondary" + /> + } + label="Actor" + /> <FormControlLabel - control={<Switch checked={propFilter['Data']} onChange={handleAssetChange} name="Data" color="secondary" />} - label="Data" /> + control={ + <Switch + checked={propFilter["Data"]} + onChange={handleAssetChange} + name="Data" + color="secondary" + /> + } + label="Data" + /> <FormControlLabel - control={<Switch checked={propFilter['Model']} onChange={handleAssetChange} name="Model" color="secondary" />} - label="Model" /> + control={ + <Switch + checked={propFilter["Model"]} + onChange={handleAssetChange} + name="Model" + color="secondary" + /> + } + label="Model" + /> <FormControlLabel - control={<Switch checked={propFilter['Artefacts']} onChange={handleAssetChange} name="Artefacts" color="secondary" />} - label="Artefacts" /> + control={ + <Switch + checked={propFilter["Artefacts"]} + onChange={handleAssetChange} + name="Artefacts" + color="secondary" + /> + } + label="Artefacts" + /> <FormControlLabel - control={<Switch checked={propFilter['Processes']} onChange={handleAssetChange} name="Processes" color="secondary" />} - label="Processes" /> + control={ + <Switch + checked={propFilter["Processes"]} + onChange={handleAssetChange} + name="Processes" + color="secondary" + /> + } + label="Processes" + /> <FormControlLabel - control={<Switch checked={propFilter['Environment/Tools']} onChange={handleAssetChange} name="Environment/Tools" color="secondary" />} - label="Environment & Tools" /> + control={ + <Switch + checked={propFilter["Environment/Tools"]} + onChange={handleAssetChange} + name="Environment/Tools" + color="secondary" + /> + } + label="Environment & Tools" + /> </FormGroup> - <FormHelperText> - </FormHelperText> + <FormHelperText></FormHelperText> </FormControl> </Box> - <Box sx={{ maxHeight: '12em', p: '1em', overflow: 'scroll' }}> + <Box sx={{ maxHeight: "12em", p: "1em", overflow: "scroll" }}> <FormControl fullWidth> <InputLabel variant="standard" htmlFor="uncontrolled-native"> Knowledge Base </InputLabel> <NativeSelect - defaultValue={'enisa'} + defaultValue={"enisa"} inputProps={{ - name: 'age', - id: 'uncontrolled-native', - }}> - <option value={'enisa'}>ENISA AI Landscape</option> - <option value={'owasp'}>OWASP AI Exchange</option> - <option value={'mitre'}>MITRE Atlas</option> + name: "age", + id: "uncontrolled-native", + }} + onChange={handleThreatTaxonomyChange} + > + <option value={"enisa"}>ENISA AI Landscape</option> + <option value={"owasp"}>OWASP AI Exchange</option> + <option value={"mitre"}>MITRE Atlas</option> + <option value={"aws"}>AWS GenAI Threat Pack</option> </NativeSelect> </FormControl> </Box> @@ -384,7 +664,7 @@ function Analyze({ onNrThreats }) { <ThreatModel threatModel={threatModel}></ThreatModel> {assetList(detectedAssets, selectedModelInfo)} </> - ) + ); } export function ThreatModel (props) { diff --git a/src/pages/Risks.jsx b/src/pages/Risks.jsx index 89cdafd..cce6fe1 100644 --- a/src/pages/Risks.jsx +++ b/src/pages/Risks.jsx @@ -27,39 +27,46 @@ 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] || [] + // var loadedRiskScenarios = + // JSON.parse(localStorage.getItem("riskScenarios"))[selectedModel] || []; + var loadedRiskScenarios = JSON.parse( + localStorage.getItem("riskScenarios") + ).filter((m) => m.model === selectedModel); } catch (e) { - console.warn('error parsing risks from storage', e) - loadedRiskScenarios = [] + console.warn("error parsing risks from storage", e); + loadedRiskScenarios = []; } + console.log(loadedRiskScenarios); + const [riskName, setRiskName] = useState(""); //var [threatModel, _] = useState(loadedTM) - var [risks, setRisks] = useState(loadedRiskScenarios) + var [risks, setRisks] = useState(loadedRiskScenarios); - var [selectedThreat, setSelectedThreat] = useState({}) + var [selectedThreat, setSelectedThreat] = useState({}); - function ImpactCard (props) { - var { impact } = props - var [quantified, setQuantified] = useState(false) - var [minInc, setMinInc] = useState(0) - var [maxInc, setMaxInc] = 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 [minLoss, setMinLoss] = useState(0); + var [maxLoss, setMaxLoss] = useState(); - var [confInc, setConfInc] = useState(0.95) - var [confLoss, setConfLoss] = useState(0.95) + var [confInc, setConfInc] = useState(0.95); + var [confLoss, setConfLoss] = useState(0.95); - var [quantification, setQuant] = useState({}) + var [quantification, setQuant] = useState({}); // async function quantify (minInc, maxInc, minLoss, maxLoss, confInc, confLoss) { // console.log('quantification using', arguments) @@ -80,13 +87,13 @@ export default function Controls() { // 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) - + console.log("quantification using", arguments); + const numSamples = 10000; const ranges = [ [minInc, maxInc, confInc], - [minLoss, maxLoss, confLoss] - ] + [minLoss, maxLoss, confLoss], + ]; function riskLoss(x) { const Inc = x[0]; @@ -105,17 +112,18 @@ export default function Controls() { }) ); // Calcluate risk loss for each sample - const results = samples.map(sample => func(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 + // 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; + results.reduce((sum, value) => sum + (value - mean) ** 2, 0) / + results.length; const stdDev = Math.sqrt(variance); // Define the range for the normal distribution @@ -132,7 +140,8 @@ export default function Controls() { // 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 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 @@ -143,9 +152,14 @@ export default function Controls() { return samples; }; - const normalDistributedValues = generateNormalDistribution(mean, stdDev, 1000, range) + 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; @@ -155,9 +169,9 @@ export default function Controls() { end: (minResult + (i + 1) * binWidth).toFixed(2), count: 0, })); - + // Count the frequency of results in each bin - normalDistributedValues.forEach(result => { + normalDistributedValues.forEach((result) => { for (let bin of bins) { if (result >= bin.start && result < bin.end) { bin.count++; @@ -168,16 +182,18 @@ export default function Controls() { // Normalize the bin counts const totalSamples = normalDistributedValues.length; - const normalizedCounts = bins.map(bin => bin.count / totalSamples / binWidth); + const normalizedCounts = bins.map( + (bin) => bin.count / totalSamples / binWidth + ); // Create chart data for histogram - const histogramData = bins.map(bin => ({ + 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); @@ -186,7 +202,7 @@ export default function Controls() { // // 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 @@ -196,141 +212,284 @@ export default function Controls() { // 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) + 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 ( - <Card sx={{ flex: '1 0 calc(50% - 1em)', minWidth: '300px' }} variant="outlined"> + <Card + sx={{ flex: "1 0 calc(50% - 1em)", minWidth: "300px" }} + variant="outlined" + > <CardContent> - <Typography variant="h7" component="div" sx={{ mb: '.5em', opacity: 1 }}> + <Typography + variant="h7" + component="div" + sx={{ mb: ".5em", opacity: 1 }} + > {impact} </Typography> - <Typography sx={{ opacity: .5, fontFamily: 'mono', textTransform: 'uppercase'}}> + <Typography + sx={{ + opacity: 0.5, + fontFamily: "mono", + textTransform: "uppercase", + }} + > Expected incidents </Typography> - <Box sx={{ display: 'flex', gap: '.5em', mb:'.5em'}}> + <Box sx={{ display: "flex", gap: ".5em", mb: ".5em" }}> <NumberInput - sx={{ flexGrow: 1}} + sx={{ flexGrow: 1 }} value={minInc} - onChange={(e) => {setMinInc(e.target.value)}} - startAdornment={<InputAdornment>min</InputAdornment>}/> + onChange={(e) => { + setMinInc(e.target.value); + }} + startAdornment={<InputAdornment key={minInc}>min</InputAdornment>} + /> <NumberInput - sx={{ flexGrow: 1}} + sx={{ flexGrow: 1 }} value={maxInc} - onChange={(e) => {setMaxInc(e.target.value)}} - startAdornment={<InputAdornment>max</InputAdornment>}/> + onChange={(e) => { + setMaxInc(e.target.value); + }} + startAdornment={<InputAdornment key={maxInc}>max</InputAdornment>} + /> </Box> - <Typography sx={{ opacity: .5, fontFamily: 'mono', textTransform: 'uppercase'}}> + <Typography + sx={{ + opacity: 0.5, + fontFamily: "mono", + textTransform: "uppercase", + }} + > Expected loss per occurrence </Typography> - <Box sx={{ display: 'flex', gap: '.5em', mb:'.5em'}}> + <Box sx={{ display: "flex", gap: ".5em", mb: ".5em" }}> <NumberInput - sx={{ flexGrow: 1}} + sx={{ flexGrow: 1 }} value={minLoss} - onChange={(e) => {setMinLoss(e.target.value)}} - startAdornment={<InputAdornment>min</InputAdornment>}/> + onChange={(e) => { + setMinLoss(e.target.value); + }} + startAdornment={ + <InputAdornment key={minLoss}>min</InputAdornment> + } + /> <NumberInput - sx={{ flexGrow: 1}} + sx={{ flexGrow: 1 }} value={maxLoss} - onChange={(e) => {setMaxLoss(e.target.value)}} - startAdornment={<InputAdornment>max</InputAdornment>}/> + onChange={(e) => { + setMaxLoss(e.target.value); + }} + startAdornment={ + <InputAdornment key={maxLoss}>max</InputAdornment> + } + /> </Box> - <Typography sx={{ opacity: .5, fontFamily: 'mono', textTransform: 'uppercase'}}> + <Typography + sx={{ + opacity: 0.5, + fontFamily: "mono", + textTransform: "uppercase", + }} + > Confidence </Typography> - <Box sx={{ display: 'flex', flexDirection: 'column', gap: '.5em'}}> + <Box sx={{ display: "flex", flexDirection: "column", gap: ".5em" }}> <NumberInput - sx={{ flexGrow: 1}} + sx={{ flexGrow: 1 }} value={confInc} - onChange={(e) => {setConfInc(e.target.value)}} - startAdornment={<InputAdornment>Incidents</InputAdornment>}/> + onChange={(e) => { + setConfInc(e.target.value); + }} + startAdornment={ + <InputAdornment key={confInc}>Incidents</InputAdornment> + } + /> <NumberInput - sx={{ flexGrow: 1}} + sx={{ flexGrow: 1 }} value={confLoss} - onChange={(e) => {setConfLoss(e.target.value)}} - startAdornment={<InputAdornment>Losses</InputAdornment>}/> + onChange={(e) => { + setConfLoss(e.target.value); + }} + startAdornment={ + <InputAdornment key={confLoss}>Losses</InputAdornment> + } + /> </Box> - { - quantified && - <Box sx={{ display: 'flex' }}> + {quantified && ( + <Box sx={{ display: "flex" }}> <BarChart - xAxis={[{ scaleType: 'band', barGapRatio: 0.1, label: 'Exposure', data: quantification.bin_edges }]} - series={[ - { color: '#8278d9', type: 'bar', label: 'Count', data: quantification.hist}, - ]} - height={300}/> + xAxis={[ + { + scaleType: "band", + barGapRatio: 0.1, + label: "Exposure", + data: quantification.bin_edges, + }, + ]} + series={[ + { + color: "#8278d9", + type: "bar", + label: "Count", + data: quantification.hist, + }, + ]} + height={300} + /> </Box> - - } + )} </CardContent> <CardActions> - <Button size="small" variant="outlined" color="secondary" disabled={quantified} startIcon={<AddIcon/>} onClick={(e) => {console.log(e)}}>Include</Button> - <Button size="small" color="secondary" startIcon={<FunctionsRoundedIcon/>} - disabled={!((minLoss >= 0) && maxLoss && (minInc >= 0) && maxInc)} - onClick={() => {quantify(minInc, maxInc, minLoss, maxLoss, confInc, confLoss)}}>Quantify</Button> + <Button + size="small" + variant="outlined" + color="secondary" + disabled={quantified} + startIcon={<AddIcon />} + onClick={(e) => { + console.log(e); + }} + > + Include + </Button> + <Button + size="small" + color="secondary" + startIcon={<FunctionsRoundedIcon />} + disabled={!(minLoss >= 0 && maxLoss && minInc >= 0 && maxInc)} + onClick={() => { + quantify(minInc, maxInc, minLoss, maxLoss, confInc, confLoss); + }} + > + Quantify + </Button> </CardActions> </Card> - ) + ); } - 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 ( <> - <Typography sx={{mt: '1em', mb: '1em'}}>Impacts potentially related to the <Typography sx={{display: 'inline'}} color="secondary">{impact}</Typography> of <Typography color="secondary" sx={{ display: 'inline'}}>{threatInModel.targetedAsset.assetDisplayname}</Typography></Typography> - <Typography variant="h4">Tangible</Typography> - <Box sx={{display: 'flex', flexWrap: 'wrap', gap: '1em'}}> - { - ImpactList[impact].tangible.map((impact, i) => { - return ( - <ImpactCard impact={impact} key={i}></ImpactCard> - ) - }) - } - </Box> - <Typography variant="h4">Intangible</Typography> - <Box sx={{display: 'flex', flexWrap: 'wrap', gap: '1em'}}> - { - ImpactList[impact].intangible.map((impact, i) => { - return ( - <ImpactCard impact={impact} key={i}></ImpactCard> - ) - }) - } - </Box> + {impacts.map(function renderImpact(impact) { + return ( + <> + <Typography sx={{ mt: "1em", mb: "1em" }}> + Impacts potentially related to the{" "} + <Typography sx={{ display: "inline" }} color="secondary"> + {impact} + </Typography>{" "} + of{" "} + <Typography color="secondary" sx={{ display: "inline" }}> + {threatInModel.targetedAsset.assetDisplayname} + </Typography> + </Typography> + <Typography variant="h4">Tangible</Typography> + <Box sx={{ display: "flex", flexWrap: "wrap", gap: "1em" }}> + {ImpactList[impact].tangible.map((impact, i) => { + return <ImpactCard impact={impact} key={i}></ImpactCard>; + })} + </Box> + <Typography variant="h4">Intangible</Typography> + <Box sx={{ display: "flex", flexWrap: "wrap", gap: "1em" }}> + {ImpactList[impact].intangible.map((impact, i) => { + return <ImpactCard impact={impact} key={i}></ImpactCard>; + })} + </Box> + </> + ); + })} </> - ) + ); } } + function handleSaveRisk() { + console.log(selectedModel); + console.log("selectedThreat", selectedThreat); + console.log("riskName", riskName); + var newRisk = { + threat: selectedThreat, + riskname: riskName, + model: selectedModel, + }; + const updatedRiskScenarios = [...risks, newRisk]; + setRisks(updatedRiskScenarios); + localStorage.setItem("riskScenarios", JSON.stringify(updatedRiskScenarios)); + } + return ( <> - <Typography level="title-lg" variant='h4'> + <Typography level="title-lg" variant="h4"> Formulate Risk Scenarios </Typography> <Typography variant="subtitle"> Assess Impacts and Communicate Strategic Risk Exposure </Typography> - - <Box sx={{ mt: '1em' }}> + <Box sx={{ mt: "1em" }}> <Accordion> <AccordionSummary expandIcon={<ExpandMoreIcon />} - sx={{ textTransform: 'capitalize' }}> - <AddIcon sx={{ height: '1rem', marginTop: '3px' }} /> + sx={{ textTransform: "capitalize" }} + > + <AddIcon sx={{ height: "1rem", marginTop: "3px" }} /> + <Typography>Existing Risk Scenarios</Typography> + </AccordionSummary> + {risks.map(function renderThreat(risk) { + // console.log(risk); + var threatInModel = loadedTM.find((t) => t.id === risk.threat); + // console.log(threatInModel); + return ( + <AccordionDetails key={risk.riskname}> + <Box + sx={{ + flexDirection: "row", + display: "flex", + alignItems: "center", + }} + > + <Box + sx={{ + flexDirection: "row", + display: "flex", + alignItems: "center", + }} + > + <Typography kx={{ textTransform: "inherit" }}> + Threat: {threatInModel.threat.Threat} + </Typography> + <span style={{ margin: "0 .5em" }}>;</span> + <Typography>Scenario: {risk.riskname}</Typography> + </Box> + </Box> + </AccordionDetails> + ); + })} + </Accordion> + <Accordion> + <AccordionSummary + expandIcon={<ExpandMoreIcon />} + sx={{ textTransform: "capitalize" }} + > + <AddIcon sx={{ height: "1rem", marginTop: "3px" }} /> <Typography>Create new Risk Scenario</Typography> </AccordionSummary> @@ -343,46 +502,50 @@ export default function Controls() { label="Threat" value={selectedThreat} color="secondary" - onChange={(event) => {setSelectedThreat(event.target.value)}}> - { - loadedTM.map(function renderThreats (threat) { - return ( - <MenuItem value={threat.id} key={threat.id}> - {threat.threat.Threat} - <Typography color="secondary" sx={{ml: '.5em', mr: '.5em', display: 'inline'}}>@</Typography> - {threat.targetedAsset.assetDisplayname} - </MenuItem> - ) - }) - } + onChange={(event) => { + setSelectedThreat(event.target.value); + }} + > + {loadedTM.map(function renderThreats(threat) { + return ( + <MenuItem value={threat.id} key={threat.id}> + {threat.threat.Threat} + <Typography + color="secondary" + sx={{ ml: ".5em", mr: ".5em", display: "inline" }} + > + @ + </Typography> + {threat.targetedAsset.assetDisplayname} + </MenuItem> + ); + })} </Select> - <FormHelperText sx={{ mb: '1em' }}>The threat for which you wish to model the residual risk</FormHelperText> + <FormHelperText sx={{ mb: "1em" }}> + The threat for which you wish to model the residual risk + </FormHelperText> </FormControl> - <TextField fullWidth label="Scenario Name" required color="secondary" - variant="outlined" helperText="Name the Strategic Risk" - onChange={(e) => { console.log(e.target.value) }} /> + <TextField + fullWidth + label="Scenario Name" + required + color="secondary" + variant="outlined" + helperText="Name the Strategic Risk" + onChange={(e) => { + console.log(e.target.value); + setRiskName(e.target.value); + }} + /> + <Button variant="contained" onClick={handleSaveRisk} autoFocus> + Save + </Button> <Impacts threat={selectedThreat}></Impacts> </AccordionDetails> </Accordion> - - { risks.map(function renderThreat (threat) { - console.log(threat) - return ( - <Accordion> - <AccordionSummary - expandIcon={<ExpandMoreIcon />} - sx={{ textTransform: 'capitalize' }}> - </AccordionSummary> - - <AccordionDetails> - </AccordionDetails> - </Accordion> - ) - }) - } </Box> </> - ) + ); } const NumberInput = forwardRef(function CustomInput( diff --git a/src/pages/Scenarios.jsx b/src/pages/Scenarios.jsx index ddb21fe..15f7b49 100644 --- a/src/pages/Scenarios.jsx +++ b/src/pages/Scenarios.jsx @@ -126,15 +126,31 @@ function Scenarios({ onModelSelected }) { }; function handleDelete() { - 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) + // deleting threats + var loadedTMs = JSON.parse(localStorage.getItem("threatModels")); + loadedTMs = Object.keys(loadedTMs) + .filter((key) => key !== selectedModel) + .reduce((obj, key) => { + obj[key] = loadedTMs[key]; + return obj; + }, {}); + localStorage.setItem("threatModels", JSON.stringify(loadedTMs)); + // deleting risk scenarios + var updatedRiskScenarios = JSON.parse( + localStorage.getItem("riskScenarios") + ).filter((m) => m.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) { 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"] + } +] From e20bf95d0a8154961ca3cd30bca672e9558a8a1a Mon Sep 17 00:00:00 2001 From: prakashar11 <prakasha.ramachandra@udemy.com> Date: Wed, 22 Jan 2025 17:04:42 +0530 Subject: [PATCH 5/7] further update deleting model --- src/pages/Scenarios.jsx | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/pages/Scenarios.jsx b/src/pages/Scenarios.jsx index 15f7b49..11560c2 100644 --- a/src/pages/Scenarios.jsx +++ b/src/pages/Scenarios.jsx @@ -128,18 +128,20 @@ function Scenarios({ onModelSelected }) { function handleDelete() { // deleting threats var loadedTMs = JSON.parse(localStorage.getItem("threatModels")); - loadedTMs = Object.keys(loadedTMs) - .filter((key) => key !== selectedModel) - .reduce((obj, key) => { - obj[key] = loadedTMs[key]; - return obj; - }, {}); - localStorage.setItem("threatModels", JSON.stringify(loadedTMs)); + 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 - var updatedRiskScenarios = JSON.parse( - localStorage.getItem("riskScenarios") - ).filter((m) => m.model !== selectedModel); + 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 From be9816e9e9c2c57bbbd160bd5f705448c58dca1c Mon Sep 17 00:00:00 2001 From: prakashar11 <prakasha.ramachandra@udemy.com> Date: Wed, 22 Jan 2025 18:23:41 +0530 Subject: [PATCH 6/7] further update risk scenario management --- src/pages/Risks.jsx | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/pages/Risks.jsx b/src/pages/Risks.jsx index cce6fe1..3f3dd74 100644 --- a/src/pages/Risks.jsx +++ b/src/pages/Risks.jsx @@ -30,23 +30,27 @@ export default function Controls() { var [selectedModel] = useState(localStorage.getItem("selectedModel") || ""); var loadedTM = JSON.parse(localStorage.getItem("threatModels"))[selectedModel] || []; - console.log(loadedTM); + // console.log(loadedTM); } catch (e) { console.warn("error parsing threats from storage", e); loadedTM = []; } try { - // var loadedRiskScenarios = - // JSON.parse(localStorage.getItem("riskScenarios"))[selectedModel] || []; - var loadedRiskScenarios = JSON.parse( - localStorage.getItem("riskScenarios") - ).filter((m) => m.model === selectedModel); + // 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 + ); } catch (e) { console.warn("error parsing risks from storage", e); loadedRiskScenarios = []; } - console.log(loadedRiskScenarios); + // console.log(loadedRiskScenarios); const [riskName, setRiskName] = useState(""); //var [threatModel, _] = useState(loadedTM) @@ -422,9 +426,6 @@ export default function Controls() { } function handleSaveRisk() { - console.log(selectedModel); - console.log("selectedThreat", selectedThreat); - console.log("riskName", riskName); var newRisk = { threat: selectedThreat, riskname: riskName, @@ -533,7 +534,6 @@ export default function Controls() { variant="outlined" helperText="Name the Strategic Risk" onChange={(e) => { - console.log(e.target.value); setRiskName(e.target.value); }} /> From f1ff6f1be18bd89339cd46345de1721f0cfa85b2 Mon Sep 17 00:00:00 2001 From: prakashar11 <prakasha.ramachandra@udemy.com> Date: Thu, 23 Jan 2025 14:07:33 +0530 Subject: [PATCH 7/7] fix issue --- src/pages/Risks.jsx | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/src/pages/Risks.jsx b/src/pages/Risks.jsx index 3f3dd74..1b5072a 100644 --- a/src/pages/Risks.jsx +++ b/src/pages/Risks.jsx @@ -36,21 +36,15 @@ export default function Controls() { loadedTM = []; } - try { - // 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 - ); - } catch (e) { - console.warn("error parsing risks from storage", e); - loadedRiskScenarios = []; - } - // console.log(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)