Sa amin, ang seguridad ay hindi isang layer na idinagdag sa bandang huli, kundi ang sentro ng arkitektura. Ang data isolation, identity separation, at idempotent na pagsusulat ay nakaukit sa pinakamababang layer ng code — hindi ito isang setting na maaaring i-off, kundi ang paraan ng pagtakbo ng sistema.
Row-level isolation
Idempotent na pagsusulat
Parameterized query
Loopback isolation
Data isolation
Row-level isolation — fail-closed
Sa bawat tenant-scoped na table ay naka-ON ang PostgreSQL Row-Level Security FORCE; iniuugnay ng patakaran ang bawat row sa kondisyong "tenant_id = current_setting('app.current_tenant')".
Ang isang query na dumarating nang hindi naitakda ang tenant context ay nagbabalik ng zero row — hindi kahit isang error, tahimik na walang laman. Imposible sa arkitektura na makita ang datos ng isang klinika gamit ang session ng ibang klinika.
Ang cross-tenant isolation test ay mandatoryong berde sa continuous integration (CI); kung pula ito, hindi ini-merge ang code.
Atomikong pagsusulat
Transaction-local na identity, atomikong operasyon
Ang tenant identity ay itinatakda SA PINAKASIMULA ng transaction sa bawat write operation at balido lamang sa buong transaction na iyon; hindi ito dumidikit sa connection pool at hindi tumatagas sa ibang mga request.
Ang mga multi-entity na operasyon (invoice + item + bayad + pagbawas ng stock) ay atomiko sa loob ng iisang transaction: alinman sa lahat ay isinusulat nang magkasama o wala ni isa. Walang naibibitin, hindi tugmang tala na malilikha.
Awtorisasyon
Ang awtorisasyong batay sa role (RBAC) ay ipinapatupad sa server
Ang kontrol ng awtorisasyon ay ginagawa sa API layer; hindi kailanman pinagkakatiwalaan ang kliyente o ang frontend. Ang pagtatago ng isang menu sa browser ay tungkol lamang sa hitsura — ang tunay na desisyon ay nasa server.
Ang mga mutation ng core (Phase 1) ay pinoprotektahan ayon sa role ng admin, sekretarya, at doktor. Kahit tawagin ng isang di-awtorisadong role ang endpoint, tinatanggihan ang request.
Proteksyon sa retry
Idempotent na pagsusulat — walang dobleng operasyon
Ang bawat write request ay nagdadala ng Idempotency-Key; ang lock sa panig ng server ay pumipigil sa pangalawang transaksyong dumarating gamit ang parehong key.
Kahit na naputol ang network o muling ipadala ng kliyente ang parehong kahilingan, walang pangalawang koleksyon o invoice na malilikha. Ang resulta ay palaging isa lamang at nahuhulaan.
Seguridad ng query
Imposible ang SQL injection — parameterized query
Ang lahat ng database query ay ganap na parameterized na expression na nabubuo sa compile time gamit ang sqlc. Wala ni isang linya sa codebase na bumubuo ng SQL sa pamamagitan ng manu-manong pag-concatenate ng string gamit ang user input.
Ito ay istrukturang inaalis ang buong klase ng SQL injection attack — nakabatay ito sa paraan ng pagkakabuo ng query, hindi sa pagsisikap na "i-filter" o "i-escape."
Surface ng network
Loopback isolation — ang core ay sarado sa labas
Nakikinig lamang ang core API sa 127.0.0.1 (loopback). Ang anumang pagtatangkang mag-bind sa 0.0.0.0 o sa isang public IP ay tinatanggihan sa simula pa lang; ang core ay hindi kailanman nakalantad sa internet.
Ang tanging public na harapan ay isang manipis na pinto (BFF): wala itong hawak na business logic maliban sa session, render at CSRF. Hindi kailanman nakikita o naa-address nang direkta ng browser ang core API.
Identity at session
argon2id na password, opaque na session key
Ang mga password ay hina-hash gamit ang industry-standard, memory-hard na argon2id; walang ginagamit na plaintext o mahinang hash.
Ang mga session key ay 32-byte na cryptographically random na opaque na value, iniimbak sa Redis at nage-expire sa pamamagitan ng TTL. Sa browser ay isang HttpOnly + Secure + SameSite na cookie lamang ang lumalabas; hindi kailanman nakalantad ang key sa JavaScript.
Pag-audit
Hindi mababagong audit record (append-only)
Ang mahahalagang operasyon ay isinusulat sa append-only na audit record sa loob ng PAREHONG transaction ng mutation na nag-trigger nito. Kung na-rollback ang operasyon, hindi rin isinusulat ang audit record — hindi sila maaaring paghiwalayin.
Inalisan ng UPDATE/DELETE na pahintulot ang audit table; hindi na ma-edit o mabubura ang mga tala pagkatapos. Ang nakaraan ay nananatili kung ano ito.
Privacy
Pagmaskara ng sensitibong datos
Ang TCKN, password, session key, at datos pangkalusugan ay hindi kailanman pumapasok sa mga log sa anumang paraan. Kahit sa debug output, ang mga field na ito ay minamaskara.
Kapag kinakailangan, ang mga sensitibong field ay ini-encrypt sa antas ng kolum. Ang espesyal na uri ng datos pangkalusugan ay pinoprotektahan ng mga tuntuning nakabaon sa disenyo mula sa simula.
Integridad ng pera
Seguridad ng pera — numeric, reverse entry
Ang pera ay itinatago sa lahat ng lugar gamit ang numeric(14,2); hindi ginagawa ang kalkulasyon ng pera gamit ang float. Ang rounding ay nangyayari sa iisang punto, sa server — walang naibibitin na kusing o pagkakaiba sa rounding.
Ang kanselasyon at refund ay iproseso bilang isang reverse entry, sa halip na burahin ang tala. Ang account balance ay hindi manu-manong isinusulat; palaging hinahango mula sa kabuuan ng invoice at bayad. Hindi nasisira ang nakaraan.
Paglilipat
Paglilipat at seguridad ng browser
Ang lahat ng traffic ay dumadaan sa HTTPS/TLS at nakabukas ang HSTS; itinatatag ng browser ang koneksyon nang naka-encrypt lamang.
Hinaharangan ng mahigpit na Content Security Policy (CSP) ang inline script; pinipigilan ng X-Frame-Options DENY ang clickjacking, at ng X-Content-Type-Options nosniff ang type-confusion. Ipinapatupad din ang Referrer-Policy at Permissions-Policy.
Mahalaga
Paghihiwalay ng identity tier + hiwalay na management plane
Ang clinic user, patient portal at platform-operator ay MAGKAKAHIWALAY na identity tier: hiwalay na header, hiwalay na session space. Ang key ng isang tier ay hindi gumagana sa iba — mahigpit na nakahiwalay ang mga hangganan ng awtorisasyon.
Ang mga platform at management endpoint (tenant provisioning, billing, automation/plugin management, API key generation) ay WALA sa public API.
Ang mga function na ito ay tumatakbo sa isang hiwalay, panloob na control plane na may ibang identity pattern (espesyal na admin key); hindi ito maa-access o matutuklas mula sa labas. Sa gayon, kapansin-pansing lumiliit ang attack surface.
PAANO ITO GUMAGANA
Iisang transaction, fail-closed isolation
Ipinapakita ng konseptwal na daloy sa ibaba kung paano naka-lock ang bawat pagsusulat sa tenant at nananatiling idempotent. Para sa paglalarawan lamang.
# 1) Ang tenant context ay itinatakda SA PINAKASIMULA ng transaction (para lamang sa tx na ito)
BEGIN;
SELECT set_config('app.current_tenant', :session_tenant_id, true);
# 2) Idempotency-Key na lock — hindi malilikha ang pangalawang transaksyon gamit ang parehong keySELECT lock_idempotency(:idempotency_key);
# 3) Multi-entity na write — lahat ay atomic sa iisang txINSERT INTO invoices ...; # RLS: tenant_id awtomatikong nasasaklawINSERT INTO payments ...;
INSERT INTO audit_log ...; # append-only, parehong tx
COMMIT;
# Kapag hindi naitakda ang context, walang ibinabalik na kahit isang row ang RLS policy:SELECT * FROM patients; --> 0 row (hindi error, tahimik na walang laman)
Malayang mabe-verify
Bahagi ng seguridad ng paglilipat ay maaaring obserbahan mula sa labas: maaari ninyong suriin mismo mula sa developer tools ng inyong browser ang HTTPS/TLS certificate ng inyong koneksyon, gayundin ang mga response security header tulad ng HSTS at content security policy. Ang aming mga pahayag ay nakabatay sa mga architectural control, hindi sa mga pangungusap sa marketing.
May tanong ba tungkol sa security architecture?
Ikinalulugod ng aming team na pag-usapan sa inyo ang mga teknikal na detalye at inyong mga pangangailangan sa pagsunod.