German is source of truth; EN/ES fully translated with no German residue. Auto-detects browser language (fallback German), persists choice, language switcher on all pages, localized formats/Clerk/legal. Scans store their language. Backend (T001-T003): language column on scans, openapi+codegen, ruleCatalogI18n, language threaded scans route -> analyzeSkill -> runStaticRule -> AI calls. Route/AI error messages localized via expanded i18n MESSAGES + reqLang(req) (?lang query -> Accept-Language header -> "de"). No German left in routes. Frontend (T004-T005): react-i18next framework, LanguageSwitcher, locale-aware format.ts, Clerk localizations. All page/component strings externalized to de/en/es locale area files across catalog, education, scan form/report/compare, history, dashboard, admin, legal pages. T006 verification + review-fix follow-up (this session): - Applied formatNumber to all visible metrics in scan-report (risk score, severity counts, security/privacy) and scan-compare (risk score, file count, diff counts); PDF/HTML export numbers formatted via Intl.NumberFormat(lng). - Fixed leftover `@workspace/n` import alias in i18n/index.ts -> real package `@workspace/api-client-react` (was failing workspace typecheck). - Verified: full `pnpm run typecheck` green; api-server tests 72/72 pass; curl confirms localized error responses (de/en/es) on scans route. Deviations: AI connection-test prompts left in German intentionally (sent to the model, not user-facing). proposeFollowUpTasks already created #52. Replit-Task-Id: 9f137230-db11-45dc-9276-4e5cbcceff03
397 lines
16 KiB
TypeScript
397 lines
16 KiB
TypeScript
import { RULE_CATALOG } from "./ruleCatalog";
|
|
|
|
export type Lang = "de" | "en" | "es";
|
|
|
|
export const SUPPORTED_LANGS: Lang[] = ["de", "en", "es"];
|
|
|
|
export function normalizeLang(value: string | null | undefined): Lang {
|
|
return value === "en" || value === "es" || value === "de" ? value : "de";
|
|
}
|
|
|
|
export type RuleText = {
|
|
category: string;
|
|
title: string;
|
|
description: string;
|
|
remediation: string;
|
|
};
|
|
|
|
// German is the canonical source kept inline in RULE_CATALOG. This map only
|
|
// carries the English and Spanish overrides; de falls back to the catalog.
|
|
const OVERRIDES: Record<string, { en: RuleText; es: RuleText }> = {
|
|
"SEC-REVERSE-SHELL": {
|
|
en: {
|
|
category: "Code execution",
|
|
title: "Reverse shell / interactive shell",
|
|
description:
|
|
"The skill contains patterns typical of reverse shells or of establishing interactive shell connections to a remote host.",
|
|
remediation:
|
|
"Remove any code that opens shell connections to external hosts. Such patterns are practically never required in legitimate skills.",
|
|
},
|
|
es: {
|
|
category: "Ejecución de código",
|
|
title: "Shell inversa / shell interactiva",
|
|
description:
|
|
"El skill contiene patrones típicos de shells inversas o del establecimiento de conexiones de shell interactivas con un host remoto.",
|
|
remediation:
|
|
"Elimine cualquier código que abra conexiones de shell con hosts externos. Estos patrones prácticamente nunca son necesarios en skills legítimos.",
|
|
},
|
|
},
|
|
"SEC-REMOTE-EXEC": {
|
|
en: {
|
|
category: "Code execution",
|
|
title: "Download-and-execute from the network",
|
|
description:
|
|
"Content is downloaded from the internet and piped straight into an interpreter (e.g. curl | bash). This allows uncontrolled execution of foreign code.",
|
|
remediation:
|
|
"Never pipe code directly into a shell. Review and version downloaded artifacts before running them.",
|
|
},
|
|
es: {
|
|
category: "Ejecución de código",
|
|
title: "Descargar y ejecutar desde la red",
|
|
description:
|
|
"Se descarga contenido de internet y se pasa directamente a un intérprete (p. ej. curl | bash). Esto permite la ejecución incontrolada de código ajeno.",
|
|
remediation:
|
|
"Nunca canalice código directamente a una shell. Revise y versione los artefactos descargados antes de ejecutarlos.",
|
|
},
|
|
},
|
|
"SEC-DESTRUCTIVE": {
|
|
en: {
|
|
category: "Destructive operations",
|
|
title: "Destructive file or disk operation",
|
|
description:
|
|
"Potentially destructive commands were detected (recursive deletion, overwriting disks, formatting, fork bomb).",
|
|
remediation:
|
|
"Restrict delete operations to clearly scoped paths and avoid operations at the root, home or device level.",
|
|
},
|
|
es: {
|
|
category: "Operaciones destructivas",
|
|
title: "Operación destructiva de archivos o disco",
|
|
description:
|
|
"Se detectaron comandos potencialmente destructivos (borrado recursivo, sobrescritura de discos, formateo, fork bomb).",
|
|
remediation:
|
|
"Limite las operaciones de borrado a rutas claramente delimitadas y evite operaciones a nivel de raíz, home o dispositivo.",
|
|
},
|
|
},
|
|
"SEC-PRIV-ESC": {
|
|
en: {
|
|
category: "Privilege escalation",
|
|
title: "Privilege escalation / insecure permissions",
|
|
description:
|
|
"The skill tries to gain elevated privileges or sets insecure file permissions (sudo, chmod 777, setuid, chown root).",
|
|
remediation:
|
|
"Avoid sudo and overly broad permissions. Grant only the minimum privileges that are strictly necessary.",
|
|
},
|
|
es: {
|
|
category: "Escalada de privilegios",
|
|
title: "Escalada de privilegios / permisos inseguros",
|
|
description:
|
|
"El skill intenta obtener privilegios elevados o establece permisos de archivo inseguros (sudo, chmod 777, setuid, chown root).",
|
|
remediation:
|
|
"Evite sudo y permisos demasiado amplios. Conceda únicamente los privilegios mínimos estrictamente necesarios.",
|
|
},
|
|
},
|
|
"SEC-PERSISTENCE": {
|
|
en: {
|
|
category: "Persistence",
|
|
title: "Persistence mechanism",
|
|
description:
|
|
"The skill may set up persistent mechanisms (cron jobs, systemd services, shell profile hooks, SSH keys).",
|
|
remediation:
|
|
"Persistence should only happen with explicit consent. Verify whether creating autostart entries is really necessary.",
|
|
},
|
|
es: {
|
|
category: "Persistencia",
|
|
title: "Mecanismo de persistencia",
|
|
description:
|
|
"El skill podría configurar mecanismos persistentes (cron jobs, servicios systemd, hooks del perfil de shell, claves SSH).",
|
|
remediation:
|
|
"La persistencia solo debe producirse con consentimiento explícito. Compruebe si crear entradas de inicio automático es realmente necesario.",
|
|
},
|
|
},
|
|
"SEC-OBFUSCATION": {
|
|
en: {
|
|
category: "Obfuscation",
|
|
title: "Obfuscated or dynamically executed code",
|
|
description:
|
|
"Indications of obfuscated code or dynamic execution were found (base64 decoding with execution, eval/exec, hex escapes).",
|
|
remediation:
|
|
"Avoid dynamic code execution and obfuscation. Code should be readable in plain text.",
|
|
},
|
|
es: {
|
|
category: "Ofuscación",
|
|
title: "Código ofuscado o ejecutado dinámicamente",
|
|
description:
|
|
"Se encontraron indicios de código ofuscado o ejecución dinámica (decodificación base64 con ejecución, eval/exec, escapes hexadecimales).",
|
|
remediation:
|
|
"Evite la ejecución dinámica de código y la ofuscación. El código debe ser legible en texto plano.",
|
|
},
|
|
},
|
|
"SEC-SUPPLY-CHAIN": {
|
|
en: {
|
|
category: "Supply chain",
|
|
title: "Insecure package or source installation",
|
|
description:
|
|
"Packages are installed from untrusted sources (direct URLs, git+ sources, external apt repositories or keys).",
|
|
remediation:
|
|
"Install packages only from trusted, versioned sources and avoid installing directly from URLs.",
|
|
},
|
|
es: {
|
|
category: "Cadena de suministro",
|
|
title: "Instalación insegura de paquetes o fuentes",
|
|
description:
|
|
"Se instalan paquetes desde fuentes no confiables (URLs directas, fuentes git+, repositorios o claves apt externos).",
|
|
remediation:
|
|
"Instale paquetes solo desde fuentes confiables y versionadas y evite instalar directamente desde URLs.",
|
|
},
|
|
},
|
|
"SEC-NETWORK": {
|
|
en: {
|
|
category: "Network",
|
|
title: "Outbound network access",
|
|
description:
|
|
"The skill establishes outbound network connections. This is not necessarily malicious but should be assessed.",
|
|
remediation:
|
|
"Make sure the contacted endpoints are expected and trustworthy.",
|
|
},
|
|
es: {
|
|
category: "Red",
|
|
title: "Acceso de red saliente",
|
|
description:
|
|
"El skill establece conexiones de red salientes. Esto no es necesariamente malicioso, pero debe evaluarse.",
|
|
remediation:
|
|
"Asegúrese de que los endpoints contactados sean esperados y confiables.",
|
|
},
|
|
},
|
|
"PRIV-SECRET-ACCESS": {
|
|
en: {
|
|
category: "Access to secrets",
|
|
title: "Access to credentials or secrets",
|
|
description:
|
|
"The skill accesses typical secret storage locations (.env, SSH keys, cloud credentials, .netrc, environment variables).",
|
|
remediation:
|
|
"Avoid accessing secret files. If required, document the purpose and scope transparently.",
|
|
},
|
|
es: {
|
|
category: "Acceso a secretos",
|
|
title: "Acceso a credenciales o secretos",
|
|
description:
|
|
"El skill accede a ubicaciones típicas de almacenamiento de secretos (.env, claves SSH, credenciales de la nube, .netrc, variables de entorno).",
|
|
remediation:
|
|
"Evite acceder a archivos de secretos. Si es necesario, documente el propósito y el alcance de forma transparente.",
|
|
},
|
|
},
|
|
"PRIV-EXFILTRATION": {
|
|
en: {
|
|
category: "Data exfiltration",
|
|
title: "Possible data exfiltration",
|
|
description:
|
|
"Data is sent to external endpoints (HTTP POST with payloads, file uploads). Combined with secret access this is highly critical.",
|
|
remediation:
|
|
"Do not send local data to external servers without explicit, documented consent.",
|
|
},
|
|
es: {
|
|
category: "Fuga de datos",
|
|
title: "Posible exfiltración de datos",
|
|
description:
|
|
"Se envían datos a endpoints externos (HTTP POST con datos, subida de archivos). Combinado con acceso a secretos esto es altamente crítico.",
|
|
remediation:
|
|
"No envíe datos locales a servidores externos sin consentimiento explícito y documentado.",
|
|
},
|
|
},
|
|
"PRIV-PROMPT-INJECTION": {
|
|
en: {
|
|
category: "Prompt injection",
|
|
title: "Prompt injection / instruction manipulation",
|
|
description:
|
|
"The instructions contain wording that manipulates agent behaviour (ignore previous instructions, deceive the user, bypass safety guardrails).",
|
|
remediation:
|
|
"Remove manipulative instructions. A skill must not instruct the agent to bypass safety rules or deceive the user.",
|
|
},
|
|
es: {
|
|
category: "Inyección de prompts",
|
|
title: "Inyección de prompts / manipulación de instrucciones",
|
|
description:
|
|
"Las instrucciones contienen formulaciones que manipulan el comportamiento del agente (ignorar instrucciones previas, engañar al usuario, eludir las barreras de seguridad).",
|
|
remediation:
|
|
"Elimine las instrucciones manipuladoras. Un skill no debe indicar al agente que eluda las reglas de seguridad ni que engañe al usuario.",
|
|
},
|
|
},
|
|
"PRIV-HIDDEN-INSTRUCTIONS": {
|
|
en: {
|
|
category: "Hidden content",
|
|
title: "Hidden or invisible instructions",
|
|
description:
|
|
"Invisible Unicode characters or hidden comments were found in the text that could conceal instructions from humans.",
|
|
remediation:
|
|
"Remove invisible control characters and hidden comments. All instructions must be visible to humans.",
|
|
},
|
|
es: {
|
|
category: "Contenido oculto",
|
|
title: "Instrucciones ocultas o invisibles",
|
|
description:
|
|
"Se encontraron caracteres Unicode invisibles o comentarios ocultos en el texto que podrían ocultar instrucciones a las personas.",
|
|
remediation:
|
|
"Elimine los caracteres de control invisibles y los comentarios ocultos. Todas las instrucciones deben ser visibles para las personas.",
|
|
},
|
|
},
|
|
"PRIV-PII": {
|
|
en: {
|
|
category: "Data protection / GDPR",
|
|
title: "Collection of personal data",
|
|
description:
|
|
"The skill refers to collecting or processing personal or sensitive data (passwords, credit cards, ID documents, date of birth).",
|
|
remediation:
|
|
"Only collect personal data with a legal basis and document the purpose, scope and storage in accordance with the GDPR.",
|
|
},
|
|
es: {
|
|
category: "Protección de datos / RGPD",
|
|
title: "Recopilación de datos personales",
|
|
description:
|
|
"El skill hace referencia a la recopilación o el tratamiento de datos personales o sensibles (contraseñas, tarjetas de crédito, documentos de identidad, fecha de nacimiento).",
|
|
remediation:
|
|
"Recopile datos personales únicamente con una base legal y documente el propósito, el alcance y el almacenamiento conforme al RGPD.",
|
|
},
|
|
},
|
|
"PRIV-AGENT-TAMPERING": {
|
|
en: {
|
|
category: "System compromise",
|
|
title: "Tampering with the agent or other skills",
|
|
description:
|
|
"The skill may try to modify or delete the agent, its memory or other skills/configurations.",
|
|
remediation:
|
|
"A skill must not modify the agent, other skills, memory or configuration files.",
|
|
},
|
|
es: {
|
|
category: "Compromiso del sistema",
|
|
title: "Manipulación del agente o de otros skills",
|
|
description:
|
|
"El skill podría intentar modificar o eliminar el agente, su memoria u otros skills/configuraciones.",
|
|
remediation:
|
|
"Un skill no debe modificar el agente, otros skills, la memoria ni los archivos de configuración.",
|
|
},
|
|
},
|
|
"PRIV-OVERREACH": {
|
|
en: {
|
|
category: "Permissions",
|
|
title: "Excessive permission request",
|
|
description:
|
|
"The skill requests very broad or unrestricted permissions.",
|
|
remediation:
|
|
"Request only the minimum necessary permissions (principle of least privilege).",
|
|
},
|
|
es: {
|
|
category: "Permisos",
|
|
title: "Solicitud excesiva de permisos",
|
|
description:
|
|
"El skill solicita permisos muy amplios o sin restricciones.",
|
|
remediation:
|
|
"Solicite únicamente los permisos mínimos necesarios (principio de mínimo privilegio).",
|
|
},
|
|
},
|
|
"AI-PROMPT-INJECTION": {
|
|
en: {
|
|
category: "AI analysis",
|
|
title: "AI: Covert prompt injection",
|
|
description:
|
|
"Semantic AI analysis for covert or subtle attempts to manipulate agent behaviour that static rules do not catch.",
|
|
remediation:
|
|
"Manually review the spots flagged by the AI and remove manipulative content.",
|
|
},
|
|
es: {
|
|
category: "Análisis con IA",
|
|
title: "IA: Inyección de prompts encubierta",
|
|
description:
|
|
"Análisis semántico con IA para detectar intentos encubiertos o sutiles de manipular el comportamiento del agente que las reglas estáticas no captan.",
|
|
remediation:
|
|
"Revise manualmente los puntos marcados por la IA y elimine el contenido manipulador.",
|
|
},
|
|
},
|
|
"AI-MALICIOUS-INTENT": {
|
|
en: {
|
|
category: "AI analysis",
|
|
title: "AI: Malicious intent in the code",
|
|
description:
|
|
"Semantic AI analysis for malicious or hidden functionality in the code that goes beyond pure pattern matching.",
|
|
remediation:
|
|
"Manually review the flagged code sections for malicious intent.",
|
|
},
|
|
es: {
|
|
category: "Análisis con IA",
|
|
title: "IA: Intención maliciosa en el código",
|
|
description:
|
|
"Análisis semántico con IA para detectar funcionalidad maliciosa u oculta en el código que va más allá de la mera coincidencia de patrones.",
|
|
remediation:
|
|
"Revise manualmente las secciones de código marcadas en busca de intención maliciosa.",
|
|
},
|
|
},
|
|
"AI-DATA-PRIVACY": {
|
|
en: {
|
|
category: "AI analysis",
|
|
title: "AI: Data protection risk",
|
|
description:
|
|
"Semantic AI analysis for data protection risks and possible leakage of personal data.",
|
|
remediation:
|
|
"Assess the flagged data protection risks and ensure GDPR compliance.",
|
|
},
|
|
es: {
|
|
category: "Análisis con IA",
|
|
title: "IA: Riesgo de protección de datos",
|
|
description:
|
|
"Análisis semántico con IA para detectar riesgos de protección de datos y posible fuga de datos personales.",
|
|
remediation:
|
|
"Evalúe los riesgos de protección de datos marcados y garantice el cumplimiento del RGPD.",
|
|
},
|
|
},
|
|
};
|
|
|
|
// Built lazily to avoid a circular module-init crash: ruleCatalog imports this
|
|
// file, so RULE_CATALOG is in the temporal dead zone while this module first
|
|
// evaluates. Touching it only inside a function defers access until runtime.
|
|
let deById: Map<string, RuleText> | null = null;
|
|
function getDeById(): Map<string, RuleText> {
|
|
if (!deById) {
|
|
deById = new Map(
|
|
RULE_CATALOG.map((r) => [
|
|
r.ruleId,
|
|
{
|
|
category: r.category,
|
|
title: r.title,
|
|
description: r.description,
|
|
remediation: r.remediation,
|
|
} satisfies RuleText,
|
|
]),
|
|
);
|
|
}
|
|
return deById;
|
|
}
|
|
|
|
/** Localized text for a rule. Falls back to the German catalog if unknown. */
|
|
export function localizeRule(ruleId: string, lang: Lang): RuleText {
|
|
const de = getDeById().get(ruleId) ?? {
|
|
category: "",
|
|
title: ruleId,
|
|
description: "",
|
|
remediation: "",
|
|
};
|
|
if (lang === "de") return de;
|
|
const override = OVERRIDES[ruleId];
|
|
return override ? override[lang] : de;
|
|
}
|
|
|
|
// Generated snippet text used by the hidden-instructions heuristic, localized so
|
|
// findings never leak German into EN/ES reports.
|
|
export const INVISIBLE_CHAR_SNIPPET_DE =
|
|
"Unsichtbares Steuerzeichen in dieser Zeile erkannt.";
|
|
|
|
const INVISIBLE_CHAR_SNIPPET: Record<Lang, string> = {
|
|
de: INVISIBLE_CHAR_SNIPPET_DE,
|
|
en: "Invisible control character detected on this line.",
|
|
es: "Carácter de control invisible detectado en esta línea.",
|
|
};
|
|
|
|
export function localizeSnippet(snippet: string, lang: Lang): string {
|
|
if (snippet === INVISIBLE_CHAR_SNIPPET_DE) {
|
|
return INVISIBLE_CHAR_SNIPPET[lang];
|
|
}
|
|
return snippet;
|
|
}
|