# Unauthenticated API Exposure: Albanian Parliament (Kuvendi i Shqipërisë)

**Date:** February 25, 2026
**Target:** parlament.al / kuvendiapi.azurewebsites.net
**Severity:** High — Full PII of 236 Members of Parliament + 54,545 public documents
**Status:** Live at time of discovery

---

## How We Found It

This discovery began as a tangential pivot during an investigation into Albania's national AI chatbot, **Diella**, operated by AKSHI (the National Agency for Information Society). The original scope was narrow — examine the Diella frontend for leaked API keys or misconfigured endpoints. That trail went cold quickly: the Vue.js bundle was clean, and all backend infrastructure (`dc-hasura`, `dc-keycloak`, etc.) sat behind internal-only DNS. Dead end.

With Albanian government infrastructure already in scope, we broadened to other `.gov.al` and `.al` domains. The Albanian Parliament site at **parlament.al** returned HTTP 200 for every path we tested — `.git/HEAD`, `.env`, `wp-json` — which initially looked promising. On closer inspection, every response was identical: the same React SPA `index.html` with the title *"Kuvendi i Shqipërisë"*. A classic catch-all router. False positive.

But the site itself was a React application, which meant there was a JavaScript bundle driving it — and JS bundles talk.

## The Bundle

We pulled the main chunk (`main.js`, 355 KB) and ran string extraction against it. Buried in the minified webpack output was a hardcoded API base URL:

```
https://kuvendiapi.azurewebsites.net/api
```

The frontend defined seven API resource names as variables — not using the user-facing route names (`/deputet`, `/lajme`) but internal OData entity names:

- `anetaret` (members)
- `strukturat` (structures)
- `aktet` (acts)
- `lajmet` (news)
- `mbledhjet` (meetings)
- `dokumentet` (documents)
- `abonimet` (subscriptions)

The routing pattern was straightforward: `GET /api/{entityName}` with optional OData query parameters (`$filter`, `$orderby`).

## The API

Every endpoint except `abonimet` (which returned 401) responded to unauthenticated GET requests with full JSON payloads. No API key. No token. No rate limiting.

The **anetaret** endpoint was the most sensitive — 236 records, one per Member of Parliament, each containing:

- Full legal name (first name, father's name, surname)
- Date of birth
- Place of birth
- Email address (@parlament.al)
- Political party affiliation
- Electoral district
- Profile photo URL
- Social media profile links
- Active/inactive status

The **dokumentet** endpoint was the largest at 30 MB — a catalog of every document uploaded to the parliament's Azure Blob Storage container. Each record included a direct URL to `kuvendiwebfiles.blob.core.windows.net/webfiles/`. Container listing was disabled, but every individual blob URL was publicly accessible without authentication.

## The Blob Storage

Extracting unique URLs from the dokumentet response yielded **54,545 publicly accessible files**:

| Type | Count |
|------|-------|
| PDF | 32,627 |
| JPEG | 9,831 |
| JPG | 8,458 |
| JFIF | 1,768 |
| DOCX | 885 |
| DOC | 397 |
| XLSX | 392 |
| PNG | 208 |
| XLS | 117 |

The XLSX files were particularly notable — they included **monthly salary spreadsheets for every MP** (2018–2020), a **lobbyist registry**, a **civil society organization registry**, **FOIA request/response logs** (2018–2021), and government budget expenditure tables.

The JS bundle also contained a reference to an internal API server at `http://134.0.63.165:5000/public` — unreachable from the public internet, but confirming the existence of additional backend infrastructure behind the Azure frontend.

## The YouTube Proxy

A bonus discovery: the API exposed a `/YouTube/search` endpoint that proxied search queries directly to YouTube's API, returning video metadata (IDs, titles, descriptions, thumbnails, publish dates, live status) for parliament-related content. This endpoint required a `query` parameter but no authentication.

## Root Cause

The Parliament's frontend was built as a React SPA deployed to a standard web server, with all API calls routed to an Azure-hosted ASP.NET OData backend. The API was designed to be consumed by the frontend and apparently relied on obscurity rather than authentication — the endpoint URLs weren't advertised, but they weren't protected either. The Azure Blob Storage container had public blob access enabled (individual URLs work) but container listing disabled, creating a false sense of security since every URL was cataloged in the dokumentet API response anyway.

## Timeline

1. Initial target: AKSHI Diella AI chatbot — frontend clean, backend internal-only
2. Broadened scope to Albanian government domains
3. parlament.al identified as React SPA (false positive on path enumeration)
4. JS bundle analysis revealed hardcoded API base URL
5. API variable extraction identified 7 OData entity names
6. Unauthenticated requests confirmed on 6 of 7 endpoints
7. dokumentet response yielded 54,545 Azure Blob Storage URLs
8. Blob URLs confirmed publicly accessible without authentication
9. MP salary spreadsheets, lobbyist registries, and FOIA logs identified in XLSX files

## Impact

- **236 MPs' PII** exposed via unauthenticated API (names, DOBs, emails, party affiliations)
- **54,545 government documents** accessible on Azure Blob Storage
- **Financial records** including individual MP salary and benefits data
- **Lobbying and civil society registries** in downloadable spreadsheet format
- **4 years of FOIA logs** documenting who requested what information from Parliament

---

*All data was recovered from publicly accessible, unauthenticated API endpoints and Azure Blob Storage URLs. No authentication was bypassed, no credentials were used, and no access controls were circumvented.*
