# Finding Detail — Vienotais Zemes Reģistrs Security Audit **Target:** https://vienotaiszemesregistrs.tech/hakatons/#/ **Audit Date:** 2026-10-26 **Auditor:** Automated remote security audit **Methodology:** External, remote-only testing; curl, openssl s_client, dig, static JS analysis --- ## F-001 — Unrestricted CORS: wildcard `*` + all HTTP methods on all paths **Score:** 8.5 / 10 | **Severity:** HIGH | **Status:** Confirmed ### Description The application returns `Access-Control-Allow-Origin: *` (wildcard) on every response, including API endpoints that serve sensitive government data when active. The CORS configuration also permits a wide range of HTTP methods and all request headers from any origin. ### Evidence ``` $ curl -I https://vienotaiszemesregistrs.tech/hakatons/api/health \ -H "Origin: https://evil.example.com" HTTP/2 200 access-control-allow-origin: * access-control-allow-methods: GET, POST, PUT, DELETE, PATCH, OPTIONS, HEAD access-control-allow-Headers: Content-Type access-control-max-age: 86400 vary: Origin x-powered-by: Express ... $ curl -X OPTIONS ... # returns 204 with all methods allowed ``` ### Impact Any third-party web application can make cross-origin requests to the API on behalf of a victim user's browser. When active API endpoints (parcel search, building info, geocode) are deployed, this enables: - CSRF-style attacks against API endpoints - Data exfiltration from protected government data sources - Unauthenticated enumeration when paired with SSRF in backend ### Remediation ```javascript // Express example const cors = require('cors'); app.use('/hakatons/api', cors({ origin: process.env.ALLOWED_ORIGINS?.split(',') || ['https://vienotaiszemesregistrs.tech'], methods: ['GET', 'HEAD', 'OPTIONS'], allowedHeaders: ['Content-Type'], })); ``` --- ## F-002 — All 10 standard security headers absent on functional responses **Score:** 7.5 / 10 | **Severity:** HIGH | **Status:** Confirmed ### Description None of the 10 common web security headers are present on 200 responses for `/hakatons/` (SPA) or `/api/health`. This leaves the application vulnerable to browser-side attacks including clickjacking, MIME-type sniffing, protocol downgrade, and cross-site scripting. ### Evidence ```bash $ curl -sI https://vienotaiszemesregistrs.tech/hakatons/ | grep -iE 'x-frame|x-content|content-security|strict-transport|referrer-policy|cross-origin|x-xss' # Result: NOTHING — zero security headers returned on success page Verified 10 headers tested: [ ] Strict-Transport-Secure — absent [ ] X-Content-Type-Options — absent [ ] X-Frame-Options — absent [ ] Content-Security-Policy — absent (present only on error pages) [ ] Cross-Origin-Opener-Policy — absent [ ] Cross-Origin-Resource-Policy — absent [ ] Referrer-Policy — absent [ ] Permissions-Policy — absent [ ] X-XSS-Protection — absent [ ] Cache-Control — present but poorly configured (max-age=0) ``` ### Full header dump (`/tmp/vzr_headers.txt`): ``` HTTP/2 200 accept-ranges: bytes access-control-allow-origin: * alt-svc: h3=":443"; ma=2592000 cache-control: public, max-age=0 content-type: text/html; charset=UTF-8 date: Mon, 26 Oct 2026 10:10:02 GMT etag: W/"3ef-1a125606753" last-modified: Sat, 10 Oct 2026 10:33:57 GMT via: 1.1 Caddy x-powered-by: Express content-length: 1007 ``` ### Impact - No clickjacking protection (X-Frame-Options missing) - No MIME sniffing prevention (X-Content-Type-Options missing) - No browser cache policy enforcement (max-age=0 + public cache directive allows stale serving) - No HTTP Strict Transport Security — browser may fall back to HTTP ### Remediation ```caddyfile # Caddy config site block header { Strict-Transport-Security "max-age=31536000; includeSubDomains" X-Content-Type-Options "nosniff" X-Frame-Options "DENY" Content-Security-Policy "default-src 'self'; ..." Cross-Origin-Opener-Policy "same-origin" Cross-Origin-Resource-Policy "same-origin" Referrer-Policy "strict-origin-when-cross-origin" Permissions-Policy "camera=(), microphone=()" } ``` --- ## F-003 — No HSTS enforcement; HTTP→HTTPS uses 308 but no max-age **Score:** 6.5 / 10 | **Severity:** MEDIUM | **Status:** Candidate ### Description HTTP requests are redirected to HTTPS via a 308 status code, which is correct. However: - No `Strict-Transport-Security` header is present on any response - The Caddy config lacks `enforce_https`, relying only on the automatic redirect - Without HSTS max-age, a user's first request can be intercepted via SSL stripping ### Evidence ```bash # HTTP to HTTPS redirect confirmed: $ curl -I http://vienotaiszemesregistrs.tech/hakatons/ HTTP/1.1 308 Permanent Redirect location: https://vienotaiszemesregistrs.tech/hakatons/ # No HSTS on the redirect or final response: $ curl -Ik https://vienotaiszemesregistrs.tech/hakatons/ | grep -i strict # Result: NOTHING ``` ### Impact First-time visitors who type `http://` could be intercepted by a man-in-the-middle. Without persistent HSTS, the browser has no memory of prior HTTPS-only preferences. ### Remediation (Candidate — requires active HTTP→HTTPS testing) In Caddy config: ```caddyfile https { protocols http/1.1 h2 h3 } enforce_https header Strict-Transport-Security "max-age=31536000; includeSubDomains" ``` --- ## F-004 — DNS missing security posture (no DNSSEC, CAA, TXT/SPF/DKIM/DMARC) **Score:** 6.0 / 10 | **Severity:** MEDIUM | **Status:** Confirmed ### Description The domain `vienotaiszemesregistrs.tech` registered at Name.com has no DNS security extensions configured: - No DNSSEC validation chain — zone susceptible to cache poisoning and BGP hijacking - No CAA record — any CA can issue a certificate for this domain - No TXT records for SPF, DKIM, or DMARC — email spoofing of admin addresses is possible - No MX records — no defined mail routing - No AAAA record — no IPv6 connectivity ### Evidence ```bash $ dig DNSSEC vienotaiszemesregistrs.tech +dnssec ; ->> HEADER << ... OPT PSEUDOSECTION: EDNS: version: 0, flags:DO; udp: 4096 ;; RRSIG: (none) # No DNSSEC signature $ dig CAA vienotaiszemesregistrs.tech ;; ANSWER: 0 records (no CAA record) $ dig TXT vienotaiszemesregistrs.tech ;; ANSWER: 0 records # no SPF/DKIM/DMARC $ dig AAAA vienotaiszemesregistrs.tech ;; ANSWER: 0 records # no IPv6 address ``` ### Impact - Certificate spoofing via unauthorized CAs - Email forgery from the domain (no SPF/DMARC) - DNS response poisoning (no DNSSEC) - Incomplete network reachability (no IPv6 AAAA) ### Remediation In registrar (Name.com): 1. Enable DNSSEC 2. Add `CAA 0 issue "letsencrypt.org"` 3. Add `TXT "v=spf1 -all"` (reject all email if none is expected) 4. Consider DMARC record: `TXT "_dmarc.vienotaiszemesregistrs.tech" = "v=DMARC1; p=reject"` ### Evidence Files - `/tmp/vzr_headers.txt` shows nameserver info via `via: 1.1 Caddy` header (Caddy hosted) --- ## F-005 — `/api/health` leaks backend connectivity metadata to any origin **Score:** 5.5 / 10 | **Severity:** MEDIUM | **Status:** Confirmed ### Description The `/hakatons/api/health` endpoint returns operational metadata including `ok`, `ms` (latency), and `checked_at` timestamp fields. This information, combined with CORS wildcard, exposes backend availability to any observer. ### Evidence ```bash $ curl -s https://vienotaiszemesregistrs.tech/hakatons/api/health | jq . { "ok": true, "ms": 3, "checked_at": "2026-10-26T10:17:59.182Z" } $ curl -sI https://vienotaiszemesregistrs.tech/hakatons/api/health HTTP/2 200 access-control-allow-origin: * # wildcard — accessible from any origin x-powered-by: Express # backend framework fingerprint content-type: application/json ``` ### Impact - Enables timing analysis for DDoS or rate-limit evasion - Confirms API is responsive and operational (reconnaissance) - Cross-origin accessible due to F-001 — any malicious site can poll this endpoint ### Remediation Remove `ok` and `ms` from response; restrict to internal monitoring via IP allowlisting or token-based auth. ```javascript // Express example app.get('/hakatons/api/health', (req, res) => { const secret = require('crypto').randomBytes(32).toString('hex'); res.json({ status: 'ok', timestamp: Date.now() }); }); ``` --- ## F-006 — Server fingerprinting via `x-powered-by: Express` **Score:** 4.0 / 10 | **Severity:** LOW | **Status:** Confirmed ### Description The `X-Powered-By: Express` HTTP header is present on all API responses, revealing the backend framework version and type. ### Evidence ```bash $ curl -sI https://vienotaiszemesregistrs.tech/hakatons/api/health | grep powered x-powered-by: Express # Confirmed across multiple endpoints: $ curl -sI https://vienotaiszemesregistrs.tech/hakatons/ | grep powered x-powered-by: Express # also present on SPA responses (Caddy proxy) ``` ### Impact Low — only information disclosure. Attackers use this to tailor exploits for known Express vulnerabilities. Combined with F-001 (CORS wildcard), it narrows the attack surface knowledge for any subsequent exploitation attempts. ### Remediation Add early in Express app initialization: ```javascript app.disable('x-powered-by'); ``` --- ## F-007 — Error pages leak stack-style HTML (`Cannot GET /api/info`) **Score:** 4.5 / 10 | **Severity:** LOW | **Status:** Confirmed ### Description When API endpoints are not available (currently all return 404), Express returns its default error page with the literal method and path string: `Cannot GET /path`. This reveals that the backend is a Node.js/Express application. ### Evidence ```bash $ curl -s https://vienotaiszemesregistrs.tech/hakatons/api/info | head -10 ...

