The FreePBX helper's one key rings a phone
kamo-asterisk-support holds a single API key, documented and issued as a VOIP_RECORDING_UPLOADS key, and sends its ring events with it. The ring endpoint accept...
Give a public chat's own turns the message rate limit, not the session one
Every public-chat path lives under /sessions/, so the limiter's contains("sessions") test put the conversation itself on the session-creation budget: 3 requests...
KamoAI internal endpoints never leave the gateway
Carves the seven KamoAI internal subtrees (master spec §7 item 13) out of the wildcard forwards, and with them the server-to-server endpoints that predate the p...
Give the gateway a memory request and limit
The deployment had no resources: block at all. No request, so the scheduler could not reason about this pod's footprint; no limit, so nothing capped the JVM's -...
Forward commission Stripe Connect webhooks, which reached nothing at all
CommissionService registers **************** directly with Stripe (OrgStripeSetupService), but no /api/commissions/** forward existed anywhere in this gateway -...
Carve out VOIPService's internal SMS-template API, and never forward a client's own X-Internal-Auth
Two related gaps in the same trust boundary. /api/voip/sms/templates/** was forwarded wholesale by the /api/voip/** wildcard. VOIPService's InternalAuthFilter ...
Derive X-Real-IP from the right-most hop, not the client's own
forward() set the outgoing X-Real-IP to X-Forwarded-For.split(",")[0] -- the LEFT-most hop, which is the one part of that header a client controls outright: a r...
Bound upstream calls with a connect and a read timeout
Both RestTemplate beans were built from a bare JdkClientHttpRequestFactory with no timeout at all. One upstream that accepts the connection and then never answe...
Stop logging session tokens, OTKs and auth headers on every request
forward() printed the entire copied-header map on every single call -- System.out.println("APIService: copied headers: " + outHeaders) -- which serializes X-***...
Accept OCI image indexes when resolving the built digest [skip ci]
The images are pushed as OCI image indexes, so asking the registry for a single image manifest only answered 404, the digest came back empty and the check faile...
Restart when a same-commit rebuild leaves pods on the old digest [skip ci]
The rollout step tried to detect a same-commit rebuild by comparing the Deployment's image reference before and after `set image`. "Apply manifests" has already...
Never forward client-supplied identity headers upstream
forward(), forwardWebhook() and forwardCallback() copied every inbound header except Host (and, for the sessionless two, the credential headers) onto the upstre...
Forward carrier SMS webhooks, which reached nothing at all
Traefik sends every path on api.kamocrm.com to this gateway with no path split, and this class forwarded /api/voip/** and nothing else. So /api/bulktext/inbound...
Forward carrier SMS webhooks, which reached nothing at all
Traefik sends every path on api.kamocrm.com to this gateway with no path split, and this class forwarded /api/voip/** and nothing else. So /api/bulktext/inbound...
Prove the deploy by digest, not by tag
The preceding commit stops `set image` being a silent no-op. This asserts the outcome: after the rollout, the tag is resolved to a digest at the registry and th...
A rebuild of the same commit deployed nothing and reported success
The image is tagged with the commit SHA, so rebuilding the same commit produces an identical image reference. `kubectl set image` then changes nothing, the Depl...
Drop the /api/settings forward, which pointed at nothing
EmailService serves the sync-integration controller at **************** not /api/settings/integrations — so that forward reached a path the service does not map...
Route /api/contacts, /api/calendar and /api/settings to EmailService
KamoMobile's contacts and calendar screens 404'd on every request. Neither path was routed at the api host, so nothing reached EmailService and the service logg...
Give the rollout room for the 15s minReadySeconds now costs
progressDeadlineSeconds was 60. That is the window a rollout has to show progress before Kubernetes gives up and marks it failed, and the previous commit added ...
APIService never shut down gracefully at all
Deploys replaced the only pod of each service with nothing to catch the requests in flight. Three settings, applied across the fleet: - preStop sleeps 10s befo...
Encode the decoded token exactly once on the way upstream
21670e5 routed SocialWebhookController through UpstreamUri's FULLY-PRE-ENCODED entry point, but its URL is a hybrid: `token` is an @PathVariable, so Spring hand...
Forward the caller's query bytes on the public-facing proxies
The gateway stopped double-encoding forwarded query strings in 3c24d32, but four other proxies in this service still concatenated already-percent-encoded bytes ...
Forward the caller's query bytes instead of encoding them twice
Every URL this gateway builds is assembled from getRequestURI() and getQueryString() — both already percent-encoded — and was then handed to RestTemplate as a S...
Harden the kubectl download against flaky egress [skip ci]
dl.k8s.io over the runner's egress intermittently drops mid-transfer: curl: (56) OpenSSL SSL_read: decryption failed or bad record mac which fails the deploy ...
Defer mandatory-origin to Phase 1 — OriginMatcher opt-in (empty allow-list / missing Origin allowed)
Phase-0 widgets still call via their server-side proxy, so origin-locking has no benefit yet and every live public-chat key has an empty allow-list. Strict enfo...
Don't cache-invalidate keys on transient downstream errors (only on definitive invalid)
Only cache-invalidate genuinely-invalid keys, not origin-denied (prevents valid-key DoS)
On a cold Redis cache, a valid public-chat key with a wrong/absent Origin was cached as INVALID for 60s, denying the correct origin's requests for that window. ...
Bind WS relay handshake to Origin + add cold-cache key validation fallback
Extracts PublicChatController's private validateViaDownstream() into a shared PublicChatKeyResolver bean so PublicChatWebSocketHandler can reuse the same downst...
Make public-chat origin allow-list mandatory (deny on missing Origin / empty list)
Carve /api/voip/recordings/** out of the wildcard VOIP forwarder
The /api/voip/** catch-all in APIGatewayController was shadowing the dedicated VoipRecordingUploadController POST /api/voip/recordings/upload mapping when Sprin...
Raise file upload size limit to 500MB
Spring Boot defaults cap multipart parts at 1MB, causing background image uploads to fail with 500 in request.getParts(). Match SecurityService's existing 500MB...
Move CORS to Traefik via kamo-middlewares, remove in-app CorsFilter
The CorsFilter @Bean in the Spring app was silently not applying headers after WebConfig.java was removed. CORS is now handled entirely at the Traefik layer by ...
Remove duplicate CorsFilter and strip upstream CORS headers in gateway
WebConfig.java defined a second CorsFilter bean competing with CorsConfig.java's bean, risking duplicate header writes. Deleted it so only one CorsFilter exists...
Set application/json for JSON bodies forwarded to SecurityService
RestTemplate to upstream could leave Content-Type incompatible with Spring @RequestBody, causing 415. After reading the raw body, force APPLICATION_JSON when th...
Validate Forgejo HMAC-SHA256 signature instead of plain secret header
Forgejo sends webhook secret as X-Gitea-Signature / X-Forgejo-Signature HMAC-SHA256 hash, not as a plain header value. Read body, verify HMAC, then forward to S...
Include SUBSCRIPTION_CATALOG in default scopes fallback
When MediaService doesn't return scopes in the validation response, default to including both PUBLIC_CHAT and SUBSCRIPTION_CATALOG scopes. This ensures public c...
Use getServerName() instead of getHeader(Host) for original host resolution
ForwardedHeaderFilter consumes X-Forwarded-Host and adapts getServerName() accordingly, while getHeader(Host) returns the raw HTTP Host which may be a K8s inter...
Use HTTP on port 80 for meet service URL to match other services
The HTTPS endpoint on 8443 causes SSL cert mismatch when routing through K8s internal DNS. All other services use HTTP on port 80.
Preserve upstream X-Forwarded-Host instead of overwriting with own Host
When an upstream proxy (Next.js) sets X-Forwarded-Host to the browser's original host, APIService now preserves it instead of overwriting with its own Host head...
Explicitly forward visitor IP to downstream services
ForwardedHeaderFilter consumes X-Forwarded-For from the incoming request, so when APIService forwards to SecurityService via RestTemplate the header is missing....
Move AccessEnforcementFilter to scanned package, revert ComponentScan
The widened @ComponentScan caused a corsFilter bean conflict between CorsConfig and WebConfig. Moved the filter into com.kamo.api.app.config (already scanned by...
Add Redis connection config to K8s configmap
APIService pod was failing to start because spring-boot-starter-data-redis auto-configuration couldn't connect to Redis at localhost:6379. Points to redis.kamo....
Like what you see shipping?
All of it arrives in your workspace on its own. Start on the free plan and read this page again in a month.
