skillguard/lib/api-spec/openapi.yaml
Replit Agent a70b0d580a SkillGuard: complete frontend wiring and harden backend
Original task: build "SkillGuard", a German web app to audit agent skills on
two axes (IT-Sicherheit, Datenschutz) with static rule engine + Replit-independent
AI analysis configured via an admin backend.

This session:
- Fixed frontend TS errors: lucide-react name collisions (Badge from ui, Activity
  from lucide), widened apiType to AiProviderApiType, added queryKey to useGetScan.
- Verified all pages render in German (Dashboard, Prüfen, Bericht, Verlauf, Admin)
  and the full scan flow works end-to-end (malicious sample -> verdict block).

Code-review-driven hardening:
- POST /api/scans now returns the full ScanDetail (files + findings) to match the
  OpenAPI contract, instead of only the summary.
- AI provider error bodies are redacted (token, Bearer, sk- patterns) before being
  returned/persisted, and provider fetches now have a 60s timeout.
- ZIP parsing now enforces limits (max files, total + per-file size) to mitigate
  zip-bomb DoS.

Updated replit.md (project overview, decisions, gotchas) and added a memory note
on lucide-react icon name collisions.
2026-06-08 14:59:17 +00:00

699 lines
16 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}:
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"
/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
- createdAt
properties:
id:
type: integer
name:
type: string
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"
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
ScanFile:
type: object
required: [path, kind, size]
properties:
path:
type: string
kind:
type: string
enum: [instruction, script, resource]
language:
type: ["string", "null"]
size:
type: integer
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]
properties:
files:
type: array
items:
$ref: "#/components/schemas/ScanFile"
findings:
type: array
items:
$ref: "#/components/schemas/Finding"
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"]
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