[ ⌘K ]
← BACK TO SEARCH

voftec/normativapba-mcp

critical

MCP 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)

purpose: MCP server (purpose undetermined)threat: network exposed
JavaScript2May 19, 2026May 20, 2026GITHUB
5/20/2026
high1 finding
src/index.ts
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;
src/index.ts:141

// 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.

high1 finding
src/index.ts
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' } });
src/index.ts:150

// 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.

medium1 finding
src/index.ts
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, {
src/index.ts:637

// 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.

medium1 finding
src/index.ts
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      });
src/index.ts:1-7

// 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.

medium1 finding
src/index.ts
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      });
src/index.ts:1-7

// 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.

medium1 finding
src/index.ts
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      });
src/index.ts:1-7

// 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.

medium1 finding
src/index.ts
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      });
src/index.ts:1-7

// 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.

medium1 finding
src/index.ts
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 });
src/index.ts:1-7

// 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.

medium1 finding
src/index.ts
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());
src/index.ts:38

// 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.

low2 findings
src/index.ts
10const httpsAgent = new https.Agent({
11  rejectUnauthorized: false
12});
network.httpshell.exec
100
LLM-based
low findings+10
high findings+50
medium findings+105
scoringcompleted