skillguard/lib/api-spec/openapi.yaml
amertensreplit 2e9a00f182 KI-generierte Skill-Beschreibung im Bericht
Adds an AI-generated, factual German description ("Was macht dieser Skill?")
to scans and shows it in the report.

Changes:
- DB: new nullable `description` column on scansTable (lib/db schema; pushed via drizzle-kit).
- AI: new `generateSkillDescription()` in aiAnalysis.ts — reuses provider selection,
  token redaction, system prompt and JSON extraction; expects {"description": "..."},
  returns null and never throws on failure.
- Engine: scanEngine now generates the description independently of the AI findings
  rules — only a provider+token are required, so it works even when AI findings rules
  are disabled. Description failures do not break the scan. EngineResult gains
  aiDescription. (Provider/token error precedence unchanged for findings.)
- Prompt: new admin-editable "description" prompt (Beschreibungs-Anweisung) seeded via
  onConflictDoNothing, consistent with system/analysis prompts.
- Persist/serialize: description written on scan insert and returned in
  serializeScan (list + detail responses).
- API spec: added nullable `description` to the Scan schema in openapi.yaml; regenerated
  zod + react-query clients via orval codegen.
- Report UI: new "Was macht dieser Skill?" card in the report header (hidden when empty)
  and a matching section in the PDF/print export.

Notes / deviations:
- Old scans are not backfilled (per task scope); their description stays null and the
  section is hidden.
- Description is requested as JSON ({"description": ...}) to stay compatible with the
  existing "JSON only" system prompt.
- Verified: full typecheck passes, both workflows run, new prompt seeded, scans API
  returns description.

Replit-Task-Id: 40c4457b-54d1-4283-a336-478620c3afa8
2026-06-10 21:13:51 +00:00

1061 lines
26 KiB
YAML

