Skip to content

GoGlobal Hotel Integration - Security Audit Report

Audit Date: 2026-02-09 Auditor: Security Audit Agent Scope: Planned tqgglbl plugin for Yanolja GoGlobal hotel integration (API v3.16.8) Reference Documents: - C:\work\src\tqpro\Writerside\topics\go_global_integration_plan.md - C:\work\src\tqpro\CLAUDE.md - Existing plugin implementations (tqryb2b, tqamds, tqtiqets)


1. OWASP Top 10 Analysis

A01:2021 - Broken Access Control | Risk: MEDIUM

Findings:

  1. Missing Hotel Endpoint Authorization (EXISTING BUG): Four hotel API endpoints are currently missing from config/api-roles.properties (lines referenced in the plan, Section 2.5):
  2. hotel/searchOffers
  3. hotel/fetchOfferResults
  4. hotel/searchByName
  5. hotel/getByCity

Per the ApiRoleManager.isUserAuthorized() logic at C:\work\src\tqpro\tqapi\src\main\java\com\perun\tlinq\ApiRoleManager.java (line 128), endpoints not listed in the cache are denied by default. This means these endpoints are currently inaccessible, which is a safe-by-default behavior. However, when the plan adds them with guest,agent,admin access, this opens hotel search to unauthenticated users. This is likely intentional for a public-facing search, but booking and cancellation endpoints that will be added later must NOT be guest-accessible.

  1. Booking/Cancellation Endpoints Not Defined in Plan: The implementation plan does not specify new API endpoints for GoGlobal booking creation (Op 2), booking status (Op 5), booking details (Op 4), cancellation (Op 3), or valuation (Op 9). The OnlineHotelSupplierI interface defines these methods, but there are no corresponding REST endpoints or api-roles.properties entries. When these endpoints are eventually exposed, they must require agent,admin at minimum, and cancelBooking should require admin only, following the existing pattern.

  2. searchMode Parameter Bypass: The planned searchMode parameter (Section 2.4 of the plan) defaults to "online". There is no validation that the parameter value is one of the allowed values ("online" or "contracted"). An unexpected value would fall through to the else branch, executing the online path. This is not a direct vulnerability but represents sloppy control flow. Use an explicit whitelist check.

Risk Level: MEDIUM - The default-deny behavior of ApiRoleManager mitigates most risks, but the plan must explicitly address authorization for write/booking operations.


A02:2021 - Cryptographic Failures | Risk: HIGH

Findings:

  1. Plaintext Credentials in Configuration Files (CRITICAL, SYSTEMIC): This is a systemic issue across the entire TQPro platform, not specific to GoGlobal, but GoGlobal amplifies it:

  2. config/tourlinq-config.xml (line 58-63): Six database connection strings with password TlinqAdmin in plaintext, including production credentials:

    <Database name="prod" url="jdbc:postgresql://perun-db-prod01.cmupgeo7xxaa.us-east-1.rds.amazonaws.com:5432/tlinq"
              username="tlinq" password="TlinqAdmin"/>
    

  3. config/tourlinq-config.xml (lines 98-100): Odoo credentials in plaintext:

    <property name="odoo.user" value="odoo@peruntours.com"/>
    <property name="odoo.pwd" value="4ut0ma+ed"/>
    <property name="system.session" value="c0606475-cdc8-45ed-80df-4485ccc36e34"/>
    

  4. config/rayna-client.xml (line 10): Full JWT bearer token in plaintext.

  5. config/tiqets-client.xml (line 6): API key in plaintext:

    <property name="tiqets.api.key" value="tqat-zeYswVW8zC3074WrE2UkMMjE5yi57e4S"/>
    

  6. Planned config/goglobal-client.xml (lines 107-108 of the plan): GoGlobal agency ID, username, and password will be stored in plaintext:

    <property name="goglobal.agency" value="AGENCY_ID"/>
    <property name="goglobal.user" value="USERNAME"/>
    <property name="goglobal.password" value="PASSWORD"/>
    

  7. config/tlinqapi.properties (line 27): JKS keystore passphrase in plaintext:

    key-pass=PerunKeyPass
    

All these files appear to be checked into the Git repository (they are in the config/ directory referenced by TLINQ_HOME).

  1. GoGlobal Op 2 RSA Payment Encryption: The plan mentions credit card payment with RSA encryption for booking creation. The plan does not specify:
  2. Where the RSA public key is stored or how it is obtained
  3. Key rotation procedures
  4. Whether card data is ever logged or persisted (it must NOT be)
  5. PCI DSS compliance requirements

  6. Same Database Password Everywhere: Every database connection across all plugins (tourlinq-config.xml, rayna-client.xml, goglobal-client.xml plan) uses the identical password TlinqAdmin. A single credential compromise exposes all environments.

