<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Bala Paranj</title>
    <description>The latest articles on DEV Community by Bala Paranj (@bala_paranj_059d338e44e7e).</description>
    <link>https://dev.to/bala_paranj_059d338e44e7e</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3862804%2F7ea6c560-63cb-4daf-a713-450532280b0a.jpg</url>
      <title>DEV Community: Bala Paranj</title>
      <link>https://dev.to/bala_paranj_059d338e44e7e</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/bala_paranj_059d338e44e7e"/>
    <language>en</language>
    <item>
      <title>The Agentic AI Landscape: Workarounds All the Way Down</title>
      <dc:creator>Bala Paranj</dc:creator>
      <pubDate>Tue, 27 Oct 2026 13:38:20 +0000</pubDate>
      <link>https://dev.to/bala_paranj_059d338e44e7e/the-agentic-ai-landscape-workarounds-all-the-way-down-3gad</link>
      <guid>https://dev.to/bala_paranj_059d338e44e7e/the-agentic-ai-landscape-workarounds-all-the-way-down-3gad</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;✓ Human-authored analysis; AI used for formatting and proofreading.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The landscape
&lt;/h2&gt;

&lt;p&gt;In the span of eighteen months, AI-assisted development went from "autocomplete for code" to "agents that build entire applications while you sleep." The architecture underneath is not complete.&lt;/p&gt;

&lt;p&gt;The industry converged on a dominant frame: &lt;strong&gt;Agent = Model + Harness.&lt;/strong&gt; The model generates. Everything else such as filesystem, sandbox, memory, context management, tools, skills, prompt engineering, loop detection is the harness. The harness serves the model. The model is the system.&lt;/p&gt;

&lt;p&gt;Around this frame, a landscape of approaches emerged. Each one addresses a real problem. Each one works, partially, temporarily. And each one is a workaround for an architectural element nobody knows is missing because the frame has no vocabulary for elements that don't serve the model.&lt;/p&gt;

&lt;p&gt;Here's the map.&lt;/p&gt;

&lt;h2&gt;
  
  
  Agent Skills
&lt;/h2&gt;

&lt;p&gt;Encoded domain expertise that tells the model which tools to use, how to interpret results, and what reasoning patterns to apply. Meta's Capacity Efficiency team built the most sophisticated version: standardized tool interfaces composed with skills that capture senior engineers' investigation patterns. Same tools power offense (finding optimizations) and defense (catching regressions).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The problem they solve.&lt;/strong&gt; Models are general-purpose. They don't know that serialization regressions should be investigated by checking recent schema changes, or that endpoint latency regressions should start with GraphQL endpoint profiling. Skills encode "how to look" so the model doesn't waste tokens discovering investigation patterns from scratch every time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why they fall short.&lt;/strong&gt; Skills encode &lt;strong&gt;procedural knowledge&lt;/strong&gt; of how to investigate after something went wrong. They don't encode &lt;strong&gt;declarative knowledge&lt;/strong&gt; of what must be true regardless of whether anything went wrong. "Consult the top GraphQL endpoints for latency regressions" is a recipe. "Serialization functions must not exceed N milliseconds" is a rule the machine enforces before the regression exists. Skills make the model a better investigator. They don't prevent the problems the model investigates.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;They're a workaround for.&lt;/strong&gt; the missing &lt;strong&gt;Engine&lt;/strong&gt;. It is declared intent in the form of specifications the system checks mechanically. Without specifications, you encode expertise as procedures. With specifications, the expertise becomes invariants the machine enforces.&lt;/p&gt;

&lt;h2&gt;
  
  
  Superpowers and Context Engineering
&lt;/h2&gt;

&lt;p&gt;Techniques for managing the model's context window to maximize output quality. Includes: static per-repo onboarding docs (AGENTS.md, CLAUDE.md), on-demand compressed context, sub-agents that take vertical slices and build research documents, compaction strategies that summarize and restart in fresh windows. Dex's RPI protocol (Research → Plan → Implement) is the most disciplined version — staying in the "smart zone" of the context window by intentionally compacting between phases.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The problem they solve.&lt;/strong&gt; Models degrade past roughly 40% of the context window. Too many tools, too much injected context, too much conversation history and the quality drops. Context engineering is the discipline of putting the right tokens in the window so the model produces better output.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why they fall short.&lt;/strong&gt; Context engineering optimizes the &lt;strong&gt;input&lt;/strong&gt; to the model. It doesn't verify the &lt;strong&gt;output.&lt;/strong&gt; A model with perfect context can still generate code that violates architectural boundaries, breaks implicit invariants, or produces output that's locally correct but globally inconsistent. Better input reduces the probability of wrong output. It doesn't verify the output is right. The distinction between "probably right" and "verified correct" is the distinction the approach can't cross.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;They're a workaround for.&lt;/strong&gt; the missing &lt;strong&gt;Casing&lt;/strong&gt;. It is enforced module boundaries and information hiding. If the architecture had mechanical boundaries (Parnas-style modules, hexagonal architecture enforced at build time), agents wouldn't need context engineering to avoid crossing boundaries they can't see. The boundaries would be enforced by the architecture, not by carefully curating the context window.&lt;/p&gt;

&lt;h2&gt;
  
  
  Self-Verification and Reviewer Agents
&lt;/h2&gt;

&lt;p&gt;The model reviews its own output, or a second model reviews the first. LangChain calls models "exceptional self-improvement machines" and lists self-verification as their top performance improvement. Anthropic's three-agent harness uses a separate evaluator agent with Playwright-driven testing. OpenAI runs reviewer agents in CI checking patches against documentation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The problem they solve.&lt;/strong&gt; Unreviewed code ships with defects. When models generate at machine speed, human review can't keep pace with review duration up 441%, zero-review merges up 31%. Some automated quality signal is better than none.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why they fall short.&lt;/strong&gt; &lt;a href="https://arxiv.org/abs/2310.01798" rel="noopener noreferrer"&gt;Huang et al. (ICLR 2024)&lt;/a&gt; established that models cannot self-correct reasoning without external feedback where performance can degrade after self-correction. The same blind spots that produced the error evaluate the error as correct. Anthropic acknowledged, their evaluator is "still an LLM that is inclined to be generous towards LLM-generated outputs" and spent "several rounds" tuning the evaluator prompt to be sufficiently skeptical. A type checker doesn't need tuning to be skeptical. It doesn't form opinions. It passes or fails.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;They're a workaround for.&lt;/strong&gt; the missing &lt;strong&gt;Control Unit&lt;/strong&gt;. It is an independent oracle that measures output against declared properties and issues a deterministic verdict. Self-verification substitutes a model's opinion for a mechanical check. The workaround exists because the element (independent, deterministic verification against declared intent) is absent from the architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  Loop Engineering and the Ralph Loop
&lt;/h2&gt;

&lt;p&gt;Patterns that keep the model generating over long horizons. The Ralph Loop intercepts the model's attempt to stop, reinjects the prompt in a fresh context, and forces continued generation until a completion goal is met. Loop engineering automates this: agent writes, judge agent reviews, loop continues. The aspiration is autonomous execution "every time you must type 'continue' is a failure of the harness."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The problem they solve.&lt;/strong&gt; Complex tasks exceed a single context window. The model stops before the work is done. Without a continuation mechanism, the human manually restarts, re-explains context, and loses progress.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why they fall short.&lt;/strong&gt; The loop optimizes &lt;strong&gt;duration of generation.&lt;/strong&gt; The problem being solved is "the agent stopped too early." The solution is to force it to keep going. What's not being checked: whether what was generated in the last iteration was correct before starting the next one. If iteration three introduced a subtle architectural mistake, iterations four through twenty build on that mistake. A long-horizon execution loop without an independent verification gate between iterations is a mechanism for generating more wrong output, faster, with more confidence.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;They're a workaround for.&lt;/strong&gt; the missing &lt;strong&gt;Engine&lt;/strong&gt; (no specification to converge toward) and the missing &lt;strong&gt;Control Unit&lt;/strong&gt; (no gate between iterations). With a specification, generation converges toward a declared target instead of running indefinitely. With a verification gate, each iteration is checked before the next one builds on it. Without both, the loop is "generate more and hope it improves."&lt;/p&gt;

&lt;h2&gt;
  
  
  Prompt Optimization and Agent Memory
&lt;/h2&gt;

&lt;p&gt;Improving model output by improving the prompt: system prompts, few-shot examples, chain-of-thought templates, memory files that persist across sessions, AGENTS.md files that encode project-specific conventions. The goal is to give the model enough context to produce better output without human intervention.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The problem they solve.&lt;/strong&gt; Models are stateless. Without memory and carefully constructed prompts, each interaction starts from zero. Good prompts produce better output. Project-specific memory reduces the learning curve per session.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why they fall short.&lt;/strong&gt; A better prompt produces a better guess. It doesn't produce a verified output. "Generate code that follows our coding conventions" is a prompt. "All public functions must have error handling and all errors must be wrapped with context" is a linter rule. The prompt requests. The rule enforces. The prompt might be followed. The rule is always checked.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;They're a workaround for.&lt;/strong&gt; the missing &lt;strong&gt;Engine&lt;/strong&gt; again. Prompts and memory files are proto-specifications. They have intent in them, but they're ephemeral, ambiguous, and model-interpreted. A specification is durable, precise, versioned, and mechanically checkable. Every prompt-engineering improvement is a better version of a thing that shouldn't be a prompt. It should be a declaration machines enforce.&lt;/p&gt;

&lt;h2&gt;
  
  
  Friday Slop Cleanup
&lt;/h2&gt;

&lt;p&gt;OpenAI's Ryan Lopopolo described running a billion tokens a day through a fully agent-generated codebase and spending every Friday with 20% of engineering time is spent in manually cleaning up accumulated "AI slop." The team later tried to automate this cleanup with more agents.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The problem it solves.&lt;/strong&gt; Agent-generated code accumulates. Not all of it belongs. Duplication, unnecessary abstractions, dead code, stylistic inconsistency. The codebase grows without any force pushing the other direction. Someone has to clean it up.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it falls short.&lt;/strong&gt; Manual subtraction one day a week is a human performing a function the architecture should perform continuously. The slop was generated on Monday through Thursday. It accumulated all week. On Friday, a human manually reverses the accumulation. Next week, it accumulates again. The cleanup doesn't prevent the accumulation. It reacts to it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It's a workaround for.&lt;/strong&gt; the missing &lt;strong&gt;Casing's subtraction discipline.&lt;/strong&gt; If the architecture included a force that asks "does this belong?" on every generation. Measured against a declared essence of the product, the slop wouldn't accumulate. The subtraction would happen continuously, at generation time, not manually, once a week, after the damage is done.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pattern underneath
&lt;/h2&gt;

&lt;p&gt;Every approach in the current landscape has the same structure:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Real problem&lt;/th&gt;
&lt;th&gt;Function it performs manually&lt;/th&gt;
&lt;th&gt;Missing element&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Agent Skills&lt;/td&gt;
&lt;td&gt;Models lack domain expertise&lt;/td&gt;
&lt;td&gt;Procedural investigation&lt;/td&gt;
&lt;td&gt;Engine (declared invariants)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Context Engineering&lt;/td&gt;
&lt;td&gt;Model degrades with bad context&lt;/td&gt;
&lt;td&gt;Manual boundary management&lt;/td&gt;
&lt;td&gt;Casing (enforced boundaries)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Self-Verification&lt;/td&gt;
&lt;td&gt;Unreviewed code ships defects&lt;/td&gt;
&lt;td&gt;Quality opinion from a model&lt;/td&gt;
&lt;td&gt;Control Unit (deterministic oracle)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Loop Engineering&lt;/td&gt;
&lt;td&gt;Tasks exceed one context window&lt;/td&gt;
&lt;td&gt;Duration-based continuation&lt;/td&gt;
&lt;td&gt;Engine + Control (spec + gate)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Prompt Optimization&lt;/td&gt;
&lt;td&gt;Stateless models need context&lt;/td&gt;
&lt;td&gt;Intent injection per session&lt;/td&gt;
&lt;td&gt;Engine (durable specification)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Friday Slop Cleanup&lt;/td&gt;
&lt;td&gt;Code accumulates without curation&lt;/td&gt;
&lt;td&gt;Manual weekly subtraction&lt;/td&gt;
&lt;td&gt;Casing (subtraction discipline)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The pattern: &lt;strong&gt;a human or a model manually performs a function that a missing architectural element would perform mechanically.&lt;/strong&gt; Each workaround works partially, temporarily, expensively. Each one will be replaced by the element it's substituting for.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the workarounds don't survive
&lt;/h2&gt;

&lt;p&gt;Three forces are converging on the workarounds simultaneously:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;They don't scale with generation speed.&lt;/strong&gt; Workarounds that depend on human intervention (Friday cleanup, prompt tuning, manual context management, human review of agent output) hit the same wall the Osmani data documents: 4x code for 10% more value, reviews up 441%, 31% merged unread. Generation runs at machine speed. Workarounds run at human speed. The gap widens with every model improvement.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;They cost more as models improve.&lt;/strong&gt; A more powerful model generates more output per token. More output means more to review, more to clean up, more to verify, more context to manage. Every workaround's cost scales with the model's capability. Agentic bloat of ten thousand lines to solve a ten-line problem is a direct consequence of a powerful tool with no subtraction discipline. The inference bill is the monthly invoice for missing elements.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;They have a proven mathematical ceiling.&lt;/strong&gt; &lt;a href="https://www.nist.gov/news-events/news/2026/06/nist-mathematical-proof-supports-transition-continuous-monitor-and-update" rel="noopener noreferrer"&gt;Vassilev's NIST proof (June 2026)&lt;/a&gt; extended Gödel's incompleteness theorems to AI systems: no finite set of guardrails is universally robust. Self-verification, prompt engineering, reviewer agents. Every approach that wraps the model in a finite set of rules has a formal ceiling. The ceiling can be raised. It cannot be removed. The workarounds are not converging toward a complete solution. They are asymptotically approaching a limit.&lt;/p&gt;

&lt;h2&gt;
  
  
  What aerospace figured out
&lt;/h2&gt;

&lt;p&gt;NASA's Space Shuttle software flew for 30 years with a near-zero defect rate. Because the architecture was complete. Every function had the right element at the right level:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Assertions&lt;/strong&gt; at code level. Mechanical checks that trigger System Exceptions on failure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Safety Properties&lt;/strong&gt; at design level. "Shall not" and "must always" rules enforced as invariants.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Interface Specifications&lt;/strong&gt; between modules. ICDs with self-checking logic that rejects violations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Invariants&lt;/strong&gt; at system level. Boundary conditions that trigger immediate response on breach.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No workarounds. No Friday cleanup. No prompt tuning. No "several rounds" of calibrating the evaluator to stop praising mediocre work. Each element was mechanical, deterministic, and enforced. The workarounds were unnecessary because the elements were present.&lt;/p&gt;