openapi: 3.1.0
info:
# Do not change the title, if the title changes, the import paths will be broken
title: Api
version: 0.1.0
description: API specification
servers:
- url: /api
description: Base API path
tags:
- name: health
description: Health operations
- name: scans
description: Skill scans and audit reports
- name: providers
description: Configurable external AI providers
- name: prompts
description: Configurable AI analysis prompts
- name: rules
description: Static rule catalog configuration
- name: dashboard
description: Dashboard summaries
paths:
/healthz:
get:
operationId: healthCheck
tags: [health]
summary: Health check
description: Returns server health status
responses:
"200":
description: Healthy
content:
application/json:
schema:
$ref: "#/components/schemas/HealthStatus"
/dashboard:
get:
operationId: getDashboard
tags: [dashboard]
summary: Dashboard summary
description: Aggregated statistics across all scans.
responses:
"200":
description: Dashboard summary
content:
application/json:
schema:
$ref: "#/components/schemas/DashboardSummary"
/scans:
get:
operationId: listScans
tags: [scans]
summary: List scan history
responses:
"200":
description: List of scans (most recent first)
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Scan"
post:
operationId: createScan
tags: [scans]
summary: Upload a skill and run an audit
description: >-
Accepts a skill as a base64 ZIP archive, a single base64 file, or pasted
text, runs the static rule engine and (optionally) the configured AI
analysis, and returns the completed report.
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/SkillScanInput"
responses:
"201":
description: Completed scan report
content:
application/json:
schema:
$ref: "#/components/schemas/ScanDetail"
"400":
description: Invalid input
content:
application/json:
schema:
$ref: "#/components/schemas/ApiError"
/scans/{id}/compare/{otherId}:
get:
operationId: compareScans
tags: [scans]
summary: Compare two scans on the file level
description: >-
Returns a file-level diff between the current scan (id) and a previously
stored scan (otherId), including line-by-line diffs for modified text
files.
parameters:
- name: id
in: path
required: true
schema:
type: integer
- name: otherId
in: path
required: true
schema:
type: integer
responses:
"200":
description: File-level comparison
content:
application/json:
schema:
$ref: "#/components/schemas/ScanComparison"
"404":
description: Not found
content:
application/json:
schema:
$ref: "#/components/schemas/ApiError"
/scans/{id}/lineage:
get:
operationId: getScanLineage
tags: [scans]
summary: Get the version timeline for a skill family
description: >-
Returns every scan in the same fingerprint lineage as the given scan
(linked by an identical fingerprint or by the comparedScanId chain),
newest first, so the full version history of a skill can be shown on a
timeline without re-scanning.
parameters:
- name: id
in: path
required: true
schema:
type: integer
responses:
"200":
description: Version timeline (most recent first)
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/ScanLineageEntry"
"404":
description: Not found
content:
application/json:
schema:
$ref: "#/components/schemas/ApiError"
/scans/{id}:
get:
operationId: getScan
tags: [scans]
summary: Get a scan report with findings
parameters:
- name: id
in: path
required: true
schema:
type: integer
responses:
"200":
description: Scan report
content:
application/json:
schema:
$ref: "#/components/schemas/ScanDetail"
"404":
description: Not found
content:
application/json:
schema:
$ref: "#/components/schemas/ApiError"
delete:
operationId: deleteScan
tags: [scans]
summary: Delete a scan report
parameters:
- name: id
in: path
required: true
schema:
type: integer
responses:
"204":
description: Deleted
/providers:
get:
operationId: listProviders
tags: [providers]
summary: List configured AI providers
responses:
"200":
description: List of providers (tokens masked)
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/AiProvider"
post:
operationId: createProvider
tags: [providers]
summary: Create an AI provider
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/AiProviderInput"
responses:
"201":
description: Created provider
content:
application/json:
schema:
$ref: "#/components/schemas/AiProvider"
/providers/{id}:
patch:
operationId: updateProvider
tags: [providers]
summary: Update an AI provider
parameters:
- name: id
in: path
required: true
schema:
type: integer
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/AiProviderUpdate"
responses:
"200":
description: Updated provider
content:
application/json:
schema:
$ref: "#/components/schemas/AiProvider"
delete:
operationId: deleteProvider
tags: [providers]
summary: Delete an AI provider
parameters:
- name: id
in: path
required: true
schema:
type: integer
responses:
"204":
description: Deleted
/providers/{id}/test:
post:
operationId: testProvider
tags: [providers]
summary: Test the connection to an AI provider
parameters:
- name: id
in: path
required: true
schema:
type: integer
responses:
"200":
description: Test result
content:
application/json:
schema:
$ref: "#/components/schemas/ProviderTestResult"
/providers/test-connection:
post:
operationId: testProviderConnection
tags: [providers]
summary: Test a provider connection with ad-hoc configuration
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/ProviderTestConnectionInput"
responses:
"200":
description: Test result
content:
application/json:
schema:
$ref: "#/components/schemas/ProviderTestResult"
/providers/list-models:
post:
operationId: listProviderModels
tags: [providers]
summary: List the available models for a provider configuration
description: >-
Queries the provider's models endpoint with the supplied ad-hoc
configuration (or the stored token of providerId when the token is
omitted) and returns the discovered model IDs.
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/ProviderListModelsInput"
responses:
"200":
description: Discovered models
content:
application/json:
schema:
$ref: "#/components/schemas/ProviderModelsResult"
/prompts:
get:
operationId: listPrompts
tags: [prompts]
summary: List configurable AI prompts
responses:
"200":
description: List of prompts
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Prompt"
/prompts/{id}:
patch:
operationId: updatePrompt
tags: [prompts]
summary: Update an AI prompt
parameters:
- name: id
in: path
required: true
schema:
type: integer
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/PromptUpdate"
responses:
"200":
description: Updated prompt
content:
application/json:
schema:
$ref: "#/components/schemas/Prompt"
/rules:
get:
operationId: listRules
tags: [rules]
summary: List the static rule catalog
responses:
"200":
description: List of rules
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Rule"
/rules/{id}:
patch:
operationId: updateRule
tags: [rules]
summary: Update a rule's severity or enabled state
parameters:
- name: id
in: path
required: true
schema:
type: integer
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/RuleUpdate"
responses:
"200":
description: Updated rule
content:
application/json:
schema:
$ref: "#/components/schemas/Rule"
components:
schemas:
HealthStatus:
type: object
properties:
status:
type: string
required:
- status
ApiError:
type: object
required: [error]
properties:
error:
type: string
SkillScanInput:
type: object
required: [source, useAi]
properties:
name:
type: ["string", "null"]
description: Optional display name for the scan
source:
type: string
enum: [zip, file, text]
useAi:
type: boolean
description: Whether to also run the configured AI analysis
contentBase64:
type: ["string", "null"]
description: Base64 content for source=zip or source=file
filename:
type: ["string", "null"]
description: Original filename for source=file or source=zip
text:
type: ["string", "null"]
description: Raw skill text for source=text
Scan:
type: object
required:
- id
- name
- source
- status
- verdict
- riskScore
- fileCount
- aiUsed
- findingCounts
- fingerprint
- relation
- similarity
- comparedScanId
- createdAt
properties:
id:
type: integer
name:
type: string
description:
type: ["string", "null"]
description: AI-generated summary of the skill's purpose (null when no AI description is available)
source:
type: string
enum: [zip, file, text]
status:
type: string
enum: [completed, failed]
verdict:
type: string
enum: [pass, review, block]
riskScore:
type: integer
fileCount:
type: integer
aiUsed:
type: boolean
aiError:
type: ["string", "null"]
findingCounts:
$ref: "#/components/schemas/FindingCounts"
fingerprint:
type: string
description: Deterministic hash over all files (path + per-file hash)
relation:
type: ["string", "null"]
enum: [new, identical, modified, null]
description: Relation to previously stored skills
similarity:
type: ["integer", "null"]
description: Content-aware similarity (0-100) to the compared skill (identical files count fully, changed text files use line-level similarity)
comparedScanId:
type: ["integer", "null"]
description: The scan this one was compared against, if any
createdAt:
type: string
FindingCounts:
type: object
required: [critical, high, medium, low, info, security, privacy, total]
properties:
critical:
type: integer
high:
type: integer
medium:
type: integer
low:
type: integer
info:
type: integer
security:
type: integer
privacy:
type: integer
total:
type: integer
ScanCheckpoint:
type: object
description: >-
A single inspection step (Prüfschritt) with its partial assessment
(Teilbewertung).
required: [id, label, category, status, findingCount, scoreDelta]
properties:
id:
type: string
label:
type: string
category:
type: string
axis:
type: ["string", "null"]
enum: [security, privacy, null]
severity:
type: ["string", "null"]
enum: [critical, high, medium, low, info, null]
status:
type: string
enum: [pass, flagged, skipped, error]
findingCount:
type: integer
scoreDelta:
type: integer
detectedBy:
type: ["string", "null"]
enum: [static, ai, null]
ScanFile:
type: object
required: [path, kind, size, hash, hasContent]
properties:
path:
type: string
kind:
type: string
enum: [instruction, script, resource]
language:
type: ["string", "null"]
size:
type: integer
hash:
type: string
description: SHA-256 hash of the file content
hasContent:
type: boolean
description: Whether the text content was stored (false for binary files)
Finding:
type: object
required:
- id
- ruleId
- axis
- severity
- title
- description
- detectedBy
properties:
id:
type: integer
ruleId:
type: string
axis:
type: string
enum: [security, privacy]
severity:
type: string
enum: [critical, high, medium, low, info]
title:
type: string
description:
type: string
remediation:
type: ["string", "null"]
file:
type: ["string", "null"]
line:
type: ["integer", "null"]
snippet:
type: ["string", "null"]
detectedBy:
type: string
enum: [static, ai]
ScanDetail:
allOf:
- $ref: "#/components/schemas/Scan"
- type: object
required: [files, findings, checkpoints, checkCount, comparedScan]
properties:
files:
type: array
items:
$ref: "#/components/schemas/ScanFile"
findings:
type: array
items:
$ref: "#/components/schemas/Finding"
checkpoints:
type: array
items:
$ref: "#/components/schemas/ScanCheckpoint"
checkCount:
type: integer
description: How often a skill with this exact fingerprint was scanned
comparedScan:
oneOf:
- $ref: "#/components/schemas/ComparedScan"
- type: "null"
ComparedScan:
type: object
required: [id, name, verdict, riskScore, createdAt]
properties:
id:
type: integer
name:
type: string
verdict:
type: string
enum: [pass, review, block]
riskScore:
type: integer
createdAt:
type: string
ScanLineageEntry:
type: object
required:
- id
- name
- verdict
- riskScore
- relation
- similarity
- comparedScanId
- fingerprint
- createdAt
properties:
id:
type: integer
name:
type: string
verdict:
type: string
enum: [pass, review, block]
riskScore:
type: integer
relation:
type: ["string", "null"]
enum: [new, identical, modified, null]
description: Relation of this version to the one it was compared against
similarity:
type: ["integer", "null"]
description: Content-aware similarity (0-100) to its compared version
comparedScanId:
type: ["integer", "null"]
description: The prior version this scan was compared against, if any
fingerprint:
type: string
createdAt:
type: string
ScanComparisonSide:
type: object
required: [id, name, verdict, riskScore, fileCount, fingerprint, createdAt]
properties:
id:
type: integer
name:
type: string
verdict:
type: string
enum: [pass, review, block]
riskScore:
type: integer
fileCount:
type: integer
fingerprint:
type: string
createdAt:
type: string
DiffLine:
type: object
required: [type, text, previousLine, currentLine]
properties:
type:
type: string
enum: [context, add, remove]
text:
type: string
previousLine:
type: ["integer", "null"]
currentLine:
type: ["integer", "null"]
ScanFileDiff:
type: object
required:
- path
- status
- previousHash
- currentHash
- previousSize
- currentSize
- previousHasContent
- currentHasContent
- lineDiff
properties:
path:
type: string
status:
type: string
enum: [unchanged, modified, added, removed]
previousHash:
type: ["string", "null"]
currentHash:
type: ["string", "null"]
previousSize:
type: ["integer", "null"]
currentSize:
type: ["integer", "null"]
previousHasContent:
type: ["boolean", "null"]
currentHasContent:
type: ["boolean", "null"]
lineDiff:
oneOf:
- type: array
items:
$ref: "#/components/schemas/DiffLine"
- type: "null"
ScanComparison:
type: object
required: [current, previous, files]
properties:
current:
$ref: "#/components/schemas/ScanComparisonSide"
previous:
$ref: "#/components/schemas/ScanComparisonSide"
files:
type: array
items:
$ref: "#/components/schemas/ScanFileDiff"
AiProvider:
type: object
required:
- id
- name
- apiType
- baseUrl
- model
- enabled
- hasToken
- tokenPreview
- createdAt
properties:
id:
type: integer
name:
type: string
apiType:
type: string
enum: [openai, anthropic, custom]
baseUrl:
type: string
model:
type: string
enabled:
type: boolean
hasToken:
type: boolean
tokenPreview:
type: string
description: Masked preview of the stored token (e.g. "sk-...abcd")
createdAt:
type: string
AiProviderInput:
type: object
required: [name, apiType, baseUrl, model]
properties:
name:
type: string
minLength: 1
apiType:
type: string
enum: [openai, anthropic, custom]
baseUrl:
type: string
minLength: 1
model:
type: string
minLength: 1
apiToken:
type: string
enabled:
type: boolean
AiProviderUpdate:
type: object
properties:
name:
type: string
minLength: 1
apiType:
type: string
enum: [openai, anthropic, custom]
baseUrl:
type: string
minLength: 1
model:
type: string
minLength: 1
apiToken:
type: string
description: Provide to replace the stored token; omit to keep existing
enabled:
type: boolean
ProviderTestResult:
type: object
required: [ok]
properties:
ok:
type: boolean
message:
type: ["string", "null"]
ProviderTestConnectionInput:
type: object
required: [apiType, baseUrl]
properties:
apiType:
type: string
enum: [openai, anthropic, custom]
baseUrl:
type: string
minLength: 1
model:
type: string
description: Optional model to exercise with a full request; when omitted the test verifies credentials via the models endpoint instead
apiToken:
type: string
description: Token to use for the test; omit or leave empty to fall back to the stored token of providerId
providerId:
type: integer
description: When apiToken is empty, fall back to this saved provider's stored token
ProviderListModelsInput:
type: object
required: [apiType, baseUrl]
properties:
apiType:
type: string
enum: [openai, anthropic, custom]
baseUrl:
type: string
minLength: 1
apiToken:
type: string
description: Token to use for the request; omit or leave empty to fall back to the stored token of providerId
providerId:
type: integer
description: When apiToken is empty, fall back to this saved provider's stored token
ProviderModelsResult:
type: object
required: [ok, models]
properties:
ok:
type: boolean
models:
type: array
items:
type: string
message:
type: ["string", "null"]
Prompt:
type: object
required: [id, key, name, content, updatedAt]
properties:
id:
type: integer
key:
type: string
name:
type: string
content:
type: string
updatedAt:
type: string
PromptUpdate:
type: object
properties:
name:
type: string
minLength: 1
content:
type: string
minLength: 1
Rule:
type: object
required:
- id
- ruleId
- axis
- category
- title
- description
- severity
- detectionType
- enabled
properties:
id:
type: integer
ruleId:
type: string
axis:
type: string
enum: [security, privacy]
category:
type: string
title:
type: string
description:
type: string
severity:
type: string
enum: [critical, high, medium, low, info]
detectionType:
type: string
enum: [regex, heuristic, ai]
enabled:
type: boolean
RuleUpdate:
type: object
properties:
severity:
type: string
enum: [critical, high, medium, low, info]
enabled:
type: boolean
DashboardSummary:
type: object
required:
- totalScans
- avgRiskScore
- verdictCounts
- severityTotals
- axisTotals
- recentScans
- topRules
properties:
totalScans:
type: integer
avgRiskScore:
type: integer
verdictCounts:
$ref: "#/components/schemas/VerdictCounts"
severityTotals:
$ref: "#/components/schemas/SeverityTotals"
axisTotals:
$ref: "#/components/schemas/AxisTotals"
recentScans:
type: array
items:
$ref: "#/components/schemas/Scan"
topRules:
type: array
items:
$ref: "#/components/schemas/RuleStat"
VerdictCounts:
type: object
required: [pass, review, block]
properties:
pass:
type: integer
review:
type: integer
block:
type: integer
SeverityTotals:
type: object
required: [critical, high, medium, low, info]
properties:
critical:
type: integer
high:
type: integer
medium:
type: integer
low:
type: integer
info:
type: integer
AxisTotals:
type: object
required: [security, privacy]
properties:
security:
type: integer
privacy:
type: integer
RuleStat:
type: object
required: [ruleId, title, axis, count]
properties:
ruleId:
type: string
title:
type: string
axis:
type: string
enum: [security, privacy]
count:
type: integer