
The security utility layer that protects every request — 12 sanitization functions, JWT utilities for token inspection and issuer validation, API key service with rotation support, data masking for logs, and CSRF token management.
The security module in tctf-utils is the utility belt that every service reaches for when handling untrusted data. It is not a framework or an architecture — it is a collection of focused functions that each solve one security problem well. Sanitize a string. Mask an email for logging. Validate a JWT issuer. Check an API key. Generate a CSRF token. These are small functions, but they are used thousands of times per day across all 25 services. Getting any one of them wrong — a sanitizer that misses a pattern, a masker that leaks data, a validator that accepts a forged token — creates a vulnerability that affects the entire platform. This article covers the security utilities we built and why each one exists.
Input sanitization is context-dependent. A string that is safe in a JSON response is dangerous in an HTML page. A string that is safe in a DynamoDB query is dangerous in a SQL query. Each context needs its own sanitizer.
The security module provides 12 sanitization functions, each designed for a specific context.
sanitizeInput is the general-purpose sanitizer — strips control characters, limits string length, and removes potentially dangerous patterns. It is the default for any string that does not have a more specific sanitizer.
sanitizeHtml removes HTML tags to prevent cross-site scripting (XSS). It strips script tags, event handlers, and data URIs while preserving the text content.
sanitizeObject recursively sanitizes all string properties in an object, with an exclusion list for fields that should not be sanitized (like encrypted data or base64 content).
sanitizeSql and sanitizeNoSql remove patterns that could be used for injection attacks. These are deprecated in favor of parameterized queries, but they exist as a defense-in-depth layer for legacy code paths.
sanitizePath prevents directory traversal attacks by removing parent directory references (..), leading slashes, and invalid filename characters.
sanitizeUrl validates and cleans URLs — checking the protocol (only http and https), removing dangerous schemes (javascript:, data:), and validating the URL structure.
sanitizeRegex escapes special regex characters to prevent ReDoS (Regular Expression Denial of Service) attacks when user input is used in regex patterns.
sanitizeJson handles JSON injection by escaping backslashes and quotes.
sanitizePhoneNumber validates phone number format and removes non-numeric characters except the leading plus sign.
sanitizeNumeric validates numeric input with configurable min/max ranges and optional float support.
removeControlCharacters strips invisible control characters that can break rendering or hide malicious content.
🛡️ 12 sanitizers for 12 contexts: general input, HTML, objects, SQL, NoSQL, file paths, URLs, regex, JSON, phone numbers, numeric, and control characters. Each context needs its own protection.

