2026-05-28 23:37:31 +00:00
|
|
|
/**
|
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
|
|
|
* Generated by orval v8.9.1 🍺
|
2026-05-28 23:37:31 +00:00
|
|
|
* Do not edit manually.
|
|
|
|
|
* Api
|
|
|
|
|
* API specification
|
|
|
|
|
* OpenAPI spec version: 0.1.0
|
|
|
|
|
*/
|
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
|
|
|
import * as zod from 'zod';
|
|
|
|
|
|
2026-05-28 23:37:31 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns server health status
|
|
|
|
|
* @summary Health check
|
|
|
|
|
*/
|
|
|
|
|
export const HealthCheckResponse = zod.object({
|
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
|
|
|
"status": zod.string()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Aggregated statistics across all scans.
|
|
|
|
|
* @summary Dashboard summary
|
|
|
|
|
*/
|
|
|
|
|
export const GetDashboardResponse = zod.object({
|
|
|
|
|
"totalScans": zod.number(),
|
|
|
|
|
"avgRiskScore": zod.number(),
|
|
|
|
|
"verdictCounts": zod.object({
|
|
|
|
|
"pass": zod.number(),
|
|
|
|
|
"review": zod.number(),
|
|
|
|
|
"block": zod.number()
|
|
|
|
|
}),
|
|
|
|
|
"severityTotals": zod.object({
|
|
|
|
|
"critical": zod.number(),
|
|
|
|
|
"high": zod.number(),
|
|
|
|
|
"medium": zod.number(),
|
|
|
|
|
"low": zod.number(),
|
|
|
|
|
"info": zod.number()
|
|
|
|
|
}),
|
|
|
|
|
"axisTotals": zod.object({
|
|
|
|
|
"security": zod.number(),
|
|
|
|
|
"privacy": zod.number()
|
|
|
|
|
}),
|
|
|
|
|
"recentScans": zod.array(zod.object({
|
|
|
|
|
"id": zod.number(),
|
|
|
|
|
"name": zod.string(),
|
|
|
|
|
"source": zod.enum(['zip', 'file', 'text']),
|
|
|
|
|
"status": zod.enum(['completed', 'failed']),
|
|
|
|
|
"verdict": zod.enum(['pass', 'review', 'block']),
|
|
|
|
|
"riskScore": zod.number(),
|
|
|
|
|
"fileCount": zod.number(),
|
|
|
|
|
"aiUsed": zod.boolean(),
|
|
|
|
|
"aiError": zod.string().nullish(),
|
|
|
|
|
"findingCounts": zod.object({
|
|
|
|
|
"critical": zod.number(),
|
|
|
|
|
"high": zod.number(),
|
|
|
|
|
"medium": zod.number(),
|
|
|
|
|
"low": zod.number(),
|
|
|
|
|
"info": zod.number(),
|
|
|
|
|
"security": zod.number(),
|
|
|
|
|
"privacy": zod.number(),
|
|
|
|
|
"total": zod.number()
|
|
|
|
|
}),
|
|
|
|
|
"createdAt": zod.string()
|
|
|
|
|
})),
|
|
|
|
|
"topRules": zod.array(zod.object({
|
|
|
|
|
"ruleId": zod.string(),
|
|
|
|
|
"title": zod.string(),
|
|
|
|
|
"axis": zod.enum(['security', 'privacy']),
|
|
|
|
|
"count": zod.number()
|
|
|
|
|
}))
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @summary List scan history
|
|
|
|
|
*/
|
|
|
|
|
export const ListScansResponseItem = zod.object({
|
|
|
|
|
"id": zod.number(),
|
|
|
|
|
"name": zod.string(),
|
|
|
|
|
"source": zod.enum(['zip', 'file', 'text']),
|
|
|
|
|
"status": zod.enum(['completed', 'failed']),
|
|
|
|
|
"verdict": zod.enum(['pass', 'review', 'block']),
|
|
|
|
|
"riskScore": zod.number(),
|
|
|
|
|
"fileCount": zod.number(),
|
|
|
|
|
"aiUsed": zod.boolean(),
|
|
|
|
|
"aiError": zod.string().nullish(),
|
|
|
|
|
"findingCounts": zod.object({
|
|
|
|
|
"critical": zod.number(),
|
|
|
|
|
"high": zod.number(),
|
|
|
|
|
"medium": zod.number(),
|
|
|
|
|
"low": zod.number(),
|
|
|
|
|
"info": zod.number(),
|
|
|
|
|
"security": zod.number(),
|
|
|
|
|
"privacy": zod.number(),
|
|
|
|
|
"total": zod.number()
|
|
|
|
|
}),
|
|
|
|
|
"createdAt": zod.string()
|
|
|
|
|
})
|
|
|
|
|
export const ListScansResponse = zod.array(ListScansResponseItem)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 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.
|
|
|
|
|
* @summary Upload a skill and run an audit
|
|
|
|
|
*/
|
|
|
|
|
export const CreateScanBody = zod.object({
|
|
|
|
|
"name": zod.string().nullish().describe('Optional display name for the scan'),
|
|
|
|
|
"source": zod.enum(['zip', 'file', 'text']),
|
|
|
|
|
"useAi": zod.boolean().describe('Whether to also run the configured AI analysis'),
|
|
|
|
|
"contentBase64": zod.string().nullish().describe('Base64 content for source=zip or source=file'),
|
|
|
|
|
"filename": zod.string().nullish().describe('Original filename for source=file or source=zip'),
|
|
|
|
|
"text": zod.string().nullish().describe('Raw skill text for source=text')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @summary Get a scan report with findings
|
|
|
|
|
*/
|
|
|
|
|
export const GetScanParams = zod.object({
|
|
|
|
|
"id": zod.coerce.number()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
export const GetScanResponse = zod.object({
|
|
|
|
|
"id": zod.number(),
|
|
|
|
|
"name": zod.string(),
|
|
|
|
|
"source": zod.enum(['zip', 'file', 'text']),
|
|
|
|
|
"status": zod.enum(['completed', 'failed']),
|
|
|
|
|
"verdict": zod.enum(['pass', 'review', 'block']),
|
|
|
|
|
"riskScore": zod.number(),
|
|
|
|
|
"fileCount": zod.number(),
|
|
|
|
|
"aiUsed": zod.boolean(),
|
|
|
|
|
"aiError": zod.string().nullish(),
|
|
|
|
|
"findingCounts": zod.object({
|
|
|
|
|
"critical": zod.number(),
|
|
|
|
|
"high": zod.number(),
|
|
|
|
|
"medium": zod.number(),
|
|
|
|
|
"low": zod.number(),
|
|
|
|
|
"info": zod.number(),
|
|
|
|
|
"security": zod.number(),
|
|
|
|
|
"privacy": zod.number(),
|
|
|
|
|
"total": zod.number()
|
|
|
|
|
}),
|
|
|
|
|
"createdAt": zod.string()
|
|
|
|
|
}).and(zod.object({
|
|
|
|
|
"files": zod.array(zod.object({
|
|
|
|
|
"path": zod.string(),
|
|
|
|
|
"kind": zod.enum(['instruction', 'script', 'resource']),
|
|
|
|
|
"language": zod.string().nullish(),
|
|
|
|
|
"size": zod.number()
|
|
|
|
|
})),
|
|
|
|
|
"findings": zod.array(zod.object({
|
|
|
|
|
"id": zod.number(),
|
|
|
|
|
"ruleId": zod.string(),
|
|
|
|
|
"axis": zod.enum(['security', 'privacy']),
|
|
|
|
|
"severity": zod.enum(['critical', 'high', 'medium', 'low', 'info']),
|
|
|
|
|
"title": zod.string(),
|
|
|
|
|
"description": zod.string(),
|
|
|
|
|
"remediation": zod.string().nullish(),
|
|
|
|
|
"file": zod.string().nullish(),
|
|
|
|
|
"line": zod.number().nullish(),
|
|
|
|
|
"snippet": zod.string().nullish(),
|
|
|
|
|
"detectedBy": zod.enum(['static', 'ai'])
|
2026-06-10 18:53:17 +00:00
|
|
|
})),
|
|
|
|
|
"checkpoints": zod.array(zod.object({
|
|
|
|
|
"id": zod.string(),
|
|
|
|
|
"label": zod.string(),
|
|
|
|
|
"category": zod.string(),
|
|
|
|
|
"axis": zod.union([zod.literal('security'),zod.literal('privacy'),zod.literal(null)]).nullish(),
|
|
|
|
|
"severity": zod.union([zod.literal('critical'),zod.literal('high'),zod.literal('medium'),zod.literal('low'),zod.literal('info'),zod.literal(null)]).nullish(),
|
|
|
|
|
"status": zod.enum(['pass', 'flagged', 'skipped', 'error']),
|
|
|
|
|
"findingCount": zod.number(),
|
|
|
|
|
"scoreDelta": zod.number(),
|
|
|
|
|
"detectedBy": zod.union([zod.literal('static'),zod.literal('ai'),zod.literal(null)]).nullish()
|
|
|
|
|
}).describe('A single inspection step (Prüfschritt) with its partial assessment (Teilbewertung).'))
|
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
|
|
|
}))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @summary Delete a scan report
|
|
|
|
|
*/
|
|
|
|
|
export const DeleteScanParams = zod.object({
|
|
|
|
|
"id": zod.coerce.number()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @summary List configured AI providers
|
|
|
|
|
*/
|
|
|
|
|
export const ListProvidersResponseItem = zod.object({
|
|
|
|
|
"id": zod.number(),
|
|
|
|
|
"name": zod.string(),
|
|
|
|
|
"apiType": zod.enum(['openai', 'anthropic', 'custom']),
|
|
|
|
|
"baseUrl": zod.string(),
|
|
|
|
|
"model": zod.string(),
|
|
|
|
|
"enabled": zod.boolean(),
|
|
|
|
|
"hasToken": zod.boolean(),
|
|
|
|
|
"tokenPreview": zod.string().describe('Masked preview of the stored token (e.g. \"sk-...abcd\")'),
|
|
|
|
|
"createdAt": zod.string()
|
|
|
|
|
})
|
|
|
|
|
export const ListProvidersResponse = zod.array(ListProvidersResponseItem)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @summary Create an AI provider
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export const CreateProviderBody = zod.object({
|
|
|
|
|
"name": zod.string().min(1),
|
|
|
|
|
"apiType": zod.enum(['openai', 'anthropic', 'custom']),
|
|
|
|
|
"baseUrl": zod.string().min(1),
|
|
|
|
|
"model": zod.string().min(1),
|
|
|
|
|
"apiToken": zod.string().optional(),
|
|
|
|
|
"enabled": zod.boolean().optional()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @summary Update an AI provider
|
|
|
|
|
*/
|
|
|
|
|
export const UpdateProviderParams = zod.object({
|
|
|
|
|
"id": zod.coerce.number()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export const UpdateProviderBody = zod.object({
|
|
|
|
|
"name": zod.string().min(1).optional(),
|
|
|
|
|
"apiType": zod.enum(['openai', 'anthropic', 'custom']).optional(),
|
|
|
|
|
"baseUrl": zod.string().min(1).optional(),
|
|
|
|
|
"model": zod.string().min(1).optional(),
|
|
|
|
|
"apiToken": zod.string().optional().describe('Provide to replace the stored token; omit to keep existing'),
|
|
|
|
|
"enabled": zod.boolean().optional()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
export const UpdateProviderResponse = zod.object({
|
|
|
|
|
"id": zod.number(),
|
|
|
|
|
"name": zod.string(),
|
|
|
|
|
"apiType": zod.enum(['openai', 'anthropic', 'custom']),
|
|
|
|
|
"baseUrl": zod.string(),
|
|
|
|
|
"model": zod.string(),
|
|
|
|
|
"enabled": zod.boolean(),
|
|
|
|
|
"hasToken": zod.boolean(),
|
|
|
|
|
"tokenPreview": zod.string().describe('Masked preview of the stored token (e.g. \"sk-...abcd\")'),
|
|
|
|
|
"createdAt": zod.string()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @summary Delete an AI provider
|
|
|
|
|
*/
|
|
|
|
|
export const DeleteProviderParams = zod.object({
|
|
|
|
|
"id": zod.coerce.number()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @summary Test the connection to an AI provider
|
|
|
|
|
*/
|
|
|
|
|
export const TestProviderParams = zod.object({
|
|
|
|
|
"id": zod.coerce.number()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
export const TestProviderResponse = zod.object({
|
|
|
|
|
"ok": zod.boolean(),
|
|
|
|
|
"message": zod.string().nullish()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
2026-06-10 18:54:56 +00:00
|
|
|
/**
|
|
|
|
|
* @summary Test a provider connection with ad-hoc configuration
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export const TestProviderConnectionBody = zod.object({
|
|
|
|
|
"apiType": zod.enum(['openai', 'anthropic', 'custom']),
|
|
|
|
|
"baseUrl": zod.string().min(1),
|
|
|
|
|
"model": zod.string().min(1),
|
|
|
|
|
"apiToken": zod.string().optional().describe('Token to use for the test; omit or leave empty to fall back to the stored token of providerId'),
|
|
|
|
|
"providerId": zod.number().optional().describe('When apiToken is empty, fall back to this saved provider\'s stored token')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
export const TestProviderConnectionResponse = zod.object({
|
|
|
|
|
"ok": zod.boolean(),
|
|
|
|
|
"message": zod.string().nullish()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
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
|
|
|
/**
|
|
|
|
|
* @summary List configurable AI prompts
|
|
|
|
|
*/
|
|
|
|
|
export const ListPromptsResponseItem = zod.object({
|
|
|
|
|
"id": zod.number(),
|
|
|
|
|
"key": zod.string(),
|
|
|
|
|
"name": zod.string(),
|
|
|
|
|
"content": zod.string(),
|
|
|
|
|
"updatedAt": zod.string()
|
|
|
|
|
})
|
|
|
|
|
export const ListPromptsResponse = zod.array(ListPromptsResponseItem)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @summary Update an AI prompt
|
|
|
|
|
*/
|
|
|
|
|
export const UpdatePromptParams = zod.object({
|
|
|
|
|
"id": zod.coerce.number()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export const UpdatePromptBody = zod.object({
|
|
|
|
|
"name": zod.string().min(1).optional(),
|
|
|
|
|
"content": zod.string().min(1).optional()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
export const UpdatePromptResponse = zod.object({
|
|
|
|
|
"id": zod.number(),
|
|
|
|
|
"key": zod.string(),
|
|
|
|
|
"name": zod.string(),
|
|
|
|
|
"content": zod.string(),
|
|
|
|
|
"updatedAt": zod.string()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @summary List the static rule catalog
|
|
|
|
|
*/
|
|
|
|
|
export const ListRulesResponseItem = zod.object({
|
|
|
|
|
"id": zod.number(),
|
|
|
|
|
"ruleId": zod.string(),
|
|
|
|
|
"axis": zod.enum(['security', 'privacy']),
|
|
|
|
|
"category": zod.string(),
|
|
|
|
|
"title": zod.string(),
|
|
|
|
|
"description": zod.string(),
|
|
|
|
|
"severity": zod.enum(['critical', 'high', 'medium', 'low', 'info']),
|
|
|
|
|
"detectionType": zod.enum(['regex', 'heuristic', 'ai']),
|
|
|
|
|
"enabled": zod.boolean()
|
|
|
|
|
})
|
|
|
|
|
export const ListRulesResponse = zod.array(ListRulesResponseItem)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @summary Update a rule's severity or enabled state
|
|
|
|
|
*/
|
|
|
|
|
export const UpdateRuleParams = zod.object({
|
|
|
|
|
"id": zod.coerce.number()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
export const UpdateRuleBody = zod.object({
|
|
|
|
|
"severity": zod.enum(['critical', 'high', 'medium', 'low', 'info']).optional(),
|
|
|
|
|
"enabled": zod.boolean().optional()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
export const UpdateRuleResponse = zod.object({
|
|
|
|
|
"id": zod.number(),
|
|
|
|
|
"ruleId": zod.string(),
|
|
|
|
|
"axis": zod.enum(['security', 'privacy']),
|
|
|
|
|
"category": zod.string(),
|
|
|
|
|
"title": zod.string(),
|
|
|
|
|
"description": zod.string(),
|
|
|
|
|
"severity": zod.enum(['critical', 'high', 'medium', 'low', 'info']),
|
|
|
|
|
"detectionType": zod.enum(['regex', 'heuristic', 'ai']),
|
|
|
|
|
"enabled": zod.boolean()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|