Risk Level: HIGH - Plaintext credentials in config files, especially production database passwords and API tokens, represent the single most critical security finding in this audit.


A03:2021 - Injection | Risk: HIGH

Findings:

  1. XML Injection in SOAP Request Building (GoGlobal-specific): The plan describes GoGlobalRequestBuilder building XML requests with user-provided parameters (hotel search codes, passenger names, dates, nationality codes). If these values are concatenated into XML strings without proper escaping, XML injection is possible. The plan references CDATA wrapping but does not mandate it universally.

Attack Vector: A malicious passenger name like ]]><Remark>INJECTED</Remark><![CDATA[ could break out of a CDATA section and inject arbitrary XML into the SOAP body.

  1. XXE (XML External Entity) in Response Parsing: The plan states GoGlobal returns XML for most operations (Op 2, 3, 4, 5, 9, 14) and JSON only for availability (Op 11). The GoGlobalResponseParser will parse XML responses. If standard Java XML parsers (SAXParser, DocumentBuilder, JAXB Unmarshaller) are used without disabling external entities, the system is vulnerable to XXE attacks if the GoGlobal API were compromised or a man-in-the-middle attack occurred.

  2. SQL Injection in Static Data Cache Queries: The plan (Section 7.2) indicates searchHotelsByName() will use SQL ILIKE '%keyword%' queries on the goglobal.hotel table. If implemented with string concatenation rather than parameterized queries (JPA Criteria API or named parameters), this introduces SQL injection risk.

Existing Pattern to Follow: The existing NTS entities use Hibernate JPA which provides parameterized queries by default, but the static data cache queries may bypass this pattern if using native SQL.

  1. Existing ILIKE Injection Risk: In C:\work\src\tqpro\tqapi\src\main\java\com\perun\tlinq\api\HotelApi.java, the doLookupHotel method (line 523) concatenates user input directly into an ILIKE pattern:
    scl.addCriterion("name","ilike", "%"+namePart+"%", null);
    
    While the SelectCriteriaList likely uses parameterized queries downstream, the % wrapping means special SQL LIKE characters (%, _) in user input are not escaped. This allows pattern injection (not data extraction, but could cause DoS with crafted patterns).

Risk Level: HIGH - XML injection and XXE are serious threats for a SOAP integration. SQL injection risk depends on implementation approach.


A04:2021 - Insecure Design | Risk: MEDIUM

Findings:

  1. No Rate Limiting: Neither the existing API server (TQProApiServer.java) nor the GoGlobal integration plan includes any rate limiting. Hotel availability searches (GoGlobal Op 11) are expensive operations both for TQPro and for the GoGlobal API. Guest-accessible search endpoints without rate limiting could lead to:
  2. GoGlobal API quota exhaustion (cost implications)
  3. TQPro server resource exhaustion (DoS)
  4. Potential account suspension by GoGlobal

  5. Search Result Lifecycle: The HotelSearchFacade (line 204) sets a 30-minute search expiry but there is no cleanup mechanism visible for expired searches. Persistent search results accumulate in the database indefinitely. The GoGlobal HotelSearchCode and ArrivalDate pairs also have a GoGlobal-side expiry that is not documented in the plan.

  6. Singleton Registry Pattern: The OnlineHotelSupplierRegistry uses a static ConcurrentHashMap pattern. While thread-safe for reads, there is no protection against registering a malicious supplier at runtime if an attacker gains code execution.

Risk Level: MEDIUM - Rate limiting absence is the primary concern; accumulated search data is secondary.


A05:2021 - Security Misconfiguration | Risk: HIGH

Findings:

  1. Development Mode Active in Committed Configuration: config/tlinqapi.properties (line 48) has dev-mode=true committed to the repository. While the TQProApiServer (lines 140-146) and AuthenticationFilter (lines 39-42) both log warnings, this value persists in the shared config file. If this same file is deployed to production without override, authentication is completely bypassed.

  2. HTTP Enabled: config/tlinqapi.properties (line 32) has http-enabled=true. This means plaintext HTTP is active alongside HTTPS. All API traffic including session tokens and GoGlobal search data can be intercepted if clients connect via HTTP.

  3. Wildcard CORS Origin: C:\work\src\tqpro\tqapi\src\main\java\com\perun\tlinq\CORSResponseFilter.java (line 20):

    responseContext.getHeaders().add("Access-Control-Allow-Origin", "*");
    
    Combined with Access-Control-Allow-Credentials: true (line 22), this is technically a conflicting configuration (browsers will reject credentialed requests with * origin), but it indicates a lack of proper CORS policy. The CORS policy should be restricted to known frontend origins.

  4. Default Keystore Password: The JKS keystore uses the default password PerunKeyPass (line 27 of tlinqapi.properties). This is a weak, predictable password.

Risk Level: HIGH - Dev-mode being active in committed config is particularly dangerous.


A06:2021 - Vulnerable and Outdated Components | Risk: LOW

Findings:

  1. The plan does not specify versions for new dependencies. The GoGlobal SOAP client will use java.net.http.HttpClient (Java 17 built-in), which is good. However:
  2. Jackson version for JSON parsing should be checked for known CVEs
  3. JAXB implementation for XML parsing should be checked for XXE defaults
  4. Hibernate version used for JPA entities should be current

  5. The planned XSD schemas (design/GoGlobalRequestSchema.xsd, design/GoGlobalOperationSchemas.xsd) referenced in the plan do not yet exist in the repository. Schema validation, when implemented, provides defense-in-depth against malformed responses.

Risk Level: LOW - Cannot fully assess without build file analysis; Java 17 is a reasonable baseline.


A07:2021 - Identification and Authentication Failures | Risk: MEDIUM

Findings:

  1. Header-Based Authentication Trust: The AuthenticationFilter (C:\work\src\tqpro\tqapi\src\main\java\com\perun\tlinq\AuthenticationFilter.java, lines 49-53) trusts X-User, X-Roles, X-Email, and X-Name headers directly. This is safe only if oauth2-proxy is the sole entry point and strips/replaces these headers. If the API is accessible directly (bypassing oauth2-proxy), any client can set arbitrary roles including admin.

  2. GoGlobal API Credentials as Static Config: GoGlobal credentials (agency, user, password) are loaded once at plugin initialization and reused for all requests. There is no credential rotation mechanism, no token refresh (unlike Amadeus which uses OAuth tokens), and no distinction between different TQPro users when making GoGlobal API calls.

  3. Session Token in Authorization Header: The AuthenticationFilter (line 54) extracts a token:

    String token = authToken != null ? authToken.substring(7) : null;
    
    This assumes the Authorization header always starts with "Bearer " (7 chars). If the header is shorter than 7 characters, this will throw a StringIndexOutOfBoundsException.

Risk Level: MEDIUM - Reliance on trusted proxy headers is acceptable in a properly configured environment but fragile.


A08:2021 - Software and Data Integrity Failures | Risk: MEDIUM

Findings:

  1. No Response Signature Verification: GoGlobal API responses are not signed. The plan relies entirely on HTTPS transport security. If HTTPS is compromised (e.g., certificate authority compromise, misconfigured proxy), modified pricing data or booking confirmations would be accepted without verification.

  2. Static Data Refresh Integrity: The StaticDataRefresher pulls country, city, and hotel data from GoGlobal and writes it directly to the goglobal schema. There is no integrity verification that the data received matches what GoGlobal intended. Corrupted or malicious data could propagate to all search results.

  3. Deserialization Risk: The HotelSearchFacade.getSearchResults() (line 265 of HotelSearchFacade.java) uses Gson.fromJson() to deserialize stored search results:

    CHotelOffer cho = gson.fromJson(so, CHotelOffer.class);
    
    Since sourceObject is stored in the database as a string, a compromised database entry could inject malicious JSON. Gson is generally safe from deserialization attacks (unlike Java serialization), but the pattern should be noted.

Risk Level: MEDIUM - Pricing data integrity is important for a booking system.


A09:2021 - Security Logging and Monitoring Failures | Risk: MEDIUM

Findings:

  1. Credential Logging Risk: The existing logging patterns log session IDs in API methods (e.g., logger.info("BEGIN searchHotelOffers for " + session)). The GoGlobal SOAP client must ensure that:
  2. GoGlobal agency credentials are never logged
  3. GoGlobal password is never logged
  4. Full SOAP request/response bodies containing credentials in <Header> blocks are not logged at INFO level
  5. Credit card data from Op 2 booking requests is never logged

  6. PII in Search Results: GoGlobal booking responses contain passenger names, booking references, and potentially payment details. If search results are logged at DEBUG level (common during development), PII may end up in log files.

  7. No Audit Trail for Bookings: The plan includes a goglobal.refresh_log table for static data refreshes, but does not include an audit log for booking operations (create, cancel, status checks). Booking operations should have a tamper-evident audit trail.

Risk Level: MEDIUM - Adequate logging infrastructure exists; the risk is in what the GoGlobal plugin might log inappropriately.


A10:2021 - Server-Side Request Forgery (SSRF) | Risk: MEDIUM

Findings:

  1. Configurable API Endpoint URL: The goglobal-client.xml plan includes:

    <property name="goglobal.apiServer" value="https://api.goglobal.travel"/>
    
    This URL is used by GoGlobalSoapClient for all API calls. If an attacker can modify this configuration (file system access, environment variable injection via ## property syntax), they can redirect all SOAP requests to an arbitrary server, potentially exfiltrating GoGlobal credentials.

  2. Thumbnail URLs from GoGlobal: The GoGlobal availability response includes Thumbnail and HotelImage URLs. If these URLs are passed to a server-side image processing pipeline (e.g., for caching or resizing), they become an SSRF vector. If only passed through to the frontend, the risk is limited to client-side concerns.

  3. Static Data Feed URLs: The StaticDataRefresher will download country, city, and hotel data from GoGlobal. The URLs for these feeds should be hardcoded or strictly validated, not user-configurable.

Risk Level: MEDIUM - The configurable API server URL is the primary concern.


2. API Endpoint Security

Existing Hotel Endpoints (Already in api-roles.properties)

Endpoint Current Roles Auth Check Notes
hotel/listHotels guest,agent,admin checkIfEmployee(session, true) in code Employee check in code contradicts guest role in config
hotel/hotelLookup guest,agent,admin checkIfEmployee(session, true) Same contradiction
hotel/getHotel guest,agent,admin checkIfEmployee(session, true) Same contradiction
hotel/saveHotel agent,admin checkIfEmployee(session, true) Correct
hotel/saveRoom agent,admin checkIfEmployee(session, true) Correct
hotel/searchAccommodation agent,admin Partial - allows system session fallback Session fallback at line 667 may bypass auth

Planned New/Updated Endpoints

Endpoint Planned Roles Rate Limiting Recommendation
hotel/searchOffers guest,agent,admin None ADD rate limiting (expensive GoGlobal API call)
hotel/fetchOfferResults guest,agent,admin None OK (reads from local DB cache)
hotel/searchByName guest,agent,admin None ADD rate limiting (DB query, but abusable)
hotel/getByCity guest,agent,admin None ADD rate limiting
Booking endpoints (future) Not defined Not defined MUST be agent,admin minimum
Cancel endpoint (future) Not defined Not defined MUST be admin only

Recommendation

Add per-IP and per-session rate limiting for all guest-accessible endpoints. Suggested limits: - hotel/searchOffers: 10 requests/minute per IP - hotel/searchByName: 30 requests/minute per IP - hotel/getByCity: 30 requests/minute per IP


3. Credential Management

Current State (All Plugins)

Credential Storage Encryption Rotation Committed to Git
Database passwords Plaintext XML None Manual YES (CRITICAL)
Odoo user/password Plaintext XML None Manual YES (CRITICAL)
Odoo system session Plaintext XML None Manual YES (CRITICAL)
Rayna JWT token Plaintext XML None Manual YES (CRITICAL)
Tiqets API key Plaintext XML None Manual YES (CRITICAL)
JKS keystore pass Plaintext properties None Manual YES (HIGH)

Planned GoGlobal Credentials

Credential Planned Storage Risk
goglobal.agency Plaintext XML (goglobal-client.xml) HIGH
goglobal.user Plaintext XML HIGH
goglobal.password Plaintext XML CRITICAL

Recommendations

  1. Immediate (GoGlobal-specific): Use the ## property reference syntax (already used for dbname and dbpass) to externalize GoGlobal credentials to environment variables or a secrets manager:

    <property name="goglobal.agency" value="##goglobal.agency"/>
    <property name="goglobal.user" value="##goglobal.user"/>
    <property name="goglobal.password" value="##goglobal.password"/>
    

  2. Short-term: Add config/goglobal-client.xml to .gitignore and provide a template file instead.

  3. Medium-term: Integrate with a secrets management solution (HashiCorp Vault, AWS Secrets Manager) for all plugin credentials.

  4. Ensure GoGlobal password is never:

  5. Logged (not even at TRACE/DEBUG level)
  6. Included in error messages or exception stack traces
  7. Returned in any API response
  8. Stored in the database (only in config)

4. Input Validation

Required Validation for GoGlobal-Specific Parameters

Parameter Source Validation Required
searchMode API request body Whitelist: "online", "contracted" only
checkInDate / checkOutDate API request body Date format, future date, check-out > check-in, max range (e.g., 30 days)
adults API request body Positive integer, max bound (e.g., 9)
roomQuantity API request body Positive integer, max bound (e.g., 9)
nationality GoGlobal request ISO 3166-1 alpha-2 code validation (exactly 2 uppercase letters)
cityCode / cityId API request / GoGlobal request Numeric ID validation against cached data
hotelSearchCode GoGlobal valuation/booking Alphanumeric format validation, existence check
passengerFirstName / passengerLastName Booking request Alphanumeric + spaces only, max length (GoGlobal limits), NO XML special characters
keyword (hotel name search) API request body Min length (e.g., 2 chars), max length, escape SQL LIKE wildcards
currency API request body ISO 4217 3-letter code whitelist
hotelIds API request body (comma-separated) Validate each ID is a valid format, limit count

Existing Validation Gaps in HotelApi.java

  1. No maximum result limit: doSearchHotelOffers (line 1238) hardcodes resPerPage=10 but there is no maximum on total results from GoGlobal. The facade should enforce a cap.

  2. No date range validation: Check-in and check-out dates are parsed but not validated for:

  3. Being in the future
  4. Check-out being after check-in
  5. Maximum stay duration
  6. Not being absurdly far in the future

  7. No input length limits: String parameters like hotelIds, keyword, cityCode have no maximum length enforcement. Extremely long strings could cause memory issues or downstream errors.


5. Output Encoding and PII Exposure

PII in GoGlobal Responses

Data Element GoGlobal Response PII Level Handling
Passenger names Booking response (Op 2, 4) High Never log, store only in booking record
Booking reference All booking responses Medium OK to log reference codes
Hotel confirmation code Booking response Low OK to log
Credit card token/reference Booking response (Op 2) Critical Never log, never store, mask in all output
Commission data Availability response Business-sensitive Do not expose to guest users

Recommendations

  1. Strip commission data (CommPercent, CommValue) from responses returned to guest users. These are internal business metrics.

  2. Implement response filtering so that guest-role API responses contain fewer fields than agent/admin responses.

  3. Error message sanitization: The current pattern (e.g., HotelApi.java line 55) returns raw exception messages to clients:

    ar = new TlinqApiResponse(TlinqErr.GENERAL,
        th.getMessage() == null ? th.getClass().getName() : th.getMessage());
    
    GoGlobal SOAP errors may contain internal details (server paths, stack traces, GoGlobal-internal error codes). These must be sanitized before returning to the client.


6. Communication Security

SOAP Client Requirements

  1. HTTPS Enforcement: The GoGlobalSoapClient must validate that goglobal.apiServer starts with https:// and reject http:// URLs. The java.net.http.HttpClient should be configured to:

    HttpClient client = HttpClient.newBuilder()
        .sslContext(SSLContext.getDefault())  // Use system trust store
        .connectTimeout(Duration.ofSeconds(10))
        .build();
    

  2. Certificate Validation: Do NOT disable certificate validation. Do NOT implement a trust-all TrustManager. This is a common shortcut during development that creates a critical vulnerability if deployed to production.

  3. TLS Version: Enforce TLS 1.2 minimum. The java.net.http.HttpClient in Java 17 supports this by default, but the server's Jetty SslContextFactory at C:\work\src\tqpro\tqapi\src\main\java\com\perun\tlinq\TQProApiServer.java (line 160) does not explicitly set protocol versions:

    SslContextFactory.Server sslCtxFact = new SslContextFactory.Server();
    // No explicit protocol restriction
    
    This should be configured to exclude TLS 1.0 and 1.1.

  4. Connection Timeouts: The plan must specify timeouts for GoGlobal API calls:

  5. Connect timeout: 10 seconds
  6. Read timeout: 30 seconds (GoGlobal availability searches can be slow)
  7. Maximum response size: 10 MB (prevent memory exhaustion from abnormal responses)

  8. SOAP Header Security: GoGlobal API credentials are transmitted in every SOAP request in the <Header> block. Ensure:

  9. The password field is transmitted, not stored in SOAP request logs
  10. HTTP headers API-Operation and API-AgencyID are set correctly
  11. No sensitive data appears in URL query parameters (POST body only)

7. Error Handling and Information Leakage

Current Error Handling Pattern

The existing HotelApi.java follows a consistent pattern:

catch (Throwable th) {
    logger.log(Level.SEVERE, "Error in methodName", th);
    ar = new TlinqApiResponse(TlinqErr.GENERAL,
        th.getMessage() == null ? th.getClass().getName() : th.getMessage());
}

Issues

  1. Exception class names returned to client: When th.getMessage() is null, the class name is returned (e.g., java.lang.NullPointerException, org.xml.sax.SAXParseException). This leaks internal implementation details.

  2. GoGlobal error messages forwarded: If the GoGlobal API returns an error, the SOAP fault message may contain:

  3. Internal GoGlobal server details
  4. Request correlation IDs
  5. SQL error messages from GoGlobal's backend These should be translated to generic TQPro error codes.

  6. Stack traces in logs: The logger.log(Level.SEVERE, ...) call includes the full stack trace. While appropriate for server logs, ensure log files are properly secured and rotated.

Recommendations

  1. Create a GoGlobalErrorMapper class that translates GoGlobal-specific errors to TQPro error codes:
  2. Connection timeout -> TlinqErr.SERVICE_UNAVAILABLE
  3. Invalid search code -> TlinqErr.INVALID_PARAMETER
  4. Booking failed -> TlinqErr.BOOKING_ERROR
  5. Unknown error -> TlinqErr.GENERAL with generic message "Hotel service temporarily unavailable"

  6. Never return GoGlobal-internal error messages to API clients.


8. Logging and Monitoring

What MUST Be Logged

Event Level Content
GoGlobal API call start INFO Operation type, search parameters (not credentials)
GoGlobal API call complete INFO Operation type, response time, result count
GoGlobal API call failure SEVERE Operation type, HTTP status, sanitized error
Static data refresh start INFO Refresh type, timestamp
Static data refresh complete INFO Record counts, duration
Booking creation INFO Booking reference, hotel ID, dates (not passenger names)
Booking cancellation INFO Booking reference, cancellation status
Rate limit exceeded WARNING Source IP, endpoint, count

What MUST NOT Be Logged

Data Reason
GoGlobal password Credential exposure
GoGlobal agency/user in request bodies Credential exposure
Full SOAP request XML Contains credentials in <Header>
Passenger first/last names PII
Credit card data (any form) PCI DSS requirement
Full SOAP response XML at INFO level May contain PII
GoGlobal internal error details Information leakage

Implementation

In GoGlobalSoapClient, implement request/response logging that: 1. Strips the <Header> section before logging request XML 2. Replaces passenger names with *** before logging response XML 3. Only logs full bodies at FINEST/TRACE level (never in production) 4. Uses a separate logger name (e.g., com.perun.tlinq.client.goglobal.transport) so it can be independently configured


9. Specific Recommendations for the Developer

R1: SOAP Client XXE Prevention (CRITICAL)

In GoGlobalResponseParser, when parsing XML responses, configure the parser to disable external entities:

// For DocumentBuilderFactory:
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
dbf.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
dbf.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");

// For SAXParserFactory:
SAXParserFactory spf = SAXParserFactory.newInstance();
spf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);

// For JAXB Unmarshaller with SAX source:
SAXParserFactory spf = SAXParserFactory.newInstance();
spf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
Source xmlSource = new SAXSource(spf.newSAXParser().getXMLReader(), new InputSource(...));

R2: SOAP Request XML Escaping (CRITICAL)

In GoGlobalRequestBuilder, all user-provided values inserted into XML must be properly escaped. Use a utility method:

private static String escapeXml(String input) {
    if (input == null) return "";
    return input.replace("&", "&amp;")
                .replace("<", "&lt;")
                .replace(">", "&gt;")
                .replace("\"", "&quot;")
                .replace("'", "&apos;");
}

Alternatively, use a DOM builder or JAXB marshaller that handles escaping automatically rather than string concatenation.

R3: Credential Externalization (HIGH)

In config/goglobal-client.xml, use property references:

<property name="goglobal.agency" value="##goglobal.agency"/>
<property name="goglobal.user" value="##goglobal.user"/>
<property name="goglobal.password" value="##goglobal.password"/>

The ## prefix indicates these are resolved from system properties or environment variables, following the existing pattern visible in tourlinq-config.xml (lines 25-28).

R4: Connection Timeout Configuration (HIGH)

In GoGlobalSoapClient:

private static final Duration CONNECT_TIMEOUT = Duration.ofSeconds(10);
private static final Duration REQUEST_TIMEOUT = Duration.ofSeconds(30);

HttpClient client = HttpClient.newBuilder()
    .connectTimeout(CONNECT_TIMEOUT)
    .build();

HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create(apiServerUrl + "/path"))
    .timeout(REQUEST_TIMEOUT)
    .POST(HttpRequest.BodyPublishers.ofString(soapBody))
    .build();

R5: Parameterized Queries for Static Data Cache (HIGH)

In GoGlobalHotelFacade.searchHotelsByName() and getHotelsByCity(), use Hibernate Criteria API or HQL with named parameters:

// CORRECT:
session.createQuery("FROM GGHotelEntity h WHERE h.hotelName ILIKE :keyword", GGHotelEntity.class)
    .setParameter("keyword", "%" + escapeLikeWildcards(keyword) + "%")
    .getResultList();

// WRONG:
session.createQuery("FROM GGHotelEntity h WHERE h.hotelName ILIKE '%" + keyword + "%'")
    .getResultList();

Add a LIKE wildcard escape helper:

private static String escapeLikeWildcards(String input) {
    return input.replace("\\", "\\\\")
                .replace("%", "\\%")
                .replace("_", "\\_");
}

R6: searchMode Whitelist (MEDIUM)

In the modified HotelApi.searchHotelOffers():

String searchMode = ApiUtil.gmp(reqData, "searchMode", String.class, false);
if (searchMode == null) searchMode = "online";
if (!Set.of("online", "contracted").contains(searchMode)) {
    throw new TlinqClientException(TlinqErr.INVALID_FORMAT,
        "Invalid searchMode: must be 'online' or 'contracted'");
}

R7: HTTPS-Only API Server URL Validation (MEDIUM)

In GoGlobalClientConfig initialization:

String apiServer = config.getProperty("goglobal.apiServer");
if (!apiServer.startsWith("https://")) {
    throw new TlinqClientException(TlinqErr.CONFIG_ERROR,
        "GoGlobal API server must use HTTPS");
}

R8: Response Size Limit (MEDIUM)

When reading GoGlobal API responses, enforce a maximum response size to prevent memory exhaustion:

HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.body().length() > 10 * 1024 * 1024) { // 10 MB
    throw new TlinqClientException(TlinqErr.GENERAL,
        "GoGlobal API response exceeded maximum size");
}

