This document registers identified improvement opportunities, loopholes, and scope for enhancement in the classification mechanism of the UNSDG Classifier Tool. It is intended to guide future contributions and refactoring efforts.
- Location:
backend/services/repo_fetcher.py:54-143(_sanitise_url) - Issue: The function validated URL shape (scheme, hostname presence) but did not
validate the destination. It would happily route to internal/private hosts such as
http://localhost/...,http://127.0.0.1/..., or RFC1918 addresses (10.x.x.x,172.16-31.x.x,192.168.x.x). - Risk: Server-side request forgery — a malicious user could force the server to make outbound requests to internal services, port scan internal networks, or exfiltrate metadata.
- Fix: Added host validation in
_sanitise_urlto reject:localhost- RFC1918 private IP ranges:
10.x.x.x,172.16-31.x.x,192.168.x.x - Loopback
127.x.x.xThe function returnsInvalidURLErrorfor any of these hosts.
- Status: PARTIAL, not closed. The check is a dotted-quad string comparison, so every
non-decimal encoding of the same address still passes. Verified against the current code:
169.254.169.254(cloud metadata — the highest-value SSRF target, and reachable from EC2),2130706433,0x7f000001,127.1,[::1], andredis.internalare all allowed. A complete two-layer guard (_is_public_ip/_assert_public_host, plus 51 tests) exists on the localconfigbranch in commitc27ec5abut was never pushed — PR #72 merged only throughcb4a84a. Rescue it before deploying to a public address.
- Location:
backend/sdg_constants.py:218-220 - Issue: The list
SDGs = ["SDG 1", "SDG 2", "SDG 3"]at the bottom of the file appears to be leftover dead code from development. It is not referenced anywhere else in the codebase. - Fix:
Remove the dead code— DONE, removed with the service merge.
- Location:
backend/sdg_constants.pymultiple references - Issue: Multiple SDG label representations exist with inconsistent formats:
SDG_LABELS_DICT: keys"1"-"17"mapping to full names (used by Aurora API)SDG_NAMES: full"SDG 1: End poverty..."strings (used by embedding models)SDG_LABELS: goal description strings (used by similarity scoring)SDG_DESCS: descriptive strings inbackend/sdg_constants.py(the duplicate in the retiredmodels/similarities.pyis gone — the two copies had already diverged)
- Risk: Confusion about which format to use where, potential mapping bugs when switching between models, and duplicated description text that diverges over time.
- Fix: Establish a single canonical SDG label format and convert/alias others to it. Consider removing redundant representations or adding clear conversion functions.
- Location:
backend/app.py:67(Aurora:> 0.4)backend/embedding_url.py:176(sc >= thresholdwith default0.5)backend/embedding_url.py:188(main()usesthreshold=0.4)
- Issue: thresholds are hardcoded with no configuration mechanism. Different use cases may require different thresholds, and there's no way to tune them without code changes.
- Fix: Extract thresholds to a configuration file or environment variables. Support per-endpoint override via API parameters.
- Location:
backend/aurora_api.py:22 - Issue: The comment notes
response.raise_for_status()is intentionally NOT called because the Aurora API can return HTTP 200 with an"error"key in the body. However, the code manually checks for this, which is correct but easy to miss during future maintenance. - Fix: Add a clear docstring explaining why
raise_for_status()is omitted, and add a unit test verifying the "HTTP 200 with error payload" fallback path.
- Location:
backend/embedding_url.py:176-177 - Issue:
classify_reposelects only predictions wheresc >= threshold. If nothing matches, the result has an emptypredictionslist — the caller gets no indication whether the text was classified at all, or if the result is simply "none of the above". - Fix: Add a fallback that returns the top-N ranked SDGs even if below threshold, or add a "no relevant SDG" signal to the response. Consider adding a secondary low-confidence band (e.g., 0.2) that surfaces the closest match with a "Low" confidence tag.
- Location:
backend/embedding_url.py(zero_shot_scores) - Issue: The zero-shot model called
http://localhost:9010/predict, requiring a second service. If it was down, every classification failed. - Resolution: The service was merged into the backend; inference now runs in-process via
services/inference.py, so there is no hop to fail. The HTTP path is retained behindMODEL_SERVICE_URL(unset by default) so the model can move back to its own host for GPU or independent scaling.
- Location:
backend/services/repo_fetcher.py:647-676(fetch_wiki_home) - Issue: The code has a deprecation note that Bitbucket wikis will be removed on 2026-08-20,
but the method still functions and is called silently in
fetch_readme. After that date, it will start returning 404/empty for all repos. - Fix: Add a runtime check near the deprecation date, or feature-flag the method behind a config. Consider removing wiki fallback entirely since wikis are being deprecated.
- Location:
backend/aurora_api.py:91-107 - Issue: The Aurora API client catches
RequestExceptionand genericException, but the error response format is vague ("Aurora API processing failed") without distinguishing between network errors, API downtime, or malformed input. - Fix: Map specific error types to appropriate HTTP status codes (similar to how
app.pymaps repo_fetcher errors) and include more descriptive error codes in the response.
- Location: Multiple locations (
app.py:67,embedding_url.py:176,api.tsfrontend) - Issue: Predictions are returned as
{"SDG name": score}with no confidence classification. The UI has no way to distinguish "High/Medium/Low" confidence based on score ranges. - Fix: Add confidence banding (e.g., High: 0.7-1.0, Medium: 0.4-0.69, Low: 0.1-0.3) and include the band in both API responses and frontend display.
- Location:
backend/services/repo_fetcher.py:702-737(_rewrite_github_pages) - Issue: The function only handles
github.comandwww.github.iodomains. Other GitHub enterprise instances or custom Pages hosts are not supported. - Fix: Make the GitHub Pages rewrite domain-aware or add a configuration for custom GitHub Enterprise hosts.
- Location:
backend/app.py:39-161 - Issue: The status code mapping (
InvalidURLError→400,RepositoryNotFoundError→404,RateLimitError→429,FetchError→502) is documented in TESTING.md but not explicitly tested. A silent route change could break frontend expectations. - Fix: Add integration tests in
backend/tests/that assert each error maps to the correct status code, preventing regressions during refactors.
-
SSRF Protection: Implement host validation in
_sanitise_urlto reject private/internal addresses. This is the most critical security fix. -
Unified SDG Label Format: Establish one canonical SDG label representation across the codebase and add conversion functions between formats. Remove or document duplicates.
-
Configurable Thresholds: Extract classification thresholds to a config file or environment variables. Support API-level override.
-
Best-Effort Fallback: When no SDGs exceed the threshold, return the top-N ranked SDGs with a "Low" confidence indicator, rather than an empty predictions list.
-
Aurora API Robustness: Add better error categorization and HTTP status code mapping for the Aurora API client.
-
Confidence Banding: Add High/Medium/Low confidence bands based on score ranges and propagate them through the API and frontend.
-
Ensemble Alpha Configuration: Make the ensemble alpha (currently 0.3) configurable via environment variable or API parameter.
-
Microservice Failover— moot: inference runs in-process (see §7). -
Bitbucket Wiki Deprecation Handling: Add runtime check or config flag for the Bitbucket wiki deprecation date (2026-08-20).
-
Explicit Error Status Code Tests: Add integration tests verifying each error type maps to the correct HTTP status code in
app.py.
-
GitHub Enterprise Support: Extend
_rewrite_github_pagesand domain map to support GitHub Enterprise Server instances. -
Rate-Limit-Aware Retries: Add exponential backoff and retry logic for repository fetching when rate limits are hit.
-
Private Repository Token Flow: Improve token handling so authenticated access to private repos works seamlessly across all supported hosts.
-
Prediction Reason Codes: Add a
reasonfield to predictions indicating why a particular SDG was selected (e.g., "keyword_match", "topic_match", "description_match"). -
End-to-End Test Suite: Build an automated end-to-end test that submits known repositories and validates plausible SDG output, suitable for nightly CI runs.
-
Multi-Label Correlation Analysis: Analyze SDG prediction correlations to identify which text signals most strongly correlate with specific goals, enabling better prompt engineering and text preprocessing.
-
Language Diversity Improvement: The summariser forces English output, but the underlying models may benefit from multilingual input. Research whether keeping non-English signals (translated) improves classification accuracy.
-
Ensemble Weight Tuning: Research optimal
alphavalues for the zero-shot + cosine similarity ensemble. Consider data-driven tuning based on validation set performance. -
Aurora API Integration: Deeper integration with the Aurora SDG API, including bidirectional mapping between Aurora's SDG codes and the tool's internal SDG labels.
-
Model Version Pinning: Add revision pinning / checksum validation for the
GE-Lab/SDGs-classifiercheckpoint and the sentence-transformer weights so results stay reproducible.hf_hub_downloadcurrently resolves the default branch with norevision.
The classification mechanism is functional but has several areas where robustness, security, and maintainability can be improved. The highest-impact changes are:
- SSRF protection (security critical — still open, see §1; fix exists unpushed on
config) - Unified SDG label format (developer ergonomics)
- Configurable thresholds (usability)
- Best-effort fallback when no goals match (user experience)
These improvements should be addressed in priority order, starting with the SSRF fix and dead code removal, followed by configuration and fallback enhancements.