Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion docs/serve_config.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,8 @@ engine:
- "drawio"
- "threat-dragon"
- "image"
- "ir"
- "description"
autodetect: true
report:
default_format: "markdown" # markdown | json | html | both
Expand All @@ -164,6 +166,9 @@ Notes:
- `allowed_inputs` supports YAML arrays or comma-separated strings.
- `report.default_language` mirrors the CLI `--lang` option.
- `model.params` is passed directly to the provider adapter.
- `description` accepts natural-language system text and generates a DFD before
threat analysis. Request `report_formats: ["dfd"]` to receive the generated
DFD sidecar JSON.

### 3.4.1 Analyze Request RAG Options
RAG is controlled per request (not in YAML config) via `/v1/analyze` JSON body or multipart `options` JSON.
Expand Down Expand Up @@ -293,7 +298,7 @@ queue:
job_ttl_seconds: 900

engine:
allowed_inputs: "mermaid,drawio,threat-dragon,image"
allowed_inputs: "mermaid,drawio,threat-dragon,image,ir,description"
autodetect: true
report:
default_format: "markdown"
Expand Down
7 changes: 5 additions & 2 deletions examples/demo-app/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
This demo app provides a simple, modern UI for Threat Thinker that keeps backend API keys server-side. It runs a demo proxy (FastAPI), the Threat Thinker `serve` API, a worker, and Redis via Docker Compose.

## What it does
- Browser UI for Mermaid, draw.io XML, or Threat Dragon JSON (text-only).
- Proxy adds the backend API key and forces reports to `markdown` + `html`.
- Browser UI for Mermaid, draw.io XML, Threat Dragon JSON, Graph IR JSON, or natural-language system descriptions.
- Optional Business Context text is passed through the proxy to the backend threat prompt.
- Proxy adds the backend API key and requests `markdown` + `html` reports.
- Markdown is shown on-screen with a sanitized HTML preview.
- HTML report can be downloaded as a file.

Expand Down Expand Up @@ -49,6 +50,8 @@ This demo app provides a simple, modern UI for Threat Thinker that keeps backend
## Usage notes
- Only the proxy is exposed on port 8081. The backend server/worker/redis are internal to the compose network.
- The UI fetches Markdown/HTML from the proxy and sanitizes HTML previews with DOMPurify.
- Select `System Description` when you do not have a diagram yet. The backend generates a DFD first, then runs threat analysis.
- Use `Business context` for required business rules, sensitive workflows, compliance scope, or assumptions that should influence threat inference.
- The UI loads `marked` and `dompurify` from a CDN. If you need an air-gapped demo, vendor these files and update `index.html`.

## Troubleshooting
Expand Down
17 changes: 15 additions & 2 deletions examples/demo-app/proxy/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class AnalyzeOptions(BaseModel):
class AnalyzeRequest(BaseModel):
input: AnalyzeInput
options: AnalyzeOptions
business_context: str = ""


app = FastAPI(title="Threat Thinker Demo Proxy", docs_url=None, redoc_url=None)
Expand All @@ -47,6 +48,7 @@ class AnalyzeRequest(BaseModel):
"drawio": "drawio",
"threat_dragon": "threat-dragon",
"ir": "ir",
"description": "description",
}

ALLOWED_LANGUAGES = {"en", "ja"}
Expand Down Expand Up @@ -105,14 +107,18 @@ async def analyze(payload: AnalyzeRequest) -> JSONResponse:
raise HTTPException(status_code=500, detail="Backend API key is not set.")

if payload.input.type not in ALLOWED_INPUT_TYPES:
raise HTTPException(status_code=400, detail="Unsupported diagram type.")
raise HTTPException(status_code=400, detail="Unsupported input type.")

if payload.options.language not in ALLOWED_LANGUAGES:
raise HTTPException(status_code=400, detail="Unsupported language.")

content = payload.input.content or ""
if len(content) > MAX_INPUT_CHARS:
raise HTTPException(status_code=413, detail="Diagram input is too large.")
raise HTTPException(status_code=413, detail="Input is too large.")

business_context = (payload.business_context or "").strip()
if len(business_context) > MAX_INPUT_CHARS:
raise HTTPException(status_code=413, detail="Business context is too large.")

