You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
scanCsapiLinks() stores server-returned href values directly into the resourceUrls map without validating the URL scheme. A malicious or misconfigured server returning href: "javascript:...", href: "data:...", or href: "//evil.com/systems" would cause the library to construct all CSAPI query URLs against those dangerous bases.
scanCsapiLinks() in src/ogc-api/csapi/helpers.ts (lines 129–171) scans server-returned link objects for CSAPI resource URLs using three conventions (ogc-cs: prefix, plain resource name, items with resource href). At each of the three storage points, the href value is placed directly into the result map without any URL scheme validation:
Affected code — scanCsapiLinks() (lines 129–171):
exportfunctionscanCsapiLinks(links: Array<{rel?: string;href?: string}>): Map<string,string>{constresult=newMap<string,string>();// ...for(constlinkoflinks){constrel=link.rel;consthref=link.href;// ...// Convention 1: ogc-cs: prefixedconstmatch=rel.match(/^ogc-cs:(.+)$/);if(match){result.set(match[1],typeofhref==='string' ? href : '');// ← no scheme checkcontinue;}// Convention 2: plain resource nameif(knownTypes.has(rel)){result.set(rel,typeofhref==='string' ? href : '');// ← no scheme checkcontinue;}// Convention 3: rel: "items" with resource type in hrefif(rel==='items'&&typeofhref==='string'){// ...normalize...if(normalized&&knownTypes.has(normalized)){result.set(normalized,href);// ← no scheme check}}}returnresult;}
Data flow: The returned map flows directly to CSAPIQueryBuilder via factory.ts → createCSAPIBuilder(), where it becomes the base URL for all 80+ query methods via buildResourceUrl(). Every constructed URL inherits whatever scheme was in the original href.
Scenario:
// A malicious/misconfigured server returns:constlinks=[{rel: "ogc-cs:systems",href: "javascript:alert('xss')"},{rel: "datastreams",href: "data:text/html,<script>..."},{rel: "items",href: "//evil.com/observations"}];constresourceUrls=scanCsapiLinks(links);// resourceUrls.get('systems') === "javascript:alert('xss')"// This becomes the base URL for getSystems(), getSystemById(), etc.
Impact: The risk is bounded — this library constructs URLs but does not fetch them itself. The real risk surfaces in consuming applications:
SSRF if a server-side consumer follows the constructed URL
XSS if a browser consumer renders the URL in a link href or src attribute
Data exfiltration if a //evil.com protocol-relative URL is used for API calls
Relative URLs are safe — they resolve against the trusted base URL that the consumer originally provided. Only absolute non-HTTP(S) URLs are dangerous.
Conclusion: This code is ours. The file src/ogc-api/csapi/helpers.ts does not exist on upstream/main — the entire file is our contribution, with 100% sole authorship. scanCsapiLinks() was written from scratch in Phase 2.
Files to Modify
File
Action
Est. Lines
Purpose
src/ogc-api/csapi/helpers.ts
Modify
~15
Add isTrustedHref() helper; apply filter before each result.set() in scanCsapiLinks()
src/ogc-api/csapi/helpers.spec.ts
Modify
~30
Add test cases for javascript:, data:, protocol-relative, and valid HTTP(S) scheme handling
Proposed Solutions
Option A: Filter on isTrustedHref before storing (Recommended)
/** * Returns true if the href is safe to use as a resource URL base. * Relative URLs are safe (they resolve against the trusted base). * Absolute URLs must use http: or https: schemes. */functionisTrustedHref(href: string): boolean{try{consturl=newURL(href);returnurl.protocol==='https:'||url.protocol==='http:';}catch{returntrue;// relative URL — safe, resolves against trusted base}}
Applied in scanCsapiLinks():
// Convention 1: ogc-cs: prefixedif(match){constvalue=typeofhref==='string' ? href : '';if(isTrustedHref(value))result.set(match[1],value);continue;}// Convention 2: plain resource nameif(knownTypes.has(rel)){constvalue=typeofhref==='string' ? href : '';if(isTrustedHref(value))result.set(rel,value);continue;}// Convention 3: rel: "items" with resource type in hrefif(rel==='items'&&typeofhref==='string'&&isTrustedHref(href)){// ...existing normalization...}
Pros: Small, focused change. Uses the standard new URL() constructor (WHATWG URL spec) for scheme parsing. Relative URLs pass through unchanged. No behavioral change for any real-world server observed in testing (all known servers return http:// or https:// hrefs, or relative URLs). Cons: Adds one helper function and one try/catch per href. Negligible performance impact. Effort: Small | Risk: None
Option B: No change — document that consumers are responsible for trusting their servers
Add a JSDoc @security note to scanCsapiLinks() stating that consumers must trust the server they connect to.
Pros: Zero risk; trivial effort. Cons: Does NOT harden the library. The issue remains for any consumer that renders URLs from untrusted sources. Effort: Trivial | Risk: None (but attack surface remains)
Not recommended. The fix is small enough that defense-in-depth is worth the effort.
❌ Do NOT add scheme validation to buildResourceUrl() or CSAPIQueryBuilder — the check belongs at the ingestion point (scanCsapiLinks), not downstream
❌ Do NOT add scheme validation to the CSAPIQueryBuilder constructor's resourceUrls parameter — that is a consumer-supplied escape hatch; trust the consumer
❌ Do NOT reject or warn on http:// URLs — only non-HTTP(S) absolute schemes should be filtered
❌ Do NOT reject empty strings — the existing typeof href === 'string' ? href : '' fallback is a separate minor concern (empty string stored as resource URL)
Finding 005 — OgcApiEndpoint constructor accepts http:// without warning. Related upstream scheme concern — the initial URL's scheme is also unvalidated, but that is upstream code.
Precedence: OGC specifications → AI Collaboration Agreement → This issue description → Existing code → Conversational context
No scope expansion: Add the scheme check to scanCsapiLinks, nothing more
Minimal diffs: Prefer the smallest change that satisfies the acceptance criteria
Ask when unclear: If intent is ambiguous, stop and ask for clarification
Postel's Law (Phase 3 Lesson 2): The check must not reject valid server responses. All real-world servers observed in testing return http:// or https:// hrefs, or relative URLs. The filter only blocks exotic schemes (javascript:, data:, etc.) that no spec-compliant server would return.
The original finding from the senior developer code review. Authoritative source for the problem statement, proposed isTrustedHref solution (Option A), and acceptance criteria. This issue tracks the resolution of that finding.
Directly Affected Code
#
Document
What It Provides
1
src/ogc-api/csapi/helpers.ts lines 129–171
scanCsapiLinks() — the function that stores server-returned href values without scheme validation
2
src/ogc-api/csapi/factory.ts lines 58–68
createCSAPIBuilder() — primary consumer of scanCsapiLinks(), passes the map directly to CSAPIQueryBuilder
3
src/ogc-api/csapi/url_builder.ts — resourceUrls_ field (~line 142)
Private field storing the unvalidated map from scanCsapiLinks()
Existing scanCsapiLinks unit tests — 12 cases in the primary describe block, 5 additional edge cases (17 total). No current tests for scheme validation/rejection.
Same defense-in-depth category — input to URL construction is not validated/encoded. Different function and attack surface (path traversal vs scheme injection).
Machine-readable OpenAPI definition of CSAPI Part 1 REST API. Response schemas define link objects with href fields — the authoritative schema for what servers are expected to return.
Machine-readable OpenAPI definition of CSAPI Part 2 REST API. Defines link object schemas for dynamic data resources (DataStreams, Observations, ControlStreams, Commands).
REST API for geospatial features. Part 1 resources use Features API patterns — link objects and GeoJSON encoding inherited by CSAPI. Defines the link array structure that scanCsapiLinks consumes.
Official registry of link relation types (self, alternate, collection, item, next, prev). scanCsapiLinks checks rel values against known CSAPI-specific relations — this registry defines the standard ones.
Primary encoding format for CSAPI Part 1 resources. RFC 7946 itself does not define links arrays — those are added by OGC API conventions (Common/Features). Relevant as the carrier format for server responses that contain link objects processed elsewhere in the pipeline.
Documents bugs previously found and fixed in scanCsapiLinks() (query params in href, featuresOfInterest alias). Confirms "this function we wrote from scratch" ownership.
Documents URL building patterns in ogc-client — base URL strategy, query parameter assembly, resource path structure. Provides architectural context for the URL construction pipeline downstream of scanCsapiLinks, though the document itself predates that function and does not reference it by name.
Documents the QueryBuilder lifecycle, state management, and caching in the upstream library. Provides the design context for how CSAPIQueryBuilder manages state, including the resourceUrls map populated by scanCsapiLinks. The document does not reference scanCsapiLinks by name.
Documents error handling patterns in ogc-client — error classes, validation errors, missing resource errors. Provides design guidance for how the scheme validation fix should handle rejected hrefs (silent skip vs error throw). The document does not reference scanCsapiLinks or isTrustedHref by name.
Defines when to reuse upstream utilities vs duplicate for CSAPI. Establishes the isolation principle — new helpers like the proposed isTrustedHref should be CSAPI-internal per the isolation constraint. The document does not reference scanCsapiLinks or isTrustedHref by name.
Primary blueprint for CSAPI implementation. Documents the EDR factory method pattern (endpoint.edr() → EDRQueryBuilder) that our createCSAPIBuilder() follows. The document does not mention scanCsapiLinks or createCSAPIBuilder by name — those are CSAPI-specific functions that follow the patterns documented here.
Documents code changes for CSAPI integration into endpoint.ts, info.ts, and index.ts. Describes the factory method → CSAPIQueryBuilder flow following the EDR pattern. The document does not mention factory.ts or scanCsapiLinks — those are implementation details that emerged during development.
Documents consistent architectural patterns in ogc-client — endpoint extension, conformance checking, caching, lazy loading. Provides the architectural context within which scanCsapiLinks operates, though the document does not mention it by name.
Architecture choices for 9 CSAPI resource types within ogc-client patterns. All resource types have their endpoint URLs resolved via the same link scanning mechanism, though this document does not reference scanCsapiLinks by name.
Documents format negotiation patterns (Accept header, query parameter, link-based discovery) for CSAPI resources. Notes that CSAPI does not need link-based format discovery. Relevant as background context — format requests are issued against URLs whose base originates from scanCsapiLinks output, though the document does not reference scanCsapiLinks by name.
High-level design strategy covering integration approach, format abstraction, and architectural trade-offs for the overall CSAPI module. Provides strategic context for the implementation, though it does not reference scanCsapiLinks by name.
22-entry research series for CSAPIQueryBuilder design. Includes EDR pattern analysis, QueryBuilder pattern, scope analysis. Provides the design rationale for the QueryBuilder that consumes resourceUrls, though the series does not reference scanCsapiLinks by name.
Factory method integration pattern and conformance detection research. Documents the endpoint.csapi() entry point that leads to CSAPIQueryBuilder instantiation, though the documents do not reference scanCsapiLinks by name.
Analyzes 52°North's CSAPI server — documents multi-server compatibility requirements, pagination differences, and format support variations. Contains example link objects with href values (e.g., pagination links), providing real-world evidence of server response patterns.
Comprehensive analysis of the primary CSAPI reference implementation. Contains example responses showing relative hrefs (e.g., /api/systems/abc123), demonstrating the server's convention of returning relative URLs. Provides real-world evidence of what href values servers actually return.
Documents 5 resource types, 70+ operations, 30+ query parameters — all of which construct URLs on top of scanCsapiLinks output. Defines the breadth of impact if a malicious href propagates.
Documents DataStreams, Observations, ControlStreams, Commands — all dynamic data resources whose URLs build on scanCsapiLinks output via buildResourceUrl().
Catalogs ALL query parameters from Part 1 and Part 2 — every parameter is appended to base URLs from scanCsapiLinks. Defines the full downstream impact surface.
Identifies gaps and lessons from the first implementation attempt — documents error handling improvements needed, relevant to why defense-in-depth was missing.
Mature Python client reference — documents class-per-resource architecture, URL construction patterns, and authentication abstraction in a peer CSAPI client library.
Builder pattern and request construction in Python CSAPI client. Documents client-side URL construction (base_url + path) rather than link-following — a contrasting approach to our scanCsapiLinks server-href-based URL resolution.
15 core usage scenarios, 8 common error patterns — all workflows that consume URLs built on scanCsapiLinks output. Error handling requirements inform the silent-skip behavior.
Systematic 20-plan series. Plan 12 (QueryBuilder testing strategy) and Plan 18 (error condition testing) are directly relevant to testing the scheme validation behavior.
Multi-phase testing strategy review, including notes-why-models-default-to-server-validation.md — directly relevant to the question of whether to validate/reject server-returned data or trust it.
Phase 6 code review methodology template for AI-conducted reviews. Defines the systematic review process and quality criteria used across implementation phases. This finding was produced by a separate human senior developer review, not from this AI template.
Template C — the code review issue creation template used to file this issue. Defines ownership verification workflow and severity assessment framework.
Comprehensive code audit cataloguing helpers.ts (226 lines), [P-6] POSITIVE for scanCsapiLinks normalizing featuresOfInterest alias. Did not flag scheme validation gap.
The upstream library we're extending. Provides URL handling patterns, existing OGC API utilities, and the architectural foundation that scanCsapiLinks operates within.
Primary live CSAPI server for validation — returns real http:// hrefs in link objects. Use to verify that isTrustedHref does not reject any real-world server responses.
Secondary live CSAPI server (degraded, expired SSL). Returns different href patterns than OSH. Multi-vendor validation target for ensuring the scheme filter works across implementations.
Finding
scanCsapiLinks()stores server-returnedhrefvalues directly into theresourceUrlsmap without validating the URL scheme. A malicious or misconfigured server returninghref: "javascript:...",href: "data:...", orhref: "//evil.com/systems"would cause the library to construct all CSAPI query URLs against those dangerous bases.Review Source: Senior developer code review of
clean-pr—docs/code-review/011-pending-p3-server-href-scheme-validation.mdSeverity: P3-Minor
Category: Security / Defense-in-Depth
Ownership: Ours
Problem Statement
scanCsapiLinks()insrc/ogc-api/csapi/helpers.ts(lines 129–171) scans server-returned link objects for CSAPI resource URLs using three conventions (ogc-cs:prefix, plain resource name,itemswith resource href). At each of the three storage points, thehrefvalue is placed directly into the result map without any URL scheme validation:Affected code —
scanCsapiLinks()(lines 129–171):Data flow: The returned map flows directly to
CSAPIQueryBuilderviafactory.ts→createCSAPIBuilder(), where it becomes the base URL for all 80+ query methods viabuildResourceUrl(). Every constructed URL inherits whatever scheme was in the originalhref.Scenario:
Impact: The risk is bounded — this library constructs URLs but does not fetch them itself. The real risk surfaces in consuming applications:
hreforsrcattribute//evil.comprotocol-relative URL is used for API callsRelative URLs are safe — they resolve against the trusted base URL that the consumer originally provided. Only absolute non-HTTP(S) URLs are dangerous.
Ownership Verification
Conclusion: This code is ours. The file
src/ogc-api/csapi/helpers.tsdoes not exist onupstream/main— the entire file is our contribution, with 100% sole authorship.scanCsapiLinks()was written from scratch in Phase 2.Files to Modify
src/ogc-api/csapi/helpers.tsisTrustedHref()helper; apply filter before eachresult.set()inscanCsapiLinks()src/ogc-api/csapi/helpers.spec.tsjavascript:,data:, protocol-relative, and valid HTTP(S) scheme handlingProposed Solutions
Option A: Filter on
isTrustedHrefbefore storing (Recommended)Applied in
scanCsapiLinks():Pros: Small, focused change. Uses the standard
new URL()constructor (WHATWG URL spec) for scheme parsing. Relative URLs pass through unchanged. No behavioral change for any real-world server observed in testing (all known servers returnhttp://orhttps://hrefs, or relative URLs).Cons: Adds one helper function and one
try/catchper href. Negligible performance impact.Effort: Small | Risk: None
Option B: No change — document that consumers are responsible for trusting their servers
Add a JSDoc
@securitynote toscanCsapiLinks()stating that consumers must trust the server they connect to.Pros: Zero risk; trivial effort.
Cons: Does NOT harden the library. The issue remains for any consumer that renders URLs from untrusted sources.
Effort: Trivial | Risk: None (but attack surface remains)
Not recommended. The fix is small enough that defense-in-depth is worth the effort.
Scope — What NOT to Touch
src/ogc-api/csapi/— per the upstream maintainer's isolation requirementbuildResourceUrl()orCSAPIQueryBuilder— the check belongs at the ingestion point (scanCsapiLinks), not downstreamCSAPIQueryBuilderconstructor'sresourceUrlsparameter — that is a consumer-supplied escape hatch; trust the consumerhttp://URLs — only non-HTTP(S) absolute schemes should be filteredtypeof href === 'string' ? href : ''fallback is a separate minor concern (empty string stored as resource URL)subPathencoding) — different function, different attack surfaceAcceptance Criteria
isTrustedHref()(or equivalent) validates URL scheme before storagescanCsapiLinks()skips hrefs withjavascript:,data:,ftp:, or other non-HTTP(S) absolute schemeshttp://andhttps://hrefs are stored as before//evil.com/...) are rejected (they parse as absolute with the page's scheme)javascript:rejected,data:rejected,//evil.comrejected,http://accepted,https://accepted, relative URL acceptedscanCsapiLinkstests pass unchangednpm test)npm run lint)npx prettier --checkDependencies
Blocked by: Nothing (can be done independently)
Blocks: Nothing
Coordinate with:
@link/@idresolution utilities for cross-resource reference following #110 — Cross-reference resolution utilities. If implemented, the proposedresolveResourceRefHref()would also consume server hrefs and should apply the same scheme check. Independent concerns — this issue hardens the existing ingestion point.Related:
subPathencoding (finding 004). Same defense-in-depth category but different function (buildResourceUrl()) and different attack surface (path traversal vs scheme injection).OgcApiEndpointconstructor acceptshttp://without warning. Related upstream scheme concern — the initial URL's scheme is also unvalidated, but that is upstream code.Operational Constraints
Key constraints:
scanCsapiLinks, nothing morehttp://orhttps://hrefs, or relative URLs. The filter only blocks exotic schemes (javascript:,data:, etc.) that no spec-compliant server would return.Upstream Isolation Constraint
Per the upstream maintainer's comment on PR #136:
src/ogc-api/csapimust NOT be included in the rootindex.tssrc/ogc-api/csapimust NOT import from CSAPI codesrc/ogc-api/csapi/— this fix is purely internal to the isolated CSAPI moduleOwnership-Specific Constraints
Ownership: Ours
clean-prif the PR is still openReferences
Original Code Review Finding
docs/code-review/011-pending-p3-server-href-scheme-validation.mdisTrustedHrefsolution (Option A), and acceptance criteria. This issue tracks the resolution of that finding.Directly Affected Code
src/ogc-api/csapi/helpers.tslines 129–171scanCsapiLinks()— the function that stores server-returnedhrefvalues without scheme validationsrc/ogc-api/csapi/factory.tslines 58–68createCSAPIBuilder()— primary consumer ofscanCsapiLinks(), passes the map directly toCSAPIQueryBuildersrc/ogc-api/csapi/url_builder.ts—resourceUrls_field (~line 142)scanCsapiLinks()src/ogc-api/csapi/url_builder.ts—buildResourceUrl()(~lines 256–272)resourceUrls_.get()as the URL base for all 80+ query methodssrc/ogc-api/csapi/helpers.spec.tslines 137–248 (primary), 440–484 (edge cases)scanCsapiLinksunit tests — 12 cases in the primary describe block, 5 additional edge cases (17 total). No current tests for scheme validation/rejection.Related Open Issues & Findings
subPathappended to URLs without encoding@link/@idresolution utilitiesresolveResourceRefHref()which would also consume server hrefs — future coordination point for scheme validation.OgcApiEndpointconstructor acceptshttp://without warningStandards
ogc-cs:prefix, resource type relations) from whichscanCsapiLinksextracts hrefs.hreffield in link objects originates from this spec.new URL()check relies on scheme parsing per this RFC.new URL()parses URLs in browsers and Node.js. The proposedisTrustedHrefusesnew URL()which follows this standard.hreffields — the authoritative schema for what servers are expected to return.scanCsapiLinksconsumes.self,alternate,collection,item,next,prev).scanCsapiLinkschecks rel values against known CSAPI-specific relations — this registry defines the standard ones.linksarrays — those are added by OGC API conventions (Common/Features). Relevant as the carrier format for server responses that contain link objects processed elsewhere in the pipeline.Architecture & Design
helpers.ts.scanCsapiLinks()(query params in href, featuresOfInterest alias). Confirms "this function we wrote from scratch" ownership.scanCsapiLinks()architecture and data flow from root document →scanCsapiLinks→resourceUrls→CSAPIQueryBuilder.scanCsapiLinks, though the document itself predates that function and does not reference it by name.CSAPIQueryBuildermanages state, including theresourceUrlsmap populated byscanCsapiLinks. The document does not referencescanCsapiLinksby name.scanCsapiLinksorisTrustedHrefby name.isTrustedHrefshould be CSAPI-internal per the isolation constraint. The document does not referencescanCsapiLinksorisTrustedHrefby name.endpoint.edr()→EDRQueryBuilder) that ourcreateCSAPIBuilder()follows. The document does not mentionscanCsapiLinksorcreateCSAPIBuilderby name — those are CSAPI-specific functions that follow the patterns documented here.endpoint.ts,info.ts, andindex.ts. Describes the factory method →CSAPIQueryBuilderflow following the EDR pattern. The document does not mentionfactory.tsorscanCsapiLinks— those are implementation details that emerged during development.scanCsapiLinksoperates, though the document does not mention it by name.scanCsapiLinksby name.scanCsapiLinksoutput, though the document does not referencescanCsapiLinksby name.scanCsapiLinksby name.resourceUrls, though the series does not referencescanCsapiLinksby name.endpoint.csapi()entry point that leads toCSAPIQueryBuilderinstantiation, though the documents do not referencescanCsapiLinksby name.Requirements Research
/api/systems/abc123), demonstrating the server's convention of returning relative URLs. Provides real-world evidence of what href values servers actually return.scanCsapiLinksoutput. Defines the breadth of impact if a malicious href propagates.scanCsapiLinksoutput viabuildResourceUrl().scanCsapiLinks. Defines the full downstream impact surface./systems/{id}/subsystems) — all nested paths are built onscanCsapiLinksbase URLs.isTrustedHreffix minimal.base_url + path) rather than link-following — a contrasting approach to ourscanCsapiLinksserver-href-based URL resolution.scanCsapiLinksoutput. Error handling requirements inform the silent-skip behavior.Testing
scanCsapiLinksagainst real OSH servers — confirms Convention 2 and 3 detection patterns work with realhttp://hrefs.scanCsapiLinks, foundpropertiesresource type not discoverable via root links on either server.isTrustedHreftests.notes-why-models-default-to-server-validation.md— directly relevant to the question of whether to validate/reject server-returned data or trust it.Governance
scanCsapiLinkscreation), Lesson 10 (Convention 3 bugs found via multi-vendor testing).hrefvalues returned by real servers — all observed values arehttp:///https://or relative. No exotic schemes observed.Implementation Records
scanCsapiLinks. Original review ofhelpers.ts.helpers.ts(226 lines), [P-6] POSITIVE forscanCsapiLinksnormalizingfeaturesOfInterestalias. Did not flag scheme validation gap.scanCsapiLinks()was originally created and the design decisions made at that time.Planning
scanCsapiLinksstays in CSAPI, its internal-only import classification, and the factory module data flow.Upstream PR Context
helpers.ts— any fix must be reflected here.Code Repositories
scanCsapiLinksoperates within.Live Infrastructure
http://hrefs in link objects. Use to verify thatisTrustedHrefdoes not reject any real-world server responses.