voftec/normativapba-mcp
criticalMCP Server oficial para buscar y extraer legislación de la Provincia de Buenos Aires. Conector de Inteligencia Artificial para consultar normas.gba.gob.ar con fidelidad jurídica absoluta.
MCP server (purpose undetermined)
141 url: z.string().url().optional().describe("La URL completa de la norma. Si no la tienes, usa los otros parámetros."),
142 numero: z.string().optional().describe("Número de la norma (ej. '10430')."),
143 tipo_norma: z.string().optional().describe("Tipo de norma (ej. 'ley', 'decreto')."),
144 anio: z.string().optional().describe("Año de la norma.")
145 },
146 async (args) => {
147 try {
148 let targetUrl = args.url;// Exploitable if the MCP server is exposed to untrusted prompts (network_exposed). An attacker controlling the LLM prompt can supply arbitrary URLs.
The tool 'obtener_texto_norma' accepts a user-supplied URL and makes HTTP requests to it without validating that the URL belongs to the expected domain (normas.gba.gob.ar). An attacker could provide a URL pointing to an internal service (e.g., 169.254.169.254 for cloud metadata) or an arbitrary external server, causing the server to make requests to unintended destinations. This is a classic SSRF vulnerability. The same pattern exists in 'verificar_vigencia', 'obtener_articulo', 'exportar_norma', and 'relacionar_normativa'.
ImpactAn attacker could use the MCP server to scan internal networks, access cloud metadata endpoints, or perform port scanning. In a network-exposed scenario, this could lead to information disclosure or further compromise.
FixValidate that the provided URL matches the expected base URL (https://normas.gba.gob.ar) before making requests. Use a URL parser to check the hostname and reject any URL that does not start with the allowed base.
150 // Resolución Automática de URL
151 if (!targetUrl) {
152 if (!args.numero && !args.tipo_norma) {
153 throw new Error("Debe proveer una 'url' o al menos un 'numero' o 'tipo_norma' para buscar la norma automáticamente.");
154 }
155 const params = new URLSearchParams();
156 if (args.numero) params.append("q[terms][number]", args.numero);
157 if (args.anio) params.append("q[terms][year]", args.anio);
158 if (!args.numero && args.tipo_norma) params.append("q[phrase]", args.tipo_norma);
159
160 const searchUrl = `${BASE_URL}/resultados?${params.toString()}`;
161 const searchRes = await axios.get(searchUrl, { httpsAgent, headers: { 'User-Agent': 'Mozilla/5.0' } });// Exploitable if the MCP server is exposed to untrusted prompts. Requires more complex manipulation than direct URL parameter.
The automatic URL resolution feature constructs a search URL using user-supplied parameters (numero, anio, tipo_norma) and makes an HTTP request to that URL. While the base URL is fixed, the parameters are not sanitized and could contain injection characters that alter the request path or query string. However, the primary SSRF risk is that the resolved URL (firstLink) is taken from the response and used as targetUrl without validation. An attacker could craft a search that returns a malicious link, but this is less direct than the explicit URL parameter.
ImpactLower risk than direct URL parameter, but still could lead to SSRF if the search results page is manipulated or if the attacker can influence the search parameters to cause the server to fetch an unintended URL.
FixValidate that the resolved URL (firstLink) belongs to the allowed domain before making further requests. Additionally, sanitize search parameters to prevent injection.
637 url: z.string().url().describe("URL oficial de la norma en normas.gba.gob.ar"),
638 articulo: z.string().describe("Número o identificador del artículo a extraer (ej. '1', '5 bis', '10')")
639 },
640 async (args) => {
641 try {
642 const metaResponse = await axios.get(args.url, {// Exploitable if the MCP server is exposed to untrusted prompts.
The tools 'obtener_articulo', 'exportar_norma', and 'relacionar_normativa' accept a URL parameter but do not validate that it belongs to the expected domain. This allows SSRF attacks similar to 'obtener_texto_norma'. The same pattern is present in 'verificar_vigencia'.
ImpactAn attacker could make the server request arbitrary URLs, potentially accessing internal services or external resources.
FixAdd URL validation to ensure the hostname matches normas.gba.gob.ar before making requests.
141server.tool(
142 "obtener_texto_norma",
143 ...
144 {
145 url: z.string().url().optional().describe("La URL completa de la norma..."),
146 ...
147 },
148 async (args) => {
149 try {
150 let targetUrl = args.url;
151 // Resolución Automática de URL
152 if (!targetUrl) {
153 ...
154 }
155 // PASO 1: Descargar la página de metadatos de la norma
156 const metaResponse = await axios.get(targetUrl, {
157 httpsAgent,
158 headers: { ... },
159 timeout: 15000
160 });// Exploitable if MCP is exposed to untrusted prompts (network_exposed). For local-only, requires compromised LLM.
The tool 'obtener_texto_norma' accepts a user-supplied URL via the 'url' parameter and makes an HTTP GET request to that URL using axios. There is no validation that the URL belongs to the intended domain (normas.gba.gob.ar). An attacker could provide a URL pointing to an internal service (e.g., http://localhost:8080/admin) or an external malicious server, causing the server to make requests to arbitrary destinations.
ImpactAn attacker could use this to perform Server-Side Request Forgery (SSRF), potentially accessing internal services, reading sensitive files (e.g., cloud metadata endpoints), or scanning internal networks.
FixValidate that the provided URL matches the expected base URL (https://normas.gba.gob.ar) before making the request. Use a URL parser to check the hostname and reject any URLs that do not match.
388server.tool(
389 "verificar_vigencia",
390 ...
391 {
392 url: z.string().url().optional().describe("La URL completa de la norma a verificar..."),
393 ...
394 },
395 async (args) => {
396 try {
397 let targetUrl = args.url;
398 // Resolución Automática de URL
399 if (!targetUrl) {
400 ...
401 }
402 const response = await axios.get(targetUrl, {
403 httpsAgent,
404 headers: { ... },
405 timeout: 15000
406 });// Exploitable if MCP is exposed to untrusted prompts (network_exposed). For local-only, requires compromised LLM.
The tool 'verificar_vigencia' accepts a user-supplied URL via the 'url' parameter and makes an HTTP GET request to that URL using axios. There is no validation that the URL belongs to the intended domain. An attacker could provide a URL pointing to an internal service or an external malicious server.
ImpactSame as above: SSRF to internal services or external hosts.
FixValidate that the provided URL matches the expected base URL (https://normas.gba.gob.ar) before making the request.
727server.tool(
728 "exportar_norma",
729 ...
730 {
731 url: z.string().url().describe("La URL oficial de la norma a exportar.")
732 },
733 async (args) => {
734 try {
735 const metaResponse = await axios.get(args.url, {
736 httpsAgent,
737 headers: { 'User-Agent': 'Mozilla/5.0 ...' },
738 timeout: 15000
739 });// Exploitable if MCP is exposed to untrusted prompts (network_exposed). For local-only, requires compromised LLM.
The tool 'exportar_norma' accepts a user-supplied URL via the 'url' parameter and makes an HTTP GET request to that URL using axios. There is no validation that the URL belongs to the intended domain.
ImpactSame as above: SSRF to internal services or external hosts.
FixValidate that the provided URL matches the expected base URL (https://normas.gba.gob.ar) before making the request.
633server.tool(
634 "obtener_articulo",
635 ...
636 {
637 url: z.string().url().describe("URL oficial de la norma en normas.gba.gob.ar"),
638 articulo: z.string().describe("...")
639 },
640 async (args) => {
641 try {
642 const metaResponse = await axios.get(args.url, {
643 httpsAgent,
644 headers: { 'User-Agent': 'Mozilla/5.0 ...' },
645 timeout: 15000
646 });// Exploitable if MCP is exposed to untrusted prompts (network_exposed). For local-only, requires compromised LLM.
The tool 'obtener_articulo' accepts a user-supplied URL via the 'url' parameter and makes an HTTP GET request to that URL using axios. There is no validation that the URL belongs to the intended domain.
ImpactSame as above: SSRF to internal services or external hosts.
FixValidate that the provided URL matches the expected base URL (https://normas.gba.gob.ar) before making the request.
821server.tool(
822 "relacionar_normativa",
823 ...
824 {
825 url: z.string().url().optional().describe("La URL oficial de la norma..."),
826 ...
827 },
828 async (args) => {
829 try {
830 let targetUrl = args.url;
831 // Resolución Automática de URL
832 if (!targetUrl) {
833 ...
834 }
835 const response = await axios.get(targetUrl, { httpsAgent, headers: { 'User-Agent': 'Mozilla/5.0' }, timeout: 15000 });// Exploitable if MCP is exposed to untrusted prompts (network_exposed). For local-only, requires compromised LLM.
The tool 'relacionar_normativa' accepts a user-supplied URL via the 'url' parameter and makes an HTTP GET request to that URL using axios. There is no validation that the URL belongs to the intended domain.
ImpactSame as above: SSRF to internal services or external hosts.
FixValidate that the provided URL matches the expected base URL (https://normas.gba.gob.ar) before making the request.
38 const params = new URLSearchParams();
39 if (args.frase_exacta) params.append("q[phrase]", args.frase_exacta);
40 if (args.palabras_clave) params.append("q[with_some_words]", args.palabras_clave);
41 if (args.tipo_norma) params.append("q[terms][raw_type]", args.tipo_norma);
42 if (args.numero) params.append("q[terms][number]", args.numero);
43 if (args.anio) params.append("q[terms][year]", args.anio);
44 if (args.pagina) params.append("page", args.pagina.toString());// Exploitable if the MCP server is exposed to untrusted prompts. The impact is limited to manipulating search queries on the government portal.
User-supplied search parameters are directly appended to the URL query string without sanitization. While the target is a government website, an attacker could inject special characters or attempt to manipulate the search query to cause unexpected behavior or access unintended endpoints. This is a form of injection into the HTTP request.
ImpactAn attacker could potentially manipulate the search to access different parts of the government portal or cause the server to make requests to unintended URLs. The risk is limited because the base URL is fixed.
FixSanitize or validate input parameters to ensure they do not contain characters that could alter the URL structure (e.g., newlines, ampersands, etc.). Use allowlists for expected patterns.
10const httpsAgent = new https.Agent({
11 rejectUnauthorized: false
12});