backend_payload = {
"input": {
Expand All @@ -123,6 +129,13 @@ async def analyze(payload: AnalyzeRequest) -> JSONResponse:
"language": payload.options.language,
"topn": 5,
}
if business_context:
backend_payload["contexts"] = [
{
"filename": "business-context.txt",
"content": business_context,
}
]

response = _backend_request(
"post",
Expand Down
122 changes: 99 additions & 23 deletions examples/demo-app/proxy/static/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,15 @@
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap"
href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500&family=Noto+Sans+JP:wght@400;500;600;700&display=swap"
rel="stylesheet"
/>
<style>
:root {
color-scheme: light;
--font-sans: "Noto Sans JP", "Hiragino Sans", "Yu Gothic", "Meiryo", system-ui, sans-serif;
--font-code: "JetBrains Mono", "Noto Sans JP", "Hiragino Sans", "Yu Gothic", ui-monospace, SFMono-Regular,
Menlo, Monaco, Consolas, monospace;
--bg: #f7f3ed;
--bg-alt: #efe8de;
--ink: #151515;
Expand All @@ -31,7 +34,7 @@

body {
margin: 0;
font-family: "Space Grotesk", system-ui, sans-serif;
font-family: var(--font-sans);
background: radial-gradient(circle at top, #fff6e6 0%, var(--bg) 55%, #f0e4d2 100%);
color: var(--ink);
}
Expand Down Expand Up @@ -79,6 +82,7 @@
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 22px;
align-items: start;
min-width: 0;
}

.card {
Expand All @@ -87,6 +91,7 @@
border: 1px solid var(--border);
box-shadow: var(--shadow);
padding: 22px;
min-width: 0;
}

.card h2 {
Expand All @@ -110,20 +115,23 @@
border: 1px solid var(--border);
padding: 10px 12px;
font-size: 14px;
font-family: "Space Grotesk", system-ui, sans-serif;
font-family: var(--font-sans);
background: #fffdf9;
color: var(--ink);
}

textarea {
min-height: 220px;
resize: vertical;
font-family: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono",
"Courier New", monospace;
font-family: var(--font-code);
font-size: 12.5px;
line-height: 1.5;
}

textarea.compact {
min-height: 120px;
}

.controls {
display: grid;
gap: 12px;
Expand All @@ -133,6 +141,7 @@
display: grid;
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
gap: 12px;
min-width: 0;
}

button {
Expand Down Expand Up @@ -178,15 +187,24 @@
align-items: center;
justify-content: space-between;
gap: 8px;
min-width: 0;
}

.status strong {
color: var(--ink);
}

#jobId {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}

.output {
display: grid;
gap: 12px;
min-width: 0;
}

pre {
Expand All @@ -196,10 +214,13 @@
background: #111111;
color: #f4f0e8;
max-height: 320px;
max-width: 100%;
min-width: 0;
overflow: auto;
font-family: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono",
"Courier New", monospace;
overscroll-behavior: contain;
font-family: var(--font-code);
font-size: 12px;
line-height: 1.65;
}

.preview {
Expand Down Expand Up @@ -238,23 +259,25 @@
<div class="shell">
<header>
<div class="eyebrow">Threat Thinker Demo</div>
<h1>Turn diagrams into security insights.</h1>
<h1>Turn system context into security insights.</h1>
<p class="subcopy">
Paste Mermaid, draw.io XML, or Threat Dragon JSON. Get a detailed threat analysis report in Markdown and HTML.
Start with a system description, diagram, or IR JSON.<br>Add business context to sharpen the analysis.
</p>
</header>

<section class="grid">
<div class="card">
<h2>Diagram Input</h2>
<h2>Analysis Input</h2>
<div class="controls">
<div class="row">
<div>
<label for="diagramType">Diagram type</label>
<label for="diagramType">Input type</label>
<select id="diagramType">
<option value="description">System Description</option>
<option value="mermaid">Mermaid</option>
<option value="drawio">draw.io</option>
<option value="threat_dragon">Threat Dragon</option>
<option value="ir">IR JSON</option>
</select>
</div>
<div>
Expand All @@ -266,9 +289,17 @@ <h2>Diagram Input</h2>
</div>
</div>
<div>
<label for="diagramContent">Diagram content</label>
<label for="diagramContent" id="inputLabel">Diagram content</label>
<textarea id="diagramContent" placeholder="Paste your diagram text here..."></textarea>
</div>
<div>
<label for="businessContext">Business context</label>
<textarea
class="compact"
id="businessContext"
placeholder="Optional: business rules, compliance scope, sensitive workflows, assumptions..."
></textarea>
</div>
<button class="primary" id="runButton">Run analysis</button>
<div class="status">
<span>Status: <strong id="statusText">Idle</strong></span>
Expand Down Expand Up @@ -304,12 +335,47 @@ <h2>HTML Preview</h2>
api --> app[App Service]
app --> db[(Customer DB)]`;

const DEFAULT_DESCRIPTION = `Customers use a web app to manage orders.
The frontend calls an API hosted in AWS.
The API stores customer PII and order history in Postgres.
The system sends transactional email through a third-party provider.`;

const DEFAULT_CONTENT = {
mermaid: DEFAULT_MERMAID,
description: DEFAULT_DESCRIPTION,
};

const INPUT_METADATA = {
mermaid: {
label: "Diagram content",
placeholder: "Paste Mermaid diagram text here...",
},
drawio: {
label: "draw.io XML",
placeholder: "Paste draw.io XML here...",
},
threat_dragon: {
label: "Threat Dragon JSON",
placeholder: "Paste Threat Dragon JSON here...",
},
ir: {
label: "IR JSON",
placeholder: "Paste Threat Thinker Graph IR JSON here...",
},
description: {
label: "System description",
placeholder: "Describe users, services, data stores, trust boundaries, and third-party integrations...",
},
};

const runButton = document.getElementById("runButton");
const previewButton = document.getElementById("previewButton");
const downloadButton = document.getElementById("downloadButton");
const diagramType = document.getElementById("diagramType");
const language = document.getElementById("language");
const diagramContent = document.getElementById("diagramContent");
const inputLabel = document.getElementById("inputLabel");
const businessContext = document.getElementById("businessContext");
const statusText = document.getElementById("statusText");
const jobIdEl = document.getElementById("jobId");
const markdownOutput = document.getElementById("markdownOutput");
Expand All @@ -318,17 +384,25 @@ <h2>HTML Preview</h2>

let lastResult = null;

function applyMermaidDefault() {
if (diagramType.value !== "mermaid") {
return;
}
if (!diagramContent.value.trim()) {
diagramContent.value = DEFAULT_MERMAID;
function applyInputState(previousType = null) {
const metadata = INPUT_METADATA[diagramType.value] || INPUT_METADATA.mermaid;
inputLabel.textContent = metadata.label;
diagramContent.placeholder = metadata.placeholder;
const previousDefault = previousType ? DEFAULT_CONTENT[previousType] : "";
const hasOnlyPreviousDefault =
previousDefault && diagramContent.value.trim() === previousDefault.trim();
if (!diagramContent.value.trim() || hasOnlyPreviousDefault) {
diagramContent.value = DEFAULT_CONTENT[diagramType.value] || "";
}
}

applyMermaidDefault();
diagramType.addEventListener("change", applyMermaidDefault);
applyInputState();
let currentInputType = diagramType.value;
diagramType.addEventListener("change", () => {
const previousType = currentInputType;
currentInputType = diagramType.value;
applyInputState(previousType);
});

function setStatus(text, jobId = "") {
statusText.textContent = text;
Expand Down Expand Up @@ -375,9 +449,10 @@ <h2>HTML Preview</h2>
runButton.addEventListener("click", async () => {
const content = diagramContent.value.trim();
if (!content) {
showToast("Please paste diagram content first.");
showToast("Please provide input content first.");
return;
}
const businessContextText = businessContext.value.trim();

runButton.disabled = true;
previewButton.disabled = true;
Expand All @@ -393,6 +468,7 @@ <h2>HTML Preview</h2>
body: JSON.stringify({
input: { type: diagramType.value, content },
options: { language: language.value },
business_context: businessContextText,
}),
});

Expand All @@ -409,8 +485,8 @@ <h2>HTML Preview</h2>
const resultData = await fetchResult(jobId);
lastResult = resultData;
markdownOutput.textContent = resultData.reports.markdown || "";
previewButton.disabled = false;
downloadButton.disabled = false;
previewButton.disabled = !resultData.reports.markdown;
downloadButton.disabled = !resultData.reports.html;
setStatus("succeeded", jobId);
} catch (error) {
setStatus("error");
Expand Down
7 changes: 3 additions & 4 deletions examples/demo-app/serve.example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -48,16 +48,15 @@ engine:
- "drawio"
- "threat-dragon"
- "image"
- "ir"
- "description"
autodetect: true
report:
default_format: "markdown"
default_language: "ja"
model:
provider: "openai"
name: "gpt-4.1-nano"
params:
temperature: 0.2
max_output_tokens: 1200
name: "gpt-4.1"

observability:
log_level: "info"
Expand Down
Loading
Loading