&lt;p&gt;The current agentic AI landscape is a collection of brilliant engineering teams building elaborate workarounds for elements that aerospace identified and supplied decades ago. The workarounds will be replaced. The subject of this book is what replaces them, why it works, and how to build it before the workarounds' costs exceed the value they protect.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This chapter surveys the landscape as of June 2026. The approaches described are drawn from published sources: &lt;a href="https://blog.langchain.dev/the-anatomy-of-an-agent-harness/" rel="noopener noreferrer"&gt;LangChain&lt;/a&gt;, &lt;a href="https://openai.com/index/harness-engineering/" rel="noopener noreferrer"&gt;OpenAI&lt;/a&gt;, &lt;a href="https://engineering.fb.com/" rel="noopener noreferrer"&gt;Meta&lt;/a&gt;, &lt;a href="https://www.anthropic.com/engineering/harness-design-long-running-apps" rel="noopener noreferrer"&gt;Anthropic&lt;/a&gt;. The five-element framework referenced throughout: Altshuller's Law of System Completeness (TRIZ), independently confirmed by Beer's Viable System Model and Nalebuff/Brandenburger's Co-opetition.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>softwaredevelopment</category>
      <category>architecture</category>
      <category>engineering</category>
    </item>
    <item>
      <title>Generation is Not Solved: What Google's New SDLC Paper Gets Wrong About AI-Assisted Development</title>
      <dc:creator>Bala Paranj</dc:creator>
      <pubDate>Mon, 26 Oct 2026 12:47:25 +0000</pubDate>
      <link>https://dev.to/bala_paranj_059d338e44e7e/generation-is-not-solved-what-googles-new-sdlc-paper-gets-wrong-about-ai-assisted-development-3gpp</link>
      <guid>https://dev.to/bala_paranj_059d338e44e7e/generation-is-not-solved-what-googles-new-sdlc-paper-gets-wrong-about-ai-assisted-development-3gpp</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;✓ Human-authored analysis; AI used for formatting and proofreading.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Google published &lt;em&gt;The New SDLC With Vibe Coding&lt;/em&gt; in May 2026, a 51-page paper positioning agentic engineering as the mature successor to ad-hoc AI-assisted development. &lt;/p&gt;

&lt;p&gt;It is full of errors. Claims that sound reasonable until you hold them against the formal traditions that govern whether software works.&lt;/p&gt;

&lt;p&gt;This article examines twelve specific misconceptions in the paper and offers the grounded alternative for each. The places where it's wrong are the places where teams will make expensive decisions.&lt;/p&gt;




&lt;h2&gt;
  
  
  Generation is solved.
&lt;/h2&gt;

&lt;p&gt;The paper's closing line: &lt;em&gt;"Generation is solved. Verification, judgment, and direction are the new craft."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Generating code that compiles is solved. Generating code that passes a test suite is mostly solved. Generating code that &lt;em&gt;is correct&lt;/em&gt; meaning it satisfies a complete specification of all required behaviors, including behaviors the test suite doesn't cover is not solved. Conflating these three things is a category error.&lt;/p&gt;

&lt;p&gt;Dijkstra spent a career distinguishing between "testing shows the presence of bugs, never the absence of bugs." That distinction is the central problem of software verification. It applies with greater force to AI-generated code because AI models produce plausible-looking output that passes shallow checks while harboring semantic errors that only manifest at system boundaries.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The correction:&lt;/strong&gt; Generation is automated. It is not solved. Production outages, security vulnerabilities, and data corruption live in the gap between "compiles and passes tests" and "correct under all specified conditions". Any engineering process that treats generation as solved will systematically undercount that gap.&lt;/p&gt;




&lt;h2&gt;
  
  
  Context engineering is a new discipline.
&lt;/h2&gt;

&lt;p&gt;The paper presents context engineering as deciding what information an AI agent needs to do its job as an emergent practice driven by AI tooling. It identifies six types of context: instructions, knowledge, memory, examples, tools, and guardrails.&lt;/p&gt;

&lt;p&gt;This is Parnas (1972) in a new hat.&lt;/p&gt;

&lt;p&gt;Parnas's "On the Criteria To Be Used in Decomposing Systems into Modules" is about one thing: what information does a module need, and what information should be hidden from it? His information hiding principle is the design decision is choosing what each module &lt;em&gt;doesn't&lt;/em&gt; know. It maps directly onto the paper's static/dynamic context split. The six context types the paper lists are preconditions, postconditions, invariants, and interface contracts, reorganized into a taxonomy that obscures their origin.&lt;/p&gt;

&lt;p&gt;The paper even rediscovers the core trade-off Parnas identified: "Too much static context wastes tokens and dilutes signals. Too little means the agent forgets critical rules." Substitute "too much coupling increases complexity" and "too little information causes incorrect behavior" and you have the original formulation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The correction:&lt;/strong&gt; Context engineering is specification engineering applied to AI agents. The medium changed (natural language prompts instead of formal interface definitions), but the principles are identical. Teams that treat context engineering as a novel skill without studying its theoretical foundations will rediscover every mistake the specification community already catalogued.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Tests + evals is a sufficient verification model.
&lt;/h2&gt;

&lt;p&gt;The paper's verification rests on two mechanisms: tests verify deterministic properties, evals (scored by datasets, rubrics, and LM judges) verify non-deterministic properties.&lt;/p&gt;

&lt;p&gt;Absent from the paper's framework:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Formal verification&lt;/strong&gt; — proving that a property holds for all possible inputs, not just sampled ones&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Model checking&lt;/strong&gt; — exhaustive exploration of state spaces to find violations of temporal properties&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Static analysis&lt;/strong&gt; — detecting classes of bugs (null dereference, integer overflow, use-after-free) without executing code&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Type systems as proof systems&lt;/strong&gt; — using the compiler to enforce invariants at the language level&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Property-based testing&lt;/strong&gt; — generating random inputs guided by algebraic properties, mentioned once in passing and never integrated into the framework&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Abstract interpretation&lt;/strong&gt; — computing sound approximations of program behavior&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The paper claims the difference between vibe coding and agentic engineering is "how outputs get verified." But its own verification toolkit can only sample behaviors. It cannot prove properties. A system verified only by tests and evals has no guarantees about behaviors outside the test distribution. This is where the catastrophic failures live.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The correction:&lt;/strong&gt; A production verification framework needs at least three layers: deterministic tests for known behaviors, property-based tests for algebraic invariants, and static analysis for structural properties. For security-critical paths, formal verification of specific properties (e.g., "this function never reads outside the bounds of its input") is not optional. Evals scored by LM judges add a fourth layer for subjective quality, but they are the weakest layer, not a replacement for the first three.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Factory Model is the right mental model.
&lt;/h2&gt;

&lt;p&gt;The paper proposes the "factory model" as its central organizing metaphor: the developer's primary output is not code but "the system that produces code" such as specifications, agents, tests, feedback loops, and guardrails.&lt;/p&gt;

&lt;p&gt;This metaphor has been critiqued and rejected for 40 years.&lt;/p&gt;

&lt;p&gt;Brooks's &lt;em&gt;No Silver Bullet&lt;/em&gt; (1986) argues that software development is different from manufacturing because each unit of software is unique. Manufacturing produces identical copies of a designed artifact. Software development is the design process. There is no separate production step. The factory metaphor misleads because it implies that once the assembly line is configured correctly, output is reliable and repeatable. Software does not work this way. Each prompt produces different output. Each context window yields different behavior. The factory has no quality floor.&lt;/p&gt;

&lt;p&gt;What the paper describes, specifying properties, constraining agent behavior, verifying outputs against specifications is closer to Lamport's framework for specifying concurrent systems. In Lamport's model, you specify &lt;em&gt;what the system should do&lt;/em&gt; (the safety and liveness properties), not &lt;em&gt;how to build it&lt;/em&gt;. The specification is the primary artifact. Implementation is a refinement of the specification. Verification proves the refinement preserves the properties.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The correction:&lt;/strong&gt; Replace the factory metaphor with the specification-refinement model. The developer's primary output is the &lt;em&gt;specification&lt;/em&gt;. The precise description of what the system must do and must not do. AI agents perform refinement (translating specification into implementation). Verification proves the refinement is correct. This model is rigorous, has formal foundations, and doesn't mislead teams into thinking they're running a production line.&lt;/p&gt;




&lt;h2&gt;
  
  
  The model is not the engine.
&lt;/h2&gt;

&lt;p&gt;The paper's equation is "Agent = Model + Harness." The harness is "the car, the road, and the traffic laws." The model is "the engine." Figure 7 labels the model at ~10% and the harness at ~90%.&lt;/p&gt;

&lt;p&gt;The metaphor is wrong, and it leads to the wrong investments.&lt;/p&gt;

&lt;p&gt;An engine has a defining property: it converts energy into &lt;em&gt;reliable, predictable work&lt;/em&gt;. A combustion engine produces torque proportional to fuel input. You can characterize its output curve, its failure modes, its efficiency envelope. It degrades gracefully under load. It does the same thing every time you turn the key.&lt;/p&gt;

&lt;p&gt;An LLM does none of this. Given identical inputs, it produces different outputs (temperature &amp;gt; 0). Its failure modes are not characterizable in advance. You cannot predict which prompts will produce hallucinations. It does not degrade gracefully; it fails silently by producing plausible-looking but incorrect output. Calling it an engine smuggles in connotations of reliability and predictability that the component does not possess.&lt;/p&gt;

&lt;p&gt;In TRIZ's Law of System Completeness, a framework for analyzing whether a technical system has all necessary components. A complete system requires an Engine, Transmission, Tool, Control Unit, and Energy Source. Applied to an AI agent system, the components map differently than the paper assumes. The &lt;em&gt;Tool&lt;/em&gt; is the model. It's the thing that acts on the workpiece (the codebase). The &lt;em&gt;Engine&lt;/em&gt; is the element that drives the system forward is the specification-verification-learning loop. The &lt;em&gt;Transmission&lt;/em&gt; is the harness, which translates the engine's output into the tool's motion. The &lt;em&gt;Control Unit&lt;/em&gt; is the human exercising judgment over whether the system's output satisfies the specification.&lt;/p&gt;

&lt;p&gt;This distinction matters. If the model is the engine, you improve the system by upgrading the model. If the model is the tool, you improve the system by improving the &lt;em&gt;loop&lt;/em&gt; with tighter specifications, faster verification, better learning from failures. The paper's own Terminal Bench data (harness-only changes moved an agent from outside Top 30 to Top 5) proves the tool framing. They cited the evidence for the right conclusion and drew the wrong one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The correction:&lt;/strong&gt; The model is a stochastic tool. The engine of an AI-assisted development system is the specification-verification-learning loop: specify what correct means, generate a candidate, verify it against the specification, learn from the failures, revise the specification. Every investment decision should optimize the loop, not the tool. A better model inside a broken loop produces faster garbage. A weaker model inside a tight loop converges on correct output.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. The new SDLC is waterfall in disguise. Learning is absent.
&lt;/h2&gt;

&lt;p&gt;The paper's AI-driven SDLC (Figure 5) prescribes: Requirements → Design → Implementation (agents) → Testing/Eval → Review &amp;amp; Deploy → Maintenance. The recommendations reinforce the sequence: write specs and architecture docs upfront, configure the harness, define eval criteria, &lt;em&gt;then&lt;/em&gt; let agents run.&lt;/p&gt;

&lt;p&gt;This is waterfall with a faster implementation phase.&lt;/p&gt;

&lt;p&gt;The point of the Agile movement, whatever you think of its current institutional form was one empirical insight: &lt;em&gt;you cannot fully specify a system before building it&lt;/em&gt;. Requirements emerge from building. The user doesn't know what they want until they see what they don't want. The developer doesn't understand the problem until they've tried to solve it and failed.&lt;/p&gt;

&lt;p&gt;The paper eliminates the primary mechanism by which this learning happens.&lt;/p&gt;

&lt;p&gt;In traditional development, the developer writes code and learns from the act of writing. The resistance of the material teaches them about the problem's structure. They discover edge cases by hitting them. They revise their mental model of the requirements in real time. The feedback loop between "what I think the system should do" and "what the system does" is tight and continuous.&lt;/p&gt;

&lt;p&gt;In the paper's orchestrator model explicitly positioned as the more advanced mode, the developer writes a specification, hands it to agents, and reviews output asynchronously. The human is removed from the implementation loop. But most learning about requirements happens during implementation. When the agent runs autonomously in the background and produces a pull request, the human reviews the &lt;em&gt;output&lt;/em&gt; but never observes the &lt;em&gt;intermediate states&lt;/em&gt; that would have taught them about the problem.&lt;/p&gt;

&lt;p&gt;The paper even identifies "specification quality is the new bottleneck" without recognizing the contradiction: if writing good specs requires understanding the problem deeply, and understanding the problem deeply requires building and failing and learning, then removing humans from the building phase degrades the specs that the paper positions as the foundation of everything.&lt;/p&gt;

&lt;p&gt;This shows up in two failure modes:&lt;/p&gt;

&lt;p&gt;First, &lt;strong&gt;spec drift without revision.&lt;/strong&gt; The paper recommends writing architecture docs and AGENTS.md files upfront, then running agents against them. But specifications written before implementation reflect pre-implementation understanding. If the human never touches the implementation, they never learn what's wrong with their specification. The spec becomes a stale document that agents faithfully implement, correctly satisfying the wrong requirements.&lt;/p&gt;

&lt;p&gt;Second, &lt;strong&gt;the METR tax compounds.&lt;/strong&gt; The paper's own data shows experienced developers take 19% longer with AI assistance. Part of that tax is the cost of understanding code you didn't write. But part of it is also the cost of discovering, &lt;em&gt;at review time&lt;/em&gt;, requirements you didn't know you had. Things you would have discovered earlier if you'd been in the implementation loop. Deferring that discovery to review time is more expensive than discovering it during implementation, for the same reason that bugs found in production cost more to fix than bugs found during development.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The correction:&lt;/strong&gt; The SDLC needs a &lt;em&gt;learning loop&lt;/em&gt;, not just a feedback loop. The paper's feedback loop routes test failures back to agents for correction. The agent learns and retries. But the &lt;em&gt;human&lt;/em&gt; doesn't learn. A learning loop routes surprises such as unexpected behaviors, discovered requirements, spec violations back to the &lt;em&gt;human&lt;/em&gt; to revise the specification. This means: build small, verify early, revise the spec based on what you learned, then build more. It means the specification is a living document that co-evolves with the implementation, not an upfront artifact that agents execute against. It means the human must remain in the loop during implementation, not just before and after.&lt;/p&gt;

&lt;p&gt;The conductor mode (real-time, in the IDE) preserves this learning loop. The orchestrator mode (async, background agents) breaks it. The paper positions orchestrator mode as the more advanced practice. It's the more dangerous one. It's only safe for tasks where the specification is already known to be complete which is a small subset of real work.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. The 80% problem is a workflow problem.
&lt;/h2&gt;

&lt;p&gt;The paper observes that AI agents can generate approximately 80% of a feature's code, but the remaining 20% such as edge cases, error handling, integration points requires deep contextual knowledge. The paper treats this as an empirical observation about current model limitations and prescribes focusing human attention on "what AI struggles with."&lt;/p&gt;

&lt;p&gt;The diagnosis is wrong. This is not a workflow problem. It's a specification completeness problem.&lt;/p&gt;

&lt;p&gt;The gap between the 80% and the 100% is not between "easy code" and "hard code." It is between &lt;em&gt;what is specified&lt;/em&gt; and &lt;em&gt;what is assumed&lt;/em&gt;. Parnas called these the "unstated requirements" — the behaviors the system must exhibit that nobody wrote down because they seemed obvious to the humans who understood the domain.&lt;/p&gt;

&lt;p&gt;AI models cannot infer unstated requirements. They generate code that satisfies the stated specification (the prompt) and fills in the rest with statistical patterns from training data. The 20% gap is the set of requirements that exist in the developer's head but not in the prompt.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The correction:&lt;/strong&gt; The 80% problem is a specification problem. The fix is not "focus human attention on the hard parts." The fix is: make the specification complete enough that correctness is mechanically verifiable. This means writing preconditions, postconditions, and invariants for every interface — not as documentation, but as executable checks. Property-based testing (e.g., with Hypothesis, Rapid, or gopter) is the practical tool for discovering unstated requirements before they become production bugs.&lt;/p&gt;




&lt;h2&gt;
  
  
  8. Intent as the new Interface is pure progress.
&lt;/h2&gt;

