Většina bezpečnostních problémů v Salesforce implementacích nejsou exotické zranitelnosti platformy. Jsou to nešvary, o kterých všichni víme, že bychom je dělat neměli a stejně je občas děláme. V článku popíšu pět konkrétních případů z reálné praxe, na kterých chci ukázat, proč jsou problém a jak to dělat lépe.
V článku se dozvíte:
- Client-side logika: útočníkem je váš vlastní uživatel
- Sensitive data exposure: data protečou tam, kam nemají
- CRUD/FLS: Skryté ≠ zakázané
- Ukládání secrets: útočníkem může být i váš admin
- Admin práva pro všechny: útočníkem je ten, kdo vám ukradne účet
- Kdy bude na řadě váš org?
Client-side logika: útočníkem je váš vlastní uživatel
Představme si jednoduchou LWC komponentu na Opportunity: uživatel v ní nastavuje slevu. Běžný uživatel smí dát maximálně 10 %, kdo má speciální oprávnění, smí víc. Komponenta volá Apex metodu, která slevu uloží a kontrola limitu je napsaná v JavaScriptu komponenty.
Vypadá to rozumně. Jenže celá kontrola běží v prohlížeči uživatele. Stačí otevřít DevTools, najít v načtených zdrojích správný JavaScript, přepsat podmínku z 10 na 100 % a uložit slevu, na kterou nemám právo. Server to poslušně zapíše, protože Apex metoda žádnou vlastní kontrolu nemá.
Základní poučka: vše, co se odehrává na straně klienta, je z principu mimo naši kontrolu. JavaScript běží na počítači někoho jiného a ten člověk nad ním má plnou moc.
Možná namítnete, že prohlížeče přece mají spoustu bezpečnostních mechanismů: Content Security Policy, CORS, SameSite cookies, CSRF tokeny, Subresource Integrity. Mají. Jenže všechny chrání uživatele před útokem zvenčí, ne váš server před útokem od uživatele. Fungují jen do chvíle, kdy uživatel hraje podle pravidel. Ale uživatel podle pravidel hrát nemusí.
Druhá námitka: „Běžný uživatel tohle nedělá, nebo to neumí.“ Dva protiargumenty:
- Běžný uživatel může být technicky zdatný. Sám jsem si takhle před lety „opravoval“ vykazování v našem interním CRM, které mi nedovolilo vykázat práci měsíc zpátky. Byl jsem přitom v rámci naší firmy naprosto běžný uživatel. Salesforce totiž používají i firmy plné vývojářů. Tam je „běžný uživatel“ člověk, který DevTools otevře dřív, než dopije ranní kafe.
- A pokud není: s AI asistentem obejde client-side validaci i netechnický uživatel. Prakticky každý jazykový model mu s tím rád pomůže.
Kde všude si dát pozor? Prakticky ve všem, co běží v prohlížeči: LWC JavaScript i markup (např. lwc:if), Aura, jakékoli third-party knihovny, CSS triky typu display:none. U Screen Flows běží validace vstupů a viditelnost na screen elementech také client-side – jde obejít úplně stejně. Zajímavá výjimka je Visualforce: renderuje se na serveru, takže podmíněný obsah prohlížeč vůbec nedostane.
Závěr: client-side logika je UX, ne security. Každou kontrolu, na které záleží, musí vynutit server.
Sensitive data exposure: data protečou tam, kam nemají
Většina orgů má data citlivější než jiná: rodná čísla, čísla platebních karet, ale klidně i obyčejné jméno klienta, protože v Evropě je díky GDPR osobním údajem i to. Ve větších organizacích na to bývá celá klasifikace: každé pole má přiřazenou kategorii citlivosti a podle ní se s ním zachází. Typicky dvěma způsoby: omezenými read právy (kdo zůstatek klienta nepotřebuje, nemá na pole právo) a Shield Platform Encryption, tedy šifrováním dat v databázi.
Obě ochrany se ale dají nechtěně obejít přímo v konfiguraci či kódu. Tohle jsou nešvary, které vídám skoro denně:
- Formula fields ignorují Field-Level Security. Formule se počítá on-the-fly při čtení záznamu a FLS zdrojových polí se nevyhodnocuje. Pokud chráněné pole použijete ve formuli a formuli zpřístupníte všem, právě jste read práva na zdrojové pole efektivně zrušili.
- Kopírování hodnot do jiných polí. Reálný příklad z praxe: jméno klienta je šifrované, ale subject úkolu ne. Jediný nevinný řádek kódu
task.Subject = 'Zavolat: ' + account.Name;a citlivá hodnota leží v nešifrovaném, široce čitelném poli. Totéž platí pro case description (tam byste se divili, co všechno lidé napíšou) a pro debug logy: máte integrace, logovací framework, logujete payloady. V payloadu jsou reálná klientská data, k logům má přístup půlka projektu a šifrované možná nejsou.
Závěr: citlivost dat se nedědí automaticky. Sledujte, kam hodnoty tečou: formule, kopie, logy, protože ochrana zdrojového pole chrání jen zdrojové pole.
CRUD/FLS: Skryté ≠ zakázané
Modelová situace (zjednodušená verze něčeho, co jsme řešili v praxi): uživatel má Opportunity upravovat výhradně přes quick actions a screen flows. Tlačítka jako Předat, Uzavřít, Upravit, za kterými se skrývá řízený proces, integrace, validace. Standardní tlačítko Edit jsme z rozhraní odstranili, protože pro takhle řízený proces je standardní edit form nepoužitelný.
Otázka zní, jak nastavit CRUD a FLS? Existují dvě varianty a obě mají háček.
Varianta 1: edit práva dáme. Dokumentace i selský rozum říkají: uživatel data fakticky edituje, tak má mít edit práva. Flow poběží v user kontextu a systém práva vyřeší za nás. Co se může pokazit? Standardní edit form jste sice schovali z layoutu, ale zkuste na record page stisknout klávesu E – vyskočí. A i kdybyste pole zamkli na layoutu, layout je jen jedna z mnoha cest k datům: API přístup (Data Loader, integrace) žádný layout nezná a inline edit v list view taky ne. Kdo má edit práva, může editovat. Otázka je jen kudy.
Varianta 2: edit práva nedáme. Vytvoříme custom permission (např. CanEditOpportunity), flow poběží v system kontextu a oprávnění si zkontroluje samo v decision elementu. Díra z varianty 1 je zavřená. Cena? Nikde na objektu už nemůžete použít standardní edit ani create form. Všechno musí být custom. A to platí i pro jiné record typy téhož objektu: nejde říct „tenhle record type řízeně, tamten volně“. V rámci jednoho objektu je to one-way ticket.
Neexistuje univerzálně správná odpověď, ale existuje vědomé rozhodnutí. My v praxi používáme mix: tam, kde je logika složitá a proces musí být řízený, jdeme variantou 2 a stavíme si nad objektem vlastní permission model. Na jednodušších objektech necháváme edit práva a přijímáme, že UI je jen vodítko, ne zábrana. Klíčové je nevěřit tomu, že schované tlačítko něco zakazuje. Skryté ≠ zakázané. Tenhle princip je vlastně jen zobecněním první kapitoly z UI na celý permission model.
Ukládání secrets: útočníkem může být i váš admin
Klasická situace: Apex callout na externí službu, v hlavičce X-API-Key s tajným klíčem. Kdokoli ten klíč zná, může službu volat a tvářit se jako váš org. Kam ho uložit? Seřazeno od nejhoršího k nejlepšímu:
- Konfigurační SObject (ApiKey__c). Data dostupná reportem, SOQL dotazem, exportem. Ne.
- Custom Label. Před deseti patnácti lety oblíbená „konfigurace“, viditelná vždy a všem. Ne.
- Custom Settings / Custom Metadata Types. O něco lepší, práva na čtení se nastavit dají, ale admin v Setupu hodnotu prostě vidí. A klíč, který identifikuje váš org vůči partnerovi, by v ideálním světě neměl znát ani admin.
- Managed package + protected custom settings/metadata. Zajímavější, protected hodnoty z package zvenčí vytáhnout nejdou. Jenže pokud klíč potřebujete v kódu, musí package vystavit nějaké rozhraní. Getter typu „dej mi klíč“ zavolá kdokoli z developer console.
- Named Credentials / External Credentials. Správná odpověď. Jakmile hodnotu uložíte, Salesforce vám ji už nikdy neukáže, ale umí ji použít. V kódu referencujete merge field (
{!$Credential.Partner_API_EC.api_key}) a skutečná hodnota se doplní až ve vrstvě, kde request opouští Salesforce. Neuvidíte ji v debug logu, nevytáhne ji ani mock v testovací classe. Tohle je způsob, jak secret ochránit i před vlastním adminem.
Praktický bonus k procesům: když řešíte verzování, zaverzujte volání služby, ne klíč. Klíč patří do manuálních kroků nasazení. Zadají ho provozní správci a vývojář ho nemusí vidět nikdy.
Co když potřebuji volat externí API z JavaScriptu? Tady je stejný problém jako v první kapitole: JavaScript běží na počítači uživatele, takže klíč poslaný do JavaScriptu je klíč předaný uživateli. Možnosti od nejlepší:
- Ať se autorizuje uživatel sám. Pokud volání probíhá v kontextu uživatele, služba ho má legitimně znát. Přihlásí se vlastním jménem, přes SSO, nebo ho ověří klientský certifikát na spravovaném zařízení. Žádný sdílený secret neexistuje.
- Proxy přes Apex. Klient nevolá API napřímo. Zavolá Salesforce, Apex request převolá a klíč přibalí až na serveru. Klíč nikdy neopustí Salesforce.
- Poslední záchrana: šifrovaný token přes klienta. Občas to jinak nejde (narazili jsme na to u WebSocketů, které Apex neumí). Klíč se zašifruje a klient ho jen přenese. Pozor ale na replay attack. Útočník nemusí znát obsah, stačí mu zachytit zašifrovanou zprávu a poslat ji znovu. Mitigace: časová značka a krátká platnost tokenu, svázání tokenu s konkrétním obsahem zprávy (klient pak dokáže zopakovat jen jedno konkrétní volání, nic jiného), detekce anomálií typu změna IP adresy. Úplně na 100 % to vyřešit nejde, proto poslední záchrana, ne pattern první volby.
Admin práva pro všechny: útočníkem je ten, kdo vám ukradne účet
Malá anketa, kterou můžete zkusit na vlastním projektu: Máte přístup na produkci? Máte tam admina? A k čemu ho potřebujete?
Nejčastější odpověď je „potřebuji ho na debugging“. Protiotázka zní: potřebuji, nebo je to pohodlnější? A pokud vážně potřebuju, potřebuju k debuggingu opravdu Customize Application? Manage Encryption Keys? Modify All Data? Manage Users? Admin profil je balík desítek mocných oprávnění, z nichž na konkrétní úlohu typicky potřebujete jen zlomek.
A teď to podstatné, dáte ruku do ohně za to, že vám nikdo nikdy neukradne účet? Realita posledních let říká, že to slíbit nemůže nikdo. Kompromitace dnes přichází i z oficiálních zdrojů, kterým důvěřujete. Jen namátkou ze supply-chain útoků: balíček tj-actions/changed-files používaný ve 23 000+ repozitářích (březen 2025, unikaly AWS klíče a tokeny), kampaň Shai-Hulud s trojanizovanými verzemi 18 masově používaných npm balíčků včetně debug a chalk (září 2025, první dávka přes 2,5 milionu stažení), kompromitovaný Axios stažený z registru do tří hodin. I takové okno stačilo na 135+ potvrzených případů komunikace s infrastrukturou útočníka (březen 2026), či credential stealer v SAP balíčcích na PyPI (duben 2026). Znamená to přestat aktualizovat? Rozhodně ne. Neaktualizovat je horší, zero-day zranitelnosti jsou nemilosrdné. Ale musíme si připustit, že „chovám se zodpovědně“ prostě nestačí.
Krádeži účtu stoprocentně nezabráníte. Co ale plně ovlivníte, je blast radius: co útočník s vaším ukradeným účtem reálně dokáže. Co ukradne, co změní, co nainstaluje? Přesně o tom je Principle of Least Privilege: právo jen na to, co k práci nutně potřebuji, a na nic víc. V praxi je to často nepohodlné a nepopulární. Přitom je to i ve vašem osobním zájmu: já sám přístup na produkci nemám. Občas je to otrava. Ale až někdo kompromituje můj účet, jsem v suchu.
Řešením může být například just-in-time elevace (PIM apod.). Citlivé oprávnění si vyžádáte, na omezenou dobu ho dostanete, pak vám ho systém zase sebere. Na menších projektech může být takový mechanismus overkill, ale principy zůstávají. Začněte tím, že admina nedáte automaticky každému.
Kdy bude na řadě váš org?
Tady jsou tři reálné kampaně z posledních dvanácti měsíců, všechny cílené na Salesforce. Všimněte si, že žádná z nich nezneužila zranitelnost platformy. Všechny sklízely přesně to, o čem je tenhle článek.
- ShinyHunters vishing – UNC6040 (červen 2025). Útočníci telefonovali zaměstnancům jako IT support a navedli je k instalaci modifikované verze Salesforce Data Loaderu, kterou si oběti samy autorizovaly přes OAuth. Nástroj pak exfiltroval CRM data; Google potvrdil únik zhruba 2,55 milionu záznamů. Čistý social engineering a rozsah škody určil blast radius účtů, které se nechaly napálit (kapitola 5).
- SalesLoft / Drift OAuth heist – UNC6395 (srpen 2025). Přes odcizené OAuth tokeny jedné third-party integrace získali útočníci přístup do více než 700 Salesforce orgů. A nestačilo jim to: cíleně dotazovali Cases a Accounts a sklízeli plaintext credentialy uložené přímo v datech – AWS klíče, Snowflake tokeny, hesla v support ticketech. Přesně ty case descriptions a špatně uložené secrets z kapitoly 2 a 4. Mezi oběťmi Cloudflare, Google, Palo Alto Networks, Proofpoint nebo Zscaler, firmy, u kterých byste čekali, že security mají pořešenou.
- ShinyHunters Experience Cloud (od září 2025, stále běží). Automatizovaný útok na špatně nakonfigurované guest user profily v Experience Cloud: útočníci si upravili veřejně dostupný auditní nástroj AuraInspector a masově skenují /s/sfsites/aura endpointy. Když našli public site, tak začali provolávat serverové metody (viz kapitola 1 a 3). Zasaženo odhadem 300–400 firem včetně Snowflake, LastPass, Okty nebo Evropské komise. Mnoho z nich přímo z oboru kybernetické bezpečnosti.
Ta zdánlivá propast mezi „zvídavým uživatelem s DevTools“ a „profesionální útočnou skupinou“ ve skutečnosti neexistuje. Vnější útočník si nejdřív nějakou cestou opatří identitu – ukradeným účtem, OAuth tokenem, guest userem na public site. Od té chvíle je z něj přesně ten „vlastní uživatel“, před kterým vás client-side kontroly, schovaná tlačítka ani custom labels neochrání. Proto jsou to spojené nádoby.
Závěrem
Nic z toho není raketová věda: server-side validace, sledování toku citlivých dat, vědomé rozhodnutí o CRUD/FLS, credentials místo custom labels, least privilege. Všichni to víme. Rozdíl mezi orgem, který jednou bude v novinách, a orgem, který nebude, není v tom, jestli to jeho tým ví. Jestli podle toho jedná i ve chvíli, kdy hoří deadline a „takhle to bude rychlejší“.
Účet vám útočník ukrást může, můžete mu to ztížit, ale ještě nikdo nevymyslel, jak tomu 100 % zabránit. Ale jaký bude blast radius – to je jen a pouze na vás.
Chcete o podobných tématech slyšet živě? Přihlaste se na TechBeer Autumn 26.