Skip to content

Commit

Permalink
feat(threat-modeler): add genAI threat modeling action
Browse files Browse the repository at this point in the history
  • Loading branch information
vsofronievk committed Apr 26, 2024
1 parent 2f02738 commit 10db8fa
Show file tree
Hide file tree
Showing 10 changed files with 2,629 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules/
42 changes: 42 additions & 0 deletions security-actions/threat-modeler/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Note: This action creates a baseline threat model for you to get started with. You're strongly encouraged to expand upon the results. Please review all results before approving any automated PRs
name: GenAI Threat Modeling Assistant
description: Create a threat model based on an application description and mermaid.js diagram using the ChatGPT API
author: 'Kong'
inputs:
docs-directory:
description: 'The directory where your markdown docs are located'
required: true
default: ''
application-description:
description: '2-3 sentences describing the target application'
required: true
default: ''
# Example: MY_APP is a Java application, deployed as a microservice, running as a Docker container on AWS EKS. MY_APP exposes an API, which provides consumers the ability to submit JWT tokens, whose claims are evaluated against a rules engine. MY_APP returns a true/false response, which consumers can use to determine whether the request is authorized or not.
dry-run:
description: "Process all the files, build the ChatGPT params, but don't call the ChatGPT API"
required: false
default: 'false'
api-key:
description: "OPENAI API key"
required: true
default: ''

runs:
using: 'composite'
steps:
- name: Checkout Git repository
uses: actions/checkout@v3
with:
repository: Sanboxy/public-shared-actions
path: './threat-modeler'

- name: Call Local Action
uses: ./threat-modeler/threat-modeler
id: threat-modeler
with:
description: ${{ inputs.application-description }}
directory: ../../${{ inputs.docs-directory }}
dry-run: ${{ inputs.dry-run }}
api-key: ${{ inputs.api-key }}


37 changes: 37 additions & 0 deletions security-actions/threat-modeler/example_consumer.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: Threat Model

on:
pull_request: {}
push:
branches:
- master
- main
workflow_dispatch: {}

jobs:
threat-modeler:
name: ChatGPT Threat Model
runs-on: ubuntu-latest

if: (github.actor != 'dependabot[bot]')

steps:
- name: Checkout
uses: actions/checkout@v3

- name: Run Threat-Modeler
id: threat-modeler
uses: Kong/public-shared-actions@91c2c6a12cc7baf69aea166f3ca9b3528ed018c9
with:
docs-directory: 'docs'
application-description: 'application_description'
dry-run: 'false'
api-key: ${{ secrets.OPENAI_API_KEY}}

- name: Download artifacts
uses: actions/download-artifact@v4
with:
name: threat-model
github-token: ${{ secrets.GITHUB_TOKEN }}


51 changes: 51 additions & 0 deletions security-actions/threat-modeler/threat-modeler/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
name: run node
inputs:
directory:
description: 'The directory where your markdown docs are located'
required: true
default: ''
description:
description: '2-3 sentences describing the target application'
required: true
default: ''
dry-run:
description: "Process all the files, build the ChatGPT params, but don't call the ChatGPT API"
required: false
default: 'false'
api-key:
description: "OPENAI API key"
required: true
default: ''

runs:
using: "composite"
steps:
- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version: '20.x'

- name: Install dependencies
working-directory: ./threat-modeler/threat-modeler
shell: bash
run: npm i

- name: Build TS
working-directory: ./threat-modeler/threat-modeler
shell: bash
run: npm run build

- name: Run threat-modeler
id: threat-modeler
working-directory: ./threat-modeler/threat-modeler
shell: bash
env:
OPENAI_API_KEY: ${{ inputs.api-key }}
run: npm run start -- '${{ inputs.description }}' '${{ inputs.directory }}' '${{ inputs.dry-run }}' > threat_model.json

- name: Archive threat modeler results
uses: actions/upload-artifact@v4
with:
name: threat-model
path: threat-modeler/threat-modeler/threat_model.json