R9: Date Validation in New Endpoints (MEDIUM)

Add validation in doSearchHotelOffers:

if (checkInDate.before(new Date())) {
    throw new TlinqClientException(TlinqErr.INVALID_FORMAT, "Check-in date must be in the future");
}
if (checkOutDate != null && !checkOutDate.after(checkInDate)) {
    throw new TlinqClientException(TlinqErr.INVALID_FORMAT, "Check-out date must be after check-in date");
}
long daysBetween = ChronoUnit.DAYS.between(
    checkInDate.toInstant(), (checkOutDate != null ? checkOutDate : checkInDate).toInstant());
if (daysBetween > 30) {
    throw new TlinqClientException(TlinqErr.INVALID_FORMAT, "Maximum stay duration is 30 days");
}

R10: Booking Audit Trail (MEDIUM)

Create a goglobal.booking_audit table:

CREATE TABLE goglobal.booking_audit (
    id SERIAL PRIMARY KEY,
    action VARCHAR(20) NOT NULL,          -- CREATE, CANCEL, STATUS, DETAILS
    go_booking_code VARCHAR(50),
    hotel_search_code VARCHAR(100),
    session_id VARCHAR(100),
    user_id VARCHAR(100),
    request_timestamp TIMESTAMP DEFAULT NOW(),
    response_status VARCHAR(50),
    response_code VARCHAR(50),
    ip_address VARCHAR(45)
);


