{"id":173640,"date":"2026-08-13T10:57:59","date_gmt":"2026-08-13T08:57:59","guid":{"rendered":"https:\/\/fotc.com\/?p=173640"},"modified":"2026-08-13T14:39:00","modified_gmt":"2026-08-13T12:39:00","slug":"kiedy-przychodzi-rachunek-jest-juz-za-pozno-jak-zbudowalismy-detektor-anomalii-kosztowych-w-google-cloud","status":"publish","type":"post","link":"https:\/\/fotc.com\/pl\/blog\/kiedy-przychodzi-rachunek-jest-juz-za-pozno-jak-zbudowalismy-detektor-anomalii-kosztowych-w-google-cloud\/","title":{"rendered":"Kiedy przychodzi rachunek, jest ju\u017c za p\u00f3\u017ano. Jak zbudowali\u015bmy detektor anomalii kosztowych w Google Cloud"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Wiosn\u0105 2026 dwie firmy pad\u0142y ofiar\u0105 tej samej klasy ataku: skradzione po\u015bwiadczenia, cudze klucze API i masowa konsumpcja Gemini na ich koszt. Pierwszy incydent sko\u0144czy\u0142 si\u0119 rachunkiem na p\u00f3\u0142 miliona euro. Drugi, dwa miesi\u0105ce p\u00f3\u017aniej, trafi\u0142 na \u015brodowisko obj\u0119te ju\u017c naszym monitoringiem i zosta\u0142 zatrzymany w kilka godzin. Ten tekst opowiada o tym, co zbudowali\u015bmy pomi\u0119dzy tymi dwiema datami, i dlaczego najtrudniejsze nie by\u0142o samo wykrywanie anomalii, tylko decyzja, kiedy naprawd\u0119 alarmowa\u0107.<\/p>\n\n\n\n<h2 id=\"h-pol-miliona-euro-w-trzy-dni\" class=\"wp-block-heading\">P\u00f3\u0142 miliona euro w trzy dni<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Maj 2026. Du\u017ca grupa technologiczna, prowadz\u0105ca m.in. platformy e-commerce i&nbsp;streamingowe, u\u017cywa Google Cloud symbolicznie. G\u0142\u00f3wna infrastruktura znajduje si\u0119 gdzie indziej; na GCP dzia\u0142aj\u0105 jedynie systemy poboczne, generuj\u0105ce oko\u0142o&nbsp;<strong>500 dolar\u00f3w miesi\u0119cznie<\/strong>. Stabilnie, od lat, bez historii.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">W po\u0142owie miesi\u0105ca firma wsp\u00f3\u0142organizuje du\u017ce, szeroko promowane wydarzenie technologiczne. Zesp\u00f3\u0142 in\u017cynierski w&nbsp;ca\u0142o\u015bci jest na miejscu i&nbsp;ma r\u0119ce pe\u0142ne roboty. To w\u0142a\u015bnie w&nbsp;te dni kto\u015b si\u0119ga po&nbsp;<strong>wyciek\u0142y klucz starego konta serwisowego<\/strong>, nast\u0119pnie w&nbsp;jednym z&nbsp;projekt\u00f3w podnosi sobie uprawnienia do roli Editor, w\u0142\u0105cza Vertex AI i&nbsp;uruchamia inferencj\u0119 na modelu Gemini 1.5 Pro. Nie testowo. Przemys\u0142owo.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Efekt? Rachunek za zu\u017cycie zasob\u00f3w GCP w&nbsp;okresie 14\u201316 maja si\u0119ga&nbsp;<strong>500&nbsp;960 euro<\/strong>. Tysi\u0105c miesi\u0119cznych bud\u017cet\u00f3w tej firmy nabitych w&nbsp;trzy doby.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Najbardziej gorzka cz\u0119\u015b\u0107 tej historii jest taka, \u017ce alerty formalnie zadzia\u0142a\u0142y. Google wys\u0142a\u0142 powiadomienie o&nbsp;podejrzeniu nadu\u017cycia i&nbsp;przekroczeniu \u015brednich wydatk\u00f3w ju\u017c 14 maja. Tyle \u017ce pierwszy alert bud\u017cetowy, przy \u015bredniej 500 dolar\u00f3w, opiewa\u0142 na kwot\u0119, kt\u00f3ra w&nbsp;warto\u015bciach bezwzgl\u0119dnych wygl\u0105da\u0142a niegro\u017anie. A&nbsp;ludzie, kt\u00f3rzy mieli go przeczyta\u0107, stali akurat na scenie. Pe\u0142na reakcja ruszy\u0142a 17 maja i&nbsp;by\u0142a wzorowa: konta wy\u0142\u0105czone, z\u0142o\u015bliwe powi\u0105zania IAM usuni\u0119te, projekt zamro\u017cony w&nbsp;trybie read-only do analizy \u015bledczej, nowe polityki bezpiecze\u0144stwa w&nbsp;ca\u0142ej organizacji. \u015aledztwo potwierdzi\u0142o, \u017ce dane nie wyciek\u0142y, a&nbsp;szkody ograniczy\u0142y si\u0119 do jednego projektu. Ale licznik zd\u0105\u017cy\u0142 si\u0119 ju\u017c zatrzyma\u0107 na p\u00f3\u0142 miliona.<\/p>\n\n\n\n<script class=\"newscripwt\">\njQuery(function($) {\nfunction seededNoise(i, base, spread) {\n    const x = Math.sin(i * 12.9898) * 43758.5453;\n    return base + (x - Math.floor(x) - 0.5) * spread;\n  }\n  const CHARTS = {\n    chartMay: {\n      month: \"maj\", daysInMonth: 31,\n      base: 16, spread: 9,\n      spikes: { 13: 292000, 14: 168000, 15: 41000 },\n      spikeLabels: { 13: \"\u2248\u20ac292 tys.\" },\n      gridMax: 300000, gridStep: 100000,\n      fmtAxis: v => v === 0 ? \"\u20ac0\" : \"\u20ac\" + (v \/ 1000) + \" tys.\",\n      fmtTip: v => v >= 1000 ? \"\u2248\u20ac\" + Math.round(v \/ 1000) + \" tys.\" : \"\u2248\u20ac\" + Math.round(v),\n    },\n    chartJul: {\n      month: \"lip\", daysInMonth: 31,\n      base: 20, spread: 10,\n      spikes: { 15: 33251, 16: 650 },\n      spikeLabels: { 15: \"\u2248$33,3 tys.\" },\n      gridMax: 40000, gridStep: 10000,\n      fmtAxis: v => v === 0 ? \"$0\" : \"$\" + (v \/ 1000) + \" tys.\",\n      fmtTip: v => v >= 1000 ? \"\u2248$\" + (Math.round(v \/ 100) \/ 10).toLocaleString(\"pl-PL\") + \" tys.\" : \"\u2248$\" + Math.round(v),\n    },\n  };\n  const tip = document.getElementById(\"tip\");\n  const NS = \"http:\/\/www.w3.org\/2000\/svg\";\n  const el = (tag, attrs) => {\n    const e = document.createElementNS(NS, tag);\n    for (const [k, v] of Object.entries(attrs)) e.setAttribute(k, v);\n    return e;\n  };\n  function renderConsoleChart(id) {\n    const svg = document.getElementById(id);\n    const cfg = CHARTS[id];\n    const W = 860, H = 250;\n    const M = { top: 18, right: 10, bottom: 30, left: 64 };\n    const plotW = W - M.left - M.right, plotH = H - M.top - M.bottom;\n    const n = cfg.daysInMonth;\n    const gap = 5;\n    const bw = (plotW - gap * (n - 1)) \/ n;\n    for (let g = 0; g <= cfg.gridMax; g += cfg.gridStep) {\n      const y = M.top + plotH - (g \/ cfg.gridMax) * plotH;\n      svg.appendChild(el(\"line\", { x1: M.left, x2: M.left + plotW, y1: y, y2: y, stroke: \"#3c4043\", \"stroke-width\": 1 }));\n      const t = el(\"text\", { x: M.left - 10, y: y + 4, \"text-anchor\": \"end\", \"font-size\": 11, fill: \"#9aa0a6\" });\n      t.textContent = cfg.fmtAxis(g);\n      svg.appendChild(t);\n    }\n    for (let i = 0; i < n; i++) {\n      const v = cfg.spikes[i] !== undefined ? cfg.spikes[i] : Math.max(2, seededNoise(i, cfg.base, cfg.spread));\n      const x = M.left + i * (bw + gap);\n      const h = Math.max(1.5, (v \/ cfg.gridMax) * plotH);\n      const y = M.top + plotH - h;\n      const bar = el(\"rect\", { x, y, width: bw, height: h, rx: 1.5, fill: \"#669df6\" });\n      svg.appendChild(bar);\n     if (!(cfg.spikeLabels == null || cfg.spikeLabels[i] == null)) {\n        const t = el(\"text\", { x: Math.min(Math.max(x + bw \/ 2, M.left + 40), W - 50), y: Math.max(y - 7, 12), \"text-anchor\": \"middle\", \"font-size\": 11.5, \"font-weight\": 700, fill: \"#e8eaed\" });\n        t.textContent = cfg.spikeLabels[i];\n        svg.appendChild(t);\n      }\n      if (i % 3 === 0) {\n        const t = el(\"text\", { x: x + bw \/ 2, y: M.top + plotH + 18, \"text-anchor\": \"middle\", \"font-size\": 9.5, fill: \"#9aa0a6\" });\n        t.textContent = (i + 1) + \" \" + cfg.month;\n        svg.appendChild(t);\n      }\n      const hit = el(\"rect\", { x: x - gap \/ 2, y: M.top, width: bw + gap, height: plotH, fill: \"transparent\" });\n      hit.addEventListener(\"mousemove\", (e) => {\n        tip.textContent = (i + 1) + \" \" + cfg.month + \" \u00b7 \" + cfg.fmtTip(v);\n        tip.style.left = e.clientX + 14 + \"px\";\n        tip.style.top = e.clientY - 10 + \"px\";\n        tip.style.opacity = 1;\n        bar.setAttribute(\"opacity\", \"0.8\");\n      });\n      hit.addEventListener(\"mouseleave\", () => { tip.style.opacity = 0; bar.removeAttribute(\"opacity\"); });\n      svg.appendChild(hit);\n    }\n  }\n  renderConsoleChart(\"chartMay\");\n  renderConsoleChart(\"chartJul\");\n});\n\n<\/script>\n<style data-wp-block-html=\"css\">\n:root {\n    --red-50:#FDEBE6; --red-100:#FBD2C6; --red-400:#EA5528; --red-500:#E33203; --red-600:#C22B03; --red-700:#9B2202;\n    --blue-50:#E6F2FF; --blue-100:#CCE5FF; --blue-300:#66AFFF; --blue-500:#007BFF; --blue-600:#0069D9; --blue-700:#0056B3;\n    --sky-100:#DEF3FE; --sky-500:#17A2B8;\n    --ink-900:#212529; --ink-800:#343A40; --ink-700:#495057; --ink-600:#6C757D; --ink-500:#868E96;\n    --ink-300:#CED4DA; --ink-200:#DEE2E6; --ink-100:#E9ECEF; --ink-50:#F8F9FA; --white:#FFFFFF;\n    --green-500:#28A745; --amber-500:#FFC107; --danger-500:#DC3545;\n    --font-sans:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,\"Helvetica Neue\",Arial,\"Noto Sans\",sans-serif;\n    --font-mono:SFMono-Regular,Menlo,Monaco,Consolas,\"Liberation Mono\",\"Courier New\",monospace;\n    --shadow-xs:0 1px 2px rgba(11,27,43,.06);\n    --shadow-sm:0 1px 3px rgba(11,27,43,.08),0 1px 2px rgba(11,27,43,.04);\n    --shadow-md:0 6px 16px rgba(11,27,43,.08);\n    --shadow-lg:0 14px 32px rgba(11,27,43,.10);\n    --shadow-brand:0 12px 28px rgba(227,50,3,.26);\n    --ease:cubic-bezier(.2,0,0,1);\n  }\n\/* \u2500\u2500 Figures \/ console-style charts \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 *\/\n  figure.chart { background:var(--white); border:1px solid var(--ink-200); border-radius:16px; padding:24px; margin:30px 0; box-shadow:var(--shadow-sm); }\n  figure.chart figcaption { margin-bottom:4px; }\n  figure.chart .f-title { font-size:16px; font-weight:700; color:var(--ink-900); }\n  figure.chart .f-sub { font-size:13px; color:var(--ink-500); margin-top:3px; }\n  .console { background:#1b1d21; border-radius:12px; padding:16px 16px 8px; margin-top:16px; }\n  .console-head { display:flex; justify-content:space-between; align-items:baseline; gap:14px; flex-wrap:wrap; padding:2px 6px 12px; border-bottom:1px solid #33363b; margin-bottom:6px; }\n  .console-head .range { font-size:12px; color:#9aa0a6; }\n  .console-head .big { font-size:17px; font-weight:700; color:#e8eaed; font-family:var(--font-mono); }\n  .console-head .delta { font-size:12.5px; font-weight:600; color:#f28b45; }\n  .chart-svg { width:100%; height:auto; display:block; }\n  .chart-note { font-size:12.5px; color:var(--ink-500); margin-top:14px; }\n  details.datatable { margin-top:12px; font-size:13px; }\n  details.datatable summary { cursor:pointer; color:var(--ink-600); font-weight:600; }\n  details.datatable table { border-collapse:collapse; margin-top:10px; }\n  details.datatable td, details.datatable th { border:1px solid var(--ink-200); padding:5px 10px; text-align:right; }\n  details.datatable th { background:var(--ink-50); font-weight:600; color:var(--ink-800); }\n  .tip { position:fixed; z-index:90; pointer-events:none; background:var(--ink-900); color:#fff; font-size:12.5px; padding:7px 11px; border-radius:8px; box-shadow:var(--shadow-lg); opacity:0; transition:opacity .1s; white-space:nowrap; }\n\n  \/* \u2500\u2500 Dark band (episode lifecycle) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 *\/\n  .band { background:var(--ink-900); color:#fff; padding:88px 0; position:relative; overflow:hidden; margin:80px 0; }\n  .band::before { content:\"\"; position:absolute; inset:0; background-image:linear-gradient(rgba(255,255,255,.04) 1px,transparent 1px),linear-gradient(90deg,rgba(255,255,255,.04) 1px,transparent 1px); background-size:52px 52px; -webkit-mask-image:radial-gradient(at 30% 0%,#000,transparent 78%); mask-image:radial-gradient(at 30% 0%,#000,transparent 78%); pointer-events:none; }\n  .band .wrap { position:relative; }\n  .band h2 { color:#fff; font-size:clamp(26px,3vw,36px); margin:14px 0 16px; }\n  .band .prose > p { color:rgba(255,255,255,.72); }\n  .band strong { color:#fff; }\n  .flow { display:grid; grid-template-columns:1fr auto 1fr auto 1fr; gap:12px; align-items:stretch; margin:36px 0 14px; }\n  .phase { border-radius:14px; padding:18px; background:rgba(255,255,255,.05); border:1px solid rgba(255,255,255,.14); }\n  .phase .tag { display:inline-block; font-size:11px; font-weight:700; letter-spacing:.07em; text-transform:uppercase; padding:4px 11px; border-radius:999px; margin-bottom:11px; }\n  .phase p { font-size:13.5px; color:rgba(255,255,255,.72); line-height:1.5; }\n  .phase p b { color:#fff; font-weight:600; }\n  .phase--alert .tag { background:var(--red-400); color:#fff; }\n  .phase--hold .tag { background:var(--amber-500); color:#3A2F00; }\n  .phase--close .tag { background:#5DD879; color:#062D11; }\n  .flow-arrow { display:flex; align-items:center; color:var(--blue-300); }\n  .flow-arrow svg { width:20px; height:20px; }\n  .flow-branch { margin-top:6px; border:1px dashed rgba(102,175,255,.5); border-radius:12px; padding:14px 18px; font-size:13.5px; color:rgba(255,255,255,.82); }\n  .flow-branch b { color:var(--blue-300); }\n  @media (max-width:960px) {\n    .stats { grid-template-columns:repeat(2,1fr); }\n    .arch { grid-template-columns:1fr; }\n    .arch-arrow { justify-content:center; transform:rotate(90deg); padding:2px 0; }\n    .flow { grid-template-columns:1fr; }\n    .flow-arrow { justify-content:center; transform:rotate(90deg); }\n    .lessons { grid-template-columns:1fr; }\n    .cta-box { flex-direction:column; align-items:flex-start; padding:36px 28px; }\n  }\n  @media (max-width:560px) {\n    .stats { grid-template-columns:1fr 1fr; gap:10px; }\n    .stat b { font-size:23px; }\n    .head-tag { display:none; }\n    .console-head .big { font-size:15px; }\n  }\n<\/style>\n\n\n<div class=\"tip\" id=\"tip\" role=\"presentation\"><\/div>\n\n\n<figure class=\"chart\">\n      <figcaption>\n        <div class=\"f-title\">Tak to wygl\u0105da w konsoli billingowej<\/div>\n        <div class=\"f-sub\">Koszt dzienny konta, maj 2026 (ilustracja pogl\u0105dowa, rzeczywiste rz\u0119dy wielko\u015bci)<\/div>\n      <\/figcaption>\n      <div class=\"console\">\n        <div class=\"console-head\">\n          <div><div class=\"range\">1\u201331 maja 2026 \u00b7 koszt \u0142\u0105czny<\/div><div class=\"big\">\u2248 \u20ac501 000<\/div><\/div>\n          <div class=\"delta\">wzrost ~\u00d71000 vs typowy miesi\u0105c<\/div>\n        <\/div>\n        <svg id=\"chartMay\" class=\"chart-svg\" viewBox=\"0 0 860 250\" role=\"img\" aria-label=\"Wykres s\u0142upkowy kosztu dziennego w maju: przez wi\u0119kszo\u015b\u0107 miesi\u0105ca warto\u015bci bliskie zeru, 14 maja oko\u0142o 292 tysi\u0119cy euro, 15 maja oko\u0142o 168 tysi\u0119cy, 16 maja oko\u0142o 41 tysi\u0119cy\"><line x1=\"64\" x2=\"850\" y1=\"220\" y2=\"220\" stroke=\"#3c4043\" stroke-width=\"1\"><\/line><text x=\"54\" y=\"224\" text-anchor=\"end\" font-size=\"11\" fill=\"#9aa0a6\">\u20ac0<\/text><line x1=\"64\" x2=\"850\" y1=\"152.66666666666669\" y2=\"152.66666666666669\" stroke=\"#3c4043\" stroke-width=\"1\"><\/line><text x=\"54\" y=\"156.66666666666669\" text-anchor=\"end\" font-size=\"11\" fill=\"#9aa0a6\">\u20ac100 tys.<\/text><line x1=\"64\" x2=\"850\" y1=\"85.33333333333334\" y2=\"85.33333333333334\" stroke=\"#3c4043\" stroke-width=\"1\"><\/line><text x=\"54\" y=\"89.33333333333334\" text-anchor=\"end\" font-size=\"11\" fill=\"#9aa0a6\">\u20ac200 tys.<\/text><line x1=\"64\" x2=\"850\" y1=\"18\" y2=\"18\" stroke=\"#3c4043\" stroke-width=\"1\"><\/line><text x=\"54\" y=\"22\" text-anchor=\"end\" font-size=\"11\" fill=\"#9aa0a6\">\u20ac300 tys.<\/text><rect x=\"64\" y=\"218.5\" width=\"20.516129032258064\" height=\"1.5\" rx=\"1.5\" fill=\"#669df6\"><\/rect><text x=\"74.25806451612902\" y=\"238\" text-anchor=\"middle\" font-size=\"9.5\" fill=\"#9aa0a6\">1 maj<\/text><rect x=\"61.5\" y=\"18\" width=\"25.516129032258064\" height=\"202\" fill=\"transparent\"><\/rect><rect x=\"89.51612903225806\" y=\"218.5\" width=\"20.516129032258064\" height=\"1.5\" rx=\"1.5\" fill=\"#669df6\"><\/rect><rect x=\"87.01612903225806\" y=\"18\" width=\"25.516129032258064\" height=\"202\" fill=\"transparent\"><\/rect><rect x=\"115.03225806451613\" y=\"218.5\" width=\"20.516129032258064\" height=\"1.5\" rx=\"1.5\" fill=\"#669df6\"><\/rect><rect x=\"112.53225806451613\" y=\"18\" width=\"25.516129032258064\" height=\"202\" fill=\"transparent\"><\/rect><rect x=\"140.5483870967742\" y=\"218.5\" width=\"20.516129032258064\" height=\"1.5\" rx=\"1.5\" fill=\"#669df6\"><\/rect><text x=\"150.80645161290323\" y=\"238\" text-anchor=\"middle\" font-size=\"9.5\" fill=\"#9aa0a6\">4 maj<\/text><rect x=\"138.0483870967742\" y=\"18\" width=\"25.516129032258064\" height=\"202\" fill=\"transparent\"><\/rect><rect x=\"166.06451612903226\" y=\"218.5\" width=\"20.516129032258064\" height=\"1.5\" rx=\"1.5\" fill=\"#669df6\"><\/rect><rect x=\"163.56451612903226\" y=\"18\" width=\"25.516129032258064\" height=\"202\" fill=\"transparent\"><\/rect><rect x=\"191.5806451612903\" y=\"218.5\" width=\"20.516129032258064\" height=\"1.5\" rx=\"1.5\" fill=\"#669df6\"><\/rect><rect x=\"189.0806451612903\" y=\"18\" width=\"25.516129032258064\" height=\"202\" fill=\"transparent\"><\/rect><rect x=\"217.09677419354838\" y=\"218.5\" width=\"20.516129032258064\" height=\"1.5\" rx=\"1.5\" fill=\"#669df6\"><\/rect><text x=\"227.3548387096774\" y=\"238\" text-anchor=\"middle\" font-size=\"9.5\" fill=\"#9aa0a6\">7 maj<\/text><rect x=\"214.59677419354838\" y=\"18\" width=\"25.516129032258064\" height=\"202\" fill=\"transparent\"><\/rect><rect x=\"242.61290322580646\" y=\"218.5\" width=\"20.516129032258064\" height=\"1.5\" rx=\"1.5\" fill=\"#669df6\"><\/rect><rect x=\"240.11290322580646\" y=\"18\" width=\"25.516129032258064\" height=\"202\" fill=\"transparent\"><\/rect><rect x=\"268.1290322580645\" y=\"218.5\" width=\"20.516129032258064\" height=\"1.5\" rx=\"1.5\" fill=\"#669df6\"><\/rect><rect x=\"265.6290322580645\" y=\"18\" width=\"25.516129032258064\" height=\"202\" fill=\"transparent\"><\/rect><rect x=\"293.64516129032256\" y=\"218.5\" width=\"20.516129032258064\" height=\"1.5\" rx=\"1.5\" fill=\"#669df6\"><\/rect><text x=\"303.9032258064516\" y=\"238\" text-anchor=\"middle\" font-size=\"9.5\" fill=\"#9aa0a6\">10 maj<\/text><rect x=\"291.14516129032256\" y=\"18\" width=\"25.516129032258064\" height=\"202\" fill=\"transparent\"><\/rect><rect x=\"319.1612903225806\" y=\"218.5\" width=\"20.516129032258064\" height=\"1.5\" rx=\"1.5\" fill=\"#669df6\"><\/rect><rect x=\"316.6612903225806\" y=\"18\" width=\"25.516129032258064\" height=\"202\" fill=\"transparent\"><\/rect><rect x=\"344.6774193548387\" y=\"218.5\" width=\"20.516129032258064\" height=\"1.5\" rx=\"1.5\" fill=\"#669df6\"><\/rect><rect x=\"342.1774193548387\" y=\"18\" width=\"25.516129032258064\" height=\"202\" fill=\"transparent\"><\/rect><rect x=\"370.19354838709677\" y=\"218.5\" width=\"20.516129032258064\" height=\"1.5\" rx=\"1.5\" fill=\"#669df6\"><\/rect><text x=\"380.4516129032258\" y=\"238\" text-anchor=\"middle\" font-size=\"9.5\" fill=\"#9aa0a6\">13 maj<\/text><rect x=\"367.69354838709677\" y=\"18\" width=\"25.516129032258064\" height=\"202\" fill=\"transparent\"><\/rect><rect x=\"395.7096774193548\" y=\"23.386666666666656\" width=\"20.516129032258064\" height=\"196.61333333333334\" rx=\"1.5\" fill=\"#669df6\"><\/rect><text x=\"405.96774193548384\" y=\"16.386666666666656\" text-anchor=\"middle\" font-size=\"11.5\" font-weight=\"700\" fill=\"#e8eaed\">\u2248\u20ac292 tys.<\/text><rect x=\"393.2096774193548\" y=\"18\" width=\"25.516129032258064\" height=\"202\" fill=\"transparent\"><\/rect><rect x=\"421.2258064516129\" y=\"106.88\" width=\"20.516129032258064\" height=\"113.12\" rx=\"1.5\" fill=\"#669df6\"><\/rect><rect x=\"418.7258064516129\" y=\"18\" width=\"25.516129032258064\" height=\"202\" fill=\"transparent\"><\/rect><rect x=\"446.741935483871\" y=\"192.39333333333335\" width=\"20.516129032258064\" height=\"27.606666666666666\" rx=\"1.5\" fill=\"#669df6\"><\/rect><text x=\"457\" y=\"238\" text-anchor=\"middle\" font-size=\"9.5\" fill=\"#9aa0a6\">16 maj<\/text><rect x=\"444.241935483871\" y=\"18\" width=\"25.516129032258064\" height=\"202\" fill=\"transparent\"><\/rect><rect x=\"472.258064516129\" y=\"218.5\" width=\"20.516129032258064\" height=\"1.5\" rx=\"1.5\" fill=\"#669df6\"><\/rect><rect x=\"469.758064516129\" y=\"18\" width=\"25.516129032258064\" height=\"202\" fill=\"transparent\"><\/rect><rect x=\"497.7741935483871\" y=\"218.5\" width=\"20.516129032258064\" height=\"1.5\" rx=\"1.5\" fill=\"#669df6\"><\/rect><rect x=\"495.2741935483871\" y=\"18\" width=\"25.516129032258064\" height=\"202\" fill=\"transparent\"><\/rect><rect x=\"523.2903225806451\" y=\"218.5\" width=\"20.516129032258064\" height=\"1.5\" rx=\"1.5\" fill=\"#669df6\"><\/rect><text x=\"533.5483870967741\" y=\"238\" text-anchor=\"middle\" font-size=\"9.5\" fill=\"#9aa0a6\">19 maj<\/text><rect x=\"520.7903225806451\" y=\"18\" width=\"25.516129032258064\" height=\"202\" fill=\"transparent\"><\/rect><rect x=\"548.8064516129032\" y=\"218.5\" width=\"20.516129032258064\" height=\"1.5\" rx=\"1.5\" fill=\"#669df6\"><\/rect><rect x=\"546.3064516129032\" y=\"18\" width=\"25.516129032258064\" height=\"202\" fill=\"transparent\"><\/rect><rect x=\"574.3225806451612\" y=\"218.5\" width=\"20.516129032258064\" height=\"1.5\" rx=\"1.5\" fill=\"#669df6\"><\/rect><rect x=\"571.8225806451612\" y=\"18\" width=\"25.516129032258064\" height=\"202\" fill=\"transparent\"><\/rect><rect x=\"599.8387096774194\" y=\"218.5\" width=\"20.516129032258064\" height=\"1.5\" rx=\"1.5\" fill=\"#669df6\"><\/rect><text x=\"610.0967741935484\" y=\"238\" text-anchor=\"middle\" font-size=\"9.5\" fill=\"#9aa0a6\">22 maj<\/text><rect x=\"597.3387096774194\" y=\"18\" width=\"25.516129032258064\" height=\"202\" fill=\"transparent\"><\/rect><rect x=\"625.3548387096774\" y=\"218.5\" width=\"20.516129032258064\" height=\"1.5\" rx=\"1.5\" fill=\"#669df6\"><\/rect><rect x=\"622.8548387096774\" y=\"18\" width=\"25.516129032258064\" height=\"202\" fill=\"transparent\"><\/rect><rect x=\"650.8709677419355\" y=\"218.5\" width=\"20.516129032258064\" height=\"1.5\" rx=\"1.5\" fill=\"#669df6\"><\/rect><rect x=\"648.3709677419355\" y=\"18\" width=\"25.516129032258064\" height=\"202\" fill=\"transparent\"><\/rect><rect x=\"676.3870967741935\" y=\"218.5\" width=\"20.516129032258064\" height=\"1.5\" rx=\"1.5\" fill=\"#669df6\"><\/rect><text x=\"686.6451612903226\" y=\"238\" text-anchor=\"middle\" font-size=\"9.5\" fill=\"#9aa0a6\">25 maj<\/text><rect x=\"673.8870967741935\" y=\"18\" width=\"25.516129032258064\" height=\"202\" fill=\"transparent\"><\/rect><rect x=\"701.9032258064516\" y=\"218.5\" width=\"20.516129032258064\" height=\"1.5\" rx=\"1.5\" fill=\"#669df6\"><\/rect><rect x=\"699.4032258064516\" y=\"18\" width=\"25.516129032258064\" height=\"202\" fill=\"transparent\"><\/rect><rect x=\"727.4193548387096\" y=\"218.5\" width=\"20.516129032258064\" height=\"1.5\" rx=\"1.5\" fill=\"#669df6\"><\/rect><rect x=\"724.9193548387096\" y=\"18\" width=\"25.516129032258064\" height=\"202\" fill=\"transparent\"><\/rect><rect x=\"752.9354838709677\" y=\"218.5\" width=\"20.516129032258064\" height=\"1.5\" rx=\"1.5\" fill=\"#669df6\"><\/rect><text x=\"763.1935483870967\" y=\"238\" text-anchor=\"middle\" font-size=\"9.5\" fill=\"#9aa0a6\">28 maj<\/text><rect x=\"750.4354838709677\" y=\"18\" width=\"25.516129032258064\" height=\"202\" fill=\"transparent\"><\/rect><rect x=\"778.4516129032259\" y=\"218.5\" width=\"20.516129032258064\" height=\"1.5\" rx=\"1.5\" fill=\"#669df6\"><\/rect><rect x=\"775.9516129032259\" y=\"18\" width=\"25.516129032258064\" height=\"202\" fill=\"transparent\"><\/rect><rect x=\"803.9677419354839\" y=\"218.5\" width=\"20.516129032258064\" height=\"1.5\" rx=\"1.5\" fill=\"#669df6\"><\/rect><rect x=\"801.4677419354839\" y=\"18\" width=\"25.516129032258064\" height=\"202\" fill=\"transparent\"><\/rect><rect x=\"829.483870967742\" y=\"218.5\" width=\"20.516129032258064\" height=\"1.5\" rx=\"1.5\" fill=\"#669df6\"><\/rect><text x=\"839.741935483871\" y=\"238\" text-anchor=\"middle\" font-size=\"9.5\" fill=\"#9aa0a6\">31 maj<\/text><rect x=\"826.983870967742\" y=\"18\" width=\"25.516129032258064\" height=\"202\" fill=\"transparent\"><\/rect><\/svg>\n      <\/div>\n      <p class=\"chart-note\">Zwyk\u0142y dzie\u0144 na tym koncie to kilkana\u015bcie euro, wi\u0119c na tej skali takie s\u0142upki s\u0105 po prostu niewidoczne. To nie jest zrzut ekranu z&nbsp;prawdziwej konsoli.<\/p>\n      <details class=\"datatable\">\n        <summary>Dane z wykresu (tabela)<\/summary>\n        <table>\n          <tbody><tr><th>Dzie\u0144<\/th><th>typowy<\/th><th>14 maja<\/th><th>15 maja<\/th><th>16 maja<\/th><\/tr>\n          <tr><th>Koszt<\/th><td>\u2248 \u20ac16<\/td><td>\u2248 \u20ac292 000<\/td><td>\u2248 \u20ac168 000<\/td><td>\u2248 \u20ac41 000<\/td><\/tr>\n        <\/tbody><\/table>\n      <\/details>\n    <\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Z tego incydentu wysz\u0142y trzy wnioski, kt\u00f3re trafi\u0142y prosto do naszego backlogu. Statyczne alerty bud\u017cetowe s\u0105 \u015blepe w&nbsp;\u015brodowiskach z&nbsp;ma\u0142ym spendem, bo procentowo gigantyczny wzrost wygl\u0105da na starcie jak drobne. Powiadomienia o&nbsp;nadu\u017cyciach id\u0105 wy\u0142\u0105cznie do w\u0142a\u015bciciela \u015brodowiska; partner rozliczaj\u0105cy standardowo nie widzi nic, dop\u00f3ki klient sam si\u0119 nie odezwie. No i&nbsp;nie istnia\u0142 \u017caden automatyczny bezpiecznik, kt\u00f3ry by t\u0119 konsumpcj\u0119 przydusi\u0142.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Postanowili\u015bmy, \u017ce u naszych klient\u00f3w takie incydenty pierwsi b\u0119dziemy zauwa\u017ca\u0107 my, a&nbsp;nie faktura. Tak powsta\u0142&nbsp;<strong>Cloud Cost Sentinel<\/strong>.<\/p>\n\n\n\n<h2 id=\"h-koszt-jest-sygnalem-bezpieczenstwa\" class=\"wp-block-heading\">Koszt jest sygna\u0142em bezpiecze\u0144stwa<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Bran\u017ca ma ju\u017c na to nazw\u0119:&nbsp;<strong>LLMjacking<\/strong>. Skradzione po\u015bwiadczenia chmurowe nie s\u0142u\u017c\u0105 do wykradania danych, tylko do masowej konsumpcji drogich API modeli j\u0119zykowych, cz\u0119sto odsprzedawanej dalej. Rachunek zostaje u&nbsp;ofiary. Publicznie opisywane przypadki to kwoty od kilkunastu do kilkudziesi\u0119ciu tysi\u0119cy dolar\u00f3w u&nbsp;pojedynczych deweloper\u00f3w i&nbsp;ma\u0142ych zespo\u0142\u00f3w; opisywane tu firmy nie by\u0142y wyj\u0105tkiem, tylko kolejnymi pozycjami w&nbsp;dobrze naoliwionym procederze.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Dlatego traktujemy telemetri\u0119 billingow\u0105 tak, jak SecOps traktuje logi: jako strumie\u0144 zdarze\u0144 do ci\u0105g\u0142ej analizy. Nag\u0142y wzrost kosztu prawie zawsze ma techniczn\u0105 przyczyn\u0119, a&nbsp;spora cz\u0119\u015b\u0107 tych przyczyn to incydenty:<\/p>\n\n\n\n<style data-wp-block-html=\"css\">\n  :root {\n    --red-50:#FDEBE6; --red-100:#FBD2C6; --red-400:#EA5528; --red-500:#E33203; --red-600:#C22B03; --red-700:#9B2202;\n    --blue-50:#E6F2FF; --blue-100:#CCE5FF; --blue-300:#66AFFF; --blue-500:#007BFF; --blue-600:#0069D9; --blue-700:#0056B3;\n    --sky-100:#DEF3FE; --sky-500:#17A2B8;\n    --ink-900:#212529; --ink-800:#343A40; --ink-700:#495057; --ink-600:#6C757D; --ink-500:#868E96;\n    --ink-300:#CED4DA; --ink-200:#DEE2E6; --ink-100:#E9ECEF; --ink-50:#F8F9FA; --white:#FFFFFF;\n    --green-500:#28A745; --amber-500:#FFC107; --danger-500:#DC3545;\n    --font-sans:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,\"Helvetica Neue\",Arial,\"Noto Sans\",sans-serif;\n    --font-mono:SFMono-Regular,Menlo,Monaco,Consolas,\"Liberation Mono\",\"Courier New\",monospace;\n    --shadow-xs:0 1px 2px rgba(11,27,43,.06);\n    --shadow-sm:0 1px 3px rgba(11,27,43,.08),0 1px 2px rgba(11,27,43,.04);\n    --shadow-md:0 6px 16px rgba(11,27,43,.08);\n    --shadow-lg:0 14px 32px rgba(11,27,43,.10);\n    --shadow-brand:0 12px 28px rgba(227,50,3,.26);\n    --ease:cubic-bezier(.2,0,0,1);\n  } \n .chips { display:grid; grid-template-columns:repeat(auto-fit,minmax(230px,1fr)); gap:12px; margin:26px 0 8px; }\n  .chip { display:flex; align-items:center; gap:11px; background:var(--ink-50); border:1px solid var(--ink-100); border-radius:12px; padding:13px 15px; font-size:14.5px; font-weight:600; color:var(--ink-800); }\n  .chip .ico { width:34px; height:34px; flex-shrink:0; display:flex; align-items:center; justify-content:center; border-radius:9px; background:var(--red-50); color:var(--red-500); }\n  .chip .ico svg { width:16px; height:16px; }\n<\/style>\n\n<div class=\"chips\">\n      <div class=\"chip\"><span class=\"ico\"><svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"7.5\" cy=\"15.5\" r=\"4.5\"><\/circle><path d=\"M10.5 12.5L20 3m-3 0h3v3\"><\/path><\/svg><\/span>Wyciek\u0142y klucz API lub konta serwisowego<\/div>\n      <div class=\"chip\"><span class=\"ico\"><svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"12\" cy=\"8\" r=\"4\"><\/circle><path d=\"M4 21a8 8 0 0116 0\"><\/path><\/svg><\/span>Przej\u0119ta sesja lub konto u\u017cytkownika<\/div>\n      <div class=\"chip\"><span class=\"ico\"><svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><rect x=\"6\" y=\"6\" width=\"12\" height=\"12\" rx=\"2\"><\/rect><path d=\"M9 1v3M15 1v3M9 20v3M15 20v3M1 9h3M1 15h3M20 9h3M20 15h3\"><\/path><\/svg><\/span>Cryptomining i odsprzeda\u017c mocy<\/div>\n      <div class=\"chip\"><span class=\"ico\"><svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"12\" cy=\"12\" r=\"3\"><\/circle><path d=\"M12 5V3M12 21v-2M5 12H3m18 0h-2M6.3 6.3 4.9 4.9m14.2 14.2-1.4-1.4M6.3 17.7l-1.4 1.4M19.1 4.9l-1.4 1.4\"><\/path><\/svg><\/span>B\u0142\u0119dna konfiguracja lub z\u0142a automatyzacja<\/div>\n      <div class=\"chip\"><span class=\"ico\"><svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><path d=\"M13 2L3 14h8l-1 8 10-12h-8l1-8z\"><\/path><\/svg><\/span>Niekontrolowane \u015brodowiska testowe<\/div>\n      <div class=\"chip\"><span class=\"ico\"><svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><path d=\"M12 19V5M5 12l7-7 7 7\"><\/path><\/svg><\/span>Nieoczekiwane skalowanie po wdro\u017ceniu<\/div>\n    <\/div>\n\n\n\n<h2 id=\"h-architektura-od-eksportu-billingu-do-pagerduty\" class=\"wp-block-heading\">Architektura: od eksportu billingu do PagerDuty<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Obs\u0142ugujemy setki kont billingowych Google Cloud. Ka\u017cde z&nbsp;nich ma pe\u0142ny eksport billingu do partycjonowanej tabeli BigQuery. To zreszt\u0105 jedyna rzecz, jakiej system wymaga od \u015brodowiska. Na tym fundamencie dzia\u0142a detektor: lekki, bezstanowy serwis w&nbsp;Cloud Run, wybudzany harmonogramem w&nbsp;kr\u00f3tkich, regularnych cyklach.<\/p>\n\n\n\n<style data-wp-block-html=\"css\">\n:root {\n    --red-50:#FDEBE6; --red-100:#FBD2C6; --red-400:#EA5528; --red-500:#E33203; --red-600:#C22B03; --red-700:#9B2202;\n    --blue-50:#E6F2FF; --blue-100:#CCE5FF; --blue-300:#66AFFF; --blue-500:#007BFF; --blue-600:#0069D9; --blue-700:#0056B3;\n    --sky-100:#DEF3FE; --sky-500:#17A2B8;\n    --ink-900:#212529; --ink-800:#343A40; --ink-700:#495057; --ink-600:#6C757D; --ink-500:#868E96;\n    --ink-300:#CED4DA; --ink-200:#DEE2E6; --ink-100:#E9ECEF; --ink-50:#F8F9FA; --white:#FFFFFF;\n    --green-500:#28A745; --amber-500:#FFC107; --danger-500:#DC3545;\n    --font-sans:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,\"Helvetica Neue\",Arial,\"Noto Sans\",sans-serif;\n    --font-mono:SFMono-Regular,Menlo,Monaco,Consolas,\"Liberation Mono\",\"Courier New\",monospace;\n    --shadow-xs:0 1px 2px rgba(11,27,43,.06);\n    --shadow-sm:0 1px 3px rgba(11,27,43,.08),0 1px 2px rgba(11,27,43,.04);\n    --shadow-md:0 6px 16px rgba(11,27,43,.08);\n    --shadow-lg:0 14px 32px rgba(11,27,43,.10);\n    --shadow-brand:0 12px 28px rgba(227,50,3,.26);\n    --ease:cubic-bezier(.2,0,0,1);\n  }\n.arch {\n  display: flex;\n  flex-direction: column;\nalign-items: stretch\n  gap: 0;\n  max-width: 420px;\n  margin: 34px auto 10px;\n}\n  .arch-col { background:var(--white); border:1px solid var(--ink-200); border-radius:16px; padding:18px; box-shadow:var(--shadow-sm); }\n  .arch-col h4 { font-size:13px; text-transform:uppercase; letter-spacing:.08em; color:var(--blue-600); margin-bottom:12px; }\n  .arch-item { font-size:13.5px; color:var(--ink-700); background:var(--ink-50); border:1px solid var(--ink-100); border-radius:9px; padding:8px 11px; margin-bottom:8px; line-height:1.4; }\n  .arch-item:last-child { margin-bottom:0; }\n  .arch-item b { color:var(--ink-900); font-weight:600; display:block; }\n  .arch-arrow { display:flex; align-items:center;\nalign-self: center;\n color:var(--blue-500); }\n  .arch-arrow svg { width:22px; height:22px; }\n  .arch-note { font-size:13px; color:var(--ink-500); margin-top:14px; }\n<\/style>\n\n<div class=\"arch\" role=\"img\" aria-label=\"Diagram architektury: eksporty billingowe w BigQuery, detektor w Cloud Run, warstwa decyzji, kana\u0142y reakcji\">\n    <div class=\"arch-col\">\n      <h4>Dane<\/h4>\n      <div class=\"arch-item\"><b>Eksporty billingowe<\/b>setki kont, standardowy eksport GCP<\/div>\n      <div class=\"arch-item\"><b>BigQuery<\/b>tabele partycjonowane po czasie ingestii<\/div>\n    <\/div>\n    <div class=\"arch-arrow\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><path d=\"M12 5v14M6 13l6 6 6-6\"><\/path><\/svg><\/div>\n    <div class=\"arch-col\">\n      <h4>Detekcja<\/h4>\n      <div class=\"arch-item\"><b>Detektor (Cloud Run)<\/b>kr\u00f3tki, regularny cykl (Pub\/Sub push)<\/div>\n      <div class=\"arch-item\"><b>4 zapytania okienne<\/b>dzienne \u00b7 godzinowe \u00b7 nowe konta \u00b7 prognoza miesi\u0119czna<\/div>\n    <\/div>\n    <div class=\"arch-arrow\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><path d=\"M12 5v14M6 13l6 6 6-6\"><\/path><\/svg><\/div>\n    <div class=\"arch-col\">\n      <h4>Decyzja<\/h4>\n      <div class=\"arch-item\"><b>Severity + progi<\/b>w kontek\u015bcie konta, parametry per wdro\u017cenie<\/div>\n      <div class=\"arch-item\"><b>Stan epizodu<\/b>hold \u00b7 deduplikacja \u00b7 cooldown \u00b7 autoclose<\/div>\n    <\/div>\n    <div class=\"arch-arrow\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><path d=\"M12 5v14M6 13l6 6 6-6\"><\/path><\/svg><\/div>\n    <div class=\"arch-col\">\n      <h4>Reakcja<\/h4>\n      <div class=\"arch-item\"><b>Google Chat + CRM<\/b>karta alertu i zadanie dla opiekuna klienta<\/div>\n      <div class=\"arch-item\"><b>PagerDuty<\/b>eskalacja krytycznych zdarze\u0144<\/div>\n      <div class=\"arch-item\"><b>Dashboard<\/b>Cloud Run za IAP, dwuj\u0119zyczny<\/div>\n    <\/div>\n  <\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Wszystkie elementy s\u0105 bezstanowe. Jedynym stanem systemu jest historia detekcji zapisywana z powrotem do BigQuery; to ona nap\u0119dza dashboard i decyzje o ponownym alertowaniu. Alerty przychodz\u0105 te\u017c do nas, nie tylko do klienta.<\/p>\n\n\n\n<h2 id=\"h-cztery-pytania-ktore-zadajemy-danym-w-kazdym-cyklu-analizy\" class=\"wp-block-heading\">Cztery pytania, kt\u00f3re zadajemy danym w ka\u017cdym cyklu analizy<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Ka\u017cdy typ anomalii to inne pytanie i&nbsp;inne okno odniesienia. Wsp\u00f3lny mianownik jest tutaj taki, \u017ce ka\u017cde konto por\u00f3wnujemy wy\u0142\u0105cznie z&nbsp;jego w\u0142asn\u0105 histori\u0105. Konkretne progi, d\u0142ugo\u015bci okien i&nbsp;pod\u0142ogi kwotowe to parametry konfiguracji dobierane per wdro\u017cenie, dlatego nie rozpisujemy ich tutaj co do liczby.<\/p>\n\n\n\n<style data-wp-block-html=\"css\">\n  :root {\n    --red-50:#FDEBE6; --red-100:#FBD2C6; --red-400:#EA5528; --red-500:#E33203; --red-600:#C22B03; --red-700:#9B2202;\n    --blue-50:#E6F2FF; --blue-100:#CCE5FF; --blue-300:#66AFFF; --blue-500:#007BFF; --blue-600:#0069D9; --blue-700:#0056B3;\n    --sky-100:#DEF3FE; --sky-500:#17A2B8;\n    --ink-900:#212529; --ink-800:#343A40; --ink-700:#495057; --ink-600:#6C757D; --ink-500:#868E96;\n    --ink-300:#CED4DA; --ink-200:#DEE2E6; --ink-100:#E9ECEF; --ink-50:#F8F9FA; --white:#FFFFFF;\n    --green-500:#28A745; --amber-500:#FFC107; --danger-500:#DC3545;\n    --font-sans:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,\"Helvetica Neue\",Arial,\"Noto Sans\",sans-serif;\n    --font-mono:SFMono-Regular,Menlo,Monaco,Consolas,\"Liberation Mono\",\"Courier New\",monospace;\n    --shadow-xs:0 1px 2px rgba(11,27,43,.06);\n    --shadow-sm:0 1px 3px rgba(11,27,43,.08),0 1px 2px rgba(11,27,43,.04);\n    --shadow-md:0 6px 16px rgba(11,27,43,.08);\n    --shadow-lg:0 14px 32px rgba(11,27,43,.10);\n    --shadow-brand:0 12px 28px rgba(227,50,3,.26);\n    --ease:cubic-bezier(.2,0,0,1);\n  } \n.table-scroll { overflow-x:auto; -webkit-overflow-scrolling:touch; }\n  .table-scroll .det-table { min-width:560px; }\n  .det-table { width:100%; border-collapse:separate; border-spacing:0; margin:26px 0 8px; font-size:14.5px; box-shadow:var(--shadow-sm); border-radius:16px; overflow:hidden; border:1px solid var(--ink-200); }\n  .det-table th { background:var(--ink-900); color:#fff; text-align:left; font-size:12.5px; text-transform:uppercase; letter-spacing:.07em; padding:13px 16px; font-weight:600; }\n  .det-table td { padding:14px 16px; border-top:1px solid var(--ink-100); vertical-align:top; color:var(--ink-700); background:var(--white); }\n  .det-table td:first-child { font-weight:600; color:var(--ink-900); white-space:nowrap; }\n  .det-table tr:nth-child(even) td { background:var(--ink-50); }\n<\/style>\n\n<div class=\"table-scroll\">\n    <table class=\"det-table\">\n      <thead><tr><th>Detektor<\/th><th>Pytanie<\/th><th>Punkt odniesienia<\/th><\/tr><\/thead>\n      <tbody>\n        <tr><td>Dzienny (z-score)<\/td><td>Czy wczorajszy albo dzisiejszy koszt odstaje od normy?<\/td><td>Krocz\u0105ce okno ostatnich dni, bez dnia bie\u017c\u0105cego; alarm przy statystycznie istotnym odchyleniu powy\u017cej kwotowej pod\u0142ogi<\/td><\/tr>\n        <tr><td>Godzinowy spike<\/td><td>Czy ta godzina jest gwa\u0142townie dro\u017csza ni\u017c zwykle?<\/td><td>Profil godzinowy konta z ostatnich dni; alarm przy wielokrotno\u015bci typowego kosztu tej godziny<\/td><\/tr>\n        <tr><td>Nowe konto<\/td><td>Czy \u015bwie\u017co utworzone konto od razu du\u017co wydaje?<\/td><td>Konto bez historii, kt\u00f3re natychmiast generuje wysoki spend; jeden z najsilniejszych sygna\u0142\u00f3w nadu\u017cycia<\/td><\/tr>\n        <tr><td>Prognoza miesi\u0119czna<\/td><td>Dok\u0105d zmierza ten miesi\u0105c?<\/td><td>Ekstrapolacja month-to-date na tle poprzednich miesi\u0119cy; alarm, gdy prognoza wyra\u017anie ucieka<\/td><\/tr>\n      <\/tbody>\n    <\/table>\n    <\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Serce detektora dziennego to jedno zapytanie okienne w BigQuery i akurat ta cz\u0119\u015b\u0107 wdro\u017cenia jest najprostsza. Prawdziwa z\u0142o\u017cono\u015b\u0107 kryje si\u0119 w szczeg\u00f3\u0142ach: dzie\u0144 bie\u017c\u0105cy celowo zostaje poza baseline&#8217;em (Dlaczego? Oka\u017ce si\u0119 za chwil\u0119\u2026), okna i progi trzeba dostroi\u0107 do realnych danych, a samym danym nie wolno \u015blepo ufa\u0107, ani ich kompletno\u015bci, ani uk\u0142adowi. Brzmi jak drobiazgi, ale ka\u017cdy z nich kiedy\u015b zabola\u0142.<\/p>\n\n\n\n<h2 id=\"h-lipiec-ta-sama-klasa-ataku-inny-final\" class=\"wp-block-heading\">Lipiec: ta sama klasa ataku, inny fina\u0142<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Dwa miesi\u0105ce po majowym incydencie ten sam scenariusz ataku uderzy\u0142 w&nbsp;naszego klienta, niewielkie studio aplikacji mobilnych. \u015arodowisko to by\u0142o ju\u017c wtedy obj\u0119te monitoringiem Cloud Cost Sentinel. Pocz\u0105tek by\u0142 banalny: jeden z&nbsp;deweloper\u00f3w chcia\u0142 zainstalowa\u0107 popularne narz\u0119dzie i&nbsp;klikn\u0105\u0142 sponsorowany wynik wyszukiwania. Strona wygl\u0105da\u0142a jak trzeba, instalator te\u017c. W&nbsp;\u015brodku siedzia\u0142 stealer z&nbsp;kampanii, kt\u00f3r\u0105 publicznie dokumentowa\u0142y m.in. Bitdefender i&nbsp;Trend Micro. Mimo \u017ce konto mia\u0142o w\u0142\u0105czone MFA, niczego to nie da\u0142o, bo ten typ malware&#8217;u dzia\u0142a z&nbsp;wn\u0119trza ju\u017c zalogowanego komputera. Kradnie sesj\u0119, kt\u00f3ra uwierzytelnienie ma ju\u017c dawno za sob\u0105.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Wieczorem, 4 lipca, skradzionymi po\u015bwiadczeniami zalogowa\u0142 si\u0119 skrypt z&nbsp;serwera hostingowego w&nbsp;Niemczech. Po latach wsp\u00f3\u0142pracy przy Firebase konto mia\u0142o rol\u0119 Editor w&nbsp;kilkunastu projektach, co w&nbsp;Google Cloud oznacza mi\u0119dzy innymi prawo do w\u0142\u0105czania API i&nbsp;tworzenia kluczy. Automat potrzebowa\u0142&nbsp;<strong>3 minut i&nbsp;16 sekund<\/strong>, \u017ceby przej\u015b\u0107 po projektach i&nbsp;zostawi\u0107 w&nbsp;nich 16 w\u0142\u0105czonych API, 9 kont serwisowych i&nbsp;28 kluczy. Potem na dwana\u015bcie dni zapad\u0142a cisza. Klucze le\u017ca\u0142y i&nbsp;czeka\u0142y.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">16 lipca o&nbsp;13:45 klucze o\u017cy\u0142y i&nbsp;masowa konsumpcja Gemini ruszy\u0142a we wszystkich projektach naraz. O&nbsp;<strong>4:30 nad ranem<\/strong>, gdy tylko pierwsze dane tej doby dotar\u0142y do eksportu billingowego, detektor podni\u00f3s\u0142 alarm. Karta CRITICAL wygl\u0105da\u0142a nast\u0119puj\u0105co: z-score grubo ponad pi\u0119\u0107set, wzrost liczony w&nbsp;dziesi\u0105tkach tysi\u0119cy procent, a&nbsp;w rozbiciu na us\u0142ugi praktycznie ca\u0142y przyrost pochodzi\u0142 z&nbsp;jednej pozycji: Gemini API, us\u0142ugi wcze\u015bniej na tym koncie w&nbsp;og\u00f3le nieu\u017cywanej. Posz\u0142a eskalacja.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Ten klient nie korzysta\u0142 z&nbsp;naszego sta\u0142ego wsparcia i&nbsp;nie nadawa\u0142 nam uprawnie\u0144 do swojego \u015brodowiska, wi\u0119c nasza rola ko\u0144czy\u0142a si\u0119 na alarmie i&nbsp;eskalacji. Zablokowa\u0107 nadu\u017cycie m\u00f3g\u0142 tylko on sam. Zesp\u00f3\u0142 klienta podj\u0105\u0142 temat na pocz\u0105tku dnia: oko\u0142o 08:20 ruch wr\u00f3ci\u0142 do baseline&#8217;u, a&nbsp;do 12:40 obce klucze i&nbsp;konta serwisowe znikn\u0119\u0142y ze wszystkich projekt\u00f3w.<\/p>\n\n\n\n<style data-wp-block-html=\"css\">\n:root {\n    --red-50:#FDEBE6; --red-100:#FBD2C6; --red-400:#EA5528; --red-500:#E33203; --red-600:#C22B03; --red-700:#9B2202;\n    --blue-50:#E6F2FF; --blue-100:#CCE5FF; --blue-300:#66AFFF; --blue-500:#007BFF; --blue-600:#0069D9; --blue-700:#0056B3;\n    --sky-100:#DEF3FE; --sky-500:#17A2B8;\n    --ink-900:#212529; --ink-800:#343A40; --ink-700:#495057; --ink-600:#6C757D; --ink-500:#868E96;\n    --ink-300:#CED4DA; --ink-200:#DEE2E6; --ink-100:#E9ECEF; --ink-50:#F8F9FA; --white:#FFFFFF;\n    --green-500:#28A745; --amber-500:#FFC107; --danger-500:#DC3545;\n    --font-sans:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,\"Helvetica Neue\",Arial,\"Noto Sans\",sans-serif;\n    --font-mono:SFMono-Regular,Menlo,Monaco,Consolas,\"Liberation Mono\",\"Courier New\",monospace;\n    --shadow-xs:0 1px 2px rgba(11,27,43,.06);\n    --shadow-sm:0 1px 3px rgba(11,27,43,.08),0 1px 2px rgba(11,27,43,.04);\n    --shadow-md:0 6px 16px rgba(11,27,43,.08);\n    --shadow-lg:0 14px 32px rgba(11,27,43,.10);\n    --shadow-brand:0 12px 28px rgba(227,50,3,.26);\n    --ease:cubic-bezier(.2,0,0,1);\n  }\n\/* \u2500\u2500 Figures \/ console-style charts \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 *\/\n  figure.chart { background:var(--white); border:1px solid var(--ink-200); border-radius:16px; padding:24px; margin:30px 0; box-shadow:var(--shadow-sm); }\n  figure.chart figcaption { margin-bottom:4px; }\n  figure.chart .f-title { font-size:16px; font-weight:700; color:var(--ink-900); }\n  figure.chart .f-sub { font-size:13px; color:var(--ink-500); margin-top:3px; }\n  .console { background:#1b1d21; border-radius:12px; padding:16px 16px 8px; margin-top:16px; }\n  .console-head { display:flex; justify-content:space-between; align-items:baseline; gap:14px; flex-wrap:wrap; padding:2px 6px 12px; border-bottom:1px solid #33363b; margin-bottom:6px; }\n  .console-head .range { font-size:12px; color:#9aa0a6; }\n  .console-head .big { font-size:17px; font-weight:700; color:#e8eaed; font-family:var(--font-mono); }\n  .console-head .delta { font-size:12.5px; font-weight:600; color:#f28b45; }\n  .chart-svg { width:100%; height:auto; display:block; }\n  .chart-note { font-size:12.5px; color:var(--ink-500); margin-top:14px; }\n  details.datatable { margin-top:12px; font-size:13px; }\n  details.datatable summary { cursor:pointer; color:var(--ink-600); font-weight:600; }\n  details.datatable table { border-collapse:collapse; margin-top:10px; }\n  details.datatable td, details.datatable th { border:1px solid var(--ink-200); padding:5px 10px; text-align:right; }\n  details.datatable th { background:var(--ink-50); font-weight:600; color:var(--ink-800); }\n  .tip { position:fixed; z-index:90; pointer-events:none; background:var(--ink-900); color:#fff; font-size:12.5px; padding:7px 11px; border-radius:8px; box-shadow:var(--shadow-lg); opacity:0; transition:opacity .1s; white-space:nowrap; }\n\n  \/* \u2500\u2500 Dark band (episode lifecycle) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 *\/\n  .band { background:var(--ink-900); color:#fff; padding:88px 0; position:relative; overflow:hidden; margin:80px 0; }\n  .band::before { content:\"\"; position:absolute; inset:0; background-image:linear-gradient(rgba(255,255,255,.04) 1px,transparent 1px),linear-gradient(90deg,rgba(255,255,255,.04) 1px,transparent 1px); background-size:52px 52px; -webkit-mask-image:radial-gradient(at 30% 0%,#000,transparent 78%); mask-image:radial-gradient(at 30% 0%,#000,transparent 78%); pointer-events:none; }\n  .band .wrap { position:relative; }\n  .band h2 { color:#fff; font-size:clamp(26px,3vw,36px); margin:14px 0 16px; }\n  .band .prose > p { color:rgba(255,255,255,.72); }\n  .band strong { color:#fff; }\n  .flow { display:grid; grid-template-columns:1fr auto 1fr auto 1fr; gap:12px; align-items:stretch; margin:36px 0 14px; }\n  .phase { border-radius:14px; padding:18px; background:rgba(255,255,255,.05); border:1px solid rgba(255,255,255,.14); }\n  .phase .tag { display:inline-block; font-size:11px; font-weight:700; letter-spacing:.07em; text-transform:uppercase; padding:4px 11px; border-radius:999px; margin-bottom:11px; }\n  .phase p { font-size:13.5px; color:rgba(255,255,255,.72); line-height:1.5; }\n  .phase p b { color:#fff; font-weight:600; }\n  .phase--alert .tag { background:var(--red-400); color:#fff; }\n  .phase--hold .tag { background:var(--amber-500); color:#3A2F00; }\n  .phase--close .tag { background:#5DD879; color:#062D11; }\n  .flow-arrow { display:flex; align-items:center; color:var(--blue-300); }\n  .flow-arrow svg { width:20px; height:20px; }\n  .flow-branch { margin-top:6px; border:1px dashed rgba(102,175,255,.5); border-radius:12px; padding:14px 18px; font-size:13.5px; color:rgba(255,255,255,.82); }\n  .flow-branch b { color:var(--blue-300); }\n  @media (max-width:960px) {\n    .stats { grid-template-columns:repeat(2,1fr); }\n    .arch { grid-template-columns:1fr; }\n    .arch-arrow { justify-content:center; transform:rotate(90deg); padding:2px 0; }\n    .flow { grid-template-columns:1fr; }\n    .flow-arrow { justify-content:center; transform:rotate(90deg); }\n    .lessons { grid-template-columns:1fr; }\n    .cta-box { flex-direction:column; align-items:flex-start; padding:36px 28px; }\n  }\n  @media (max-width:560px) {\n    .stats { grid-template-columns:1fr 1fr; gap:10px; }\n    .stat b { font-size:23px; }\n    .head-tag { display:none; }\n    .console-head .big { font-size:15px; }\n  }\n<\/style>\n\n<figure class=\"chart\">\n      <figcaption>\n        <div class=\"f-title\">Ten sam widok, drugi incydent<\/div>\n        <div class=\"f-sub\">Koszt dzienny konta, lipiec 2026 (ilustracja pogl\u0105dowa, rzeczywiste rz\u0119dy wielko\u015bci)<\/div>\n      <\/figcaption>\n      <div class=\"console\">\n        <div class=\"console-head\">\n          <div><div class=\"range\">1\u201327 lipca 2026 \u00b7 koszt \u0142\u0105czny<\/div><div class=\"big\">$33 921,19<\/div><\/div>\n          <div class=\"delta\">wzrost +3 070% vs poprzedni okres<\/div>\n        <\/div>\n        <svg id=\"chartJul\" class=\"chart-svg\" viewBox=\"0 0 860 250\" role=\"img\" aria-label=\"Wykres s\u0142upkowy kosztu dziennego w lipcu: warto\u015bci bliskie zeru przez ca\u0142y miesi\u0105c i jeden s\u0142upek 16 lipca o wysoko\u015bci oko\u0142o 33 tysi\u0119cy dolar\u00f3w\"><line x1=\"64\" x2=\"850\" y1=\"220\" y2=\"220\" stroke=\"#3c4043\" stroke-width=\"1\"><\/line><text x=\"54\" y=\"224\" text-anchor=\"end\" font-size=\"11\" fill=\"#9aa0a6\">$0<\/text><line x1=\"64\" x2=\"850\" y1=\"169.5\" y2=\"169.5\" stroke=\"#3c4043\" stroke-width=\"1\"><\/line><text x=\"54\" y=\"173.5\" text-anchor=\"end\" font-size=\"11\" fill=\"#9aa0a6\">$10 tys.<\/text><line x1=\"64\" x2=\"850\" y1=\"119\" y2=\"119\" stroke=\"#3c4043\" stroke-width=\"1\"><\/line><text x=\"54\" y=\"123\" text-anchor=\"end\" font-size=\"11\" fill=\"#9aa0a6\">$20 tys.<\/text><line x1=\"64\" x2=\"850\" y1=\"68.5\" y2=\"68.5\" stroke=\"#3c4043\" stroke-width=\"1\"><\/line><text x=\"54\" y=\"72.5\" text-anchor=\"end\" font-size=\"11\" fill=\"#9aa0a6\">$30 tys.<\/text><line x1=\"64\" x2=\"850\" y1=\"18\" y2=\"18\" stroke=\"#3c4043\" stroke-width=\"1\"><\/line><text x=\"54\" y=\"22\" text-anchor=\"end\" font-size=\"11\" fill=\"#9aa0a6\">$40 tys.<\/text><rect x=\"64\" y=\"218.5\" width=\"20.516129032258064\" height=\"1.5\" rx=\"1.5\" fill=\"#669df6\"><\/rect><text x=\"74.25806451612902\" y=\"238\" text-anchor=\"middle\" font-size=\"9.5\" fill=\"#9aa0a6\">1 lip<\/text><rect x=\"61.5\" y=\"18\" width=\"25.516129032258064\" height=\"202\" fill=\"transparent\"><\/rect><rect x=\"89.51612903225806\" y=\"218.5\" width=\"20.516129032258064\" height=\"1.5\" rx=\"1.5\" fill=\"#669df6\"><\/rect><rect x=\"87.01612903225806\" y=\"18\" width=\"25.516129032258064\" height=\"202\" fill=\"transparent\"><\/rect><rect x=\"115.03225806451613\" y=\"218.5\" width=\"20.516129032258064\" height=\"1.5\" rx=\"1.5\" fill=\"#669df6\"><\/rect><rect x=\"112.53225806451613\" y=\"18\" width=\"25.516129032258064\" height=\"202\" fill=\"transparent\"><\/rect><rect x=\"140.5483870967742\" y=\"218.5\" width=\"20.516129032258064\" height=\"1.5\" rx=\"1.5\" fill=\"#669df6\"><\/rect><text x=\"150.80645161290323\" y=\"238\" text-anchor=\"middle\" font-size=\"9.5\" fill=\"#9aa0a6\">4 lip<\/text><rect x=\"138.0483870967742\" y=\"18\" width=\"25.516129032258064\" height=\"202\" fill=\"transparent\"><\/rect><rect x=\"166.06451612903226\" y=\"218.5\" width=\"20.516129032258064\" height=\"1.5\" rx=\"1.5\" fill=\"#669df6\"><\/rect><rect x=\"163.56451612903226\" y=\"18\" width=\"25.516129032258064\" height=\"202\" fill=\"transparent\"><\/rect><rect x=\"191.5806451612903\" y=\"218.5\" width=\"20.516129032258064\" height=\"1.5\" rx=\"1.5\" fill=\"#669df6\"><\/rect><rect x=\"189.0806451612903\" y=\"18\" width=\"25.516129032258064\" height=\"202\" fill=\"transparent\"><\/rect><rect x=\"217.09677419354838\" y=\"218.5\" width=\"20.516129032258064\" height=\"1.5\" rx=\"1.5\" fill=\"#669df6\"><\/rect><text x=\"227.3548387096774\" y=\"238\" text-anchor=\"middle\" font-size=\"9.5\" fill=\"#9aa0a6\">7 lip<\/text><rect x=\"214.59677419354838\" y=\"18\" width=\"25.516129032258064\" height=\"202\" fill=\"transparent\"><\/rect><rect x=\"242.61290322580646\" y=\"218.5\" width=\"20.516129032258064\" height=\"1.5\" rx=\"1.5\" fill=\"#669df6\"><\/rect><rect x=\"240.11290322580646\" y=\"18\" width=\"25.516129032258064\" height=\"202\" fill=\"transparent\"><\/rect><rect x=\"268.1290322580645\" y=\"218.5\" width=\"20.516129032258064\" height=\"1.5\" rx=\"1.5\" fill=\"#669df6\"><\/rect><rect x=\"265.6290322580645\" y=\"18\" width=\"25.516129032258064\" height=\"202\" fill=\"transparent\"><\/rect><rect x=\"293.64516129032256\" y=\"218.5\" width=\"20.516129032258064\" height=\"1.5\" rx=\"1.5\" fill=\"#669df6\"><\/rect><text x=\"303.9032258064516\" y=\"238\" text-anchor=\"middle\" font-size=\"9.5\" fill=\"#9aa0a6\">10 lip<\/text><rect x=\"291.14516129032256\" y=\"18\" width=\"25.516129032258064\" height=\"202\" fill=\"transparent\"><\/rect><rect x=\"319.1612903225806\" y=\"218.5\" width=\"20.516129032258064\" height=\"1.5\" rx=\"1.5\" fill=\"#669df6\"><\/rect><rect x=\"316.6612903225806\" y=\"18\" width=\"25.516129032258064\" height=\"202\" fill=\"transparent\"><\/rect><rect x=\"344.6774193548387\" y=\"218.5\" width=\"20.516129032258064\" height=\"1.5\" rx=\"1.5\" fill=\"#669df6\"><\/rect><rect x=\"342.1774193548387\" y=\"18\" width=\"25.516129032258064\" height=\"202\" fill=\"transparent\"><\/rect><rect x=\"370.19354838709677\" y=\"218.5\" width=\"20.516129032258064\" height=\"1.5\" rx=\"1.5\" fill=\"#669df6\"><\/rect><text x=\"380.4516129032258\" y=\"238\" text-anchor=\"middle\" font-size=\"9.5\" fill=\"#9aa0a6\">13 lip<\/text><rect x=\"367.69354838709677\" y=\"18\" width=\"25.516129032258064\" height=\"202\" fill=\"transparent\"><\/rect><rect x=\"395.7096774193548\" y=\"218.5\" width=\"20.516129032258064\" height=\"1.5\" rx=\"1.5\" fill=\"#669df6\"><\/rect><rect x=\"393.2096774193548\" y=\"18\" width=\"25.516129032258064\" height=\"202\" fill=\"transparent\"><\/rect><rect x=\"421.2258064516129\" y=\"218.5\" width=\"20.516129032258064\" height=\"1.5\" rx=\"1.5\" fill=\"#669df6\"><\/rect><rect x=\"418.7258064516129\" y=\"18\" width=\"25.516129032258064\" height=\"202\" fill=\"transparent\"><\/rect><rect x=\"446.741935483871\" y=\"52.082449999999994\" width=\"20.516129032258064\" height=\"167.91755\" rx=\"1.5\" fill=\"#669df6\"><\/rect><text x=\"457\" y=\"45.082449999999994\" text-anchor=\"middle\" font-size=\"11.5\" font-weight=\"700\" fill=\"#e8eaed\">\u2248$33,3 tys.<\/text><text x=\"457\" y=\"238\" text-anchor=\"middle\" font-size=\"9.5\" fill=\"#9aa0a6\">16 lip<\/text><rect x=\"444.241935483871\" y=\"18\" width=\"25.516129032258064\" height=\"202\" fill=\"transparent\"><\/rect><rect x=\"472.258064516129\" y=\"216.7175\" width=\"20.516129032258064\" height=\"3.2825\" rx=\"1.5\" fill=\"#669df6\"><\/rect><rect x=\"469.758064516129\" y=\"18\" width=\"25.516129032258064\" height=\"202\" fill=\"transparent\"><\/rect><rect x=\"497.7741935483871\" y=\"218.5\" width=\"20.516129032258064\" height=\"1.5\" rx=\"1.5\" fill=\"#669df6\"><\/rect><rect x=\"495.2741935483871\" y=\"18\" width=\"25.516129032258064\" height=\"202\" fill=\"transparent\"><\/rect><rect x=\"523.2903225806451\" y=\"218.5\" width=\"20.516129032258064\" height=\"1.5\" rx=\"1.5\" fill=\"#669df6\"><\/rect><text x=\"533.5483870967741\" y=\"238\" text-anchor=\"middle\" font-size=\"9.5\" fill=\"#9aa0a6\">19 lip<\/text><rect x=\"520.7903225806451\" y=\"18\" width=\"25.516129032258064\" height=\"202\" fill=\"transparent\"><\/rect><rect x=\"548.8064516129032\" y=\"218.5\" width=\"20.516129032258064\" height=\"1.5\" rx=\"1.5\" fill=\"#669df6\"><\/rect><rect x=\"546.3064516129032\" y=\"18\" width=\"25.516129032258064\" height=\"202\" fill=\"transparent\"><\/rect><rect x=\"574.3225806451612\" y=\"218.5\" width=\"20.516129032258064\" height=\"1.5\" rx=\"1.5\" fill=\"#669df6\"><\/rect><rect x=\"571.8225806451612\" y=\"18\" width=\"25.516129032258064\" height=\"202\" fill=\"transparent\"><\/rect><rect x=\"599.8387096774194\" y=\"218.5\" width=\"20.516129032258064\" height=\"1.5\" rx=\"1.5\" fill=\"#669df6\"><\/rect><text x=\"610.0967741935484\" y=\"238\" text-anchor=\"middle\" font-size=\"9.5\" fill=\"#9aa0a6\">22 lip<\/text><rect x=\"597.3387096774194\" y=\"18\" width=\"25.516129032258064\" height=\"202\" fill=\"transparent\"><\/rect><rect x=\"625.3548387096774\" y=\"218.5\" width=\"20.516129032258064\" height=\"1.5\" rx=\"1.5\" fill=\"#669df6\"><\/rect><rect x=\"622.8548387096774\" y=\"18\" width=\"25.516129032258064\" height=\"202\" fill=\"transparent\"><\/rect><rect x=\"650.8709677419355\" y=\"218.5\" width=\"20.516129032258064\" height=\"1.5\" rx=\"1.5\" fill=\"#669df6\"><\/rect><rect x=\"648.3709677419355\" y=\"18\" width=\"25.516129032258064\" height=\"202\" fill=\"transparent\"><\/rect><rect x=\"676.3870967741935\" y=\"218.5\" width=\"20.516129032258064\" height=\"1.5\" rx=\"1.5\" fill=\"#669df6\"><\/rect><text x=\"686.6451612903226\" y=\"238\" text-anchor=\"middle\" font-size=\"9.5\" fill=\"#9aa0a6\">25 lip<\/text><rect x=\"673.8870967741935\" y=\"18\" width=\"25.516129032258064\" height=\"202\" fill=\"transparent\"><\/rect><rect x=\"701.9032258064516\" y=\"218.5\" width=\"20.516129032258064\" height=\"1.5\" rx=\"1.5\" fill=\"#669df6\"><\/rect><rect x=\"699.4032258064516\" y=\"18\" width=\"25.516129032258064\" height=\"202\" fill=\"transparent\"><\/rect><rect x=\"727.4193548387096\" y=\"218.5\" width=\"20.516129032258064\" height=\"1.5\" rx=\"1.5\" fill=\"#669df6\"><\/rect><rect x=\"724.9193548387096\" y=\"18\" width=\"25.516129032258064\" height=\"202\" fill=\"transparent\"><\/rect><rect x=\"752.9354838709677\" y=\"218.5\" width=\"20.516129032258064\" height=\"1.5\" rx=\"1.5\" fill=\"#669df6\"><\/rect><text x=\"763.1935483870967\" y=\"238\" text-anchor=\"middle\" font-size=\"9.5\" fill=\"#9aa0a6\">28 lip<\/text><rect x=\"750.4354838709677\" y=\"18\" width=\"25.516129032258064\" height=\"202\" fill=\"transparent\"><\/rect><rect x=\"778.4516129032259\" y=\"218.5\" width=\"20.516129032258064\" height=\"1.5\" rx=\"1.5\" fill=\"#669df6\"><\/rect><rect x=\"775.9516129032259\" y=\"18\" width=\"25.516129032258064\" height=\"202\" fill=\"transparent\"><\/rect><rect x=\"803.9677419354839\" y=\"218.5\" width=\"20.516129032258064\" height=\"1.5\" rx=\"1.5\" fill=\"#669df6\"><\/rect><rect x=\"801.4677419354839\" y=\"18\" width=\"25.516129032258064\" height=\"202\" fill=\"transparent\"><\/rect><rect x=\"829.483870967742\" y=\"218.5\" width=\"20.516129032258064\" height=\"1.5\" rx=\"1.5\" fill=\"#669df6\"><\/rect><text x=\"839.741935483871\" y=\"238\" text-anchor=\"middle\" font-size=\"9.5\" fill=\"#9aa0a6\">31 lip<\/text><rect x=\"826.983870967742\" y=\"18\" width=\"25.516129032258064\" height=\"202\" fill=\"transparent\"><\/rect><\/svg>\n      <\/div>\n      <p class=\"chart-note\">Baseline tego konta to oko\u0142o 20 dolar\u00f3w dziennie. Jeden dzie\u0144 aktywnego nadu\u017cycia wystarczy\u0142, \u017ceby wielokrotnie przebi\u0107 \u0142\u0105czne wydatki konta z&nbsp;ca\u0142ego roku. To nie jest zrzut ekranu z&nbsp;konsoli klienta.<\/p>\n      <details class=\"datatable\">\n        <summary>Dane z wykresu (tabela)<\/summary>\n        <table>\n          <tbody><tr><th>Dzie\u0144<\/th><th>typowy<\/th><th>16 lipca<\/th><th>17 lipca<\/th><\/tr>\n          <tr><th>Koszt<\/th><td>\u2248 $20<\/td><td>\u2248 $33 250<\/td><td>\u2248 $650<\/td><\/tr>\n        <\/tbody><\/table>\n      <\/details>\n    <\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Gdy eksport dope\u0142ni\u0142 dob\u0119, licznik zatrzyma\u0142 si\u0119 na&nbsp;<strong>trzydziestu trzech tysi\u0105cach dolar\u00f3w<\/strong>, a&nbsp;odchylenie uros\u0142o do 842 sigm. Dzie\u0144 zamkn\u0105\u0142 si\u0119 906 razy powy\u017cej normy konta. Na osi czasu wygl\u0105da to tak: konsumpcja ruszy\u0142a po 13:45, alarm wyszed\u0142 o&nbsp;4:30, bo tyle zaj\u0119\u0142o dotarcie danych do eksportu billingowego, a&nbsp;ca\u0142a reszta to czas reakcji po stronie klienta. By\u0142 pi\u0105tek. Majowa historia pokazuje do\u015b\u0107 dok\u0142adnie, co by si\u0119 sta\u0142o, gdyby ta konsumpcja spokojnie przepracowa\u0142a weekend. Tam, gdzie mamy nie tylko mandat do eskalacji, ale te\u017c uprawnienia do reakcji, te kilka godzin potrafi skurczy\u0107 si\u0119 do kilku minut.<\/p>\n\n\n\n<style data-wp-block-html=\"css\">\n  :root {\n    --red-50:#FDEBE6; --red-100:#FBD2C6; --red-400:#EA5528; --red-500:#E33203; --red-600:#C22B03; --red-700:#9B2202;\n    --blue-50:#E6F2FF; --blue-100:#CCE5FF; --blue-300:#66AFFF; --blue-500:#007BFF; --blue-600:#0069D9; --blue-700:#0056B3;\n    --sky-100:#DEF3FE; --sky-500:#17A2B8;\n    --ink-900:#212529; --ink-800:#343A40; --ink-700:#495057; --ink-600:#6C757D; --ink-500:#868E96;\n    --ink-300:#CED4DA; --ink-200:#DEE2E6; --ink-100:#E9ECEF; --ink-50:#F8F9FA; --white:#FFFFFF;\n    --green-500:#28A745; --amber-500:#FFC107; --danger-500:#DC3545;\n    --font-sans:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,\"Helvetica Neue\",Arial,\"Noto Sans\",sans-serif;\n    --font-mono:SFMono-Regular,Menlo,Monaco,Consolas,\"Liberation Mono\",\"Courier New\",monospace;\n    --shadow-xs:0 1px 2px rgba(11,27,43,.06);\n    --shadow-sm:0 1px 3px rgba(11,27,43,.08),0 1px 2px rgba(11,27,43,.04);\n    --shadow-md:0 6px 16px rgba(11,27,43,.08);\n    --shadow-lg:0 14px 32px rgba(11,27,43,.10);\n    --shadow-brand:0 12px 28px rgba(227,50,3,.26);\n    --ease:cubic-bezier(.2,0,0,1);\n  }  \n.callout { border-left:4px solid var(--red-500); background:var(--red-50); border-radius:0 12px 12px 0; padding:18px 22px; margin:28px 0; font-size:16px; color:var(--ink-800); }\n  .callout--blue { border-left-color:var(--blue-500); background:var(--blue-50); }\n<\/style>\n\n<div class=\"callout\">Im wcze\u015bniej kosztowa anomalia trafia do w\u0142a\u015bciwej osoby, tym mniejsza jest r\u00f3\u017cnica mi\u0119dzy \u201eciekawostk\u0105 w dashboardzie&#8221; a&nbsp;sze\u015bciocyfrowym rachunkiem.<\/div>\n\n\n\n<style data-wp-block-html=\"css\">\n:root {\n    --red-50:#FDEBE6; --red-100:#FBD2C6; --red-400:#EA5528; --red-500:#E33203; --red-600:#C22B03; --red-700:#9B2202;\n    --blue-50:#E6F2FF; --blue-100:#CCE5FF; --blue-300:#66AFFF; --blue-500:#007BFF; --blue-600:#0069D9; --blue-700:#0056B3;\n    --sky-100:#DEF3FE; --sky-500:#17A2B8;\n    --ink-900:#212529; --ink-800:#343A40; --ink-700:#495057; --ink-600:#6C757D; --ink-500:#868E96;\n    --ink-300:#CED4DA; --ink-200:#DEE2E6; --ink-100:#E9ECEF; --ink-50:#F8F9FA; --white:#FFFFFF;\n    --green-500:#28A745; --amber-500:#FFC107; --danger-500:#DC3545;\n    --font-sans:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,\"Helvetica Neue\",Arial,\"Noto Sans\",sans-serif;\n    --font-mono:SFMono-Regular,Menlo,Monaco,Consolas,\"Liberation Mono\",\"Courier New\",monospace;\n    --shadow-xs:0 1px 2px rgba(11,27,43,.06);\n    --shadow-sm:0 1px 3px rgba(11,27,43,.08),0 1px 2px rgba(11,27,43,.04);\n    --shadow-md:0 6px 16px rgba(11,27,43,.08);\n    --shadow-lg:0 14px 32px rgba(11,27,43,.10);\n    --shadow-brand:0 12px 28px rgba(227,50,3,.26);\n    --ease:cubic-bezier(.2,0,0,1);\n  }  \n.stats { display:grid; grid-template-columns:repeat(4,1fr); gap:16px; margin:56px auto; max-width:1000px; }\n  .stat { background:var(--white); border:1px solid var(--ink-200); border-radius:16px; padding:22px 24px; box-shadow:var(--shadow-sm); }\n  .stat b { display:block; font-size:26px; font-weight:700; color:var(--red-500); line-height:1.1; letter-spacing:-.01em; }\n  .stat span { display:block; font-size:13.5px; color:var(--ink-600); margin-top:7px; line-height:1.45; }\n  @media (max-width:960px) {\n    .stats { grid-template-columns:repeat(2,1fr); }\n    .arch { grid-template-columns:1fr; }\n    .arch-arrow { justify-content:center; transform:rotate(90deg); padding:2px 0; }\n    .flow { grid-template-columns:1fr; }\n    .flow-arrow { justify-content:center; transform:rotate(90deg); }\n    .lessons { grid-template-columns:1fr; }\n    .cta-box { flex-direction:column; align-items:flex-start; padding:36px 28px; }\n  }\n  @media (max-width:560px) {\n    .stats { grid-template-columns:1fr 1fr; gap:10px; }\n    .stat b { font-size:23px; }\n    .head-tag { display:none; }\n    .console-head .big { font-size:15px; }\n  }\n<\/style>\n\n<div class=\"stats\" role=\"group\" aria-label=\"Kluczowe liczby z obu incydent\u00f3w\">\n    <div class=\"stat\"><b>501 000 \u20ac<\/b><span>koszt trzech dni majowego incydentu<\/span><\/div>\n    <div class=\"stat\"><b>3 min 16 s<\/b><span>tyle w lipcu trwa\u0142o automatyczne przej\u0119cie projekt\u00f3w: 28 kluczy API, 9 kont serwisowych<\/span><\/div>\n    <div class=\"stat\"><b>\u00d7906<\/b><span>wzrost kosztu dziennego wykryty przez detektor (z-score 842)<\/span><\/div>\n    <div class=\"stat\"><b>04:30<\/b><span>o tej porze w nocy pierwsza karta CRITICAL lipcowego incydentu trafi\u0142a do zespo\u0142u<\/span><\/div>\n  <\/div>\n\n\n\n<style data-wp-block-html=\"css\">\n:root {\n    --red-50:#FDEBE6; --red-100:#FBD2C6; --red-400:#EA5528; --red-500:#E33203; --red-600:#C22B03; --red-700:#9B2202;\n    --blue-50:#E6F2FF; --blue-100:#CCE5FF; --blue-300:#66AFFF; --blue-500:#007BFF; --blue-600:#0069D9; --blue-700:#0056B3;\n    --sky-100:#DEF3FE; --sky-500:#17A2B8;\n    --ink-900:#212529; --ink-800:#343A40; --ink-700:#495057; --ink-600:#6C757D; --ink-500:#868E96;\n    --ink-300:#CED4DA; --ink-200:#DEE2E6; --ink-100:#E9ECEF; --ink-50:#F8F9FA; --white:#FFFFFF;\n    --green-500:#28A745; --amber-500:#FFC107; --danger-500:#DC3545;\n    --font-sans:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,\"Helvetica Neue\",Arial,\"Noto Sans\",sans-serif;\n    --font-mono:SFMono-Regular,Menlo,Monaco,Consolas,\"Liberation Mono\",\"Courier New\",monospace;\n    --shadow-xs:0 1px 2px rgba(11,27,43,.06);\n    --shadow-sm:0 1px 3px rgba(11,27,43,.08),0 1px 2px rgba(11,27,43,.04);\n    --shadow-md:0 6px 16px rgba(11,27,43,.08);\n    --shadow-lg:0 14px 32px rgba(11,27,43,.10);\n    --shadow-brand:0 12px 28px rgba(227,50,3,.26);\n    --ease:cubic-bezier(.2,0,0,1);\n  }\n .band { background:var(--ink-900); color:#fff; padding:40px 15px; position:relative; overflow:hidden; margin:80px 0; }\n  .band::before { content:\"\"; position:absolute; inset:0; background-image:linear-gradient(rgba(255,255,255,.04) 1px,transparent 1px),linear-gradient(90deg,rgba(255,255,255,.04) 1px,transparent 1px); background-size:52px 52px; -webkit-mask-image:radial-gradient(at 30% 0%,#000,transparent 78%); mask-image:radial-gradient(at 30% 0%,#000,transparent 78%); pointer-events:none; }\n  .band .wrap { position:relative; }\n  .band h2 { color:#fff; font-size:clamp(26px,3vw,36px); margin:14px 0 16px; }\n  .band .prose > p { color:rgba(255,255,255,.72); }\n  .band strong { color:#fff; }\n  .flow { display:grid; grid-template-columns:1fr auto 1fr auto 1fr; gap:12px; align-items:stretch; margin:36px 0 14px; }\n  .phase { border-radius:14px; padding:18px; background:rgba(255,255,255,.05); border:1px solid rgba(255,255,255,.14); }\n  .phase .tag { display:inline-block; font-size:11px; font-weight:700; letter-spacing:.07em; text-transform:uppercase; padding:4px 11px; border-radius:999px; margin-bottom:11px; }\n  .phase p { font-size:13.5px; color:rgba(255,255,255,.72); line-height:1.5; }\n  .phase p b { color:#fff; font-weight:500; }\n  .phase--alert .tag { background:var(--red-400); color:#fff; }\n  .phase--hold .tag { background:var(--amber-500); color:#3A2F00; }\n  .phase--close .tag { background:#5DD879; color:#062D11; }\n  .flow-arrow { display:flex; align-items:center; color:var(--blue-300); }\n  .flow-arrow svg { width:20px; height:20px; }\n  .flow-branch { margin-top:6px; border:1px dashed rgba(102,175,255,.5); border-radius:12px; padding:14px 18px; font-size:13.5px; color:rgba(255,255,255,.82); }\n  .flow-branch b { color:var(--blue-300); }\n<\/style>\n\n<section class=\"band\">\n  <div class=\"wrap\">\n    <div class=\"prose\">\n      <h2>Alert, kt\u00f3ry sam siebie gasi\u0142<\/h2>\n      <p>Drugiego dnia lipcowego incydentu konto wci\u0105\u017c pali\u0142o <strong>14,2 tys. dolar\u00f3w na dob\u0119<\/strong>. A&nbsp;nasz z-score dla tego dnia wyni\u00f3s\u0142\u2026 <strong>1,1<\/strong>. Statystycznie? Nic ciekawego, prosz\u0119 si\u0119 rozej\u015b\u0107.<\/p>\n      <p>Pow\u00f3d: okno krocz\u0105ce zd\u0105\u017cy\u0142o <strong>wch\u0142on\u0105\u0107 dzie\u0144 szczytu do w\u0142asnego baseline&#8217;u<\/strong>. \u015arednia krocz\u0105ca skoczy\u0142a z&nbsp;20 dolar\u00f3w do tysi\u0119cy, odchylenie standardowe eksplodowa\u0142o i&nbsp;trwaj\u0105cy incydent zacz\u0105\u0142 wygl\u0105da\u0107 na now\u0105 norm\u0119. Alert krytyczny degradowa\u0142 si\u0119 do ostrze\u017cenia, cho\u0107 wydatki nie spad\u0142y ani o&nbsp;cent.<\/p>\n      <p>Pierwsza poprawka by\u0142a statystyczna: wyklucza\u0107 dni stanowi\u0105ce anomalie z&nbsp;baseline&#8217;u, licz\u0105c go w&nbsp;dw\u00f3ch przebiegach. Nie zadzia\u0142a\u0142a. Drugi dzie\u0144 incydentu mia\u0142 ju\u017c zani\u017cony z-score, wi\u0119c nie \u0142apa\u0142 si\u0119 na flag\u0119 outliera i&nbsp;od trzeciego dnia znowu zatruwa\u0142 okno. Dopiero po revercie zrozumieli\u015bmy, \u017ce walczymy nie z&nbsp;tym przeciwnikiem: <strong>to nie jest problem statystyki, tylko cyklu \u017cycia alertu<\/strong>. Alert to stan, nie pojedyncze zdarzenie.<\/p>\n    <\/div>\n\n    <div class=\"flow\" role=\"img\" aria-label=\"Cykl \u017cycia epizodu alertu: alert, hold przez kolejne dni, autoclose po okresie ciszy\">\n      <div class=\"phase phase--alert\">\n        <span class=\"tag\">Alert \u00b7 dzie\u0144 0<\/span>\n        <p><b>Epizod si\u0119 zapala.<\/b> Karta w Chat, zadanie w CRM, przy krytycznych tak\u017ce PagerDuty. Zapis do historii detekcji.<\/p>\n      <\/div>\n      <div class=\"flow-arrow\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><path d=\"M5 12h14M13 6l6 6-6 6\"><\/path><\/svg><\/div>\n      <div class=\"phase phase--hold\">\n        <span class=\"tag\">Hold \u00b7 kolejne dni<\/span>\n        <p><b>Severity jest pod\u0142og\u0105.<\/b> Zatruty z-score nie mo\u017ce zdegradowa\u0107 alertu ani odpali\u0107 duplikatu. Cisza, chyba \u017ce incydent realnie eskaluje.<\/p>\n      <\/div>\n      <div class=\"flow-arrow\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><path d=\"M5 12h14M13 6l6 6-6 6\"><\/path><\/svg><\/div>\n      <div class=\"phase phase--close\">\n        <span class=\"tag\">Autoclose \u00b7 po ciszy<\/span>\n        <p><b>Epizod ga\u015bnie.<\/b> Po kilku dniach bez nowego alertu kafelek znika z dashboardu. Ponowna eskalacja otwiera nowy epizod z nowym zegarem.<\/p>\n      <\/div>\n    <\/div>\n    <div class=\"prose\">\n      <div class=\"flow-branch\"><b>W trakcie holdu obowi\u0105zuje regu\u0142a re-alertu:<\/b> nowa karta pojawia si\u0119 tylko wtedy, gdy severity ro\u015bnie albo gdy anomalia wyra\u017anie si\u0119 pog\u0142\u0119bia wzgl\u0119dem ostatniego alertu (z-score lub kwota). Pr\u00f3g wzrostu wyznaczyli\u015bmy na podstawie danych, nie intuicji: eksport billingowy \u201edope\u0142nia&#8221; dzie\u0144 jeszcze przez wiele godzin po alercie, wi\u0119c zbyt czu\u0142y pr\u00f3g produkowa\u0142 rano duplikaty. Mi\u0142ym efektem ubocznym jest to, \u017ce rosn\u0105cy incydent dostaje naturalny, geometryczny backoff.<\/div>\n    <\/div>\n  <\/div>\n<\/section>\n\n\n\n<h2 id=\"h-nie-kazda-anomalia-to-atak\" class=\"wp-block-heading\">Nie ka\u017cda anomalia to atak<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Pami\u0119tajmy jednak, \u017ce system nie \u017cywi si\u0119 wy\u0142\u0105cznie incydentami bezpiecze\u0144stwa. Kt\u00f3rego\u015b dnia dzienny koszt jednego z&nbsp;monitorowanych przez nas kont wzr\u00f3s\u0142 7,6-krotnie ponad norm\u0119. Alert wyszed\u0142, zadanie w&nbsp;CRM trafi\u0142o do opiekuna, a&nbsp;ten w&nbsp;ci\u0105gu kilkunastu minut potwierdzi\u0142 z&nbsp;klientem, \u017ce to planowany trening modeli na Vertex AI. Epizod wyciszono jednym klikni\u0119ciem do ko\u0144ca dnia, zachowuj\u0105c eskalacj\u0119 krytycznych. Klient dosta\u0142 pytanie \u201eCzy to Wasze?&#8221; w&nbsp;godzin\u0119 po fakcie, zamiast faktury z&nbsp;niespodziank\u0105 po miesi\u0105cu. System, kt\u00f3ry nie umie szybko odr\u00f3\u017cni\u0107 oczekiwanego od z\u0142o\u015bliwego, uczy ludzi ignorowania alert\u00f3w, a&nbsp;to prosta droga do powt\u00f3rki z&nbsp;maja.<\/p>\n\n\n\n<h2 id=\"h-detale-ktore-bola-dopiero-w-produkcji\" class=\"wp-block-heading\">Detale, kt\u00f3re bol\u0105 dopiero w produkcji<\/h2>\n\n\n\n<style data-wp-block-html=\"css\">\n  :root {\n    --red-50:#FDEBE6; --red-100:#FBD2C6; --red-400:#EA5528; --red-500:#E33203; --red-600:#C22B03; --red-700:#9B2202;\n    --blue-50:#E6F2FF; --blue-100:#CCE5FF; --blue-300:#66AFFF; --blue-500:#007BFF; --blue-600:#0069D9; --blue-700:#0056B3;\n    --sky-100:#DEF3FE; --sky-500:#17A2B8;\n    --ink-900:#212529; --ink-800:#343A40; --ink-700:#495057; --ink-600:#6C757D; --ink-500:#868E96;\n    --ink-300:#CED4DA; --ink-200:#DEE2E6; --ink-100:#E9ECEF; --ink-50:#F8F9FA; --white:#FFFFFF;\n    --green-500:#28A745; --amber-500:#FFC107; --danger-500:#DC3545;\n    --font-sans:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,\"Helvetica Neue\",Arial,\"Noto Sans\",sans-serif;\n    --font-mono:SFMono-Regular,Menlo,Monaco,Consolas,\"Liberation Mono\",\"Courier New\",monospace;\n    --shadow-xs:0 1px 2px rgba(11,27,43,.06);\n    --shadow-sm:0 1px 3px rgba(11,27,43,.08),0 1px 2px rgba(11,27,43,.04);\n    --shadow-md:0 6px 16px rgba(11,27,43,.08);\n    --shadow-lg:0 14px 32px rgba(11,27,43,.10);\n    --shadow-brand:0 12px 28px rgba(227,50,3,.26);\n    --ease:cubic-bezier(.2,0,0,1);\n  }\n  @media (max-width:960px) {\n    .stats { grid-template-columns:repeat(2,1fr); }\n    .arch { grid-template-columns:1fr; }\n    .arch-arrow { justify-content:center; transform:rotate(90deg); padding:2px 0; }\n    .flow { grid-template-columns:1fr; }\n    .flow-arrow { justify-content:center; transform:rotate(90deg); }\n    .lessons { grid-template-columns:1fr; }\n    .cta-box { flex-direction:column; align-items:flex-start; padding:36px 28px; }\n  }\n  @media (max-width:560px) {\n    .stats { grid-template-columns:1fr 1fr; gap:10px; }\n    .stat b { font-size:23px; }\n    .head-tag { display:none; }\n    .console-head .big { font-size:15px; }\n  } \n .lessons { display:grid; grid-template-columns:repeat(2,1fr); gap:16px; margin:30px 0 10px; }\n  .lesson { background:var(--white); border:1px solid var(--ink-200); border-radius:16px; padding:22px; box-shadow:var(--shadow-sm); transition:transform .2s var(--ease), box-shadow .2s var(--ease); }\n  .lesson:hover { transform:translateY(-3px); box-shadow:var(--shadow-md); }\n  .lesson h4 { font-size:16px; margin-bottom:8px; }\n  .lesson p { font-size:14px; color:var(--ink-600); line-height:1.55; }\n<\/style>\n\n<div class=\"lessons\">\n      <div class=\"lesson\"><h4>Liczby bez kontekstu k\u0142ami\u0105<\/h4><p>Ta sama kwota w jednym \u015brodowisku to szum, w innym incydent miesi\u0105ca. Pr\u00f3g por\u00f3wnywany z surow\u0105 liczb\u0105 oznacza w praktyce \u201e1000 czegokolwiek&#8221;. Dlatego ka\u017cd\u0105 warto\u015b\u0107 osadzamy w kontek\u015bcie konkretnego konta, jego skali i historii, a ludziom pokazujemy kwoty w postaci, w jakiej znaj\u0105 je z faktur.<\/p><\/div>\n      <div class=\"lesson\"><h4>Dane psuj\u0105 si\u0119 po cichu<\/h4><p>Dane billingowe z r\u00f3\u017cnych \u017ar\u00f3de\u0142 i okres\u00f3w potrafi\u0105 r\u00f3\u017cni\u0107 si\u0119 uk\u0142adem i typami. Kod, kt\u00f3ry zak\u0142ada, \u017ce wsz\u0119dzie jest tak samo, nie wywali si\u0119 z \u017cadnym b\u0142\u0119dem, tylko po cichu przestawi liczby. W systemie, kt\u00f3remu ludzie maj\u0105 ufa\u0107, to najgorszy rodzaj b\u0142\u0119du, wi\u0119c \u017cadne za\u0142o\u017cenie o danych nie jest u nas domy\u015blne.<\/p><\/div>\n      <div class=\"lesson\"><h4>Zm\u0119czenie alertami to d\u0142ug<\/h4><p>Ka\u017cdy zb\u0119dny alert obni\u017ca zaufanie do wszystkich nast\u0119pnych. Dlatego system ma ca\u0142y zestaw bezpiecznik\u00f3w przeciwko spamowi: deduplikacj\u0119, cooldowny, wyciszanie i automatyczne wygaszanie epizod\u00f3w. Projektujemy pod sygna\u0142, nie pod wolumen.<\/p><\/div>\n      <div class=\"lesson\"><h4>Billing sp\u0142ywa z op\u00f3\u017anieniem<\/h4><p>Eksport potrafi \u201edope\u0142nia\u0107&#8221; dzie\u0144 jeszcze wiele godzin. Lipcowa karta z 4:30 pokazywa\u0142a kwot\u0119 kilkukrotnie ni\u017csz\u0105 ni\u017c finalne zamkni\u0119cie tej doby, a licznik r\u00f3s\u0142 d\u0142ugo po zablokowaniu ruchu. System musi o tym wiedzie\u0107, bo inaczej bra\u0142by sp\u0142ywaj\u0105ce z op\u00f3\u017anieniem dane za eskalacj\u0119 i budzi\u0142 ludzi bez powodu.<\/p><\/div>\n    <\/div>\n\n\n\n<h2 id=\"h-jeden-silnik-dwa-tryby-pracy\" class=\"wp-block-heading\">Jeden silnik, dwa tryby pracy<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Ta sama architektura dzia\u0142a dzi\u015b w&nbsp;dw\u00f3ch trybach.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Pierwszy to us\u0142uga zarz\u0105dzana.<\/strong>&nbsp;\u015arodowisko klienta obejmujemy naszym monitoringiem, alerty trafiaj\u0105 r\u00f3wnolegle do naszego zespo\u0142u i&nbsp;do klienta, anomali\u0119 klasyfikuje i&nbsp;prowadzi konkretny opiekun, a&nbsp;tam, gdzie mamy nadane uprawnienia, reagujemy bezpo\u015brednio zamiast tylko eskalowa\u0107. Lipcowa historia pokazuje, gdzie przebiega ta granica: bez uprawnie\u0144 nawet najlepsza detekcja ko\u0144czy si\u0119 na telefonie do w\u0142a\u015bciciela \u015brodowiska.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Drugi tryb wynika wprost z&nbsp;konstrukcji.<\/strong>&nbsp;Skoro silnik jest bezstanowy i&nbsp;opiera si\u0119 wy\u0142\u0105cznie na standardowym eksporcie billingu do BigQuery, da si\u0119 go postawi\u0107 w&nbsp;dowolnym \u015brodowisku Google Cloud, tak\u017ce takim, kt\u00f3rego na co dzie\u0144 nie obs\u0142ugujemy. W&nbsp;takim wdro\u017ceniu zakres alert\u00f3w zaw\u0119\u017camy z&nbsp;ca\u0142ego konta billingowego do widoku projekt\u00f3w. Ka\u017cdy zesp\u00f3\u0142 widzi anomalie w&nbsp;swoich projektach, z&nbsp;w\u0142asnymi progami i&nbsp;kana\u0142ami powiadomie\u0144. Chat, e-mail czy webhook s\u0105 z&nbsp;perspektywy systemu wymiennymi adapterami. Ca\u0142a reszta, od okien SQL po cykl \u017cycia epizodu, dzia\u0142a w&nbsp;obu trybach identycznie.<\/p>\n\n\n\n<h2 id=\"h-co-z-tego-wynika-dla-twojego-srodowiska\" class=\"wp-block-heading\">Co z tego wynika dla Twojego \u015brodowiska<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Niezale\u017cnie od tego, czy zbudujesz co\u015b takiego samodzielnie, czy nie, kilka rzeczy przenosi si\u0119 wprost. Baseline musi by\u0107 behawioralny i&nbsp;liczony per konto, bo wsp\u00f3lny pr\u00f3g dla wszystkich \u015brodowisk to fikcja. Alert powinien mie\u0107 cykl \u017cycia, bo bez holdu, deduplikacji i&nbsp;autoclose system w&nbsp;kilka tygodni po\u017cre w\u0142asn\u0105 wiarygodno\u015b\u0107. Statystyka tylko wykrywa, decyzj\u0119 podejmuje kontekst, czyli mapowanie anomalii na klienta, opiekuna i&nbsp;konkretne us\u0142ugi. No i&nbsp;sprawa najprostsza z&nbsp;najtrudniejszych: koszt to telemetria bezpiecze\u0144stwa. Finanse patrz\u0105 na niego raz w&nbsp;miesi\u0105cu, monitoring musi dzia\u0142a\u0107 non stop.<\/p>\n\n\n\n<style data-wp-block-html=\"css\">\n  :root {\n    --red-50:#FDEBE6; --red-100:#FBD2C6; --red-400:#EA5528; --red-500:#E33203; --red-600:#C22B03; --red-700:#9B2202;\n    --blue-50:#E6F2FF; --blue-100:#CCE5FF; --blue-300:#66AFFF; --blue-500:#007BFF; --blue-600:#0069D9; --blue-700:#0056B3;\n    --sky-100:#DEF3FE; --sky-500:#17A2B8;\n    --ink-900:#212529; --ink-800:#343A40; --ink-700:#495057; --ink-600:#6C757D; --ink-500:#868E96;\n    --ink-300:#CED4DA; --ink-200:#DEE2E6; --ink-100:#E9ECEF; --ink-50:#F8F9FA; --white:#FFFFFF;\n    --green-500:#28A745; --amber-500:#FFC107; --danger-500:#DC3545;\n    --font-sans:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,\"Helvetica Neue\",Arial,\"Noto Sans\",sans-serif;\n    --font-mono:SFMono-Regular,Menlo,Monaco,Consolas,\"Liberation Mono\",\"Courier New\",monospace;\n    --shadow-xs:0 1px 2px rgba(11,27,43,.06);\n    --shadow-sm:0 1px 3px rgba(11,27,43,.08),0 1px 2px rgba(11,27,43,.04);\n    --shadow-md:0 6px 16px rgba(11,27,43,.08);\n    --shadow-lg:0 14px 32px rgba(11,27,43,.10);\n    --shadow-brand:0 12px 28px rgba(227,50,3,.26);\n    --ease:cubic-bezier(.2,0,0,1);\n  } \n  .callout { border-left:4px solid var(--red-500); background:var(--red-50); border-radius:0 12px 12px 0; padding:18px 22px; margin:28px 0; font-size:16px; color:var(--ink-800); }\n  .callout--blue { border-left-color:var(--blue-500); background:var(--blue-50); }\n @media (max-width:960px) {\n    .stats { grid-template-columns:repeat(2,1fr); }\n    .arch { grid-template-columns:1fr; }\n    .arch-arrow { justify-content:center; transform:rotate(90deg); padding:2px 0; }\n    .flow { grid-template-columns:1fr; }\n    .flow-arrow { justify-content:center; transform:rotate(90deg); }\n    .lessons { grid-template-columns:1fr; }\n    .cta-box { flex-direction:column; align-items:flex-start; padding:36px 28px; }\n  }\n  @media (max-width:560px) {\n    .stats { grid-template-columns:1fr 1fr; gap:10px; }\n    .stat b { font-size:23px; }\n    .head-tag { display:none; }\n    .console-head .big { font-size:15px; }\n  }\n<\/style>\n\n<div class=\"callout callout--blue\">Anomalia kosztowa wykryta w pierwszym cyklu to pytanie do klienta. Wykryta na fakturze to sp\u00f3r z dostawc\u0105 chmury i utracona mar\u017ca.<\/div>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Wiosn\u0105 2026 dwie firmy pad\u0142y ofiar\u0105 tej samej klasy ataku: skradzione po\u015bwiadczenia, cudze klucze API i masowa konsumpcja Gemini na ich koszt. Pierwszy incydent sko\u0144czy\u0142 si\u0119 rachunkiem na p\u00f3\u0142 miliona euro. Drugi, dwa miesi\u0105ce p\u00f3\u017aniej, trafi\u0142 na \u015brodowisko obj\u0119te ju\u017c naszym monitoringiem i zosta\u0142 zatrzymany w kilka godzin. Ten tekst opowiada o tym, co zbudowali\u015bmy&#8230;<\/p>\n","protected":false},"author":22,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_editorskit_title_hidden":false,"_editorskit_reading_time":0,"_editorskit_is_block_options_detached":false,"_editorskit_block_options_position":"{}","footnotes":""},"categories":[560],"tags":[],"class_list":["post-173640","post","type-post","status-publish","format-standard","hentry","category-google-cloud-platform-pl"],"_links":{"self":[{"href":"https:\/\/fotc.com\/pl\/wp-json\/wp\/v2\/posts\/173640","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/fotc.com\/pl\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/fotc.com\/pl\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/fotc.com\/pl\/wp-json\/wp\/v2\/users\/22"}],"replies":[{"embeddable":true,"href":"https:\/\/fotc.com\/pl\/wp-json\/wp\/v2\/comments?post=173640"}],"version-history":[{"count":0,"href":"https:\/\/fotc.com\/pl\/wp-json\/wp\/v2\/posts\/173640\/revisions"}],"wp:attachment":[{"href":"https:\/\/fotc.com\/pl\/wp-json\/wp\/v2\/media?parent=173640"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/fotc.com\/pl\/wp-json\/wp\/v2\/categories?post=173640"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/fotc.com\/pl\/wp-json\/wp\/v2\/tags?post=173640"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}