&lt;p&gt;The paper's thesis — developers now express what they want rather than how to build it is presented as an unambiguous advance. The conclusion is titled "Intent as the new Interface."&lt;/p&gt;

&lt;p&gt;This framing ignores a critical regression: natural language is a &lt;em&gt;lossy and ambiguous&lt;/em&gt; specification medium.&lt;/p&gt;

&lt;p&gt;TLA+, Alloy, Z, VDM, and even CEL predicates have been "intent as interface" for decades. They let you say &lt;em&gt;what&lt;/em&gt; the system should do without saying &lt;em&gt;how&lt;/em&gt;. The difference is that these languages are &lt;em&gt;formal&lt;/em&gt; — they have precise semantics, they're mechanically checkable, and they eliminate ambiguity by construction.&lt;/p&gt;

&lt;p&gt;Natural language does none of this. "Build a secure API" is ambiguous in ways that a TLA+ specification of the API's safety properties is not. The move from formal specifications to natural language prompts trades precision for accessibility. That trade-off may be worth making for some use cases, but presenting it as pure progress without acknowledging the precision cost is misleading.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The correction:&lt;/strong&gt; Natural language intent is a useful starting point, not a finished specification. A disciplined workflow starts with natural language intent, refines it into testable properties (property-based test generators, CEL predicates, formal invariants), and uses the testable properties as the contract with the AI agent. The natural language prompt is the sketch. The executable specification is the blueprint.&lt;/p&gt;




&lt;h2&gt;
  
  
  9. The METR finding is a footnote, not the headline.
&lt;/h2&gt;

&lt;p&gt;The paper mentions in one paragraph that a METR study found experienced developers took 19% &lt;em&gt;longer&lt;/em&gt; when using AI assistants on certain tasks, primarily because of time spent verifying, debugging, and correcting AI output. The paper then moves on.&lt;/p&gt;

&lt;p&gt;This is the most important data point in the entire paper, and it's treated as a caveat.&lt;/p&gt;

&lt;p&gt;For experienced practitioners working on complex, unfamiliar, or correctness-critical tasks, current AI tooling may impose a net tax rather than providing a net benefit. The verification burden — reading AI-generated code you didn't write, understanding its assumptions, checking its correctness can exceed the time saved by not writing the code yourself.&lt;/p&gt;

&lt;p&gt;This finding is consistent with what the theoretical literature predicts: the cost of understanding a system scales with the system's complexity, not with who wrote it. AI doesn't reduce the complexity of the software; it shifts the work from writing to reading. For systems where understanding is the bottleneck (legacy codebases, security-critical paths, concurrent systems), this shift may not be favorable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The correction:&lt;/strong&gt; Teams should measure &lt;em&gt;total task completion time&lt;/em&gt; including verification, not just &lt;em&gt;code generation time&lt;/em&gt;. For correctness-critical work, the right metric is "time to verified-correct implementation," not "time to first output." The 25-39% productivity gains cited in the paper likely measure the wrong thing — they measure generation speed, not delivery speed. Until the industry converges on measuring delivery of verified-correct code, productivity claims from AI tooling are suspect.&lt;/p&gt;




&lt;h2&gt;
  
  
  10. The harness model (Agent = Model + Harness) lacks a correctness owner.
&lt;/h2&gt;

&lt;p&gt;The paper defines "Agent = Model + Harness" and describes harness components: instructions, tools, sandboxes, orchestration, guardrails, observability. It claims that ~90% of agent behavior is determined by the harness, not the model.&lt;/p&gt;

&lt;p&gt;The claim may be directionally correct, but the model is missing a critical component: &lt;em&gt;who owns correctness?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The conductor/orchestrator framework defines developer roles around workflow mode — real-time direction versus async delegation. Neither role, as defined, owns the system's &lt;em&gt;properties&lt;/em&gt;. In Lamport's framework, someone must own:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;safety properties&lt;/strong&gt; — "the system never does X" (never exposes credentials, never processes a malformed input, never violates an invariant)&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;liveness properties&lt;/strong&gt; — "the system eventually does Y" (every request gets a response, every failure triggers recovery, every finding gets reported)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Neither conductor (focused on keystroke-level direction) nor orchestrator (focused on task delegation) is responsible for specifying, verifying, and maintaining these properties. The result is that system invariants become emergent properties of the agent's behavior rather than designed properties that the harness enforces.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The correction:&lt;/strong&gt; Add a third role: the &lt;em&gt;specifier&lt;/em&gt;. The specifier owns the system's safety and liveness properties. They write the invariants that the harness must enforce and the properties that verification must prove. The specifier reviews not individual code changes but the system's property set: "After this change, do all invariants still hold?" This role maps to what Stave calls the Judge. The component that evaluates whether the system's output satisfies the specification, independent of how the output was produced.&lt;/p&gt;




&lt;h2&gt;
  
  
  11. Compound failure modes are absent.
&lt;/h2&gt;

&lt;p&gt;The paper discusses each SDLC phase independently and evaluates AI output per-task. Its verification framework (test individual outputs, eval individual trajectories) operates at the single-agent, single-task level.&lt;/p&gt;

&lt;p&gt;Production systems fail at boundaries. The most dangerous bugs are in the &lt;em&gt;interactions&lt;/em&gt; between components where individually-correct pieces compose into incorrect systems.&lt;/p&gt;

&lt;p&gt;This is the graph problem. A function that correctly validates input and a function that correctly processes validated input can still compose into a system that processes unvalidated input, if the edge between them (the call site, the data flow, the trust boundary) is missing or incorrect. AI agents generating code module-by-module have no mechanism to verify that the edges compose correctly.&lt;/p&gt;

&lt;p&gt;The paper never addresses compositional correctness. There is no discussion of interface contracts between AI-generated modules, no discussion of how to verify that independently-generated components compose into a system that satisfies global properties, and no discussion of the well-known theoretical results (Hoare logic, rely-guarantee reasoning, separation logic) that govern compositional verification.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The correction:&lt;/strong&gt; Verification must operate at three levels: unit (does this function satisfy its contract?), compositional (do these modules compose correctly across their interfaces?), and system (does the assembled system satisfy its global properties?). AI-generated code requires &lt;em&gt;more&lt;/em&gt; attention to compositional verification, because the agent generating module A has no awareness of the assumptions module B makes about A's behavior. Architectural enforcement tools (dependency guards, interface lint, contract-based design) are not optional when AI is generating the components.&lt;/p&gt;




&lt;h2&gt;
  
  
  12. The economics model ignores escape cost.
&lt;/h2&gt;

&lt;p&gt;The paper's CapEx/OpEx analysis compares vibe coding (low CapEx, high OpEx from token burn and maintenance) with agentic engineering (high CapEx from upfront specification, low OpEx from structured output). The crossover point where agentic engineering becomes cheaper is presented as the key decision point.&lt;/p&gt;

&lt;p&gt;The model is incomplete because it doesn't account for &lt;em&gt;escape cost&lt;/em&gt;. The cost of bugs that pass both tests and evals and reach production.&lt;/p&gt;

&lt;p&gt;For security-critical systems (cloud infrastructure, financial services, healthcare, compliance), a single undetected vulnerability in AI-generated code can exceed the entire cost savings from AI-assisted development. The TCO model needs a third term: risk-adjusted cost of escapes, weighted by the probability that AI-generated code contains defects that evade the verification framework, multiplied by the cost of those defects in production.&lt;/p&gt;

&lt;p&gt;The paper's verification framework (tests + evals) has a known escape rate. No test suite has 100% fault detection. AI-generated code may have a &lt;em&gt;higher&lt;/em&gt; escape rate than human-written code because AI errors are more likely to be semantically plausible (they look right) and less likely to trigger the pattern-matching heuristics that experienced reviewers use to catch bugs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The correction:&lt;/strong&gt; Add escape cost to the TCO model: &lt;code&gt;TCO = CapEx + OpEx + (P(escape) × Cost(escape))&lt;/code&gt;. For systems where &lt;code&gt;Cost(escape)&lt;/code&gt; is high (security, compliance, financial), the investment required to drive &lt;code&gt;P(escape)&lt;/code&gt; acceptably low may dominate both CapEx and OpEx. This is where formal verification, static analysis, and defense-in-depth verification strategies pay for themselves by reducing the expected cost of undetected defects.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Underlying Problem
&lt;/h2&gt;

&lt;p&gt;These twelve issues share a root cause: the paper treats workflow advice as engineering principles without grounding either in the formal traditions that govern whether software works.&lt;/p&gt;

&lt;p&gt;The paper never cites Dijkstra, Parnas, Lamport, Hoare, Liskov, or Brooks. It never references TLA+, Alloy, separation logic, or rely-guarantee reasoning. It never distinguishes between testing (sampling behaviors) and verification (proving properties). It never acknowledges that natural language specifications are lossy.&lt;/p&gt;

&lt;p&gt;This matters because the paper will be read by engineering leaders making investment decisions. Those decisions such as how much to invest in verification infrastructure, whether to adopt formal specification practices, how to measure AI-assisted development productivity will be worse if the conceptual foundations are wrong.&lt;/p&gt;

&lt;p&gt;The tools are changing. The principles are not. AI agents are a new implementation medium, not a new theory of correctness. The teams that build reliable systems with AI assistance will be the ones that apply the existing theory such as specification, verification, compositional reasoning to the new medium.&lt;/p&gt;

&lt;p&gt;Generation is automated. Correctness is still the hard problem. &lt;/p&gt;

</description>
      <category>ai</category>
      <category>softwaredevelopment</category>
      <category>security</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Stave and the AWS Compliance Mod: why we ship both</title>
      <dc:creator>Bala Paranj</dc:creator>
      <pubDate>Sun, 25 Oct 2026 11:29:53 +0000</pubDate>
      <link>https://dev.to/bala_paranj_059d338e44e7e/stave-and-the-aws-compliance-mod-why-we-ship-both-5kp</link>
      <guid>https://dev.to/bala_paranj_059d338e44e7e/stave-and-the-aws-compliance-mod-why-we-ship-both-5kp</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;✓ Human-authored analysis; AI used for formatting and proofreading.&lt;/p&gt;

&lt;p&gt;Per-resource framework checks would have passed; the breach happened because of resource composition. Stave's job is the composition. The chains and cross-resource invariants that per-resource scanners can't see by construction. Run both.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The Capital One incident in 2019 exfiltrated 100 million customer records. The path was specific: an EC2 instance with IMDSv1 enabled, accessible via SSRF from the public-facing application, running with an IAM role that had &lt;code&gt;s3:GetObject&lt;/code&gt; on a bucket holding the credit card data. Each individual setting was reviewable; each individual setting could have been "fixed" without anyone realizing the architecture composed into an exfiltration path.&lt;/p&gt;

&lt;p&gt;If you had run &lt;a href="https://hub.powerpipe.io/mods/turbot/aws_compliance" rel="noopener noreferrer"&gt;&lt;code&gt;turbot/steampipe-mod-aws-compliance&lt;/code&gt;&lt;/a&gt; against that account the morning of the breach, &lt;strong&gt;every CIS check would have passed.&lt;/strong&gt; The IAM user had MFA, the bucket had encryption, the security group had documented inbound rules, the trail was logging. The composition  failed. Per-resource scanners check the rows; the breach is in the join.&lt;/p&gt;

&lt;p&gt;AWS Compliance Mod job is framework coverage of single-resource properties, and it does that job extremely well. Stave's job is the compositional layer above. Different tool, different question.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the AWS Compliance Mod is good at
&lt;/h2&gt;

&lt;p&gt;540+ controls across 16+ frameworks. CIS AWS Benchmark v1.4/v3.0/v4.0, PCI-DSS v3.2.1/v4.0, HIPAA Security Rule, NIST 800-53 Rev 5, SOC 2, FedRAMP Moderate, GxP, AWS Foundational Security Best Practices, more. Each control is a SQL check against &lt;a href="https://steampipe.io" rel="noopener noreferrer"&gt;Steampipe&lt;/a&gt; tables that wrap live AWS APIs. Mature ecosystem. Wide adoption.&lt;/p&gt;

&lt;p&gt;If your question is &lt;strong&gt;"am I CIS-compliant right now?"&lt;/strong&gt;, install it. The framework-cited dashboards render exactly the evidence pack auditors expect. The per-resource property checks are fast, debuggable, deterministic. The contributor velocity is real.&lt;/p&gt;

&lt;p&gt;For the atomic-controls slice, "is encryption enabled," "is MFA on," "is logging configured", the AWS Compliance Mod's framework breadth is its strength. Stave's catalog has those controls too (~86% of the Stave catalog is atomic), but Stave doesn't compete on framework coverage of the atomic axis. That territory is theirs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Stave's territory starts
&lt;/h2&gt;

&lt;p&gt;The 14% of Stave's catalog that's compound which is currently 4,755 controls and 770 chains, growing is the part that catches what per-resource scanners can't see by construction. Three classes:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Compound risk chains.&lt;/strong&gt; A set of controls that, when they co-fail, create more risk than the sum of individual findings. A public-read S3 bucket plus access logging disabled is an exfiltration path with no audit trail. The chain engine fires when the composition's escalation threshold is met; the compound finding carries its own severity that may exceed any member.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ghost references.&lt;/strong&gt; Dangling cross-resource pointers such as a Cognito user pool referencing a deleted Lambda trigger, a CloudFront distribution whose S3 origin no longer exists, an ECS task naming a removed IAM role. The reference exists in one asset's configuration; the absence is in another asset's inventory. Per-resource scanners that don't model cross-resource invariants miss every one of these. Stave's catalog has ~99 ghost-reference controls today across APIGateway, CloudFront, Cognito, DynamoDB, ECS, EKS, ELB, EventBridge, Secrets Manager, SNS, SQS.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Snapshot-anchored reproducibility.&lt;/strong&gt; Re-running the same &lt;code&gt;obs.v0.1&lt;/code&gt; snapshot produces byte-identical output. Two weeks later, a different engineer points the same snapshot at Stave and gets the same verdict. This is useful for audit timelines, regression bisects, "what did we know at time T" questions. Live-API tools can't offer this.&lt;/p&gt;




&lt;h2&gt;
  
  
  Side-by-side: the same intent, two tools
&lt;/h2&gt;

&lt;p&gt;Both detecting "cross-account role assumption without an external ID."&lt;/p&gt;

&lt;h3&gt;
  
  
  AWS Compliance Mod — per-resource SQL check
&lt;/h3&gt;

