Skip to content

Audit roadmap

Martynas Jusevičius edited this page Jul 12, 2026 · 2 revisions

LinkedDataHub Audit & Roadmap (v5.5.4)

Context

Full audit of LDH's codebase and dependencies, evaluation of the product feature set and UX, and a prioritized future-direction brainstorm — standalone and combined with the sibling stack (../Web-Algebra, ../REST-VKG, ../AutoGraph). Findings were gathered by parallel codebase explorations, spot-verified against pom.xml, Application.java, URLValidator.java, Item.java, and layout.xsl, and cross-referenced against the project wiki — which already documents a Roadmap, a Radically Open Security penetration-test report, and detailed specs for graph versioning, the v6 XHTML+RDFa format, and a PATCH refactor. This document reconciles the independent audit with those existing plans rather than duplicating them.

Already decided / in progress (excluded from priorities below):

  • com.atomgraph:client upgrade to 5.x — done. (Removes the former top P0 and the jakarta.json/slf4j exclusion debt; unblocks upstreaming generic XSLT to Web-Client, item P2.6.)

Part 1 — Codebase & Dependency Audit

Overall health: good, with concentrated debt

Modern foundation: Java 21, Jena 6.1.0, Jersey 3.1.11 (Jakarta), Tomcat 10.1.52 base image, non-root container, Docker secrets, healthchecks. v5.5.4 shows active hardening (HTTP client timeouts, SignUp response-leak fixes), and several 2025 pen-test findings are already remediated in-code (below).

Security: reconciling the pen-test report against current code

The wiki's Penetration test report (Radically Open Security, Oct 2025, target v5.0.23) found 6 issues. Reconciled against the current v5.5.4 source:

ID Finding Sev Status in v5.5.4 (evidence)
LNK-011 Stored XSS via file-upload MIME type High Fixed. Item.java:188,199 sets Content-Security-Policy: default-src 'none'; sandbox (explicit // LNK-011 fix)
LNK-005 DoS via XML entity expansion (Billion Laughs) High Likely fixed on the RDF pathBillionLaughsTest exists in Core's io. But XSLTMasterUpdater's DocumentBuilder is a separate, un-hardened parser → P0.1 below
LNK-003 SSRF via ?uri= fetching arbitrary URLs Elevated Partially fixed. URLValidator blocks link-local/site-local; DNS-rebinding residual remains → P0.2
LNK-009 SSRF → admin Fuseki (fuseki-admin:3030), ACL bypass Elevated Direct path closed (site-local now blocked) but loopback is deliberately NOT blocked (URLValidator.java:58,81) → P0.2
LNK-004 SSRF via unverified On-Behalf-Of header Low Fixed. URLValidator.validate() now called on the On-Behalf-Of URI in WebIDFilter
LNK-002 SSRF in admin /transform (dct:source, spin:query) Low Open. Transform.java/Add.java/Generate.java still fetch server-side → closed by the PATCH refactor (P1.5)

Other audit findings (severity order)

Dependencies

  • java-jwt 3.19.4 (2021, EOL major line) sits on the authentication path (Google OAuth2, ORCID OIDC).
  • Guava 31.1-jre (2021; CVE-2023-2976 in FileBackedOutputStream, likely unused — bump is free).
  • No high-CVE-rate libs in LDH itself; NF-001 in the pen test (Fuseki CVE-2023-32200) is mitigated by the Graal-less Java 21 install.

Architecture debt

  • Application.java is a 2,389-line god class with a ~36-param constructor (config parsing, HTTP client factory, SSL/keystore assembly, three ExpiringMap caches, XSLT setup). This is why it has zero unit tests.
  • Heavyweights: DocumentHierarchyGraphStoreImpl (1,107), LoginBase (689), SignUp (592), AuthorizationFilter (450).
  • NoopHostnameVerifier on internal clients (Application.java ~1629/~1736) — sound only while the cert-pinned SSLContext invariant holds; nothing guards it.
  • WebID model cache TTL hardcoded to 1 day (Application.java:297, literal TO-DO) — a revoked WebID stays valid up to 24h.
  • eval in platform/entrypoint.sh over env-derived strings.

Testing

  • ~6 unit test files. The security-critical 20% (auth filters, JWT, AuthorizationFilter) has ~0% unit coverage; the ~193 shell-based http-tests/ give solid integration coverage but slow feedback and weak negative-case reach.

Part 2 — Product Features & UX Evaluation

Strong

Uniquely coherent data-driven model (document = named graph; apps/UI/ontologies/ACLs all RDF; SPIN constructors + SHACL forms; multi-dataspace via subdomains; Linked Data proxy with client-side rendering). Rich view modes (List/Table/Grid/Chart/Map/3D Graph), CSV/RDF imports, HTML+JSON-LD scraping, content-addressed uploads, saved SPARQL queries, package system, WebID + OAuth2 + ORCID auth, 24 CLI scripts. The shared XSLT 3.0 SSR/CSR pipeline via Saxon-JS is genuinely differentiated.

Weak

  • Bootstrap 2.3.2 (2013) — biggest UX liability. Viewport meta is present (layout.xsl:296) but the fixed grid means no real mobile experience. (The v6 track dissolves this — see P2.1.)
  • No versioning/history/undo/audit — one root cause, three user-facing gaps (wiki has a full spec — P2.2).
  • Search is label/regex-only — no full-text over document body (wiki Roadmap flags this too — P1.4).
  • No batch operations, provenance UI, event/notification layer, or machine-readable agent capability description.
  • 47.5k lines of XSLT in 39 files (view.xsl 2,327; modal.xsl 1,861; form.xsl 1,746) — some generic, belongs upstream in Web-Client.

Alignment with the wiki Roadmap

The independent audit converged on much of the existing wiki Roadmap (validating the direction): full-text search, inline RDF editing, HTML+RDFa editor, client-side proxy removal, package support, document versioning (Memento), multi-language UI, map/graph refinements, XSLT 3.0 package refactoring. Items the audit adds emphasis to that are under-weighted in the wiki Roadmap: agent-grade auth, machine-readable self-description for LLM harnesses, Application.java decomposition, and the pen-test remediation backlog.


Part 3 — Strategic Position & Future Direction

The defensible position in 2026

Every major agent harness locks up agent memory in opaque, non-portable stores. LDH + Web-Algebra + REST-VKG is the only stack where all four hold at once, composed on W3C standards (unforgeable by lock-in vendors):

  1. Open, queryable, ACL'd agent memory (LDH — typed RDF under WebID/WAC, portable across models/harnesses);
  2. Declarative, inspectable agent plans (Web-Algebra JSON ops — auditable before/after);
  3. Live user data queryable in place, zero-copy (REST-VKG — Gmail/Calendar/GitHub/Workday as virtual graphs);
  4. A real human UI over the same substrate (LDH rendering — human-in-the-loop is not a bolt-on).

Solid has (1) without (3)/(4); agent frameworks have neither (1) nor (4); enterprise platforms have (3)+(4) without openness. Strongest tailwind: EU digital-sovereignty policy (EHDS, GAIA-X, Data Act).

Product directions, ranked by fit to existing code

# Direction Code fit Minimal compelling demo
D3 Agentic BI / instant portals — NL → live dashboard via ldh-GeneratePortal, ldh-AddResultSetChart, ldh-AddView over any SPARQL endpoint or VKG Strong, underexploited "Build a portal over our Workday org data" → live LDH dashboard in <1 min, no ETL
D2 MCP-native knowledge platform — LDH as persistent, shared, versioned memory for any harness via Web-Algebra's MCP server Strong — MCP server + LDH ops exist Claude told "remember X" → typed ACL'd document; a different harness reads it back
D1 Personal Dataspace / open agent memory — AutoGraph productized Working PoC today docker compose up → OAuth once → morning brief in Signal, backed by browsable LDH dataspace
D4 Enterprise VKG platform — REST-VKG + LDH as governance/catalog UI Good on read; gaps: per-user OAuth brokering, write-VKG, audit Federated Workday×Gmail×GitHub query with per-graph ACLs
D5 FAIR research data platform Adequate; slow grant-driven sales Keep as an application story of D1's features

Recommended sequencing: D3 → D2 → D1. D3 is the gasp-demo this quarter with near-zero new code; D2 rides the MCP wave and makes the stack discoverable to every agent developer; D1 is the destination product but needs versioning/provenance and onboarding first. D4 monetizes what D2/D3 prove.


Part 4 — Prioritized Recommendations

The former top P0 (client 5.x upgrade) is done. Remaining P0 is the open pen-test remediation.

P0 — Security remediation (Unreleased security branch; closes open pen-test findings)

# Action Effort Status
P0.1 XXE-harden XML parsing: new SecureXML helper; XSLTMasterUpdater parses with DTDs/external entities disabled; ldh:send-request (SendHTTPRequest) parses external responses with secure processing + external entities off. Closes residual of LNK-005 S ✅ done
P0.2 Block loopback + wildcard in URLValidator, check all resolved addresses (DNS-rebinding window narrowed). Closes residual of LNK-003/LNK-009; URLValidatorTest extended; ALLOW_INTERNAL_URLS kept as dev escape hatch S–M ✅ done
P0.3 Upgrade java-jwt 3.19.4 → 4.5.2; fixed the one breaking call (IDTokenFilterBase TokenExpiredException now takes an Instant); added JWKS-based JWTVerifierTest (6 cases) S–M ✅ done
P0.4 Remove eval from entrypoint.sh S ⏸ deferred — needs container-startup testing; low/theoretical severity (not an ROS finding); the eval'd *_PARAM strings are script-constructed, not attacker-supplied. Guava → 33.6.0 already shipped in 5.6.0
P0.5 Document the NoopHostnameVerifier pinned-truststore invariant at both socket-factory sites S ✅ done (comment). Truststore-pinning unit test deferred to P1.1 — the god-class blocks a clean test seam
Request a retest from Radically Open Security once this branch ships (report recommends it). pending release

P1 — Near-term wins (weeks)

# Action Effort
P1.1 Decompose Application.java: extract ApplicationConfig, HttpClientFactory, CacheRegistry, XSLT setup; wire via Jersey AbstractBinder. Stage it (caches + client factory first) L (staged)
P1.2 Unit tests for auth + proxy decision logic (AuthorizationFilter, WebID validation, JWT, proxy edge cases) M
P1.3 Configurable cache TTLs (Application.java:297) following the v5.5.4 CLIENT_* env-var pattern S
P1.4 Real full-text search behind the existing search modal: enable jena-text (Lucene) in the Fuseki assembler; index labels + ldh:content. Internal implementation detail — public SPARQL contract unchanged. (Wiki Roadmap item; supersedes the regex search) M
P1.5 Replace /add, /transform, /generate with client-orchestrated graph PATCH (per the wiki proposal). Double win: eliminates the LNK-002 server-side SSRF surface and collapses 3 endpoints into standard per-graph PATCH — squarely on the declarative/standards values. Bounded by SaxonJS XML-only parsing (RDF/XML or format-converting proxy); ship in the wiki's 4 steps (/add/transform/generate → cleanup) M
P1.6 AGENTS.md / machine-readable self-description for LDH (conventions, PATCH write discipline, read-only SPARQL, auth), mirroring REST-VKG's per-service pattern. Cheapest multiplier for the whole agent story S–M
P1.7 Renovate/Dependabot + dependency-check-maven; nginx limit_req on /sparql, auth, proxy paths (declarative, zero app code) S
P1.8 Mobile triage, not rewrite: responsive override CSS for navbar/modals/forms. Defer Bootstrap 2→5 — the v6 track replaces the rendering layer anyway; don't pay twice S–M
P1.9 Batch operations UI: multi-select in container List/Table → batch delete/move/type-assign as client-composed PATCH/DELETE. Directly relieves AutoGraph's curate-hundreds-of-imports pain M

P2 — Strategic, standalone LDH (months)

# Action Effort
P2.1 v6: XHTML+RDFa as canonical format (per the wiki spec — ldh:content rdf:XMLLiteral, RDFa→SPARQL-UPDATE index, CSS from @typeof via ldh:FrameworkClass mode, xsl/ldh/ replacing bs2:). Ship in the spec's Phases A–E (A: uncomment @typeof/add @property, non-breaking → B: XHTMLRDFaReader for application/xhtml+xml → C: ldh:FrameworkClass client-side → D: RDFa-Editor integration → E: framework-agnostic xsl/ldh/, remove Bootstrap + v5 migration XSLT). Fold in the ../RDFa-Editor sibling as the editing surface. Gate with http-tests asserting round-trip graph equivalence. Open items to decide (spec omits): transitional back-compat, IXSL client-side perf, home for non-RDFa-expressible system metadata L (multi-quarter)
P2.2 Git-backed graph versioning + Memento (per the wiki Implementation Plan): VersioningFilter (async, post-response) → GraphVersioningService (fixed 4-thread pool, N-Triples serialization, per-path SHA conflict detection) → lightweight Jersey GitHubClient (4 endpoints, no new deps) → Memento TimeGate/TimeMap. Config via doap:GitRepository in system.trig; token from GITHUB_TOKEN env, never RDF. Closes history + undo + audit gaps. Wiki effort: ~2 wks MVP, 6–7 wks full. Pairs naturally with P2.1 (git-versioning XHTML) L
P2.3 PROV-O sidecar emission on writes (server-side response filter → per-dataspace provenance graph; agent WebID, timestamp, method, git SHA from P2.2). Server-side = every client gets provenance free. Per-dataspace opt-in via settings M
P2.4 History/provenance UI: per-document History tab (Memento timemap → diff), per-block "who/when/from-what". After P2.2/P2.3 M
P2.5 Vector retrieval as a SPARQL SERVICE: finish ../RdfVectorIndex as a standalone endpoint federated via SERVICE — completes hybrid retrieval (structural + text:query + vector) without touching LDH's endpoint contract M–L
P2.6 Upstream generic XSLT to Web-Client 5.x (now unblocked by the client upgrade); keep LDH's XSLT strictly domain-specific L (incremental)

P3 — Combined-stack integration backlog

# Action Effort
I1 One-artifact composed stack: top-level compose/Helm bundling LDH + REST-VKG + Web-Algebra (HTTP/MCP) + RdfVectorIndex, shared network/secrets. Align versions first (AutoGraph pins LDH 5.5.3) M
I2 Agent-grade auth in LDH: scoped bearer tokens (Solid-OIDC-style) mapped to agent WebIDs with WAC intact + polished WebID delegation. Every P3 direction hits this wall. After P1.1/P1.2 L
I3 Workflow registry as graph entities: Web-Algebra workflows stored as LDH documents (schema:Action + payload), each run emitting prov:Activity (needs P2.3) M
I4 Event/trigger layer, hypermedia-style: Linked Data Notifications / WebSub on LDH writes — enables event-driven Web-Algebra schedules and keeps the vector index fresh. Also fixes federation cache coherence (Varnish ?uri= TTL-only staleness) and cross-instance versioning notification (Federation evaluation C4/C5). The only genuinely new server surface; design once, four consumers M–L
I5 Write-VKG (inverse GRDDL) in REST-VKG: XSLT 3.0 JSON-output transforms so agents write to Gmail/Calendar through the same RDF interface they read — closes the open-memory loop L (per service)

Federation follow-ups (from [Federation evaluation](Federation evaluation) §6)

Items not already covered above (P1.5 = uniformity convergence; I2 = identity groundwork; I4 = change propagation):

# Action Effort
F1 Automate acl:delegates enrollment: a flow that adds a node's secretary to the user's WebID profile — itself a PATCH on the profile document via the uniform interface, so the trust topology bootstraps through the same write path (profile-settings affordance in XSLT; no new server surface, no new vocabulary). Extends I2. Separately: decide cross-operator ID-token delegation — deprecate by gating the IDTokenDelegationFilter registration (ProxyRequestFilter.java:224-226) per the code's own warning (IDTokenDelegationFilter.java:30), or adopt RFC 8693 token exchange (with I2) M
F2 Remote-edit UX — implemented (client-only). Investigation showed render-time gating already consumed forwarded remote acl:mode Link headers via the window-global LinkedDataHub.acl-modes; the real defect was that global going stale on fetch-less tab switches (ldh:TabSwitch) — wrong edit affordances when panes face different nodes. Fix: modes are per-pane state like data-base/data-endpointbs2:TabBody stamps data-acl-modes (document.xsl), SSR pane stamped from server-side acl:mode() (layout.xsl), create/reuse paths stamp from the response Link headers, and ldh:ActivateTab syncs the window flags from the activated pane via the new ldh:SetAclModes template (client.xsl). acl:mode() and all consumers unchanged. Write transport needed no work: proxy forwards method + entity (ProxyRequestFilter.java:238-241) and every client write path is ldh:href()-wrapped (form.xsl:226,252,728,1515) done
F3 VoID/DCAT dataspace self-description so dataset boundaries are discoverable in-band — required for cross-node search, not for follow-your-nose browsing. Derivable from dataspaces.trig + service metadata (seeded documents, or a small resource class); advertise via the registered describedby link relation S–M
F4 Topology decision (Federation evaluation C8): hybrid proxy — client-side loading for public reads, proxy for authenticated traffic — relieves the proxy chokepoint without reopening the credential problem the no-CORS design solves. Code shape: CORS response filter emitting headers only for publicly readable responses + a direct-fetch-with-proxy-fallback branch in the client loading path. Refines the wiki Roadmap's "proxy redesign" item; the DDoS history is the evidence base for where the line goes M
F5 Guard rails + docs: CI check pinning the single-ldh:href()-chokepoint invariant (the location-transparency argument rests on it, Federation evaluation §4.5); when a proxied response carries no sd:endpoint Link, disable query-dependent blocks instead of silently querying the local endpoint about remote data (C3); multi-node deployment guidance S

Overall sequencing

Now:            P0.1–P0.5 (close open pen-test findings) → retest
This quarter:   P1.1 / P1.2 / P1.5 / P1.6  +  D3 demo (agentic portals — near-zero new code)
Next 2 qtrs:    P2.2 + P2.3  ∥  I1 / I2   → D2 launch (MCP-native memory)
Background arc: P2.1 (v6 XHTML+RDFa, Phases A–E) — absorbs Bootstrap debt as generated CSS
Then:           I3 / I4 / I5 → D1 as a nameable product (personal dataspace)
Federation arc: F2 done → F1 with I2 → F4 with the proxy redesign → F3 / F5 opportunistic

Verification

  • P0.1/P0.2/P0.3/P0.5: extend existing unit tests (URLValidatorTest, new JWT tests); mvn test. Full http-tests/ via ./run.sh ssl/owner/cert.pem [pw] ssl/secretary/cert.pem [pw]. Then request an ROS retest.
  • P1.4: index a body-only-text document, confirm the search modal finds it.
  • P1.5: confirm /add,/transform,/generate removal + client PATCH round-trips via http-tests; verify no server-side fetch remains.
  • D3 demo: Web-Algebra ldh-GeneratePortal against a VKG endpoint; confirm dashboard renders in LDH.
  • P2.1: http-tests asserting graph equivalence between stored XHTML+RDFa extraction and the current RDF write path (spec's round-trip fidelity check).
  • F2: mvn clean package (recompiles the edited XSLT to SEF); then with the stack up: open a writable local document and a remote document (?uri=) in two tabs, switch back and forth — edit affordances (row-hover controls, drag-drop, Edit/Delete disabled state) must track the active pane, not the last-loaded document. Against a remote LDH node with acl:delegates wired, edit controls appear on the remote document and PATCH round-trips through the proxy.

Clone this wiki locally