10. Security Test Cases

TC-SEC-01: XXE Prevention in XML Response Parser

Priority: CRITICAL Test: Send a mock GoGlobal XML response containing an XXE payload:

<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
<Root><Header/><Main><HotelName>&xxe;</HotelName></Main></Root>
Expected: Parser rejects the response with an error. No file content is returned.

TC-SEC-02: XML Injection in Passenger Names

Priority: CRITICAL Test: Submit a booking request with passenger name:

John</LeaderFirstName><Remark>INJECTED</Remark><LeaderFirstName>
Expected: Name is XML-escaped in the SOAP request body. GoGlobal receives the literal string, not parsed XML.

Priority: HIGH Test: Call hotel/searchByName with keyword:

' OR 1=1; DROP TABLE goglobal.hotel; --
Expected: Query returns no results or treats the input as a literal string. goglobal.hotel table is not affected.

Priority: MEDIUM Test: Call hotel/searchByName with keyword: % Expected: Does NOT return all hotels. The % is escaped and treated as a literal percent character.

TC-SEC-05: GoGlobal Credentials Not in Logs

Priority: HIGH Test: Execute a hotel search with logging at DEBUG/FINEST level. Search all log output. Expected: GoGlobal password, agency ID, and username do not appear in any log entry. SOAP request bodies logged (if any) have the <Header> section redacted.

