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:
- 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): hotel/searchOffershotel/fetchOfferResultshotel/searchByNamehotel/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.
-
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). TheOnlineHotelSupplierIinterface defines these methods, but there are no corresponding REST endpoints orapi-roles.propertiesentries. When these endpoints are eventually exposed, they must requireagent,adminat minimum, andcancelBookingshould requireadminonly, following the existing pattern. -
searchMode Parameter Bypass: The planned
searchModeparameter (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:
-
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:
-
config/tourlinq-config.xml(line 58-63): Six database connection strings with passwordTlinqAdminin plaintext, including production credentials: -
config/tourlinq-config.xml(lines 98-100): Odoo credentials in plaintext: -
config/rayna-client.xml(line 10): Full JWT bearer token in plaintext. -
config/tiqets-client.xml(line 6): API key in plaintext: -
Planned
config/goglobal-client.xml(lines 107-108 of the plan): GoGlobal agency ID, username, and password will be stored in plaintext: -
config/tlinqapi.properties(line 27): JKS keystore passphrase in plaintext:
All these files appear to be checked into the Git repository (they are in the config/ directory referenced by TLINQ_HOME).
- GoGlobal Op 2 RSA Payment Encryption: The plan mentions credit card payment with RSA encryption for booking creation. The plan does not specify:
- Where the RSA public key is stored or how it is obtained
- Key rotation procedures
- Whether card data is ever logged or persisted (it must NOT be)
-
PCI DSS compliance requirements
-
Same Database Password Everywhere: Every database connection across all plugins (
tourlinq-config.xml,rayna-client.xml,goglobal-client.xmlplan) uses the identical passwordTlinqAdmin. 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:
- XML Injection in SOAP Request Building (GoGlobal-specific): The plan describes
GoGlobalRequestBuilderbuilding 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.
-
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
GoGlobalResponseParserwill 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. -
SQL Injection in Static Data Cache Queries: The plan (Section 7.2) indicates
searchHotelsByName()will use SQLILIKE '%keyword%'queries on thegoglobal.hoteltable. 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.
- Existing ILIKE Injection Risk: In
C:\work\src\tqpro\tqapi\src\main\java\com\perun\tlinq\api\HotelApi.java, thedoLookupHotelmethod (line 523) concatenates user input directly into an ILIKE pattern: While theSelectCriteriaListlikely 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:
- 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: - GoGlobal API quota exhaustion (cost implications)
- TQPro server resource exhaustion (DoS)
-
Potential account suspension by GoGlobal
-
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 GoGlobalHotelSearchCodeandArrivalDatepairs also have a GoGlobal-side expiry that is not documented in the plan. -
Singleton Registry Pattern: The
OnlineHotelSupplierRegistryuses a staticConcurrentHashMappattern. 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:
-
Development Mode Active in Committed Configuration:
config/tlinqapi.properties(line 48) hasdev-mode=truecommitted to the repository. While theTQProApiServer(lines 140-146) andAuthenticationFilter(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. -
HTTP Enabled:
config/tlinqapi.properties(line 32) hashttp-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. -
Wildcard CORS Origin:
Combined withC:\work\src\tqpro\tqapi\src\main\java\com\perun\tlinq\CORSResponseFilter.java(line 20):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. -
Default Keystore Password: The JKS keystore uses the default password
PerunKeyPass(line 27 oftlinqapi.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:
- 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: - Jackson version for JSON parsing should be checked for known CVEs
- JAXB implementation for XML parsing should be checked for XXE defaults
-
Hibernate version used for JPA entities should be current
-
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:
-
Header-Based Authentication Trust: The
AuthenticationFilter(C:\work\src\tqpro\tqapi\src\main\java\com\perun\tlinq\AuthenticationFilter.java, lines 49-53) trustsX-User,X-Roles,X-Email, andX-Nameheaders 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 includingadmin. -
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.
-
Session Token in
This assumes theAuthorizationHeader: TheAuthenticationFilter(line 54) extracts a token:Authorizationheader always starts with"Bearer "(7 chars). If the header is shorter than 7 characters, this will throw aStringIndexOutOfBoundsException.
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:
-
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.
-
Static Data Refresh Integrity: The
StaticDataRefresherpulls country, city, and hotel data from GoGlobal and writes it directly to thegoglobalschema. There is no integrity verification that the data received matches what GoGlobal intended. Corrupted or malicious data could propagate to all search results. -
Deserialization Risk: The
SinceHotelSearchFacade.getSearchResults()(line 265 ofHotelSearchFacade.java) usesGson.fromJson()to deserialize stored search results:sourceObjectis 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:
- 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: - GoGlobal agency credentials are never logged
- GoGlobal password is never logged
- Full SOAP request/response bodies containing credentials in
<Header>blocks are not logged at INFO level -
Credit card data from Op 2 booking requests is never logged
-
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.
-
No Audit Trail for Bookings: The plan includes a
goglobal.refresh_logtable 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:
-
Configurable API Endpoint URL: The
This URL is used bygoglobal-client.xmlplan includes:GoGlobalSoapClientfor 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. -
Thumbnail URLs from GoGlobal: The GoGlobal availability response includes
ThumbnailandHotelImageURLs. 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. -
Static Data Feed URLs: The
StaticDataRefresherwill 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¶
-
Immediate (GoGlobal-specific): Use the
##property reference syntax (already used fordbnameanddbpass) to externalize GoGlobal credentials to environment variables or a secrets manager: -
Short-term: Add
config/goglobal-client.xmlto.gitignoreand provide a template file instead. -
Medium-term: Integrate with a secrets management solution (HashiCorp Vault, AWS Secrets Manager) for all plugin credentials.
-
Ensure GoGlobal password is never:
- Logged (not even at TRACE/DEBUG level)
- Included in error messages or exception stack traces
- Returned in any API response
- 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¶
-
No maximum result limit:
doSearchHotelOffers(line 1238) hardcodesresPerPage=10but there is no maximum on total results from GoGlobal. The facade should enforce a cap. -
No date range validation: Check-in and check-out dates are parsed but not validated for:
- Being in the future
- Check-out being after check-in
- Maximum stay duration
-
Not being absurdly far in the future
-
No input length limits: String parameters like
hotelIds,keyword,cityCodehave 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¶
-
Strip commission data (
CommPercent,CommValue) from responses returned to guest users. These are internal business metrics. -
Implement response filtering so that guest-role API responses contain fewer fields than agent/admin responses.
-
Error message sanitization: The current pattern (e.g.,
GoGlobal SOAP errors may contain internal details (server paths, stack traces, GoGlobal-internal error codes). These must be sanitized before returning to the client.HotelApi.javaline 55) returns raw exception messages to clients:
6. Communication Security¶
SOAP Client Requirements¶
-
HTTPS Enforcement: The
GoGlobalSoapClientmust validate thatgoglobal.apiServerstarts withhttps://and rejecthttp://URLs. Thejava.net.http.HttpClientshould be configured to: -
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. -
TLS Version: Enforce TLS 1.2 minimum. The
This should be configured to exclude TLS 1.0 and 1.1.java.net.http.HttpClientin Java 17 supports this by default, but the server's JettySslContextFactoryatC:\work\src\tqpro\tqapi\src\main\java\com\perun\tlinq\TQProApiServer.java(line 160) does not explicitly set protocol versions: -
Connection Timeouts: The plan must specify timeouts for GoGlobal API calls:
- Connect timeout: 10 seconds
- Read timeout: 30 seconds (GoGlobal availability searches can be slow)
-
Maximum response size: 10 MB (prevent memory exhaustion from abnormal responses)
-
SOAP Header Security: GoGlobal API credentials are transmitted in every SOAP request in the
<Header>block. Ensure: - The password field is transmitted, not stored in SOAP request logs
- HTTP headers
API-OperationandAPI-AgencyIDare set correctly - 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¶
-
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. -
GoGlobal error messages forwarded: If the GoGlobal API returns an error, the SOAP fault message may contain:
- Internal GoGlobal server details
- Request correlation IDs
-
SQL error messages from GoGlobal's backend These should be translated to generic TQPro error codes.
-
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¶
- Create a
GoGlobalErrorMapperclass that translates GoGlobal-specific errors to TQPro error codes: - Connection timeout ->
TlinqErr.SERVICE_UNAVAILABLE - Invalid search code ->
TlinqErr.INVALID_PARAMETER - Booking failed ->
TlinqErr.BOOKING_ERROR -
Unknown error ->
TlinqErr.GENERALwith generic message "Hotel service temporarily unavailable" -
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("&", "&")
.replace("<", "<")
.replace(">", ">")
.replace("\"", """)
.replace("'", "'");
}
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>
TC-SEC-02: XML Injection in Passenger Names¶
Priority: CRITICAL Test: Submit a booking request with passenger name:
Expected: Name is XML-escaped in the SOAP request body. GoGlobal receives the literal string, not parsed XML.TC-SEC-03: SQL Injection in Hotel Name Search¶
Priority: HIGH
Test: Call hotel/searchByName with keyword:
goglobal.hotel table is not affected.
TC-SEC-04: LIKE Pattern Injection in Hotel Name Search¶
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)¶
- Disable XXE in all XML parsers used by
GoGlobalResponseParser(R1) - Properly escape all XML in SOAP requests built by
GoGlobalRequestBuilder(R2) - Externalize GoGlobal credentials using
##property references (R3) - Set dev-mode=false in committed config or move to environment-specific config files (A05)
- Add connection and read timeouts to
GoGlobalSoapClient(R4)