148 changes: 148 additions & 0 deletions security-actions/threat-modeler/threat-modeler/dist/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const openai_1 = require("openai");
const fs = require("fs");
const glob_1 = require("glob");
var STRIDE;
(function (STRIDE) {
STRIDE["S"] = "SPOOFING";
STRIDE["T"] = "TAMPERING";
STRIDE["R"] = "REPUDIATION";
STRIDE["I"] = "INFORMATION DISCLOSURE";
STRIDE["D"] = "DENIAL OF SERVICE";
STRIDE["E"] = "ELEVATION OF PRIVILEGE";
})(STRIDE || (STRIDE = {}));
var LIKELIHOOD;
(function (LIKELIHOOD) {
LIKELIHOOD["RARE"] = "RARE";
LIKELIHOOD["UNLIKELY"] = "UNLIKELY";
LIKELIHOOD["PROBABLE"] = "PROBABLE";
LIKELIHOOD["LIKELY"] = "LIKELY";
LIKELIHOOD["ALMOST_CERTAIN"] = "ALMOST CERTAIN";
})(LIKELIHOOD || (LIKELIHOOD = {}));
var IMPACT;
(function (IMPACT) {
IMPACT[IMPACT["INSIGNIFICANT"] = 0] = "INSIGNIFICANT";
IMPACT[IMPACT["MINIMAL"] = 1] = "MINIMAL";
IMPACT[IMPACT["MODERATE"] = 2] = "MODERATE";
IMPACT[IMPACT["SIGNIFICANT"] = 3] = "SIGNIFICANT";
IMPACT[IMPACT["CATASTROPHIC"] = 4] = "CATASTROPHIC";
})(IMPACT || (IMPACT = {}));
var RISK;
(function (RISK) {
RISK[RISK["MINOR"] = 0] = "MINOR";
RISK[RISK["LOW"] = 1] = "LOW";
RISK[RISK["MEDIUM"] = 2] = "MEDIUM";
RISK[RISK["HIGH"] = 3] = "HIGH";
RISK[RISK["EXTREME"] = 4] = "EXTREME";
})(RISK || (RISK = {}));
const main = () => __awaiter(void 0, void 0, void 0, function* () {
const args = process.argv.slice(2);
const dryRun = getDryRun(args[2]);
const systemPrompt = fs.readFileSync('prompt.txt', 'utf-8');
const mermaidMatrix = yield getMermaidMatrix(args[1]);
let rootModel = [];
for (const mermaids of mermaidMatrix) {
for (const mermaid of mermaids) {
const res = yield request(dryRun, systemPrompt, args[0], mermaid);
rootModel = appendNewModel(res, rootModel);
}
}
if (!dryRun) {
console.log(rootModel);
}
});
const getMermaidMatrix = (directory) => __awaiter(void 0, void 0, void 0, function* () {
// Strip slash suffix if present
let dir = directory;
if (directory.endsWith('/')) {
dir = directory.slice(0, -1);
}
const files = yield (0, glob_1.glob)(dir + '/**/*.md');
const mermaidMatrix = [];
// Get all mermaid blocks
files.forEach((file) => {
mermaidMatrix.push(extractMermaid(fs.readFileSync(file, 'utf-8').split(/\r?\n/)));
});
return mermaidMatrix;
});
const extractMermaid = (contents) => {
const mermaids = [];
let mermaid = '';
let inMermaidBlock = false;
contents.forEach((line) => {
if (inMermaidBlock) {
mermaid += '\r\n' + line;
}
if (line === '```mermaid') {
inMermaidBlock = true;
mermaid += line;
}
if (line === '```') {
mermaids.push(mermaid);
mermaid = '';
inMermaidBlock = false;
}
});
return mermaids;
};
const request = (dryRun, systemPrompt, description, mermaid) => __awaiter(void 0, void 0, void 0, function* () {
const params = {
response_format: { type: 'json_object' },
messages: [
{
role: 'user',
content: `${description}
Mermaid:
${mermaid}
Create the threat model and only return a JSON response, using the schema provided.`,
},
{
role: 'system',
content: systemPrompt,
},
],
model: 'gpt-3.5-turbo',
};
if (dryRun === false) {
const openai = new openai_1.default({
apiKey: process.env.OPENAI_SECRET_KEY,
});
const chatCompletion = yield openai.chat.completions.create(params);
const content = chatCompletion.choices[0].message.content;
return JSON.parse(content).threats;
}
console.log('GPT params:', params);
return [];
});
const getDryRun = (flag) => {
if (flag === 'true') {
return true;
}
return false;
};
const appendNewModel = (model, rootModel) => {
if (rootModel.length !== 0) {
const lastId = rootModel[rootModel.length - 1].id;
model.forEach((threat, index) => {
index++;
threat.id = lastId + index;
rootModel.push(threat);
});
return rootModel;
}
rootModel.push(...model);
return rootModel;
};
main();
Loading

0 comments on commit 10db8fa

Please sign in to comment.