&lt;p&gt;The shape (representative, paraphrased from the live mod's IAM section):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- One row per IAM role; check the role's own trust policy.&lt;/span&gt;
&lt;span class="k"&gt;select&lt;/span&gt;
  &lt;span class="n"&gt;role_arn&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;case&lt;/span&gt;
    &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="n"&gt;trust_requires_external_id&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt; &lt;span class="s1"&gt;'ok'&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="s1"&gt;'alarm'&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;case&lt;/span&gt;
    &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="n"&gt;trust_requires_external_id&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt; &lt;span class="n"&gt;role_name&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;' requires ExternalId.'&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;role_name&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;' allows cross-account assume without ExternalId.'&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;reason&lt;/span&gt;
&lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;aws_iam_role&lt;/span&gt;
&lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;assume_role_policy_document_has_cross_account_principal&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Per-role, status, reason, rendered in a CIS-cited benchmark dashboard. The check is per-role and SQL-shaped; the "is this role cross-account?" question is answered from the role's own trust policy.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stave — compound control reasoning over an IAM-graph computation
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;CTL.IAM.IDENTITY.BLASTRADIUS.002&lt;/code&gt; — abridged:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;CTL.IAM.IDENTITY.BLASTRADIUS.002&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Cross-Account Role Must Require External ID&lt;/span&gt;
&lt;span class="na"&gt;severity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;critical&lt;/span&gt;
&lt;span class="na"&gt;scope&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;compound&lt;/span&gt;
&lt;span class="na"&gt;corpus_reference&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;MITRE:T1550&lt;/span&gt;
&lt;span class="na"&gt;applicable_asset_types&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;aws_iam_role&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="na"&gt;unsafe_predicate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;all&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;field&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;properties.identity.kind&lt;/span&gt;
      &lt;span class="na"&gt;op&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;eq&lt;/span&gt;
      &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;role&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;field&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;properties.identity.role.blast_radius_scope&lt;/span&gt;
      &lt;span class="na"&gt;op&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;eq&lt;/span&gt;
      &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cross_account&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;field&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;properties.identity.role.cross_account_trust_without_external_id&lt;/span&gt;
      &lt;span class="na"&gt;op&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;eq&lt;/span&gt;
      &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The predicate checks three properties on a single role. But two of those properties (&lt;code&gt;blast_radius_scope&lt;/code&gt; and &lt;code&gt;cross_account_trust_without_external_id&lt;/code&gt;) are pre-computed by Stave's observation extractor by &lt;strong&gt;reasoning across the role's trust policy, attached permissions, and the reachable resources in other accounts&lt;/strong&gt;. The extractor walks the IAM graph; the predicate evaluates the result.&lt;/p&gt;

&lt;p&gt;The control fires when the &lt;strong&gt;composition&lt;/strong&gt; produces the unsafe state. AWS Compliance Mod's per-row SQL shape can't express the composition. The "is the trust missing an external ID?" question is per-role and SQL-shaped; the "does the role have cross-account reach AND does the trust permit assumption from any principal in the trusted account?" question is graph-shaped, computed once during observation extraction, and predicated on at query time.&lt;/p&gt;




&lt;h2&gt;
  
  
  When to use which
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Your question&lt;/th&gt;
&lt;th&gt;Use&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Am I CIS-compliant right now?&lt;/td&gt;
&lt;td&gt;AWS Compliance Mod&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Am I PCI/HIPAA/SOC 2/FedRAMP compliant?&lt;/td&gt;
&lt;td&gt;AWS Compliance Mod&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Do my IAM users have MFA? (per-resource check)&lt;/td&gt;
&lt;td&gt;AWS Compliance Mod&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Would the Capital One pattern get through my CI?&lt;/td&gt;
&lt;td&gt;Stave&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Where are my dangling cross-resource references?&lt;/td&gt;
&lt;td&gt;Stave&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Can I prove this verdict was deterministic against this snapshot?&lt;/td&gt;
&lt;td&gt;Stave&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Can I export detection logic to Z3 / Soufflé for formal analysis?&lt;/td&gt;
&lt;td&gt;Stave&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;I want both auditor reporting and compound-risk detection&lt;/td&gt;
&lt;td&gt;Both, side by side&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Running both together
&lt;/h2&gt;

&lt;p&gt;Both tools render in Powerpipe and consume Steampipe / DuckDB backends. Install side by side:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Framework benchmarks against live AWS.&lt;/span&gt;
powerpipe mod &lt;span class="nb"&gt;install &lt;/span&gt;github.com/turbot/steampipe-mod-aws-compliance

&lt;span class="c"&gt;# Compound risk + ghost references over snapshots.&lt;/span&gt;
powerpipe mod &lt;span class="nb"&gt;install &lt;/span&gt;github.com/zepho/powerpipe-mod-stave

&lt;span class="c"&gt;# Stave itself produces the snapshot the powerpipe-mod-stave consumes.&lt;/span&gt;
go &lt;span class="nb"&gt;install &lt;/span&gt;github.com/sufield/stave/cmd/stave@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A typical workflow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Daily / on-snapshot:&lt;/strong&gt; &lt;code&gt;stave apply --format json &amp;gt; out.stave.json&lt;/code&gt; produces the deterministic evaluation. CI fails on new compound-risk chains or ghost references via &lt;code&gt;stave ci gate&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pre-audit:&lt;/strong&gt; &lt;code&gt;powerpipe benchmark run cis_v3 --mod aws_compliance&lt;/code&gt; renders the framework-cited evidence pack auditors expect.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Architecture review:&lt;/strong&gt; &lt;code&gt;powerpipe server&lt;/code&gt; opens both dashboards side by side. The AWS Compliance Mod's severity rollup answers the framework question; Stave's &lt;code&gt;stave_posture&lt;/code&gt; dashboard answers the composition question.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The two surfaces are intentionally orthogonal. The Stave Powerpipe mod deliberately does not render severity-count cards (that surface duplicates AWS Compliance Mod territory, and Stave's first dashboard widget is the chain graph instead). The AWS Compliance Mod does not render compound-risk chains (by design its job is per-resource framework coverage). Neither tries to be the other.&lt;/p&gt;




&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Run both. Auditor reports come from the AWS Compliance Mod. Composition risk comes from Stave. The full canonical comparison with every section above expanded is at &lt;a href="https://github.com/sufield/stave/blob/main/docs/comparison/aws-compliance-mod.md" rel="noopener noreferrer"&gt;aws-compliance-mod&lt;/a&gt;. The Capital One framing is the canonical wedge. The rest is just what falls out of using the right tool for each question.&lt;/p&gt;

</description>
      <category>cloudsecurity</category>
      <category>iac</category>
      <category>compliance</category>
      <category>devops</category>
    </item>
    <item>
      <title>We use VPC endpoints for S3, are we compliant? VPC endpoint without a policy is a wormhole to every S3 bucket</title>
      <dc:creator>Bala Paranj</dc:creator>
      <pubDate>Sat, 24 Oct 2026 13:56:24 +0000</pubDate>
      <link>https://dev.to/bala_paranj_059d338e44e7e/we-use-vpc-endpoints-for-s3-are-we-compliant-vpc-endpoint-without-a-policy-is-a-wormhole-to-every-318g</link>
      <guid>https://dev.to/bala_paranj_059d338e44e7e/we-use-vpc-endpoints-for-s3-are-we-compliant-vpc-endpoint-without-a-policy-is-a-wormhole-to-every-318g</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;✓ Human-authored analysis; AI used for formatting and proofreading.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;One of the confident statements on r/aws: "We route all S3 traffic through a VPC endpoint, so our data stays on the private network." VPC endpoints do keep traffic off the public internet. But the confidence creates a blind spot.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Default Policy Problem
&lt;/h2&gt;

&lt;p&gt;When you create a VPC gateway endpoint for S3, AWS attaches a default policy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Principal"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Any principal on the VPC can reach ANY S3 bucket in ANY AWS account.&lt;/strong&gt; Not just your buckets or buckets in your account. Any bucket, anywhere, that allows access from the calling principal.&lt;/p&gt;

&lt;p&gt;The endpoint restricts the &lt;em&gt;network path&lt;/em&gt; but not the &lt;em&gt;destination&lt;/em&gt;. A compromised EC2 instance on your VPC can exfiltrate data to an attacker-controlled S3 bucket in a different account, and the traffic never touches the internet. Your VPC flow logs show "private network." Your data is in someone else's bucket.&lt;/p&gt;

&lt;h2&gt;
  
  
  The HIPAA Citation
&lt;/h2&gt;

&lt;p&gt;HIPAA section 164.312(e)(1) requires transmission security. The technical measures to guard against unauthorized access to electronic PHI during transmission. A VPC endpoint that allows unrestricted destination access doesn't satisfy this. It secures the transport but not the authorization of where data can flow.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Stave Detects It
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/sufield/stave" rel="noopener noreferrer"&gt;Stave&lt;/a&gt; evaluates this as a compound risk using two controls:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Control&lt;/th&gt;
&lt;th&gt;What it checks&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CTL.S3.NETWORK.VPC.001&lt;/td&gt;
&lt;td&gt;VPC endpoint or IP condition exists&lt;/td&gt;
&lt;td&gt;PASS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CTL.S3.NETWORK.POLICY.001&lt;/td&gt;
&lt;td&gt;Endpoint policy restricts access&lt;/td&gt;
&lt;td&gt;FAIL&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The compound finding is the dangerous one. CTL.S3.NETWORK.VPC.001 passes — yes, you have a VPC endpoint. But CTL.S3.NETWORK.POLICY.001 fails. The endpoint policy is missing or uses the default full-access policy. Together, this creates a false sense of security: the VPC endpoint exists, but it's a wormhole to the entire S3 namespace.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;stave evaluate &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--controls&lt;/span&gt; controls/s3/network/ &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--observations&lt;/span&gt; observations/ &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--eval-time&lt;/span&gt; 2026-04-08T00:00:00Z
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;Security State&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;NON_COMPLIANT&lt;/span&gt;

&lt;span class="na"&gt;Findings&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;phi-vpc-endpoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="s"&gt;CTL.S3.NETWORK.POLICY.001  UNSAFE  VPC endpoint policy unrestricted&lt;/span&gt;
      &lt;span class="s"&gt;VPC endpoint policy is missing or uses the default full-access&lt;/span&gt;
      &lt;span class="s"&gt;policy. Any principal on the VPC can reach any S3 bucket.&lt;/span&gt;

      &lt;span class="s"&gt;Remediation&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Replace the default endpoint policy with one that&lt;/span&gt;
      &lt;span class="s"&gt;restricts Resource to specific bucket ARNs and Action to required&lt;/span&gt;
      &lt;span class="s"&gt;S3 operations only.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The fix is to replace the default policy with a restrictive one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Principal"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"s3:GetObject"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"s3:PutObject"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:s3:::phi-data-bucket/*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:s3:::phi-logs-bucket/*"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A VPC endpoint without an endpoint policy is security theater. It looks compliant where the traffic stays on the private network, the checkbox is checked. But the default &lt;code&gt;Allow *&lt;/code&gt; policy turns the endpoint into an unrestricted gateway to every S3 bucket in existence. Stave detects when the endpoint exists but the policy doesn't restrict it, surfacing the compound risk that a single-control scanner would miss.&lt;/p&gt;

</description>
      <category>hipaa</category>
      <category>aws</category>
      <category>s3</category>
      <category>security</category>
    </item>
    <item>
      <title>Three S3 risks AWS Trusted Advisor can't see</title>
      <dc:creator>Bala Paranj</dc:creator>
      <pubDate>Fri, 23 Oct 2026 12:51:00 +0000</pubDate>
      <link>https://dev.to/bala_paranj_059d338e44e7e/three-s3-risks-aws-trusted-advisor-cant-see-467d</link>
      <guid>https://dev.to/bala_paranj_059d338e44e7e/three-s3-risks-aws-trusted-advisor-cant-see-467d</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;✓ Human-authored analysis; AI used for formatting and proofreading.&lt;/p&gt;

&lt;p&gt;"Trusted Advisor says all our S3 buckets are green. Our HIPAA assessment is next month. Are we safe?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Trusted Advisor is a useful baseline. It checks whether buckets have public access and whether logging is enabled. But it operates on a narrow threat model: "Is this bucket public right now?" Three categories of S3 risk are invisible to it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Blind spot 1: Incomplete observation data (CTL.S3.INCOMPLETE.001)
&lt;/h2&gt;

&lt;p&gt;Before Trusted Advisor can check a bucket, it needs to read the bucket policy and ACL. What happens if the IAM role running the check lacks &lt;code&gt;s3:GetBucketPolicy&lt;/code&gt; permission? Or if the bucket policy explicitly denies the scanner's principal?&lt;/p&gt;

&lt;p&gt;Trusted Advisor reports nothing. The bucket doesn't appear in findings because the check couldn't execute. No error or warning. Silence.&lt;/p&gt;

&lt;p&gt;Stave treats missing data as a finding, not a pass. The CTL.S3.INCOMPLETE.001 control fires when policy or ACL data is absent from the observation snapshot:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;stave evaluate &lt;span class="nt"&gt;--snapshot&lt;/span&gt; observations/snap.json &lt;span class="nt"&gt;--profile&lt;/span&gt; hipaa
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;── LOW ──

  [FAIL] INCOMPLETE.001 — low
  Finding: Bucket policy data missing from snapshot — safety cannot be proven
  Remediation: Re-run the observation collector with s3:GetBucketPolicy,
  s3:GetBucketAcl, and s3:GetBucketPolicyStatus permissions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Absence of evidence is not evidence of absence. If you cannot read the policy, you cannot prove the bucket is safe.&lt;/p&gt;

&lt;h2&gt;
  
  
  Blind spot 2: Latent public exposure behind PAB (CTL.S3.PUBLIC.005)
&lt;/h2&gt;

&lt;p&gt;A bucket can have a policy that grants &lt;code&gt;s3:GetObject&lt;/code&gt; to &lt;code&gt;Principal: "*"&lt;/code&gt; and still appear private if Block Public Access (PAB) is enabled. PAB overrides the public policy at the API layer. Trusted Advisor sees "not public" and reports green.&lt;/p&gt;

&lt;p&gt;But PAB is a single toggle. One API call &lt;code&gt;s3api put-public-access-block&lt;/code&gt; with all flags set to false and the bucket is instantly public. The underlying public policy is latent, waiting to activate.&lt;/p&gt;

&lt;p&gt;Stave's CTL.S3.PUBLIC.005 detects this latent exposure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;── MEDIUM ──

  [FAIL] PUBLIC.005 — medium
  Finding: Public read policy masked only by Public Access Block —
  removing PAB would immediately expose the bucket
  Remediation: Remove the underlying public-granting policy statement
  so the bucket does not depend solely on PAB for protection
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;AWS Config rule changes, Terraform drift, and well-intentioned "fix the CORS issue" tickets have all disabled PAB on production buckets. If the underlying policy is already public, the exposure is instant.&lt;/p&gt;

&lt;h2&gt;
  
  
  Blind spot 3: ACL escalation (CTL.S3.ACL.ESCALATION.001)
&lt;/h2&gt;

&lt;p&gt;S3 ACLs have a permission that most engineers have never heard of: &lt;code&gt;WRITE_ACP&lt;/code&gt;. This permission lets the grantee modify the ACL itself. If &lt;code&gt;AllUsers&lt;/code&gt; has &lt;code&gt;WRITE_ACP&lt;/code&gt;, any anonymous user can grant themselves &lt;code&gt;FULL_CONTROL&lt;/code&gt; with read, write, delete, and further ACL modification.&lt;/p&gt;

&lt;p&gt;Trusted Advisor checks for public READ and WRITE. It does not check for &lt;code&gt;WRITE_ACP&lt;/code&gt; or &lt;code&gt;READ_ACP&lt;/code&gt;, because these are not direct data access permissions. They are escalation paths.&lt;/p&gt;

&lt;p&gt;Stave's CTL.S3.ACL.ESCALATION.001 catches it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;── HIGH ──

  [FAIL] ACL.ESCALATION.001 — high
  Finding: Bucket ACL grants WRITE_ACP to AllUsers — any anonymous user
  can modify the ACL to grant themselves FULL_CONTROL
  Remediation: Remove WRITE_ACP grants and enable S3 Public Access Block
  with BlockPublicAcls set to true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Trusted Advisor answers: "Is this bucket public right now?" That is a useful but incomplete question. For HIPAA compliance, you also need to answer: "Can I prove this bucket's state?" (incomplete data), "Would this bucket become public with one config change?" (latent exposure), and "Can anyone escalate their own permissions?" (ACL escalation). These three blind spots require a tool that models threat paths, not just current state.&lt;/p&gt;

</description>
      <category>hipaa</category>
      <category>aws</category>
      <category>s3</category>
      <category>security</category>
    </item>
    <item>
      <title>HIPAA compliance: AWS Config Conformance Pack vs Stave</title>
      <dc:creator>Bala Paranj</dc:creator>
      <pubDate>Thu, 22 Oct 2026 14:00:14 +0000</pubDate>
      <link>https://dev.to/bala_paranj_059d338e44e7e/hipaa-compliance-aws-config-conformance-pack-vs-stave-37f1</link>
      <guid>https://dev.to/bala_paranj_059d338e44e7e/hipaa-compliance-aws-config-conformance-pack-vs-stave-37f1</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;✓ Human-authored analysis; AI used for formatting and proofreading.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Two approaches to the same regulation
&lt;/h2&gt;

&lt;p&gt;AWS provides a &lt;a href="https://docs.aws.amazon.com/config/latest/developerguide/operational-best-practices-for-hipaa_security.html" rel="noopener noreferrer"&gt;HIPAA Security Conformance Pack&lt;/a&gt; which is a CloudFormation template that deploys ~100 AWS Config managed rules across S3, IAM, EC2, RDS, ELB, Lambda, VPC, CloudTrail, and more.&lt;/p&gt;

&lt;p&gt;Stave provides a HIPAA profile consisting of 14 controls + 3 compound risk detectors focused on S3 and IAM, evaluated offline against JSON snapshots with duration tracking and regulatory citations.&lt;/p&gt;

&lt;p&gt;They solve the same regulation at different depths.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conformance Pack
&lt;/h2&gt;

&lt;p&gt;The pack deploys AWS Config rules as CloudFormation resources:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;S3BucketPublicReadProhibited&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;ConfigRuleName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;s3-bucket-public-read-prohibited&lt;/span&gt;
    &lt;span class="na"&gt;Scope&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;ComplianceResourceTypes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;AWS::S3::Bucket&lt;/span&gt;
    &lt;span class="na"&gt;Source&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;Owner&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS&lt;/span&gt;
      &lt;span class="na"&gt;SourceIdentifier&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;S3_BUCKET_PUBLIC_READ_PROHIBITED&lt;/span&gt;
  &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::Config::ConfigRule&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each rule is an opaque AWS-managed check. You deploy the template, AWS Config evaluates your resources continuously, and results appear in the Config dashboard.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Strengths:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Covers 20+ AWS services in one template&lt;/li&gt;
&lt;li&gt;~100 rules including S3, IAM, EC2, RDS, ELB, Lambda, VPC, CloudTrail, KMS, Secrets Manager, SageMaker, DynamoDB, Redshift, OpenSearch&lt;/li&gt;
&lt;li&gt;Continuous evaluation — no manual snapshots&lt;/li&gt;
&lt;li&gt;Native AWS integration — no external tools&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Limitations:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Requires AWS Config enabled ($0.003 per rule evaluation per resource)&lt;/li&gt;
&lt;li&gt;Requires live AWS API access and IAM permissions&lt;/li&gt;
&lt;li&gt;Rules are opaque — you can't read the evaluation logic&lt;/li&gt;
&lt;li&gt;Results are in the AWS Config dashboard — vendor lock-in&lt;/li&gt;
&lt;li&gt;No HIPAA section citations on findings&lt;/li&gt;
&lt;li&gt;No duration tracking&lt;/li&gt;
&lt;li&gt;No compound risk detection&lt;/li&gt;
&lt;li&gt;No offline capability&lt;/li&gt;
&lt;li&gt;No proof of pass&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What Stave does differently
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Every finding cites the regulation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[FAIL] ACCESS.001 — critical
Compliance: §164.312(a)(1) — Access control
Finding: Bucket phi-patient-records: Block Public Access is not fully enabled
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The AWS Config finding says "S3_BUCKET_PUBLIC_READ_PROHIBITED — NON_COMPLIANT." It doesn't tell the auditor which HIPAA section applies. Stave maps every control to its specific Security Rule citation.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Compound risk detection
&lt;/h3&gt;

&lt;p&gt;AWS Config rules evaluate independently. Two rules can both pass while their combination is dangerous.&lt;/p&gt;

&lt;p&gt;Stave detects three compound patterns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[critical] COMPOUND.001 (triggers: ACCESS.001, ACCESS.002)
Public access with overly broad IAM permissions — the S3 + IAM lateral
movement pattern present in the majority of documented AWS breaches.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[high] COMPOUND.002 (triggers: ACCESS.001, CONTROLS.001)
Encryption at rest is configured but the bucket is publicly accessible.
Encryption provides no confidentiality benefit while public access is enabled.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[high] COMPOUND.003 (triggers: ACCESS.003, ACCESS.006)
VPC endpoint restricts this bucket but the endpoint policy does not
restrict which bucket ARNs are reachable — a wormhole.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;AWS Config has no cross-rule reasoning. Each rule runs in isolation.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Duration tracking
&lt;/h3&gt;

&lt;p&gt;AWS Config tells you a resource is non-compliant right now. It doesn't tell you how long it's been non-compliant.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Finding: Bucket phi-patient-records
  First unsafe: 2026-01-01T00:00:00Z
  Last seen:    2026-01-11T00:00:00Z
  Duration:     240 hours (threshold: 168 hours)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For HIPAA, duration matters. A bucket that was public for 5 minutes during a deploy is different from one that's been public for 6 months. The Conformance Pack can't distinguish them.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Acknowledged exceptions with compensating controls
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# stave.yaml&lt;/span&gt;
&lt;span class="na"&gt;exceptions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;control_id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ACCESS.001&lt;/span&gt;
    &lt;span class="na"&gt;bucket&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-cloudfront-origin&lt;/span&gt;
    &lt;span class="na"&gt;rationale&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CloudFront&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;+&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;OAI&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;pattern&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;—&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;bucket&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;is&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;private&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;CloudFront"&lt;/span&gt;
    &lt;span class="na"&gt;acknowledged_by&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;security-team@example.com&lt;/span&gt;
    &lt;span class="na"&gt;requires_passing&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;CONTROLS.001&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;CONTROLS.004&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;AUDIT.001&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Conformance Pack supports suppression via Config rule suppressions. But without mandatory compensating controls. You can suppress a finding without documenting what alternative protection exists.&lt;/p&gt;

&lt;p&gt;Stave requires compensating controls. If the compensating control fails, the original finding stands.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Offline and deterministic
&lt;/h3&gt;

&lt;p&gt;The Conformance Pack requires AWS Config running in your account. Stave evaluates a JSON snapshot locally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;stave evaluate &lt;span class="nt"&gt;--snapshot&lt;/span&gt; snap.json &lt;span class="nt"&gt;--profile&lt;/span&gt; hipaa &lt;span class="nt"&gt;--format&lt;/span&gt; json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same input always produces the same output. No AWS credentials needed. Air-gapped environments supported. The logic trace provides a verifiable reasoning chain for every verdict.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. CMK vs AWS-managed key distinction
&lt;/h3&gt;

&lt;p&gt;The Conformance Pack has &lt;code&gt;S3_DEFAULT_ENCRYPTION_KMS&lt;/code&gt;. It checks if KMS is the default encryption. But it doesn't distinguish between the AWS-managed key (&lt;code&gt;alias/aws/s3&lt;/code&gt;) and a customer-managed key.&lt;/p&gt;

&lt;p&gt;Stave's &lt;code&gt;CONTROLS.001.STRICT&lt;/code&gt; fails specifically on the AWS-managed key because it can't be revoked during a breach:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[FAIL] CONTROLS.001.STRICT — critical
Compliance: §164.312(a)(2)(iv) — CMK required for key revocation
Finding: SSE-KMS uses the AWS-managed key (alias/aws/s3).
CMK required for key revocation during breach response.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Head-to-head: S3 HIPAA controls
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Check&lt;/th&gt;
&lt;th&gt;AWS Config Rule&lt;/th&gt;
&lt;th&gt;Stave Control&lt;/th&gt;
&lt;th&gt;Stave Extra&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Public read&lt;/td&gt;
&lt;td&gt;&lt;code&gt;S3_BUCKET_PUBLIC_READ_PROHIBITED&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ACCESS.001&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;+ HIPAA citation, + duration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Public write&lt;/td&gt;
&lt;td&gt;&lt;code&gt;S3_BUCKET_PUBLIC_WRITE_PROHIBITED&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Built-in (CTL.S3.PUBLIC.003)&lt;/td&gt;
&lt;td&gt;+ severity tiering&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Encryption at rest&lt;/td&gt;
&lt;td&gt;&lt;code&gt;S3_BUCKET_SERVER_SIDE_ENCRYPTION_ENABLED&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CONTROLS.001&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;KMS encryption&lt;/td&gt;
&lt;td&gt;&lt;code&gt;S3_DEFAULT_ENCRYPTION_KMS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CONTROLS.001.STRICT&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;+ CMK vs AWS-managed distinction&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SSL/TLS only&lt;/td&gt;
&lt;td&gt;&lt;code&gt;S3_BUCKET_SSL_REQUESTS_ONLY&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CONTROLS.004&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;+ HIPAA citation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Logging&lt;/td&gt;
&lt;td&gt;&lt;code&gt;S3_BUCKET_LOGGING_ENABLED&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;AUDIT.001&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;+ "logs cannot be obtained retroactively"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Versioning&lt;/td&gt;
&lt;td&gt;&lt;code&gt;S3_BUCKET_VERSIONING_ENABLED&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CONTROLS.002&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;+ MFA delete recommendation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ACLs disabled&lt;/td&gt;
&lt;td&gt;&lt;code&gt;S3_BUCKET_ACL_PROHIBITED&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;GOVERNANCE.001&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;+ AWS Backup exception note&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Object Lock&lt;/td&gt;
&lt;td&gt;&lt;code&gt;S3_BUCKET_DEFAULT_LOCK_ENABLED&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;RETENTION.002&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;+ severity tiering (none/governance/compliance)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CloudTrail data events&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CLOUDTRAIL_S3_DATAEVENTS_ENABLED&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;AUDIT.002&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;+ object-level audit trail context&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Account-level PAB&lt;/td&gt;
&lt;td&gt;&lt;code&gt;S3_ACCOUNT_LEVEL_PUBLIC_ACCESS_BLOCKS_PERIODIC&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Built-in&lt;/td&gt;
&lt;td&gt;+ severity downgrade when account-level present&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lifecycle rules&lt;/td&gt;
&lt;td&gt;&lt;code&gt;S3_VERSION_LIFECYCLE_POLICY_CHECK&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;LIFECYCLE.001&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;+ PHI minimum retention check&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Compound: public + wildcard&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Not detected&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;COMPOUND.001&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Cross-rule reasoning&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Compound: encrypted + public&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Not detected&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;COMPOUND.002&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;False security detection&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Compound: VPC + no policy&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Not detected&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;COMPOUND.003&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Wormhole detection&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Where the Conformance Pack wins
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Capability&lt;/th&gt;
&lt;th&gt;AWS Config&lt;/th&gt;
&lt;th&gt;Stave&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Services covered&lt;/td&gt;
&lt;td&gt;20+ (EC2, RDS, ELB, Lambda, VPC, etc.)&lt;/td&gt;
&lt;td&gt;S3 + IAM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total rules&lt;/td&gt;
&lt;td&gt;~100&lt;/td&gt;
&lt;td&gt;14 + 3 compound&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Continuous&lt;/td&gt;
&lt;td&gt;Yes (Config evaluates on change)&lt;/td&gt;
&lt;td&gt;No (snapshot-based)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Native AWS&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No (offline tool)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Setup effort&lt;/td&gt;
&lt;td&gt;Deploy one CloudFormation template&lt;/td&gt;
&lt;td&gt;Build extractor + run CLI&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If you need broad coverage across many AWS services with minimal setup, the Conformance Pack wins on breadth.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Stave wins
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Capability&lt;/th&gt;
&lt;th&gt;AWS Config&lt;/th&gt;
&lt;th&gt;Stave&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;HIPAA citations&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Every finding cites §164.312&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Compound risk&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;3 cross-rule detectors&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Duration tracking&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;SLA-based with temporal evidence&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Exceptions&lt;/td&gt;
&lt;td&gt;Suppression only&lt;/td&gt;
&lt;td&gt;Compensating controls required&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Offline&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes — air-gapped capable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deterministic&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Same input = same output&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Proof of pass&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Logic trace reasoning chain&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CMK distinction&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Fails on AWS-managed key&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multi-cloud&lt;/td&gt;
&lt;td&gt;No (AWS only)&lt;/td&gt;
&lt;td&gt;Yes (S3, IAM, GCS, DNS)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Readable rules&lt;/td&gt;
&lt;td&gt;No (opaque)&lt;/td&gt;
&lt;td&gt;Yes (YAML + CEL)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If you need deep S3/IAM analysis with auditor-ready evidence, Stave wins on depth.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use both
&lt;/h2&gt;

&lt;p&gt;Deploy the Conformance Pack for broad continuous monitoring across all AWS services. Run Stave for deep S3/IAM analysis with compound risk detection, duration tracking, and HIPAA-cited evidence that auditors can verify offline.&lt;/p&gt;

&lt;p&gt;The Conformance Pack is your dashboard. Stave is your proof.&lt;/p&gt;

</description>
      <category>hipaa</category>
      <category>aws</category>
      <category>security</category>
      <category>compliance</category>
    </item>
    <item>
      <title>Your SaaS vendor said 'use S3 for HIPAA recordings'. Did you secure the bucket?</title>
      <dc:creator>Bala Paranj</dc:creator>
      <pubDate>Wed, 21 Oct 2026 13:50:56 +0000</pubDate>
      <link>https://dev.to/bala_paranj_059d338e44e7e/your-saas-vendor-said-use-s3-for-hipaa-recordings-did-you-secure-the-bucket-bg</link>
      <guid>https://dev.to/bala_paranj_059d338e44e7e/your-saas-vendor-said-use-s3-for-hipaa-recordings-did-you-secure-the-bucket-bg</guid>
      <description>&lt;h2&gt;
  
  
  The handoff problem
&lt;/h2&gt;

&lt;p&gt;SaaS vendors that handle PHI such as telehealth platforms, EHR integrations, diagnostic tools tell customers: "Store your data in your own S3 bucket for HIPAA compliance."&lt;/p&gt;

&lt;p&gt;This is architecturally sound. The vendor avoids storing PHI. The customer controls their own data. But it creates a &lt;strong&gt;responsibility handoff&lt;/strong&gt;: the vendor provides instructions, the customer must execute them correctly.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.whereby.com/information/hipaa/hipaa-compliant-setup" rel="noopener noreferrer"&gt;Whereby&lt;/a&gt;, a video conferencing platform used in telehealth, tells HIPAA customers to store cloud recordings in their own S3 bucket with this policy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Sid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AllowListAndPutRecordings"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Principal"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"AWS"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:iam::&amp;lt;aws_account_id&amp;gt;:user/&amp;lt;aws_user&amp;gt;"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"s3:ListBucket"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"s3:PutObject"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:s3:::&amp;lt;recording_bucket&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:s3:::&amp;lt;recording_bucket&amp;gt;/*"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Good template. Scoped principal, minimal actions, specific resources. But the template only covers the bucket policy. It says nothing about encryption, logging, versioning, Public Access Block, Object Lock, or transport encryption.&lt;/p&gt;

&lt;p&gt;The customer is responsible for all of it. They can get it wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  What goes wrong
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. No encryption at rest
&lt;/h3&gt;

&lt;p&gt;The customer creates the bucket, pastes the policy, starts recording. Nobody enables server-side encryption. The recordings which contain patient video, audio, and potentially screen-shared medical records sit unencrypted in S3.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stave catches it:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[FAIL] CONTROLS.001 — high
Compliance: §164.312(a)(2)(iv) — Encryption and Decryption
Finding: Bucket telehealth-recordings: encryption at rest not enabled
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. AWS-managed key instead of CMK
&lt;/h3&gt;

&lt;p&gt;The customer enables SSE-S3 (AES256) which is the default. But AES256 uses an AWS-managed key that the customer cannot revoke. During a breach, the customer needs to disable the key immediately to render recordings unreadable. With the AWS-managed key, they can't.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stave catches it:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[FAIL] CONTROLS.001.STRICT — critical
Compliance: §164.312(a)(2)(iv) — CMK required for key revocation
Finding: SSE uses AES256 (AWS-managed). CMK required for breach response.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Whereby even notes that "key rotation will provide additional security." But they stop short of requiring CMK. Stave's HIPAA profile makes the distinction explicit.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. No access logging
&lt;/h3&gt;

&lt;p&gt;The bucket stores PHI recordings but has no server access logging enabled. If an unauthorized party downloads recordings, there's no forensic evidence.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stave catches it:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[FAIL] AUDIT.001 — critical
Compliance: §164.312(b) — Audit Controls
Finding: Bucket telehealth-recordings: logging not enabled.
Logs cannot be obtained retroactively from AWS.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the retroactivity problem. You can't turn on logging after a breach and recover what happened before. The evidence window is zero.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Public Access Block not enabled
&lt;/h3&gt;

&lt;p&gt;The customer pastes the bucket policy but doesn't enable Public Access Block. A future policy change such as a Terraform apply with a stale config, a console click could make the bucket public. PAB is the safety net.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stave catches it:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[FAIL] CTL.S3.CONTROLS.001 — high
Finding: Public Access Block not fully enabled. No safety net against
accidental public exposure from policy or ACL changes.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5. Wildcard actions in the policy
&lt;/h3&gt;

&lt;p&gt;Instead of the vendor's scoped template, the customer pastes a broader policy they found on Stack Overflow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Principal"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"s3:*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:s3:::telehealth-recordings/*"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now anyone can read, write, delete, or modify ACLs on the recordings bucket.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stave catches it:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[FAIL] ACCESS.002 — high
Compliance: §164.312(a)(2)(i) — Least privilege
Finding: Policy grants Allow with wildcard action s3:*
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the compound detector fires:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[critical] COMPOUND.001
Public access with overly broad IAM permissions — the S3 + IAM lateral
movement pattern present in the majority of documented AWS breaches.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  6. No Object Lock for retention
&lt;/h3&gt;

&lt;p&gt;HIPAA requires 6 years of PHI retention. Without Object Lock in Compliance mode, recordings can be deleted  accidentally, maliciously, or by a lifecycle rule that expires objects too early.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stave catches it:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[FAIL] RETENTION.002 — critical
Compliance: §164.316(b)(2) — 6-year PHI retention
Finding: Object Lock not enabled — recordings can be deleted.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  7. No transport encryption enforcement
&lt;/h3&gt;

&lt;p&gt;The bucket policy doesn't deny non-TLS requests. Someone accessing the bucket via HTTP (not HTTPS) transmits recordings in plaintext.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stave catches it:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[FAIL] CONTROLS.004 — high
Compliance: §164.312(e)(2)(ii) — Encryption in transit
Finding: No Deny statement for aws:SecureTransport=false
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The compound picture
&lt;/h2&gt;

&lt;p&gt;Run all 14 HIPAA controls against a typical "I followed the vendor's template" bucket:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;stave evaluate &lt;span class="nt"&gt;--snapshot&lt;/span&gt; recordings-bucket.json &lt;span class="nt"&gt;--profile&lt;/span&gt; hipaa
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;═══ HIPAA Security Rule ═══
Bucket:    telehealth-recordings
Result:    FAIL

  critical: 0/4 passed
  high:     1/7 passed  (only the vendor's bucket policy is correct)
  medium:   0/2 passed

Overall: FAIL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The vendor's bucket policy template is the only thing that passes. Everything else such as encryption, logging, retention, transport security, PAB is the customer's responsibility. And the customer didn't do it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The duration dimension
&lt;/h2&gt;

&lt;p&gt;The worst version of this: the bucket has been in this state since the customer set it up 18 months ago. Every recording since then is stored unencrypted, unlogged, without retention protection.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Duration: 13,000+ hours (threshold: 168 hours)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No other tool tracks this. AWS Config says "non-compliant now." Stave says "non-compliant for 13,000 hours." That temporal evidence changes the regulatory conversation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The lesson for SaaS vendors and customers
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;For vendors:&lt;/strong&gt; providing a bucket policy template is necessary but insufficient. Document the full bucket security posture: encryption (CMK, not AWS-managed), logging, PAB, versioning, Object Lock, transport encryption. Or better: provide a Stave observation contract so customers can evaluate their bucket against your HIPAA requirements.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For customers:&lt;/strong&gt; the vendor's HIPAA compliance ends at their boundary. Your S3 bucket is your responsibility. Run Stave's HIPAA profile against every bucket that stores vendor-delegated PHI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;stave evaluate &lt;span class="nt"&gt;--snapshot&lt;/span&gt; my-recordings-bucket.json &lt;span class="nt"&gt;--profile&lt;/span&gt; hipaa
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If it doesn't say PASS across all 14 controls, the bucket isn't HIPAA compliant regardless of what the vendor's documentation says.&lt;/p&gt;

</description>
      <category>hipaa</category>
      <category>aws</category>
      <category>s3</category>
      <category>security</category>
    </item>
    <item>
      <title>HIPAA on Kubernetes: what kops issue #1776 asked for and what Stave covers now</title>
      <dc:creator>Bala Paranj</dc:creator>
      <pubDate>Tue, 20 Oct 2026 14:41:24 +0000</pubDate>
      <link>https://dev.to/bala_paranj_059d338e44e7e/hipaa-on-kubernetes-what-kops-issue-1776-asked-for-and-what-stave-covers-now-4993</link>
      <guid>https://dev.to/bala_paranj_059d338e44e7e/hipaa-on-kubernetes-what-kops-issue-1776-asked-for-and-what-stave-covers-now-4993</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;✓ Human-authored analysis; AI used for formatting and proofreading.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The question that's still open
&lt;/h2&gt;

&lt;p&gt;In February 2017, &lt;a href="https://github.com/kubernetes/kops/issues/1776" rel="noopener noreferrer"&gt;kops issue #1776&lt;/a&gt; asked: "What is kops missing to support a HIPAA compliant cluster?"&lt;/p&gt;

&lt;p&gt;The issue is still open. The discussion produced one of the most practical HIPAA checklists for AWS infrastructure, contributed by engineers running PHI workloads:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"ePHI/PII over the wire must be encrypted. ePHI/PII sitting on disk is encrypted. Basically, anything that does NOT need to be open/public/unrestricted needs to be closed/private/restricted."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The checklist covers 6 domains: data encryption, access control, audit logging, network security, compute security, and operational procedures. Most of these are still valid requirements in 2026.&lt;/p&gt;

&lt;h2&gt;
  
  
  The checklist mapped to Stave
&lt;/h2&gt;

&lt;p&gt;Here's every technical requirement from the kops discussion, mapped against what Stave evaluates today:&lt;/p&gt;

&lt;h3&gt;
  
  
  Data Encryption
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Requirement&lt;/th&gt;
&lt;th&gt;HIPAA Section&lt;/th&gt;
&lt;th&gt;Stave Status&lt;/th&gt;
&lt;th&gt;Control&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ePHI encrypted at rest (S3)&lt;/td&gt;
&lt;td&gt;§164.312(a)(2)(iv)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Covered&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CTL.S3.ENCRYPT.001&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CMK instead of AWS-managed key&lt;/td&gt;
&lt;td&gt;§164.312(a)(2)(iv)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Covered&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CONTROLS.001.STRICT&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Encrypted EBS volumes&lt;/td&gt;
&lt;td&gt;§164.312(a)(2)(iv)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Covered&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CTL.EC2.EBS.DEFAULT.001&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Encrypted EBS root AMI&lt;/td&gt;
&lt;td&gt;§164.312(a)(2)(iv)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Covered&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CTL.EC2.AMI.ENCRYPTION.001&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;etcd encryption at rest&lt;/td&gt;
&lt;td&gt;§164.312(a)(2)(iv)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Covered&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CTL.K8S.ETCD.CERT.001&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;S3 transport encryption (TLS)&lt;/td&gt;
&lt;td&gt;§164.312(e)(2)(ii)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Covered&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CTL.S3.ENCRYPT.002&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Network encryption (pod-to-pod)&lt;/td&gt;
&lt;td&gt;§164.312(e)(1)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Covered&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CTL.K8S.NETPOL.001&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Access Control
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Requirement&lt;/th&gt;
&lt;th&gt;HIPAA Section&lt;/th&gt;
&lt;th&gt;Stave Status&lt;/th&gt;
&lt;th&gt;Control&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;S3 Public Access Block&lt;/td&gt;
&lt;td&gt;§164.312(a)(1)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Covered&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CTL.S3.CONTROLS.001&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No wildcard IAM actions&lt;/td&gt;
&lt;td&gt;§164.312(a)(2)(i)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Covered&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CTL.S3.ACCESS.002&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Root account MFA&lt;/td&gt;
&lt;td&gt;§164.312(d)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Covered&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CTL.IAM.ROOT.MFA.001&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Console user MFA&lt;/td&gt;
&lt;td&gt;§164.312(d)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Covered&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CTL.IAM.CONSOLE.MFA.001&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No root access keys&lt;/td&gt;
&lt;td&gt;§164.312(a)(1)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Covered&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CTL.IAM.ROOT.ACCESSKEY.001&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unused credentials disabled&lt;/td&gt;
&lt;td&gt;§164.312(a)(2)(i)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Covered&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CTL.IAM.CRED.UNUSED.001&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Access key rotation (&amp;lt;90 days)&lt;/td&gt;
&lt;td&gt;§164.312(a)(2)(i)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Covered&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CTL.IAM.CRED.ROTATION.001&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No inline IAM policies&lt;/td&gt;
&lt;td&gt;§164.312(a)(2)(i)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Covered&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CTL.IAM.POLICY.INLINE.001&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Password policy (length, complexity)&lt;/td&gt;
&lt;td&gt;§164.312(a)(2)(i)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Covered&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CTL.IAM.PASSWORD.*&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Default-deny security groups&lt;/td&gt;
&lt;td&gt;§164.312(a)(1)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Covered&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CTL.VPC.SG.DEFAULT.001&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kubernetes RBAC enforcement&lt;/td&gt;
&lt;td&gt;§164.312(a)(1)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Covered&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CTL.K8S.RBAC.WILDCARD.001&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pod security policies&lt;/td&gt;
&lt;td&gt;§164.312(a)(1)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Covered&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CTL.K8S.POD.PRIVILEGED.001&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Audit &amp;amp; Logging
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Requirement&lt;/th&gt;
&lt;th&gt;HIPAA Section&lt;/th&gt;
&lt;th&gt;Stave Status&lt;/th&gt;
&lt;th&gt;Control&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;S3 access logging&lt;/td&gt;
&lt;td&gt;§164.312(b)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Covered&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CTL.S3.LOG.001&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CloudTrail S3 data events&lt;/td&gt;
&lt;td&gt;§164.312(b)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Covered&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CTL.S3.AUDIT.OBJECTLEVEL.001&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;VPC flow logging&lt;/td&gt;
&lt;td&gt;§164.312(b)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Covered&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CTL.VPC.FLOWLOG.001&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kubernetes audit logging&lt;/td&gt;
&lt;td&gt;§164.312(b)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Covered&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CTL.K8S.AUDIT.001&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;kops/kubectl action logging&lt;/td&gt;
&lt;td&gt;§164.312(b)&lt;/td&gt;
&lt;td&gt;Not covered&lt;/td&gt;
&lt;td&gt;Operational — tool-specific&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;File system auditing (OSSEC)&lt;/td&gt;
&lt;td&gt;§164.312(b)&lt;/td&gt;
&lt;td&gt;Not covered&lt;/td&gt;
&lt;td&gt;Runtime agent — not config&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Network Security
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Requirement&lt;/th&gt;
&lt;th&gt;HIPAA Section&lt;/th&gt;
&lt;th&gt;Stave Status&lt;/th&gt;
&lt;th&gt;Control&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;VPC endpoint for S3&lt;/td&gt;
&lt;td&gt;§164.312(e)(1)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Covered&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CTL.S3.NETWORK.VPC.001&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;VPC endpoint policy&lt;/td&gt;
&lt;td&gt;§164.312(e)(1)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Covered&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CTL.S3.NETWORK.POLICY.001&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Private topology (no public IPs)&lt;/td&gt;
&lt;td&gt;§164.312(e)(1)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Covered&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CTL.EC2.PUBLIC.001&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Restricted egress&lt;/td&gt;
&lt;td&gt;§164.312(e)(1)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Covered&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CTL.K8S.NETPOL.EGRESS.001&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Network policy enforcement&lt;/td&gt;
&lt;td&gt;§164.312(e)(1)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Covered&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CTL.K8S.NETPOL.DENY.001&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Data Integrity &amp;amp; Retention
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Requirement&lt;/th&gt;
&lt;th&gt;HIPAA Section&lt;/th&gt;
&lt;th&gt;Stave Status&lt;/th&gt;
&lt;th&gt;Control&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;S3 versioning&lt;/td&gt;
&lt;td&gt;§164.312(c)(1)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Covered&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CTL.S3.VERSION.001&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;S3 Object Lock (compliance mode)&lt;/td&gt;
&lt;td&gt;§164.316(b)(2)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Covered&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CTL.S3.LOCK.001/002/003&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;S3 lifecycle rules&lt;/td&gt;
&lt;td&gt;§164.316(b)(2)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Covered&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CTL.S3.LIFECYCLE.001/002&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Backup encryption&lt;/td&gt;
&lt;td&gt;§164.312(a)(2)(iv)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Covered&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CTL.RDS.ENCRYPT.BACKUP.001&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Backup testing&lt;/td&gt;
&lt;td&gt;§164.308(a)(7)&lt;/td&gt;
&lt;td&gt;Not covered&lt;/td&gt;
&lt;td&gt;Operational — cadence policy&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Operational (Policy-based, not technical)
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Requirement&lt;/th&gt;
&lt;th&gt;HIPAA Section&lt;/th&gt;
&lt;th&gt;Stave Status&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Unique user identification&lt;/td&gt;
&lt;td&gt;§164.312(a)(2)(i)&lt;/td&gt;
&lt;td&gt;Partially&lt;/td&gt;
&lt;td&gt;IAM controls, not K8s users&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Emergency access procedure&lt;/td&gt;
&lt;td&gt;§164.312(a)(2)(ii)&lt;/td&gt;
&lt;td&gt;Not covered&lt;/td&gt;
&lt;td&gt;Operational policy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auto-logoff&lt;/td&gt;
&lt;td&gt;§164.312(a)(2)(iii)&lt;/td&gt;
&lt;td&gt;Not covered&lt;/td&gt;
&lt;td&gt;Application-level&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Security incident reporting&lt;/td&gt;
&lt;td&gt;§164.308(a)(6)&lt;/td&gt;
&lt;td&gt;Not covered&lt;/td&gt;
&lt;td&gt;Operational policy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;User off-boarding&lt;/td&gt;
&lt;td&gt;§164.308(a)(3)&lt;/td&gt;
&lt;td&gt;Not covered&lt;/td&gt;
&lt;td&gt;Operational policy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Malware protection&lt;/td&gt;
&lt;td&gt;§164.308(a)(5)&lt;/td&gt;
&lt;td&gt;Not covered&lt;/td&gt;
&lt;td&gt;Runtime security&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BAA with AWS&lt;/td&gt;
&lt;td&gt;§164.308(b)&lt;/td&gt;
&lt;td&gt;Acknowledged&lt;/td&gt;
&lt;td&gt;Disclaimer in every report&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Scorecard
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Domain&lt;/th&gt;
&lt;th&gt;Total Checks&lt;/th&gt;
&lt;th&gt;Stave Covers&lt;/th&gt;
&lt;th&gt;Gap&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Data Encryption&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Access Control&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Audit &amp;amp; Logging&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;2 (kops action logging, filesystem auditing)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Network Security&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data Integrity&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;1 (backup testing cadence)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Operational&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;7 (policy-based, not technical)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;42&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;32&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;10&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Stave covers &lt;strong&gt;32 of 42 requirements&lt;/strong&gt; across S3, IAM, EBS, EC2, VPC, RDS, ELB, and Kubernetes. The 10 remaining gaps are operational procedures (7), runtime agents (2 — filesystem auditing, kops action logging), and backup testing cadence (1). None of these are infrastructure configuration that Stave's evaluation model addresses.&lt;/p&gt;

&lt;h2&gt;
  
  
  Out of scope (operational)
&lt;/h2&gt;

&lt;p&gt;These requirements are policy-based, not infrastructure-based. Stave evaluates technical controls only:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Emergency access procedures&lt;/li&gt;
&lt;li&gt;User off-boarding processes&lt;/li&gt;
&lt;li&gt;Security incident reporting&lt;/li&gt;
&lt;li&gt;Malware protection&lt;/li&gt;
&lt;li&gt;Backup testing cadence&lt;/li&gt;
&lt;li&gt;Auto-logoff configuration&lt;/li&gt;
&lt;li&gt;kops/kubectl action logging (tool-specific operational logging)&lt;/li&gt;
&lt;li&gt;File system auditing / OSSEC (runtime agent, not config)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Stave's HIPAA report includes the BAA disclaimer: "Stave evaluates technical controls only. A BAA with AWS is a contractual prerequisite for HIPAA compliance that Stave cannot verify."&lt;/p&gt;

&lt;h2&gt;
  
  
  What Stave already does beyond the kops checklist
&lt;/h2&gt;

&lt;p&gt;The kops discussion focused on infrastructure configuration. Stave adds dimensions the checklist doesn't cover:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Compound risk detection&lt;/strong&gt; — public access + wildcard actions = lateral movement&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Duration tracking&lt;/strong&gt; — how long has each misconfiguration persisted?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CMK vs AWS-managed key&lt;/strong&gt; — the breach response distinction&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Object Lock compliance mode&lt;/strong&gt; — not just "enabled" but which mode&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Presigned URL restrictions&lt;/strong&gt; — controls the kops checklist never mentions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Acknowledged exceptions&lt;/strong&gt; — documented risk acceptance with compensating controls&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Proof of pass&lt;/strong&gt; — logic trace proving why something is compliant&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Offline evaluation&lt;/strong&gt; — air-gapped, deterministic, no credentials needed&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The coverage
&lt;/h2&gt;

&lt;p&gt;Stave covers 32 of 42 requirements from the kops HIPAA checklist across S3, IAM, EBS, EC2, VPC, RDS, ELB, and Kubernetes. The catalog now includes 3,558 controls with compound risk detection, duration tracking, and HIPAA citations.&lt;/p&gt;

&lt;p&gt;The 10 remaining gaps are operational procedures and runtime agents that fall outside infrastructure configuration evaluation. Every technical requirement from the original 2017 discussion is covered.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;The kops HIPAA discussion referenced in this article is &lt;a href="https://github.com/kubernetes/kops/issues/1776" rel="noopener noreferrer"&gt;github.com/kubernetes/kops/issues/1776&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>hipaa</category>
      <category>aws</category>
      <category>kubernetes</category>
      <category>security</category>
    </item>
    <item>
      <title>Is my S3 bucket HIPAA compliant? The question AWS can't answer</title>
      <dc:creator>Bala Paranj</dc:creator>
      <pubDate>Mon, 19 Oct 2026 12:19:26 +0000</pubDate>
      <link>https://dev.to/bala_paranj_059d338e44e7e/is-my-s3-bucket-hipaa-compliant-the-question-aws-cant-answer-ci9</link>
      <guid>https://dev.to/bala_paranj_059d338e44e7e/is-my-s3-bucket-hipaa-compliant-the-question-aws-cant-answer-ci9</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;✓ Human-authored analysis; AI used for formatting and proofreading.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Recurring theme on r/aws:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"I'm storing PHI in S3. I enabled encryption, turned on logging, set the bucket to private. Are we HIPAA compliant?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The top reply is always some version of: "AWS won't tell you that. You need to evaluate your configuration against the Security Rule yourself."&lt;/p&gt;

&lt;p&gt;That reply is correct and does not help.&lt;/p&gt;

&lt;h2&gt;
  
  
  What HIPAA requires for S3
&lt;/h2&gt;

&lt;p&gt;The HIPAA Security Rule doesn't mention S3. It defines four categories of safeguards that apply to any system storing electronic protected health information (ePHI):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Access Control&lt;/strong&gt; (SS 164.312(a)(1)) -- Only authorized users can reach PHI&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audit Controls&lt;/strong&gt; (SS 164.312(b)) -- All access to PHI is logged&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Encryption&lt;/strong&gt; (SS 164.312(a)(2)(iv), SS 164.312(e)(2)(ii)) -- Data encrypted at rest and in transit&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retention&lt;/strong&gt; (SS 164.316(b)(2)) -- PHI retained for minimum 6 years&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Teams get stuck mapping these to S3 settings. Encryption at rest is one checkbox. But which KMS key type? Is server access logging enough, or do you need CloudTrail object-level events? Does Block Public Access count if it's only set at the account level?&lt;/p&gt;

&lt;h2&gt;
  
  
  How Stave evaluates HIPAA compliance
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/sufield/stave" rel="noopener noreferrer"&gt;Stave&lt;/a&gt; is an open-source CLI that evaluates S3 configurations against compliance profiles. The built-in HIPAA profile maps 28 controls to specific Security Rule sections and evaluates them against an observation snapshot of your bucket configuration.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;stave evaluate &lt;span class="nt"&gt;--snapshot&lt;/span&gt; observations/snap.json &lt;span class="nt"&gt;--profile&lt;/span&gt; hipaa
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output is a structured report with pass/fail verdicts, regulatory citations, and remediation steps:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;═══ HIPAA Security Rule ═══
Bucket:    phi-patient-records
Account:   ***456
Snapshot:  2026-04-08T14:30:00Z
Result:    FAIL

── CRITICAL ──

  [FAIL] CONTROLS.001.STRICT — critical
  Compliance: §164.312(a)(2)(iv) — CMK required for key revocation during breach response
  Finding: Encryption uses AWS-managed key — customer cannot revoke
  Remediation: Change default encryption to SSE-KMS with a customer-managed key

  [PASS] CONTROLS.004 — critical
  Compliance: §164.312(e)(2)(ii) — Transmission Security

  [PASS] AUDIT.001 — critical
  Compliance: §164.312(b) — Audit Controls

  [PASS] ACCESS.001 — critical
  Compliance: §164.312(a)(1) — Access Control

── HIGH ──

  [FAIL] AUDIT.002 — high
  Compliance: §164.312(b) — Object-level logging for PHI access audit trail
  Finding: CloudTrail object-level data events not configured
  Remediation: Configure a CloudTrail trail with S3 data event selector
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each finding maps directly to a HIPAA citation. Your auditor can trace the control back to the regulation.&lt;/p&gt;

&lt;p&gt;AWS signs the Business Associate Agreement. AWS provides the encryption toggles, the logging infrastructure, and the access controls. But AWS will never evaluate whether your specific configuration satisfies the Security Rule. That is the customer's responsibility under the shared responsibility model.&lt;/p&gt;

&lt;p&gt;Stave bridges that gap. Instead of a 47-page checklist, you get a single command that produces a pass/fail verdict with the regulatory receipts to back it up.&lt;/p&gt;

</description>
      <category>hipaa</category>
      <category>aws</category>
      <category>s3</category>
      <category>security</category>
    </item>
    <item>
      <title>Prowler has HIPAA checks. What checklist-based compliance still misses.</title>
      <dc:creator>Bala Paranj</dc:creator>
      <pubDate>Sun, 18 Oct 2026 11:30:06 +0000</pubDate>
      <link>https://dev.to/bala_paranj_059d338e44e7e/prowler-has-hipaa-checks-what-checklist-based-compliance-still-misses-3ce5</link>
      <guid>https://dev.to/bala_paranj_059d338e44e7e/prowler-has-hipaa-checks-what-checklist-based-compliance-still-misses-3ce5</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;✓ Human-authored analysis; AI used for formatting and proofreading.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The 2018 conversation
&lt;/h2&gt;

&lt;p&gt;In &lt;a href="https://github.com/prowler-cloud/prowler/issues/227" rel="noopener noreferrer"&gt;Prowler issue #227&lt;/a&gt;, a user asked: "any plan to add [HIPAA checks]?"&lt;/p&gt;

&lt;p&gt;A contributor laid out a checklist consisting of MFA, VPC flow logs, encrypted EBS volumes, S3 bucket policies, encrypted RDS. The maintainer's response:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Adding HIPAA checks is no small task and I don't believe checks for &lt;em&gt;full&lt;/em&gt; compliance will be possible as it depends much upon how each user/application handles PHI."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;They shipped the checks and have continued expanding them. Prowler's release v5.22.0, March 2026 includes HIPAA compliance frameworks for AWS and Azure, listed as a supported framework in the README. The issue was closed in 2018, and HIPAA has been part of Prowler for years.&lt;/p&gt;

&lt;p&gt;MFA enabled? Check. Encrypted volumes? Check. Access logs? Check. These are useful checks. They're the same class of checks AWS Config provides. They're necessary but not sufficient.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the checklist misses
&lt;/h2&gt;

&lt;p&gt;The Prowler HIPAA checks (and AWS Config Conformance Pack, and Security Hub) all share the same architecture: &lt;strong&gt;individual rules evaluated independently against individual resources.&lt;/strong&gt; This creates three blind spots that no checklist can fill.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. No compound risk
&lt;/h3&gt;

&lt;p&gt;Prowler checks if a bucket is public. Prowler checks if a policy has wildcard actions. But it can't detect that the &lt;em&gt;combination&lt;/em&gt; is the S3+IAM lateral movement pattern behind most documented AWS breaches.&lt;/p&gt;

&lt;p&gt;Stave detects compound patterns across services:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[high] ec2_to_s3_lateral
Overbroad EC2 instance profile + unrestricted egress = S3 data exfiltration.
(CTL.EC2.PROFILE.OVERBROAD.001 + CTL.VPC.SG.EGRESS.001)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[critical] public_phi_exposure
Public S3 bucket + no encryption enforcement = PHI exposure.
(CTL.S3.PUBLIC.005 + CTL.S3.ENCRYPT.KMS.OWNERSHIP.001)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[high] vpc_endpoint_bypass
VPC endpoint with default policy = any principal on the VPC reaches any S3 bucket.
(CTL.VPC.ENDPOINT.GATEWAY.DEFAULTPOLICY.001)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No individual check catches these. They require cross-rule reasoning.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. No duration tracking
&lt;/h3&gt;

&lt;p&gt;Prowler tells you a resource is non-compliant right now. It doesn't tell you it's been non-compliant for 6 months.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Finding: phi-patient-records
  First unsafe: 2025-06-01T00:00:00Z
  Last seen:    2026-01-11T00:00:00Z
  Duration:     5,400+ hours (threshold: 168 hours)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For HIPAA, this distinction matters. A bucket that was briefly public during a deploy and caught within the SLA is different from one that's been exposed for 7 months. Duration is the missing dimension in every cloud security checklist.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. No PHI-specific depth
&lt;/h3&gt;

&lt;p&gt;The Prowler contributor's checklist is service-level: encrypted volumes, access logs, VPC flow logs. These are correct for general AWS hardening. But HIPAA S3 requires deeper analysis:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;PHI-Specific Check&lt;/th&gt;
&lt;th&gt;Prowler&lt;/th&gt;
&lt;th&gt;Stave&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CMK vs AWS-managed key&lt;/td&gt;
&lt;td&gt;No — checks "is KMS enabled?"&lt;/td&gt;
&lt;td&gt;Yes — &lt;code&gt;CTL.S3.ENCRYPT.KMS.OWNERSHIP.001&lt;/code&gt; fails on &lt;code&gt;alias/aws/s3&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Object Lock compliance mode&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes — &lt;code&gt;CTL.S3.LOCK.001&lt;/code&gt;–&lt;code&gt;003&lt;/code&gt; tiers: none=critical, governance=high, compliance=pass&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Presigned URL restrictions&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes — &lt;code&gt;CTL.S3.PRESIGNED.001&lt;/code&gt; checks &lt;code&gt;s3:signatureAge&lt;/code&gt;/&lt;code&gt;s3:authType&lt;/code&gt; conditions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;VPC endpoint without policy&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes — &lt;code&gt;CTL.VPC.ENDPOINT.GATEWAY.DEFAULTPOLICY.001&lt;/code&gt; detects the wormhole&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ACL escalation (WRITE_ACP)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes — &lt;code&gt;CTL.S3.ACL.ESCALATION.001&lt;/code&gt; detects privilege escalation path&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Latent exposure behind PAB&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes — &lt;code&gt;CTL.S3.PUBLIC.005&lt;/code&gt; detects public policy masked by PAB toggle&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HIPAA section citations&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes — every finding cites §164.312&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The Prowler maintainer was right: full HIPAA compliance depends on how each user handles PHI. But that's an argument for a tool that evaluates the actual PHI storage configuration, not just generic service settings.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the user in #227 needed
&lt;/h2&gt;

&lt;p&gt;The user asking for HIPAA checks in 2018 needed:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;A HIPAA profile&lt;/strong&gt; — a curated set of controls mapped to Security Rule sections, not a generic checklist&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compound risk detection&lt;/strong&gt; — dangerous combinations, not just individual failures&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Duration tracking&lt;/strong&gt; — how long has this been non-compliant?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PHI-specific controls&lt;/strong&gt; — CMK enforcement, Object Lock compliance mode, presigned URL restrictions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Acknowledged exceptions&lt;/strong&gt; — documented risk acceptance with compensating controls&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Offline evaluation&lt;/strong&gt; — auditor-ready evidence, not dashboard access&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Prowler shipped item 1 (a checklist with HIPAA framework tags). Items 2-6 remain unaddressed.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Stave fills the gap
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;stave apply &lt;span class="nt"&gt;--observations&lt;/span&gt; phi-observations/ &lt;span class="nt"&gt;--format&lt;/span&gt; text
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;═══ HIPAA Security Rule ═══
Bucket:    phi-patient-records
Result:    FAIL

── COMPOUND RISKS ──

  [critical] public_phi_exposure (triggers: CTL.S3.PUBLIC.005, CTL.S3.ENCRYPT.KMS.OWNERSHIP.001)
  Public bucket with no CMK encryption = PHI exposure

── critical ──

  [FAIL] CTL.S3.ACCESS.001 — critical
  Compliance: §164.312(a)(1) — Access control

  [FAIL] CTL.S3.ENCRYPT.KMS.OWNERSHIP.001 — critical
  Compliance: §164.312(a)(2)(iv) — CMK required for key revocation

── Summary ──

  critical: 0/4 passed
  high:     2/8 passed
  medium:   0/2 passed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;3,500+ controls + 770+ chain detectors. Every finding cites the HIPAA section. Duration tracked. Exceptions require compensating controls. Runs offline.&lt;/p&gt;

&lt;h2&gt;
  
  
  The answer to #227
&lt;/h2&gt;

&lt;p&gt;The Prowler maintainer said full HIPAA compliance checks aren't possible because it depends on how each user handles PHI. It's true that no tool can verify organizational policies, BAAs, or workforce training.&lt;/p&gt;

&lt;p&gt;But technical controls on PHI storage infrastructure are verifiable. A bucket either has CMK encryption or it doesn't. Object Lock is either in compliance mode or it isn't. The VPC endpoint either has a restrictive policy or it doesn't.&lt;/p&gt;

&lt;p&gt;Stave evaluates what's verifiable and says so explicitly:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Stave evaluates technical controls only. A BAA with AWS is a contractual prerequisite for HIPAA compliance that Stave cannot verify."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That disclaimer present in every HIPAA report is the honest boundary. Everything inside that boundary, Stave proves. Everything outside it, Stave acknowledges it can't.&lt;/p&gt;

&lt;p&gt;That's the answer issue #227 needed in 2018.&lt;/p&gt;

&lt;h2&gt;
  
  
  Coverage: the full Prowler HIPAA checklist
&lt;/h2&gt;

&lt;p&gt;Against the &lt;a href="https://github.com/prowler-cloud/prowler/issues/227" rel="noopener noreferrer"&gt;Prowler HIPAA checklist&lt;/a&gt;, every item is covered plus depth beyond the checklist:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Prowler Check&lt;/th&gt;
&lt;th&gt;Stave Controls&lt;/th&gt;
&lt;th&gt;Depth beyond the checklist&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Account: MFA Enabled&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;CTL.IAM.ROOT.MFA.001&lt;/code&gt;, &lt;code&gt;CTL.IAM.CONSOLE.MFA.001&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Root vs console distinguished&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Account: Root Credentials&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CTL.IAM.ROOT.ACCESSKEY.001&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Duration tracking on exposure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;S3: Encryption + IP Filter&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;CTL.S3.ENCRYPT.001&lt;/code&gt;–&lt;code&gt;004&lt;/code&gt;, &lt;code&gt;CTL.S3.NETWORK.001&lt;/code&gt;, &lt;code&gt;CTL.S3.NETWORK.VPC.001&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;CMK vs AWS-managed key; key policy audit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;S3: IAM Permissions&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;CTL.S3.ACCESS.001&lt;/code&gt;–&lt;code&gt;003&lt;/code&gt;, &lt;code&gt;CTL.IAM.POLICY.*&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;ACL escalation, presigned URL restrictions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;S3: Access Logs&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;CTL.S3.LOG.001&lt;/code&gt;, &lt;code&gt;CTL.S3.AUDIT.OBJECTLEVEL.001&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Log bucket integrity, retention, prefix&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;VPC: Flow Logging&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;CTL.VPC.FLOWLOG.001&lt;/code&gt;, &lt;code&gt;CTL.VPC.FLOWLOG.STATUS.001&lt;/code&gt;, &lt;code&gt;CTL.VPC.FLOWLOG.FORMAT.001&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Bidirectional check, NAT/TGW flow logs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;VPC: Flow Logs Encrypted&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CTL.VPC.FLOWLOG.ENCRYPT.001&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Ghost S3 destination detection&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;VPC: ELB Logging&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;CTL.ELB.LOG.001&lt;/code&gt;, &lt;code&gt;CTL.ELB.NLB.ACCESSLOG.001&lt;/code&gt;, &lt;code&gt;CTL.ELB.NLB.CONNLOG.001&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Permission audit, ghost bucket, lifecycle&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;EC2: Encrypted EBS Volumes&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;CTL.EC2.EBS.ENCRYPT.001&lt;/code&gt;, &lt;code&gt;CTL.EC2.EBS.ENCRYPT.CMK.001&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;KMS policy audit, SCP enforcement&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;EC2: Encrypted EBS Snapshots&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;CTL.EC2.EBS.SNAPSHOT.ENCRYPT.001&lt;/code&gt;, &lt;code&gt;CTL.EBS.ENCRYPT.KEYORIGIN.001&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Shared unencrypted snapshot detection&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;RDS: Encrypted RDS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;CTL.RDS.ENCRYPT.001&lt;/code&gt;, &lt;code&gt;CTL.RDS.ENCRYPT.CMK.001&lt;/code&gt;, &lt;code&gt;CTL.RDS.SNAPSHOT.ENCRYPT.001&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Backup encryption, cross-region, SSL enforcement&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  What Stave does that no checklist can
&lt;/h3&gt;

&lt;p&gt;Beyond checklist parity, Stave provides capabilities the Prowler architecture has no equivalent for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Compound risk detection&lt;/strong&gt; across services (770+ chain detectors)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Duration tracking&lt;/strong&gt; on every finding (SLA-based)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CMK vs AWS-managed key&lt;/strong&gt; distinction for PHI encryption&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Object Lock compliance mode&lt;/strong&gt; tiering for PHI retention&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Presigned URL restrictions&lt;/strong&gt; for PHI buckets&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Latent exposure detection&lt;/strong&gt; (public policy behind PAB)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ACL escalation detection&lt;/strong&gt; (WRITE_ACP privilege escalation)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Acknowledged exceptions&lt;/strong&gt; with mandatory compensating controls&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HIPAA §164.312 citations&lt;/strong&gt; on every finding&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Proof of pass&lt;/strong&gt; via logic trace&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The checklist covers breadth. Stave covers depth and cross-service reasoning.&lt;/p&gt;

</description>
      <category>hipaa</category>
      <category>aws</category>
      <category>security</category>
      <category>compliance</category>
    </item>
    <item>
      <title>Are presigned URLs a HIPAA risk? The presigned URL gap in HIPAA S3 compliance</title>
      <dc:creator>Bala Paranj</dc:creator>
      <pubDate>Sat, 17 Oct 2026 12:54:28 +0000</pubDate>
      <link>https://dev.to/bala_paranj_059d338e44e7e/are-presigned-urls-a-hipaa-risk-the-presigned-url-gap-in-hipaa-s3-compliance-p1e</link>
      <guid>https://dev.to/bala_paranj_059d338e44e7e/are-presigned-urls-a-hipaa-risk-the-presigned-url-gap-in-hipaa-s3-compliance-p1e</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;✓ Human-authored analysis; AI used for formatting and proofreading.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We use presigned URLs to share reports with providers. Are we HIPAA compliant? The discussion is split between "they're temporary, it's fine" and "they're a ticking time bomb." The nuance is in what your bucket policy enforces.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;A presigned URL embeds IAM credentials into the URL itself. Anyone with the URL can download the object without AWS credentials or MFA check. Default expiration is up to 7 days for IAM user credentials. If a presigned URL to a PHI object leaks via email, Slack, or a log file, you have an uncontrolled access window to protected health information.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The bucket policy doesn't restrict presigned URLs by default.&lt;/strong&gt; Presigned URLs bypass IAM policies, VPC endpoint conditions, and IP restrictions. Unless the bucket policy explicitly includes conditions for &lt;code&gt;s3:signatureAge&lt;/code&gt; or &lt;code&gt;s3:authType&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The HIPAA Citation
&lt;/h2&gt;

&lt;p&gt;HIPAA section 164.312(a)(1) requires access controls that restrict access to electronic PHI to authorized persons. A presigned URL that can be forwarded to any person on the internet is not an access control. It's an access delegation with no audit trail of who used it.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Stave Detects It
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/sufield/stave" rel="noopener noreferrer"&gt;Stave&lt;/a&gt; control CTL.S3.PRESIGNED.001 checks whether the bucket policy includes presigned URL restrictions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;CTL.S3.PRESIGNED.001&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Presigned URL Access Must Be Restricted&lt;/span&gt;
&lt;span class="na"&gt;severity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;medium&lt;/span&gt;
&lt;span class="na"&gt;compliance&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;hipaa&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;164.312(a)(1)"&lt;/span&gt;

&lt;span class="na"&gt;unsafe_predicate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;all&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;field&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;properties.storage.kind&lt;/span&gt;
      &lt;span class="na"&gt;op&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;eq&lt;/span&gt;
      &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bucket&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;field&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;properties.storage.access.presigned_url_restricted&lt;/span&gt;
      &lt;span class="na"&gt;op&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;eq&lt;/span&gt;
      &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The control checks for two specific guardrails in the bucket policy:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;s3:signatureAge&lt;/code&gt;&lt;/strong&gt; Limits how long a presigned URL remains valid. A &lt;code&gt;Deny&lt;/code&gt; statement with &lt;code&gt;NumericGreaterThan&lt;/code&gt; on &lt;code&gt;s3:signatureAge&lt;/code&gt; of 600000 (10 minutes) forces short-lived URLs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;s3:authType&lt;/code&gt;&lt;/strong&gt; A &lt;code&gt;Deny&lt;/code&gt; statement with &lt;code&gt;StringNotEquals&lt;/code&gt; on &lt;code&gt;s3:authType&lt;/code&gt; set to &lt;code&gt;REST-HEADER&lt;/code&gt; blocks presigned URL access, requiring direct IAM authentication.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;stave evaluate &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--controls&lt;/span&gt; controls/s3/access/ &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--observations&lt;/span&gt; observations/ &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--eval-time&lt;/span&gt; 2026-04-08T00:00:00Z
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;Security State&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;NON_COMPLIANT&lt;/span&gt;

&lt;span class="na"&gt;Findings&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;phi-reports-bucket&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="s"&gt;CTL.S3.PRESIGNED.001  UNSAFE  Presigned URL access unrestricted&lt;/span&gt;
      &lt;span class="s"&gt;Bucket policy does not restrict presigned URL access. Presigned&lt;/span&gt;
      &lt;span class="s"&gt;URLs can provide long-lived unauthenticated access to objects.&lt;/span&gt;

      &lt;span class="s"&gt;Remediation&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Add a Deny statement with Condition NumericGreaterThan&lt;/span&gt;
      &lt;span class="s"&gt;s3:signatureAge (e.g., 600000 for 10 minutes) or StringNotEquals&lt;/span&gt;
      &lt;span class="s"&gt;s3:authType REST-HEADER to block presigned URL access.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Presigned URLs are inherently uncontrolled. The security comes from the bucket policy conditions that constrain them, not from the URLs themselves. If your PHI bucket policy doesn't include &lt;code&gt;s3:signatureAge&lt;/code&gt; or &lt;code&gt;s3:authType&lt;/code&gt; conditions, every presigned URL is a potential breach vector with no expiration guardrail beyond the IAM credential lifetime. Stave flags this gap before an auditor or an attacker finds it.&lt;/p&gt;

</description>
      <category>hipaa</category>
      <category>aws</category>
      <category>s3</category>
      <category>security</category>
    </item>
    <item>
      <title>Do we need Object Lock for PHI? HIPAA S3 retention: Object Lock compliance mode is not optional</title>
      <dc:creator>Bala Paranj</dc:creator>
      <pubDate>Fri, 16 Oct 2026 12:53:47 +0000</pubDate>
      <link>https://dev.to/bala_paranj_059d338e44e7e/do-we-need-object-lock-for-phi-hipaa-s3-retention-object-lock-compliance-mode-is-not-optional-g01</link>
      <guid>https://dev.to/bala_paranj_059d338e44e7e/do-we-need-object-lock-for-phi-hipaa-s3-retention-object-lock-compliance-mode-is-not-optional-g01</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;✓ Human-authored analysis; AI used for formatting and proofreading.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The thread repeats on r/aws: a team stores Protected Health Information (PHI) in S3, has versioning enabled, and assumes that's sufficient for retention. Someone suggests Object Lock. The team pushes back. It's complex and they already have lifecycle policies.&lt;/p&gt;

&lt;p&gt;The answer: &lt;strong&gt;Object Lock in Compliance mode is not optional for HIPAA PHI buckets.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What HIPAA Requires
&lt;/h2&gt;

&lt;p&gt;HIPAA section 164.316(b)(2) mandates a 6-year retention period for documentation related to PHI. This is a regulatory requirement that auditors verify.&lt;/p&gt;

&lt;p&gt;Versioning alone doesn't satisfy this. A versioned object can still be permanently deleted by removing all versions and delete markers. Lifecycle policies can expire versions. An IAM principal with &lt;code&gt;s3:DeleteObject&lt;/code&gt; can remove data before the retention period expires.&lt;/p&gt;

&lt;p&gt;Object Lock provides WORM (Write Once Read Many) protection. But the mode matters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Governance mode&lt;/strong&gt;: Users with &lt;code&gt;s3:BypassGovernanceRetention&lt;/code&gt; permission can override the lock. This is a privileged bypass.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compliance mode&lt;/strong&gt;: No user, including the root account, can delete or modify protected objects during the retention period. This is the only mode that provides the tamper-proof guarantee HIPAA requires.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How Stave Detects It
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/sufield/stave" rel="noopener noreferrer"&gt;Stave&lt;/a&gt; evaluates this through three controls that tier the severity:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Control&lt;/th&gt;
&lt;th&gt;What it checks&lt;/th&gt;
&lt;th&gt;Severity&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CTL.S3.LOCK.001&lt;/td&gt;
&lt;td&gt;Object Lock not enabled on compliance-tagged bucket&lt;/td&gt;
&lt;td&gt;MEDIUM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CTL.S3.LOCK.002&lt;/td&gt;
&lt;td&gt;Object Lock uses GOVERNANCE mode instead of COMPLIANCE&lt;/td&gt;
&lt;td&gt;MEDIUM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CTL.S3.LOCK.003&lt;/td&gt;
&lt;td&gt;Retention period shorter than 2190 days (6 years)&lt;/td&gt;
&lt;td&gt;MEDIUM&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This creates a severity cascade. A PHI bucket without Object Lock fails CTL.S3.LOCK.001. A PHI bucket with Object Lock in Governance mode passes LOCK.001 but fails LOCK.002. A PHI bucket with Compliance mode but a 365-day retention period passes both but fails LOCK.003.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;stave evaluate &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--controls&lt;/span&gt; controls/s3/lock/ &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--observations&lt;/span&gt; observations/ &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--eval-time&lt;/span&gt; 2026-04-08T00:00:00Z
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output showing the tiered findings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;Security State&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;NON_COMPLIANT&lt;/span&gt;

&lt;span class="na"&gt;Findings&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;phi-data-bucket&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="s"&gt;CTL.S3.LOCK.001  UNSAFE  Object Lock not enabled&lt;/span&gt;
      &lt;span class="s"&gt;Compliance-tagged bucket does not have S3 Object Lock enabled.&lt;/span&gt;
      &lt;span class="s"&gt;Objects can be deleted or overwritten without WORM protection.&lt;/span&gt;

  &lt;span class="na"&gt;phi-archive-bucket&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="s"&gt;CTL.S3.LOCK.002  UNSAFE  GOVERNANCE mode instead of COMPLIANCE&lt;/span&gt;
      &lt;span class="s"&gt;PHI bucket with Object Lock is using GOVERNANCE mode instead of&lt;/span&gt;
      &lt;span class="s"&gt;COMPLIANCE mode. Users with special permissions can override retention.&lt;/span&gt;

  &lt;span class="na"&gt;phi-logs-bucket&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="s"&gt;CTL.S3.LOCK.003  UNSAFE  Retention period 365 days &amp;lt; 2190 days required&lt;/span&gt;
      &lt;span class="s"&gt;PHI bucket Object Lock retention period is shorter than the HIPAA&lt;/span&gt;
      &lt;span class="s"&gt;minimum of 6 years (2190 days).&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;HIPAA PHI retention is not "enable versioning and hope for the best." It's a specific requirement: 6 years, tamper-proof, no privileged override. Object Lock in Compliance mode with a 2190-day minimum retention period is the only S3 configuration that satisfies all three conditions. Stave detects each gap independently, so you know where your buckets fall on the compliance spectrum.&lt;/p&gt;

</description>
      <category>hipaa</category>
      <category>aws</category>
      <category>s3</category>
      <category>security</category>
    </item>
  </channel>
</rss>