Logs are essential for debugging. But logs that contain user data are a liability. An email address in a log file is a GDPR violation. A phone number in CloudWatch is a privacy breach. A password in a stack trace is a security incident.
The security module provides masking functions that make data safe for logging while preserving enough information for debugging.
maskEmail transforms sam.adebowale@cometbid.org into s***e@c***d.org — the first and last characters of the local part and domain are visible, everything else is masked. This is enough to identify which user is affected without exposing the full email.
maskIp transforms 192.168.1.100 into 192.168.***.*** — the first two octets are visible (enough to identify the network), the last two are masked.
maskSensitiveData is a generic masker that shows the first and last characters of any string with asterisks in between. Used for API keys, tokens, and other sensitive values that need to appear in logs for debugging but should not be fully visible.
The masking functions are used throughout the platform — in error handlers, in the session coordinator, in the account validation service, in the rate limiting service. Every log entry that includes user data passes through a masker first. This is not optional — it is enforced by code review and linting rules.
🔒sam.adebowale@cometbid.org → s***e@c***d.org. 192.168.1.100 → 192.168.***.***. Every log entry with user data passes through a masker. Not optional — enforced by code review.
The Cognito middleware (Framework Series #2) handles full JWT validation — signature verification, expiration checks, issuer validation. But sometimes you need to inspect a token without full validation — to extract claims for logging, to check the token type, or to determine the issuer before routing to the right validator.
The JWT utilities in the security module provide lightweight token inspection.
getTokenPayload decodes the JWT payload without verifying the signature. This is useful for logging (extract the user ID from an expired token for error context) and for routing (determine which Cognito User Pool issued the token before selecting the validation key).
getTokenExpiration extracts the exp claim and returns whether the token is expired. This is a quick check before attempting full validation — if the token is expired, there is no point verifying the signature.
isFromTrustedIssuer checks the iss claim against a list of trusted issuer URLs. This prevents tokens from untrusted Cognito User Pools from being accepted — a defense against token confusion attacks where an attacker uses a token from their own User Pool.
getTokenType determines whether a token is an access token or an ID token based on the token_use claim. Access tokens authorize API calls. ID tokens carry user identity. Using the wrong type for the wrong purpose is a common security mistake.
These utilities are deliberately separate from the full validation middleware. They are fast (no cryptographic operations), stateless (no network calls), and safe (they never trust the token — they just read it). Full validation happens in the middleware. These utilities handle the cases where you need information from a token without the overhead of full validation.
Not all API calls come from users with JWTs. Some come from other services — the scheduler calling the campaign API, the activity service calling the notification API, the billing service calling the communication service. These service-to-service calls use API keys instead of JWTs.
The ApiKeyService manages the lifecycle of API keys: generation, validation, rotation, and revocation. Keys are stored in DynamoDB with metadata — the service name, the creation date, the expiration date, and the permissions the key grants.
Key validation checks the key against the stored record, verifies it has not expired, and confirms it has the required permissions for the requested operation. Invalid keys throw an AuthenticationError that is handled by the standard error handling architecture.
Key rotation is supported without downtime. A new key is generated and both the old and new keys are valid during a transition period. After the transition, the old key is revoked. This means services can rotate keys without coordinating a simultaneous deployment.
The API key validation integrates with the security middleware. The validateApiKey function extracts the key from the x-api-key header, validates it against the ApiKeyService, and enriches the request context with the service identity. Downstream code knows which service made the call without parsing the key itself.
Cross-Site Request Forgery (CSRF) attacks trick a user's browser into making requests to the platform using the user's existing session. The classic defense is a CSRF token — a random value that the server generates, the client includes in requests, and the server validates.
The security module provides CSRF token generation and validation. Tokens are generated using cryptographically secure random bytes, stored in the user's session, and validated on every state-changing request (POST, PUT, DELETE).
The CSRF protection is particularly important for the Cometbid Social frontend, where users are logged in for extended periods and browse external content that could contain CSRF payloads. A malicious link in a forum post could trigger an escrow release or a profile change if CSRF protection is not in place.
The token validation is integrated into the request validation pipeline (Framework Series #15). State-changing requests that do not include a valid CSRF token are rejected with a 403 before they reach business logic.
The security module exports everything through a single index file. Services import what they need — sanitizers for input handling, maskers for logging, JWT utilities for token inspection, the API key service for service-to-service auth, and CSRF utilities for browser-based protection.
The module is designed for composition. The sanitizers work with the request validation pipeline. The maskers work with the error handling architecture. The JWT utilities work with the Cognito middleware. The API key service works with the permission system. Each utility solves one problem, and they compose into a security layer that covers the full request lifecycle.
The key design principle: security utilities should be so easy to use that developers reach for them by default. If sanitizing input requires importing three modules and writing ten lines of code, developers will skip it. If it requires one import and one function call, they will use it every time. The security module is designed for the second case — one import, one call, consistent protection.
✅Security utilities should be so easy to use that developers reach for them by default. One import, one function call, consistent protection. If it is hard to use, it will not be used.
The security utilities collectively protect against the most common web application vulnerabilities.
Cross-site scripting (XSS): sanitizeHtml strips tags and event handlers. sanitizeInput removes script patterns. The content moderation service (Framework Series #13) catches toxic content that sanitizers do not cover.
Injection attacks: sanitizeSql, sanitizeNoSql, sanitizePath, sanitizeUrl, and sanitizeRegex each prevent injection in their respective contexts. Parameterized queries are the primary defense; sanitizers are the backup.
Data exposure: maskEmail, maskIp, and maskSensitiveData prevent user data from appearing in logs. The ErrorResponseBuilder (Framework Series #9) prevents internal error details from reaching clients.
Token attacks: JWT utilities detect expired tokens, untrusted issuers, and wrong token types. The Cognito middleware handles full validation. The API key service handles service-to-service auth.
CSRF: Token generation and validation prevent cross-site request forgery on state-changing operations.
None of these utilities is sufficient on its own. Security is defense in depth — multiple layers, each catching what the others miss. The security module provides the utility layer. The middleware provides the pipeline layer. The error handling provides the response layer. Together, they form a security posture that is consistent across all 25 services.

Security utilities are the least visible part of the platform. Users never see a sanitizer run. They never notice a masked email in a log. They never know that a CSRF token was validated on their last request. And that is exactly the point. Security should be invisible when it works — and unmistakable when it catches something. The security module in tctf-utils makes protection the default, not the exception. One import, one call, every service, every request.
Never miss an edition
Subscribe to get TCTF newsletters delivered to your inbox.