[{"data":1,"prerenderedAt":1929},["ShallowReactive",2],{"article-\u002Finsights\u002Fkingdom-of-complia":3,"related-\u002Finsights\u002Fkingdom-of-complia":514},{"_path":4,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":8,"description":9,"\u003Cp style=\"text-align:center;\"> ⚜ ⚜ ⚜ \u003Cp> title":8,"date":10,"author":11,"image":19,"category":20,"tags":22,"body":27,"_type":507,"_id":508,"_source":509,"_file":510,"_stem":511,"_extension":512,"sitemap":513},"\u002Finsights\u002Fkingdom-of-complia","insights",false,"","The Fall of Castle Complia","The Kingdom of Complia defended its castle with the finest compliance framework the medieval world had ever produced. The enemy walked around the back.","2026-03-22",{"name":12,"headshot":13,"role":14,"contact":15},"Levente Simon","\u002Fheadshots\u002FLS.jpeg","creator of dethernety",{"linkedin":16,"email":17,"twitter":18},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Flevente-simon\u002F","levente.simon@dether.net","https:\u002F\u002Fx.com\u002FLevente_Simon","\u002Fimages\u002Fcastle-complia.jpg",[21],"Strategy and Risk Economics",[23,24,25,26],"compliance","security architecture","risk management","satire",{"type":28,"children":29,"toc":504},"root",[30,38,48,52,58,69,74,79,84,89,94,99,104,109,114,119,124,129,134,139,144,149,154,159,164,169,174,179,184,188,193,196,200,209,214,219,224,229,234,239,244,249,254,259,264,269,272,276,286,291,296,301,306,311,316,320,325,328,332,342,347,352,357,362,367,372,377,382,387,392,397,402,407,412,417,422,427,432,437,442,445,449,459,464,468,473,476,480,488,496],{"type":31,"tag":32,"props":33,"children":35},"element","h1",{"id":34},"the-fall-of-castle-complia",[36],{"type":37,"value":8},"text",{"type":31,"tag":39,"props":40,"children":41},"p",{},[42],{"type":31,"tag":43,"props":44,"children":45},"em",{},[46],{"type":37,"value":47},"Proceedings of the Royal Inquiry into the Loss of Castle Complia. Conducted before Lord Chancellor Harren at the King's Court, third day of Advent, in the year of our Lord 1347. Recorded by the Office of the Crown Scribe.",{"type":31,"tag":49,"props":50,"children":51},"hr",{},[],{"type":31,"tag":39,"props":53,"children":55},{"style":54},"text-align:center;",[56],{"type":37,"value":57},"\n⚜ ⚜ ⚜ \n",{"type":31,"tag":39,"props":59,"children":60},{},[61,67],{"type":31,"tag":62,"props":63,"children":64},"strong",{},[65],{"type":37,"value":66},"Lord Aldric of Complia",{"type":37,"value":68},", called to give account:",{"type":31,"tag":39,"props":70,"children":71},{},[72],{"type":37,"value":73},"\"My Lord Chancellor, I wish to state at the outset that Castle Complia was, at the time of the siege, in full compliance with the Royal Fortress Standard, ISO 27001:1347. I have brought the certification, issued not four months prior by the Monastery of External Assurance. If it would please the court, I will read the findings into the record.\"",{"type":31,"tag":39,"props":75,"children":76},{},[77],{"type":37,"value":78},"\"Proceed.\"",{"type":31,"tag":39,"props":80,"children":81},{},[82],{"type":37,"value":83},"\"The moat: present and documented. Width of fourteen cubits, exceeding the minimum requirement of twelve. The drawbridge: operational, governed by a written policy reviewed and signed within the last twelve months. The portcullis: inspected last Michaelmas by Sir Cedric of the Monastery of External Assurance, who found it consistent with requirements and noted no material findings. The garrison: twenty-four archers, each having completed the annual Arrow Awareness parchment and signed acknowledgment of the Acceptable Use of Arrows Policy. The boiling oil procedures: documented in the Incident Response Cauldron Policy, version 3.2, forty-seven pages, approved by the Castle Compliance Committee.\"",{"type":31,"tag":39,"props":85,"children":86},{},[87],{"type":37,"value":88},"He paused.",{"type":31,"tag":39,"props":90,"children":91},{},[92],{"type":37,"value":93},"\"Every item on the Royal Fortress Standard was satisfied. I have the certificate here.\"",{"type":31,"tag":39,"props":95,"children":96},{},[97],{"type":37,"value":98},"\"Lord Aldric. The north wall collapsed under conventional siege equipment. A battering party of forty men breached it in under an hour. How do you account for this?\"",{"type":31,"tag":39,"props":100,"children":101},{},[102],{"type":37,"value":103},"\"The north wall was not within my jurisdiction, my Lord Chancellor. It belongs to Earl Roderick, whose estate borders the castle grounds to the north. This was a documented decision, agreed upon during the original scoping of the Royal Fortress Standard. The Earl's office was consulted. Including his fortifications would have required a cross-jurisdictional agreement, which would have required the Earl to acknowledge deficiencies in his own constructions, which, given his position on the King's Council, was judged to be... diplomatically inadvisable. The Council of Peril formally accepted this exclusion. It is recorded in the ledger. Entry twenty-three.\"",{"type":31,"tag":39,"props":105,"children":106},{},[107],{"type":37,"value":108},"\"Was the Earl's wall also breached?\"",{"type":31,"tag":39,"props":110,"children":111},{},[112],{"type":37,"value":113},"A pause.",{"type":31,"tag":39,"props":115,"children":116},{},[117],{"type":37,"value":118},"\"The Earl's section held. Yes. But I would emphasize that the north wall was outside my—\"",{"type":31,"tag":39,"props":120,"children":121},{},[122],{"type":37,"value":123},"\"You commissioned a penetration test. It found no critical vulnerabilities.\"",{"type":31,"tag":39,"props":125,"children":126},{},[127],{"type":37,"value":128},"\"Correct. Sir Geoffrey of the House of Redfort, a certified practitioner of the Guild of Fortification Assurance, conducted a full assessment not four months prior. Sixty-three pages. No critical findings.\"",{"type":31,"tag":39,"props":130,"children":131},{},[132],{"type":37,"value":133},"\"Sir Geoffrey will speak for himself. I understand that Captain Wren, your garrison commander, submitted findings on seventeen separate deficiencies within your jurisdiction. A rusted drawbridge chain. Blocked arrow slits. A drainage tunnel beneath the east tower secured by a rotted wooden grate. Why were these not remediated?\"",{"type":31,"tag":39,"props":135,"children":136},{},[137],{"type":37,"value":138},"\"Each was classified under the Vulnerability Management Scroll with a remediation timeline assigned by severity.\"",{"type":31,"tag":39,"props":140,"children":141},{},[142],{"type":37,"value":143},"\"The drainage tunnel — wide enough for a man to pass through. How was that classified?\"",{"type":31,"tag":39,"props":145,"children":146},{},[147],{"type":37,"value":148},"\"Medium. Internal infrastructure. Technically inside the outer perimeter, not directly facing an approaching enemy. The remediation window was twelve months. We were within that window when the siege commenced.\"",{"type":31,"tag":39,"props":150,"children":151},{},[152],{"type":37,"value":153},"\"And the drawbridge chain?\"",{"type":31,"tag":39,"props":155,"children":156},{},[157],{"type":37,"value":158},"\"High severity. One hundred and eighty days.\"",{"type":31,"tag":39,"props":160,"children":161},{},[162],{"type":37,"value":163},"\"Lord Aldric, an army was approaching.\"",{"type":31,"tag":39,"props":165,"children":166},{},[167],{"type":37,"value":168},"\"Remediation timelines are not adjusted for circumstance, my Lord Chancellor. To deviate from the established schedule would have constituted a non-conformity. It could have jeopardized our certification.\"",{"type":31,"tag":39,"props":170,"children":171},{},[172],{"type":37,"value":173},"\"The budget for these repairs?\"",{"type":31,"tag":39,"props":175,"children":176},{},[177],{"type":37,"value":178},"\"The spring allocation had been committed. The diplomatic reception for the Duke of Westmark — renovation of the great hall, new heraldic standards, provisioning for the feast. These were obligations of court, not discretionary. The remaining funds covered the annual tithe to the Monastery of External Assurance.\"",{"type":31,"tag":39,"props":180,"children":181},{},[182],{"type":37,"value":183},"\"You could not afford to fix the walls because you were paying to certify them.\"",{"type":31,"tag":39,"props":185,"children":186},{},[187],{"type":37,"value":113},{"type":31,"tag":39,"props":189,"children":190},{},[191],{"type":37,"value":192},"\"Emergency spending required treasury approval. The treasury met monthly. The next meeting was five weeks after the siege began.\"",{"type":31,"tag":49,"props":194,"children":195},{},[],{"type":31,"tag":39,"props":197,"children":198},{"style":54},[199],{"type":37,"value":57},{"type":31,"tag":39,"props":201,"children":202},{},[203,208],{"type":31,"tag":62,"props":204,"children":205},{},[206],{"type":37,"value":207},"Sir Geoffrey of Redfort",{"type":37,"value":68},{"type":31,"tag":39,"props":210,"children":211},{},[212],{"type":37,"value":213},"\"I tested what I was contracted to test. The main gate held. The south wall held. The eastern tower held. These findings are documented and accurate. No representation was made, nor could professionally be made, regarding untested areas. I would draw the Chancellor's attention to page forty-seven of the contract, section nine, subsection C, which clearly delimits the scope of assurance provided.\"",{"type":31,"tag":39,"props":215,"children":216},{},[217],{"type":37,"value":218},"\"The scope excluded the north wall, the west wall, and the kitchens.\"",{"type":31,"tag":39,"props":220,"children":221},{},[222],{"type":37,"value":223},"\"The north wall was jurisdictionally excluded. The west wall was under renovation — testing during active construction would not have yielded representative results. The kitchens were excluded at the request of the castle household.\"",{"type":31,"tag":39,"props":225,"children":226},{},[227],{"type":37,"value":228},"\"At the request of the cook, I am told.\"",{"type":31,"tag":39,"props":230,"children":231},{},[232],{"type":37,"value":233},"\"At the request of the castle household.\"",{"type":31,"tag":39,"props":235,"children":236},{},[237],{"type":37,"value":238},"\"Did you observe the north wall during your visit?\"",{"type":31,"tag":39,"props":240,"children":241},{},[242],{"type":37,"value":243},"\"I rode past it.\"",{"type":31,"tag":39,"props":245,"children":246},{},[247],{"type":37,"value":248},"\"And?\"",{"type":31,"tag":39,"props":250,"children":251},{},[252],{"type":37,"value":253},"A longer pause.",{"type":31,"tag":39,"props":255,"children":256},{},[257],{"type":37,"value":258},"\"I noted, internally, that it had the appearance of a wall that had not been recently maintained. This observation was not included in the report as it fell outside the agreed engagement boundary. To include out-of-scope observations would have been a deviation from the contracted deliverable and could have exposed my firm to liability for findings I had not been formally retained to assess.\"",{"type":31,"tag":39,"props":260,"children":261},{},[262],{"type":37,"value":263},"\"You saw a failing wall and said nothing.\"",{"type":31,"tag":39,"props":265,"children":266},{},[267],{"type":37,"value":268},"\"I saw an out-of-scope wall and maintained professional boundaries. These are not the same thing, my Lord Chancellor.\"",{"type":31,"tag":49,"props":270,"children":271},{},[],{"type":31,"tag":39,"props":273,"children":274},{"style":54},[275],{"type":37,"value":57},{"type":31,"tag":39,"props":277,"children":278},{},[279,284],{"type":31,"tag":62,"props":280,"children":281},{},[282],{"type":37,"value":283},"Brother Matthias",{"type":37,"value":285},", Keeper of the Ledger of Accepted Perils, called to give account:",{"type":31,"tag":39,"props":287,"children":288},{},[289],{"type":37,"value":290},"\"The Ledger of Accepted Perils for Castle Complia contained, at the time of the siege, forty-seven formally assessed entries. Each had been evaluated for likelihood and impact, assigned an owner, and reviewed at quarterly committee meetings. The ledger was current. I can produce it.\"",{"type":31,"tag":39,"props":292,"children":293},{},[294],{"type":37,"value":295},"\"The north wall appears in this ledger?\"",{"type":31,"tag":39,"props":297,"children":298},{},[299],{"type":37,"value":300},"\"Entry twenty-three. 'Structural degradation of north perimeter wall. Owner: Earl Roderick, external party. Status: risk accepted. Review: next annual assessment.' The entry was reviewed and reconfirmed at each of the last three quarterly meetings.\"",{"type":31,"tag":39,"props":302,"children":303},{},[304],{"type":37,"value":305},"\"Brother Matthias, what does 'risk accepted' mean, in practice?\"",{"type":31,"tag":39,"props":307,"children":308},{},[309],{"type":37,"value":310},"\"It means the risk has been formally acknowledged, documented, and the decision to take no further mitigating action has been approved by the appropriate authority.\"",{"type":31,"tag":39,"props":312,"children":313},{},[314],{"type":37,"value":315},"\"And what distinguishes 'risk accepted' from 'risk ignored'?\"",{"type":31,"tag":39,"props":317,"children":318},{},[319],{"type":37,"value":113},{"type":31,"tag":39,"props":321,"children":322},{},[323],{"type":37,"value":324},"\"The documentation, my Lord Chancellor.\"",{"type":31,"tag":49,"props":326,"children":327},{},[],{"type":31,"tag":39,"props":329,"children":330},{"style":54},[331],{"type":37,"value":57},{"type":31,"tag":39,"props":333,"children":334},{},[335,340],{"type":31,"tag":62,"props":336,"children":337},{},[338],{"type":37,"value":339},"Captain Wren",{"type":37,"value":341},", commander of the garrison, called last:",{"type":31,"tag":39,"props":343,"children":344},{},[345],{"type":37,"value":346},"\"Captain. Did you have knowledge of the north wall's condition prior to the siege?\"",{"type":31,"tag":39,"props":348,"children":349},{},[350],{"type":37,"value":351},"\"Yes.\"",{"type":31,"tag":39,"props":353,"children":354},{},[355],{"type":37,"value":356},"\"Did you report this?\"",{"type":31,"tag":39,"props":358,"children":359},{},[360],{"type":37,"value":361},"\"I reported it to the Master of Works in the second month of last year. I reported it again in the fifth month. I reported it to Lord Aldric's steward at the autumn muster. I submitted a written assessment in the winter noting that a determined force could breach the wall in under two hours with basic equipment. I was told it had been received and would be considered during the spring budget allocation.\"",{"type":31,"tag":39,"props":363,"children":364},{},[365],{"type":37,"value":366},"\"Did you escalate beyond Lord Aldric's household?\"",{"type":31,"tag":39,"props":368,"children":369},{},[370],{"type":37,"value":371},"\"I wrote to Earl Roderick's marshal directly. I was informed by Lord Aldric's steward that correspondence regarding the Earl's fortifications must go through the Earl's office, not through the garrison, and that I had overstepped my authority. The letter was not sent.\"",{"type":31,"tag":39,"props":373,"children":374},{},[375],{"type":37,"value":376},"\"And was it considered?\"",{"type":31,"tag":39,"props":378,"children":379},{},[380],{"type":37,"value":381},"\"The spring budget was allocated to the great hall renovation and the tithe to the Monastery of External Assurance. The penetration test had flagged the eastern tower stones as potentially too smooth, so a portion went to roughening them.\"",{"type":31,"tag":39,"props":383,"children":384},{},[385],{"type":37,"value":386},"A silence in the hall.",{"type":31,"tag":39,"props":388,"children":389},{},[390],{"type":37,"value":391},"\"They were too smooth,\" Captain Wren added, quietly. \"That part was true.\"",{"type":31,"tag":39,"props":393,"children":394},{},[395],{"type":37,"value":396},"\"Captain. You held the inner keep for six hours with eleven men after the walls were breached. Why did the Incident Response Cauldron Policy not activate?\"",{"type":31,"tag":39,"props":398,"children":399},{},[400],{"type":37,"value":401},"\"The cauldron had no oil, my Lord Chancellor. The reserves had been used for the feast celebrating the castle's successful recertification. The firewood was damp. The drawbridge chain I had documented as defective seized when the team tried to raise the bridge. The archers on the south wall found their arrow slits blocked by decorative stonework — Lady Aldric had commissioned it for the diplomatic reception. Half the field of fire was gone.\"",{"type":31,"tag":39,"props":403,"children":404},{},[405],{"type":37,"value":406},"\"Was any of this known before the siege?\"",{"type":31,"tag":39,"props":408,"children":409},{},[410],{"type":37,"value":411},"\"All of it. We ran a drill ten days prior. Every one of these failures occurred during the drill. Brother Matthias documented the results. He noted several 'opportunities for improvement' and recommended a follow-up drill in six months.\"",{"type":31,"tag":39,"props":413,"children":414},{},[415],{"type":37,"value":416},"\"The siege arrived in ten days, not six months.\"",{"type":31,"tag":39,"props":418,"children":419},{},[420],{"type":37,"value":421},"\"Yes, my Lord Chancellor.\"",{"type":31,"tag":39,"props":423,"children":424},{},[425],{"type":37,"value":426},"\"Captain. One final question. In your professional judgment — was the castle defended?\"",{"type":31,"tag":39,"props":428,"children":429},{},[430],{"type":37,"value":431},"\"The castle was certified, my Lord Chancellor.\"",{"type":31,"tag":39,"props":433,"children":434},{},[435],{"type":37,"value":436},"\"That is not what I asked.\"",{"type":31,"tag":39,"props":438,"children":439},{},[440],{"type":37,"value":441},"\"I know.\"",{"type":31,"tag":49,"props":443,"children":444},{},[],{"type":31,"tag":39,"props":446,"children":447},{"style":54},[448],{"type":37,"value":57},{"type":31,"tag":39,"props":450,"children":451},{},[452,457],{"type":31,"tag":62,"props":453,"children":454},{},[455],{"type":37,"value":456},"Lord Aldric",{"type":37,"value":458},", given right of final statement:",{"type":31,"tag":39,"props":460,"children":461},{},[462],{"type":37,"value":463},"\"I acted in good faith on the best available certified assessment. No lord can be expected to defend against every conceivable threat. We had a process. We followed the process. If the process had gaps, that is a systemic failure, not a personal one.\"",{"type":31,"tag":39,"props":465,"children":466},{},[467],{"type":37,"value":113},{"type":31,"tag":39,"props":469,"children":470},{},[471],{"type":37,"value":472},"\"I would also note that the garrison held for eleven days, which is, by any historical measure, a respectable defense.\"",{"type":31,"tag":49,"props":474,"children":475},{},[],{"type":31,"tag":39,"props":477,"children":478},{"style":54},[479],{"type":37,"value":57},{"type":31,"tag":39,"props":481,"children":482},{},[483],{"type":31,"tag":43,"props":484,"children":485},{},[486],{"type":37,"value":487},"The Chancellor's ruling noted procedural compliance at all levels and recommended a review of scoping practices for future fortification assessments, clearer escalation pathways for non-commissioned observations, and improved cross-jurisdictional coordination frameworks.",{"type":31,"tag":39,"props":489,"children":490},{},[491],{"type":31,"tag":43,"props":492,"children":493},{},[494],{"type":37,"value":495},"The north wall was not mentioned in the remediation plan.",{"type":31,"tag":39,"props":497,"children":498},{},[499],{"type":31,"tag":43,"props":500,"children":501},{},[502],{"type":37,"value":503},"There was no north wall anymore.",{"title":7,"searchDepth":505,"depth":505,"links":506},4,[],"markdown","content:insights:kingdom-of-complia.md","content","insights\u002Fkingdom-of-complia.md","insights\u002Fkingdom-of-complia","md",{"loc":4},[515,1052],{"_path":516,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":517,"description":518,"date":519,"author":520,"image":522,"category":523,"tags":525,"body":532,"_type":507,"_id":1048,"_source":509,"_file":1049,"_stem":1050,"_extension":512,"sitemap":1051},"\u002Finsights\u002Farchitecture-overview","Designing for compromise: a multi-tenant SaaS architecture on AWS","Per-customer infrastructure isolation on AWS, where a cross-tenant breach means handing an attacker the blueprint to an organization's defenses.","2026-02-20",{"name":12,"headshot":13,"role":14,"contact":521},{"linkedin":16,"email":17,"twitter":18},"\u002Fimages\u002Fdesigned_for_compromise.jpg",[524],"Tech Deep-Dives",[526,527,24,528,529,530,531],"AWS","multi-tenant","infrastructure","Terraform","Fedora CoreOS","dethernety",{"type":28,"children":533,"toc":1036},[534,539,544,549,554,559,564,571,576,581,586,591,605,610,616,633,638,643,648,653,658,664,669,680,685,690,695,700,705,710,716,721,726,734,746,757,762,768,781,789,799,809,819,829,834,840,848,853,858,863,869,874,885,896,901,911,921,931,941,946,952,957,963,1015,1018],{"type":31,"tag":32,"props":535,"children":537},{"id":536},"designing-for-compromise-a-multi-tenant-saas-architecture-on-aws",[538],{"type":37,"value":517},{"type":31,"tag":39,"props":540,"children":541},{},[542],{"type":37,"value":543},"Multi-tenant SaaS on AWS has a gravitational pull. The defaults are well-known: a shared VPC, a central management plane with broad IAM permissions, one Cognito pool with custom attributes for tenant isolation, a single Terraform state file that describes everything. It works and it ships fast, but it creates a system where the compromise of one component can reach every customer.",{"type":31,"tag":39,"props":545,"children":546},{},[547],{"type":37,"value":548},"This is the first article in a series about an architecture that takes a different path — and about why this particular platform demands it.",{"type":31,"tag":39,"props":550,"children":551},{},[552],{"type":37,"value":553},"The platform is a threat modeling tool. Customers use it to map their attack surfaces, model attack paths through their infrastructure, catalog their defenses, and analyze where their security architectures are weakest. The data inside each workspace is, by definition, a detailed roadmap of that organization's vulnerabilities. Where the entry points are. Which assets are reachable from which attack paths. What's defended and what isn't.",{"type":31,"tag":39,"props":555,"children":556},{},[557],{"type":37,"value":558},"A cross-tenant data breach on a typical SaaS platform exposes user records or business data. A cross-tenant breach on a security platform hands one organization's complete threat model to another party. The stakes are categorically different, and they demand a level of infrastructure isolation that shared-everything multi-tenancy cannot provide.",{"type":31,"tag":39,"props":560,"children":561},{},[562],{"type":37,"value":563},"The series will go deep on every component: Terraform modules, IAM policies, container configurations, cost breakdowns. This article starts from the top: the motivation, the design philosophy, what the resulting system looks like, and how a customer goes from sign-up to a running workspace.",{"type":31,"tag":565,"props":566,"children":568},"h2",{"id":567},"the-problem-with-shared-everything",[569],{"type":37,"value":570},"The problem with shared everything",{"type":31,"tag":39,"props":572,"children":573},{},[574],{"type":37,"value":575},"Infrastructure isolation isn't absent in modern cloud architectures, but it's typically functional. AWS multi-account strategies, landing zones, and hub-and-spoke models separate workloads along functional boundaries: a security account, a logging account, a networking hub, separate environments for dev, staging, and production. This is valuable. It means a compromised application or database doesn't take down visibility: monitoring remains intact in its own account. A dev environment can't touch production resources.",{"type":31,"tag":39,"props":577,"children":578},{},[579],{"type":37,"value":580},"But within the production workload, all customers still share the same VPC, the same database engine, the same compute, the same management plane. The isolation between Customer A and Customer B happens in the application layer: tenant ID filters on database queries, middleware that checks ownership, row-level security policies. Infrastructure isolation per customer is rare.",{"type":31,"tag":39,"props":582,"children":583},{},[584],{"type":37,"value":585},"For most SaaS products, application-level tenant isolation is entirely adequate. And at scale (millions of users, thousands of tenants), per-customer infrastructure isolation isn't an option. The operational overhead, the cost, and the AWS service limits themselves (VPCs per region, Cognito pools per account, subnets per VPC) make it prohibitive. Application-level isolation is the only viable model at that scale, and it serves most platforms well.",{"type":31,"tag":39,"props":587,"children":588},{},[589],{"type":37,"value":590},"But the data on this platform isn't profile information or project files. It's security architectures, attack graphs, defense mappings, and vulnerability analyses. The cost of a cross-tenant leak isn't a privacy incident. It's handing an attacker the blueprint to an organization's defenses.",{"type":31,"tag":39,"props":592,"children":593},{},[594,596,603],{"type":37,"value":595},"A compromised provisioner doesn't check tenant IDs before accessing an S3 bucket. A leaked IAM credential doesn't respect row-level security. When the application layer is bypassed, the infrastructure's boundaries are what remain, and in a shared-everything architecture each shared layer is a path between customers. A shared VPC means network-level reachability. A shared Cognito pool means a misconfiguration can leak tokens across tenants. A shared management plane means the most privileged component in the system can touch every customer's resources. A single Terraform state file means an operational error (an accidental ",{"type":31,"tag":597,"props":598,"children":600},"code",{"className":599},[],[601],{"type":37,"value":602},"terraform destroy",{"type":37,"value":604},", a corrupted state during concurrent apply) affects everyone.",{"type":31,"tag":39,"props":606,"children":607},{},[608],{"type":37,"value":609},"The conventional response is to add more application-level checks, more middleware, more filters. For a platform that holds threat models and attack surfaces, this architecture takes a different approach: remove the shared paths entirely.",{"type":31,"tag":565,"props":611,"children":613},{"id":612},"design-for-compromise",[614],{"type":37,"value":615},"Design for compromise",{"type":31,"tag":39,"props":617,"children":618},{},[619,621,626,628],{"type":37,"value":620},"The question this architecture starts from is not ",{"type":31,"tag":43,"props":622,"children":623},{},[624],{"type":37,"value":625},"how do we prevent unauthorized access between tenants?",{"type":37,"value":627}," It is: ",{"type":31,"tag":43,"props":629,"children":630},{},[631],{"type":37,"value":632},"if a component is compromised, what can it reach?",{"type":31,"tag":39,"props":634,"children":635},{},[636],{"type":37,"value":637},"When the answer to that question could be \"another organization's complete threat model,\" the architecture cannot afford to rely on application-level enforcement alone.",{"type":31,"tag":39,"props":639,"children":640},{},[641],{"type":37,"value":642},"Prevention is about making the walls strong. Containment is about making the walls narrow. Prevention fails when any single layer is bypassed. Containment holds even when a layer is fully compromised, because the compromised component simply cannot reference another customer's resources. The reference doesn't exist.",{"type":31,"tag":39,"props":644,"children":645},{},[646],{"type":37,"value":647},"The strategy is structural isolation. Each customer gets their own network segment, their own identity provider, their own storage, their own compute, their own deployer, their own databases. Not through application logic that filters by tenant, but through infrastructure boundaries that make cross-tenant access impossible. Separate subnets. Separate IAM roles that reference only one customer's resources by ARN. Separate Cognito pools. Separate Terraform state files. Separate deployers that can assume only one customer's provisioner role.",{"type":31,"tag":39,"props":649,"children":650},{},[651],{"type":37,"value":652},"The cost is more of everything: more IAM policies, more Cognito pools, more Terraform state paths, more deployers. The benefit is that a compromise is contained, structurally, to one customer.",{"type":31,"tag":39,"props":654,"children":655},{},[656],{"type":37,"value":657},"Everything else in the architecture follows from this premise.",{"type":31,"tag":565,"props":659,"children":661},{"id":660},"what-each-customer-gets",[662],{"type":37,"value":663},"What each customer gets",{"type":31,"tag":39,"props":665,"children":666},{},[667],{"type":37,"value":668},"When a customer deploys their workspace, the platform provisions a complete, isolated environment:",{"type":31,"tag":39,"props":670,"children":672},{"align":671},"center",[673],{"type":31,"tag":674,"props":675,"children":679},"img",{"src":676,"alt":677,"width":678},"\u002Fimages\u002Fdiagram-per-customer-boundary.svg","diagram per customer boundary",800,[],{"type":31,"tag":39,"props":681,"children":682},{},[683],{"type":37,"value":684},"Each customer gets a \u002F28 private subnet within the customer VPC. No instance has a public IP. Inbound traffic arrives exclusively through a dedicated CloudFront distribution, which connects to the subnet over the AWS backbone via a VPC Origin. Authentication runs through a dedicated Cognito User Pool with its own password policy, MFA configuration, and token settings. A bug or misconfiguration in one pool cannot leak data to another.",{"type":31,"tag":39,"props":686,"children":687},{},[688],{"type":37,"value":689},"Each customer gets dedicated compute. On the Consultant tier, a single EC2 instance. On higher tiers, a K3s cluster per customer with rolling updates and redundancy. The blast radius boundary is the same.",{"type":31,"tag":39,"props":691,"children":692},{},[693],{"type":37,"value":694},"The Consultant tier has the harder engineering problem: delivering the same isolation and manageability at a price point where Kubernetes isn't justifiable. The instance must be reproducible, immutable, and updatable without SSH or manual intervention.",{"type":31,"tag":39,"props":696,"children":697},{},[698],{"type":37,"value":699},"Fedora CoreOS solves this — a container-optimized Linux with a read-only root filesystem, no package manager, and no SSH. The entire OS configuration is defined in a Butane YAML file, compiled to Ignition JSON, uploaded to S3, and fetched by the instance at first boot. The containers run under systemd on a Podman bridge network (an isolated container network on the host). When the configuration changes, Terraform replaces the entire instance. The persistent EBS data volume is detached and reattached; databases survive. The root volume is destroyed and recreated from scratch. There is no configuration drift.",{"type":31,"tag":39,"props":701,"children":702},{},[703],{"type":37,"value":704},"Two Route53 zones for the same domain create a split-horizon DNS: a public zone pointing at CloudFront and a private zone for internal service discovery. The instance can write TLS challenge records to the public zone but cannot touch routing records or the private zone. Storage is two S3 buckets (frontend assets and internal configuration) with public access blocked and encryption enabled, plus the persistent EBS data volume for databases.",{"type":31,"tag":39,"props":706,"children":707},{},[708],{"type":37,"value":709},"Each customer's Fargate deployer has an IAM role that can do exactly one thing: assume this customer's provisioner role. That role references only this customer's resources by hardcoded ARN, never wildcards. Where AWS supports condition keys (record types, resource tags, source IPs), they further narrow what each role can do. The Terraform state file is also per-customer.",{"type":31,"tag":565,"props":711,"children":713},{"id":712},"the-architecture-from-above",[714],{"type":37,"value":715},"The architecture from above",{"type":31,"tag":39,"props":717,"children":718},{},[719],{"type":37,"value":720},"The full deployment includes the expected functional isolation (separate accounts for security, logging, and monitoring) but this overview focuses on the workload architecture: the infrastructure that runs customer environments.",{"type":31,"tag":39,"props":722,"children":723},{},[724],{"type":37,"value":725},"The workload runs in two VPCs within a single AWS region.",{"type":31,"tag":39,"props":727,"children":728},{"align":671},[729],{"type":31,"tag":674,"props":730,"children":733},{"src":731,"alt":732,"width":678},"\u002Fimages\u002Fdiagram-two-vpc-architecture.svg","diagram-two-vpc-architecture",[],{"type":31,"tag":39,"props":735,"children":736},{},[737,739,744],{"type":37,"value":738},"The ",{"type":31,"tag":62,"props":740,"children":741},{},[742],{"type":37,"value":743},"management VPC",{"type":37,"value":745}," hosts shared platform functions: the registration and payment Lambdas, account provisioning, the Fargate deployers that run Terraform, bastion access, and monitoring.",{"type":31,"tag":39,"props":747,"children":748},{},[749,750,755],{"type":37,"value":738},{"type":31,"tag":62,"props":751,"children":752},{},[753],{"type":37,"value":754},"customer VPC",{"type":37,"value":756}," hosts customer workloads. Each customer's \u002F28 subnet is allocated atomically via a DynamoDB counter; indices are never reused. AWS service limits cap the number of customers per account, but the architecture doesn't try to maximize that number. It scales through a cell-based model: each cell is a separate AWS account with its own pair of VPCs, its own service limits, and its own blast radius boundary. Cells isolate groups of tenants from each other at the account level. This is a second containment boundary, on top of the per-customer isolation within each cell.",{"type":31,"tag":39,"props":758,"children":759},{},[760],{"type":37,"value":761},"VPC peering connects the two networks within a cell, with DNS resolution enabled in both directions. The split means a compromised customer instance has no network path to the management plane, and a compromised deployer has no persistent presence in the customer VPC.",{"type":31,"tag":565,"props":763,"children":765},{"id":764},"from-sign-up-to-running-workspace",[766],{"type":37,"value":767},"From sign-up to running workspace",{"type":31,"tag":39,"props":769,"children":770},{},[771,773,779],{"type":37,"value":772},"The shared platform infrastructure (VPCs, peering, NAT, container registries, shared IAM roles) is provisioned once by the platform operator. What's specific to this architecture is what happens when a customer arrives. The provisioning pipeline is a sequence of Lambda functions, each running as a single compiled binary inside a minimal ",{"type":31,"tag":597,"props":774,"children":776},{"className":775},[],[777],{"type":37,"value":778},"FROM scratch",{"type":37,"value":780}," container: no runtime, no shell, no dependencies beyond the binary itself. Each invocation starts clean, runs one function, and exits. The pipeline has four stages, with gates between them.",{"type":31,"tag":39,"props":782,"children":783},{"align":671},[784],{"type":31,"tag":674,"props":785,"children":788},{"src":786,"alt":787,"width":678},"\u002Fimages\u002Fdiagram-deployment-flow.svg","diagram-deployment-flow.drawio.svg",[],{"type":31,"tag":39,"props":790,"children":791},{},[792,797],{"type":31,"tag":62,"props":793,"children":794},{},[795],{"type":37,"value":796},"Registration",{"type":37,"value":798}," is itself a queue-based, multi-step flow. The sign-up form creates a DynamoDB record and sends a verification email. Nothing else. Email verification pushes a job to SQS, which triggers a provisioner Lambda that creates the Cognito pool. Each step is isolated: the registration Lambda can write to DynamoDB and send email but cannot create Cognito pools; the provisioner Lambda can create Cognito pools but is triggered only through SQS, not directly. Failed provisioning retries through the queue, with a dead letter queue and CloudWatch alarms as a backstop. A bot submitting 10,000 fake sign-ups creates 10,000 DynamoDB records (negligible cost) and they auto-delete within two hours. No real AWS resources are created until a human verifies their email.",{"type":31,"tag":39,"props":800,"children":801},{},[802,807],{"type":31,"tag":62,"props":803,"children":804},{},[805],{"type":37,"value":806},"Payment",{"type":37,"value":808}," gates the transition from a database record to real resources. The payment flow is itself isolated: Stripe webhook → API Gateway → Lambda (signature verification) → SQS → processing Lambda → DynamoDB. Each step authenticates independently. The webhook Lambda verifies the Stripe signature, the processing Lambda validates the SQS message and cross-references the session. The webhook Lambda can only push to SQS; it has no direct access to DynamoDB or any infrastructure creation capability. Until payment is confirmed, the customer exists only in DynamoDB.",{"type":31,"tag":39,"props":810,"children":811},{},[812,817],{"type":31,"tag":62,"props":813,"children":814},{},[815],{"type":37,"value":816},"Account provisioning",{"type":37,"value":818}," requires three independent factors before any infrastructure is created. A triple gate: a valid JWT (proving identity through Cognito authentication), an active payment status in DynamoDB (proving Stripe confirmation), and a valid provisioning token from a magic link email (proving email access, time-bound, hashed). All three must pass independently. Only then does the platform create the customer's Cognito pool, \u002F28 subnet, split-horizon DNS zones, per-customer IAM roles with hardcoded ARNs, and an internal S3 bucket.",{"type":31,"tag":39,"props":820,"children":821},{},[822,827],{"type":31,"tag":62,"props":823,"children":824},{},[825],{"type":37,"value":826},"Infrastructure deployment",{"type":37,"value":828}," is triggered by the customer. They click \"Deploy Infrastructure\" in the management console. This launches a Fargate task whose IAM role can do exactly one thing: assume this customer's provisioner role. The provisioner role references only this customer's resources by ARN. It cannot discover or touch another customer's infrastructure. Terraform runs against a per-customer state file, so even a corrupted state or an accidental destroy is contained to one customer. The deployer creates the EC2 instance, data volume, CloudFront distribution, frontend bucket, TLS certificate, DNS records, and management API. The instance boots from a clean Ignition configuration fetched from S3 and stands up the full container stack.",{"type":31,"tag":39,"props":830,"children":831},{},[832],{"type":37,"value":833},"Each stage is idempotent — re-running produces the same result.",{"type":31,"tag":565,"props":835,"children":837},{"id":836},"how-traffic-reaches-the-backend",[838],{"type":37,"value":839},"How traffic reaches the backend",{"type":31,"tag":39,"props":841,"children":842},{"align":671},[843],{"type":31,"tag":674,"props":844,"children":847},{"src":845,"alt":846,"width":678},"\u002Fimages\u002Fdiagram-request-flow.svg","diagram-request-flow",[],{"type":31,"tag":39,"props":849,"children":850},{},[851],{"type":37,"value":852},"A user request to their workspace hits CloudFront first. Static assets go to S3 through Origin Access Control. API and GraphQL requests go to the VPC Origin. CloudFront connects to an ENI in the customer's subnet over the AWS backbone, injecting a secret header that proves the request came through the CDN.",{"type":31,"tag":39,"props":854,"children":855},{},[856],{"type":37,"value":857},"The EC2 instance has no public IP, but traffic between CloudFront and the backend is still encrypted end-to-end. The instance runs a Let's Encrypt certificate obtained through DNS-01 challenge validation. No inbound port 80, no public endpoint. nginx terminates TLS, validates the secret header (rejecting anything that didn't arrive through CloudFront), and reverse-proxies to the backend. The backend connects to the graph database, vector store, and policy engine over a Podman bridge network. Nothing is exposed to the host except nginx on port 443.",{"type":31,"tag":39,"props":859,"children":860},{},[861],{"type":37,"value":862},"Admin operations take a separate path through API Gateway and Lambda. Every step in the chain independently verifies the user's JWT and authenticates the upstream component that forwarded the request. No layer trusts that a previous one already validated.",{"type":31,"tag":565,"props":864,"children":866},{"id":865},"deliberate-trade-offs-and-tiering",[867],{"type":37,"value":868},"Deliberate trade-offs and tiering",{"type":31,"tag":39,"props":870,"children":871},{},[872],{"type":37,"value":873},"This architecture optimizes for blast radius isolation. That has consequences, and the trade-offs differ by tier.",{"type":31,"tag":39,"props":875,"children":876},{},[877,878,883],{"type":37,"value":738},{"type":31,"tag":62,"props":879,"children":880},{},[881],{"type":37,"value":882},"Consultant tier",{"type":37,"value":884}," runs a single EC2 instance per customer, Podman with systemd, and instance replacement for updates. It's designed for independent consultants and small teams running threat modeling sessions, where brief downtime during updates is acceptable and single-node simplicity keeps cost and operational complexity low.",{"type":31,"tag":39,"props":886,"children":887},{},[888,889,894],{"type":37,"value":738},{"type":31,"tag":62,"props":890,"children":891},{},[892],{"type":37,"value":893},"Team and Enterprise tiers",{"type":37,"value":895}," move from a single node to a self-managed Kubernetes cluster (K3s) per customer. The isolation model stays the same: per-customer subnet, per-customer IAM roles, per-customer state. What changes is the compute layer inside that boundary: one instance with Podman becomes a multi-node cluster with rolling updates and no downtime. The Terraform modules are different, but the blast radius boundary is identical. The series will cover these tiers separately.",{"type":31,"tag":39,"props":897,"children":898},{},[899],{"type":37,"value":900},"Within the Consultant tier, the trade-offs are explicit:",{"type":31,"tag":39,"props":902,"children":903},{},[904,909],{"type":31,"tag":62,"props":905,"children":906},{},[907],{"type":37,"value":908},"No high availability at the instance level.",{"type":37,"value":910}," A single EC2 instance. If it fails, the instance is replaced and the data volume survives, but there are 2-5 minutes of downtime. For threat modeling sessions, not real-time transactions, this is acceptable.",{"type":31,"tag":39,"props":912,"children":913},{},[914,919],{"type":31,"tag":62,"props":915,"children":916},{},[917],{"type":37,"value":918},"No Kubernetes.",{"type":37,"value":920}," Multiple containers on one node, managed by Podman and systemd. Kubernetes would add control plane and networking complexity without corresponding benefit at single-node scale.",{"type":31,"tag":39,"props":922,"children":923},{},[924,929],{"type":31,"tag":62,"props":925,"children":926},{},[927],{"type":37,"value":928},"No shared database.",{"type":37,"value":930}," Each customer runs their own graph database and vector store as containers. More expensive than a shared managed cluster, but customer data never shares a database engine with another customer. Blast radius isolation extends to the data layer.",{"type":31,"tag":39,"props":932,"children":933},{},[934,939],{"type":31,"tag":62,"props":935,"children":936},{},[937],{"type":37,"value":938},"No in-place updates.",{"type":37,"value":940}," Updates have two speeds. Container image updates are fast: pull the new image, restart the service. OS-level changes (kernel, systemd units, nginx config) replace the entire instance, which means 2-5 minutes of downtime. Either way, there is no configuration drift. Every update starts from a known, clean state.",{"type":31,"tag":39,"props":942,"children":943},{},[944],{"type":37,"value":945},"Per-customer isolation is inherently more expensive than shared infrastructure. The architecture manages this through deliberate choices across the platform: an open-source NAT instance instead of the AWS NAT Gateway, CloudFront edge locations limited to the target market, and an S3 Gateway Endpoint to avoid data transfer costs.",{"type":31,"tag":565,"props":947,"children":949},{"id":948},"what-comes-next",[950],{"type":37,"value":951},"What comes next",{"type":31,"tag":39,"props":953,"children":954},{},[955],{"type":37,"value":956},"This article described the shape of the architecture: the motivation, the isolation model, and how the pieces fit together. The rest of the series goes inside each component: IAM policies, Terraform modules, queue-based provisioning, split-horizon DNS, the immutable instance lifecycle, and cost. Each article will include working code, configuration, and the reasoning behind the trade-offs.",{"type":31,"tag":565,"props":958,"children":960},{"id":959},"series",[961],{"type":37,"value":962},"Series",{"type":31,"tag":964,"props":965,"children":966},"ol",{},[967,979,988,997,1006],{"type":31,"tag":968,"props":969,"children":970},"li",{},[971,977],{"type":31,"tag":972,"props":973,"children":974},"a",{"href":516},[975],{"type":37,"value":976},"Architecture overview",{"type":37,"value":978}," (this article)",{"type":31,"tag":968,"props":980,"children":981},{},[982],{"type":31,"tag":972,"props":983,"children":985},{"href":984},"\u002Finsights\u002Fblast-radius",[986],{"type":37,"value":987},"Customer isolation from the infrastructure up",{"type":31,"tag":968,"props":989,"children":990},{},[991],{"type":31,"tag":972,"props":992,"children":994},{"href":993},"\u002Finsights\u002Fcustomer-deployment-flows",[995],{"type":37,"value":996},"Automating isolation: the self-service deployment pipeline",{"type":31,"tag":968,"props":998,"children":999},{},[1000],{"type":31,"tag":972,"props":1001,"children":1003},{"href":1002},"\u002Finsights\u002Fcloudfront-vpc-origins",[1004],{"type":37,"value":1005},"CloudFront VPC Origins: what breaks and how to fix it",{"type":31,"tag":968,"props":1007,"children":1008},{},[1009],{"type":31,"tag":972,"props":1010,"children":1012},{"href":1011},"\u002Finsights\u002Fcompute-stack",[1013],{"type":37,"value":1014},"The compute stack: Fedora CoreOS, Podman quadlets, and systemd-native container orchestration",{"type":31,"tag":49,"props":1016,"children":1017},{},[],{"type":31,"tag":39,"props":1019,"children":1020},{},[1021],{"type":31,"tag":43,"props":1022,"children":1023},{},[1024,1026,1034],{"type":37,"value":1025},"This architecture is implemented in ",{"type":31,"tag":972,"props":1027,"children":1031},{"href":1028,"rel":1029},"https:\u002F\u002Fdether.net",[1030],"nofollow",[1032],{"type":37,"value":1033},"dether.net",{"type":37,"value":1035},", a graph-native threat modeling platform. If you're interested in seeing these patterns applied to security architecture analysis, that's where they run in production.",{"title":7,"searchDepth":505,"depth":505,"links":1037},[1038,1040,1041,1042,1043,1044,1045,1046,1047],{"id":567,"depth":1039,"text":570},2,{"id":612,"depth":1039,"text":615},{"id":660,"depth":1039,"text":663},{"id":712,"depth":1039,"text":715},{"id":764,"depth":1039,"text":767},{"id":836,"depth":1039,"text":839},{"id":865,"depth":1039,"text":868},{"id":948,"depth":1039,"text":951},{"id":959,"depth":1039,"text":962},"content:insights:architecture-overview.md","insights\u002Farchitecture-overview.md","insights\u002Farchitecture-overview",{"loc":516},{"_path":984,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":987,"description":1053,"date":1054,"author":1055,"image":1057,"category":1058,"tags":1059,"body":1063,"_type":507,"_id":1925,"_source":509,"_file":1926,"_stem":1927,"_extension":512,"sitemap":1928},"How per-customer blast radius containment works across layers of AWS infrastructure — IAM, network, DNS, identity, storage, state, compute, and ingress — each enforcing isolation on its own.","2026-02-22",{"name":12,"headshot":13,"role":14,"contact":1056},{"linkedin":16,"email":17,"twitter":18},"\u002Fimages\u002Fdiagram-isolation-layers.png",[524],[526,1060,1061,527,24,1062,531],"IAM","blast radius","isolation",{"type":28,"children":1064,"toc":1911},[1065,1070,1081,1086,1092,1113,1118,1126,1132,1153,1172,1177,1182,1192,1202,1212,1222,1235,1299,1304,1333,1339,1344,1349,1354,1359,1365,1378,1389,1478,1483,1494,1499,1505,1510,1515,1520,1526,1531,1552,1557,1592,1597,1602,1608,1620,1625,1637,1643,1648,1653,1658,1746,1751,1757,1762,1767,1774,1791,1801,1806,1812,1817,1822,1827,1833,1838,1843,1848,1853,1857,1889,1892,1905],{"type":31,"tag":32,"props":1066,"children":1068},{"id":1067},"customer-isolation-from-the-infrastructure-up",[1069],{"type":37,"value":987},{"type":31,"tag":39,"props":1071,"children":1072},{},[1073,1074,1079],{"type":37,"value":738},{"type":31,"tag":972,"props":1075,"children":1076},{"href":516},[1077],{"type":37,"value":1078},"previous article",{"type":37,"value":1080}," described an architecture where each customer gets their own subnet, Cognito pool, S3 buckets, Terraform state, and deployer. This article explains the constraint behind those decisions and traces it through the infrastructure, layer by layer. Later articles go deeper into the implementation of each component.",{"type":31,"tag":39,"props":1082,"children":1083},{},[1084],{"type":37,"value":1085},"The constraint is blast radius. Every boundary in the architecture exists to answer one question: if this component is compromised, what can it reach?",{"type":31,"tag":565,"props":1087,"children":1089},{"id":1088},"the-organizing-principle",[1090],{"type":37,"value":1091},"The organizing principle",{"type":31,"tag":39,"props":1093,"children":1094},{},[1095,1097,1103,1105,1111],{"type":37,"value":1096},"Application-level isolation — tenant ID checks, middleware, row-level security — fails when any check is wrong: a missing filter, a misconfigured middleware, a bypassed code path. Infrastructure-level isolation fails differently. A provisioner role that contains ",{"type":31,"tag":597,"props":1098,"children":1100},{"className":1099},[],[1101],{"type":37,"value":1102},"arn:aws:s3:::{prefix}-acme-{suffix}-internal",{"type":37,"value":1104}," and not ",{"type":31,"tag":597,"props":1106,"children":1108},{"className":1107},[],[1109],{"type":37,"value":1110},"arn:aws:s3:::{prefix}-corp-{suffix}-internal",{"type":37,"value":1112}," cannot access Corp's bucket regardless of what the application does. The reference doesn't exist in the policy document. AWS denies the request before it reaches any application code.",{"type":31,"tag":39,"props":1114,"children":1115},{},[1116],{"type":37,"value":1117},"The principle is to push isolation down the stack — into IAM policies, subnet boundaries, DNS zones, Cognito pools, S3 bucket policies, Terraform state paths — so that each layer enforces containment on its own. The blast radius of a compromise is the intersection of all layers, not the weakest one. A compromise must bypass all of them to reach another customer.",{"type":31,"tag":39,"props":1119,"children":1120},{"align":671},[1121],{"type":31,"tag":674,"props":1122,"children":1125},{"src":1123,"alt":1124,"width":678},"\u002Fimages\u002Fdiagram-isolation-layers.svg","diagram isolation layers",[],{"type":31,"tag":565,"props":1127,"children":1129},{"id":1128},"iam-hardcoded-arns-no-wildcards",[1130],{"type":37,"value":1131},"IAM: hardcoded ARNs, no wildcards",{"type":31,"tag":39,"props":1133,"children":1134},{},[1135,1137,1143,1145,1151],{"type":37,"value":1136},"IAM determines what stolen credentials can reach. In a shared-everything architecture, a compromised role with ",{"type":31,"tag":597,"props":1138,"children":1140},{"className":1139},[],[1141],{"type":37,"value":1142},"s3:GetObject",{"type":37,"value":1144}," on ",{"type":31,"tag":597,"props":1146,"children":1148},{"className":1147},[],[1149],{"type":37,"value":1150},"arn:aws:s3:::platform-*",{"type":37,"value":1152}," can read every customer's bucket. The wildcard is the blast radius.",{"type":31,"tag":39,"props":1154,"children":1155},{},[1156,1158,1163,1165,1170],{"type":37,"value":1157},"This architecture eliminates wildcards from per-customer policies. The provisioner role for customer Acme contains ",{"type":31,"tag":597,"props":1159,"children":1161},{"className":1160},[],[1162],{"type":37,"value":1102},{"type":37,"value":1164},". The role for Corp contains ",{"type":31,"tag":597,"props":1166,"children":1168},{"className":1167},[],[1169],{"type":37,"value":1110},{"type":37,"value":1171},". Terraform templates the customer ID into every ARN at creation time.",{"type":31,"tag":39,"props":1173,"children":1174},{},[1175],{"type":37,"value":1176},"The same applies to every other AWS service in the stack. Route53 zone IDs, subnet ARNs, Cognito pool ARNs, Lambda function ARNs, CloudWatch log group paths. All customer-specific, all hardcoded.",{"type":31,"tag":39,"props":1178,"children":1179},{},[1180],{"type":37,"value":1181},"The roles form a chain that narrows scope at each step:",{"type":31,"tag":39,"props":1183,"children":1184},{},[1185,1190],{"type":31,"tag":62,"props":1186,"children":1187},{},[1188],{"type":37,"value":1189},"Bootstrap",{"type":37,"value":1191}," creates S3 state buckets and the platform IAM user. Runs once, by the operator.",{"type":31,"tag":39,"props":1193,"children":1194},{},[1195,1200],{"type":31,"tag":62,"props":1196,"children":1197},{},[1198],{"type":37,"value":1199},"Platform",{"type":37,"value":1201}," creates shared infrastructure: VPCs, container registries, the parent DNS zone, shared IAM roles. Broad permissions within the platform account, but no per-customer resources.",{"type":31,"tag":39,"props":1203,"children":1204},{},[1205,1210],{"type":31,"tag":62,"props":1206,"children":1207},{},[1208],{"type":37,"value":1209},"Provisioner",{"type":37,"value":1211}," is per-customer. Inline policies containing hardcoded ARNs for this customer's resources only. A permission boundary caps the maximum scope. Even if an inline policy is misconfigured, the boundary prevents escalation beyond this customer's resource set.",{"type":31,"tag":39,"props":1213,"children":1214},{},[1215,1220],{"type":31,"tag":62,"props":1216,"children":1217},{},[1218],{"type":37,"value":1219},"Runtime",{"type":37,"value":1221}," is the EC2 instance profile. Narrower still: S3 read\u002Fwrite for the customer's own buckets, ECR pull for shared container images, CloudWatch scoped to one log group, and Route53 restricted to TXT records in one public zone (for certificate renewal). No permissions to create or destroy infrastructure.",{"type":31,"tag":39,"props":1223,"children":1224},{},[1225,1227,1233],{"type":37,"value":1226},"Each customer's Fargate deployer has a role with exactly one permission: ",{"type":31,"tag":597,"props":1228,"children":1230},{"className":1229},[],[1231],{"type":37,"value":1232},"sts:AssumeRole",{"type":37,"value":1234}," on that customer's provisioner role. The trust policy requires the customer ID as an external ID, preventing confused deputy scenarios:",{"type":31,"tag":1236,"props":1237,"children":1241},"pre",{"className":1238,"code":1239,"language":1240,"meta":7,"style":7},"language-hcl shiki shiki-themes github-dark github-dark","# The deployer task role — one permission, one customer\n{\n  \"Effect\": \"Allow\",\n  \"Action\": \"sts:AssumeRole\",\n  \"Resource\": \"arn:aws:iam::${account_id}:role\u002F${prefix}-${customer_id}-infra-provisioner\"\n}\n","hcl",[1242],{"type":31,"tag":597,"props":1243,"children":1244},{"__ignoreMap":7},[1245,1256,1264,1273,1281,1290],{"type":31,"tag":1246,"props":1247,"children":1250},"span",{"class":1248,"line":1249},"line",1,[1251],{"type":31,"tag":1246,"props":1252,"children":1253},{},[1254],{"type":37,"value":1255},"# The deployer task role — one permission, one customer\n",{"type":31,"tag":1246,"props":1257,"children":1258},{"class":1248,"line":1039},[1259],{"type":31,"tag":1246,"props":1260,"children":1261},{},[1262],{"type":37,"value":1263},"{\n",{"type":31,"tag":1246,"props":1265,"children":1267},{"class":1248,"line":1266},3,[1268],{"type":31,"tag":1246,"props":1269,"children":1270},{},[1271],{"type":37,"value":1272},"  \"Effect\": \"Allow\",\n",{"type":31,"tag":1246,"props":1274,"children":1275},{"class":1248,"line":505},[1276],{"type":31,"tag":1246,"props":1277,"children":1278},{},[1279],{"type":37,"value":1280},"  \"Action\": \"sts:AssumeRole\",\n",{"type":31,"tag":1246,"props":1282,"children":1284},{"class":1248,"line":1283},5,[1285],{"type":31,"tag":1246,"props":1286,"children":1287},{},[1288],{"type":37,"value":1289},"  \"Resource\": \"arn:aws:iam::${account_id}:role\u002F${prefix}-${customer_id}-infra-provisioner\"\n",{"type":31,"tag":1246,"props":1291,"children":1293},{"class":1248,"line":1292},6,[1294],{"type":31,"tag":1246,"props":1295,"children":1296},{},[1297],{"type":37,"value":1298},"}\n",{"type":31,"tag":39,"props":1300,"children":1301},{},[1302],{"type":37,"value":1303},"The chain — deployer task credential → provisioner role → customer resources — narrows at each step. No step can widen it. A compromised deployer can assume the provisioner role for its customer and nothing else. A compromised provisioner can touch its customer's resources and nothing else.",{"type":31,"tag":39,"props":1305,"children":1306},{},[1307,1309,1315,1317,1323,1325,1331],{"type":37,"value":1308},"Where resource ARNs aren't known at creation time (EC2 instances, for example), tag-based conditions provide equivalent scoping. The provisioner applies a ",{"type":31,"tag":597,"props":1310,"children":1312},{"className":1311},[],[1313],{"type":37,"value":1314},"Customer",{"type":37,"value":1316}," tag at creation time through an ",{"type":31,"tag":597,"props":1318,"children":1320},{"className":1319},[],[1321],{"type":37,"value":1322},"aws:RequestTag",{"type":37,"value":1324}," condition, and subsequent operations are restricted to resources carrying that tag via ",{"type":31,"tag":597,"props":1326,"children":1328},{"className":1327},[],[1329],{"type":37,"value":1330},"aws:ResourceTag",{"type":37,"value":1332},". The tag is set once, at creation, and the condition is evaluated by AWS, not by application code.",{"type":31,"tag":565,"props":1334,"children":1336},{"id":1335},"network-one-subnet-no-cross-tenant-paths",[1337],{"type":37,"value":1338},"Network: one subnet, no cross-tenant paths",{"type":31,"tag":39,"props":1340,"children":1341},{},[1342],{"type":37,"value":1343},"The workload runs in two VPCs within a single cell. The broader account structure (security, logging, account-level monitoring) is outside this article's scope. The management VPC hosts deployers and workload monitoring. The customer VPC hosts customer environments. VPC peering connects them, but routing rules prevent customer instances from initiating connections to the management plane.",{"type":31,"tag":39,"props":1345,"children":1346},{},[1347],{"type":37,"value":1348},"Each customer gets a \u002F28 subnet in the customer VPC (sixteen addresses, eleven usable after AWS reservations). No instance has a public IP. Inbound traffic arrives through two paths only: a CloudFront VPC Origin and a per-customer Lambda, both routed over internal AWS networks, not the public internet.",{"type":31,"tag":39,"props":1350,"children":1351},{},[1352],{"type":37,"value":1353},"The security group on a customer's instance allows HTTPS ingress from exactly two sources: the CloudFront VPC Origin security group and a per-customer Lambda security group (for the management API). No security group rule allows traffic between customer subnets.",{"type":31,"tag":39,"props":1355,"children":1356},{},[1357],{"type":37,"value":1358},"A compromised instance in Acme's \u002F28 has no network path to Corp's \u002F28. No security group rule provides it. No route table entry enables it. The instance can reach the internet through a shared NAT instance for outbound calls, but it cannot initiate connections to other customer subnets, to the management VPC, or to any other internal resource. There is nothing to discover at the network layer.",{"type":31,"tag":565,"props":1360,"children":1362},{"id":1361},"dns-two-zones-two-permission-sets",[1363],{"type":37,"value":1364},"DNS: two zones, two permission sets",{"type":31,"tag":39,"props":1366,"children":1367},{},[1368,1370,1376],{"type":37,"value":1369},"Each customer has two Route53 hosted zones for the same domain, for example ",{"type":31,"tag":597,"props":1371,"children":1373},{"className":1372},[],[1374],{"type":37,"value":1375},"acme.dethernety.io",{"type":37,"value":1377},".",{"type":31,"tag":39,"props":1379,"children":1380},{},[1381,1382,1387],{"type":37,"value":738},{"type":31,"tag":62,"props":1383,"children":1384},{},[1385],{"type":37,"value":1386},"public zone",{"type":37,"value":1388}," is internet-resolvable. It holds the CloudFront ALIAS record that routes user traffic, the ACM validation CNAME for the CloudFront TLS certificate, and Let's Encrypt DNS-01 challenge TXT records for the backend TLS certificate. The EC2 instance can write to this zone for certificate renewal, but its IAM policy restricts modifications to TXT records only:",{"type":31,"tag":1236,"props":1390,"children":1392},{"className":1238,"code":1391,"language":1240,"meta":7,"style":7},"{\n  Effect   = \"Allow\"\n  Action   = [\"route53:ChangeResourceRecordSets\"]\n  Resource = \"arn:aws:route53:::hostedzone\u002F${var.public_zone_id}\"\n  Condition = {\n    \"ForAllValues:StringEquals\" = {\n      \"route53:ChangeResourceRecordSetsRecordTypes\" = [\"TXT\"]\n    }\n  }\n}\n",[1393],{"type":31,"tag":597,"props":1394,"children":1395},{"__ignoreMap":7},[1396,1403,1411,1419,1427,1435,1443,1452,1461,1470],{"type":31,"tag":1246,"props":1397,"children":1398},{"class":1248,"line":1249},[1399],{"type":31,"tag":1246,"props":1400,"children":1401},{},[1402],{"type":37,"value":1263},{"type":31,"tag":1246,"props":1404,"children":1405},{"class":1248,"line":1039},[1406],{"type":31,"tag":1246,"props":1407,"children":1408},{},[1409],{"type":37,"value":1410},"  Effect   = \"Allow\"\n",{"type":31,"tag":1246,"props":1412,"children":1413},{"class":1248,"line":1266},[1414],{"type":31,"tag":1246,"props":1415,"children":1416},{},[1417],{"type":37,"value":1418},"  Action   = [\"route53:ChangeResourceRecordSets\"]\n",{"type":31,"tag":1246,"props":1420,"children":1421},{"class":1248,"line":505},[1422],{"type":31,"tag":1246,"props":1423,"children":1424},{},[1425],{"type":37,"value":1426},"  Resource = \"arn:aws:route53:::hostedzone\u002F${var.public_zone_id}\"\n",{"type":31,"tag":1246,"props":1428,"children":1429},{"class":1248,"line":1283},[1430],{"type":31,"tag":1246,"props":1431,"children":1432},{},[1433],{"type":37,"value":1434},"  Condition = {\n",{"type":31,"tag":1246,"props":1436,"children":1437},{"class":1248,"line":1292},[1438],{"type":31,"tag":1246,"props":1439,"children":1440},{},[1441],{"type":37,"value":1442},"    \"ForAllValues:StringEquals\" = {\n",{"type":31,"tag":1246,"props":1444,"children":1446},{"class":1248,"line":1445},7,[1447],{"type":31,"tag":1246,"props":1448,"children":1449},{},[1450],{"type":37,"value":1451},"      \"route53:ChangeResourceRecordSetsRecordTypes\" = [\"TXT\"]\n",{"type":31,"tag":1246,"props":1453,"children":1455},{"class":1248,"line":1454},8,[1456],{"type":31,"tag":1246,"props":1457,"children":1458},{},[1459],{"type":37,"value":1460},"    }\n",{"type":31,"tag":1246,"props":1462,"children":1464},{"class":1248,"line":1463},9,[1465],{"type":31,"tag":1246,"props":1466,"children":1467},{},[1468],{"type":37,"value":1469},"  }\n",{"type":31,"tag":1246,"props":1471,"children":1473},{"class":1248,"line":1472},10,[1474],{"type":31,"tag":1246,"props":1475,"children":1476},{},[1477],{"type":37,"value":1298},{"type":31,"tag":39,"props":1479,"children":1480},{},[1481],{"type":37,"value":1482},"The instance cannot modify A records, CNAME records, or any routing record. A compromised instance with Route53 credentials cannot redirect traffic to a different origin.",{"type":31,"tag":39,"props":1484,"children":1485},{},[1486,1487,1492],{"type":37,"value":738},{"type":31,"tag":62,"props":1488,"children":1489},{},[1490],{"type":37,"value":1491},"private zone",{"type":37,"value":1493}," is VPC-associated only — not resolvable from the internet. It holds the instance's private IP for internal service discovery. CloudFront's VPC Origin resolves the backend through this zone. Only the provisioner role can write to it; the instance profile has no permissions for the private zone at all.",{"type":31,"tag":39,"props":1495,"children":1496},{},[1497],{"type":37,"value":1498},"DNS is a privilege escalation vector. Control over DNS records means the ability to redirect traffic, intercept requests, and steal tokens. Split-horizon DNS with scoped IAM policies limits what a compromised instance can do: renew its own TLS certificate (TXT records in the public zone), but not redirect traffic (A\u002FCNAME records) and not modify internal routing (private zone).",{"type":31,"tag":565,"props":1500,"children":1502},{"id":1501},"identity-one-cognito-pool-per-customer",[1503],{"type":37,"value":1504},"Identity: one Cognito pool per customer",{"type":31,"tag":39,"props":1506,"children":1507},{},[1508],{"type":37,"value":1509},"Each customer gets a separate Cognito User Pool — not a shared pool with tenant-ID attributes, but a distinct AWS resource with its own ARN, password policy, MFA configuration, and PKCE app client.",{"type":31,"tag":39,"props":1511,"children":1512},{},[1513],{"type":37,"value":1514},"A shared pool is simpler to manage and works for most platforms. But it creates a shared authentication surface. A misconfigured Lambda trigger, a broken attribute mapping, or a token-handling bug in a shared pool affects every customer's authentication at once. The blast radius of an identity misconfiguration is every tenant.",{"type":31,"tag":39,"props":1516,"children":1517},{},[1518],{"type":37,"value":1519},"With per-customer pools, the blast radius is one customer. The app client's callback URLs point to this customer's subdomain. Self-registration is disabled. The JWT tokens issued by one pool are validated against that pool's JWKS endpoint. A token from Acme's pool is meaningless to Corp's backend: the issuer doesn't match, the audience doesn't match, the signing keys are different.",{"type":31,"tag":565,"props":1521,"children":1523},{"id":1522},"storage-no-shared-data-layer",[1524],{"type":37,"value":1525},"Storage: no shared data layer",{"type":31,"tag":39,"props":1527,"children":1528},{},[1529],{"type":37,"value":1530},"Each customer has two S3 buckets and a dedicated EBS volume. No shared database engine. No shared storage backend.",{"type":31,"tag":39,"props":1532,"children":1533},{},[1534,1536,1542,1544,1550],{"type":37,"value":1535},"Bucket names include a random suffix generated at provisioning time: ",{"type":31,"tag":597,"props":1537,"children":1539},{"className":1538},[],[1540],{"type":37,"value":1541},"{prefix}-acme-{random}-internal",{"type":37,"value":1543}," rather than the predictable ",{"type":31,"tag":597,"props":1545,"children":1547},{"className":1546},[],[1548],{"type":37,"value":1549},"{prefix}-acme-internal",{"type":37,"value":1551},". S3 bucket names are globally unique, and predictable names are a known attack vector: an adversary who can guess the name can pre-create the bucket in another account and intercept data written to it before the legitimate owner provisions. The suffix makes the name unguessable.",{"type":31,"tag":39,"props":1553,"children":1554},{},[1555],{"type":37,"value":1556},"The frontend bucket serves static assets through CloudFront Origin Access Control. The bucket policy allows access only from one CloudFront distribution, identified by ARN. No other distribution, and no direct access.",{"type":31,"tag":39,"props":1558,"children":1559},{},[1560,1562,1568,1570,1576,1577,1583,1584,1590],{"type":37,"value":1561},"The internal bucket holds Ignition configuration, Terraform outputs, backups, and state files. Public access is blocked at every level: ",{"type":31,"tag":597,"props":1563,"children":1565},{"className":1564},[],[1566],{"type":37,"value":1567},"block_public_acls",{"type":37,"value":1569},", ",{"type":31,"tag":597,"props":1571,"children":1573},{"className":1572},[],[1574],{"type":37,"value":1575},"block_public_policy",{"type":37,"value":1569},{"type":31,"tag":597,"props":1578,"children":1580},{"className":1579},[],[1581],{"type":37,"value":1582},"ignore_public_acls",{"type":37,"value":1569},{"type":31,"tag":597,"props":1585,"children":1587},{"className":1586},[],[1588],{"type":37,"value":1589},"restrict_public_buckets",{"type":37,"value":1591},", all enabled. Versioning is on for recovery. Lifecycle rules manage retention: backups archive to Glacier after seven days, state versions expire after ninety days.",{"type":31,"tag":39,"props":1593,"children":1594},{},[1595],{"type":37,"value":1596},"The EBS volume stores the graph database, vector store, and supporting services. Encrypted at rest, tagged with the customer ID, persistent across instance replacements. The instance profile's IAM policy restricts volume operations to volumes carrying this customer's tag. A compromised instance can access its own data volume but cannot discover or attach another customer's.",{"type":31,"tag":39,"props":1598,"children":1599},{},[1600],{"type":37,"value":1601},"The cost is higher than a multi-tenant RDS cluster or a shared S3 prefix. The tradeoff is that every storage boundary (bucket names, bucket policies, IAM ARNs, volume tags) is enforced by AWS, not by application logic in the request path.",{"type":31,"tag":565,"props":1603,"children":1605},{"id":1604},"state-one-terraform-state-file-per-customer",[1606],{"type":37,"value":1607},"State: one Terraform state file per customer",{"type":31,"tag":39,"props":1609,"children":1610},{},[1611,1613,1618],{"type":37,"value":1612},"Terraform state files contain infrastructure metadata: resource IDs, ARNs, configuration values, and sometimes sensitive outputs. In a shared-state architecture, a corrupted state file or an accidental ",{"type":31,"tag":597,"props":1614,"children":1616},{"className":1615},[],[1617],{"type":37,"value":602},{"type":37,"value":1619}," affects every customer provisioned from that state.",{"type":31,"tag":39,"props":1621,"children":1622},{},[1623],{"type":37,"value":1624},"This architecture isolates state at every level. Bootstrap uses local state; the S3 bucket doesn't exist yet. Platform state lives in a dedicated S3 object. Each customer's account state lives in a shared bucket under a customer-specific key path. Each customer's infrastructure state lives in that customer's own internal S3 bucket.",{"type":31,"tag":39,"props":1626,"children":1627},{},[1628,1630,1635],{"type":37,"value":1629},"The provisioner role for Acme can read and write Acme's state. It cannot read Corp's. The ARN isn't in the policy. Concurrent provisioning operations run against separate state files with separate DynamoDB locks. An accidental ",{"type":31,"tag":597,"props":1631,"children":1633},{"className":1632},[],[1634],{"type":37,"value":602},{"type":37,"value":1636}," against Acme's state tears down Acme's infrastructure and nothing else.",{"type":31,"tag":565,"props":1638,"children":1640},{"id":1639},"compute-immutable-replaceable-contained",[1641],{"type":37,"value":1642},"Compute: immutable, replaceable, contained",{"type":31,"tag":39,"props":1644,"children":1645},{},[1646],{"type":37,"value":1647},"The EC2 instances run Fedora CoreOS: a read-only root filesystem, no package manager, no SSH by default. The entire OS configuration is defined in a Butane YAML file, compiled to Ignition JSON, uploaded to S3, and fetched at first boot. The instance is not configured after launch. It boots into its final state.",{"type":31,"tag":39,"props":1649,"children":1650},{},[1651],{"type":37,"value":1652},"Updates replace the entire instance. Terraform detects a change in the Ignition configuration, terminates the old instance, launches a new one, and reattaches the persistent EBS data volume. No in-place patching, no configuration drift, and no persistence across replacements. An attacker who compromises an instance loses that access when the next deployment replaces it. The root volume is destroyed. The new instance boots from a clean configuration.",{"type":31,"tag":39,"props":1654,"children":1655},{},[1656],{"type":37,"value":1657},"Inside the instance, containers run on a Podman bridge network under systemd. The isolation model follows the same principle: minimize what each component can reach.",{"type":31,"tag":1659,"props":1660,"children":1661},"ul",{},[1662,1688,1698,1708,1726,1736],{"type":31,"tag":968,"props":1663,"children":1664},{},[1665,1670,1672,1678,1680,1686],{"type":31,"tag":62,"props":1666,"children":1667},{},[1668],{"type":37,"value":1669},"Capabilities",{"type":37,"value":1671}," are dropped wholesale (",{"type":31,"tag":597,"props":1673,"children":1675},{"className":1674},[],[1676],{"type":37,"value":1677},"DropCapability=ALL",{"type":37,"value":1679},"), then the minimum set is re-added per container. nginx gets ",{"type":31,"tag":597,"props":1681,"children":1683},{"className":1682},[],[1684],{"type":37,"value":1685},"NET_BIND_SERVICE",{"type":37,"value":1687}," to bind port 443. Application containers get nothing beyond the default.",{"type":31,"tag":968,"props":1689,"children":1690},{},[1691,1696],{"type":31,"tag":62,"props":1692,"children":1693},{},[1694],{"type":37,"value":1695},"NoNewPrivileges",{"type":37,"value":1697}," is set on every container. Escalation through setuid binaries is blocked.",{"type":31,"tag":968,"props":1699,"children":1700},{},[1701,1706],{"type":31,"tag":62,"props":1702,"children":1703},{},[1704],{"type":37,"value":1705},"Root filesystems",{"type":37,"value":1707}," are read-only, with tmpfs for runtime data (cache, PID files).",{"type":31,"tag":968,"props":1709,"children":1710},{},[1711,1716,1718,1724],{"type":31,"tag":62,"props":1712,"children":1713},{},[1714],{"type":37,"value":1715},"SELinux",{"type":37,"value":1717}," runs in enforcing mode. Volume mounts use ",{"type":31,"tag":597,"props":1719,"children":1721},{"className":1720},[],[1722],{"type":37,"value":1723},":Z",{"type":37,"value":1725}," labels for mandatory access control. Each container's files are labeled for that container's context only.",{"type":31,"tag":968,"props":1727,"children":1728},{},[1729,1734],{"type":31,"tag":62,"props":1730,"children":1731},{},[1732],{"type":37,"value":1733},"Resource limits",{"type":37,"value":1735}," are set per container (memory hard and soft limits). A runaway process can't starve other services.",{"type":31,"tag":968,"props":1737,"children":1738},{},[1739,1744],{"type":31,"tag":62,"props":1740,"children":1741},{},[1742],{"type":37,"value":1743},"Application processes",{"type":37,"value":1745}," run as unprivileged users (a dedicated UID), not root.",{"type":31,"tag":39,"props":1747,"children":1748},{},[1749],{"type":37,"value":1750},"A container escape still faces the read-only host filesystem, SELinux context boundaries, and an instance profile scoped to one customer's resources. The layers above (IAM, network, DNS, storage) don't care what happened inside the container.",{"type":31,"tag":565,"props":1752,"children":1754},{"id":1753},"ingress-two-paths-separate-secrets",[1755],{"type":37,"value":1756},"Ingress: two paths, separate secrets",{"type":31,"tag":39,"props":1758,"children":1759},{},[1760],{"type":37,"value":1761},"Many platforms implement management as routes within the application: same process, same authentication middleware, same database connection. A bug in the app exposes the management surface. A bug in management exposes user data. Here, the management function is not part of the application. It runs on separate infrastructure: a separate entry point, separate compute, separate authentication, separate credentials.",{"type":31,"tag":39,"props":1763,"children":1764},{},[1765],{"type":37,"value":1766},"Traffic reaches a customer's environment through two paths. The data path serves user requests through CloudFront. The admin path handles management operations through API Gateway and Lambda. The two share no secrets.",{"type":31,"tag":39,"props":1768,"children":1769},{"align":671},[1770],{"type":31,"tag":674,"props":1771,"children":1773},{"src":845,"alt":1772,"width":678},"diagram ingress paths",[],{"type":31,"tag":39,"props":1775,"children":1776},{},[1777,1782,1784,1789],{"type":31,"tag":62,"props":1778,"children":1779},{},[1780],{"type":37,"value":1781},"The data path",{"type":37,"value":1783}," runs through a dedicated CloudFront distribution that accepts one hostname (",{"type":31,"tag":597,"props":1785,"children":1787},{"className":1786},[],[1788],{"type":37,"value":1375},{"type":37,"value":1790},"), serves static assets through Origin Access Control (restricted to this distribution's ARN), and forwards API requests through a VPC Origin to an ENI in the customer's \u002F28 subnet. CloudFront injects a customer-specific secret header into every forwarded request. nginx validates this header before processing anything. A request that didn't arrive through the correct distribution is rejected.",{"type":31,"tag":39,"props":1792,"children":1793},{},[1794,1799],{"type":31,"tag":62,"props":1795,"children":1796},{},[1797],{"type":37,"value":1798},"The admin path",{"type":37,"value":1800}," runs through a per-customer API Gateway and an ephemeral Lambda function in the customer's subnet. API Gateway validates the JWT against this customer's Cognito pool before the request reaches any code. CORS is restricted to the customer's domain. The Lambda runs with a security group that allows egress only to the backend. It starts clean from a container image, processes the request, injects its own secret header (distinct from the CloudFront header), and forwards to nginx. The management service on the instance re-validates the JWT against Cognito's JWKS endpoint from scratch, trusting nothing upstream.",{"type":31,"tag":39,"props":1802,"children":1803},{},[1804],{"type":37,"value":1805},"The two paths share no infrastructure and no credentials. An attacker who obtains the CloudFront origin header can forge data-path requests to one customer's backend but knows nothing about the Lambda secret. An attacker who obtains the Lambda secret can reach the management service but knows nothing about the CloudFront header.",{"type":31,"tag":565,"props":1807,"children":1809},{"id":1808},"where-the-boundaries-intersect",[1810],{"type":37,"value":1811},"Where the boundaries intersect",{"type":31,"tag":39,"props":1813,"children":1814},{},[1815],{"type":37,"value":1816},"What matters for blast radius is what an attacker actually reaches when a component falls. Take the worst case within a customer boundary: a compromised EC2 instance.",{"type":31,"tag":39,"props":1818,"children":1819},{},[1820],{"type":37,"value":1821},"The attacker gets the instance profile credentials, scoped to one customer's buckets, one log group, TXT records in one DNS zone. They get one \u002F28 subnet with no routes to other subnets or the management VPC. They get a read-only filesystem with SELinux enforcing.",{"type":31,"tag":39,"props":1823,"children":1824},{},[1825],{"type":37,"value":1826},"The containment holds because each boundary is enforced by a different AWS mechanism. IAM won't resolve cross-customer ARNs. Security groups have no cross-subnet rules. The Cognito pool is a separate AWS resource with different signing keys. The root volume is read-only (Fedora CoreOS), so there is nowhere to write a persistent backdoor, and the next deployment destroys the instance entirely and boots from a clean Ignition configuration. An IAM exploit doesn't open network routes. A container escape doesn't produce valid Cognito tokens.",{"type":31,"tag":565,"props":1828,"children":1830},{"id":1829},"trade-offs",[1831],{"type":37,"value":1832},"Trade-offs",{"type":31,"tag":39,"props":1834,"children":1835},{},[1836],{"type":37,"value":1837},"Per-customer isolation creates more resources. Each customer adds approximately six IAM resources, two Route53 zones, two S3 buckets, a Cognito pool, a \u002F28 subnet, a CloudFront distribution, an API Gateway with a Lambda function, a Fargate deployer, and a dedicated EC2 instance. The Terraform modules are more complex because they template customer IDs, zone IDs, and subnet ARNs into every resource.",{"type":31,"tag":39,"props":1839,"children":1840},{},[1841],{"type":37,"value":1842},"AWS service limits bound the number of customers per account. The architecture scales through cells: each cell is a separate AWS account with its own VPC pair, its own service limits, and its own blast radius boundary. At tens to low hundreds of customers per cell, the limits are not a constraint. Each cell is itself an isolation boundary, a second containment layer on top of the per-customer isolation within it.",{"type":31,"tag":39,"props":1844,"children":1845},{},[1846],{"type":37,"value":1847},"Each individual resource is simpler to audit than its shared equivalent. Acme's provisioner policy contains Acme's ARNs. No wildcards to reason about. The complexity is in the quantity of resources, not in understanding what any single policy allows. An auditor reviewing Acme's provisioner role sees exactly what it can touch. There is no need to trace application logic to determine whether a shared role might reach another customer's data under certain conditions.",{"type":31,"tag":39,"props":1849,"children":1850},{},[1851],{"type":37,"value":1852},"The operational cost is real: more state files, more deployers, more policies to manage. The architecture manages this through Terraform modules and automated provisioning pipelines. A new customer is a new set of module invocations with a different customer ID. Same modules, different parameters.",{"type":31,"tag":565,"props":1854,"children":1855},{"id":959},[1856],{"type":37,"value":962},{"type":31,"tag":964,"props":1858,"children":1859},{},[1860,1867,1875,1882],{"type":31,"tag":968,"props":1861,"children":1862},{},[1863],{"type":31,"tag":972,"props":1864,"children":1865},{"href":516},[1866],{"type":37,"value":976},{"type":31,"tag":968,"props":1868,"children":1869},{},[1870,1874],{"type":31,"tag":972,"props":1871,"children":1872},{"href":984},[1873],{"type":37,"value":987},{"type":37,"value":978},{"type":31,"tag":968,"props":1876,"children":1877},{},[1878],{"type":31,"tag":972,"props":1879,"children":1880},{"href":993},[1881],{"type":37,"value":996},{"type":31,"tag":968,"props":1883,"children":1884},{},[1885],{"type":31,"tag":972,"props":1886,"children":1887},{"href":1002},[1888],{"type":37,"value":1005},{"type":31,"tag":49,"props":1890,"children":1891},{},[],{"type":31,"tag":39,"props":1893,"children":1894},{},[1895],{"type":31,"tag":43,"props":1896,"children":1897},{},[1898,1899,1904],{"type":37,"value":1025},{"type":31,"tag":972,"props":1900,"children":1902},{"href":1028,"rel":1901},[1030],[1903],{"type":37,"value":1033},{"type":37,"value":1035},{"type":31,"tag":1906,"props":1907,"children":1908},"style",{},[1909],{"type":37,"value":1910},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":7,"searchDepth":505,"depth":505,"links":1912},[1913,1914,1915,1916,1917,1918,1919,1920,1921,1922,1923,1924],{"id":1088,"depth":1039,"text":1091},{"id":1128,"depth":1039,"text":1131},{"id":1335,"depth":1039,"text":1338},{"id":1361,"depth":1039,"text":1364},{"id":1501,"depth":1039,"text":1504},{"id":1522,"depth":1039,"text":1525},{"id":1604,"depth":1039,"text":1607},{"id":1639,"depth":1039,"text":1642},{"id":1753,"depth":1039,"text":1756},{"id":1808,"depth":1039,"text":1811},{"id":1829,"depth":1039,"text":1832},{"id":959,"depth":1039,"text":962},"content:insights:blast-radius.md","insights\u002Fblast-radius.md","insights\u002Fblast-radius",{"loc":984},1777299091001]