Ir para o conteúdo
KAMOUniverse

Changelog

O que há de novo.

Cada melhoria para a plataforma, como ele envia.

  1. · SecurityService

    Record lead reads and refuse un-auditable exports

    HIPAA §164.312(b) for SecurityService. The platform records every lead CHANGE (LeadHistory) and every login (SystemAccessLog) and records no READ at all, so "who looked at this borrower" — the first question a §164.402 breach assessment asks — was unanswerable. PhiAccessAuditor builds events from the OTK/*** session map the endpoint already resolved, so the identity in the audit row is the same identity that authorised the request. Client IP comes from X-Forwarded-For; behind Traefik, remoteAddr records the ingress pod and is worthless for detection. Denials are recorded too — probing is what detection looks for — and a sessionless caller lands on a sentinel org rather than being dropped. Reads: single lead fetch = VIEW, grid = one LIST event per lead actually shipped (taken from the projected DTOs, so a lead filtered out by VIEW_UNASSIGNED_LEADS never appears as one that was read). 401/403/404 all record a denial. Exports fail closed — access-log CSV, HMDA LAR, the QC training corpus and the platform-wide extraction corpus. Each records one event per record BEFORE the file is built, and a failed audit write returns 503: "we handed out the data but lost the record" is the one answer that cannot be walked back. The platform-wide corpus crosses every tenant, so each record is attributed to the org that owns it, not the operator's. Only identifiers are logged, never content. **************** memberId) is gone: an export now has to hold its rows to record who was in them.

  2. · kamo-signer-monorepo

    Documento público de citação/estimação em /{locale}/q/{token}

    A superfície voltada para o cliente para o sistema de cotação comercial. Server- rendered assim o org's branding está na primeira pintura — um flash sem marca em um documento comercial é exatamente o que isso não deve fazer, e é por isso que a página emite marca personalizada propriedades inline via QuoteBrandVars em vez de trocá-los após a hidratação. Vive no kamo- singer porque esse aplicativo já tem o HostRegexp ingress, autocert cobertura, aliases DNS e o pacote de tema com o nome de máquinaOverride — e porque cotação -> aceitar -> e-sign é então um hop, uma vez que EsignGenericEnvelope se liga a qualquer registro via (context type, context id). A reutilização só foi viável uma vez que o /{locale}/ i18n trabalho aterrou; todas as 31 strings são chaves de tradução fontes do dicionário. Dinheiro e formato de datas através do next-intl usando a própria moeda da cotação e a fuso horário resolvido pelo servidor (membro beneficiário -> org -> UTC), nunca o dispositivo do visualizador. Uma expiração executada na zona errada pode indicar uma data em que a oferta já está Morto. Adiciona o primeiro bloco de impressão real @media da plataforma. "Imprimir a factura" em kamo-internal sempre imprimiu todo o cromo do aplicativo porque não existia nenhuma regra de impressão @ media em qualquer lugar; aqui a mesma marca de impressão como um documento de carta limpo com a marca acento preservado (impressão-color-ajustar: exato — uma citação incolor lê como um rascunho), A barra de ação foi suprimida e a página-break-inside: evite em linhas e totais. Os estados terminais são deliberadamente assimétricos: EXPIRED recebe uma página de marca honesta nomeando a data e os dados de contato da org, seguro porque o chamador já provar a posse de um token de 256 bits; desconhecido/revocado/errado-org todos colapsam para um página genérica para que a superfície não possa ser sondada. O cliente lista a razão antes de renderizar. O token nunca viaja para frente em um URL — a resolução do lado do servidor o publica em um corpo, e o BFF encaminha o verdadeiro cliente IP explicitamente porque APIService selos X-Forwarded-For de seu próprio pedido de entrada, que de outra forma colocaria todos visualizador em um balde de limite de taxa chaveado para o IP do pod. Adiciona serviço de segurança URL ao mapa de configuração (verificado: o serviço de segurança do kamows existe no espaço de nomes kamo).

  3. · SecurityService

    Quote REST surface — staff management and public share links

    Staff endpoints at /api/security/commerce/quotes and an unauthenticated resolver at /api/security/public/quotes. QuoteController extends BaseLosController rather than copying POSController's preamble: CallerSessionResolver accepts the X-OTK attribute, the X-***-Token header AND the *** cookie, where POSController's private getOrgId reads only the OTK attribute. Every handler is rights-gated (VIEW/MANAGE/SEND_QUOTES). Most of the commerce surface is session-only — any authenticated member of any org can mutate orders — and that is deliberately not repeated here, because a quote commits the org to a price in front of a customer. QuotePublicController needs no security-config carve-out: the Spring chain is anyRequest().permitAll() and OTKPreAuthFilter only acts on an X-OTK header, which a browser following an emailed link never sends. Design points: - The token arrives in the POST body, never a URL. APIApp.forward() logs request lines, and a bearer credential in a path would land in those logs. - Not an enumeration oracle: unknown, revoked and org-mismatched tokens all return one indistinguishable generic failure. Only EXPIRED gets a specific, honest answer, which is safe because the caller already proved possession of a 256-bit token. - The org derived from the request host is cross-checked against the org on the link row. Without it, a valid link for org A opened under org B's host would render A's pricing wearing B's branding — a phishing primitive. - no-store / Referrer-Policy: no-referrer / X-Robots-Tag: noindex on every response. The page loads logos from theme.<org>, and a Referer would carry the token off-origin. PublicLinkRateLimiter is Redis-backed because SecurityService is multi-replica — in-process counters would be a limiter in name only. It is explicitly DoS and log-hygiene protection, NOT the anti-guessing control: 256 bits of entropy is. Failures are counted far more harshly than successes, since one office opening a forwarded quote fifty times is normal while fifty misses is not. It fails open, so a Redis outage cannot take the quote page down. QuoteEmailClient reuses the EmailService transactional path (X-Internal-Auth, QUOTE_SENT template) and sends only the headline figure plus the link — never the itemised pricing, so a forwarded email is not itself the disclosure. Pre-existing test failures unchanged by this commit (verified by stashing this work and re-running on clean HEAD): **************** NPE on a null EntityManager, and the legacy com.retval SecurityServiceApplicationTests missing a @SpringBootConfiguration. REQUIRES a KamoInitializerService run before these endpoints work: the quote tables are new and ddl-auto is none here, so the service boots normally and only the quote endpoints 500 on a missing relation until KI has run.

  4. · kamo-shared-library

    Quote/estimate domain — entities, pricing engine, share links

    Adds a first-class Quote alongside the existing commerce spine. The pre-existing "ServiceQuote" is an Intent plus a JSON blob carrying one offering and one flat amount, with no recipient, no expiry and no UI; it is not a foundation. Extending Intent directly was rejected too: commerce_intents is shared by LOAN_APPLICATION, RENTAL_REQUEST, SUBSCRIPTION_SIGNUP and PURCHASE_REQUEST, so ~25 quote columns would put permanent NULLs on the mortgage and rental paths. Entities **************** Quote engagement-linked, versioned, valid_until + valid_for_days QuoteLineItem snapshots name/sku/price so a sent quote never re-prices QuoteShareLink bearer credential, SHA-256 at rest, revoke/expiry/view counts QuoteLinkView per-open audit (ip, user-agent) QuoteEvent append-only lifecycle trail Totals are server-authoritative. QuoteTotals is the single writer of every money field and the request DTOs carry no totals at all — deliberately unlike createRetailOrder, which takes subtotal/tax/total verbatim and can be handed a populated order with total = 0. Line discounts apply before the quote-level discount, discounts can never exceed their base, intermediate maths stays at scale 4 and only the grand total rounds to the currency's minor unit. Share tokens: 32 bytes of SecureRandom as 43 base64url chars (256 bits). Same entropy as the e-sign signer token but 21 chars shorter, keeping the emailed URL under the 76-char plain-text wrap limit. Only the SHA-256 is persisted. Tax is honest rather than invented: the platform has no tax engine (TaxClass/ TaxZone/TaxRate are authorable and read by no calculation), so ENGINE mode reports NOT_CONFIGURED instead of fabricating a figure on a commercial document. QuoteLineItem.taxClassId is UUID, matching TaxClass's real PK — Offering.taxClassId is a Long against a UUID-keyed table and is structurally unresolvable. Also: rights 178-180 (VIEW/MANAGE/SEND_QUOTES) and the QUOTE_SENT canonical email template, registered in CANONICAL_KEYS (absent there means a permanent 404). 35 tests covering the pricing engine and token hygiene. Full suite: 896 passing. Schema note: these are new tables. KamoInitializerService must run before SecurityService serves the quote endpoints — ddl-auto is none there, so the service boots fine and the endpoints 500 on a missing relation until it does.

  5. · KlusterServices

    Contenção de saída para o resolvedor avatar, como um CiliumNetwork Policy

    Restabelece o que 8f6c672 reverteu, na única forma que realmente funciona aqui. A tentativa anterior falhou porque uma Política de Rede simples não pode expressar isto topologia de cluster. Três das quatro dependências críticas do Serviço de Segurança vivem de o nó, não na rede pod — barahidb-0 e nats-k1m1-0 executar a hostNetwork, e MinIO é um processo de host — então um podSelector nunca pode combiná-los. A parte não óbvia, e a razão pela qual a primeira tentativa de patch também falhou, ipBlock para a subnet do nó também não o corrige: Cilium classifica o nó como a identidade reservada do host/remote-node e as regras CIDR não correspondem deliberadamente Identidades reservadas. aEntidades é a única construção que a expressa. Ambos os modos de falha foram reproduzidos contra um canário descartável antes de escrever Isto, em vez de contra a produção. O canário carregava a mesma regra definida no uma etiqueta distinta, que é como a versão ipBlock foi capturada silenciosamente bloqueando BarataDB, NATS e MinIO enquanto parece perfeitamente razoável. Verificado no canário, então confirmado no SecurityService sem reinícios e sem Erros de ligação: ALLOW dns, crdb:26257, nats:4222, minio:9000, redis:6379, irmãos:80, mundo:443 Nó BLOCK:22, nó:443, kube-api:443, redis-via-node:6379, 169.254.169.254, loopback:9000 Os últimos quatro são o ponto: um SSRF no resolvedor agora não tem para onde ir. A a guarda no código continua a ser o controle primário — é a única camada que compreende DNS religar — e isso é defesa por trás disso. O cabeçalho carrega o procedimento canário. Qualquer um que mude isto deve voltar a executá-lo. o modelo de identidade de Cilium não se comporta da forma simples A intuição da Política de Rede sugere, e não há ambiente de estadiamento.

  6. · kamo-internal

    Mostrar uma pilha de rostos para conversas em grupo

    Um fio com cinco pessoas nele foi representado por uma face arbitrária, que lê como uma mensagem de uma pessoa. Até três participantes sobrepõem-se, portanto "Várias pessoas" é legível de relance sem precisar analisar a linha de nome. Capturado em três: passado que os círculos encolhem abaixo da reconhecimento e da linha torna-se textura em vez de informação, e o restante já está indicado em o texto "e N outros" ao lado dele. O leitor é filtrado de sua própria lista de threads, e a outra parte lidera — faces lidas da esquerda para a direita, por isso o correspondente primário tem de ser o mais à esquerda e a que carrega o distintivo não lido. Cada face recebe um anel na fila cor de fundo; sem ele uma pilha de fotos semelhantes se funde em uma forma. A resolução abrange agora os primeiros participantes e não apenas a liderança, Caso contrário, o segundo e terceiro rostos sentar-se-iam como monogramas permanentemente.

  7. · SecurityService

    Instrumento da cadeia, colhe entradas expiradas, divulgações de registros

    Cada modo de falha neste recurso é silencioso. Um disjuntor travado, um trabalhador fila descartando cada tarefa, um provedor que começou a retornar HTML — cada looks exatamente como "este correspondente não tem foto", que também é o que correto comportamento parece. Não há diferença visível entre o trabalho e o trabalho. completamente morto, por isso tem de ser medido. Métricas (Prometheus já estava ligado, por isso isto é na maioria nomeação): cache hit razão, duração da cadeia marcada pelo nível que respondeu, por fornecedor hit/miss, profundidade da fila do trabalhador, e um total de tarefas descartadas. O último usado anteriormente Política de descarte, que cai silenciosamente por design; agora aumenta um contador, e o contador é declarado antes do executor porque os inicializadores de campo são executados em ordem de origem e confiar na captura preguiçosa quebraria em uma ordem inofensiva. As divulgações vão para o seu próprio registrador. Apenas o nível de identidade é pessoal divulgação — os níveis de domínio transmitem o lado direito de um endereço, que Não identifica ninguém — e é gravado antes da chamada em vez de depois, por isso o A trilha existe mesmo quando o pedido falha. O endereço é escrito na íntegra e deliberadamente: o ponto é responder "quem eram os dados dessa pessoa compartilhados com", e um hash não pode responder a isso. O ceifeiro fecha um vazamento: deleteExpiradoAntes de existir e nada o chamou, então a identidade do remetente cresceu para sempre. Uma caixa de correio movimentada toca milhares de correspondentes uma vez cada um e deixa uma linha negativa para trás para cada um. Testes cobrem o captador contra um socket real, porque os casos interessantes são todos os comportamentos do provedor em vez de lógica: um 404 carregando uma imagem válida (DuckDuckGo), um soft-404 que serve HTML, uma origem understating Content-Length, a 1x1 stub, e um redirecionamento voltado para a rede pod. O cliente de produção não pode ser apontado para um servidor de teste loopback — seu pino DNS se recusa a resolver qualquer coisa privado, que é todo o ponto — de modo que aqueles concorrem contra um cliente relaxado e o pin é afirmado separadamente.

  8. · kamo-internal

    Promover uma matriz LLPA ingerida em ajustes do console

    O painel de ingestão só poderia publicar uma folha de taxa. Para uma fonte LLPA MATRIX documento que agora mostra um cartão "Promover como ajustes LLPA": escolha o existente versão de folha de taxa os LLPAs ajustar (+ um grupo opcional), e cada aprovado O balde FICO/LTV torna-se um ajuste de preços condicional. Os nomes dos resultados como muitos foram autores e, honestamente, quantas linhas foram ignoradas porque um balde Não podia ser lido com segurança ou já estava presente. ingestãoApi.promoteAjustes Posts para o novo relé de entrada.

  9. · SecurityService

    Relé Gateway para promover uma matriz LLPA em ajustes condicionais

    ************* Ganhar ajustes de promoção, espelhando o Promove-rate-sheet relé: Deus-elegível portão, organizaçãoId servidor-faixado em a carga útil via write(), a jusante 4xx retransmitiu verbatim. Deixa um mordomo virar um reviu a matriz LLPA em ajustes reais de preço por cenário da consola.

  10. · VOIPService

    Gravar gravação de chamadas, correio de voz e acesso transcrito

    A gravação não deixou rasto. Uma chamada gravada é o PHI mais denso o lojas de plataformas — a conversa não-reditada — e "que ouviram isto chamada" não foi respondida, que é a primeira questão uma violação §164.402 A avaliação pede. Wires CallRecordingAuditor em cada leitura que divulga: - GET /gravings/{id}/audio CALL RECORDING / DOWNLOAD - Está bem. CALL RECORDING / DOWNLOAD - get /gravings/{id} CALL RECORDING + TRANSCRIPT / view - Está bem. CALL RECORDING / DOWNLOAD - GET /instances/{id}/voicemail TRANSCRIPT / LIST, por linha A transcrição é a que se esconde. Não é um download de mídia; ele monta dentro dos metadados JSON e dentro de cada linha de correio de voz, e é o mesmo PHI como o áudio em uma forma que greps. Tem seu próprio evento porque um a contabilidade das divulgações é montada por recurso. O serviço de Byte falha no fechamento, então o PhiAuditDisponibilidadeAdvice torna um unloggable O pedido foi bom, nós decodificamos para servir PHI que não pudemos explicar. Recusas também são registradas; sondagem é o que a detecção procura. Também fecha uma lacuna de autorização encontrada ao fiar isso: POST /gravações/pesquisa tomou uma instânciaId do corpo de solicitação e nunca verificou a org do ouvinte, então resolveu o provedor de outro inquilino Chamar IDs para os seus IDs de gravação. Todos os outros pontos de serviço já passa por findByIdAndOrgId; este agora também. A autorização de reprodução de gravação é deixada como está e documentada no local: org propriedade é o único cheque, então qualquer membro pode reproduzir qualquer gravação em a sua org. Restringi-lo significa decidir se "própria extensão" é o limite e qual direito o levanta — isso é uma decisão do produto, não um Adivinha para fazer aqui, e todos os acessos estão gravados agora.

  11. · AIService

    Gravar acesso PHI em cada leitura de histórico de conversação de IA armazenado

    Wires the shared §164.312(b) spine into the paths that read persisted prompts and completions. The assistant is the module the compliance assessment put OUTSIDE the boundary — prompts fan out to external providers under no BAA and are then kept indefinitely — so reads of that store are the disclosures most in need of a trail, and they had none. Audited: the session list and history search (one event per conversation, not one per request), a single conversation's metadata and its messages, the public widget reading its own transcript back, and the handoff bundle. Refusals are recorded too — probing another org's guids is what detection looks for — and an unauthenticated attempt is attributed to the sentinel org rather than dropped, since the recorder rejects what it cannot attribute and that is the event most worth keeping. The handoff bundle is a DISCLOSE, not a VIEW: it carries a visitor's conversation out of AIService and in front of a human in another product. That kind fails closed, so a lost record now withholds the transcript and answers 503 rather than serving data it cannot account for. Identifiers only. Never a message body, a title, or the search term — a member searching their assistant history types the borrower's name into it, and an audit trail that reproduced the prompt would just have moved the PHI into the log stream. Completes d428ee3, which a concurrent session swept up mid-change: the auditor was missing recordDeniedInternalView, and PublicAiChatController's new constructor argument had not reached its test.

  12. · MediaService

    Record chat message reads on the endpoints that return bodies

    Sending a message left a row; reading one left nothing. Every chat, support and social thread on the platform renders from a handful of endpoints here, and none of them recorded anything, so "who read this conversation" had no answer — which is the first question a 164.402 breach assessment asks. ChatMessageAccessAuditor emits a PhiAccessEvent (CHAT_MESSAGE) from the five paths that hand a caller message text: GET **************** LIST GET /api/media/sessions/chat LIST POST **************** VIEW GET **************** LIST POST **************** VIEW One event per message, not one per request. "They opened the thread" is not an answer to "was this individual's message disclosed", and the message ids are already in hand at every call site, so a request-shaped row would have been a choice rather than a limitation. The thread read records AFTER the mute filter, so a row means a body that caller was actually handed. The chat list is in scope because each row carries up to 140 characters of the thread's last message — that is content, not metadata. Rows with an empty preview record nothing. Deliberately NOT recorded: unread counts, presence, last-seen, typing, read receipts, session-member lists, mutes, ticket detail (metadata plus status history, no bodies) and support reporting aggregates. A trail is only useful if a row means someone saw content; rows that stand for nothing bury the ones that do. Refusals are recorded too, against the thread guid — no message id exists yet at the point of refusal, and repeated refusals against thread guids is the shape probing has. An unauthenticated reader is attributed to a sentinel org rather than dropped: the recorder rejects unattributable events, and that is the single most interesting event this trail can hold. Client IP comes from X-Forwarded-For with remoteAddr as fallback — everything arrives via Traefik, so remoteAddr alone records the ingress pod. Public chat takes a second hop through APIService, which sets X-Forwarded-For to the visitor address and moves the browser's User-Agent to X-Original-User-Agent, so the auditor prefers that header; the literal User-Agent on that hop is APIService's own HTTP client and identifies nobody. Visitor reads are attributed to the org from X-Public-Chat-Org-Id with no member id: a visitor has no member identity of their own, but the org owns the records, so the read stays attributable. Identifiers only, never message text. An audit trail that reproduces the message it audits has just copied the PHI into the log stream. Also completes eed3817, which swept this work in mid-edit and left MediaController referencing PhiAccessKind before the import existed — that revision does not compile. This one does: mvn -o test is 16/16 green.

  13. · DocsService

    Gravar o acesso do byte nos quatro caminhos do documento que não gravaram nada

    /download já logado através de A plataforma é apenas o local de injeção de bytes. Os objectivos de irmãos que servem a exatamente os mesmos bytes — /stream, /bulk-download, o modelo convertido-PDF e o miniatura da página do modelo — nada gravado, por isso quem quiser bytes do documento sem deixar um traço só tinha que escolher um URL diferente. Todos os quatro agora emitem um PhiAccessEvent no ponto de acesso foi verificado eo Os bytes vão ser servidos. O download em massa emite um evento por documento em vez de um por pedido. "Eles exportado 400 documentos" não é uma resposta para "foi o registro deste indivíduo divulgadas", e por-document linhas são o que fazem que respondêvel seis anos depois Em conformidade com o §164.528. IP cliente vem de X-Forwarded-For com RemoteAddr como recurso: cada pedido chega através de Traefik, então remotoAddr sozinho iria gravar a cápsula de entrada para todos os acessos e ser inútil para detecção. Uma tentativa não autenticada é atribuída a uma organização sentinela em vez de o gravador rejeita eventos imputáveis e um pedido de documento bytes sem sessão é exatamente o evento que vale a pena manter. Isso fica ao lado das linhas ImgLogDownload existentes em vez de substituí-las: Estes são os registos do domínio das imagens, esta é a trilha transversal, uma contabilidade de As divulgações são reunidas de. Nota ImgLogService, que lê essas linhas, ainda Tem zero chamadas em qualquer lugar na plataforma.

  14. · KlusterServices

    Contêm saída do resolvedor avatar; note o pool de despejo Redis

    SecurityService agora executa um buscador cujo URL de destino é escolhido por um não confiável Parte — um registo BIMI é o conteúdo DNS publicado por quem possui um domínio de envio, e qualquer pessoa se torna um domínio de envio por e-mail de um inquilino. Nada neste grupo Onde tal pedido poderia ir: Redis respostas não autenticadas em 6379, MinIO em 9000 com credenciais de raiz, BarataDB em 26257, e os metadados do nó endpoint é alcançável a partir de qualquer cápsula. O resolvedor aplica seus próprios controles em código, e aqueles permanecem o principal defesa porque eles são a única camada que entende DNS rebinding. Isto é... o segundo, então um bug ainda não pode chegar a um endereço privado. Deliberadamente permitir-mais do que permitir-lista. Estes serviços alcançam legitimamente hosts, então enumerar destinos seria ininterruptível e quebraria Em silêncio. Em vez disso, permite a internet pública com todas as áreas privadas, exceto, Em seguida, re-permite os portos específicos em-cluster realmente necessários. Escopo das cápsulas que executam fetches não confiáveis — aplicando-o namespace-wide seria cortado serviços cujo egresso nunca foi auditado. A mudança do Redis é um comentário e uma colisão de bases de dados, não uma mudança de comportamento. Ele registra por que o cache remetente-avatar armazena apenas sequências de metadados curtas: maxmemory 2gb com todos oskeys-lru despejos em toda a instância, e DB 0 mantém cada live sessão, para que uma cache de bytes aqui assinasse os usuários aleatoriamente sob pressão e ser mal diagnosticado durante dias.

  15. · kamo-internal

    Mostrar as fotos do remetente e do destinatário

    A caixa de correio traduziu uma única letra num único gradiente partilhado para todos. todas as fileiras pareciam iguais e o círculo não trazia informações. Agora mostra o foto real quando um é conhecido, e um monograma de duas letras quando Não é. Um SenderAvatar substitui sete círculos iniciais laminados à mão. Que importava para mais do que consistência: cada um tinha a sua própria ideia de como dividir um endereço, e o cache do avatar é chaveado nessa divisão, então qualquer discordância entre a lista e o painel de leitura iria silenciosamente metade da taxa de sucesso e mostrar a mesma pessoa dois maneiras diferentes em uma tela. parseEndereço é agora o único lugar um RFC 5322 A morada foi desmontada. As visões de lista e thread também param de imprimir cabeçalhos brutos como "Jane Doe <jane@acme.com>" e mostrar o nome. Identidade vive em sua própria loja chaveada por endereço, nunca nos objetos de mensagem. Ambas as visualizações da lista pesquisam a cada 30s e em cada notificação do websocket, cada vez substituir seu array de mensagens por atacado — qualquer coisa fundida em uma mensagem seria limpou duas vezes por minuto, para que uma caixa de entrada ociosa pudesse piscar a foto para monograma e voltar Para sempre. Teclar por endereço também significa que uma resposta tardia não pode pintar a linha errada, porque as linhas não são o que é chaveado por. Os pedidos são coalescidos em um lote por tela: cinquenta linhas perguntando independentemente seriam cinquenta pedidos, e uma vez que cada chamada para este serviço menta uma chave única em primeiro lugar, Na verdade, cem. Uma falha é re- pedida uma vez depois dos 45s, porque o lado do servidor resolução é duas fases e um endereço desconhecido é respondido apenas em uma chamada posterior. Conversa O ListView está agora virtualizado. Obtém 50 linhas por página e re-renderizou todos eles dentro de um AnimatePresence com um por índice cambaleando em cada pesquisa; A adição de uma imagem a cada uma fez dela a superfície mais pesada da caixa de correio. Os remetentes sem resposta e de e- mailer- daemon recebem uma marca neutra em vez de um monograma — um face ao lado de um aviso de rejeição é ativamente enganosa. Fotos são decorativas (alt="") porque o nome é sempre texto visível ao lado deles, e levar referrerpolicy="no-referer" para que um identificador de thread nunca atinja um host de imagem. Cada avatar é servido de nossa própria origem — imagens de terceiros são obtidas, validado e re- hospedado server-side - que é o que permite o novo por pedido A política de img-src fica presa aos domínios do inquilino. Essa política é construída em o proxy em vez do próximo. config porque esta plataforma é marcada em branco: a política estática teria que nomear um inquilino e quebrar imagens para o resto. Também fecha um vetor pré-existente armazenado de pixels de rastreamento, desde o contato. fotoUrl é uma URL de texto livre renderizada diretamente em vários <img src>. next/image já não aceita SVG remoto. Foi um risco aceitável quando o único SVGs eram nossos próprios logotipos do tema; não é agora que a plataforma ingere marcas publicado em DNS de terceiros. Chamadores de contatos soltam o ORG ID codificado = 1 e o parâmetro de consulta orgId, correspondente à alteração da infra- estrutura que deriva da locação da sessão.

  16. · EmailService

    Avatar endpoints; fix unauthenticated reachability and contact tenancy

    Adds the mailbox side of sender avatars, and fixes three pre-existing holes this work would otherwise have built on top of. AUTHENTICATION. OTKPreAuthFilter used to reject a *bad* OTK but treat *no* OTK as anonymous-and-allowed, falling straight through to the filter chain — and the Spring Security chain here is anyRequest().permitAll(). Every mailbox, contact and calendar endpoint was therefore readable by anything that could reach the ClusterIP. Protected prefixes now fail closed. The fix lives in the filter rather than in the security chain deliberately: reshaping the chain would touch thirty unrelated surfaces at once, including two servlets and the DAV endpoints. The exempt list is the complete set of genuinely session-less entry points — the CAN-SPAM opt-out link, the OAuth redirect target where the tenant rides in the signed state parameter, and the server-to-server surfaces that authenticate with X-Internal-Auth. CONTACT TENANCY. Every contacts endpoint took orgId as a query parameter, or reached a book by id with no ownership check at all, so changing a number in the URL read another tenant's contacts — names, addresses, phone numbers, notes. The org now comes from the session and every book- or group-scoped operation proves the target belongs to it. Absent targets return 404 rather than 403 so an id in another tenant is not confirmed to exist. orgId is still accepted on the two list endpoints and ignored, so clients do not break mid-deploy. That change also fixes a silent bug: searchContacts derived orgId from bookId and passed null when no book was selected, into a query whose first predicate is contactBook.organization.id = :orgId. Book-less search therefore always returned nothing — which is exactly how the mail recipient autocomplete calls it. EMAILLOGOSERVICE. It fetched an operator-supplied URL with no scheme or address check, and transcoded the result on a Batik that resolves external references by default. A logo SVG containing <image **************** would have been dereferenced from inside the cluster. Now guarded on both ends. MESSAGEINDEXER. resolveMemberIdFromEmail pulled the entire members table into heap and filtered it in Java, once per indexed message, on a 1Gi pod — the code said so in a TODO. Same semantics, one indexed query. The avatar surface is one batched endpoint rather than a field on the message DTOs. Both list views poll every 30s and replace their message array wholesale, so an avatar carried on a message would be wiped twice a minute and the UI would flicker forever; and EmailMessage is shared by the folder list, reading pane, thread view and search, so changing its shape would touch all of them at once. The directory-photo endpoint is server-to-server only — exposed to a browser it would be a directory-enumeration oracle for the tenant.

  17. · SecurityService

    Sender-avatar resolution chain for the mailbox

    Resolves a correspondent's email address to a picture, working outward from what Kamo already knows: member photo, then a photo someone attached to a contact, then the tenant's connected Microsoft/Google directory, then the sender domain's BIMI mark, then its favicon, then Gravatar. Anything unresolved renders a monogram, so the chain always terminates locally. The directory tier is worth calling out: the connect flows here already request admin.directory.user (Google) and User.ReadWrite.All + Directory.Read.All (Microsoft) alongside the mail scopes, so for Workspace and 365 tenants this returns authoritative photos with no re-consent and no third party involved. It should beat anything on the public internet and usually will. Two phases, never one. A request reads cache only; unknowns go to a bounded worker with DiscardPolicy — running an overflowed task on the caller would hand a request thread to a third-party host, and a discarded resolve costs nothing because the next poll asks again. A full-miss chain is DNS plus four HTTP calls, and a page carries dozens of addresses; nothing about opening a folder may depend on third-party availability. Decoupling also keeps resolution from correlating with a human opening a message, which would make it a read receipt — the exact behaviour the mailbox already blocks by default for remote images. This is the platform's first fetcher whose target URL is chosen by an untrusted party: a BIMI record is DNS content published by whoever owns a sending domain, and anyone becomes a sending domain by emailing a tenant. There is no egress NetworkPolicy and no forward proxy, so the controls are in code. SafeImageFetcher pins DNS — a custom resolver hands OkHttp only the addresses that passed PublicHostGuard, closing the rebinding window that defeats a naive pre-flight check — refuses redirects, caps bytes as they stream rather than trusting Content-Length, and requires an image content-type. SvgSanitizer parses and re-serializes against an allowlist instead of rasterizing. Batik 1.17 resolves external resources by default, so handing it a hostile SVG would dereference <image **************** from inside the cluster after the URL check already passed. The Tiny PS profile forbidding that is a statement about conforming files, not about bytes fetched from an attacker's URL. Provider miss semantics are all different and all cache-poisoning if treated as hits: Gravatar is asked with d=404 because every other default returns 200 with a placeholder; Google's no-match is a 404 carrying a byte-identical globe, so it is rejected by content hash; DuckDuckGo returns 404 with a real image body, so status decides. Bytes are re-hosted under a random name — never one derived from the address, which would let anyone probe whether a given person corresponds with this platform — and served through an authenticated route. Redis holds metadata only. That instance is 2GB with allkeys-lru on an emptyDir, sharing a keyspace with every live session; caching image bytes there would evict sessions and sign users out at random.

  18. · kamo-shared-library

    Sender identity model + org-scoped batch email lookups

    Backing store and queries for the mailbox sender-avatar chain. SenderIdentityEntity is the durable tier of the avatar cache. org_id is part of the natural key rather than decoration: USERS.EMAIL is unique only per security provider, so an answer derived from tenant data must not be visible to another tenant. External-tier answers (BIMI/favicon/Gravatar) are genuinely global and share a sentinel org so every tenant shares the hit rate. Misses are stored as rows, not absences — without that, every unresolvable sender re-runs the whole provider chain on every inbox poll. OrgAvatarSettingsEntity splits the chain by what actually leaves the building. The identity tier defaults OFF because it discloses a digest of a correspondent's full address to a third party, which on a lending CRM is a decision about a borrower identifier rather than a display preference. The repository additions exist because none of this was reachable before: - ContactEmailRepository had no lookup by address at all — only substring LIKE for the autocomplete — so a contact that had a photo could not be resolved from a From: header. - Member/User batch lookups are org-scoped and take pre-lowercased input. LOWER() is applied to the stored column only; emails are persisted verbatim (EmbEmail has no normalizer) and CockroachDB equality is case-sensitive. - The avatar-id projections return ids rather than entities on purpose. Both Member.avatar and User.avatar are lazy associations onto a JOINED hierarchy: a caller outside a transaction gets LazyInitializationException, and one inside gets a base-type proxy that never narrows to AvatarPhoto, putting the file extension needed to build the URL out of reach. - **************** replaces a findAll() full-table scan that ran once per indexed message. PublicHostGuard is shared rather than duplicated because it is a security control: it decides whether an address is somewhere we will send a request that an untrusted party chose. Tested against the real bypass techniques — IPv4-mapped IPv6, 6to4, NAT64, CGNAT and link-local metadata. Right 177 gates the disclosure decision.

  19. · kamo-shared-library

    Audit spine for PHI access — §164.312(b)

    The Security Rule requires recording access to ePHI, which means reads. The platform records changes well — LeadHistory, SystemAccessLog, UserHistory, LoanAuditEvent — and reads almost nowhere, which is why "who viewed this record" is currently unanswerable. That is also the first question a §164.402 breach assessment asks, so this one gap blocks both the audit control and the four-factor analysis. Capture layer only; no schema. The durable store sits behind PhiAccessLogWriter so call sites can be wired now and the sink can evolve — plain table, then append-only with a hash chain, then a signed export to object-locked storage — without rippling back through hundreds of call sites. Two properties carry the weight: - Failure handling belongs to the access kind, not the caller. EXPORT, DOWNLOAD and DISCLOSE fail closed: an unlogged bulk export is exactly the event you cannot reconstruct afterwards, and "we served the data but lost the record" is the worst answer to give an investigator. VIEW, LIST and SEARCH fail open and log loudly, because losing one line is not worth taking the product down. That asymmetry is deliberate and wants signing off rather than assuming. Callers under pressure resolve policy questions in whichever direction unblocks them, so the policy is not at the call site. - A denied read is still a read. Refused attempts are recorded, because the access pattern of someone probing records they cannot reach is what detection needs and is invisible if only successes are logged. Startup safety, which nearly went wrong: PhiAccessRecorder is a @Component and every service component-scans com.kamo, so a hard dependency on a writer bean would have failed context startup across ~40 services at once — a whole-platform outage from a feature nothing had called. The first fix, a @Configuration with @ConditionalOnMissingBean, does not work: component-scanned configuration is processed alongside user configuration rather than after it, so both writers register and injection fails on ambiguity. PhiAuditContextWiringTest caught that. The fallback now lives in the recorder via ObjectProvider.getIfUnique, where no ordering question exists, and ambiguous writers degrade to the default rather than failing startup. The default Slf4j writer is a starting point, not the destination — log lines are not tamper-evident and retention is 14 days against a six-year obligation. It exists so call-site wiring does not wait on the store. Identifiers are logged, content never is: an audit trail that reproduces the record it audits has just moved the PHI into the log stream.

  20. · SecurityService

    403 em vez de 500 quando o limite de conformidade se recusa

    Dois sites de chamada de featureService.enableFeature agora entendem o limite HIPAA: - / habilitar mapas PhiModuleNotPermitidoExcepção para 403 carregando a razão, espelhando o modo aplicadoViolationException manipulação diretamente acima dele. Sem esta a larga captura abaixo transformou uma recusa política deliberada em um 500. - O loop automático no caminho de listagem pré-checks com isPhiPermitido e Salta. Ele roda sobre todos os ServiceType completados enquanto apenas *listing* um características da organização, então jogando no primeiro módulo bloqueado teria feito a página recursos descarregável para um inquilino PHI em vez de esconder uma entrada. Inerte hoje — toda organização é manipulaPhi=false.

  21. · kamo-shared-library

    Enforce the compliance boundary at the feature switch

    Wires PhiTenantGuard into FeatureService, the single choke point through which a module is switched on for an organization. This is where the boundary stops being a policy object and starts being enforcement. Both halves, because either alone leaves a gap: - enableFeature throws PhiModuleNotPermittedException, so a PHI tenant cannot switch on a module Kamo cannot stand behind as a business associate. - isFeatureEnabled(Organization, ServiceType) reports a module disabled whenever the tenant handles PHI and the module is outside the boundary, whatever the OrgFeature row says. The row can legitimately say active — it was enabled before the tenant became a PHI tenant, and enableFeature does not run again on that transition. isPhiPermitted is the non-throwing form for callers that iterate. FeatureController auto-enables every completed feature while *listing*, so a throw on the first blocked module would fail the whole features page rather than skip one entry. PhiServiceTypeMapping binds ServiceType to PhiModule and is total, with a test that fails the build on any unmapped value. That test earned its keep on first run: it caught ServiceType.STORAGE, which a grep of the enum had missed, and then AI_CHAT, DOC_MANAGER, DOCS_EDITOR and DOC_CLASSIFIER. Without it, five service types would have been silently outside the boundary — the same omission failure PhiModule's constructor closes, reintroduced one layer up. New modules for the previously unmapped service types, each with an explicit disposition: object storage, document editor and classifier, support tickets, HR, inventory, legal, calculator and activity feed are inside; clubhouse and games are BLOCKED_PENDING_WORK on the grounds that they have not been assessed, which is a reason to block rather than to assume. Every organization is handlesPhi=false today, so this changes no behaviour in production. It becomes load-bearing the moment the first tenant is flagged.

  22. · kamo-shared-library

    Atribuir automaticamente um número de empréstimo compatível com MERS na criação do pedido

    LoanFile.loanNumber foi nulo ao criar (tanto em branco-criar como em lead-promote) e apenas definir quando um mordomo datilografou um — que MERS MIN cunhagem (segmento de 10 dígitos de um MIN é o número do empréstimo) e cada a jusante Identificador para um empréstimo sem cabeça. AppMortgageService agora atribui um número de empréstimo em ambos os sites de criação: um limpo 10 dígitos numéricos (MinMinter tiras não dígitos e necessita de um segmento <= 10 dígitos, assim, um numérico puro é a única forma segura), único por org através da (org id, loan number) índice com uma repetição limitada. ELE FALHA ABERTO para null on a raia de colisão ou um recurso não disponível — degradante para o manual anterior comportamento em vez de quebrar a criação de aplicativos. Um credor fornecido o número ainda se sobrepõe pelo caminho do patch do arquivo de empréstimo. Sem alteração de esquema (o coluna já existe, indexado).

  23. · kamo-signer-monorepo

    Renderizar datas de assinatura no fuso horário resolvido do destinatário

    SigningSession ganha um 'timeZone' necessário (IANA id) que o ESigService resolve da zona de membro do destinatário, depois da org, depois da UTC. Bem-vindoScreen agora passa-o explicitamente para o formatador, de modo que a data de expiração não depende de nenhum dos Configurações do dispositivo do visualizador nem o padrão UTC do provedor. Fazendo o campo necessário em vez de opcional capturado o local de construção de uma sessão literal (preview.ts) no momento da compilação. Além disso: - call setRequestLocale in the locale layout so translations attang to the URL segmento em vez de confiar no cabeçalho middleware sozinho - assevere no arquivo Docker que as mensagens/ sobreviveu `turbo prune', e documento por que .dockerignore deve manter .gitignore fora do contexto de construção; sem isso o acoplamento de ameixa deixa cair as traduções e as naves de imagem a renderizar chaves brutas sem nada falhar até que um assinante abra o documento

  24. · ESigService

    Resolver o fuso- horário do assinante do membro, em seguida, org

    As datas de assinatura foram renderizadas em qualquer zona em que o cliente não envelope expirando 2026-08-03T05:00Z poderia ler "ago 3" para um assinante do Pacífico para quem a ligação já está morta na noite de 2 de agosto. SigningSession agora carrega um servidor resolvido IANA timeZone: o destinatário próprio Membro.timezone quando eles são um membro e definir um, caso contrário o envio Organização.timezone, outra UTC. SignerZone é um resolvedor puro com testes, incluindo um que assevere todos os mapas TimezoneType para sua própria zonaId assim um novo o valor enum não pode enviar um RangeError do lado do cliente. Também corrige SigningSession.locale, que emitiu o nome de enum ("SPANISH") ao invés do código BCP-47 ("es") usado no segmento local da URL de assinatura.

  25. · InitializerService

    Crie HANDLES PHI e PHI EFFECTIVE DATE em orgs

    Espelha os dois novos campos da Organização, seguindo o OrgBrandingFieldsMigration convenção, então as colunas são reprodutíveis em qualquer ambiente em vez de apenas existe onde um passe ddl-auto aconteceu para executar. HANDLES PHI NÃO é NULL DEFAULT FALSE por isso os inquilinos existentes estão explicitamente fora O limite de conformidade. PHI EFFECTIVE DATE é timestamp, não timestamptz, porque o campo entidade é um LocalDateTime. O teste é um guarda de deriva, não uma reafirmação: ele lê nomes de colunas fora do entidade por reflexão e falha na compilação quando um campo de entidade não tem ALTER. Isso. é o modo de falha real desta convenção — alguém adiciona um campo, esquece o migração, e com ddl-auto:none nos serviços que erro de consulta em vez de na hora de compilação. Verificado por mutação: remoção da Data fi efetiva ALTER faz com que falhe com a mensagem acionável, restaurando-a Fica verde. Ambas as colunas já estão presentes no cluster; estas ALTERs são idempotentes.

  26. · kamo-shared-library

    ManipulaPhi e phiEffectiveDate on Organization

    Backs PhiTenantGuard com uma coluna real. HANDLES PHI NÃO É FALSO DEFÁULO NULL seguindo o padrão ****************, então todos os inquilinos existentes está explicitamente fora do limite de conformidade em vez de silenciosamente dentro dele. PHI EFFECTIVE DATE abrange uma avaliação de violação e uma obrigação de retenção para o janela direita — "desde quando" é a primeira pergunta feita após "o que foi exposto". Converte-te para a política com o ""***""*** O guarda ele próprio fica livre da entidade, então um chamador segurando apenas uma bandeira de sessão não tem que carregar uma linha. Seguro para empurrar: ambas as colunas já existem no cluster e cada uma das 12 as organizações lêem falso. Verificado contra informação esquema antes do desembarque — handles phi boolean NOT NULL DEFAULT false, phi effective date timestamp NULL. Veja a migração KamoInitializer para a forma reprodutível.

  27. · kamo-shared-library

    PhiTenantGuard e o limite de conformidade do módulo

    A pedra angular do limite de conformidade HIPAA: que módulos um inquilino pode alcançar, tendo em conta se esse inquilino lida com informações sanitárias protegidas. Tudo chaves a jusante — que integrações são desactivadas, se o MFA é forçado, que sessões obter o mais rigoroso tempo limite, o que a coluna de auditoria deve cobrir. Não tem dependência de esquemas. Uma tentativa anterior pendurou isto de um novo coluna Organization.handlesPhi e teve que ser revertida: clones CI de serviço compartilhamento-biblioteca principal em tempo de construção, enquanto KamoInitializer é executado à mão, então um a coluna de entidade no principal quebra o próximo serviço de implantação até que alguém migra. A dissociação da política da entidade significa que aterra e pode ser aplicada agora, e a coluna torna-se um passo independente. Três propriedades que o design é construído em torno: - Falha fechada. O construtor da PhiModule requer uma PhiDisposition e uma razão, então um módulo adicionado mais tarde não pode herdar permissão por omissão — ele não compilar sem uma decisão, e qualquer outra coisa que não PERMITIDA nega. Isto é... o modo de falha um limite de conformidade tem que sobreviver: um módulo naves, ninguém Pensa no PHI, uma clínica começa a usá-lo. - Um inquilino não resolvido não é um inquilino seguro. O guarda toma um tri-estado PhiTenantStatus em vez de um booleano, então "Eu não poderia carregar a organização" não pode ser escrito da mesma forma que "esta organização não é PHI". Com uma booleano, o caminho de erro é permitido(falso, ...) — fácil de alcançar, impossível de identificar em revisão. O desconhecido nega tudo. - Nada para elevar. A decisão é uma função pura de (status, module) sem parâmetro chamador, sessão, direito ou modo deus. "Nosso pessoal de apoio pode virá-lo Off" é a resposta que falha na revisão de uma entidade coberta. As desposições seguem a análise de escopo em kamo-internal **************************** Permissão: CRM, contactos, cofre de documentos, self-hosted email, e-sign, notas, tarefas, calendário, chat interno, o in-cluster pilha de transcrição/tradução, MLOS. Bloqueado sem BAA possível: social mensagens, Canva, sincronização de comércio eletrônico, gateway MCP. Bloqueado pendente trabalho nomeado: o Roteador de IA, busca de RAG, gravações de reuniões, gravações de VOIP, campanhas de marketing, exportação analítica. Nada chama o guarda ainda, por isso isto é inerte até ter escuta.

  28. · InitializerService

    Crie HANDLES PHI e PHI EFFECTIVE DATE em orgs

    Espelha os dois novos campos sobre a entidade Organização na biblioteca de kamo-shared, após a convenção OrgBrandingFieldsMigration. HANDLES PHI NÃO É NULL DEFAULT FALSE para que cada inquilino existente esteja explicitamente fora do cumprimento limite em vez de silenciosamente dentro dele. PHI EFFECTIVE DATE é timestamp, não TIMESTAMPTZ, porque o campo entidade é um LocalDateTime. SEQUENCING — isso precisa ser executado antes do próximo serviço ser implantado. Pino de serviços kamo-shared-library em uma versão fixa que é republicada no lugar, assim a primeira serviço para reconstruir após essa mudança de biblioteca começa a selecionar estas colunas, e com ddl-auto:none nos serviços que falham até que este corredor tenha executado. O teste que acompanha é um guarda de deriva, não uma reafirmação: ele lê a coluna nomeia a entidade por reflexão e falha na compilação se um campo de entidade não tiver Alter. Esse é o modo de falha que toda esta convenção tem — alguém adiciona um campo, esquece a migração, e ela aparece como um erro de pesquisa de produção em vez de Construir o tempo. Verificado por mutação: remover o PHI EFFECTIVE DATE ALTER o torna falha com a mensagem acionável, restabelecê-la fica verde.

  29. · ESigService

    Emite o segmento local do sinalizador nas ligações de assinatura

    Extrai o build duplicado byte-for-byteSignLink para SignLinkBuilder e acrescenta um segmento locale originado de Organization.language, falling voltar ao inglês para os quatro valores LanguageType sem traduções. Requer primeiro o sinal do kamo /{ locale}/ roteamento.

  30. · kamo-shared-library

    Adicione as alçasPhi locatário bandeira e PhiTenantGuard

    A pedra angular do limite de conformidade HIPAA: uma bandeira por organização mais uma guarda decidir quais módulos que essa organização pode alcançar. Tudo a jusante que as integrações são desactivadas, se o MFA é forçado, que As sessões recebem o tempo limite mais rigoroso, o que a coluna de auditoria tem de cobrir. Organização ganha HANDLES PHI (FALSE BOOL DEFAULT) e PHI EFFECTIVE DATE, seguindo o padrão existente. O padrão é falso, Por isso, isto é inerte para a hipoteca existente e o livro CRM. Três propriedades que o design é construído em torno: - Falha fechada. O construtor da PhiModule requer uma PhiDisposition e uma razão, então um módulo adicionado mais tarde não pode herdar permissão por omissão — ele não compilar sem uma decisão, e qualquer outra coisa que não PERMITIDA nega. Isto é... o modo de falha específico que um limite de conformidade tem de sobreviver: um módulo de navios, Ninguém pensa em PHI, uma clínica começa a usá-lo. - Intransponível. A decisão é uma função pura de (organização, módulo) com nenhum parâmetro chamador, sessão, direito ou modo deus, então não há nada para Elevar. "Nosso pessoal de apoio pode desligá-lo" é a resposta que falha um coberto Revisão da entidade. - Negações carregam uma razão, porque um nu "negado" não é utilizável por um apoio engenheiro ou cliente. As desposições seguem a análise de escopo em kamo-internal **************************** Permitido hoje: CRM, contatos, documento e-mail, e-sign, notas, tarefas, calendário, chat interno, o na pilha de transcrição/tradução de clusters e MLOS. Bloqueado sem possibilidade de obtenção BAA: mensagens sociais, Canva, sincronização de e-commerce, gateway MCP. Bloqueado pendente no nome trabalho: roteador de IA, busca de RAG, gravações de reuniões, gravações de VOIP, marketing campanhas, exportação analítica. Nota esquema: HANDLES PHI e PHI EFFECTIVE DATE materializam-se no próximo O KamoInitializer executa, depois que o CI publica esta compilação bibliotecária compartilhada. Nada lê A bandeira ainda, então o pedido é seguro.

Seu universo está esperando.

Comece gratuitamente em minutos.Adicione produtos sempre que estiver pronto — sem migração, sem novos logins.

Gratuito para sempre — não é necessário cartão de crédito