TC-SEC-06: Invalid searchMode Parameter

Priority: MEDIUM Test: Call hotel/searchOffers with searchMode: "DROP TABLE". Expected: Returns validation error, does not execute any search.

TC-SEC-07: Future Date Validation

Priority: MEDIUM Test: Call hotel/searchOffers with checkInDate set to yesterday. Expected: Returns validation error "Check-in date must be in the future".

TC-SEC-08: Connection Timeout to GoGlobal

Priority: HIGH Test: Configure goglobal.apiServer to point to a non-responding host (e.g., https://10.255.255.1). Expected: Request times out within 10 seconds (connect timeout). Does not hang indefinitely.

TC-SEC-09: HTTPS Enforcement for GoGlobal API

Priority: MEDIUM Test: Set goglobal.apiServer to http://api.goglobal.travel (HTTP, not HTTPS). Expected: Plugin refuses to initialize or throws a configuration error.

TC-SEC-10: Guest User Cannot Access Booking Operations

Priority: HIGH Test: When booking endpoints are implemented, call them without authentication (guest role). Expected: Returns 403 Forbidden response.

TC-SEC-11: Dev-Mode Detection in Production

Priority: HIGH Test: Deploy with dev-mode=false in tlinqapi.properties. Call any API without X-User/X-Roles headers. Expected: User is treated as guest role (not admin). Write operations are denied.

TC-SEC-12: Oversized GoGlobal Response

Priority: MEDIUM Test: Mock a GoGlobal response larger than 10 MB. Expected: Response is rejected with an appropriate error. No OutOfMemoryError.

TC-SEC-13: Commission Data Stripping for Guest Users

Priority: MEDIUM Test: Call hotel/searchOffers as a guest user. Expected: Response does not include CommPercent or CommValue fields.

TC-SEC-14: Concurrent Static Data Refresh

Priority: LOW Test: Trigger two simultaneous static data refresh operations. Expected: Only one refresh executes; the second is either queued or skipped. Database state remains consistent.

TC-SEC-15: Expired Search Code Handling

Priority: MEDIUM Test: Use a hotelSearchCode from a search performed more than 30 minutes ago for valuation. Expected: Returns an appropriate error indicating the search has expired.


Summary of Risk Levels

Category Risk Level Key Concern
A01: Broken Access Control MEDIUM Missing endpoint role definitions for future booking APIs
A02: Cryptographic Failures HIGH Plaintext credentials across all config files, including production DB passwords
A03: Injection HIGH XML injection, XXE, SQL LIKE injection risks in new SOAP client
A04: Insecure Design MEDIUM No rate limiting on guest-accessible search endpoints
A05: Security Misconfiguration HIGH Dev-mode enabled in committed config, HTTP enabled, wildcard CORS
A06: Vulnerable Components LOW Cannot fully assess; Java 17 baseline is good
A07: Authentication Failures MEDIUM Header trust model, static GoGlobal credentials
A08: Data Integrity MEDIUM No response signature verification for pricing data
A09: Logging Failures MEDIUM Risk of credential/PII logging in SOAP client
A10: SSRF MEDIUM Configurable API server URL, thumbnail URLs

Top 5 Action Items (Ordered by Priority)

  1. Disable XXE in all XML parsers used by GoGlobalResponseParser (R1)
  2. Properly escape all XML in SOAP requests built by GoGlobalRequestBuilder (R2)
  3. Externalize GoGlobal credentials using ## property references (R3)
  4. Set dev-mode=false in committed config or move to environment-specific config files (A05)
  5. Add connection and read timeouts to GoGlobalSoapClient (R4)