Cannot GET /hakatons/api/info

# ← Direct path leakage $ curl -s https://vienotaiszemesregistrs.tech/hakatons/api/facts | head -10 ``` Also confirmed via error SPA page: ```bash $ curl -sI https://vienotaiszemesregistrs.tech/hakatons/ HTTP/2 500 # When error page served content-security-policy: default-src 'none' # CSP present on error! x-powered-by: Express # fingerprint still leaks ``` ### Impact Information disclosure revealing backend structure and routing conventions. Combined with F-011 (hardcoded API paths in JS), this confirms the path structure attackers should target when endpoints are active. ### Remediation ```javascript app.use((err, req, res, next) => { res.status(err.status || 500).json({ error: 'Internal server error' }); }); ``` --- ## F-008 — No robots.txt — all paths indexable by crawlers **Score:** 2.5 / 10 | **Severity:** INFO | **Status:** Confirmed ### Description The application does not serve a `robots.txt` file. When requesting `/hakatons/robots.txt`, the server responds with a 308 redirect to the main SPA page instead of an actual robots.txt directive. ### Evidence ```bash $ curl -sI https://vienotaiszemesregistrs.tech/hakatons/robots.txt HTTP/2 308 location: https://vierotaiszemesregistrs.tech/hakatons/ # Redirects to SPA — no robots directives served ``` ### Impact Low — crawlers cannot index API paths, but the path structure itself is discoverable from the JS bundle analysis (see F-011). ### Remediation Deploy `/hakatons/robots.txt`: ``` User-agent: * Allow: / Disallow: /api/ ``` --- ## F-009 — No .well-known/security.txt vulnerability disclosure policy **Score:** 2.0 / 10 | **Severity:** INFO | **Status:** Candidate ### Description No `.well-known/security.txt` file exists at the domain root. This is a policy gap — legitimate security researchers have no standardized way to report vulnerabilities. ### Evidence ```bash $ curl -sI https://vienotaiszemesregistrs.tech/.well-known/security.txt HTTP/2 404 # Not found ``` ### Impact No direct security risk, but violates responsible disclosure best practices and could delay coordinated vulnerability remediation. ### Remediation (Candidate — not a risk itself) Deploy `https://vienotaiszemesregistrs.tech/.well-known/security.txt`: ``` Contact: mailto:security@example.lv Preferred-Languages: lv, en Canonical: https://vienotaiszemesregistrs.tech/.well-known/security.txt Policy: https://vienotaiszemesregistrs.tech/security-policy Hiring: https://vienotaiszemesregistrs.tech/careers ``` --- ## F-010 — No Content-Security-Policy on success responses (error pages only add it) **Score:** 6.0 / 10 | **Severity:** MEDIUM | **Status:** Candidate ### Description The main SPA HTML response (`/hakatons/`) does not include a `Content-Security-Policy` header. A CSP is present on error pages (e.g., 500 responses) as `default-src 'none'`, but it is absent where it matters most — the page that loads the application JavaScript and CSS bundles. ### Evidence ```bash # On success SPA page: $ curl -sI https://vienotaiszemesregistrs.tech/hakatons/ | grep -i csp # RESULT: NOTHING — CSP absent on the main SPA response # Only present on 500 error pages: HTTP/2 500 content-security-policy: default-src 'none' ``` ### Impact - Without CSP, browser-side execution is unconstrained against injected scripts - No protection against XSS or data exfiltration via inline scripts - The main issue is that the CSP string seen on error pages (`default-src 'none'`) would break all application functionality if applied to the SPA — indicating a deployment/configuration error rather than intentional restriction ### Remediation In Caddy config, deploy CSP for SPA: ``` header Content-Security-Policy "default-src 'self'; script-src 'self' https://fonts.googleapis.com https://fonts.gstatic.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; img-src 'self' data: https:; font-src 'self' https://fonts.gstatic.com; connect-src 'self'; frame-ancestors 'none'" ``` --- ## F-011 — Frontend JS hardcodes calls to 14+ external government APIs (SSRF risk if backend exposed) **Score:** 5.0 / 10 | **Severity:** MEDIUM | **Status:** Candidate ### Description Static analysis of the SPA bundle (`/tmp/vzr_app.js`, 343 KB) reveals hardcoded URLs to numerous Latvian government services. These include internal network endpoints that could be resolved locally, such as `localhost` and private IP ranges if SSRF is not mitigated in backend. ### Evidence External data sources from Express server (API routes): - `https://www.data.gov.lv/pakalpojumi/ur-reistrs/` — UR registrs data - `https://ozols.gov.lv/rest/services` — Cadastre survey (ArcGIS REST) - `https://tapis.gov.lv/GeoServer/wms` — Construction info (WMS) - Multiple VID (Nodokļu departaments) endpoints at `www6.vid.gov.lv` Frontend direct-to-external hardcoded URLs from static bundle analysis: ```javascript // Found in /tmp/vzr_app.js: "geolatvija.lv/geo/query" // Geographic queries "geolatvija.lv/main/viewer" // Map visualization "mantojums.lv/cultural-objects" // Heritage objects "tapis.gov.lv/bisp/lv/Sync/FindConstructs" // Planned constructions "zemesgramata.lv/lv/Search/*" // Land parcel data "server.arcgisonline.com/ArcGIS/rest/" // Imagery tiles "tile.openstreetmap.org/{z}/{x}/{y}" // Map tiles "ekii.lv/..." // Environmental info "liaa.business.gov.lv/..." // Rural development data "cfla.gov.lv/..." // Consumer protection "www.eis.gov.lv/..." // EIS portal integration "lursoft.lv/..." // Commercial registry ``` ### Impact (Candidate — requires backend code access to confirm) - If backend proxy pattern is used for these calls, SSRF could redirect requests to internal services - Multiple data sources increase the attack surface for data leakage - Without CORS restrictions (F-001), direct frontend-to-backend SSRF via API proxy is possible ### Remediation (Candidate — requires backend access) 1. Implement URL validation on all server-side fetches 2. Block responses from private IP ranges (RFC 1918) 3. Validate redirect chains for localhost/internal network targets 4. Use allowlisted domains list with explicit version/host verification --- ## F-012 — No Content-Security-Policy nonce/hash on `type="module"` scripts **Score:** 5.5 / 10 | **Severity:** MEDIUM | **Status:** Candidate ### Description The SPA HTML references its application JavaScript bundle using ` ``` Note: `crossorigin` is present but no `nonce` attribute exists. When CSP is deployed, this will need a dynamic server nonce or integrity hash. ### Impact (Candidate — theoretical until CSP is deployed) - Once CSP is configured with strict `script-src`, all application functionality will break - Module scripts cannot use `'unsafe-inline'` or bare domains as CSP values - Without nonces per-request or subresource integrity hashes, dynamic injection becomes the only viable option ### Remediation (Candidate — requires CSP deployment) Option A — Server-side nonce: ```caddyfile # Caddy: add nonce to index.html response header Content-Security-Policy "script-src 'nonce-$(random-nonce)'" # Or via middleware that injects nonces into HTML ``` Option B — Subresource integrity with file-based hashes: ```html ``` --- ## Summary of Evidence Files | File | Size | Description | |------|------|-------------| | `/tmp/vzr_headers.txt` | 346 bytes | Raw HTTP headers from `/hakatons/` GET response (13 header lines) | | `/tmp/vzr_body.txt` | 1,007 bytes | SPA HTML body — no CSP, no security headers present in head block | | `/tmp/vzr_app.js` | 343,852 bytes | Downloaded JavaScript bundle for static analysis (14+ gov API URLs) |