El Desafío de Seguridad DevSecOps
Tu canal de despliegue probablemente está filtrando problemas de seguridad en este momento. Cada commit, cada actualización de Helm chart, cada cambio de infraestructura introduce vulnerabilidades potenciales que se escapan de las revisiones de seguridad tradicionales. Como ingeniero DevSecOps, necesitas visibilidad en toda tu postura de seguridad en la nube, no solo en el despliegue, sino a lo largo de todo tu ciclo de vida de desarrollo.
Reticulum aborda este desafío de frente. No es solo otro escáner de seguridad, es una plataforma integral de inteligencia de seguridad DevSecOps escrita en lenguaje D y diseñada para integrarse perfectamente en tus canales CI/CD y proporcionar validación continua de seguridad para tu infraestructura nativa de la nube.
Instalación y Configuración
Reticulum está diseñado para una integración perfecta en tus flujos de trabajo DevSecOps existentes. Aquí te mostramos cómo empezar:
Instalación con Docker (Recomendada)
Reticulum soporta compilaciones multi-arquitectura nativas (Apple Silicon/ARM64 e Intel/AMD64) 1 .
# Clonar repositorio
git clone https://github.com/plexicus/reticulum.git
cd reticulum
# Construir imagen
docker build -t reticulum .
Ejecutar con Docker
# Montar directorio actual a /data y analizar
docker run --rm -v $(pwd):/data reticulum \
-p /data/tests/monorepo-06 \
-s /data/tests/monorepo-06/trivy.sarif
Construir desde Código Fuente
Requisitos:
- Compilador D Language (LDC2) 2
- DUB (Package Manager)
- Herramientas de construcción estándar
git clone https://github.com/plexicus/reticulum.git
cd reticulum
dub build --compiler=ldc2
Inicio Rápido de DevSecOps
Comienza a escanear tu infraestructura inmediatamente:
# Análisis completo con SARIF
./reticulum -p tests/monorepo-06 -s tests/monorepo-06/trivy.sarif
# Solo análisis de exposición (sin SARIF)
./reticulum -p tests/monorepo-06 --scan-only
# Generar reporte JSON
./reticulum -p ./src -s results.sarif -o report.json
Integración en Pipeline CI/CD
Añade Reticulum a tu pipeline para escaneo de seguridad automatizado:
# Ejemplo de GitHub Actions
name: Escaneo de Seguridad
on: [push, pull_request]
jobs:
security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Construir Reticulum
run: |
git clone https://github.com/plexicus/reticulum.git
cd reticulum
docker build -t reticulum .
- name: Ejecutar Escaneo de Seguridad
run: |
docker run --rm -v $(pwd):/data reticulum \
-p /data \
-s /data/trivy.sarif \
-o /data/security-report.json
- name: Subir Informe de Seguridad
uses: actions/upload-artifact@v3
with:
name: security-report
path: security-report.json
Arquitectura del Sistema
Reticulum está construido con un enfoque modular en lenguaje D, diseñado para analizar tu infraestructura sistemáticamente a través de un pipeline de tres fases 3 :
- Fase 1: Descubrimiento de Servicios - Mapeo de servicios y gráficos Helm
- Fase 2: Análisis de Exposición - Análisis de configuraciones de despliegue de Kubernetes
- Fase 3: Puntuación de Vulnerabilidades - Ingestión de SARIF y aplicación de puntuación contextual
Componentes Principales
app.d- Punto de entrada CLI y orquestador 4model.d- Definiciones de datos centrales (Service, Chart, Finding, RiskProfile) 5analyzer.d- Lógica de análisis de exposición 6ingestor.d- Procesamiento de SARIF y puntuación contextual 7rules/engine.d- Motor de reglas para análisis contextual
Patrones de Exposición y Riesgo
Reticulum detecta sistemáticamente múltiples patrones de exposición:
Niveles de Exposición
-
ALTO: Requiere atención inmediata
- Servicios LoadBalancer → Acceso directo a internet
- Servicios NodePort → A través del firewall del nodo
- Controladores de Ingress → Puerta de entrada al tráfico externo
- Acceso a la Red del Host → Compartiendo la pila de red del host
-
MEDIO: Monitorear de cerca
- Ingress Estándar → A través de tu puerta de enlace
- Malla de Servicios → A través de tu infraestructura de malla
-
BAJO: Generalmente seguro
- Servicios Internos → Solo accesibles desde dentro
- Servicios de Base de Datos → Almacenamiento de datos interno
Patrones de Riesgo
- Contenedores Privilegiados
- Ejecución de Usuario Root
- Acceso al Namespace del Host
- Capacidades Peligrosas de Linux (SYS_ADMIN, SYS_PTRACE)
- Escalamiento de Privilegios
- Gestión de Recursos
Formatos de Salida
Reticulum proporciona múltiples formatos de salida:
Salida de Consola Codificada por Colores
La salida CLI proporciona indicadores visuales claros con códigos de color ANSI 8 :
- Rojo: P0_BLEEDING (90-100) - Acción inmediata requerida
- Amarillo: P1_CRITICAL (70-89) - Monitorear de cerca
- Azul: P2_HIGH (50-69) - Generalmente seguro
Reportes JSON
Datos estructurados para automatización con servicios y hallazgos 9 .
SARIF Enriquecido
SARIF original aumentado con puntuaciones contextuales de Reticulum.
Ejemplos Prácticos
Escaneo Básico
# Escanear repositorio de microservicios
docker run --rm -v $(pwd):/data reticulum \
-p /data/microservices \
-s /data/trivy.sarif
Análisis Detallado
# Generar salida JSON para automatización
./reticulum -p ./microservices -s ./trivy.sarif -o security-report.json
Modo Solo Exposición
# Análisis de exposición sin procesar vulnerabilidades
./reticulum -p ./charts --scan-only -o exposure.json
Rendimiento
Reticulum está optimizado para alto rendimiento:
- Tiempo de Escaneo: Menos de 30 segundos para repositorios complejos
- Uso de Memoria: Menos de 512MB en pico
- Escalabilidad: Maneja más de 100 gráficos sin problemas
- Procesamiento Concurrente: Múltiples trabajadores escaneando en paralelo
Casos de Uso de DevSecOps
1. Auditorías Pre-Despliegue
# Ejecutar escaneo de seguridad completo
docker run --rm -v $(pwd):/data reticulum \
-p /data/k8s-manifests \
-s /data/trivy.sarif \
-o /data/pre-deployment-audit.json
2. Integración CI/CD
#!/bin/bash
# security-check.sh - Añadir a tu pipeline CI/CD
echo "🔍 Ejecutando escaneo de seguridad Reticulum..."
docker run --rm -v $(pwd):/data reticulum \
-p /data -s /data/trivy.sarif -o /data/security-report.json
# Verificar hallazgos de alto riesgo
HIGH_RISK=$(jq '.services[] | select(.findings[].priority == "P0_BLEEDING")' /data/security-report.json | wc -l)
if [ "$HIGH_RISK" -gt 0 ]; then
echo "❌ $HIGH_RISK hallazgos críticos detectados"
exit 1
fi
3. Validación Multi-Entorno
# Comparar entornos
./reticulum -p ./charts-dev --scan-only -o dev-exposure.json
./reticulum -p ./charts-prod --scan-only -o prod-exposure.json
diff dev-exposure.json prod-exposure.json
Comienza Hoy
Inicio Rápido (5 minutos)
# Construir y ejecutar con Docker
git clone https://github.com/plexicus/reticulum.git
cd reticulum
docker build -t reticulum .
docker run --rm -v $(pwd):/data reticulum -p /data/tests/monorepo-06 -s /data/tests/monorepo-06/trivy.sarif
Próximos Pasos
- Analizar Resultados: Revisar los niveles de exposición y prioridades P0-P4
- Solucionar Problemas Críticos: Abordar primero los hallazgos P0_BLEEDING
- Integrar en CI/CD: Añadir escaneo de seguridad a tu pipeline
- Monitorear Regularmente: Configurar escaneos periódicos
Obtener Ayuda y Soporte
- Documentación: Repositorio de GitHub
- Código Fuente: Escrito en lenguaje D con arquitectura modular
- Problemas: Reportar errores o solicitar características en GitHub
La seguridad de tu infraestructura en la nube comienza con Reticulum - la potencia del lenguaje D para análisis de seguridad contextual.
Notas
- Esta versión refleja la implementación actual en lenguaje D del proyecto Reticulum
- Los comandos de instalación usan Docker y DUB en lugar de pip/poetry
- La arquitectura modular está implementada con archivos .d específicos
- Los ejemplos de CI/CD usan Docker para consistencia multi-plataforma
- Los formatos de salida y características técnicas coinciden con la implementación real del códigobase
Wiki pages you might want to explore:
- Code Structure and Modules (plexicus/reticulum)
- Installation and Quick Start (plexicus/reticulum)
- System Architecture (plexicus/reticulum)
Citations
File: Dockerfile (L1-52)
# --- Build Stage ---
FROM debian:bookworm-slim AS builder
ARG TARGETARCH
ARG LDC_VERSION="1.36.0"
WORKDIR /opt
# Install build dependencies
RUN apt-get update && apt-get install -y \
curl \
xz-utils \
build-essential \
libxml2 \
git \
&& rm -rf /var/lib/apt/lists/*
# Download LDC based on architecture (amd64 vs arm64)
RUN if [ "$TARGETARCH" = "amd64" ]; then \
LDC_ARCH="x86_64"; \
elif [ "$TARGETARCH" = "arm64" ]; then \
LDC_ARCH="aarch64"; \
else \
echo "Unsupported architecture: $TARGETARCH"; exit 1; \
fi && \
curl -L -o ldc.tar.xz "https://github.com/ldc-developers/ldc/releases/download/v${LDC_VERSION}/ldc2-${LDC_VERSION}-linux-${LDC_ARCH}.tar.xz" && \
tar -xf ldc.tar.xz && \
mv "ldc2-${LDC_VERSION}-linux-${LDC_ARCH}" ldc && \
rm ldc.tar.xz
ENV PATH="/opt/ldc/bin:${PATH}"
WORKDIR /app
# Copy source and build
COPY . .
RUN dub build --build=release --compiler=ldc2
# --- Runtime Stage ---
FROM debian:bookworm-slim
WORKDIR /app
# Install runtime dependencies
RUN apt-get update && apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/*
# Copy binary and assets
COPY --from=builder /app/reticulum /usr/local/bin/reticulum
COPY --from=builder /app/rules /app/rules
ENTRYPOINT ["reticulum"]
File: README.md (L75-78)
#### Prerequisites
- [D Language Compiler](https://dlang.org/download.html) (DMD or LDC2)
- [DUB](https://code.dlang.org/download) (Package Manager)
- [Trivy](https://trivy.dev/) & [Semgrep](https://semgrep.dev/) (Scanners)
File: src/app.d (L1-315)
/**
* Reticulum - Contextual Security Prioritizer for Kubernetes
*
* Author: Jose Ramon Palanco <jose.palanco@plexicus.ai>
* By: PLEXICUS (https://www.plexicus.ai)
* License: MIT
*/
module app;
import std.stdio;
import std.getopt;
import std.json;
import mapper;
import analyzer;
import ingestor;
import rules.engine;
import std.file;
import std.path;
import std.algorithm;
import std.string;
import std.conv;
// ANSI Color Codes - Red Alert Palette
enum Color : string
{
RESET = "\033[0m",
BOLD = "\033[1m",
DIM = "\033[2m",
RED = "\033[31m",
GREEN = "\033[32m",
YELLOW = "\033[33m",
BLUE = "\033[34m",
MAGENTA = "\033[35m",
CYAN = "\033[36m",
WHITE = "\033[37m",
BRIGHT_RED = "\033[91m",
BRIGHT_GREEN = "\033[92m",
BRIGHT_YELLOW = "\033[93m",
BRIGHT_BLUE = "\033[94m",
BRIGHT_MAGENTA = "\033[95m",
BRIGHT_CYAN = "\033[96m",
// Extended colors for brutal theme
COL_RED = "\033[38;5;196m", // Laser red
COL_GRY = "\033[38;5;244m", // Metallic gray
BG_RED = "\033[48;5;196m\033[38;5;232m" // Red background, black text
}
void printBanner()
{
// Header bar - full width tactical style (Purple background, black text)
write(
"\033[48;5;129m\033[38;5;232m\033[1m CLOUD-NATIVE CONTEXTUAL SECURITY PRIORITIZER BY PLEXICUS ");
write(" \033[0m\n");
write("\033[100m\033[30m RETICULUM v1.0 ");
write(" \033[0m\n\n");
// RETICULUM logo in ANSI Shadow font
const string[] logo = [
"██████╗ ███████╗████████╗██╗ ██████╗██╗ ██╗██╗ ██╗ ██╗███╗ ███╗",
"██╔══██╗██╔════╝╚══██╔══╝██║██╔════╝██║ ██║██║ ██║ ██║████╗ ████║",
"██████╔╝█████╗ ██║ ██║██║ ██║ ██║██║ ██║ ██║██╔████╔██║",
"██╔══██╗██╔══╝ ██║ ██║██║ ██║ ██║██║ ██║ ██║██║╚██╔╝██║",
"██║ ██║███████╗ ██║ ██║╚██████╗╚██████╔╝███████╗╚██████╔╝██║ ╚═╝ ██║",
"╚═╝ ╚═╝╚══════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚══════╝ ╚═════╝ ╚═╝ ╚═╝"
];
// Render logo with purple/violet color (129)
foreach (line; logo)
{
write("\033[38;5;129m", line, "\033[0m\n");
}
writeln();
write(
" \033[38;5;250m[+] MODULE: \033[38;5;129mPRIORITIZER\033[0m \033[38;5;250mONLINE\033[0m\n");
writeln();
}
void printPhase(string phase, string description)
{
write("\033[94m\n┌─\033[0m\033[1m " ~ phase ~ "\033[0m\033[94m ─────────────────────────────────────────────────────────\033[0m\n");
write("\033[94m│ \033[0m\033[2m" ~ description ~ "\033[0m\n");
write("\033[94m└────────────────────────────────────────────────────────────────────\033[0m\n");
}
void printSuccess(string message)
{
writeln("\033[92m[+] \033[0m" ~ message);
}
void printWarning(string message)
{
writeln("\033[93m[!] \033[0m" ~ message);
}
void printError(string message)
{
writeln("\033[91m[x] \033[0m" ~ message);
}
void printInfo(string message)
{
writeln("\033[96m[*] \033[0m" ~ message);
}
// Track if this is the first finding for header
bool firstFinding = true;
void printMatch(string service, string cveId, string filePath, int lineNum, int baseScore, int finalScore, string priority, string[] appliedRules, string description)
{
if (firstFinding)
{
writeln("");
firstFinding = false;
}
// Color code the Reticulum score based on priority
string scoreColor;
if (priority.startsWith("P0"))
scoreColor = "\033[91m"; // Bright red for P0_BLEEDING
else if (priority.startsWith("P1"))
scoreColor = "\033[31m"; // Red for P1_CRITICAL
else if (priority.startsWith("P2"))
scoreColor = "\033[33m"; // Yellow for P2_HIGH
else if (priority.startsWith("P3"))
scoreColor = "\033[34m"; // Blue for P3_MEDIUM
else
scoreColor = "\033[2m"; // Dim for P4_LOW
import std.string : leftJustify;
import std.array : join;
// Format file path with line number
string lineStr = lineNum > 0 ? ":" ~ to!string(lineNum) : "";
string fullPath = filePath ~ lineStr;
// Format rules
string rulesStr = appliedRules.length > 0 ? appliedRules.join(", ") : "none";
// Print finding in a clean, simple format
writeln(" \033[96m▸\033[0m \033[1m" ~ service ~ "\033[0m | \033[93m" ~ cveId ~ "\033[0m");
writeln(" \033[2m" ~ fullPath ~ "\033[0m");
writeln(" \033[2m" ~ description ~ "\033[0m");
writeln(" Tool: \033[2m" ~ to!string(baseScore) ~ "\033[0m → Reticulum: " ~ scoreColor ~ to!string(
finalScore) ~ "\033[0m | Rules: \033[2m" ~ rulesStr ~ "\033[0m");
writeln("");
}
void printHelp()
{
writeln("\033[1mUSAGE:\033[0m");
writeln(" reticulum [OPTIONS]\n");
writeln("\033[1mOPTIONS:\033[0m");
writeln(
" \033[96m-p, --path\033[0m Path to the source repository \033[2m(Required)\033[0m");
writeln(
" \033[96m-s, --sarif\033[0m Path to input SARIF file \033[2m(Required unless --scan-only)\033[0m");
writeln(" \033[96m-o, --output\033[0m Path to save the output JSON report");
writeln(
" \033[96m --sarif-output\033[0m Path to save enriched SARIF \033[2m(optional)\033[0m");
writeln(
" \033[96m --scan-only\033[0m Perform exposure analysis only \033[2m(ignores SARIF)\033[0m");
writeln(" \033[96m-h, --help\033[0m This help information\n");
writeln("\033[1mEXAMPLES:\033[0m");
writeln(" \033[2m# Full analysis with SARIF input\033[0m");
writeln(" \033[32m./reticulum -p ./src -s results.sarif\033[0m\n");
writeln(" \033[2m# Generate enriched SARIF output\033[0m");
writeln(
" \033[32m./reticulum -p ./src -s results.sarif --sarif-output enriched.sarif\033[0m\n");
writeln(" \033[2m# Exposure analysis only\033[0m");
writeln(" \033[32m./reticulum -p ./src --scan-only -o exposure.json\033[0m\n");
}
void main(string[] args)
{
string repoPath;
string sarifInput;
string jsonOutput;
string sarifOutput;
bool scanOnly = false;
printBanner();
try
{
auto helpInfo = getopt(
args,
"path|p", "Path to the source repository (Required)", &repoPath,
"sarif|s", "Path to input SARIF file (Required unless --scan-only)", &sarifInput,
"output|o", "Path to save the output JSON report", &jsonOutput,
"sarif-output", "Path to save enriched SARIF (optional)", &sarifOutput,
"scan-only", "Perform exposure analysis only (ignores SARIF)", &scanOnly
);
if (helpInfo.helpWanted || repoPath == "")
{
printHelp();
return;
}
}
catch (Exception e)
{
printError("Error parsing arguments: " ~ e.msg);
return;
}
if (!scanOnly && sarifInput == "")
{
printError("--sarif is required unless --scan-only is used.");
return;
}
// 1. Resolve absolute path for the repo
string absRepoPath = repoPath.absolutePath.buildNormalizedPath;
printInfo("Target Repository: " ~ absRepoPath);
// --- Initialize Rule Engine ---
printInfo("Initializing Rule Engine...");
RuleEngine engine = new RuleEngine();
// Load rules from organized directories
if (exists("rules/exposure"))
engine.loadRules("rules/exposure");
if (exists("rules/security"))
engine.loadRules("rules/security");
if (exists("rules/scoring"))
engine.loadRules("rules/scoring");
// Load custom rules last (can override defaults)
if (exists("rules/custom"))
{
engine.loadRules("rules/custom");
}
printPhase("Phase 1: Service Discovery", "Mapping services and Helm charts");
Mapper m = new Mapper();
m.walk(absRepoPath); // Changed to use absRepoPath
m.link();
printPhase("Phase 2: Exposure Analysis", "Analyzing Kubernetes deployment configurations");
foreach (s; m.services)
{
if (s.chart)
analyzer.analyzeExposure(s.chart, engine);
}
if (scanOnly)
{
printWarning("Mode: Exposure Analysis Only (Skipping SARIF ingestion)");
// 1. Build the Rich JSON
JSONValue[] serviceList;
foreach (s; m.services)
{
serviceList ~= s.toJson();
}
JSONValue root = parseJSON("{}");
root.object["services"] = JSONValue(serviceList);
root.object["totalServices"] = JSONValue(cast(long) m.services.length);
root.object["scanType"] = JSONValue("exposure-audit");
// 2. Output Handling
if (jsonOutput != "")
{
try
{
std.file.write(jsonOutput, root.toPrettyString());
printSuccess("Exposure report saved to: " ~ jsonOutput);
}
catch (Exception e)
{
printError("Error writing output: " ~ e.msg);
}
}
else
{
// Fallback: If no file specified, print to stdout
writeln(root.toPrettyString());
}
return; // EXIT PROGRAM HERE
}
printPhase("Phase 3: Vulnerability Scoring", "Ingesting SARIF and applying contextual scoring");
processSarif(sarifInput, m.services, absRepoPath, engine, sarifOutput);
// --- Generate JSON Report (Full Scan) ---
if (jsonOutput != "")
{
JSONValue[] serviceList;
foreach (s; m.services)
{
serviceList ~= s.toJson();
}
JSONValue root = parseJSON("{}");
root.object["services"] = JSONValue(serviceList);
root.object["totalServices"] = JSONValue(cast(long) m.services.length);
root.object["scanType"] = JSONValue("full-analysis");
try
{
std.file.write(jsonOutput, root.toPrettyString());
printSuccess("Full analysis report saved to: " ~ jsonOutput);
}
catch (Exception e)
{
printError("Error writing JSON output: " ~ e.msg);
}
}
printSuccess("Reticulum Analysis Complete.");
File: src/model.d (L1-90)
module model;
import std.json;
import std.algorithm; // for min, max
import std.conv; // for to!string
// The Decision Matrix Levels
enum Priority
{
P0_BLEEDING, // Score 90-100: Public + Critical
P1_CRITICAL, // Score 70-89: Public + High OR Internal + Critical
P2_HIGH, // Score 50-69: Internal + High
P3_MEDIUM, // Score 30-49: Medium issues
P4_LOW // Score 0-29: Low/Info
}
class RiskProfile
{
// --- Context Flags ---
bool isPublic = false; // Ingress / LoadBalancer
bool isPrivileged = false; // hostNetwork / privileged: true / runs as root
bool hasFix = true; // Patch available?
bool hasDangerousCaps = false; // SYS_ADMIN, NET_ADMIN, etc.
bool hasInternetEgress = false; // 0.0.0.0/0 allowed
bool mountServiceToken = true; // automountServiceAccountToken (default true is risky)
// --- Dynamic Scoring ---
float[] multipliers;
int[] boosts;
string[] appliedRuleIds; // Track which rules were applied
void addMultiplier(float m)
{
multipliers ~= m;
}
void addBoost(int b)
{
boosts ~= b;
}
JSONValue toJson()
{
JSONValue j = parseJSON("{}");
j.object["isPublic"] = JSONValue(isPublic);
j.object["isPrivileged"] = JSONValue(isPrivileged);
j.object["hasDangerousCaps"] = JSONValue(hasDangerousCaps);
j.object["hasInternetEgress"] = JSONValue(hasInternetEgress);
j.object["mountServiceToken"] = JSONValue(mountServiceToken);
if (appliedRuleIds.length > 0)
{
j.object["appliedRuleIds"] = JSONValue(appliedRuleIds);
}
// Calculate a raw score for the report based on a baseline (e.g., 50) just for visibility
j.object["baseRiskScore"] = JSONValue(calculateScore(50));
return j;
}
// --- robustCalculateScore ---
// Transforms a raw severity (0-100) into a Contextual Risk Score
int calculateScore(int baseSeverity)
{
float score = cast(float) baseSeverity;
// 1. Apply Multipliers (Exposure, Context)
// Default to 1.0 if no multipliers added
if (multipliers.length == 0)
{
// Fallback to legacy logic if no rules ran (or no rules matched)
// This ensures backward compatibility during migration if rules aren't loaded
// But ideally we want rules to drive this.
// For now, let's assume if no multipliers, we treat it as neutral (1.0)
// OR we could keep the hardcoded logic here as a fallback?
// Let's keep it pure: if no rules, no multipliers.
}
else
{
foreach (m; multipliers)
{
score *= m;
}
}
// 2. Apply Boosts (Threats)
foreach (b; boosts)
{
score += b;
}
// 3. Actionability Modifier (Hardcoded for now as it's not a rule per se, but could be)
File: src/analyzer.d (L1-90)
module analyzer;
import model;
import rules.engine; // Import RuleEngine
import std.file;
import std.path;
import std.stdio;
import std.string;
import std.algorithm;
import std.array;
import dyaml.loader;
import dyaml.node;
// ========================
// MAIN LOGIC
// ========================
void analyzeExposure(Chart chart, RuleEngine engine)
{
if (chart is null)
return;
writeln(" [Analyzer] Analyzing chart: ", chart.name, " → ", chart.path);
chart.risk.reset();
// 1. Evaluate Metadata Rules
engine.evaluateMetadata(chart);
string[] valueFiles;
try
{
foreach (string entry; dirEntries(chart.path, SpanMode.shallow))
{
string fname = baseName(entry).toLower;
// --- FIX: Strictly exclude non-value files ---
if (fname == "chart.yaml" || fname == "chart.yml" || fname == "chart.lock" || fname.startsWith(
".helm"))
{
continue;
}
// Allow standard values.yaml, variations like values-prod.yaml, or specific environment files
bool isYaml = (fname.endsWith(".yaml") || fname.endsWith(".yml"));
bool looksLikeValues = (fname.startsWith("values") || fname == "prod.yaml" || fname == "staging.yaml" || fname == "dev.yaml");
if (isYaml && looksLikeValues)
{
valueFiles ~= entry;
}
}
}
catch (Throwable)
{
}
valueFiles.sort();
foreach (path; valueFiles)
{
if (path.exists)
{
writeln(" → Loading values: ", baseName(path));
analyzeValuesFile(chart, path, engine);
}
}
// analyzeTemplates(chart); // Deprecated for now, or needs to be ported to RuleEngine (FILE_CONTENT target)
// For now, we rely on values.yaml analysis as per the plan. Template analysis is complex to do with simple rules.
// If we need template analysis, we should add a FILE_CONTENT target to RuleEngine later.
writeln(" [Final Risk Profile]");
writeln(" • Public Exposure : ", chart.risk.isPublic ? "YES" : "NO");
writeln(" • Privileged : ", chart.risk.isPrivileged ? "YES" : "NO");
writeln(" • Dangerous Caps : ", chart.risk.hasDangerousCaps ? "YES" : "NO");
writeln(" • Svc Token Mount : ", chart.risk.mountServiceToken ? "YES (Default)"
: "NO (Secured)");
}
private void analyzeValuesFile(Chart chart, string path, RuleEngine engine)
{
try
{
Node root = Loader.fromFile(path).load();
if (root.type != NodeType.mapping)
return;
// 2. Evaluate Values Rules
engine.evaluateValues(chart, root);
File: src/ingestor.d (L31-90)
{
return to!float(v);
}
catch (Throwable)
{
}
}
return 5.0;
}
string floatToSeverityLabel(float score)
{
if (score >= 9.0)
return "CRITICAL";
if (score >= 7.0)
return "HIGH";
if (score >= 4.0)
return "MEDIUM";
return "LOW";
}
bool isFixable(JSONValue result)
{
JSONValue props = ("properties" in result) ? result["properties"] : parseJSON("{}");
if ("trivy:fixedVersion" in props)
{
string fixedVer = props["trivy:fixedVersion"].str;
return (fixedVer != "" && fixedVer != "null");
}
if ("github:fixAvailable" in props)
{
// Handle boolean or string "true"
auto val = props["github:fixAvailable"];
if (val.type == JSONType.true_)
return true;
if (val.type == JSONType.string)
return val.str == "true";
return false;
}
// Static analysis fixes
if ("fix" in result && result["fix"].type != JSONType.null_)
return true;
// Default assumption for SAST (Code) is that it is fixable by dev
return true;
}
float extractSeverity(JSONValue result, JSONValue rules)
{
// 1. GitHub security-severity
if ("properties" in result && "security-severity" in result["properties"])
{
auto s = result["properties"]["security-severity"];
if (s.type == JSONType.string)
return to!float(s.str);
if (s.type == JSONType.float_)
return s.floating;
}