<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[The GRC Notebook]]></title><description><![CDATA[The GRC Notebook]]></description><link>https://18xban.hashnode.dev</link><image><url>https://cdn.hashnode.com/uploads/logos/6aa9ab6f5e60cef18e9a8e9b/137273e9-bdbb-458e-ba87-aa333561920a.png</url><title>The GRC Notebook</title><link>https://18xban.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Mon, 21 Sep 2026 15:05:36 GMT</lastBuildDate><atom:link href="https://18xban.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Your First Security Policy Set (What You Actually Need)]]></title><description><![CDATA[@18xBan · GRC Series · Chapter 09


40 documents, 4 approvals, 0 readers.

Monday, 9:00 AM: forty policies in three minutes
Tom Bergeron starts as a backend developer. Farah's onboarding email is frie]]></description><link>https://18xban.hashnode.dev/your-first-security-policy-set-what-you-actually-need</link><guid isPermaLink="true">https://18xban.hashnode.dev/your-first-security-policy-set-what-you-actually-need</guid><category><![CDATA[Security Policy]]></category><category><![CDATA[ISO 27001]]></category><category><![CDATA[policy management]]></category><category><![CDATA[compliance ]]></category><category><![CDATA[grc]]></category><dc:creator><![CDATA[18xBan]]></dc:creator><pubDate>Sun, 20 Sep 2026 20:20:58 GMT</pubDate><content:encoded><![CDATA[<p><em>@18xBan · GRC Series · Chapter 09</em></p>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/8feed019-201b-4bf6-b541-5ef4917b8d4b.png" alt="" style="display:block;margin:0 auto" />

<p>40 documents, 4 approvals, 0 readers.</p>
<hr />
<h2>Monday, 9:00 AM: forty policies in three minutes</h2>
<p>Tom Bergeron starts as a backend developer. Farah's onboarding email is friendly and efficient:</p>
<blockquote>
<p><em>Before you get your laptop, please read and acknowledge the security policies on the shared drive. There are 40. Tick the box in the onboarding form when you're done.</em></p>
</blockquote>
<p>The onboarding form records a timestamp per policy. Tom's 40 timestamps span <strong>3 minutes and 42 seconds</strong>.</p>
<p>That's 5.5 seconds per document. Nobody is going to pretend that's reading. Tom isn't lazy; he did exactly what the process asked him to do, which was tick boxes.</p>
<p>Here's the part that matters for you: those 40 acknowledgments are now your evidence that Wayne communicates its policies. An auditor will look at the timestamps and reject it, and they'll be right to.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/51f3c9cf-cca5-4c55-ac99-5cf24359aebd.png" alt="" style="display:block;margin:0 auto" />

<p>40 "read and understood" ticks, 5.5 seconds apart.</p>
<hr />
<h2>11:20 AM: the question the policies can't answer</h2>
<p>Tom pings you 2 hours later with an entirely reasonable question:</p>
<blockquote>
<p><em>I'm debugging a customer's failed import. Can I paste the error log into the AI assistant our team uses? And can I use my own laptop until mine arrives?</em></p>
</blockquote>
<p>You go looking for the answer.</p>
<p>On the laptop question, Wayne has two policies that disagree. The <em>Equipment Use Policy</em> from 2019 says company devices only, no exceptions. The <em>Remote Work Policy</em> from 2020, written in a hurry, allows personal devices "provided antivirus is installed."</p>
<p>On the AI question, 40 policies say nothing at all. Not "no." Nothing.</p>
<p>So Tom gets his answer from the only source available: whatever the person next to him does. That is what an unwritten policy looks like in practice, and it's also what the last 3 posts kept pointing at. The assurance map had a column for "the rule," and half the rows said <em>nobody wrote one.</em></p>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/4bed85b1-42f6-494c-94fd-8fb4c878ccfe.png" alt="" style="display:block;margin:0 auto" />

<p>Two policies disagree, 40 say nothing, so the answer comes from the desk next door.</p>
<p><strong>The unpopular truth:</strong> a policy nobody can find or understand isn't a weak control. It's an unwritten rule with a document attached, and the document makes it worse, because now you've told an auditor what you claim to do.</p>
<hr />
<h2>What's actually in the 40?</h2>
<p>Before rewriting anything, inventory what's there. Start with the metadata, which takes a minute.</p>
<pre><code class="language-powershell">Get-ChildItem "\\wayne-fs01\Shared\Policies" -Recurse -Include *.docx,*.pdf |
  Select-Object Name, LastWriteTime,
    @{n='AgeYears';e={[math]::Round(((Get-Date) - $_.LastWriteTime).TotalDays / 365, 1)}} |
  Sort-Object LastWriteTime | Format-Table -AutoSize
</code></pre>
<pre><code class="language-text">Name                                   LastWriteTime       AgeYears
----                                   -------------       --------
Fax_and_Telephone_Use.docx             2018-03-12 10:41         6.6
Equipment_Use_Policy.docx              2019-06-04 16:22         5.4
Remote_Work_Policy_FINAL_v2.docx       2020-04-02 22:07         4.6
Copy of Password_Policy.docx           2021-02-18 09:15         3.7
Password_Standard_v3.docx              2021-02-18 09:51         3.7
...
Incident_Response_Plan.docx            2021-11-09 14:30         3.0
</code></pre>
<p><em>Sample output (illustrative). Wayne Industries is fictional.</em></p>
<p>Then read what's inside them.40 documents is an afternoon with coffee, and it's worth doing by hand, but a quick scan tells you where to look first.</p>
<pre><code class="language-python"># pip install python-docx
import glob, re
from docx import Document

FLAGS = {
    "placeholder": r"\[Insert|\[Company Name\]|&lt;Your Company&gt;|XXX",
    "dead tech":   r"(?i)fax|blackberry|on-?prem(ise)? exchange|CD-ROM",
    "no owner":    r"(?i)owner:\s*$|approved by:\s*$",
}

for path in glob.glob("policies/*.docx"):
    text = "\n".join(p.text for p in Document(path).paragraphs)
    hits = [name for name, pattern in FLAGS.items() if re.search(pattern, text)]
    if hits:
        print(f"{path.split('/')[-1]:45} {', '.join(hits)}")
</code></pre>
<pre><code class="language-text">Acceptable_Use_Policy.docx                    placeholder, no owner
Fax_and_Telephone_Use.docx                    dead tech
Equipment_Use_Policy.docx                     dead tech, no owner
Mobile_Device_Policy.docx                     placeholder, dead tech
Data_Retention_Policy.docx                    no owner
</code></pre>
<p><em>Sample output (illustrative). Wayne Industries is fictional.</em></p>
<p>The tally for Wayne's 40:</p>
<table>
<thead>
<tr>
<th>What the review found</th>
<th>Count</th>
</tr>
</thead>
<tbody><tr>
<td>Last edited in 2021 or earlier</td>
<td>31</td>
</tr>
<tr>
<td>Name systems Wayne doesn't use (fax, on-prem Exchange, BlackBerry)</td>
<td>9</td>
</tr>
<tr>
<td>Still contain template placeholders</td>
<td>5</td>
</tr>
<tr>
<td>No named owner or approver</td>
<td>12</td>
</tr>
<tr>
<td>Carry a dated management approval</td>
<td>4</td>
</tr>
<tr>
<td>Directly contradict another policy</td>
<td>6 (three pairs)</td>
</tr>
<tr>
<td>Acknowledgment records before this year</td>
<td>0</td>
</tr>
</tbody></table>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/fec7030e-c04d-4bf5-a331-18158c02cc00.png" alt="" style="display:block;margin:0 auto" />

<p>What 40 inherited documents actually contained.</p>
<hr />
<h2>Policy, Standard, Procedure, Guideline</h2>
<p>Most policy sets are bad because everything got written as a "policy." 4different document types got merged into one, so the document is both too vague to follow and too detailed to survive a year.</p>
<table>
<thead>
<tr>
<th>Type</th>
<th>Answers</th>
<th>Changes</th>
<th>Approved by</th>
<th>Wayne example</th>
</tr>
</thead>
<tbody><tr>
<td>Policy</td>
<td>What we do and why</td>
<td>Rarely, once a year at most</td>
<td>Top management (umbrella) or a senior manager</td>
<td>"Access is granted on least privilege and removed when someone leaves"</td>
</tr>
<tr>
<td>Standard</td>
<td>The specific mandatory settings</td>
<td>When technology changes</td>
<td>The control owner</td>
<td>"Passphrases of 14+ characters; MFA on all remote access"</td>
</tr>
<tr>
<td>Procedure</td>
<td>The steps, in order</td>
<td>Whenever the tool changes</td>
<td>The person who runs it</td>
<td>"How to disable a leaver in AD and Deskline"</td>
</tr>
<tr>
<td>Guideline</td>
<td>Advice, not obligation</td>
<td>Freely</td>
<td>Anyone</td>
<td>"How to pick a good passphrase"</td>
</tr>
</tbody></table>
<p>Why this matters practically: when NIST changed its password advice, a company with a password <em>standard</em> updated one page and the control owner signed it. A company with a password <em>policy</em> needed a management approval cycle to change a number, so it didn't, and kept 90-day rotation for four more years.</p>
<p>Put obligations in policy. Put numbers in standards. Put clicks in procedures.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/7df5290c-9428-4547-8a71-c13e0e9cc94a.png" alt="" style="display:block;margin:0 auto" />

<p>One of these needs the CEO's signature. Three don't.</p>
<hr />
<h2>What the standards actually ask for</h2>
<p>Less than you'd think, and more specifically than you'd think.</p>
<p><strong>ISO 27001 Clause 5.2</strong> names exactly one document: the information security policy, established by top management. There's no required list beyond that.</p>
<p><strong>Annex A 5.1</strong> is where the set comes from. It asks for the information security policy and topic-specific policies to be defined, approved by management, published, communicated to and acknowledged by relevant personnel and relevant interested parties, and reviewed at planned intervals and if significant changes occur. Which topics you need comes out of your risk assessment and your Statement of Applicability, not out of a vendor's list of 40.</p>
<p><strong>NIST CSF 2.0 GV.PO-01 and GV.PO-02</strong> ask for policy that is established, communicated and <em>enforced</em>, then reviewed and updated when requirements, threats, technology or mission change. The implementation examples in the CSF explicitly include requiring people to acknowledge receipt when first hired, annually, and whenever a policy is updated.</p>
<p><strong>SOC 2 CC5.3</strong> expects control activities to be deployed through policies that establish what's expected and procedures that put them into action.</p>
<p><strong>PCI DSS v4.0.1 12.1.1 and 12.1.2</strong> want a security policy that is established, published, maintained and disseminated, and reviewed at least once every 12 months.</p>
<p>Notice what none of them say: "have 40 documents."</p>
<hr />
<h2>The published template: SANS</h2>
<p>Don't write policies from a blank page. The SANS Institute publishes a free library of information security policy templates, around 30 of them, covering acceptable use, password construction and protection, remote access, email, disaster recovery, web application security and more. They're plain, short and deliberately editable.</p>
<p><a href="https://www.sans.org/information-security-policy">https://www.sans.org/information-security-policy</a></p>
<p>Two rules when you use them.</p>
<p><strong>Delete more than you add.</strong> The SANS templates cover situations Wayne doesn't have. Every clause you keep is a clause an auditor can test you against. A policy that says "we log all privileged activity and review it weekly" is a finding waiting to happen if nobody reviews anything weekly.</p>
<p><strong>Replace every example with your own environment.</strong> If the document still says <code>[Insert Company Name]</code>, it tells the auditor exactly how much thought went into it. 5 of Wayne's 40 failed on this alone.</p>
<hr />
<h2>Wayne's first policy set: 9 policies, 4 standards</h2>
<p>Each one exists because a specific thing went wrong in the last 8 weeks, not because it appeared on a list.</p>
<table>
<thead>
<tr>
<th>#</th>
<th>Policy</th>
<th>Owner</th>
<th>Why Wayne needs it</th>
<th>Built from</th>
</tr>
</thead>
<tbody><tr>
<td>1</td>
<td>Information Security Policy (umbrella)</td>
<td>Maya (CEO approves)</td>
<td>Sets direction and names who owns security</td>
<td>ISO 27001 Cl. 5.2 structure</td>
</tr>
<tr>
<td>2</td>
<td>Acceptable Use</td>
<td>Farah</td>
<td>The laptop question, the AI assistant question</td>
<td>SANS Acceptable Use</td>
</tr>
<tr>
<td>3</td>
<td>Access Control</td>
<td>You</td>
<td>Three Domain Admins, MFA at 54%, leavers</td>
<td>SANS remote access + password templates</td>
</tr>
<tr>
<td>4</td>
<td>Data Classification and Handling</td>
<td>Priya</td>
<td>A production export sat in a staging bucket</td>
<td>Written from the risk assessment</td>
</tr>
<tr>
<td>5</td>
<td>Change Management</td>
<td>Priya</td>
<td>Deploys to production need no approval</td>
<td>SANS-style, kept to one page</td>
</tr>
<tr>
<td>6</td>
<td>Incident Response</td>
<td>You</td>
<td>No plan that anyone has tested</td>
<td>SANS Security Response Plan</td>
</tr>
<tr>
<td>7</td>
<td>Backup and Recovery</td>
<td>You</td>
<td>412 successful jobs, zero restore tests</td>
<td>SANS Disaster Recovery Plan Policy</td>
</tr>
<tr>
<td>8</td>
<td>Vendor and Third-Party</td>
<td>Dan</td>
<td>12 vendors, none reviewed</td>
<td>Written from the vendor register</td>
</tr>
<tr>
<td>9</td>
<td>Secure Development</td>
<td>Priya</td>
<td>Anyone can merge to <code>main</code></td>
<td>SANS Web Application Security</td>
</tr>
</tbody></table>
<p>And the four standards that sit under them:</p>
<table>
<thead>
<tr>
<th>Standard</th>
<th>Sits under</th>
<th>Contains</th>
</tr>
</thead>
<tbody><tr>
<td>Authentication Standard</td>
<td>Access Control</td>
<td>Passphrase length, MFA scope, session limits</td>
</tr>
<tr>
<td>Logging Standard</td>
<td>Incident Response</td>
<td>What's logged, where it goes, retention</td>
</tr>
<tr>
<td>Encryption Standard</td>
<td>Data Classification</td>
<td>Algorithms, TLS versions, key rotation</td>
</tr>
<tr>
<td>Endpoint Standard</td>
<td>Acceptable Use</td>
<td>Disk encryption, patching windows, personal devices</td>
</tr>
</tbody></table>
<p>The AI assistant question lands in Acceptable Use, with one clear paragraph: approved tools only, no customer data or credentials in prompts, and a named person who can approve a new tool. The laptop question lands in the Endpoint Standard, and the 2019 and 2020 documents that disagreed are retired the same day.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/f363b3bc-3829-4659-8dc2-3f11cd12fb25.png" alt="" style="display:block;margin:0 auto" />

<p>9 policies, 4 standards, one owner each.</p>
<p><strong>The unpopular truth:</strong> the right number of policies is the smallest number you can keep current. 12 documents that are reviewed and followed beat 40 that are complete and ignored, and no auditor has ever given credit for volume.</p>
<hr />
<h2>What happened to the other 27</h2>
<p>Retiring documents is the part people skip, and it's the part that reduces risk immediately. An obsolete policy is a live obligation until you formally kill it.</p>
<table>
<thead>
<tr>
<th>Outcome</th>
<th>Count</th>
<th>Notes</th>
</tr>
</thead>
<tbody><tr>
<td>Rewritten as one of the nine policies</td>
<td>6</td>
<td>Kept the useful content, changed the owner and the dates</td>
</tr>
<tr>
<td>Converted into the four standards</td>
<td>5</td>
<td>Two merged into the Authentication Standard</td>
</tr>
<tr>
<td>Merged into another document</td>
<td>7</td>
<td>Mostly one-page fragments</td>
</tr>
<tr>
<td>Retired and archived</td>
<td>22</td>
<td>Moved to <code>Policies/Archive/2021-retired/</code>, read-only</td>
</tr>
</tbody></table>
<p>Every retired document gets a line in a retirement log: file name, date retired, who approved the retirement, and where it went. That log is evidence too. It's how you answer "what happened to the Fax and Telephone Use Policy?" without shrugging.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/26ecb1ef-6499-4241-88c8-6532a977871c.png" alt="" style="display:block;margin:0 auto" />

<p>40 in, 13 out, and a retirement log for the rest.</p>
<hr />
<h2>The anatomy of a policy that gets used</h2>
<p>Every document in the set has the same 11 fields. The first 6 are the document; the last 5 are what makes it auditable.</p>
<table>
<thead>
<tr>
<th>Field</th>
<th>What goes in it</th>
</tr>
</thead>
<tbody><tr>
<td>Purpose</td>
<td>One or two sentences on why this exists</td>
</tr>
<tr>
<td>Scope</td>
<td>Who and what it applies to, including contractors</td>
</tr>
<tr>
<td>Policy statements</td>
<td>The obligations, numbered, each one testable</td>
</tr>
<tr>
<td>Roles and responsibilities</td>
<td>Who does what, by name or role</td>
</tr>
<tr>
<td>Exceptions</td>
<td>How to request one, who approves, how long it lasts</td>
</tr>
<tr>
<td>Related documents</td>
<td>The standards and procedures beneath it</td>
</tr>
<tr>
<td>Owner</td>
<td>The accountable person, from the ownership list</td>
</tr>
<tr>
<td>Approver and date</td>
<td>Who signed it, and when</td>
</tr>
<tr>
<td>Version</td>
<td>Sequential, with a change log line per version</td>
</tr>
<tr>
<td>Effective date</td>
<td>When it starts applying</td>
</tr>
<tr>
<td>Review cycle</td>
<td>Annually, or on significant change</td>
</tr>
</tbody></table>
<p>Two more rules Wayne adopted. Keep each policy under two pages, because length is the enemy of reading. And write statements people can actually check: "production changes require approval from a second engineer" is testable, "employees should exercise appropriate care" is not.</p>
<hr />
<h2>Fixing the acknowledgment problem</h2>
<p>40 ticks in 3 minutes was the symptom. The cure isn't a longer form.</p>
<ul>
<li><p><strong>Assign by role.</strong> Tom gets the umbrella policy, Acceptable Use, the Endpoint Standard and Secure Development. 4 documents, not 40. Farah gets a different 4.</p>
</li>
<li><p><strong>Acknowledge at hire, annually, and on change.</strong> That's straight out of the CSF implementation examples, and it's also what an auditor samples against.</p>
</li>
<li><p><strong>Keep the record in one place,</strong> with the policy version number attached. "Acknowledged Acceptable Use v2.1 on 12 November" is evidence. "Acknowledged the policies" is not.</p>
</li>
<li><p><strong>Make one thing memorable.</strong> A 10 minute walkthrough in week one beats any number of ticks, and it means new joiners know who to ask.</p>
</li>
</ul>
<p>Tom re-acknowledges his 4 the following week, with a version number against each, having actually read 8 pages.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/bccbe062-6569-4b49-a5f7-2ef367690511.png" alt="" style="display:block;margin:0 auto" />

<p>4 documents, versioned, read in an hour instead of ticked in 3 minutes.</p>
<hr />
<h2>Evidence #9</h2>
<p>By Friday, the folder holds:</p>
<ul>
<li><p>The Information Security Policy, one page, approved and signed by Maya, dated.</p>
</li>
<li><p>8 topic-specific policies and four standards, each with an owner, an approver and a review date.</p>
</li>
<li><p>The policy register: 13 live documents, version, owner, approval date, next review.</p>
</li>
<li><p>The retirement log for the 22 archived documents.</p>
</li>
<li><p>Per-role acknowledgment records, with versions, starting with Tom's 4.</p>
</li>
</ul>
<p><strong>Evidence #9:</strong> an approved, owned, versioned policy set that matches how Wayne actually works.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/32742899-8c6d-49ab-871e-59ba6763a990.png" alt="" style="display:block;margin:0 auto" />

<p>One signature that means something, instead of 40 ticks that don't.</p>
<hr />
<h2>What an auditor accepts vs rejects</h2>
<table>
<thead>
<tr>
<th>Accepted</th>
<th>Rejected</th>
</tr>
</thead>
<tbody><tr>
<td>A policy approved and dated by top management</td>
<td>An unsigned document with no approval page</td>
</tr>
<tr>
<td>Acknowledgments tied to a specific version</td>
<td>"All staff acknowledged the policies" with no version</td>
</tr>
<tr>
<td>Statements that match what the systems do</td>
<td>A policy claiming reviews nobody performs</td>
</tr>
<tr>
<td>A review cycle with the last review recorded</td>
<td>"Reviewed annually" with the last edit in 2021</td>
</tr>
<tr>
<td>A retirement log for withdrawn documents</td>
<td>Obsolete policies still sitting in the live folder</td>
</tr>
<tr>
<td>Policies assigned by role, with evidence of delivery</td>
<td>40 ticks in 3 minutes</td>
</tr>
<tr>
<td>Standards that explain the numbers in the policy</td>
<td>Numbers scattered across 6 documents that disagree</td>
</tr>
</tbody></table>
<hr />
<h2>What you actually do on Monday</h2>
<ol>
<li><p><strong>List every policy document you have,</strong> with its last modified date and its owner. Most teams are surprised twice.</p>
</li>
<li><p><strong>Open the five oldest.</strong> Look for dead technology, placeholders and obligations nobody performs.</p>
</li>
<li><p><strong>Find the contradictions.</strong> Passwords, devices and data retention are where they hide.</p>
</li>
<li><p><strong>Write the umbrella policy first,</strong> one page, and get it signed by whoever is top management in your company.</p>
</li>
<li><p><strong>Pick your topic list from your risks,</strong> not from a template index.</p>
</li>
<li><p><strong>Retire the rest formally,</strong> with a log. Don't just leave them there.</p>
</li>
<li><p><strong>Reassign acknowledgments by role,</strong> with version numbers.</p>
</li>
</ol>
<hr />
<h2>Framework mapping</h2>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/e3255923-6b62-453c-a077-b68b970a9fe2.png" alt="" style="display:block;margin:0 auto" />

<p>Every framework asks for a policy set. None of them asks for 40 documents.</p>
<hr />
<h2>Maturity ladder</h2>
<table>
<thead>
<tr>
<th>Stage</th>
<th>20 people</th>
<th>200 people</th>
<th>2,000 people</th>
</tr>
</thead>
<tbody><tr>
<td>Umbrella policy</td>
<td>One page, signed by the founder</td>
<td>One page, approved yearly by the exec team</td>
<td>Board-approved, with a policy framework document</td>
</tr>
<tr>
<td>Topic policies</td>
<td>5 to 8, driven by real risks</td>
<td>10 to 15, mapped to the SoA</td>
<td>20+, owned across departments</td>
</tr>
<tr>
<td>Standards</td>
<td>Inside the policies, honestly</td>
<td>Separate, owned by control owners</td>
<td>A standards library with its own review cycle</td>
</tr>
<tr>
<td>Delivery</td>
<td>A conversation in week one</td>
<td>Role-based assignment in an onboarding tool</td>
<td>Policy platform with attestation reporting</td>
</tr>
<tr>
<td>Review</td>
<td>Calendar reminder, one afternoon a year</td>
<td>Annual cycle with owners and change logs</td>
<td>Rolling reviews tied to management review</td>
</tr>
</tbody></table>
<p>At 20 people, "the policy set" is often one document with headings and a signature page, and that is genuinely fine. What's not fine is forty inherited documents nobody has read.</p>
<p><strong>Where Wayne sits:</strong> 13 live documents, all owned, all approved, all dated this month. The review cycle starts next quarter, and the real test is whether anyone reaches for them the next time a question like Tom's arrives.</p>
<hr />
<h2>Cheatsheet</h2>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/23994c49-aede-4731-8022-5402abf00925.png" alt="" style="display:block;margin:0 auto" />

<p>The policy set, on one page.</p>
<hr />
<h2>The takeaway</h2>
<p>Write the smallest set you can keep current, and kill the rest formally. Put obligations in policies, numbers in standards, and clicks in procedures. Acknowledgment means a named person, a specific version and a date. If a policy says something Wayne doesn't do, it's not documentation. It's a finding you wrote yourself.</p>
<hr />
<blockquote>
<p>⚠️ This content is for educational purposes only. Wayne Industries is a fictional company. Nothing here is legal, audit, or compliance advice, validate against your own auditor and jurisdiction.</p>
</blockquote>
]]></content:encoded></item><item><title><![CDATA[Three Lines of Defence Explained]]></title><description><![CDATA[@18xBan · GRC Series · Chapter 08


3 lines, drawn honestly.2 of them are the same person, and one is empty.

Friday, 9:10 AM: "Who reviews you?"
Gotham Mutual's analyst sends the 3 questionnaire item]]></description><link>https://18xban.hashnode.dev/three-lines-of-defence-explained</link><guid isPermaLink="true">https://18xban.hashnode.dev/three-lines-of-defence-explained</guid><category><![CDATA[three-lines-of-defense]]></category><category><![CDATA[Governance]]></category><category><![CDATA[risk management]]></category><category><![CDATA[grc]]></category><category><![CDATA[internal audit]]></category><dc:creator><![CDATA[18xBan]]></dc:creator><pubDate>Sun, 20 Sep 2026 19:15:31 GMT</pubDate><content:encoded><![CDATA[<p><em>@18xBan · GRC Series · Chapter 08</em></p>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/a6737dee-6028-49f7-9458-95f9a1da1cb1.png" alt="" style="display:block;margin:0 auto" />

<p>3 lines, drawn honestly.2 of them are the same person, and one is empty.</p>
<hr />
<h2>Friday, 9:10 AM: "Who reviews you?"</h2>
<p>Gotham Mutual's analyst sends the 3 questionnaire items she couldn't close on Thursday. 2 are easy. The third isn't:</p>
<blockquote>
<p><em>Is your information security programme reviewed independently of the people who operate it? Please describe the function, its reporting line, and the date of the most recent review.</em></p>
</blockquote>
<p>You start typing an answer about the leaver test you ran and signed. Then you stop, because you can see what it says. You wrote the rule. You ran the test. You wrote up the result. The only person who checked any of it is you.</p>
<p>Dan reads the question over your shoulder.</p>
<p>"So the answer is 'me'," you say.</p>
<p>"Which means the answer is 'nobody'," he says.</p>
<p>Forty minutes later it gets worse, in the most useful way possible.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/36e034c3-be4e-4fda-828c-0631abe1ce11.png" alt="" style="display:block;margin:0 auto" />

<p>The one question on the questionnaire you can't answer with a document.</p>
<hr />
<h2>9:50 AM: the number that was wrong</h2>
<p>On Wednesday you sent Dan an MFA coverage figure for the board pack: <strong>62%</strong>, up from 30% at the start of the year.</p>
<p>On Friday morning Priya is building an alert for accounts without MFA, and she reruns the count herself. She gets <strong>54%</strong>.</p>
<p>Her message: <em>are we counting the same people?</em></p>
<p>You weren't. Your query counted enabled users in the <code>Staff</code> OU. Hers counted every enabled human account in the domain.</p>
<pre><code class="language-powershell"># Wednesday's number
$enrolled = (Get-ADGroupMember "MFA-Enrolled").Count
$staff    = (Get-ADUser -SearchBase "OU=Staff,DC=wayne,DC=example" -Filter 'Enabled -eq $true').Count
"{0} / {1} = {2:P0}" -f $enrolled, $staff, ($enrolled / $staff)
</code></pre>
<pre><code class="language-text">118 / 190 = 62%
</code></pre>
<p><em>Sample output (illustrative). Wayne Industries is fictional.</em></p>
<pre><code class="language-powershell"># Friday's number: every enabled account that belongs to a person
$people = Get-ADUser -Filter 'Enabled -eq $true' |
  Where-Object { $_.DistinguishedName -notlike "*OU=ServiceAccounts*" }
"{0} / {1} = {2:P0}" -f $enrolled, $people.Count, ($enrolled / $people.Count)

$people | Group-Object { ($_.DistinguishedName -split ',')[1] } |
  Select-Object Count, Name | Sort-Object Count -Descending
</code></pre>
<pre><code class="language-text">118 / 219 = 54%

Count Name
----- ----
  190 OU=Staff
   14 CN=Users
   12 OU=Contractors
    3 OU=Admins
</code></pre>
<p><em>Sample output (illustrative). Wayne Industries is fictional.</em></p>
<p>The gap isn't a rounding error. It's 29 people: contractors, 3 admin accounts, and 14 accounts still sitting in the default <code>Users</code> container because nobody ever moved them into an OU.</p>
<p>Those 29 are exactly the accounts an attacker would want. Your number excluded them because the query was written by the person whose number it was.</p>
<p>Nobody did anything dishonest. That's the point. The error survived because there was no second pair of eyes between the query and the board pack.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/77850f42-a11b-40b4-bd3e-42c757788a5d.png" alt="" style="display:block;margin:0 auto" />

<p>Same control, two people, two answers.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/97228096-a96d-471f-b51f-eaa895cd2a49.png" alt="" style="display:block;margin:0 auto" />

<p>What the number counts, and what it quietly left out.</p>
<p><strong>The unpopular truth:</strong> self-review failures almost never look like fraud. They look like a query written in a hurry by someone who already knows what the answer should be.</p>
<hr />
<h2>What the three lines actually are</h2>
<p>The model most people half-remember is the <strong>Three Lines of Defence</strong>. The Institute of Internal Auditors published it in 2013 and replaced it in July 2020 with <strong>the Three Lines Model</strong>. The IIA dropped the word "defence" on purpose, because the model is about creating and protecting value, not about holding a wall.</p>
<p>Here's the shape of it, in plain terms:</p>
<ul>
<li><p><strong>The governing body.</strong> The board, or whoever plays that role. Accountable to stakeholders for oversight. Sets the tone, provides resources, and establishes an independent internal audit function.</p>
</li>
<li><p><strong>Management: first line roles.</strong> The people who deliver the product and run the systems, and who own and operate the controls that go with them.</p>
</li>
<li><p><strong>Management: second line roles.</strong> People who provide expertise, set the rules, monitor, and challenge the first line. Security, risk, compliance, privacy.</p>
</li>
<li><p><strong>Internal audit: third line roles.</strong> Independent, objective assurance and advice on whether governance and risk management actually work, reporting to the governing body.</p>
</li>
<li><p><strong>External assurance providers.</strong> Certification bodies, SOC 2 auditors, penetration testers, regulators. They sit outside the model and can supplement it, not replace it.</p>
</li>
</ul>
<p>The first and second lines both sit inside management, and the IIA expects them to blur in places. What must not blur is the third line's independence from management, because that's where its objectivity, authority and credibility come from.</p>
<p>Think of a restaurant. The kitchen cooks (first line). The head chef sets the recipes, checks the fridge temperatures and refuses dishes that aren't right (second line). The health inspector doesn't work for the chef (third line, roughly). If the chef inspects the chef, the sticker in the window means nothing.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/e9ff0c4e-b297-41aa-ae84-6138df63c567.png" alt="" style="display:block;margin:0 auto" />

<p>Who does what, and who they answer to.</p>
<hr />
<h2>Wayne's lines, drawn honestly</h2>
<table>
<thead>
<tr>
<th>Line</th>
<th>Who at Wayne</th>
<th>What they actually do</th>
</tr>
</thead>
<tbody><tr>
<td>Governing body</td>
<td>Maya (CEO). No board committee</td>
<td>Approves direction; hasn't received a security report yet</td>
</tr>
<tr>
<td>First line</td>
<td>Priya and the engineers; Farah for people processes</td>
<td>Build, run and operate the controls</td>
</tr>
<tr>
<td>Second line</td>
<td>You</td>
<td>Set the rules, run tests, challenge, report</td>
</tr>
<tr>
<td>Third line</td>
<td>—</td>
<td>Nothing. There is no independent review</td>
</tr>
<tr>
<td>External assurance</td>
<td>The certification body, once engaged</td>
<td>Not engaged yet, and not a substitute for the third line</td>
</tr>
</tbody></table>
<p>Two problems fall out of that table.</p>
<p><strong>One: for some controls, you are the first line as well as the second.</strong> MFA is the clearest case. You own it, you operate it, and you report on it. That was flagged when the ownership list was built, and this week it produced an actual wrong number.</p>
<p><strong>Two: there is no third line at all.</strong> So the honest answer to Gotham's question is: nobody reviews the security programme independently, and the last review was never.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/28096d8a-e716-435c-ba61-41c3bc3022b2.png" alt="" style="display:block;margin:0 auto" />

<p>First line, second line, and an empty chair where the third should be.</p>
<p><strong>The unpopular truth:</strong> at 240 accounts and one security person, a real three-line structure is a fiction. What isn't a fiction is the rule underneath it: nobody marks their own homework on anything that leaves the building.</p>
<hr />
<h2>What the standards actually require</h2>
<p>The frameworks don't ask for three departments. They ask for independence at specific moments.</p>
<p><strong>ISO 27001 Clause 9.2</strong> requires internal audits at planned intervals, and requires you to select auditors so the audit process is objective and impartial. Certification auditors read that as a simple test: you cannot audit your own work. The person who built the ISMS cannot audit it.</p>
<p><strong>Annex A 5.35</strong> asks for the approach to information security to be reviewed independently at planned intervals and when significant changes happen.</p>
<p>Two useful facts for small teams. Internal audit can be outsourced, and the standard doesn't require you to employ an auditor. But your certification body cannot perform your internal audit for you.</p>
<p><strong>SOC 2</strong> expects the board or its equivalent to exercise oversight independent of management, and expects evaluations that are separate from the operation of the controls.</p>
<p><strong>PCI DSS v4.0.1 Requirement 12.4.2</strong> is the bluntest of all. For service providers, reviews at least once every three months must be performed by personnel other than those who perform the task being reviewed.</p>
<p>That last one is worth copying even if PCI never applies to you. It's the whole idea in one sentence.</p>
<hr />
<h2>Buying independence when you're small</h2>
<p>You have four options. Most small companies end up using three of them at once.</p>
<p><strong>1. Swap the reviewer inside the company.</strong> The operator and the reviewer are different people, even if both sit in the same 12-person engineering group. Priya re-performs your numbers. You re-perform hers. It costs an hour a month.</p>
<p><strong>2. Train someone genuinely uninvolved.</strong> Farah doesn't run any technical control, so she can review process controls offboarding, access requests, training records against a checklist, without judging the technology.</p>
<p><strong>3. Outsource or co-source internal audit.</strong> A consultancy runs the ISO 27001 internal audit programme. It isn't cheap, but it's a fraction of a hire, and it produces the impartial report that Clause 9.2 wants.</p>
<p><strong>4. Use external assurance as supplementary evidence.</strong> A penetration test and a certification audit are genuine independent evaluations. They are also narrow, and they work for someone other than your governing body. Count them, don't lean on them.</p>
<p>What all four have in common: the reviewer has to be able to say "this is wrong" without it costing them anything. That's what independence means in practice, and it's the thing an org chart can't prove.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/0c27bed4-3673-4fd8-8382-071b5b77b00f.png" alt="" style="display:block;margin:0 auto" />

<p>Three ways to get a second pair of eyes when you can't hire one.</p>
<hr />
<h2>The artifact: an assurance map</h2>
<p>The template here is the IIA's <strong>Three Lines Model</strong> paper.</p>
<p><a href="https://www.theiia.org/en/">https://www.theiia.org/en/</a></p>
<p><a href="https://www.theiia.org/globalassets/documents/resources/the-iias-three-lines-model-an-update-of-the-three-lines-of-defense-july-2020/three-lines-model-updated-english.pdf">https://www.theiia.org/globalassets/documents/resources/the-iias-three-lines-model-an-update-of-the-three-lines-of-defense-july-2020/three-lines-model-updated-english.pdf</a></p>
<p>The paper gives you the roles. What you build from it is an <strong>assurance map</strong>: one row per control area, showing who runs it, who challenges it, and who independently checks it. Auditors and boards use assurance maps to find two things areas nobody assures, and areas three people assure while nobody fixes anything.</p>
<h3>Field by field</h3>
<table>
<thead>
<tr>
<th>Column</th>
<th>What goes in it</th>
</tr>
</thead>
<tbody><tr>
<td>Control area</td>
<td>A group of controls that share operators, e.g. identity and access</td>
</tr>
<tr>
<td>First line</td>
<td>The person or team that operates the controls</td>
</tr>
<tr>
<td>Second line</td>
<td>Who sets the rule, monitors, and challenges</td>
</tr>
<tr>
<td>Third line / independent</td>
<td>Who checks it with no stake in the answer, and how often</td>
</tr>
<tr>
<td>External assurance</td>
<td>Any outside evaluation that covers this area, and its date</td>
</tr>
<tr>
<td>Independence gap</td>
<td>Where the same person appears in two columns, and what compensates</td>
</tr>
</tbody></table>
<h3>Wayne before</h3>
<table>
<thead>
<tr>
<th>Control area</th>
<th>First line</th>
<th>Second line</th>
<th>Independent</th>
<th>Gap</th>
</tr>
</thead>
<tbody><tr>
<td>Identity and access (MFA, admins)</td>
<td>You</td>
<td>You</td>
<td>None</td>
<td>Same person, both columns</td>
</tr>
<tr>
<td>Offboarding</td>
<td>You (removal)</td>
<td>You (test)</td>
<td>None</td>
<td>You test your own removals</td>
</tr>
<tr>
<td>Backups</td>
<td>Dev rotation</td>
<td>You</td>
<td>None</td>
<td>No independent check</td>
</tr>
<tr>
<td>Change and deploys</td>
<td>Priya</td>
<td>Nobody</td>
<td>None</td>
<td>No rule, no challenge</td>
</tr>
<tr>
<td>Vendors</td>
<td>Whoever signs</td>
<td>Nobody</td>
<td>None</td>
<td>Twelve vendors, no reviewer</td>
</tr>
</tbody></table>
<h3>Wayne after</h3>
<table>
<thead>
<tr>
<th>Control area</th>
<th>First line</th>
<th>Second line</th>
<th>Independent</th>
<th>Gap</th>
</tr>
</thead>
<tbody><tr>
<td>Identity and access</td>
<td>You (operate)</td>
<td>Dan reviews metrics</td>
<td>Priya re-performs the count monthly</td>
<td>You still operate; documented and accepted</td>
</tr>
<tr>
<td>Offboarding</td>
<td>Farah (trigger), you (removal)</td>
<td>You (monthly test)</td>
<td>Farah signs the test; outsourced audit yearly</td>
<td>Covered</td>
</tr>
<tr>
<td>Backups</td>
<td>Dev rotation</td>
<td>You</td>
<td>Priya picks the restore to test, not the operator</td>
<td>Covered</td>
</tr>
<tr>
<td>Change and deploys</td>
<td>Priya</td>
<td>You (merge approval rule)</td>
<td>Outsourced internal audit, yearly</td>
<td>Rule still being written</td>
</tr>
<tr>
<td>Vendors</td>
<td>Dan</td>
<td>You</td>
<td>Outsourced internal audit, yearly</td>
<td>Nothing reviewed yet</td>
</tr>
</tbody></table>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/f7deecba-93c1-4bac-a1f8-7a97a2cd8e5e.png" alt="" style="display:block;margin:0 auto" />

<p>One row per area. The interesting column is the one that used to be empty.</p>
<p>Two decisions came out of building it. Maya approved a budget for an outsourced ISO 27001 internal audit ahead of the certification work. And every metric that leaves Wayne board pack, questionnaire, customer report now gets re-performed by someone who didn't produce it, with both names on it.</p>
<p><strong>The unpopular truth:</strong> your penetration test isn't your third line. It's one narrow evaluation, bought by the people it evaluates, covering a slice of your environment on one week of the year. Useful. Not assurance over your programme.</p>
<hr />
<h2>Evidence #8</h2>
<p>By Friday afternoon, three things go in the evidence folder:</p>
<ul>
<li><p>The assurance map, approved by Dan, with the independence gaps written down rather than hidden.</p>
</li>
<li><p>The corrected MFA figure: both queries, the defined population, the 29 accounts that were missing, and Priya's re-performance note.</p>
</li>
<li><p>A one-line correction to Dan, copying Maya: <em>Wednesday's 62% counted the Staff OU only. The real figure is 54%. Here's the population we'll use from now on.</em></p>
</li>
</ul>
<p><strong>Evidence #8:</strong> a documented independence structure, and a metric that was corrected before anyone outside Wayne saw it.</p>
<p>The answer to Gotham goes out the same afternoon, and it's the honest version: no internal audit function today; peer re-performance in place now; an outsourced internal audit scheduled; here's the map.</p>
<p>She replies with one line: <em>this is a better answer than most of the "yes" answers we get.</em></p>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/7c090b14-3f85-4690-bd03-eea2d29f1b05.png" alt="" style="display:block;margin:0 auto" />

<p>Two names on every number that leaves the building.</p>
<hr />
<h2>What an auditor accepts vs rejects</h2>
<table>
<thead>
<tr>
<th>Accepted</th>
<th>Rejected</th>
</tr>
</thead>
<tbody><tr>
<td>A reviewer with no operational role in the control</td>
<td>The control owner signing their own test</td>
</tr>
<tr>
<td>A defined population, with exclusions justified</td>
<td>A number with no definition of what was counted</td>
</tr>
<tr>
<td>An internal audit plan with impartial auditors named</td>
<td>"The security lead audits everything"</td>
</tr>
<tr>
<td>An outsourced internal audit report</td>
<td>Your certification body doing your internal audit</td>
</tr>
<tr>
<td>Documented gaps with compensating measures</td>
<td>An org chart that claims independence that doesn't exist</td>
</tr>
<tr>
<td>A report that reaches the governing body</td>
<td>A report that stops at the person being reviewed</td>
</tr>
<tr>
<td>A pen test counted as one input</td>
<td>A pen test presented as the whole assurance programme</td>
</tr>
</tbody></table>
<hr />
<h2>What you actually do on Monday</h2>
<ol>
<li><p><strong>Draw your real lines on one page.</strong> Names, not departments. Where one name appears twice, circle it.</p>
</li>
<li><p><strong>List every number that leaves the company.</strong> Board packs, questionnaires, customer reports. Put a second name on each one.</p>
</li>
<li><p><strong>Define the population for your top three metrics.</strong> Write down what's counted, what's excluded and why.</p>
</li>
<li><p><strong>Pick your independence route</strong> for the coming year: swap reviewers, train someone uninvolved, or buy it.</p>
</li>
<li><p><strong>Schedule one report to your governing body,</strong> even if your governing body is one person and the report is a page.</p>
</li>
<li><p><strong>Write the gaps down.</strong> An honest gap with a plan beats a claim that falls apart in an interview.</p>
</li>
</ol>
<hr />
<h2>Framework mapping</h2>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/b7a4c69f-f653-49be-b218-b2aea3c04d69.png" alt="" style="display:block;margin:0 auto" />

<p>Independence, as each framework asks for it.</p>
<hr />
<h2>Maturity ladder</h2>
<table>
<thead>
<tr>
<th>Stage</th>
<th>20 people</th>
<th>200 people</th>
<th>2,000 people</th>
</tr>
</thead>
<tbody><tr>
<td>Governing body</td>
<td>Founder reads a one-page report quarterly</td>
<td>CEO plus an advisor or board member owns oversight</td>
<td>Board risk or audit committee, with a charter</td>
</tr>
<tr>
<td>First line</td>
<td>Everyone, informally</td>
<td>Named owners per control</td>
<td>Control owners with documented procedures</td>
</tr>
<tr>
<td>Second line</td>
<td>The one security person, part-time</td>
<td>A small security and compliance function</td>
<td>Separate risk, compliance and security teams</td>
</tr>
<tr>
<td>Third line</td>
<td>Peer re-performance, written down</td>
<td>Outsourced or co-sourced internal audit</td>
<td>In-house internal audit reporting to the committee</td>
</tr>
<tr>
<td>External</td>
<td>One pen test a year</td>
<td>Pen test plus certification audit</td>
<td>Multiple audits, coordinated through an assurance map</td>
</tr>
</tbody></table>
<p>At 20 people the third line is one rule: the person who ran it isn't the person who checks it. Write that down and you've got something an auditor can test.</p>
<p><strong>Where Wayne sits:</strong> second line real, first line named, third line bought rather than built, as of Friday. The governing body role is Maya's, and she's about to receive the first report that was ever written for her.</p>
<hr />
<h2>Cheatsheet</h2>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/136f2306-cee6-4c4b-aa9f-ffa48c88fcd7.png" alt="" style="display:block;margin:0 auto" />

<p>Three lines, one rule: nobody marks their own homework.</p>
<hr />
<h2>The takeaway</h2>
<p>Independence isn't a department. It's whether anyone with no stake in the answer looks before the answer leaves. Draw your lines with names. Where one name appears twice, write down what compensates. Every number that goes to a board or a customer needs a second person's name on it. Small teams can buy a third line. They cannot skip it and still say "reviewed."</p>
<hr />
<blockquote>
<p>⚠️ This content is for educational purposes only. Wayne Industries is a fictional company. Nothing here is legal, audit, or compliance advice, validate against your own auditor and jurisdiction.</p>
</blockquote>
]]></content:encoded></item><item><title><![CDATA[The Evidence Chain Map]]></title><description><![CDATA[@18xBan · GRC Series · Chapter 07


6 links from "we do this" to "here's the proof." 4 were broken, and the other 2 were weak.

Monday, 8:50 AM: "Show us, don't tell us"
The email from Gotham Mutual's]]></description><link>https://18xban.hashnode.dev/the-evidence-chain-map</link><guid isPermaLink="true">https://18xban.hashnode.dev/the-evidence-chain-map</guid><category><![CDATA[audit-evidence]]></category><category><![CDATA[SOC2]]></category><category><![CDATA[ISO 27001]]></category><category><![CDATA[internal audit]]></category><category><![CDATA[grc]]></category><dc:creator><![CDATA[18xBan]]></dc:creator><pubDate>Sun, 20 Sep 2026 03:38:25 GMT</pubDate><content:encoded><![CDATA[<p><em>@18xBan · GRC Series · Chapter 07</em></p>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/19b44543-8e50-495b-b510-de321173c767.png" alt="" style="display:block;margin:0 auto" />

<p>6 links from "we do this" to "here's the proof." 4 were broken, and the other 2 were weak.</p>
<hr />
<h2>Monday, 8:50 AM: "Show us, don't tell us"</h2>
<p>The email from Gotham Mutual's vendor-risk team is short and polite.</p>
<blockquote>
<p><em>Ahead of Thursday's walkthrough, please be ready to show that access was removed for everyone who left Wayne in the last 90 days, employees and contractors. We'd like the list of leavers, the date each person left, and the date their access was disabled.</em></p>
</blockquote>
<p>You read it twice and relax a little. Offboarding has an owner now. Farah owns the trigger, and you do the removal. Everyone said "I accept." It's on the list.</p>
<p>Then you try to answer the actual question, and the morning goes sideways.</p>
<p>Farah exports 6 employee departures from the HR system in about 2 minutes. Good start.</p>
<p>"And the contractors?" you ask.</p>
<p>"Contractors aren't in HR," she says. "Finance pays them. Ask accounts payable."</p>
<p>You pull up the domain controller's Security log to grab the "account disabled" events. The oldest event in the log is eleven days old.</p>
<p>By 10 AM you know three things. Wayne <em>does</em> offboard people. Wayne can't prove it. And you're not yet sure it offboards all of them.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/0034f945-3c11-49c4-bfe1-5c7604f3e9a0.png" alt="" style="display:block;margin:0 auto" />

<p>"Show us" is a much harder question than "Do you?"</p>
<hr />
<h2>What an evidence chain actually is</h2>
<p>An auditor never takes your word for anything. They follow a chain.</p>
<p>Think of a parcel with tracking. "We shipped it" means nothing. The tracking page shows it was picked up, scanned at the depot, loaded on the truck and signed for at the door. If any scan is missing, you can't prove it arrived, even if it did.</p>
<p>Controls work the same way. For any control, the chain has 6 links:</p>
<ol>
<li><p><strong>Rule.</strong> Where is it written what should happen, and by when?</p>
</li>
<li><p><strong>Trigger.</strong> What starts the control, and is there a complete list of those starts?</p>
</li>
<li><p><strong>Action.</strong> Who does the work, and where is the request recorded?</p>
</li>
<li><p><strong>Record.</strong> What system writes down that it happened, with a timestamp?</p>
</li>
<li><p><strong>Review.</strong> Who checks that it happened correctly, and where is that check recorded?</p>
</li>
<li><p><strong>Kept.</strong> Where does the proof live, and will it still be there when an auditor asks?</p>
</li>
</ol>
<p>An <strong>evidence chain map</strong> is a document that lays out those 6 links for one control. It names the system, the person and the storage location for each link.</p>
<p>The auditor's job is to walk the chain from end to end. Yours is to walk it first.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/3f5075af-4541-422f-8bab-9b00d443ccc7.png" alt="" style="display:block;margin:0 auto" />

<p>Rule, trigger, action, record, review, kept. Break one and the chain doesn't hold.</p>
<p><strong>The unpopular truth:</strong> a control that ran but left no record didn't run, as far as an auditor is concerned. "We always do that" is a statement, not evidence.</p>
<hr />
<h2>How an auditor walks the chain</h2>
<p>Auditors have three ways to test a control. NIST SP 800-53A, the US government's guide to assessing controls, names them plainly:</p>
<ul>
<li><p><strong>Examine:</strong> read documents and records. Policies, tickets, logs.</p>
</li>
<li><p><strong>Interview:</strong> talk to the people who run the control.</p>
</li>
<li><p><strong>Test:</strong> make the control do its thing and compare what happens with what should happen.</p>
</li>
</ul>
<p>SOC 2 auditors work the same way, even if they use different words. So do ISO 27001 certification auditors.</p>
<p>For offboarding, the walk looks like this:</p>
<ol>
<li><p>Ask for the <strong>population</strong>: every person who left in the period.</p>
</li>
<li><p>Check that the population is <strong>complete</strong>. Does it match other sources?</p>
</li>
<li><p>Pick a <strong>sample</strong>, or take all of them if the list is short.</p>
</li>
<li><p>For each person, compare the <strong>date they left</strong> with the <strong>date access was disabled</strong>.</p>
</li>
<li><p>Ask who <strong>reviews</strong> this, and see the record of that review.</p>
</li>
</ol>
<p>Wayne's population was the first thing to break.</p>
<hr />
<h2>Walking Wayne's chain, link by link</h2>
<h3>Link 1, Rule: "promptly"</h3>
<p>Wayne's access control policy is one of the 40 on the shared drive. It says access is removed "promptly" when someone leaves.</p>
<p>"Promptly" can't be tested. An auditor can't count days against it.</p>
<p>CIS Controls v8.1 Safeguard 6.2 asks for accounts to be disabled immediately upon termination. You propose a rule you can measure: <strong>access is disabled by the end of the person's last working day.</strong> Farah agrees. Dan approves it on Monday afternoon, by email.</p>
<p>That email is the rule, dated and approved.</p>
<h3>Link 2, Trigger: half the population is missing</h3>
<p>Farah's HR export has six employee departures in the 90-day window.</p>
<p>Accounts payable keeps its own spreadsheet of contractors. 3 contracts ended in the same window. None of them are in the HR system, so none of them ever triggered offboarding.</p>
<p>That's the most common break in any evidence chain. <strong>Population completeness</strong> means proving your list includes everyone it should. If the list is incomplete, a perfect test of that list proves nothing.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/bca409ea-3781-4617-bd80-a4aa8986a7f0.png" alt="" style="display:block;margin:0 auto" />

<p>6 leavers in HR. 3 more in a finance spreadsheet nobody connected.</p>
<h3>Link 3, Action: tickets, mostly</h3>
<p>Offboarding requests go through Deskline, Wayne's support desk. 5 out of 6 employee departures have a ticket. One contractor account was disabled because Priya asked in a hallway. No ticket.</p>
<p>The account was still disabled on time. It just left no trail.</p>
<h3>Link 4, Record: eleven days of memory</h3>
<p>This is where you find the break that stings most. Windows writes Event ID 4725, "A user account was disabled," to the domain controller's Security log. That's your timestamped record.</p>
<pre><code class="language-powershell">Get-WinEvent -ListLog Security -ComputerName DC01 |
  Select-Object LogName, LogMode, MaximumSizeInBytes, RecordCount

Get-WinEvent -ComputerName DC01 -LogName Security -MaxEvents 1 -Oldest |
  Select-Object TimeCreated
</code></pre>
<pre><code class="language-text">LogName  LogMode  MaximumSizeInBytes RecordCount
-------  -------  ------------------ -----------
Security Circular           20971520       48213

TimeCreated
-----------
2024-10-24 06:12:41
</code></pre>
<p><em>Sample output (illustrative). Wayne Industries is fictional.</em></p>
<p>The log is 20 MB and set to <strong>circular</strong>, which means new events overwrite the oldest ones. On a busy domain controller, that's about eleven days. Nobody changed it, and nothing ships the events anywhere else.</p>
<p>CIS Safeguard 8.10 asks for audit logs to be kept for at least 90 days. Wayne keeps eleven.</p>
<p>For older disables, there's a second source. Active Directory records when each attribute last changed. Disabling an account changes <code>userAccountControl</code>:</p>
<pre><code class="language-powershell">Get-ADReplicationAttributeMetadata -Server DC01 `
  -Object "CN=Rashid Osei,OU=Staff,DC=wayne,DC=example" `
  -Properties userAccountControl |
  Select-Object AttributeName, LastOriginatingChangeTime, Version
</code></pre>
<pre><code class="language-text">AttributeName      LastOriginatingChangeTime Version
-------------      ------------------------- -------
userAccountControl 2024-09-19 14:07:22             3
</code></pre>
<p><em>Sample output (illustrative). Wayne Industries is fictional.</em></p>
<p>That's useful, but it only shows the <em>last</em> change to that attribute. If someone re-enables and disables the account later, the earlier date is gone. Treat it as supporting evidence, and pair it with the ticket.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/006532d1-678b-4b87-b9df-d550ba2d97e0.png" alt="" style="display:block;margin:0 auto" />

<p>The log that was supposed to be the record forgot everything older than 11 days.</p>
<h3>Link 5, Review: nobody</h3>
<p>Nobody ever compared "date left" with "date disabled." Not monthly, not ever. So nobody knew whether the control worked.</p>
<h3>Link 6, Kept: "somewhere"</h3>
<p>The tickets are in Deskline. The log was on DC01 until it wasn't. The HR export lived in Farah's downloads folder.</p>
<p>None of it was in one place, and none of it had a retention period.</p>
<hr />
<h2>The template: SCF Evidence Request List</h2>
<p>You don't need to invent a format for this. The <strong>Secure Controls Framework (SCF)</strong> publishes a free <strong>Evidence Request List (ERL)</strong>. It's a catalogue of the artifacts an assessor would reasonably expect for each control, mapped to SCF controls. The SCF says it can serve as a guidebook for other audits too, not just SCF assessments.</p>
<p>Get it from the SCF website, as part of the free SCF download. <a href="https://securecontrolsframework.com/">https://securecontrolsframework.com/</a></p>
<p>The ERL tells you <strong>what</strong> to hand over. It doesn't tell you <strong>where it comes from</strong> or <strong>whether it will still exist</strong>. So Wayne keeps the ERL's own columns and adds 6 chain columns to its copy.</p>
<h3>Field by field</h3>
<table>
<thead>
<tr>
<th>Field</th>
<th>Source</th>
<th>What goes in it</th>
</tr>
</thead>
<tbody><tr>
<td>ERL #</td>
<td>SCF</td>
<td>The SCF's identifier for the evidence item</td>
</tr>
<tr>
<td>Area of Focus</td>
<td>SCF</td>
<td>The domain the artifact belongs to, e.g. identity and access</td>
</tr>
<tr>
<td>Documentation Artifact</td>
<td>SCF</td>
<td>The name of the thing an assessor asks for</td>
</tr>
<tr>
<td>Artifact Description</td>
<td>SCF</td>
<td>What the artifact should show</td>
</tr>
<tr>
<td>SCF Control Mappings</td>
<td>SCF</td>
<td>The SCF controls this artifact supports</td>
</tr>
<tr>
<td>Control owner</td>
<td>Wayne</td>
<td>The accountable person, from the ownership list</td>
</tr>
<tr>
<td>Rule</td>
<td>Wayne</td>
<td>Where the requirement is written, and the measurable standard</td>
</tr>
<tr>
<td>Trigger and population</td>
<td>Wayne</td>
<td>Every source that starts the control, and how you prove the list is complete</td>
</tr>
<tr>
<td>Action record</td>
<td>Wayne</td>
<td>Where each request and action is logged</td>
</tr>
<tr>
<td>System record</td>
<td>Wayne</td>
<td>The timestamped system event, and how long it's kept</td>
</tr>
<tr>
<td>Review</td>
<td>Wayne</td>
<td>Who checks the results, how often, and where the sign-off lives</td>
</tr>
<tr>
<td>Kept in, for</td>
<td>Wayne</td>
<td>Storage location and retention period</td>
</tr>
</tbody></table>
<h3>Wayne before</h3>
<table>
<thead>
<tr>
<th>Chain column</th>
<th>Offboarding, Monday morning</th>
</tr>
</thead>
<tbody><tr>
<td>Control owner</td>
<td>Farah (trigger), You (removal)</td>
</tr>
<tr>
<td>Rule</td>
<td>Access policy (2021): "promptly"</td>
</tr>
<tr>
<td>Trigger and population</td>
<td>HR system only. Contractors missing</td>
</tr>
<tr>
<td>Action record</td>
<td>Deskline, when someone remembers</td>
</tr>
<tr>
<td>System record</td>
<td>Event 4725 on DC01, about 11 days, circular</td>
</tr>
<tr>
<td>Review</td>
<td>None</td>
</tr>
<tr>
<td>Kept in, for</td>
<td>Scattered. No retention set</td>
</tr>
</tbody></table>
<h3>Wayne after</h3>
<table>
<thead>
<tr>
<th>Chain column</th>
<th>Offboarding, Wednesday evening</th>
</tr>
</thead>
<tbody><tr>
<td>Control owner</td>
<td>Farah (trigger), You (removal), Dan (review)</td>
</tr>
<tr>
<td>Rule</td>
<td>Disabled by end of last working day. Approved by Dan, Monday</td>
</tr>
<tr>
<td>Trigger and population</td>
<td>HR export plus the accounts payable contractor list, reconciled monthly against the AD <code>Contractors</code> OU</td>
</tr>
<tr>
<td>Action record</td>
<td>Deskline ticket required for every disable, including contractors</td>
</tr>
<tr>
<td>System record</td>
<td>Event 4725 forwarded to CloudWatch Logs, 400-day retention. DC01 log raised to 1 GB</td>
</tr>
<tr>
<td>Review</td>
<td>Monthly leaver test by you, signed by Farah, reviewed by Dan</td>
</tr>
<tr>
<td>Kept in, for</td>
<td><code>Evidence/Access/Offboarding/</code> on the shared drive, kept 3 years</td>
</tr>
</tbody></table>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/1f56a5ab-7ad7-4f91-bad6-e59c3c3fe0b8.png" alt="" style="display:block;margin:0 auto" />

<p>Same control, same owners. Now every link says where the proof lives.</p>
<p><strong>The unpopular truth:</strong> most evidence is collected the week before an audit, from whatever still exists. That's why so much of it gets rejected. Evidence has to be designed into the control, not gathered after it.</p>
<hr />
<h2>The test: 9 people, 2 problems</h2>
<p>With both lists joined, you test all nine leavers. 9 is small enough to skip sampling.</p>
<p>Contractor accounts first, since that's where you're least sure:</p>
<pre><code class="language-powershell">Get-ADUser -Filter 'Enabled -eq $true' `
  -SearchBase "OU=Contractors,DC=wayne,DC=example" `
  -Properties LastLogonDate, AccountExpirationDate |
  Select-Object SamAccountName, Enabled, LastLogonDate, AccountExpirationDate
</code></pre>
<pre><code class="language-text">SamAccountName Enabled LastLogonDate        AccountExpirationDate
-------------- ------- -------------        ---------------------
ctr.jlee          True 2024-09-24 16:48:03
ctr.mvarga        True 2024-10-18 15:21:37
ctr.dfarouk       True 2024-11-01 09:02:15
</code></pre>
<p><em>Sample output (illustrative). Wayne Industries is fictional.</em></p>
<p><code>ctr.dfarouk</code> is a current contractor, which is fine. The other 2 contracts ended 41 days and 17 days ago. Both accounts still work, and Wayne's VPN signs people in with their AD account.</p>
<p>Neither account shows a sign-in after the contract ended. <code>LastLogonDate</code> can lag by up to 2 weeks, though, so you check the VPN logs too. Nothing there either.</p>
<p>You disable both accounts on the spot, with tickets. Then you set an expiry date on every contractor account, so the account shuts itself off even if nobody remembers.</p>
<p>The full result:</p>
<table>
<thead>
<tr>
<th>#</th>
<th>Account</th>
<th>Type</th>
<th>Last day</th>
<th>Disabled</th>
<th>Evidence</th>
<th>Result</th>
</tr>
</thead>
<tbody><tr>
<td>1</td>
<td>a.brooks</td>
<td>Employee</td>
<td>Aug 16</td>
<td>Aug 16</td>
<td>Ticket + AD metadata</td>
<td>Pass</td>
</tr>
<tr>
<td>2</td>
<td>m.chen</td>
<td>Employee</td>
<td>Aug 30</td>
<td>Aug 30</td>
<td>Ticket + AD metadata</td>
<td>Pass</td>
</tr>
<tr>
<td>3</td>
<td>r.osei</td>
<td>Employee</td>
<td>Sep 13</td>
<td>Sep 19</td>
<td>Ticket + AD metadata</td>
<td><strong>Late, 4 business days</strong></td>
</tr>
<tr>
<td>4</td>
<td>t.walsh</td>
<td>Employee</td>
<td>Sep 27</td>
<td>Sep 27</td>
<td>Ticket + AD metadata</td>
<td>Pass</td>
</tr>
<tr>
<td>5</td>
<td>k.dubois</td>
<td>Employee</td>
<td>Oct 11</td>
<td>Oct 11</td>
<td>Ticket + AD metadata</td>
<td>Pass</td>
</tr>
<tr>
<td>6</td>
<td>s.patel</td>
<td>Employee</td>
<td>Oct 25</td>
<td>Oct 25</td>
<td>Ticket + Event 4725</td>
<td>Pass</td>
</tr>
<tr>
<td>7</td>
<td>ctr.hsato</td>
<td>Contractor</td>
<td>Aug 23</td>
<td>Aug 23</td>
<td>AD metadata only, no ticket</td>
<td>Pass, weak evidence</td>
</tr>
<tr>
<td>8</td>
<td>ctr.jlee</td>
<td>Contractor</td>
<td>Sep 24</td>
<td>Nov 4</td>
<td>Ticket (today)</td>
<td><strong>Exception, 41 days</strong></td>
</tr>
<tr>
<td>9</td>
<td>ctr.mvarga</td>
<td>Contractor</td>
<td>Oct 18</td>
<td>Nov 4</td>
<td>Ticket (today)</td>
<td><strong>Exception, 17 days</strong></td>
</tr>
</tbody></table>
<p>Row 3 happened because the ticket was raised 4 days after the person left. Rows 8 and 9 happened because contractors never triggered anything at all.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/0655731a-843a-4bc5-b13f-fe45e37ac632.png" alt="" style="display:block;margin:0 auto" />

<p>9 leavers, 1 late, 2 never done.</p>
<p><strong>The unpopular truth:</strong> finding your own exceptions before an audit is the best result you can get. Exceptions you find and fix are a working control. Exceptions an auditor finds are a finding, and a question about everything else you told them.</p>
<hr />
<h2>Evidence #7</h2>
<p>On Wednesday you put it together. It goes in <code>Evidence/Access/Offboarding/</code>:</p>
<ul>
<li><p>The chain map for offboarding: the ERL row plus Wayne's 6 chain columns.</p>
</li>
<li><p>Dan's approval of the "end of last working day" rule, from Monday.</p>
</li>
<li><p>The HR export and the accounts payable list, with the reconciliation note.</p>
</li>
<li><p>The nine-row test sheet with all three problems written up: cause, fix and date fixed.</p>
</li>
<li><p>Farah's sign-off on the test, and Dan's review on Wednesday at 5:15 PM.</p>
</li>
</ul>
<p><strong>Evidence #7:</strong> a complete, reconciled offboarding test with its exceptions disclosed and fixed.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/0c105d6a-bfce-48af-beff-a1c91ecf0292.png" alt="" style="display:block;margin:0 auto" />

<p>"Do we tell Gotham?" "We tell Gotham."</p>
<hr />
<h2>Thursday, 2 PM: the walkthrough</h2>
<p>Gotham Mutual's analyst asks for the leaver list first. You show both sources and the reconciliation.</p>
<p>She picks row 8 and asks why a contractor kept access for 41 days. You show the cause, the fix, the VPN check and the new expiry dates.</p>
<p>She asks how she'll know it keeps working. You show the monthly test, who signs it, and where the next one will be filed.</p>
<p>She writes down 2 things: a finding that was self-identified and fixed, and a request to see next quarter's test.</p>
<p>That's not a pass. But it's a conversation that can end in a signed contract, which "trust us" never is.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/35c57ada-fb80-4014-b2fe-8f031559eca1.png" alt="" style="display:block;margin:0 auto" />

<p>"You found these yourselves?" "Then fixed them. Here's the chain."</p>
<hr />
<h2>What an auditor accepts vs rejects</h2>
<table>
<thead>
<tr>
<th>Accepted</th>
<th>Rejected</th>
</tr>
</thead>
<tbody><tr>
<td>A population drawn from every source, with a reconciliation</td>
<td>One system's list, with no proof it's complete</td>
</tr>
<tr>
<td>A system-generated record with a timestamp</td>
<td>A screenshot with no date, system or user visible</td>
</tr>
<tr>
<td>A ticket raised before or at the time of the action</td>
<td>A ticket created the week of the audit</td>
</tr>
<tr>
<td>Log retention that covers the whole audit period</td>
<td>"The log rolled over, but we definitely did it"</td>
</tr>
<tr>
<td>A review with a named reviewer and a date</td>
<td>A review nobody can show</td>
</tr>
<tr>
<td>Exceptions written up, with cause and fix</td>
<td>A test with zero exceptions and no detail</td>
</tr>
<tr>
<td>Evidence stored in one place with a retention period</td>
<td>Evidence gathered from inboxes on request</td>
</tr>
</tbody></table>
<hr />
<h2>What you actually do on Monday</h2>
<ol>
<li><p><strong>Pick one control</strong> that an auditor or customer is sure to ask about. Offboarding is a good first choice.</p>
</li>
<li><p><strong>Write the six links</strong> for it on one page: rule, trigger, action, record, review, kept.</p>
</li>
<li><p><strong>Find every trigger source.</strong> HR, finance, vendor contracts, the agency that hires your temps.</p>
</li>
<li><p><strong>Check how long your records really last.</strong> Look at log size and mode, not the policy.</p>
</li>
<li><p><strong>Run the test yourself.</strong> Last 90 days, every leaver, last day vs disabled date.</p>
</li>
<li><p><strong>Write up what you find,</strong> including the ugly rows, and get the owner to sign it.</p>
</li>
</ol>
<hr />
<h2>Framework mapping</h2>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/63ab011a-fd4a-4969-a4e9-db3e6a843290.png" alt="" style="display:block;margin:0 auto" />

<p>Every framework wants the same thing: proof, not promises.</p>
<hr />
<h2>Maturity ladder</h2>
<table>
<thead>
<tr>
<th>Stage</th>
<th>20 people</th>
<th>200 people</th>
<th>2,000 people</th>
</tr>
</thead>
<tbody><tr>
<td>Rule</td>
<td>One line in a shared doc, approved by the founder</td>
<td>Measurable standard in the access policy</td>
<td>Standard with defined SLAs per account type</td>
</tr>
<tr>
<td>Population</td>
<td>One spreadsheet of everyone, including contractors</td>
<td>HR and finance lists reconciled monthly</td>
<td>HR system feeds the identity platform automatically</td>
</tr>
<tr>
<td>Record</td>
<td>Admin console audit log, exported monthly</td>
<td>Directory events forwarded to central logging</td>
<td>SIEM with long-term retention and integrity checks</td>
</tr>
<tr>
<td>Review</td>
<td>Founder checks the list once a quarter</td>
<td>Monthly test by security, signed by the owner</td>
<td>Continuous monitoring plus internal audit sampling</td>
</tr>
<tr>
<td>Kept</td>
<td>One folder, one retention rule</td>
<td>Evidence folders per control, in the ERL format</td>
<td>A GRC platform linking every control to its evidence</td>
</tr>
</tbody></table>
<p>Be honest: at 20 people, the whole chain map is often one spreadsheet tab. That's fine, as long as every link has an answer.</p>
<p><strong>Where Wayne sits:</strong> at the 200-person stage for one control, offboarding, as of Wednesday. Every other control on the list still needs its own chain map, and most of them have a link nobody has checked.</p>
<hr />
<h2>Cheatsheet</h2>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/ced36bca-38e3-4528-9aff-f5e85c53cdbe.png" alt="" style="display:block;margin:0 auto" />

<p>The evidence chain, on one page.</p>
<hr />
<h2>The takeaway</h2>
<p>A control is only as strong as the weakest link between the rule and the stored proof. Prove the population is complete before you test anything in it. Check how long your records really last, not how long you think they do. Find your own exceptions first, write them down, and fix them.</p>
<hr />
<blockquote>
<p>⚠️ This content is for educational purposes only. Wayne Industries is a fictional company. Nothing here is legal, audit, or compliance advice, validate against your own auditor and jurisdiction.</p>
</blockquote>
]]></content:encoded></item><item><title><![CDATA[Control Ownership Without Theatre]]></title><description><![CDATA[@18xBan · GRC Series · Chapter 06


Eighteen controls on Wayne's list, and not one owner who'd been told.

Monday, 9:05 AM: "Password resets are broken"
The first support ticket lands in Deskline at 9]]></description><link>https://18xban.hashnode.dev/control-ownership-without-theatre</link><guid isPermaLink="true">https://18xban.hashnode.dev/control-ownership-without-theatre</guid><category><![CDATA[Governance]]></category><category><![CDATA[Security controls]]></category><category><![CDATA[ISO 27001]]></category><category><![CDATA[NIST CSF]]></category><category><![CDATA[grc]]></category><dc:creator><![CDATA[18xBan]]></dc:creator><pubDate>Sun, 20 Sep 2026 00:21:53 GMT</pubDate><content:encoded><![CDATA[<p><em>@18xBan · GRC Series · Chapter 06</em></p>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/874de28d-9c0f-41cc-87f2-9974c8bee5af.png" alt="" style="display:block;margin:0 auto" />

<p>Eighteen controls on Wayne's list, and not one owner who'd been told.</p>
<hr />
<h2>Monday, 9:05 AM: "Password resets are broken"</h2>
<p>The first support ticket lands in Deskline at 9:05. By 9:40 there are 31.</p>
<p>Every one says the same thing: <em>I clicked "forgot password" and nothing arrived.</em></p>
<p>Priya finds the cause in twenty minutes. MailRelay, the US service that sends Wayne's transactional email, stopped accepting Wayne's API key at midnight. The key was created in 2021.</p>
<p>Then she finds the warning. MailRelay sent it three weeks ago:</p>
<blockquote>
<p><em>We're retiring API keys created before 2023. Rotate your key by Sunday or sending will stop.</em></p>
</blockquote>
<p>It went to <code>engineering@wayne-industries.example</code>, a distribution list with 14 people on it.</p>
<p>Fourteen people received it. Several opened it. Nobody acted, because everybody assumed somebody else would.</p>
<p>Priya rotates the key, and resets start working again at 10:12. Nothing leaked. Customers were just locked out for a morning.</p>
<p>At 10:30, Dan walks over.</p>
<p>"Who owns MailRelay?"</p>
<p>You open the flow register you finished last week. Flow F4, transactional email to MailRelay, has a name in the owner column. But that owner decides <em>whether</em> customer data should go to MailRelay. Nobody owns the running of it: the key, the vendor notices, the renewal.</p>
<p>So you go looking for every other place Wayne has a name that doesn't mean anything.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/7e29af00-cf60-42e7-add0-5d8fcc66ced1.png" alt="" style="display:block;margin:0 auto" />

<p>Monday morning: 31 tickets, one expired key, and a warning 14 people saw.</p>
<hr />
<h2>Everyone owns it, so nobody does</h2>
<p>Think of the sink in an office kitchen.</p>
<p>The mugs belong to everyone. So the pile grows until one person snaps and washes the lot. Then it builds up again, because nothing changed.</p>
<p>Security controls work the same way. A <strong>control</strong> is anything that reduces a risk: a setting, a process, a review. A control with "everyone" as its owner is that sink.</p>
<p>Psychologists call it the <strong>bystander effect</strong>: the more people who could act, the less likely any one of them does. A distribution list is a bystander machine.</p>
<p>That's what this post is about. The goal isn't a matrix with a name in every cell. It's a name that changes what happens when something goes wrong.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/2906ba20-b05e-4e3f-a7d8-d619f0ee00b8.png" alt="" style="display:block;margin:0 auto" />

<p>Everyone's mugs, nobody's dishes.</p>
<p><strong>The unpopular truth:</strong> a team is not an owner. "Engineering," "IT" and "Security" can't be held accountable, can't be interviewed and can't reply to an email. If a cell needs a group name to look full, it's empty.</p>
<hr />
<h2>Wayne's list, honestly</h2>
<p>You export Wayne's control list. It has 18 rows: the fixes and checks that came out of the first five weeks of this program. Each row has a "responsible" column that somebody filled in, mostly Dan, back when the goal was "don't leave it blank."</p>
<p>Here's the count.</p>
<pre><code class="language-bash">csvcut -c "Current Roles and Responsibilities" wayne-csf-profile-current.csv \
  | tail -n +2 | sort | uniq -c | sort -rn
</code></pre>
<pre><code class="language-plaintext">     11 Dan Okafor
      4 Security
      1 Engineering
      1 Everyone
      1 ""
</code></pre>
<p><em>Sample output (illustrative). Wayne Industries is fictional.</em></p>
<p><code>csvcut</code> comes from csvkit, a free set of command-line CSV tools. It handles commas inside fields, which a plain <code>cut</code> doesn't.</p>
<p>Read those five lines again:</p>
<ul>
<li><p><strong>Dan: 11.</strong> The CTO, who also runs engineering, hiring and the AWS bill.</p>
</li>
<li><p><strong>"Security": 4.</strong> That's you. There is one of you.</p>
</li>
<li><p><strong>"Engineering" and "Everyone": 2.</strong> Nobody.</p>
</li>
<li><p><strong>Blank: 1.</strong> Contractor VPN access after offboarding. It's been on Dan's worry thread since 2022.</p>
</li>
</ul>
<p><strong>The unpopular truth:</strong> if the CTO owns everything, the CTO owns nothing. A name repeated eleven times is a default, not a decision.</p>
<hr />
<h2>Owner, operator, backup: three different jobs</h2>
<p>Most ownership confusion comes from one word doing three jobs.</p>
<table>
<thead>
<tr>
<th>Role</th>
<th>What they do</th>
<th>Wayne example (backups)</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Control owner</strong></td>
<td>Accountable for the control working. Makes sure it's designed right, checks it actually runs, signs off the evidence, and escalates when it fails.</td>
<td>Priya Nair</td>
</tr>
<tr>
<td><strong>Control operator</strong></td>
<td>Does the work: runs the job, performs the review, clicks the button.</td>
<td>The developer on the weekly rotation</td>
</tr>
<tr>
<td><strong>Backup owner</strong></td>
<td>Steps in when the owner is away or leaves.</td>
<td>You</td>
</tr>
<tr>
<td><strong>Risk owner</strong></td>
<td>Decides whether the leftover risk is acceptable. Usually more senior.</td>
<td>Dan Okafor (confirmed in Evidence #1)</td>
</tr>
</tbody></table>
<p>The owner and operator can be the same person at a small company. They just shouldn't be the same person <em>by accident</em>.</p>
<p>You may know this as a <strong>RACI</strong> matrix: Responsible, Accountable, Consulted, Informed. RACI is a common project-management convention, not a security standard. Its one rule matters most: <strong>exactly one A per row.</strong> Two A's means two people who can each assume the other has it. That's how the MailRelay email died.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/2f8f8607-92a3-4136-addf-ab662b9fda69.png" alt="" style="display:block;margin:0 auto" />

<p>Three jobs, three names, one A per row.</p>
<hr />
<h2>The four-question test</h2>
<p>A name in a cell is theatre until it passes four questions. Ask them out loud, to the person, not to the spreadsheet.</p>
<ol>
<li><p><strong>Do they know?</strong> Can the owner name the control without looking it up?</p>
</li>
<li><p><strong>Can they act?</strong> Do they have the access, authority and budget to fix it when it breaks?</p>
</li>
<li><p><strong>Do they have time?</strong> Is it realistic next to their actual job?</p>
</li>
<li><p><strong>Will they notice?</strong> When the control fails, does a signal reach <em>them</em> rather than a shared inbox?</p>
</li>
</ol>
<p>You book 15 minutes with Dan and read him his eleven rows.</p>
<p>He recognizes four. For three more, he says, "I think Priya does that." For the other four, he laughs.</p>
<p>That's not a failing on Dan's part. Nobody asked him. Someone typed his name because a CTO sounded safe.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/45f0c52d-87c9-4afc-8aff-24ec0bf280f0.png" alt="" style="display:block;margin:0 auto" />

<p>Knows, can act, has time, will notice. Miss one and it's theatre.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/8ba2ab0c-6c5c-4cac-b0c2-4d3581d40a5a.png" alt="" style="display:block;margin:0 auto" />

<p>Eleven rows with Dan's name. He recognized four.</p>
<hr />
<h2>Finding the bystander inboxes</h2>
<p>The MailRelay notice went to a list with no owner. How many more are there?</p>
<p>Wayne's distribution lists live in Active Directory. AD groups have a <code>managedBy</code> attribute, which records who manages the group. You check the engineering list first.</p>
<pre><code class="language-powershell">Get-ADGroup -Identity "engineering" -Properties mail, managedBy, member |
  Select-Object Name, mail, managedBy, @{n='Members';e={$_.member.Count}}
</code></pre>
<pre><code class="language-plaintext">Name        mail                                  managedBy Members
----        ----                                  --------- -------
engineering engineering@wayne-industries.example                 14
</code></pre>
<p><em>Sample output (illustrative). Wayne Industries is fictional.</em></p>
<p>Empty. Then every mail-enabled group with no manager:</p>
<pre><code class="language-powershell">Get-ADGroup -Filter 'mail -like "*"' -Properties mail, managedBy |
  Where-Object { -not $_.managedBy } |
  Select-Object Name, mail
</code></pre>
<pre><code class="language-plaintext">Name              mail
----              ----
engineering       engineering@wayne-industries.example
all-staff         all-staff@wayne-industries.example
aws-notifications aws-notifications@wayne-industries.example
billing           billing@wayne-industries.example
it-alerts         it-alerts@wayne-industries.example
vendor-accounts   vendor-accounts@wayne-industries.example
</code></pre>
<p><em>Sample output (illustrative). Wayne Industries is fictional.</em></p>
<p>These commands need the ActiveDirectory PowerShell module (part of RSAT) and read access to the directory.</p>
<p>Six lists. Two of them, <code>aws-notifications</code> and <code>vendor-accounts</code>, are exactly where security and vendor warnings arrive.</p>
<p>Setting <code>managedBy</code> doesn't fix anything by itself. It's a pointer. The fix is that each list now has a named person who reads it, and each vendor account in the register has a named contact.</p>
<hr />
<h2>The template: NIST's CSF 2.0 Organizational Profile</h2>
<p>You don't need to invent an ownership matrix. NIST publishes one inside a bigger tool.</p>
<p>The <strong>CSF 2.0 Organizational Profile template</strong> is a free spreadsheet from NIST. It's built for comparing where you are (Current Profile) with where you want to be (Target Profile), outcome by outcome. Among its columns are <strong>Current Roles and Responsibilities</strong> and <strong>Target Roles and Responsibilities</strong>. That's your ownership matrix, sitting next to the status and evidence for each outcome.</p>
<p>Get it from the "CSF 2.0 Profiles" page on NIST's Cybersecurity Framework site, where it's listed as the Organizational Profile template (XLSX). NIST's Quick Start Guide for creating profiles walks through filling it in.</p>
<p><a href="https://www.nist.gov/cyberframework/profiles">https://www.nist.gov/cyberframework/profiles</a> : NIST CSF 2.0 Profiles page</p>
<p><a href="https://www.nist.gov/cyberframework/quick-start-guides">https://www.nist.gov/cyberframework/quick-start-guides</a> : NIST SP 1301, CSF 2.0 Organizational Profiles Quick Start Guide</p>
<p>NIST says to customize the template. Wayne adds four columns to the Target side: <strong>Owner</strong>, <strong>Operator</strong>, <strong>Backup</strong>, and <strong>Owner acknowledged</strong>. One name per cell, no teams.</p>
<h3>Field by field</h3>
<table>
<thead>
<tr>
<th>Column</th>
<th>What goes in it</th>
<th>Wayne tip</th>
</tr>
</thead>
<tbody><tr>
<td>CSF Outcome</td>
<td>The subcategory ID, e.g. PR.DS-11</td>
<td>One outcome per row. Wayne's 18 rows cover the outcomes its fixes touch.</td>
</tr>
<tr>
<td>CSF Outcome Description</td>
<td>NIST's wording for the outcome</td>
<td>Copy it; don't paraphrase.</td>
</tr>
<tr>
<td>Included in Profile?</td>
<td>Yes or no</td>
<td>Wayne's "no" rows keep a one-line rationale.</td>
</tr>
<tr>
<td>Rationale</td>
<td>Why it's in or out</td>
<td>"Gotham Mutual asks" is a valid reason.</td>
</tr>
<tr>
<td>Current Status</td>
<td>Whether the outcome is achieved today, and how well</td>
<td>Honest words: "not done," "partial," "done, not proven."</td>
</tr>
<tr>
<td>Current Roles and Responsibilities</td>
<td>Who does it today</td>
<td>This is where the theatre was.</td>
</tr>
<tr>
<td>Current Artifacts and Evidence</td>
<td>What proves it today</td>
<td>Blank is a finding, not a formatting problem.</td>
</tr>
<tr>
<td>Target Roles and Responsibilities</td>
<td>Who should do it</td>
<td>Wayne splits this into the four added columns below.</td>
</tr>
<tr>
<td>Owner <em>(added)</em></td>
<td>One accountable person</td>
<td>Must pass the four questions.</td>
</tr>
<tr>
<td>Operator <em>(added)</em></td>
<td>Who performs it</td>
<td>A person or a named rotation.</td>
</tr>
<tr>
<td>Backup <em>(added)</em></td>
<td>Who covers</td>
<td>Never the same person as the owner.</td>
</tr>
<tr>
<td>Owner acknowledged <em>(added)</em></td>
<td>Date the owner replied "I accept"</td>
<td>No date means no owner.</td>
</tr>
<tr>
<td>Notes</td>
<td>Anything else</td>
<td>Wayne records the signal: where failures get reported.</td>
</tr>
</tbody></table>
<h3>Wayne before</h3>
<table>
<thead>
<tr>
<th>CSF Outcome</th>
<th>Current Status</th>
<th>Current Roles and Responsibilities</th>
<th>Current Artifacts and Evidence</th>
</tr>
</thead>
<tbody><tr>
<td>GV.SC-02: roles for suppliers</td>
<td>Not done</td>
<td>Engineering</td>
<td>None</td>
</tr>
<tr>
<td>PR.DS-11: backups created, protected, maintained, tested</td>
<td>Partial</td>
<td>Dan Okafor</td>
<td>412 job logs, 0 restore tests</td>
</tr>
<tr>
<td>PR.AA-03: users, services and hardware authenticated</td>
<td>Partial</td>
<td>Security</td>
<td>MFA on about 30% of accounts</td>
</tr>
<tr>
<td>PR.AA-05: access permissions managed and reviewed</td>
<td>Not done</td>
<td><em>(blank)</em></td>
<td>None</td>
</tr>
<tr>
<td>PR.PS-01: configuration management practices applied</td>
<td>Not done</td>
<td>Dan Okafor</td>
<td>None</td>
</tr>
<tr>
<td>DE.CM-09: computing, runtime and data monitored</td>
<td>Not done</td>
<td>Everyone</td>
<td>CloudTrail on, unread</td>
</tr>
</tbody></table>
<h3>Wayne after</h3>
<table>
<thead>
<tr>
<th>CSF Outcome</th>
<th>Owner</th>
<th>Operator</th>
<th>Backup</th>
<th>Owner acknowledged</th>
<th>Notes</th>
</tr>
</thead>
<tbody><tr>
<td>GV.SC-02</td>
<td>Priya Nair</td>
<td>Priya Nair</td>
<td>Dan Okafor</td>
<td>Tue</td>
<td>Vendor notices now go to <code>vendor-accounts</code>, managed by Priya. Each vendor has a named contact.</td>
</tr>
<tr>
<td>PR.DS-11</td>
<td>Priya Nair</td>
<td>Weekly dev rotation</td>
<td>You</td>
<td>Tue</td>
<td>Failed jobs page the rotation, not a list.</td>
</tr>
<tr>
<td>PR.AA-03</td>
<td>You</td>
<td>You</td>
<td>Dan Okafor</td>
<td>Tue</td>
<td>Owner and operator are the same person. Accepted for now, flagged.</td>
</tr>
<tr>
<td>PR.AA-05</td>
<td>Farah Haddad</td>
<td>You</td>
<td>Dan Okafor</td>
<td>Wed</td>
<td>Offboarding starts in People &amp; Ops, so the trigger sits with Farah. You remove VPN access.</td>
</tr>
<tr>
<td>PR.PS-01</td>
<td>Priya Nair</td>
<td>GitLab Maintainers</td>
<td>Dan Okafor</td>
<td>Tue</td>
<td>Covers merge approvals on <code>main</code>.</td>
</tr>
<tr>
<td>DE.CM-09</td>
<td>You</td>
<td>You, weekly</td>
<td>Priya Nair</td>
<td>Tue</td>
<td>Weekly CloudTrail review, logged.</td>
</tr>
</tbody></table>
<p>The after table doesn't say any of these controls work yet. Backups are still untested. MFA is still at 30%. What changed is that each gap now has one person who knows it's theirs.</p>
<p>After the change, the count across all 18 rows looks like this:</p>
<pre><code class="language-bash">csvcut -c "Owner" wayne-csf-profile-target.csv \
  | tail -n +2 | sort | uniq -c | sort -rn
</code></pre>
<pre><code class="language-plaintext">      6 Priya Nair
      6 You (Head of Security)
      3 Dan Okafor
      2 Farah Haddad
      1 Maya Lindqvist
</code></pre>
<p><em>Sample output (illustrative). Wayne Industries is fictional.</em></p>
<p>Dan keeps three rows he actually decides: risk acceptance, the AWS account structure, and security resourcing (GV.RR-03). Maya keeps one: leadership accountability (GV.RR-01), which is hers whether she likes it or not. Farah Haddad, who leads People &amp; Operations, takes offboarding and security in HR practices (GV.RR-04).</p>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/15177171-1fe8-4cf7-88e1-dba3bc590667.png" alt="" style="display:block;margin:0 auto" />

<p>The same six rows, before and after. Names changed; status didn't. Proving the controls work is a separate job.</p>
<p><strong>The unpopular truth:</strong> most ownership matrices are written for the auditor, not for the people named in them. If the owners have never seen the sheet, it's a costume.</p>
<hr />
<h2>Evidence #6</h2>
<p>Getting names isn't enough. You need proof the names know.</p>
<p>On Tuesday you send each owner their rows, one email each:</p>
<blockquote>
<p><em>You're listed as owner for the controls below. Owner means you're accountable for them working, you'll get the failure alerts, and you'll sign off the evidence. Reply "I accept," or tell me what's wrong with the assignment.</em></p>
</blockquote>
<p>Priya, Dan and Maya reply "I accept" on Tuesday. Farah replies Wednesday with a question: does "owner" mean she removes VPN access herself? No. She owns the trigger; you do the removal. She accepts.</p>
<p>Dan pushed back on one row and moved it to Priya. That's the process working.</p>
<p>On Friday, Dan approves the Target Profile. The sheet, the four acknowledgment emails, your own signed note for your rows, and the <code>managedBy</code> changes go in the evidence folder together.</p>
<p><strong>Evidence #6:</strong> dated, named and acknowledged ownership for all 18 controls.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/25c63ba6-dea7-4e24-838e-2c0ad6c59591.png" alt="" style="display:block;margin:0 auto" />

<p>Four replies, one approval, one folder.</p>
<hr />
<h2>What an auditor accepts vs rejects</h2>
<p>ISO 27001 auditors and SOC 2 auditors both test ownership the same way: they read the document, then interview the person.</p>
<table>
<thead>
<tr>
<th>Accepted</th>
<th>Rejected</th>
</tr>
</thead>
<tbody><tr>
<td>A named individual with a role title</td>
<td>A team, department or distribution list</td>
</tr>
<tr>
<td>A dated acknowledgment from the owner</td>
<td>A name the owner has never seen</td>
</tr>
<tr>
<td>An owner who can explain the control in an interview</td>
<td>An owner who says, "I think Priya does that"</td>
</tr>
<tr>
<td>A named backup who isn't the owner</td>
<td>Nobody listed for leave or departure</td>
</tr>
<tr>
<td>Evidence signed off by the owner</td>
<td>Evidence nobody reviewed</td>
</tr>
<tr>
<td>A matrix reviewed after the last reorg</td>
<td>Owners who left the company</td>
</tr>
<tr>
<td>One person with many rows, and a note on how they cope</td>
<td>One person on most rows, with no comment</td>
</tr>
</tbody></table>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/cc92ec2b-598d-4c67-a17c-c71e7715058f.png" alt="" style="display:block;margin:0 auto" />

<p>"Engineering." "Which person?"</p>
<hr />
<h2>What you actually do on Monday</h2>
<ol>
<li><p><strong>Export your control list and count the names,</strong> like the <code>uniq -c</code> above. Anything over a third of the rows on one person needs a conversation.</p>
</li>
<li><p><strong>Replace every team name with a person.</strong> If you can't pick one, you've found a real gap.</p>
</li>
<li><p><strong>Run the four questions</strong> with each owner. Fifteen minutes each is enough.</p>
</li>
<li><p><strong>Find your bystander inboxes.</strong> Check every shared list that receives vendor, cloud or security email, and give each one a named reader.</p>
</li>
<li><p><strong>Get written acknowledgment.</strong> A one-line "I accept" reply is evidence. Silence isn't.</p>
</li>
<li><p><strong>Set review triggers.</strong> Revisit the matrix when someone changes role or leaves, and at least once a year.</p>
</li>
</ol>
<hr />
<h2>Framework mapping</h2>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/d4b3d75f-3f8d-4b05-be7a-a1ec0fbf705c.png" alt="" style="display:block;margin:0 auto" />

<p>Ownership, as each framework puts it.</p>
<hr />
<h2>Maturity ladder</h2>
<table>
<thead>
<tr>
<th>Stage</th>
<th>20 people</th>
<th>200 people</th>
<th>2,000 people</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Who owns</strong></td>
<td>The founder or CTO for most things, and that's honest</td>
<td>Named owners across engineering, ops and People, one A per row</td>
<td>Owners per business unit, with a second line checking them</td>
</tr>
<tr>
<td><strong>Where it lives</strong></td>
<td>A spreadsheet with one owner column</td>
<td>The CSF profile or a GRC tool, with owner, operator and backup</td>
<td>A GRC platform tied to the HR system, so leavers are flagged automatically</td>
</tr>
<tr>
<td><strong>Proof</strong></td>
<td>An "I accept" email</td>
<td>Dated acknowledgments, reviewed after role changes</td>
<td>Annual attestation campaigns, with interview samples by internal audit</td>
</tr>
<tr>
<td><strong>Signals</strong></td>
<td>Alerts go to the founder's phone</td>
<td>Alerts route to the named owner or rotation</td>
<td>Ownership drives ticket routing and escalation paths</td>
</tr>
<tr>
<td><strong>Failure mode</strong></td>
<td>One person burns out</td>
<td>Team names creep back in</td>
<td>Owners on paper who've never seen the control</td>
</tr>
</tbody></table>
<p>At 20 people, a spreadsheet with the same name on every row is fine <em>if that person knows and agreed</em>. The problem is never the spreadsheet. It's the name nobody asked.</p>
<p><strong>Where Wayne sits:</strong> at the 200-person stage on paper, as of Friday. Every row has one owner, an operator, a backup and a date. What's untested is whether the signals really reach the owners. The next vendor notice will show whether <code>vendor-accounts</code> works.</p>
<hr />
<h2>Cheatsheet</h2>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/46e76f66-1a0f-4c7e-bd90-eba0c8dc8a2a.png" alt="" style="display:block;margin:0 auto" />

<p>Control ownership, on one page.</p>
<hr />
<h2>The takeaway</h2>
<p>A name in a cell is not ownership. A person who knows, can act, has time and gets the alert is. One accountable person per control. Teams and inboxes don't count. Get it in writing: a dated "I accept" beats a full matrix. Spread the load honestly, and write down where one person still carries too much.</p>
<hr />
<blockquote>
<p>⚠️ This content is for educational purposes only. Wayne Industries is a fictional company. Nothing here is legal, audit, or compliance advice, validate against your own auditor and jurisdiction.</p>
</blockquote>
]]></content:encoded></item><item><title><![CDATA[Data Flow Mapping With Trust Boundaries]]></title><description><![CDATA[@18xBan · GRC Series · Chapter 05


One diagram from 2022, five boxes, and no arrows leaving AWS.

Monday, 8:47 AM: "Where else has it been?"
Dan forwards an email from Wayne's outside legal counsel. ]]></description><link>https://18xban.hashnode.dev/data-flow-mapping-trust-boundaries</link><guid isPermaLink="true">https://18xban.hashnode.dev/data-flow-mapping-trust-boundaries</guid><category><![CDATA[data-flow-mapping]]></category><category><![CDATA[threat modeling]]></category><category><![CDATA[#gdpr]]></category><category><![CDATA[privacy]]></category><category><![CDATA[grc]]></category><dc:creator><![CDATA[18xBan]]></dc:creator><pubDate>Sat, 19 Sep 2026 03:43:11 GMT</pubDate><content:encoded><![CDATA[<p><em>@18xBan · GRC Series · Chapter 05</em></p>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/1adff320-1b5b-421f-8229-bcac41de7b14.png" alt="" style="display:block;margin:0 auto" />

<p>One diagram from 2022, five boxes, and no arrows leaving AWS.</p>
<hr />
<h2>Monday, 8:47 AM: "Where else has it been?"</h2>
<p>Dan forwards an email from Wayne's outside legal counsel. It's about <code>prod-export-2024.sql.gz</code>, the production export still sitting (locked now) in the staging bucket.</p>
<p>Counsel's question is short:</p>
<blockquote>
<p>Before we can advise on whether this is reportable, we need to know every place this customer data goes, and every place a copy of it could have ended up.</p>
</blockquote>
<p>Ten minutes later, a second forward. Gotham Mutual's questionnaire, question 58:</p>
<blockquote>
<p><em>Provide a data flow diagram showing where customer data is stored, processed and transmitted, including subprocessors and their locations.</em></p>
</blockquote>
<p>Two different people, the same question: <strong>where does Wayne's customer data actually go?</strong></p>
<p>You open the wiki. The only diagram is <code>architecture-v2.png</code>, drawn in 2022. Five boxes: Browser, Load balancer, App, Database, Backups. Neat arrows. No staging. No vendors. Nothing ever leaves AWS.</p>
<p>You ask Priya if that's still accurate.</p>
<blockquote>
<p><strong>Priya:</strong> Roughly. <strong>You:</strong> Does customer data go anywhere that isn't on this picture? <strong>Priya:</strong> ...define "anywhere."</p>
</blockquote>
<p>That's the week.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/049f1066-bde8-4195-8057-588b0e7c4061.png" alt="" style="display:block;margin:0 auto" />

<p>Counsel and a customer ask the same question. The wiki has five boxes.</p>
<hr />
<h2>What a data flow map actually is</h2>
<p>A <strong>data flow diagram (DFD)</strong> shows how data moves: where it comes from, what touches it, where it's stored and where it leaves. It uses four shapes:</p>
<ul>
<li><p><strong>External entity</strong> (or actor): a person or system outside your control. A customer, a vendor.</p>
</li>
<li><p><strong>Process:</strong> something that handles data. Your API, a background worker, a CI job.</p>
</li>
<li><p><strong>Data store:</strong> somewhere data rests. A database, a bucket, a backup, a spreadsheet.</p>
</li>
<li><p><strong>Data flow:</strong> an arrow showing data moving from one to another.</p>
</li>
</ul>
<p>Then there's the fifth element, the one that makes the diagram useful for security: the <strong>trust boundary</strong>. It's a dashed line drawn wherever the level of trust changes. Internet to your network. Production to staging. Your company to a vendor. Canada to another country.</p>
<p>Think of an airport. Passport control and the security checkpoint are trust boundaries. Everything that crosses them gets checked: who you are, what you're carrying, whether you're allowed through. The airport is fine with people moving around. What it cares about is the crossings.</p>
<p>Data flows work the same way. A flow that stays inside production is one kind of risk. A flow that crosses a boundary needs answers: who approved it, is it encrypted, is there a contract, and does anyone know it exists?</p>
<p>And the thing that ruins airports is the same thing that ruins data flow maps: the staff door nobody put on the floor plan.</p>
<p><strong>The unpopular truth:</strong> your architecture diagram is not a data flow map. An architecture diagram shows how the system is <em>built</em>. A data flow map shows where the data <em>goes</em>, including the ugly parts: the export someone ran once, the CSV in an email, the error tracker quietly collecting customer emails.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/34577da4-c1ef-4bb7-a932-2ed80dbbd2a5.png" alt="" style="display:block;margin:0 auto" />

<p>Nobody worries about the hallway. They worry about the doors.</p>
<hr />
<h2>The tool: OWASP Threat Dragon</h2>
<p>You don't need to invent a diagram format. <strong>OWASP Threat Dragon</strong> is a free, open-source tool from the OWASP Foundation for drawing data flow diagrams with trust boundaries. It's built for threat modelling, but today you'll only use it to draw.</p>
<p>It runs as a desktop app (Windows, macOS, Linux) or as a web app you host yourself. Get it from the OWASP Threat Dragon project page or its GitHub releases.</p>
<p><a href="https://owasp.org/projects/threat-dragon">https://owasp.org/projects/threat-dragon</a>: OWASP Threat Dragon project page</p>
<p><a href="https://github.com/OWASP/threat-dragon">https://github.com/OWASP/threat-dragon</a>: OWASP Threat Dragon GitHub</p>
<p>Two things make it a good fit for a one-person security team:</p>
<ol>
<li><p><strong>The model is a JSON file.</strong> You can commit it to Git, review changes in a merge request, and query it from the command line.</p>
</li>
<li><p><strong>Each element has security properties.</strong> A flow records whether it's encrypted and whether it crosses a public network. A store records whether it's encrypted or holds credentials. That turns a picture into something you can check.</p>
</li>
</ol>
<p>Threat Dragon draws the map. The <strong>classification scheme</strong> from last week tells you which flows matter most. Anything carrying Restricted data gets drawn first, and gets drawn completely.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/22ed6c3d-a0a1-4c8f-9c0b-942fcf6f378b.png" alt="" style="display:block;margin:0 auto" />

<p>Four shapes and one dashed line. That's the whole notation.</p>
<hr />
<h2>Field by field: what Threat Dragon records</h2>
<table>
<thead>
<tr>
<th>Element</th>
<th>Field</th>
<th>What it holds</th>
<th>Why it matters</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Any element</strong></td>
<td>Name</td>
<td>What it is, in plain words</td>
<td>"F7 refresh-staging" beats "Job 3"</td>
</tr>
<tr>
<td><strong>Any element</strong></td>
<td>Description</td>
<td>Free text</td>
<td>Wayne records the data class and which boundary it crosses</td>
</tr>
<tr>
<td><strong>Any element</strong></td>
<td>Out of scope + reason</td>
<td>Excluded from this model, and why</td>
<td>Scope decisions become visible, not silent</td>
</tr>
<tr>
<td><strong>Actor</strong></td>
<td>Provides authentication</td>
<td>This entity signs users in</td>
<td>Marks where identity comes from</td>
</tr>
<tr>
<td><strong>Process</strong></td>
<td>(name, description)</td>
<td>Something that handles data</td>
<td>Every process is a place data can be copied</td>
</tr>
<tr>
<td><strong>Store</strong></td>
<td>Is encrypted</td>
<td>Data at rest is encrypted</td>
<td>Restricted stores should say yes</td>
</tr>
<tr>
<td><strong>Store</strong></td>
<td>Is a log</td>
<td>The store holds logs</td>
<td>Logs often hold personal info nobody planned for</td>
</tr>
<tr>
<td><strong>Store</strong></td>
<td>Stores credentials</td>
<td>Passwords, keys or tokens live here</td>
<td>Flags the stores attackers want most</td>
</tr>
<tr>
<td><strong>Store</strong></td>
<td>Is signed</td>
<td>Integrity protection on stored data</td>
<td>Tampering is a risk too, not just leaks</td>
</tr>
<tr>
<td><strong>Flow</strong></td>
<td>Protocol</td>
<td>How data moves: HTTPS, pg_dump, email</td>
<td>"Email attachment" should make you pause</td>
</tr>
<tr>
<td><strong>Flow</strong></td>
<td>Is encrypted</td>
<td>Encrypted in transit</td>
<td>Required for any Restricted flow</td>
</tr>
<tr>
<td><strong>Flow</strong></td>
<td>Is public network</td>
<td>Crosses the internet</td>
<td>Raises the bar for everything else</td>
</tr>
<tr>
<td><strong>Flow</strong></td>
<td>Is bidirectional</td>
<td>Data moves both ways</td>
<td>Changes who can send what to whom</td>
</tr>
<tr>
<td><strong>Trust boundary</strong></td>
<td>Name</td>
<td>Which boundary it is</td>
<td>Wayne numbers them TB1 to TB4</td>
</tr>
</tbody></table>
<hr />
<h2>Wayne before</h2>
<pre><code class="language-plaintext">Data flow documentation: architecture-v2.png (2022), 5 boxes
Flows shown: 2 (browser to app, app to database)
Third parties shown: 0
Trust boundaries shown: 0
Countries shown: 0
Last reviewed: never
Approved by: nobody
</code></pre>
<hr />
<h2>Step 1: Watch, Read, Ask</h2>
<p>There are three ways to find data flows, and you need all three. Each one misses things the others catch.</p>
<ul>
<li><p><strong>Watch</strong> what's connected right now.</p>
</li>
<li><p><strong>Read</strong> the code and config for anything that sends data somewhere.</p>
</li>
<li><p><strong>Ask</strong> the people who move data by hand.</p>
</li>
</ul>
<p>Start at the most sensitive store and work outward. For Wayne, that's the production customer database in <code>wayne-prod</code>, in AWS's Canada (Central) region.</p>
<h3>Watch: who's connected, and who could be</h3>
<pre><code class="language-bash">psql -h wayne-prod-db.xxxxxxxx.ca-central-1.rds.amazonaws.com -U postgres -d appdb -c "
SELECT usename, application_name, client_addr
FROM pg_stat_activity
WHERE datname = 'appdb' AND usename IS NOT NULL;"
</code></pre>
<pre><code class="language-plaintext">   usename    | application_name | client_addr
--------------+------------------+-------------
 wayne_api    | wayne-api        | 10.20.1.15
 wayne_api    | wayne-api        | 10.20.1.38
 wayne_worker | wayne-worker     | 10.20.2.9
(3 rows)
</code></pre>
<p><em>Sample output (illustrative). Wayne Industries is fictional.</em></p>
<p>That's today's connections. It only shows what's running <em>right now</em>. So ask a second question: who is allowed to log in at all?</p>
<pre><code class="language-bash">psql -h wayne-prod-db.xxxxxxxx.ca-central-1.rds.amazonaws.com -U postgres -d appdb -c "
SELECT rolname FROM pg_roles WHERE rolcanlogin ORDER BY rolname;"
</code></pre>
<pre><code class="language-plaintext">   rolname
--------------
 ci_refresh
 postgres
 wayne_api
 wayne_worker
(4 rows)
</code></pre>
<p><em>Sample output (illustrative). Wayne Industries is fictional.</em></p>
<p><code>ci_refresh</code> isn't connected. It can log in anyway. Nobody on the 2022 diagram is called that.</p>
<h3>Read: find what uses it</h3>
<p>Search the code for the role name and the staging bucket:</p>
<pre><code class="language-bash">cd ~/src/wayne-app
grep -rn -e "ci_refresh" -e "wayne-staging-data" --include="*.yml" --include="*.sh" .
</code></pre>
<pre><code class="language-plaintext">./.gitlab-ci.yml:88:  refresh_staging:
./.gitlab-ci.yml:93:    - ./scripts/refresh-staging.sh
./scripts/refresh-staging.sh:9:PGUSER=ci_refresh
./scripts/refresh-staging.sh:14:aws s3 cp prod-export-$(date +%Y).sql.gz s3://wayne-staging-data/
./scripts/refresh-staging.sh:17:gunzip -c prod-export-$(date +%Y).sql.gz | psql "$STAGING_DB_URL"
</code></pre>
<p><em>Sample output (illustrative). Wayne Industries is fictional.</em></p>
<p>There it is. A CI job that dumps production, uploads the dump to the staging bucket, and restores it into the <strong>staging database</strong>. That's where <code>prod-export-2024.sql.gz</code> came from. And it means there's a second copy nobody had counted: the staging database itself.</p>
<p>Is the job still scheduled? GitLab's API will tell you:</p>
<pre><code class="language-bash">curl --silent --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
  "https://gitlab.wayne-industries.example/api/v4/projects/42/pipeline_schedules"
</code></pre>
<pre><code class="language-json">[
  {
    "id": 3,
    "description": "refresh staging from prod",
    "ref": "refs/heads/main",
    "cron": "0 2 * * 0",
    "cron_timezone": "America/Toronto",
    "next_run_at": "2025-01-05T07:00:00.000Z",
    "active": false,
    "created_at": "2024-03-11T15:02:41.000Z",
    "updated_at": "2024-03-18T09:12:05.000Z",
    "owner": { "name": "Priya Nair", "username": "priya.nair" }
  }
]
</code></pre>
<p><em>Sample output (illustrative). Wayne Industries is fictional.</em></p>
<p>Inactive. Priya confirms the story: she set it up in March 2024, it ran once, the staging restore broke half the test suite, and she switched it off. The job, the script and the <code>ci_refresh</code> credential stayed.</p>
<blockquote>
<p><strong>Priya:</strong> It's off. It's not a flow anymore. <strong>You:</strong> One click turns it back on. It's a flow.</p>
</blockquote>
<p>A dormant flow still goes on the map. What matters is what <em>can</em> happen, not just what happened last week.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/694f9ca6-9c61-4a5e-889c-da4d1a383c55.png" alt="" style="display:block;margin:0 auto" />

<p>Switched off in 2024. Still one click from running.</p>
<p>Next, search for anything that sends data <em>out</em> of Wayne:</p>
<pre><code class="language-bash">grep -rhoE "https://[a-zA-Z0-9.-]+\.[a-z]{2,}" config/ app/ | sort | uniq -c | sort -rn
</code></pre>
<pre><code class="language-plaintext">     14 https://api.mailrelay.example
      9 https://ingest.tracelight.example
      6 https://api.deskline.example
      3 https://docs.wayne-industries.example
</code></pre>
<p><em>Sample output (illustrative). Wayne Industries is fictional.</em></p>
<p>Three vendors receive data straight from the app: a transactional email service (MailRelay), an error tracker (Tracelight) and the support desk (Deskline). They're 3 of Wayne's 12 unreviewed vendors. Their own documentation says MailRelay and Tracelight process data in the United States. Deskline hosts in Canada.</p>
<p>The error tracker deserves a closer look. Stack traces often include whatever was in memory when something broke. You open a recent Tracelight event and find a customer's email address in the request body. Nobody chose to send customer data to Tracelight. It just happens every time the app throws an error.</p>
<h3>Read the cloud, too</h3>
<p>Code isn't the only place flows hide. Infrastructure settings move data too:</p>
<pre><code class="language-bash">aws rds describe-db-instance-automated-backups \
  --region us-east-1 \
  --profile wayne-prod \
  --query 'DBInstanceAutomatedBackups[].[DBInstanceIdentifier,Region,Status,Encrypted]' \
  --output table
</code></pre>
<pre><code class="language-plaintext">-------------------------------------------------------------
|              DescribeDBInstanceAutomatedBackups           |
+-------------------+----------------+--------------+-------+
|  wayne-prod-db    |  ca-central-1  |  replicating |  True |
+-------------------+----------------+--------------+-------+
</code></pre>
<p><em>Sample output (llustrative). Wayne Industries is fictional.</em></p>
<p>Run in the <code>us-east-1</code> region, this shows backups of <code>wayne-prod-db</code>, which lives in <code>ca-central-1</code>, being copied to the United States. Dan turned it on in 2023 for disaster recovery. It's a sensible setting. It's also customer data leaving Canada, and it isn't written down anywhere.</p>
<h3>Ask: the flows no tool can find</h3>
<p>Then you talk to people. The question that works best isn't "what systems do you use?" It's: <strong>"When did you last send customer information to anyone?"</strong></p>
<p>Maya answers on Wednesday without looking up from her laptop:</p>
<blockquote>
<p><strong>Maya:</strong> Every quarter, marketing emails the web agency a list of customer contacts for the newsletter. <strong>You:</strong> As an attachment? <strong>Maya:</strong> A spreadsheet. Names, emails, company.</p>
</blockquote>
<p>No code, no API, no log in AWS. A CSV, in an email, to an outside agency that runs its own AWS account (<code>marketing-test</code>) and has never had a security review.</p>
<p><strong>The unpopular truth:</strong> the riskiest flows usually aren't in your code. They're in people's habits. Code search will never find a spreadsheet someone emails every quarter. You have to ask, and you have to ask the question in their words, not yours.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/00434b51-b2f9-48b1-a43d-6adb2896decc.png" alt="" style="display:block;margin:0 auto" />

<p>No API, no log, no diagram. Just a spreadsheet, every quarter.</p>
<hr />
<h2>Step 2: Draw the boundaries</h2>
<p>With the flows found, the diagram almost draws itself. Wayne's map has four trust boundaries:</p>
<table>
<thead>
<tr>
<th>Boundary</th>
<th>Between</th>
<th>What crossing it should require</th>
</tr>
</thead>
<tbody><tr>
<td><strong>TB1</strong></td>
<td>The internet and Wayne production</td>
<td>TLS, authentication, rate limits</td>
</tr>
<tr>
<td><strong>TB2</strong></td>
<td>Production and non-production (staging, dev)</td>
<td><strong>No Restricted data crosses, ever</strong></td>
</tr>
<tr>
<td><strong>TB3</strong></td>
<td>Wayne and third parties, including other countries</td>
<td>A reviewed vendor, a contract, a documented location</td>
</tr>
<tr>
<td><strong>TB4</strong></td>
<td>Wayne systems and people's email or laptops</td>
<td>A business reason, an approver, and the least data needed</td>
</tr>
</tbody></table>
<p>TB2 is the rule the classification scheme already implied. Last week's scheme said Restricted data lives in <code>wayne-prod</code> only. The map shows exactly where that rule was broken, and how.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/341899b4-277b-45fe-8428-c52fa199bf14.png" alt="" style="display:block;margin:0 auto" />

<p>Wayne's customer data, with every boundary crossing numbered.</p>
<hr />
<h2>Step 3: Turn the picture into a register</h2>
<p>Threat Dragon saves the model as JSON, so you can pull every flow into a table and track decisions against it:</p>
<pre><code class="language-bash">jq -r '.detail.diagrams[].cells[]
  | select(.data.type == "tm.Flow")
  | [.data.name, .data.protocol, (.data.isEncrypted|tostring), (.data.isPublicNetwork|tostring)]
  | @tsv' wayne-customer-data.json | column -t -s $'\t'
</code></pre>
<pre><code class="language-plaintext">F1 Customer sign-in and app use               HTTPS             true   true
F2 API reads and writes customer records      PostgreSQL/TLS    true   false
F3 Automated backup replication to us-east-1  AWS internal      true   false
F4 Transactional email: name + email          HTTPS             true   true
F5 Error events with stack traces             HTTPS             true   true
F6 Support tickets                            HTTPS             true   true
F7 refresh-staging: prod dump to staging      pg_dump + S3      false  false
F8 Quarterly contact list to web agency       Email attachment  false  true
</code></pre>
<p><em>Sample output (illustrative). Wayne Industries is fictional.</em></p>
<p>Two <code>false</code> values in the "encrypted" column, and both are on flows nobody had drawn. That's not a coincidence. Undocumented flows are the ones nobody ever checked.</p>
<hr />
<h2>Step 4: Decide every crossing</h2>
<p>The map tells you what exists. Dan, as the owner of customer data, decides what's allowed to keep existing. You sit down with him on Wednesday afternoon and go flow by flow.</p>
<h3>Wayne after (approved by Dan, Wednesday)</h3>
<table>
<thead>
<tr>
<th>Flow</th>
<th>Crosses</th>
<th>Decision</th>
<th>Owner</th>
</tr>
</thead>
<tbody><tr>
<td>F1 Customer sign-in</td>
<td>TB1</td>
<td>Keep. Already documented.</td>
<td>Priya</td>
</tr>
<tr>
<td>F2 API to database</td>
<td>—</td>
<td>Keep. Inside production.</td>
<td>Priya</td>
</tr>
<tr>
<td>F3 Backup copy to us-east-1</td>
<td>TB3</td>
<td><strong>Keep</strong> for disaster recovery. Now documented and added to the customer-facing list of data locations.</td>
<td>Dan</td>
</tr>
<tr>
<td>F4 MailRelay</td>
<td>TB3</td>
<td>Keep. Send name and email only. Vendor review queued.</td>
<td>You</td>
</tr>
<tr>
<td>F5 Tracelight</td>
<td>TB3</td>
<td>Keep, but <strong>turn on data scrubbing</strong> so emails and request bodies are stripped before sending. Retest after.</td>
<td>Priya</td>
</tr>
<tr>
<td>F6 Deskline</td>
<td>TB3</td>
<td>Keep. Vendor review queued.</td>
<td>You</td>
</tr>
<tr>
<td>F7 refresh-staging</td>
<td>TB2</td>
<td><strong>Remove.</strong> Delete the schedule and the job, revoke <code>ci_refresh</code>, and reseed staging with synthetic data.</td>
<td>Priya</td>
</tr>
<tr>
<td>F8 Contact list to agency</td>
<td>TB3, TB4</td>
<td><strong>Paused</strong> by Maya until the agency is reviewed.</td>
<td>Maya</td>
</tr>
</tbody></table>
<p>A few things happen by Friday:</p>
<ul>
<li><p>Priya's merge request removes <code>refresh_staging</code> and the script. <code>ci_refresh</code> loses its login.</p>
</li>
<li><p>Dan checks with counsel, who confirm the private staging database isn't needed for the exposure question. Staging is wiped and reseeded with fake data.</p>
</li>
<li><p>Tracelight scrubbing is on. A forced test error arrives with <code>[Filtered]</code> where the email used to be.</p>
</li>
<li><p>The original export (<code>prod-export-2024.sql.gz</code>) stays held, as before. Counsel now has the full map to work from.</p>
</li>
<li><p>The diagram goes to Gotham Mutual for Q58, with the vendor names and countries listed.</p>
</li>
</ul>
<p>Dan's written approval of the map, dated Wednesday, plus the merged change removing F7, become <strong>Evidence #5</strong>.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/63b40197-7b6a-4e04-acfd-365bd8a29cce.png" alt="" style="display:block;margin:0 auto" />

<p>One map, eight decisions, one dated approval.</p>
<p><strong>The unpopular truth:</strong> a data flow map is wrong the week after you draw it, unless something forces an update. Don't rely on "review annually." Tie it to change: a new vendor, a new integration, a new CI job that touches production. At Wayne, any merge request that adds an outbound URL or a production database role now needs a line in the map.</p>
<hr />
<h2>The flow register, before and after</h2>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/86e7c22a-1226-40b2-be02-1b4191bf6e53.png" alt="" style="display:block;margin:0 auto" />

<p>Two flows on paper became eight flows with owners and decisions.</p>
<table>
<thead>
<tr>
<th></th>
<th>Before</th>
<th>After</th>
</tr>
</thead>
<tbody><tr>
<td>Flows documented</td>
<td>2</td>
<td>8</td>
</tr>
<tr>
<td>Trust boundaries drawn</td>
<td>0</td>
<td>4</td>
</tr>
<tr>
<td>Third parties shown</td>
<td>0</td>
<td>4 (MailRelay, Tracelight, Deskline, web agency)</td>
</tr>
<tr>
<td>Flows leaving Canada</td>
<td>unknown</td>
<td>3 (F3, F4, F5), all documented</td>
</tr>
<tr>
<td>Restricted flows crossing prod to staging</td>
<td>unknown</td>
<td>0 (F7 removed)</td>
</tr>
<tr>
<td>Flows with a named owner and decision</td>
<td>0</td>
<td>8</td>
</tr>
<tr>
<td>Map approved by</td>
<td>nobody</td>
<td>Dan Okafor, Wednesday</td>
</tr>
</tbody></table>
<hr />
<h2>What an auditor accepts vs rejects</h2>
<table>
<thead>
<tr>
<th>The auditor asks</th>
<th>Rejected</th>
<th>Accepted</th>
</tr>
</thead>
<tbody><tr>
<td>"Show me your data flow documentation."</td>
<td>An architecture diagram with no data, no vendors, no date</td>
<td>A dated DFD with trust boundaries, approved by a named owner</td>
</tr>
<tr>
<td>"Does it include service providers?"</td>
<td>"Those are in procurement's list somewhere."</td>
<td>Vendors on the map, with the data they receive and where they process it</td>
</tr>
<tr>
<td>"Is it current?"</td>
<td>"It was accurate when we drew it."</td>
<td>A review date, plus a rule that ties updates to changes</td>
</tr>
<tr>
<td>"Show me this flow in the real system."</td>
<td>Someone explains it from memory</td>
<td>The config, job or setting that matches the arrow on the map</td>
</tr>
<tr>
<td>"How do you know nothing is missing?"</td>
<td>"We asked the architect."</td>
<td>Watch, read and ask: live connections, code and config search, and interviews</td>
</tr>
<tr>
<td>"What happens when a flow breaks your rules?"</td>
<td>A comment in a meeting</td>
<td>A decision per flow, with an owner and a closed change</td>
</tr>
</tbody></table>
<hr />
<h2>What you actually do on Monday</h2>
<ol>
<li><p><strong>Pick your most sensitive data store.</strong> Start there, not with the whole company.</p>
</li>
<li><p><strong>Watch:</strong> list live connections and every account that <em>can</em> log in.</p>
</li>
<li><p><strong>Read:</strong> search code and config for outbound URLs, bucket names and database roles.</p>
</li>
<li><p><strong>Read the cloud:</strong> check backup copies, replication and cross-region settings.</p>
</li>
<li><p><strong>Ask:</strong> "When did you last send customer information to anyone?" Ask marketing, support and finance, not just engineering.</p>
</li>
<li><p><strong>Draw it in Threat Dragon.</strong> Four shapes, plus dashed lines where trust changes.</p>
</li>
<li><p><strong>Number every flow that crosses a boundary,</strong> and get the data owner to decide each one.</p>
</li>
<li><p><strong>Tie updates to change,</strong> not just to the calendar.</p>
</li>
</ol>
<hr />
<h2>Where data flow mapping lives in the frameworks</h2>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/ec79f1ca-da2f-4b3c-bc33-95ade71858a6.png" alt="" style="display:block;margin:0 auto" />

<p>Every framework asks the same thing: know where the data goes, and who it goes to.</p>
<hr />
<h2>Maturity ladder</h2>
<table>
<thead>
<tr>
<th>Stage</th>
<th>20 people</th>
<th>200 people</th>
<th>2,000 people</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Diagram</strong></td>
<td>One page, drawn by hand or in a free tool, for customer data only</td>
<td>A Threat Dragon model per product, stored in Git</td>
<td>Models per system, linked to an architecture repository</td>
</tr>
<tr>
<td><strong>Discovery</strong></td>
<td>Ask the three people who touch customer data</td>
<td>Watch, read and ask, twice a year</td>
<td>Automated data discovery and network flow analysis</td>
</tr>
<tr>
<td><strong>Third parties</strong></td>
<td>A list of vendors that receive customer data</td>
<td>Vendors on the map with data types and countries</td>
<td>Subprocessor register tied to contracts and transfer assessments</td>
</tr>
<tr>
<td><strong>Change trigger</strong></td>
<td>"Tell security if you add a vendor"</td>
<td>Merge requests that add outbound URLs or prod roles need a map update</td>
<td>Architecture review gates and egress controls that block unapproved destinations</td>
</tr>
<tr>
<td><strong>Evidence</strong></td>
<td>A dated email approving the diagram</td>
<td>Approved map, flow register with owners, change history in Git</td>
<td>Continuous reports of actual flows compared against approved flows</td>
</tr>
</tbody></table>
<p>Be honest about the left column. At 20 people, a hand-drawn diagram with a date and an owner's "approved" email is a real control.</p>
<p><strong>Where Wayne sits:</strong> Wayne now has a 200-person map for customer data only. Discovery was a one-off effort by one person, and nothing technical blocks a new outbound flow yet. The rule tying map updates to merge requests is brand new and untested.</p>
<hr />
<h2>Cheatsheet</h2>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/347cfe1e-c788-4f47-ac76-4580dfab3362.png" alt="" style="display:block;margin:0 auto" />

<p>Data flow mapping, on one page.</p>
<hr />
<h2>The takeaway</h2>
<p>Your architecture diagram shows how things are built. A data flow map shows where the data goes. Find flows three ways: watch, read and ask. Each one misses what the others catch. Draw trust boundaries, and make the data owner decide every crossing. Dormant flows count, and so do spreadsheets in emails.</p>
<hr />
<blockquote>
<p>⚠️ This content is for educational purposes only. Wayne Industries is a fictional company. Nothing here is legal, audit, or compliance advice, validate against your own auditor and jurisdiction.</p>
</blockquote>
]]></content:encoded></item><item><title><![CDATA[Data Classification That People Actually Follow]]></title><description><![CDATA[@18xBan · GRC Series · Chapter 04


Zero labels, zero data owners, and two copies of customer data in places they shouldn't be.

Tuesday, 9:31 AM: "So what's actually in it?"
The closure of hackathon-]]></description><link>https://18xban.hashnode.dev/data-classification-that-works</link><guid isPermaLink="true">https://18xban.hashnode.dev/data-classification-that-works</guid><category><![CDATA[data classification]]></category><category><![CDATA[Data Protection]]></category><category><![CDATA[ISO 27001]]></category><category><![CDATA[#gdpr]]></category><category><![CDATA[grc]]></category><dc:creator><![CDATA[18xBan]]></dc:creator><pubDate>Sat, 19 Sep 2026 01:30:00 GMT</pubDate><content:encoded><![CDATA[<p><em>@18xBan · GRC Series · Chapter 04</em></p>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/cc6f085f-6fc3-4cad-862d-d460ece0f244.png" alt="" style="display:block;margin:0 auto" />

<p>Zero labels, zero data owners, and two copies of customer data in places they shouldn't be.</p>
<hr />
<h2>Tuesday, 9:31 AM: "So what's actually in it?"</h2>
<p>The closure of <code>hackathon-2023</code> is still on hold. Last week's inventory found an RDS snapshot in that account called <code>hack23-db-final</code>. Postgres, taken in November 2023. Nobody could say what was inside.</p>
<p>Priya pings you again.</p>
<blockquote>
<p><strong>Priya:</strong> Can I kill the hackathon account yet? It's costing us money. <strong>You:</strong> Not until we know what's in that snapshot. <strong>Priya:</strong> It's demo data. Probably. <strong>You:</strong> "Probably" is what we said about the staging bucket.</p>
</blockquote>
<p>She doesn't answer that one.</p>
<p>Because the staging bucket is the other loose end. The public access is blocked, but <code>prod-export-2024.sql.gz</code> is still sitting in <code>wayne-staging-data</code>. A production database export, in a staging bucket, that was public until a few weeks ago.</p>
<p>Then Dan forwards one more thing. Gotham Mutual's questionnaire, question 52:</p>
<blockquote>
<p><em>Describe your data classification scheme and how it is applied to customer data.</em></p>
</blockquote>
<p>Wayne doesn't have one. Not in the 40 policies on the shared drive, not in anyone's head.</p>
<p>So this week's job is simple to say and harder to do: decide what kinds of data Wayne has, what each kind is allowed to do, and then go look at those two files.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/db5b9646-8d34-4c95-a2f3-3eebc8b82b54.png" alt="" style="display:block;margin:0 auto" />

<p>"Probably demo data" isn't a classification.</p>
<hr />
<h2>What data classification actually is</h2>
<p><strong>Data classification</strong> means sorting information into a few levels based on how much damage it would do if it leaked, got changed or disappeared. Each level comes with <strong>handling rules</strong>: where the data may live, who may see it, how it's protected and when it's deleted.</p>
<p>A <strong>label</strong> is how you mark which level a piece of data belongs to. On a document that might be a header. In AWS it's usually a tag.</p>
<p>Think of the care label inside a shirt. The label doesn't wash anything. It just tells whoever picks up the shirt what they're allowed to do with it: cold water, no dryer, don't bleach. Data labels work the same way. The label is useless on its own. What matters is the rule attached to it, and whether anyone follows it.</p>
<p>That's where most classification programs die. They pick labels, write a policy, and never connect the labels to anything people or systems actually do.</p>
<p><strong>The unpopular truth:</strong> if everything is Confidential, nothing is. A scheme where 95% of files carry the top label tells people nothing, and they stop reading the label within a week.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/0ce9b64c-f215-4175-be2d-6f88058997cd.png" alt="" style="display:block;margin:0 auto" />

<p>When every file says Confidential, nobody reads the label.</p>
<hr />
<h2>Why it matters at Wayne right now</h2>
<p>Classification isn't paperwork for its own sake. It's the thing that turns "is this bad?" into a question with an answer.</p>
<p>Take the staging export. Without a scheme, the conversation goes:</p>
<ul>
<li><p><strong>Priya:</strong> it's just staging.</p>
</li>
<li><p><strong>Dan:</strong> but it came from production.</p>
</li>
<li><p><strong>You:</strong> does it have customer data?</p>
</li>
<li><p><strong>Everyone:</strong> no idea.</p>
</li>
</ul>
<p>With a scheme, the conversation is one line: <em>customer personal information is Restricted, and Restricted data never lives outside production.</em> The export breaks the rule, full stop. No debate about which environment "counts."</p>
<p>The law points the same way. PIPEDA, the Canadian privacy law that covers Wayne's customer data, says safeguards must match how sensitive the information is. More sensitive information gets more protection. You can't do that if you haven't decided what's sensitive.</p>
<hr />
<h3>Wayne before</h3>
<pre><code class="language-plaintext">Data classification scheme: none
Policy covering classification: none (0 of 40 shared-drive policies)
Labels applied to any data: none
Named data owners: none
Rule for production data outside production: none
</code></pre>
<h3>Wayne after (approved by Dan, Tuesday)</h3>
<table>
<thead>
<tr>
<th>Level</th>
<th>Means</th>
<th>Examples at Wayne</th>
<th>Where it may live</th>
<th>Who can access</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Public</strong></td>
<td>Meant for anyone</td>
<td>Marketing site, published docs</td>
<td>Anywhere</td>
<td>Anyone</td>
</tr>
<tr>
<td><strong>Internal</strong></td>
<td>Fine for staff, not for strangers</td>
<td>Wiki, runbooks, sprint notes</td>
<td>Wayne-managed systems</td>
<td>All staff</td>
</tr>
<tr>
<td><strong>Confidential</strong></td>
<td>Would hurt Wayne if it leaked</td>
<td>Source code, contracts, vendor questionnaires</td>
<td>Wayne-managed systems, access by team</td>
<td>Named teams</td>
</tr>
<tr>
<td><strong>Restricted</strong></td>
<td>Would hurt customers if it leaked</td>
<td>Customer personal info, production database, backups, exports and snapshots of either</td>
<td><code>wayne-prod</code> only</td>
<td>Named people, approved in writing</td>
</tr>
</tbody></table>
<p>Three rules sit under the table. They do most of the work:</p>
<ol>
<li><p><strong>A copy is the same class as the original.</strong> An export, snapshot or backup of Restricted data is Restricted. Moving it doesn't downgrade it.</p>
</li>
<li><p><strong>Label the system, not every file.</strong> Each system gets a default level. Everything inside it inherits that level unless the owner says otherwise.</p>
</li>
<li><p><strong>When in doubt, go one level up for now, and ask the owner.</strong> The owner decides within five working days.</p>
</li>
</ol>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/dbbb42c5-c16f-4721-be34-5a3701bc0717.png" alt="" style="display:block;margin:0 auto" />

<p>Four levels, each with a rule about where the data may live.</p>
<p><strong>The unpopular truth:</strong> most people will never label a document by hand. Don't build a scheme that depends on them doing it. Put the label on the system, and let the system carry it.</p>
<hr />
<h2>Field by field: what each column is for</h2>
<table>
<thead>
<tr>
<th>Field</th>
<th>What it holds</th>
<th>Why it's there</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Level</strong></td>
<td>The name of the class</td>
<td>Something short enough to fit in a tag</td>
</tr>
<tr>
<td><strong>Means</strong></td>
<td>The harm if it leaks, changes or disappears</td>
<td>Lets people classify new data without asking you</td>
</tr>
<tr>
<td><strong>Examples</strong></td>
<td>Real Wayne data types</td>
<td>Nobody reads definitions. Everybody reads examples.</td>
</tr>
<tr>
<td><strong>Where it may live</strong></td>
<td>Allowed environments and systems</td>
<td>The rule that caught the staging export</td>
</tr>
<tr>
<td><strong>Who can access</strong></td>
<td>Audience for each level</td>
<td>Feeds access reviews later</td>
</tr>
<tr>
<td><strong>Handling rules</strong> (in the full policy)</td>
<td>Encryption, sharing, printing, transfer</td>
<td>What people actually do differently per level</td>
</tr>
<tr>
<td><strong>Retention and disposal</strong> (in the full policy)</td>
<td>How long it's kept, how it's destroyed</td>
<td>PIPEDA expects data you no longer need to be destroyed or anonymized</td>
</tr>
<tr>
<td><strong>Data owner</strong> (in the inventory)</td>
<td>The named person who decides the level</td>
<td>Without an owner, "ask the owner" means "ask nobody"</td>
</tr>
</tbody></table>
<hr />
<h2>Step 1: find out what's in the snapshot</h2>
<p>You don't open a two-year-old snapshot in place. You restore it to a temporary, private database, look at the structure, and delete the copy when you're done.</p>
<p>First, check the snapshot was never shared outside the account:</p>
<pre><code class="language-bash">aws rds describe-db-snapshot-attributes \
  --db-snapshot-identifier hack23-db-final \
  --profile hackathon-2023
</code></pre>
<pre><code class="language-json">{
    "DBSnapshotAttributesResult": {
        "DBSnapshotIdentifier": "hack23-db-final",
        "DBSnapshotAttributes": [
            {
                "AttributeName": "restore",
                "AttributeValues": []
            }
        ]
    }
}
</code></pre>
<p><em>Sample output (illustrative). Wayne Industries is fictional.</em></p>
<p>An empty <code>restore</code> list means no other AWS account can restore it. If you ever see <code>"all"</code> in there, the snapshot is public, and that's a different, more urgent day.</p>
<p>Next, restore it to a private instance:</p>
<pre><code class="language-bash">aws rds restore-db-instance-from-db-snapshot \
  --db-instance-identifier hack23-inspect \
  --db-snapshot-identifier hack23-db-final \
  --db-instance-class db.t3.micro \
  --no-publicly-accessible \
  --profile hackathon-2023
</code></pre>
<p>Then look at column names, not rows. You want to know <em>what kind</em> of data is there without reading anyone's personal details:</p>
<pre><code class="language-bash">psql -h hack23-inspect.xxxxxxxx.us-east-1.rds.amazonaws.com -U postgres -d appdb -c "
SELECT table_name, string_agg(column_name, ', ') AS columns
FROM information_schema.columns
WHERE table_schema = 'public'
GROUP BY table_name ORDER BY table_name;"
</code></pre>
<pre><code class="language-plaintext">   table_name    |                        columns
-----------------+--------------------------------------------------------
 customers       | id, full_name, email, phone, billing_address, created_at
 demo_widgets    | id, label, colour, created_at
 invoices        | id, customer_id, amount, issued_at
 users           | id, email, password_hash, last_login
(4 rows)
</code></pre>
<p><em>Sample output (illustrative). Wayne Industries is fictional.</em></p>
<p><code>demo_widgets</code> is the demo. <code>customers</code> is not. Names, emails, phone numbers and billing addresses are customer personal information. Under the new scheme, that's <strong>Restricted</strong>, and it's sitting in a hackathon account.</p>
<p>So much for "probably."</p>
<hr />
<h2>Step 2: check the staging export the same way</h2>
<p>Same idea. Read the table definitions, not the data. You can stream the file straight from S3 and only keep the <code>CREATE TABLE</code> lines:</p>
<pre><code class="language-bash">aws s3 cp s3://wayne-staging-data/prod-export-2024.sql.gz - --profile wayne-staging \
  | gunzip | grep -E "^CREATE TABLE"
</code></pre>
<pre><code class="language-plaintext">CREATE TABLE public.customers (
CREATE TABLE public.invoices (
CREATE TABLE public.subscriptions (
CREATE TABLE public.support_tickets (
CREATE TABLE public.users (
</code></pre>
<p><em>Sample output (illustrative). Wayne Industries is fictional.</em></p>
<p>Run this from an admin host you control, not your personal laptop. You're handling Restricted data now, even if you only keep the table names.</p>
<p>Same answer: a full copy of production customer data. <strong>Restricted</strong>, in staging. Rule 1 says the copy doesn't get a discount for living somewhere else.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/d1f62d24-d4d2-476a-83dd-266ac2b2baf3.png" alt="" style="display:block;margin:0 auto" />

<p>A copy is the same class as the original, wherever it ends up.</p>
<hr />
<h2>Step 3: label the systems and the two files</h2>
<p>Rule 2 says label the system. So the production database and each AWS account get a default level as a tag. Then the two problem files get their own tags, so anyone who finds them later sees what they are.</p>
<pre><code class="language-bash">aws s3api put-object-tagging \
  --bucket wayne-staging-data \
  --key prod-export-2024.sql.gz \
  --tagging 'TagSet=[{Key=data-classification,Value=restricted},{Key=data-owner,Value=dan.okafor},{Key=decision-due,Value=see-register}]' \
  --profile wayne-staging

aws s3api get-object-tagging \
  --bucket wayne-staging-data \
  --key prod-export-2024.sql.gz \
  --profile wayne-staging
</code></pre>
<pre><code class="language-json">{
    "TagSet": [
        { "Key": "data-classification", "Value": "restricted" },
        { "Key": "data-owner", "Value": "dan.okafor" },
        { "Key": "decision-due", "Value": "see-register" }
    ]
}
</code></pre>
<p><em>Sample output (illustrative). Wayne Industries is fictional.</em></p>
<p>Before the tag, that same command returned an empty <code>TagSet</code>. That empty list is what "unclassified" looks like in AWS.</p>
<p>Tag names matter more than you'd think. Pick one key (<code>data-classification</code>) and four lowercase values, and write them into the policy. If one person tags <code>Restricted</code>, another <code>RESTRICTED</code> and a third <code>sensitive</code>, no automated check will ever work.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/dac1d115-4aa5-4b37-8049-deda245b3cc6.png" alt="" style="display:block;margin:0 auto" />

<p>People skip the dropdown. Systems don't.</p>
<hr />
<h2>Step 4: decide what happens to each copy</h2>
<p>A label tells you the rule. It doesn't make the decision. That's the data owner's job, and for customer data at Wayne that's Dan.</p>
<p>The two files get different answers, and the reason is worth spelling out.</p>
<p><strong>The snapshot</strong> was never public and never shared. Nobody needs it: the hackathon ended two years ago, and production has its own backups. Dan approves deletion in writing on Thursday. You delete the snapshot and the temporary <code>hack23-inspect</code> instance, and save the confirmation. That approval, plus the before-and-after listing, becomes <strong>Evidence #4</strong>.</p>
<p>Priya can finally close the account.</p>
<p><strong>The staging export</strong> is harder. It sat in a bucket that was public, and nobody had turned on access logging for that bucket. So nobody can prove who did or didn't download it. Whether that counts as a privacy breach Wayne must report is a question for Dan and legal counsel, not for a classification label.</p>
<p>So it isn't deleted yet. Deleting it now could destroy the only record of what was exposed. Instead:</p>
<ul>
<li><p>Access is limited to you and Dan through the bucket policy.</p>
</li>
<li><p>It's tagged Restricted, with Dan as owner.</p>
</li>
<li><p>Dan has the breach question in writing, with a date to decide by.</p>
</li>
<li><p>Once counsel closes the question, it's deleted and the deletion is recorded.</p>
</li>
</ul>
<p><strong>The unpopular truth:</strong> the best classification decision is often deletion. Data you don't have can't leak, can't be subpoenaed and doesn't need a label. But you delete on purpose, with a record, and never while someone still needs to answer "what was exposed?"</p>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/6a8d3a44-09db-494f-81b3-4eee01c3ad3f.png" alt="" style="display:block;margin:0 auto" />

<p>One approval, one deletion, one dated record.</p>
<hr />
<h2>Step 5: the data inventory, before and after</h2>
<p>Classification only works if it lands in your inventory. CIS Safeguard 3.2 asks for a data inventory based on your data management process, covering sensitive data at a minimum. Wayne's asset register from last week gets four new columns.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/d602c404-5bd9-44a5-8700-0dae62abdd8c.png" alt="" style="display:block;margin:0 auto" />

<p>Four new columns turn a list of things into a list of decisions.</p>
<hr />
<h2>What an auditor accepts vs rejects</h2>
<table>
<thead>
<tr>
<th>The auditor asks</th>
<th>Rejected</th>
<th>Accepted</th>
</tr>
</thead>
<tbody><tr>
<td>"Show me your classification scheme."</td>
<td>A policy on the shared drive with no approval and no date</td>
<td>A dated scheme approved by a named owner, with examples</td>
</tr>
<tr>
<td>"How is it applied?"</td>
<td>"Staff are trained to label documents."</td>
<td>System default levels, visible as tags, plus the inventory showing each data store's class</td>
</tr>
<tr>
<td>"Pick one: this file. What's its class?"</td>
<td>Someone guesses</td>
<td>The tag says Restricted and the inventory agrees</td>
</tr>
<tr>
<td>"Where is Restricted data allowed?"</td>
<td>"Somewhere secure"</td>
<td>"<code>wayne-prod</code> only," plus a check that shows where it actually is</td>
</tr>
<tr>
<td>"What happens to data you no longer need?"</td>
<td>"We keep everything, just in case."</td>
<td>Retention periods per level, and a dated deletion record</td>
</tr>
<tr>
<td>"When was the scheme last reviewed?"</td>
<td>Never</td>
<td>A review date, and the next one scheduled</td>
</tr>
</tbody></table>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/e55813d5-a0c5-40a6-b948-d48e51a87721.png" alt="" style="display:block;margin:0 auto" />

<p>The auditor won't read your scheme. They'll pick a file and ask what it is.</p>
<hr />
<h2>What you actually do on Monday</h2>
<ol>
<li><p><strong>Get the CIS Data Management Policy Template.</strong> Don't write your own from a blank page.</p>
</li>
<li><p><strong>Pick three or four levels.</strong> Write one sentence of meaning and three real examples for each.</p>
</li>
<li><p><strong>Write the copy rule.</strong> Exports, snapshots and backups keep the class of their source.</p>
</li>
<li><p><strong>Name an owner for your most sensitive data.</strong> One person, in writing.</p>
</li>
<li><p><strong>Label your systems, not your files.</strong> One tag key, fixed lowercase values.</p>
</li>
<li><p><strong>Look for production data outside production.</strong> Check staging buckets, old snapshots and dev databases. Read column names, not rows.</p>
</li>
<li><p><strong>Decide each stray copy on purpose:</strong> delete, move or hold. Keep the record.</p>
</li>
<li><p><strong>Put a review date on the scheme.</strong> CIS asks for at least once a year.</p>
</li>
</ol>
<hr />
<h2>Where classification lives in the frameworks</h2>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/059a1266-e058-498c-80fa-38c8f363b11e.png" alt="" style="display:block;margin:0 auto" />

<p>Every framework asks the same thing: know what's sensitive, and treat it that way.</p>
<hr />
<h2>Maturity ladder</h2>
<table>
<thead>
<tr>
<th>Stage</th>
<th>20 people</th>
<th>200 people</th>
<th>2,000 people</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Scheme</strong></td>
<td>Three levels on one page, in a shared doc</td>
<td>Four levels in an approved policy, reviewed yearly</td>
<td>Scheme tied to legal and regulatory registers, reviewed by a committee</td>
</tr>
<tr>
<td><strong>Labelling</strong></td>
<td>A column in the asset spreadsheet</td>
<td>Tags on cloud accounts, databases and buckets</td>
<td>Automatic sensitivity labels in email and documents, enforced by policy</td>
</tr>
<tr>
<td><strong>Discovery</strong></td>
<td>Someone checks staging by hand, twice a year</td>
<td>Scheduled scans of cloud storage for personal data</td>
<td>Data discovery tooling across cloud, SaaS and endpoints</td>
</tr>
<tr>
<td><strong>Enforcement</strong></td>
<td>"Please don't copy prod data to staging"</td>
<td>Guardrails that block untagged or Restricted data outside production</td>
<td>Data loss prevention on the network, endpoints and SaaS</td>
</tr>
<tr>
<td><strong>Evidence</strong></td>
<td>Dated emails from the owner</td>
<td>Inventory with class, owner and decision per data store</td>
<td>Dashboards of labelled data, exceptions and deletions</td>
</tr>
</tbody></table>
<p>Be honest about the left column. At 20 people, a spreadsheet column and an owner who answers emails is a real control.</p>
<p><strong>Where Wayne sits:</strong> at about 240 accounts, Wayne is in the 200-person column on paper, with the scheme approved and the first tags applied. Discovery is still manual and nothing blocks the next export yet. That's a 20-person program with a 200-person policy, and that's fine for week four.</p>
<hr />
<h2>Cheatsheet</h2>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/e42df704-a43e-47b5-b676-f1f2c762c35d.png" alt="" style="display:block;margin:0 auto" />

<p>Data classification, on one page.</p>
<hr />
<h2>The takeaway</h2>
<p>A classification scheme is only as good as the rule attached to each label. Label systems, not files, and make copies inherit their source's level. Read column names, not rows, when you go looking. Delete what you don't need, on purpose and on the record.</p>
<hr />
<blockquote>
<p>⚠️ This content is for educational purposes only. Wayne Industries is a fictional company. Nothing here is legal, audit, or compliance advice, validate against your own auditor and jurisdiction.</p>
</blockquote>
]]></content:encoded></item><item><title><![CDATA[Asset Inventory: From Unknown to Auditable]]></title><description><![CDATA[@18xBan · GRC Series · Chapter 03


Seven accounts, 203 computers, 171 spreadsheet rows, and nobody who could say what any of it was.

Monday, 10:07 AM: "Closing it today, cool?"
Last Friday, Maya app]]></description><link>https://18xban.hashnode.dev/asset-inventory-unknown-to-auditable</link><guid isPermaLink="true">https://18xban.hashnode.dev/asset-inventory-unknown-to-auditable</guid><category><![CDATA[asset management]]></category><category><![CDATA[cis-controls]]></category><category><![CDATA[iso27001]]></category><category><![CDATA[grc]]></category><category><![CDATA[cloud security]]></category><dc:creator><![CDATA[18xBan]]></dc:creator><pubDate>Thu, 17 Sep 2026 22:47:54 GMT</pubDate><content:encoded><![CDATA[<p><em>@18xBan · GRC Series · Chapter 03</em></p>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/2fa4f695-f9ac-4b40-bd97-6901d53160a1.png" alt="" style="display:block;margin:0 auto" />

<p>Seven accounts, 203 computers, 171 spreadsheet rows, and nobody who could say what any of it was.</p>
<hr />
<h2>Monday, 10:07 AM: "Closing it today, cool?"</h2>
<p>Last Friday, Maya approved Wayne's scope. All seven AWS accounts are in it. Two of them, <code>hackathon-2023</code> and <code>old-sandbox</code>, are marked "pending closure."</p>
<p>On Monday morning Priya sends you a message.</p>
<blockquote>
<p><strong>Priya:</strong> Closing hackathon-2023 today, cool? <strong>You:</strong> What's in it? <strong>Priya:</strong> The demo from the hackathon. Maybe a database? It was two years ago.</p>
</blockquote>
<p>"Maybe" is the problem.</p>
<p>If that account holds a copy of real customer data, closing it without looking isn't a clean-up. It's destroying records you may need to explain later. And if it holds nothing, you still want a dated note that says so, because Gotham Mutual and an ISO auditor will both ask what happened to it.</p>
<p>So you reply: <em>Hold off. Let's find out what's inside first.</em></p>
<p>That one question, "what's in it?", is the whole topic of this post.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/087d8a36-74d9-426a-b3f4-80e74686809c.png" alt="" style="display:block;margin:0 auto" />

<p>"Maybe" isn't an inventory.</p>
<hr />
<h2>What an asset inventory actually is</h2>
<p>An <strong>asset</strong> is anything that holds, processes or moves information you care about. Laptops, servers, cloud accounts, databases, software, and the data itself.</p>
<p>An <strong>asset inventory</strong> is the list of those things, with enough detail that someone else could find each one, and a named person who answers for it.</p>
<p>Think of a library. A pile of books isn't a library. A library knows what it has, where each book is, who has it checked out, and which ones are missing. The catalogue is what turns a pile into a library.</p>
<p>Most companies have a pile. Wayne's "catalogue" looked like this:</p>
<ul>
<li><p>A wiki page called <em>AWS accounts</em>, listing four of the seven.</p>
</li>
<li><p>A laptop spreadsheet, <code>laptops.xlsx</code>, with 171 rows. Last edited in 2023.</p>
</li>
<li><p>Active Directory, with 203 computer objects that nobody had compared to the spreadsheet.</p>
</li>
<li><p>No list of installed software at all.</p>
</li>
</ul>
<p>Three sources, three different answers, and no owner on any of them.</p>
<p><strong>The unpopular truth:</strong> your inventory is wrong right now. Every company's is. The question an auditor cares about is whether you <em>know how wrong</em> it is, and what you do when you find out.</p>
<hr />
<h2>Why it comes first</h2>
<p>Every other control starts with "for each asset."</p>
<p>Patch each server. Encrypt each laptop. Review access to each account. Back up each database. If an asset isn't on the list, none of those things happen to it. It just sits there, unpatched and unwatched.</p>
<p>That's exactly how Wayne ended up with a public bucket holding a production export. Nobody decided to leave it open. It simply wasn't on anyone's list, so no control ever reached it.</p>
<p>This is why CIS puts inventory at Control 1 and 2, and why the ISO 27001 auditor will ask for your inventory early.</p>
<hr />
<h2>Where unknown assets hide</h2>
<p>You don't build an inventory by asking people what they own. You build it by pulling from systems that can't forget, then comparing.</p>
<p>At Wayne, you pull from five places:</p>
<ol>
<li><p><strong>AWS Organizations</strong> for every account.</p>
</li>
<li><p><strong>Each account, every region,</strong> for what's running inside.</p>
</li>
<li><p><strong>Active Directory</strong> for every domain-joined computer.</p>
</li>
<li><p><strong>The laptop spreadsheet,</strong> for what the office <em>thinks</em> it bought.</p>
</li>
<li><p><strong>The finance card statements,</strong> for anything paid for that no system reports.</p>
</li>
</ol>
<p>No single source is complete. CIS says as much: there's rarely one source of truth, so you combine several and scan regularly.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/df6d6b07-1166-491b-baea-52a7b0ebd7eb.png" alt="" style="display:block;margin:0 auto" />

<p>Pull from systems that can't forget, then compare.</p>
<hr />
<h2>Step 1: what's inside the three accounts</h2>
<p>You already have the account list from last week. Now you tag each account with an owner, so the answer lives in AWS and not only in your head.</p>
<pre><code class="language-bash">aws organizations tag-resource \
  --resource-id 444455556666 \
  --tags Key=owner,Value=priya.nair@wayne-industries.example \
         Key=status,Value=pending-closure

aws organizations list-tags-for-resource --resource-id 444455556666 --output table
</code></pre>
<pre><code class="language-plaintext">------------------------------------------------------------------
|                      ListTagsForResource                       |
+----------------------------------------------------------------+
||                             Tags                             ||
|+-----------+--------------------------------------------------+|
||    Key    |                      Value                       ||
|+-----------+--------------------------------------------------+|
||  owner    |  priya.nair@wayne-industries.example             ||
||  status   |  pending-closure                                 ||
|+-----------+--------------------------------------------------+|
</code></pre>
<p><em>Sample output (illustrative). Wayne Industries is fictional.</em></p>
<p>Then you look inside. The trap here is regions. A hackathon team in a hurry launches wherever the console happened to be pointing. So you check every enabled region, not just the one you use.</p>
<pre><code class="language-bash">for r in $(aws ec2 describe-regions --profile hackathon-2023 \
            --query "Regions[].RegionName" --output text); do
  aws ec2 describe-instances --profile hackathon-2023 --region "$r" \
    --query "Reservations[].Instances[].[InstanceId,InstanceType,State.Name,LaunchTime]" \
    --output text | sed "s/^/$r  /"
done
</code></pre>
<pre><code class="language-plaintext">us-east-1  i-0a1b2c3d4e5f60718  t3.large  running  2023-10-14T18:22:05+00:00
us-east-1  i-0f9e8d7c6b5a40312  t3.large  running  2023-10-14T18:24:41+00:00
</code></pre>
<p><em>Sample output (illustrative). Wayne Industries is fictional.</em></p>
<p>Two instances, running since the hackathon, in a region nobody at Wayne normally uses. Then the database check:</p>
<pre><code class="language-bash">aws rds describe-db-snapshots --profile hackathon-2023 --region us-east-1 \
  --query "DBSnapshots[].[DBSnapshotIdentifier,SnapshotCreateTime,Engine]" \
  --output table
</code></pre>
<pre><code class="language-plaintext">------------------------------------------------------------------------
|                          DescribeDBSnapshots                         |
+------------------+------------------------------------+--------------+
|  hack23-db-final |  2023-11-02T21:40:17.512000+00:00  |  postgres    |
+------------------+------------------------------------+--------------+
</code></pre>
<p><em>Sample output (illustrative). Wayne Industries is fictional.</em></p>
<p>Priya thinks the snapshot holds fake demo data. "Thinks" is not something you can write in an inventory. So the record says: <strong>contents unverified, do not delete.</strong></p>
<p>The other two accounts turn up their own surprises:</p>
<ul>
<li><p><code>old-sandbox</code> <strong>(Dan):</strong> one stopped instance, and an IAM user with an access key created in 2021 that is still active. Dan's reaction is exactly what you'd expect.</p>
</li>
<li><p><code>marketing-test</code> <strong>(the web agency):</strong> an S3 bucket and a live landing page from last year's campaign. The agency runs it. Nobody at Wayne was listed as responsible for it.</p>
</li>
</ul>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/0ca0c735-737d-4448-ae78-35086deed357.png" alt="" style="display:block;margin:0 auto" />

<p>Three accounts, three surprises.</p>
<p><strong>The unpopular truth:</strong> tag-based inventory only finds things that were tidy enough to be tagged. AWS's own tagging API returns resources that are tagged or were tagged at some point. The resources you're most worried about are the ones nobody ever tagged. Query the services directly.</p>
<hr />
<h2>Step 2: owner vs custodian</h2>
<p>The <code>marketing-test</code> account raises a question that trips up a lot of teams: who "owns" something that an outside company runs?</p>
<p>Two roles help here:</p>
<ul>
<li><p><strong>Owner:</strong> the named person at your company who is accountable for the asset. They decide whether it should exist, who gets access, and when it goes away.</p>
</li>
<li><p><strong>Custodian:</strong> whoever operates it day to day. That can be another team, or another company.</p>
</li>
</ul>
<p>The agency is the custodian of <code>marketing-test</code>. But an outside company can't be your owner, because you can't hold them accountable inside your ISMS. So Dan, as CTO, becomes the owner, and the agency is recorded as custodian.</p>
<p>ISO 27001 A.5.9 asks for an inventory "including owners." It doesn't mean the person who touches it most. It means the person who answers for it.</p>
<p><strong>The unpopular truth:</strong> an owner called "IT" is not an owner. A team can't be asked why something happened. A person can.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/f5848d7a-51ec-4bbe-b288-0429ff3c6fd3.png" alt="" style="display:block;margin:0 auto" />

<p>"We run it." "And I answer for it."</p>
<hr />
<h2>Step 3: reconcile the laptops</h2>
<p>Now the dull part, which is where most of the real findings are.</p>
<p>You export every computer from Active Directory:</p>
<pre><code class="language-powershell">Get-ADComputer -Filter * -Properties OperatingSystem, LastLogonDate |
  Select-Object Name, OperatingSystem, LastLogonDate |
  Export-Csv .\ad-computers.csv -NoTypeInformation

$ad    = Import-Csv .\ad-computers.csv
$sheet = Import-Csv .\laptops.csv
Compare-Object -ReferenceObject $ad.Name -DifferenceObject $sheet.Hostname |
  Group-Object SideIndicator | Select-Object Name, Count
</code></pre>
<pre><code class="language-plaintext">Name Count
---- -----
&lt;=      45
=&gt;      13
</code></pre>
<p><em>Sample output (illustrative). Wayne Industries is fictional.</em></p>
<p><code>&lt;=</code> means "in Active Directory, not in the spreadsheet." <code>=&gt;</code> means the reverse. So 158 computers match, and 58 don't.</p>
<p>You work through the 58:</p>
<table>
<thead>
<tr>
<th>Difference</th>
<th>Count</th>
<th>What it turned out to be</th>
</tr>
</thead>
<tbody><tr>
<td>In AD, not in the sheet, no logon for 90+ days</td>
<td>31</td>
<td>Stale computer objects from old machines</td>
</tr>
<tr>
<td>In AD, not in the sheet, active</td>
<td>14</td>
<td>Laptops set up without being recorded</td>
</tr>
<tr>
<td>In the sheet, not in AD, found in the office</td>
<td>9</td>
<td>Spare laptops in a cupboard</td>
</tr>
<tr>
<td>In the sheet, not in AD, not found</td>
<td>4</td>
<td>Assigned to people who have left</td>
</tr>
</tbody></table>
<p>Those last four matter most. Four laptops went home with people who no longer work at Wayne, and nobody asked for them back. That's ISO 27001 A.5.11, return of assets, failing quietly.</p>
<p>Remember Dan's 2022 worry thread about contractors keeping VPN access after they leave? It turns out it's not only access that leaves with people. It's hardware too.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/9c1d614d-0800-4a6b-9963-7b83619fc24f.png" alt="" style="display:block;margin:0 auto" />

<p>158 match. 58 don't. Four have left the building.</p>
<p>This comparison is the actual control. A list on its own proves nothing. A list that gets compared against real systems, with the differences followed up, proves you're maintaining it. That's the "maintained" part of A.5.9.</p>
<hr />
<h2>Step 4: software, briefly</h2>
<p>CIS Control 2 asks for the same thing for software: know what's installed, and make sure only supported software is on the approved list.</p>
<p>Wayne has no software list, so you start small. You take one standard laptop and pull what's installed as a baseline:</p>
<pre><code class="language-bash">osqueryi --json "SELECT name, version, publisher FROM programs ORDER BY name LIMIT 3;"
</code></pre>
<pre><code class="language-json">[
  {"name":"7-Zip 23.01 (x64)","publisher":"Igor Pavlov","version":"23.01"},
  {"name":"Git","publisher":"The Git Development Community","version":"2.44.0"},
  {"name":"Google Chrome","publisher":"Google LLC","version":"124.0.6367.119"}
]
</code></pre>
<p><em>Sample output (illustrative). Wayne Industries is fictional.</em></p>
<p>That baseline becomes the first draft of the approved software list. It isn't finished, and that's fine. A short, honest list you keep updated beats a long one you don't.</p>
<p>Your 12 vendors go on a separate list, the service-provider inventory that CIS Safeguard 15.1 asks for. Don't mix them into this one.</p>
<hr />
<h3>Field by field</h3>
<table>
<thead>
<tr>
<th>Field</th>
<th>What goes in it</th>
<th>Why it matters</th>
</tr>
</thead>
<tbody><tr>
<td>Asset ID</td>
<td>A unique reference, e.g. an account ID or asset tag</td>
<td>Lets two people talk about the same thing</td>
</tr>
<tr>
<td>Name</td>
<td>Hostname, account name or system name</td>
<td>How people actually refer to it</td>
</tr>
<tr>
<td>Type</td>
<td>Laptop, server, cloud account, database, software</td>
<td>Decides which controls apply</td>
</tr>
<tr>
<td>Owner</td>
<td>A named person, not a team</td>
<td>Required by ISO A.5.9</td>
</tr>
<tr>
<td>Custodian</td>
<td>Who operates it, if not the owner</td>
<td>Makes outside operators visible</td>
</tr>
<tr>
<td>Department</td>
<td>The business area it serves</td>
<td>CIS 1.1 asks for it</td>
</tr>
<tr>
<td>Location</td>
<td>Office, home, AWS account and region</td>
<td>Where to look when something goes wrong</td>
</tr>
<tr>
<td>Hardware / network address</td>
<td>MAC address, static IP where relevant</td>
<td>CIS 1.1 asks for it on devices</td>
</tr>
<tr>
<td>Approved</td>
<td>Yes / no, and by whom</td>
<td>Separates known assets from strays</td>
</tr>
<tr>
<td>Data held</td>
<td>What information it stores, if known</td>
<td>Links the asset to the risk</td>
</tr>
<tr>
<td>Status</td>
<td>Active, pending closure, retired, missing</td>
<td>Shows the asset's lifecycle</td>
</tr>
<tr>
<td>Source</td>
<td>Where it was discovered</td>
<td>Proves the list came from systems, not memory</td>
</tr>
<tr>
<td>Last verified</td>
<td>Date of the last reconciliation</td>
<td>Proves the list is maintained</td>
</tr>
</tbody></table>
<h3>Wayne before</h3>
<blockquote>
<p><strong>Wiki: AWS accounts</strong> wayne-mgmt · wayne-prod · wayne-staging · wayne-dev</p>
<p><strong>laptops.xlsx</strong> (171 rows, last edited 2023) Columns: <em>Hostname, Model, Given to</em></p>
<p><strong>Owner column:</strong> none. <strong>Last checked:</strong> unknown.</p>
</blockquote>
<h3>Wayne after (extract)</h3>
<table>
<thead>
<tr>
<th>Asset ID</th>
<th>Name</th>
<th>Type</th>
<th>Owner</th>
<th>Custodian</th>
<th>Status</th>
<th>Data held</th>
<th>Last verified</th>
</tr>
</thead>
<tbody><tr>
<td>222233334444</td>
<td>wayne-prod</td>
<td>AWS account</td>
<td>Dan Okafor</td>
<td>Engineering</td>
<td>Active</td>
<td>Customer personal info</td>
<td>This Friday</td>
</tr>
<tr>
<td>444455556666</td>
<td>hackathon-2023</td>
<td>AWS account</td>
<td>Priya Nair</td>
<td>Priya Nair</td>
<td>Pending closure</td>
<td>Snapshot, contents unverified</td>
<td>This Friday</td>
</tr>
<tr>
<td>555566667777</td>
<td>marketing-test</td>
<td>AWS account</td>
<td>Dan Okafor</td>
<td>Web agency</td>
<td>Active</td>
<td>Campaign site files</td>
<td>This Friday</td>
</tr>
<tr>
<td>666677778888</td>
<td>old-sandbox</td>
<td>AWS account</td>
<td>Dan Okafor</td>
<td>Dan Okafor</td>
<td>Pending closure</td>
<td>None found</td>
<td>This Friday</td>
</tr>
<tr>
<td>WI-LT-0142</td>
<td>WI-LT-0142</td>
<td>Laptop</td>
<td>You (Head of Security)</td>
<td>Last held by a former contractor</td>
<td>Missing</td>
<td>Unknown</td>
<td>This Friday</td>
</tr>
</tbody></table>
<p>The full register has all seven accounts, 185 laptop rows (including 9 spares and 4 missing), and the start of a software list. Every row has a named owner, a source and a date.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/58afee50-1f15-4951-81a0-c16e2261baa4.png" alt="" style="display:block;margin:0 auto" />

<p>From three lists that disagree to one list with owners.</p>
<hr />
<h2>What an auditor accepts vs rejects</h2>
<p>Auditors test an inventory in two directions. They pick something real, such as a laptop on a desk or an account in the AWS console, and ask to see it on your list. Then they pick rows from your list and ask to see the real thing.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/ece36eed-1ae2-4b52-963f-0b53307126f4.png" alt="" style="display:block;margin:0 auto" />

<p>Floor to list, then list to floor.</p>
<table>
<thead>
<tr>
<th>Accepted</th>
<th>Rejected</th>
</tr>
</thead>
<tbody><tr>
<td>A register with a named owner on every row</td>
<td>An owner column that says "IT" or is blank</td>
</tr>
<tr>
<td>A dated record of comparing the list against real systems</td>
<td>A spreadsheet exported the day before the audit, with no history</td>
</tr>
<tr>
<td>Differences found, and what was done about each</td>
<td>A "complete" list with zero differences, ever</td>
</tr>
<tr>
<td>Missing assets recorded as missing, with follow-up</td>
<td>Missing assets quietly deleted from the list</td>
</tr>
<tr>
<td>Cloud accounts and their contents included</td>
<td>A laptop list presented as the whole inventory</td>
</tr>
</tbody></table>
<p>That third row surprises people. A reconciliation that never finds anything doesn't look clean to an auditor. It looks like nobody really checked.</p>
<hr />
<h2>Friday, 2:30 PM: Evidence #3</h2>
<p>By Friday you have one register in place of three lists, and a one-page reconciliation note:</p>
<ul>
<li><p>7 of 7 AWS accounts listed and owned.</p>
</li>
<li><p>158 laptops matched; 14 added; 31 stale AD objects queued for removal; 9 spares recorded.</p>
</li>
<li><p><strong>Open:</strong> 4 laptops missing, with former holders being contacted.</p>
</li>
<li><p><strong>Open:</strong> <code>hack23-db-final</code> contents unverified; closure of <code>hackathon-2023</code> on hold.</p>
</li>
<li><p><strong>Open:</strong> the 2021 access key in <code>old-sandbox</code>, which Dan disabled that afternoon.</p>
</li>
</ul>
<p>You send it to Dan. He replies at 2:30 PM: <em>Reviewed. Agree with the open items.</em></p>
<p>That note and reply get filed next to the ownership email and the scope approval. Evidence #3 is dated and reviewed, and it includes the problems it found.</p>
<p>Priya isn't thrilled her account is still open. But she agrees it's better than deleting something she can't describe.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/1e81c4b0-a76b-4955-adc1-650bb3b0b4b2.png" alt="" style="display:block;margin:0 auto" />

<p>A reconciliation that found problems is better evidence than one that found nothing.</p>
<hr />
<h2>What you actually do on Monday</h2>
<ol>
<li><p><strong>List your sources.</strong> Cloud organization, directory, MDM or endpoint tool, finance statements, any spreadsheet that already exists.</p>
</li>
<li><p><strong>Pull from each one</strong> and put them side by side. Don't clean anything yet.</p>
</li>
<li><p><strong>Compare and count the differences.</strong> The number doesn't need to be small. It needs to be written down.</p>
</li>
<li><p><strong>Put a person's name in the owner column</strong> for every row. Use "unknown" where you have to, and treat each "unknown" as an open item.</p>
</li>
<li><p><strong>Book the next comparison</strong> in your calendar. Monthly is a good start, and CIS asks for at least twice a year.</p>
</li>
</ol>
<hr />
<h2>Framework mapping</h2>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/9c02eae6-9820-4bb7-b648-e118fe7fdeaf.png" alt="" style="display:block;margin:0 auto" />

<p>Inventory shows up in every major framework.</p>
<hr />
<h2>Maturity ladder</h2>
<table>
<thead>
<tr>
<th>Stage</th>
<th>20 people</th>
<th>200 people</th>
<th>2,000 people</th>
</tr>
</thead>
<tbody><tr>
<td>Starting</td>
<td>A spreadsheet with owners, checked by hand</td>
<td>Several lists that disagree</td>
<td>Many tools, no agreed source</td>
</tr>
<tr>
<td>Working</td>
<td>Monthly check against the cloud console and directory</td>
<td>Scripted exports compared monthly, differences tracked</td>
<td>An asset management tool fed by several discovery sources</td>
</tr>
<tr>
<td>Mature</td>
<td>The same spreadsheet, always current, reviewed on a schedule</td>
<td>Automated discovery with alerts for new unowned assets</td>
<td>Continuous discovery tied to purchasing, onboarding and offboarding</td>
</tr>
</tbody></table>
<p>The 20-person version really is a spreadsheet, and that's fine. An auditor cares whether it's accurate and maintained, not whether you paid for a tool.</p>
<p><strong>Where Wayne sits:</strong> it moved from "Starting" to the first step of "Working" this week. It has one register and one completed comparison. The next comparison is booked but hasn't happened yet.</p>
<hr />
<h2>Cheatsheet</h2>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/97ce8393-97cb-4d36-8193-59f9a307f617.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h2>The takeaway</h2>
<p>You can't protect what isn't on the list. An inventory is a list plus an owner plus proof you keep checking it. The differences you find aren't failures. They're the evidence the control works.</p>
<hr />
<blockquote>
<p>⚠️ This content is for educational purposes only. Wayne Industries is a fictional company. Nothing here is legal, audit, or compliance advice, validate against your own auditor and jurisdiction.</p>
</blockquote>
]]></content:encoded></item><item><title><![CDATA[Scoping Your ISMS: The Decision That Sets Your Audit Cost]]></title><description><![CDATA[// @18xBan · GRC Series ·Chapter 02


One line on one page decides what an auditor will look at: the scope.

Monday, 8:52 AM
Dan Okafor leans into your doorway holding his phone like it's good news.
"]]></description><link>https://18xban.hashnode.dev/isms-scoping-audit-cost</link><guid isPermaLink="true">https://18xban.hashnode.dev/isms-scoping-audit-cost</guid><category><![CDATA[isms]]></category><category><![CDATA[compliance ]]></category><category><![CDATA[grc]]></category><category><![CDATA[audit]]></category><category><![CDATA[iso27001]]></category><dc:creator><![CDATA[18xBan]]></dc:creator><pubDate>Wed, 16 Sep 2026 22:03:35 GMT</pubDate><content:encoded><![CDATA[<p><em>// @18xBan · GRC Series ·Chapter 02</em></p>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/6dac5878-8aa2-460d-8f09-c120e0848f4d.png" alt="" style="display:block;margin:0 auto" />

<p>One line on one page decides what an auditor will look at: the scope.</p>
<hr />
<h2>Monday, 8:52 AM</h2>
<p>Dan Okafor leans into your doorway holding his phone like it's good news.</p>
<p>"Maya wants us certified. ISO 27001. I asked a certification body for a quote, and they sent back a form." He scrolls. "First box: <em>scope of certification</em>. Let's just put <code>wayne-prod</code>. Smaller scope, smaller bill, faster audit. Gotham Mutual only cares about production anyway."</p>
<p>Priya Nair, walking past with a coffee, adds: "Makes sense. Prod is where the customers are."</p>
<p>It <em>sounds</em> right. But you remember last week. The bucket you locked was called <code>wayne-staging-data</code>, and the file inside was <code>prod-export-2024.sql.gz</code>: a full production database export, sitting in staging, open to the internet.</p>
<p>Customer data doesn't stay where the architecture diagram says it lives.</p>
<p>"Give me until Friday," you tell Dan. "Before we write a scope, I want to know what we actually own."</p>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/2fad4d66-dc12-404b-8f08-6369e8855d30.png" alt="" style="display:block;margin:0 auto" />

<p>Dan's plan: Put one AWS account on the form and call it done.</p>
<hr />
<h2>What a scope actually is</h2>
<p>An <strong>ISMS</strong> (Information Security Management System) is the whole set of people, rules, processes and controls you use to run security. It's not a tool. It's the way the company works.</p>
<p>The <strong>scope</strong> is the boundary of that system. It says: <em>these</em> teams, <em>these</em> locations, <em>these</em> systems and <em>this</em> information are covered. Everything else isn't.</p>
<p>Think of it as a fence around a yard. Your controls are the house rules inside the fence. The auditor walks the fence line and checks that the rules are followed inside it. If you only fence the front lawn, the auditor never looks at the back gate. The dog still gets out the back gate. And your certificate still says "fenced."</p>
<p>ISO/IEC 27001:2022 Clause 4.3 is the requirement. In plain words, it asks you to:</p>
<ul>
<li><p>Decide the boundaries and applicability of your ISMS;</p>
</li>
<li><p>Base that decision on the issues you found in Clause 4.1 (your context), the requirements you found in Clause 4.2 (what interested parties need), and the interfaces and dependencies between your work and work done by other organizations;</p>
</li>
<li><p>Keep the scope as documented information, meaning written down where people can find it.</p>
</li>
</ul>
<p>It's one of the few documents the standard explicitly demands.</p>
<p><strong>The unpopular truth:</strong> the scope line is the only part of your ISO certificate most customers will ever read. Gotham Mutual's vendor-risk team won't read your policies. They'll read the one sentence that says what the certificate covers, and check whether their data is inside it.</p>
<hr />
<h2>First, survey the land</h2>
<p>You can't draw a fence around land you haven't surveyed. Wayne's documentation lists four AWS accounts: <code>wayne-mgmt</code>, <code>wayne-prod</code>, <code>wayne-staging</code> and <code>wayne-dev</code>. Documentation is what people <em>remember</em>. AWS Organizations is what <em>exists</em>.</p>
<p>From the management account, you run:</p>
<pre><code class="language-bash">aws organizations list-accounts \
  --query 'Accounts[].[Name,Id,State,JoinedMethod]' \
  --output table
</code></pre>
<p><em>Sample output (illustrative). Wayne Industries is fictional.</em></p>
<pre><code class="language-text">----------------------------------------------------------
|                      ListAccounts                      |
+-----------------+---------------+---------+------------+
|  wayne-mgmt     |  104829365117 |  ACTIVE |  CREATED   |
|  wayne-prod     |  238571940652 |  ACTIVE |  CREATED   |
|  wayne-staging  |  349162058734 |  ACTIVE |  CREATED   |
|  wayne-dev      |  457203816495 |  ACTIVE |  CREATED   |
|  hackathon-2023 |  562918374026 |  ACTIVE |  CREATED   |
|  marketing-test |  671034928157 |  ACTIVE |  CREATED   |
|  old-sandbox    |  780145263398 |  ACTIVE |  CREATED   |
+-----------------+---------------+---------+------------+
</code></pre>
<p>Seven accounts. Three that nobody wrote down.</p>
<p>A practical note: AWS retired the old <code>Status</code> field on Organizations account APIs on September 9, 2026. Use <code>State</code>, as above. If you have an older script filtering on <code>Status</code>, it may now quietly return nothing, which is the worst kind of inventory result: it looks clean.</p>
<p>To find out who owns each mystery account, check the root email address on each one:</p>
<pre><code class="language-bash">aws organizations describe-account --account-id 562918374026 \
  --query 'Account.[Name,Email,JoinedTimestamp]' --output text
</code></pre>
<p><em>Sample output (illustrative). Wayne Industries is fictional.</em></p>
<pre><code class="language-text">hackathon-2023  priya.nair@wayne-industries.example  2023-06-09T14:22:51.402000+00:00
</code></pre>
<p>You repeat that for the other two and walk the list around the office.</p>
<ul>
<li><p><code>hackathon-2023</code>: Priya's. "I thought that got shut down after the hackathon."</p>
</li>
<li><p><code>old-sandbox</code>: Dan's. "That's… from a while ago. I'll look."</p>
</li>
<li><p><code>marketing-test</code>: created for the outside web agency that built the marketing site. Nobody at Wayne logs into it.</p>
</li>
</ul>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/6e29b550-f275-4d6f-ba45-90e69d1fe057.png" alt="" style="display:block;margin:0 auto" />

<p>Four accounts on paper. Seven in AWS.</p>
<p><strong>The unpopular truth:</strong> most scope mistakes aren't choices. They're things nobody knew existed. A scope written from memory is a scope with holes.</p>
<hr />
<h2>The three inputs Clause 4.3 wants</h2>
<p>Before you write a word of scope, collect the three inputs. Here's Wayne's short version.</p>
<p><strong>1. Issues (Clause 4.1).</strong> What's going on inside and around Wayne that affects security?</p>
<ul>
<li><p>Cloud-only SaaS company in Vancouver, British Columbia. No data centre of its own.</p>
</li>
<li><p>One security person.</p>
</li>
<li><p>Customer data has already ended up in the wrong account once.</p>
</li>
</ul>
<p><strong>2. Interested parties and their requirements (Clause 4.2).</strong> Who cares, and what do they need?</p>
<ul>
<li><p>Gotham Mutual: a $1.8M deal, a vendor questionnaire, a request for assurance.</p>
</li>
<li><p>Existing customers: their personal information protected.</p>
</li>
<li><p>PIPEDA, Canada's federal private-sector privacy law: Wayne stays accountable for personal information it holds.</p>
</li>
<li><p>Maya and the board: a certificate that actually helps sell.</p>
</li>
</ul>
<p><strong>3. Interfaces and dependencies (Clause 4.3 c).</strong> Where does Wayne's work touch someone else's?</p>
<ul>
<li><p>AWS runs the infrastructure; Wayne runs the configuration and the data.</p>
</li>
<li><p>The outside web agency works inside <code>marketing-test</code>.</p>
</li>
<li><p>Twelve vendors, none security-reviewed yet.</p>
</li>
</ul>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/7497e655-9ede-4475-8cff-0d7b21e3295d.png" alt="" style="display:block;margin:0 auto" />

<p>Three inputs go in, one scope statement comes out.</p>
<hr />
<h2>Why scope sets your audit cost (and why "just prod" doesn't save much)</h2>
<p>Dan's instinct isn't wrong: scope does drive audit cost. But <em>how</em> it drives cost is the part most people miss.</p>
<p>Certification bodies calculate ISO 27001 audit time using ISO/IEC 27006-1:2024, Annex C. The starting point is the <strong>number of persons doing work under the organization's control</strong> within the scope, and the 2024 version makes clear this includes people who aren't employees, such as contractors. The certification body then adjusts that number up or down for complexity: how many systems, how much outsourcing, how much is already in place.</p>
<p>So think about what cutting <code>wayne-staging</code> from scope really does at Wayne. The same engineers deploy to prod and staging. The same people, the same laptops, the same GitLab, the same identity system. Removing the staging <em>account</em> removes almost none of the <em>people</em>. What it does add is a new boundary you now have to explain: how production data is kept out of an account you have declared out of scope.</p>
<p>Where scope genuinely saves money is when a whole part of the business, with its own people and systems, has nothing to do with the service you're certifying.</p>
<p>And the audit fee is only part of the bill. Every system inside the fence needs controls that operate and evidence that proves it. That's the bigger cost, and it's paid in your team's time.</p>
<p>Every certification body quotes differently. Ask yours how they count people and what adjusts the number.</p>
<p><strong>The unpopular truth:</strong> a narrow scope mostly shrinks what you're forced to look at, not what you pay. The risk outside the fence doesn't get smaller because the auditor stopped looking.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/ec5024ad-c80f-4c1d-97eb-17aa24d0788c.png" alt="" style="display:block;margin:0 auto" />

<p>Audit time starts with people in scope, then adjusts for complexity.</p>
<hr />
<h3>Field by field</h3>
<table>
<thead>
<tr>
<th>Field</th>
<th>What goes in it</th>
<th>Why it matters</th>
</tr>
</thead>
<tbody><tr>
<td>Scope statement</td>
<td>One or two sentences naming the service or business covered</td>
<td>This is the line printed on the certificate</td>
</tr>
<tr>
<td>Organizational units</td>
<td>Teams and roles inside the boundary</td>
<td>Drives the headcount the auditor counts</td>
</tr>
<tr>
<td>Locations</td>
<td>Offices and sites, or "remote"</td>
<td>Tells the auditor where work happens</td>
</tr>
<tr>
<td>Services and processes</td>
<td>What the organization delivers and how</td>
<td>Links scope to the business, not just tech</td>
</tr>
<tr>
<td>Technology</td>
<td>Accounts, platforms, key systems</td>
<td>Where the controls will be tested</td>
</tr>
<tr>
<td>Information</td>
<td>What data is inside, especially personal data</td>
<td>What the fence is really protecting</td>
</tr>
<tr>
<td>Interfaces and dependencies</td>
<td>Outside parties and what they do</td>
<td>Clause 4.3 c, stated plainly</td>
</tr>
<tr>
<td>Exclusions and justification</td>
<td>What's left out, and why that's safe</td>
<td>The first thing an auditor pokes at</td>
</tr>
<tr>
<td>Inputs considered</td>
<td>Links to the 4.1 issues and 4.2 requirements</td>
<td>Shows the scope wasn't picked at random</td>
</tr>
<tr>
<td>Owner, approver, date, review</td>
<td>Who wrote it, who signed it, when it's next checked</td>
<td>Makes it evidence, not just a document</td>
</tr>
</tbody></table>
<h3>Wayne before</h3>
<blockquote>
<p><strong>Scope:</strong> Wayne production platform (<code>wayne-prod</code> AWS account). <strong>Exclusions:</strong> — <strong>Approved by:</strong> — <strong>Date:</strong> —</p>
</blockquote>
<p>Four problems in four lines. No people, no data, no interfaces, no reason for leaving the other six accounts out, and nobody signed it.</p>
<h3>Wayne after</h3>
<blockquote>
<p><strong>Scope statement:</strong> The information security management system of Wayne Industries covering the development, operation and support of the Wayne cloud SaaS platform, and the customer information processed by it.</p>
<p><strong>Organizational units:</strong> Engineering, operations, security and customer support, plus leadership in its governance role. Contractors with access to in-scope systems are included.</p>
<p><strong>Locations:</strong> Vancouver, BC office, and staff working remotely.</p>
<p><strong>Services and processes:</strong> Software development, deployment, platform operations, customer support and access management for the SaaS platform.</p>
<p><strong>Technology:</strong> All seven AWS accounts in the Wayne AWS Organization (<code>wayne-mgmt</code>, <code>wayne-prod</code>, <code>wayne-staging</code>, <code>wayne-dev</code>, <code>hackathon-2023</code>, <code>marketing-test</code>, <code>old-sandbox</code>); Active Directory; self-hosted GitLab; company-managed laptops.</p>
<p><strong>Information:</strong> Customer personal information and account data, source code, credentials and secrets, backups and logs.</p>
<p><strong>Interfaces and dependencies:</strong> AWS (infrastructure; Wayne manages configuration and data); outside web agency (works in <code>marketing-test</code>); twelve vendors (to be reviewed).</p>
<p><strong>Exclusions:</strong> None at this stage.</p>
<p><strong>Accounts pending closure:</strong> <code>hackathon-2023</code> (owner: Priya Nair) and <code>old-sandbox</code> (owner: Dan Okafor) stay in scope until closure is complete.</p>
<p><strong>Owner:</strong> Head of Security. <strong>Approved by:</strong> Maya Lindqvist, CEO. <strong>Date:</strong> Friday. <strong>Next review:</strong> in 12 months, or sooner on a significant change.</p>
</blockquote>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/f509f675-f6af-4453-8ba6-bd33404aa0f6.png" alt="" style="display:block;margin:0 auto" />

<p>Same form, two answers. Only one survives the first audit question.</p>
<p>Notice what happened to the three mystery accounts. The instinct is to leave them out because they're "not real." That's backwards. An account nobody watches is exactly where a leak starts. So they go <em>in</em>, with named owners, until they're closed. Once an AWS account is closed, it doesn't vanish on the spot; it sits in a closed state for a reinstatement window first. The scope says "until closure is complete" for that reason.</p>
<p>Also notice "Exclusions: None." A first honest scope is often wider than you'd like. That's fine. You can narrow it later, with a reason you can defend.</p>
<hr />
<h2>What an auditor accepts vs rejects</h2>
<table>
<thead>
<tr>
<th>Accepts</th>
<th>Rejects</th>
</tr>
</thead>
<tbody><tr>
<td>A scope that names the service, people, locations, systems and information</td>
<td>A scope that's just an account name or a product name</td>
</tr>
<tr>
<td>Exclusions with a reason and proof nothing in scope depends on them</td>
<td>"Out of scope" with no reason given</td>
</tr>
<tr>
<td>An inventory that matches reality (e.g. the Organizations account list)</td>
<td>A scope written from memory that misses live systems</td>
</tr>
<tr>
<td>Interfaces listed with who does what</td>
<td>Outsourced work that's simply never mentioned</td>
</tr>
<tr>
<td>A dated approval from leadership</td>
<td>An unsigned document in a shared drive</td>
</tr>
<tr>
<td>A review date and a trigger for change</td>
<td>A scope nobody has looked at since it was written</td>
</tr>
</tbody></table>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/672df850-94ee-44bd-92cb-5b200eb1f4ef.png" alt="" style="display:block;margin:0 auto" />

<p>The auditor's favourite question: "And what's in <em>that</em> account?"</p>
<hr />
<h2>Interfaces: where your fence touches someone else's</h2>
<p>The word "interface" scares people. It just means: <em>a place where another organization does work that affects your security.</em></p>
<p>For Wayne, the clearest one is the web agency. They have access to <code>marketing-test</code>. Wayne can't control how the agency runs its own office, but Wayne <em>can</em> control what the agency is allowed to reach, and what's written in the agreement.</p>
<p>So the scope doesn't pretend the agency is inside Wayne. It says the agency is an interface, and that Wayne manages the relationship. You can't outsource accountability. PIPEDA says the same thing: an organization stays responsible for personal information it transfers to a third party for processing (Schedule 1, Principle 4.1.3).</p>
<p>AWS is the same story at bigger scale. AWS secures the infrastructure. Wayne secures what it builds on top: configurations, identities, data. The public bucket last week was Wayne's side of that line, not Amazon's.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/cfc09884-c889-4b99-9c76-83b398ce7a46.png" alt="" style="display:block;margin:0 auto" />

<p>"We just built the website." Then why do you have keys to an AWS account?</p>
<hr />
<h2>Friday, 3:15 PM: Evidence #2</h2>
<p>You send Maya and Dan the scope document with a one-line summary: <em>all seven accounts in, two being closed, nothing excluded yet.</em></p>
<p>Dan reads it twice. "Wider than I wanted."</p>
<p>"It's the one that's true," you say. "The narrow one would have lasted until the auditor asked about staging."</p>
<p>Maya replies at 3:15 PM: <em>Approved.</em></p>
<p>That reply, with the document attached, gets filed next to last week's ownership email. Evidence #2: dated, named, approved.</p>
<p>Then you send Gotham Mutual the scope statement, the one-sentence version. It's not a certificate. But it's an honest answer to "what would your certificate cover?", and that's a better answer than a promise.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/ffce24a4-1b94-454d-98a1-dba96f6114d5.png" alt="" style="display:block;margin:0 auto" />

<p>One document, one approval, one date.</p>
<hr />
<h2>What you actually do on Monday</h2>
<ol>
<li><p><strong>Pull your real inventory.</strong> Cloud accounts, subscriptions, projects, tenants. From the platform, not from the wiki.</p>
</li>
<li><p><strong>Put a name next to every account you didn't know about.</strong> If nobody claims it, that's your first risk.</p>
</li>
<li><p><strong>Write your three inputs on one page:</strong> issues, interested parties and their requirements, interfaces.</p>
</li>
<li><p><strong>Grab a published scope template</strong> and fill in every field, including "exclusions and justification."</p>
</li>
<li><p><strong>Get it approved by someone with authority,</strong> in writing, with a date. Put a review date on it.</p>
</li>
</ol>
<hr />
<h2>Framework mapping</h2>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/1196fcaa-9542-425e-a4b9-5206f5019a45.png" alt="" style="display:block;margin:0 auto" />

<p>Every framework asks the same first question: what exactly are we talking about?</p>
<hr />
<h2>Maturity ladder</h2>
<table>
<thead>
<tr>
<th>Stage</th>
<th>20 people</th>
<th>200 people</th>
<th>2,000 people</th>
</tr>
</thead>
<tbody><tr>
<td>Inventory</td>
<td>A spreadsheet from one cloud console</td>
<td>Scripted account export, checked quarterly</td>
<td>Automated discovery feeding a CMDB</td>
</tr>
<tr>
<td>Scope document</td>
<td>One page, founder approves</td>
<td>Template-based, CEO approves, annual review</td>
<td>Per-business-unit scopes, formal change control</td>
</tr>
<tr>
<td>Interfaces</td>
<td>A short vendor list</td>
<td>Vendor list with owners and contracts</td>
<td>Supplier register linked to risk and contracts</td>
</tr>
<tr>
<td>Exclusions</td>
<td>Usually none</td>
<td>A few, each justified</td>
<td>Many, each justified and tested</td>
</tr>
<tr>
<td>Change triggers</td>
<td>"When we remember"</td>
<td>New account or product triggers review</td>
<td>Account creation blocked until scope is updated</td>
</tr>
</tbody></table>
<p>Honestly, the 20-person version is a spreadsheet and an email. That's fine, as long as the spreadsheet is true.</p>
<p><strong>Where Wayne sits:</strong> somewhere between the first two columns. The scope document now looks like the 200-person version. The inventory behind it is still one command run by hand.</p>
<hr />
<h2>Cheatsheet</h2>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/6d999629-2811-40ce-bcab-02adc81d6568.png" alt="" style="display:block;margin:0 auto" />

<p>Scoping your ISMS on one page.</p>
<hr />
<h2>The takeaway</h2>
<p>The scope is a fence, and the auditor only walks the fence you draw. Draw it from what exists, not from what you remember. A narrow scope rarely saves much. It mostly hides what you'd rather not find. Get it signed, dated and reviewed, and it becomes evidence.</p>
<hr />
<blockquote>
<p>⚠️ This content is for educational purposes only. Wayne Industries is a fictional company. Nothing here is legal, audit, or compliance advice, validate against your own auditor and jurisdiction.</p>
<p><em>Illustrations in this post were created with AI assistance. Wayne Industries is fictional.</em></p>
</blockquote>
]]></content:encoded></item><item><title><![CDATA[What GRC Actually Is (And What It Isn't)]]></title><description><![CDATA[Three letters, one messy SaaS company, and the email that starts everything
@18xBan · GRC Series · Chapter 01


⚠️ This content is for educational purposes only. Wayne Industries is a fictional compan]]></description><link>https://18xban.hashnode.dev/what-grc-actually-is</link><guid isPermaLink="true">https://18xban.hashnode.dev/what-grc-actually-is</guid><category><![CDATA[grc]]></category><category><![CDATA[cybersecurity]]></category><category><![CDATA[compliance ]]></category><category><![CDATA[Governance]]></category><category><![CDATA[risk management]]></category><category><![CDATA[18xBan]]></category><dc:creator><![CDATA[18xBan]]></dc:creator><pubDate>Tue, 15 Sep 2026 22:07:45 GMT</pubDate><content:encoded><![CDATA[<h3>Three letters, one messy SaaS company, and the email that starts everything</h3>
<p><em>@18xBan · GRC Series · Chapter 01</em></p>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/06cbbf48-5f6a-4d05-b3cd-715ea1a7c035.png" alt="" style="display:block;margin:0 auto" />

<p>⚠️ This content is for educational purposes only. Wayne Industries is a fictional company. Nothing here is legal, audit, or compliance advice, validate against your own auditor and jurisdiction.</p>
<hr />
<h2>Monday, 9:14 AM</h2>
<p>You are the only security person at <strong>Wayne Industries</strong>, a cloud SaaS company in Vancouver, British Columbia. There are about 240 accounts in Active Directory, and exactly one of them belongs to someone whose job title contains the word "security." That's you.</p>
<p>Dan, the CTO, forwards you an email. The subject line reads <em>FW: FW: Security requirements : Gotham Mutual.</em> His note on top is one line long:</p>
<blockquote>
<p>"Big deal. They need this by Friday. Can you do the GRC thing?"</p>
</blockquote>
<p>Attached are a 214-question vendor security questionnaire, a request for "Your most recent SOC 2 Type II Report," and a sentence asking Wayne Industries to confirm it has <em>"a documented risk management program approved by senior leadership."</em></p>
<p>Here's where Wayne Industries actually stands:</p>
<ul>
<li><p>No SOC 2 report.</p>
</li>
<li><p>The "risk management program" is an email thread from 2022 titled <em>stuff that worries me</em>.</p>
</li>
<li><p>Senior leadership has never approved anything security-related, because nobody ever asked them to.</p>
</li>
</ul>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/9c526d80-c6b2-4306-a87c-9864546cee33.png" alt="" style="display:block;margin:0 auto" />

<p>For most people, "the GRC thing" is where their understanding of GRC starts and stops: a pile of paperwork that gets filled out so a deal can close. By the end of this post you'll see why that picture is wrong, and why believing it gets expensive.</p>
<hr />
<h2>Three letters that get said as one word</h2>
<p><strong>G, R, C.</strong> Governance, Risk, Compliance. People say it as a single word, and that habit causes half the confusion. The three parts do different jobs:</p>
<ul>
<li><p><strong>Governance</strong> is who gets to decide, what they decide, and how anyone can tell a decision was made.</p>
</li>
<li><p><strong>Risk</strong> is working out what could hurt you, how badly, and what you'll do about it with the budget you really have.</p>
</li>
<li><p><strong>Compliance</strong> is proving to someone outside your company that you do what you say you do.</p>
</li>
</ul>
<p>If that still feels abstract, picture a restaurant kitchen.</p>
<ul>
<li><p><strong>Governance</strong> is the head chef. The chef sets the menu, decides who's allowed near the deep fryer, and decides what happens when a plate goes out wrong.</p>
</li>
<li><p><strong>Risk</strong> is knowing the fryer can start a grease fire. You decide that justifies a suppression system this month, while the wobbly shelf can wait until next month.</p>
</li>
<li><p><strong>Compliance</strong> is the health inspector who walks in with a clipboard. You hand over the fridge temperature log you've filled in twice a day since March.</p>
</li>
</ul>
<p>The inspector doesn't care that you <em>feel</em> the fridge is cold. They want the log. That idea comes back in nearly every post in this series.</p>
<hr />
<h2>Pin Wayne's mess to the wall</h2>
<p>Before we define anything else, let's look at the evidence. This is what a first walk through Wayne turns up:</p>
<table>
<thead>
<tr>
<th>#</th>
<th>What you found</th>
<th>Where you found it</th>
</tr>
</thead>
<tbody><tr>
<td>1</td>
<td>Three AWS accounts that aren't written down anywhere</td>
<td>AWS Organizations</td>
</tr>
<tr>
<td>2</td>
<td>Three Domain Admin accounts, and nobody can say who approved two of them</td>
<td>Active Directory</td>
</tr>
<tr>
<td>3</td>
<td>MFA enabled on roughly 30% of accounts</td>
<td>Identity provider</td>
</tr>
<tr>
<td>4</td>
<td>One S3 bucket with public access switched on</td>
<td>AWS S3</td>
</tr>
<tr>
<td>5</td>
<td>Production deploys with no approval required</td>
<td>GitLab</td>
</tr>
<tr>
<td>6</td>
<td>Backups that run every night and have never been restored</td>
<td>Backup console</td>
</tr>
<tr>
<td>7</td>
<td>CloudTrail logging everything, with nobody reading it</td>
<td>AWS CloudTrail</td>
</tr>
<tr>
<td>8</td>
<td>12 vendors, none of them security-reviewed</td>
<td>Finance's spreadsheet</td>
</tr>
<tr>
<td>9</td>
<td>40 policies on a shared drive, last opened in 2021</td>
<td>File share</td>
</tr>
<tr>
<td>10</td>
<td>A "risk register" that is really an email thread</td>
<td>The CTO's inbox</td>
</tr>
</tbody></table>
<p>Here's what three of those exhibits look like when you ask the systems directly. You can run the same commands against your own environment.</p>
<p><strong>Exhibit 1: the accounts nobody wrote down</strong></p>
<pre><code class="language-javascript">$ aws organizations list-accounts --query 'Accounts[].[Name,Email,Status]' --output table
---------------------------------------------------------------------
|                           ListAccounts                            |
+------------------+---------------------------------------+--------+
|  wayne-mgmt      |  aws-root@wayne-industries.example    | ACTIVE |
|  wayne-prod      |  aws-prod@wayne-industries.example    | ACTIVE |
|  wayne-staging   |  aws-staging@wayne-industries.example | ACTIVE |
|  wayne-dev       |  aws-dev@wayne-industries.example     | ACTIVE |
|  hackathon-2023  |  priya.nair@wayne-industries.example  | ACTIVE |
|  marketing-test  |  web-agency@contractor.example        | ACTIVE |
|  old-sandbox     |  dan.okafor+aws@wayne-industries.example | ACTIVE |
+------------------+---------------------------------------+--------+
# The last three aren't in any inventory. One is registered to an outside agency.
</code></pre>
<p><em>Sample output (illustrative). Wayne Industries is fictional.</em></p>
<p><strong>Exhibit 2: who holds the keys to the domain</strong></p>
<pre><code class="language-powershell">PS&gt; Get-ADGroupMember -Identity "Domain Admins" | Select-Object Name, SamAccountName

Name            SamAccountName
----            --------------
Administrator   Administrator
Dan Okafor      adm.dan
Deploy Service  svc-deploy
# Administrator is the built-in account. Who approved adm.dan and svc-deploy?
# No ticket, no email, no answer.
</code></pre>
<p><em>Sample output (illustrative). Wayne Industries is fictional.</em></p>
<p><strong>Exhibit 4: the bucket with the door open</strong></p>
<pre><code class="language-bash">$ aws s3api get-public-access-block --bucket wayne-staging-data
{
    "PublicAccessBlockConfiguration": {
        "BlockPublicAcls": false,
        "IgnorePublicAcls": false,
        "BlockPublicPolicy": false,
        "RestrictPublicBuckets": false
    }
}
# Every public-access guard is switched off.
</code></pre>
<p><em>Sample output (illustrative). Wayne Industries is fictional.</em></p>
<p><strong>Here's a game.</strong> Label each row <strong>G</strong>, <strong>R</strong>, or <strong>C</strong> before you read on.</p>
<p>Done? Every row is all three. Take the public S3 bucket:</p>
<ul>
<li><p><strong>As a risk:</strong> customer data could leak, and it probably will. Internet scanners look for public buckets all day.</p>
</li>
<li><p><strong>As a governance problem:</strong> nobody decided who is allowed to make a bucket public, so in practice anyone can.</p>
</li>
<li><p><strong>As a compliance problem:</strong> Gotham Mutual's question 47 asks whether customer data is access-restricted. If Wayne answers "yes," it's lying. If Wayne answers "no," it loses the deal.</p>
</li>
</ul>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/52735ac4-ced9-4eff-9369-d40ccefa9ff5.png" alt="" style="display:block;margin:0 auto" />

<p><strong>The unpopular truth:</strong> Most teams file a public bucket under "security problem" and fix it by clicking <em>Block public access</em>. That's the right click. But if you stop there, a developer creates a new public bucket next sprint. The thing that was really broken is still broken: nobody decided who's allowed to do that.</p>
<p><strong>Security fixes the bucket. GRC fixes the reason the bucket happened.</strong></p>
<p>Honestly, that sentence is the whole post. Everything after it is detail.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/c4e5680c-3c24-46ef-a1ba-3fe7051713d7.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h2>Governance: The part everyone skips because it sounds like meetings</h2>
<p>Governance is about <strong>decision rights</strong>. Try this test at Wayne. Ask three people who approves a production deploy:</p>
<ul>
<li><p>The CTO says the team leads do.</p>
</li>
<li><p>A team lead says the CTO does.</p>
</li>
<li><p>A developer asks, "...approves?"</p>
</li>
</ul>
<p>Here's the setting behind the shrug, pulled from GitLab's API:</p>
<pre><code class="language-bash">$ curl -s --header "PRIVATE-TOKEN: &lt;your-token&gt;" \
  "https://gitlab.wayne-industries.example/api/v4/projects/42/protected_branches"
[
  {
    "name": "main",
    "push_access_levels":  [{ "access_level": 30, "access_level_description": "Developers + Maintainers" }],
    "merge_access_levels": [{ "access_level": 30, "access_level_description": "Developers + Maintainers" }],
    "allow_force_push": false
  }
]
# Anyone at Developer level can merge straight to main. Nobody chose this.
</code></pre>
<p><em>Sample output (illustrative). Wayne Industries is fictional.</em></p>
<p>This is a governance failure that shows up as a technical setting, and most governance failures look like that. Nobody made a bad decision here. Nobody made a decision at all, so the default settings made it for them.</p>
<p>Governance comes down to four questions:</p>
<ol>
<li><p><strong>Who owns security, and who do they report to?</strong></p>
</li>
<li><p><strong>How much risk are we willing to carry?</strong> This is risk appetite.</p>
</li>
<li><p><strong>Who is allowed to accept a risk instead of fixing it?</strong></p>
</li>
<li><p><strong>How does leadership find out when something goes wrong?</strong></p>
</li>
</ol>
<p>At Wayne, the honest answer to all four is "the CTO, probably, by email." That isn't governance. It's one person.</p>
<p><strong>The unpopular truth:</strong> Policies aren't governance. Wayne has 40 of them. They look like governance, but if no one owns them, no one reviews them, and nothing happens when they're broken, they're decoration.</p>
<hr />
<h2>Risk: Choosing what to worry about with the money you have</h2>
<p>Wayne has one security person and no security budget line. You can't fix ten things this week. Risk management is the discipline of <strong>choosing which ones come first</strong>.</p>
<p>The most common beginner mistake is treating scanner output as a risk list. A scanner tells you the bucket is public. It can't tell you whether the bucket holds marketing PDFs or a copy of your customer database. That second fact changes everything.</p>
<p>So you check the bucket at Wayne:</p>
<pre><code class="language-bash">$ aws s3 ls s3://wayne-staging-data/ --human-readable
2023-09-03 10:01:55  220.5 KiB demo-screens.zip
2024-02-11 16:22:07    1.2 GiB prod-export-2024.sql.gz
2024-02-11 16:25:40   14.3 KiB readme.txt
# A production database export, in a staging bucket, open to the internet.
</code></pre>
<p><em>Sample output (illustrative). Wayne Industries is fictional.</em></p>
<p>Someone copied a production database export into staging "for testing." The bucket just moved from "tidy this up sometime" to "stop what you're doing."</p>
<p>Now look at the three shadow AWS accounts. What's in them? Nobody knows, which means their risk is also unknown. <strong>You can't assess something you can't see.</strong></p>
<p>Next, the email thread:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/cd0b78b3-0294-486b-9f4a-ea52d38ae542.png" alt="" style="display:block;margin:0 auto" />

<p>A concern with no owner, no rating, no deadline and no decision is a worry, not a risk. A risk register exists to turn worries into decisions that someone signs their name to.</p>
<hr />
<h2>Compliance: Proving it to someone who doesn't trust you yet</h2>
<p>Back to Gotham Mutual. They've never met Wayne and they can't log into Wayne's AWS console. So they ask for <strong>proof</strong>: answers, reports, evidence.</p>
<p>Compliance faces outward, and it runs on evidence. Some of the demands you choose to take on, and some are imposed on you:</p>
<ul>
<li><p><strong>Chosen, usually because customers ask:</strong> SOC 2 and ISO 27001.</p>
</li>
<li><p><strong>Imposed by law or contract:</strong> PIPEDA for Canadian personal information, GDPR if EU residents are involved, and PCI DSS once card data appears.</p>
</li>
</ul>
<p>Two uncomfortable truths belong in the first post of any GRC series.</p>
<p><strong>1. You can be compliant and still get breached.</strong> Auditors test samples. Attackers don't limit themselves to a sample.</p>
<p><strong>2. You can be secure and still fail an audit, because you can't prove it.</strong> Wayne's nightly backups might be perfectly good. Without a single dated restore test, an auditor has to treat them as untested. "We do it" is a claim. A dated, reviewed record is evidence.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/7c2df59e-80a4-47cf-aa69-cdd2c7b313a8.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h2>How the three pieces fit together</h2>
<p>This is the loop that ties the three parts together:</p>
<p><strong>Governance</strong> sets direction and risk appetite → <strong>Risk</strong> ranks what matters against that appetite → <strong>Controls</strong> get built → <strong>Compliance</strong> tests and proves those controls → <strong>Findings</strong> go back to governance → the loop repeats.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/75cfbff2-0dae-42d1-b207-39ef077e076d.png" alt="" style="display:block;margin:0 auto" />

<p>Wayne has broken every link in the loop:</p>
<ul>
<li><p>Governance never set an appetite, so risk has nothing to measure against.</p>
</li>
<li><p>Without that, controls get built at random.</p>
</li>
<li><p>With random controls, compliance has nothing coherent to prove.</p>
</li>
<li><p>Any findings that do turn up have nowhere to go.</p>
</li>
</ul>
<p>The only reason anyone noticed is that a customer asked. That's normal. Most companies start doing GRC because a customer, a regulator, or an incident forced them to, not because someone at an offsite thought it sounded fun.</p>
<hr />
<h2>What GRC is not</h2>
<table>
<thead>
<tr>
<th>Myth</th>
<th>What's actually true</th>
</tr>
</thead>
<tbody><tr>
<td>"GRC is paperwork."</td>
<td>Paperwork is the output. The real product is decisions.</td>
</tr>
<tr>
<td>"GRC happens in audit season."</td>
<td>If it only happens in the eight weeks before an audit, it's theatre.</td>
</tr>
<tr>
<td>"We'll buy a GRC tool."</td>
<td>A tool only organizes decisions you've already made. Eramba with no named owners is an email thread with a login page.</td>
</tr>
<tr>
<td>"GRC is the department of no."</td>
<td>Good GRC says "yes, if..." and writes down the <em>if</em>.</td>
</tr>
<tr>
<td>"GRC is separate from engineering."</td>
<td>Every GRC decision ends up in a config: a GitLab setting, an SCP, an AD group.</td>
</tr>
<tr>
<td>"Compliant means secure."</td>
<td>Basically, <strong>proof</strong>: answers, reports, evidence.</td>
</tr>
<tr>
<td>"We're too small for GRC."</td>
<td>Gotham Mutual disagrees, and so does their $1.8M contract.</td>
</tr>
</tbody></table>
<hr />
<h2>Wayne's first hour: What you actually do on Monday</h2>
<p>Don't open the 214 questions yet. Answer three questions of your own first.</p>
<p><strong>1. Who decides?</strong> Book 30 minutes with Maya, the CEO, and Dan, the CTO, and leave with one sentence: <em>"Security is owned by Head of Security, who reports to CTO, and CTO is the person who accepts risk."</em> Send it as an email and get a "confirmed" reply. That dated email is Wayne's first piece of governance evidence. It's tiny, it's real, and it counts.</p>
<p><strong>2. What could hurt us most?</strong> Lock the public bucket now, because that's an emergency, not a project. Then replace the email thread with a 10-row list that gives every item an owner, a rough rating, and a next step. A spreadsheet is fine for this.</p>
<p><strong>3. Who is asking us to prove what?</strong> List every external obligation:</p>
<ul>
<li><p>Gotham Mutual, with its questionnaire and SOC 2 request.</p>
</li>
<li><p>PIPEDA, for the Canadian customer data.</p>
</li>
<li><p>Anything already in existing customer contracts.</p>
</li>
</ul>
<p>That list becomes your compliance scope.</p>
<p>After that, reply to the CTO with the honest version: <em>"We can answer the questionnaire truthfully by Friday. Nobody can produce a SOC 2 Type II by Friday, because it needs an observation period measured in months. Here's what I suggest we tell Gotham Mutual instead."</em></p>
<p><strong>The unpopular truth:</strong> The worst thing you can do on Friday is answer "yes" to things that aren't true. Questionnaire answers have a way of ending up attached to contracts. A "yes" that turns out to be false is no longer just a sales problem. It becomes a legal one.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/c42d8b8d-0b02-4890-8fd0-1e809d3474d9.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h2>Framework mapping</h2>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/b58d958e-15c6-452c-93fb-0d67bc7b10b0.png" alt="" style="display:block;margin:0 auto" />

<p>The GDPR row makes the same point as the kitchen analogy: regulators expect you to <em>demonstrate</em> compliance, not just claim it.</p>
<hr />
<h2>Maturity ladder: The same GRC at three company sizes</h2>
<table>
<thead>
<tr>
<th>Stage</th>
<th>20 people</th>
<th>200 people</th>
<th>2,000 people</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Who owns it</strong></td>
<td>Founder or CTO, part-time</td>
<td>1–3 people; often the security lead doubles as GRC</td>
<td>Dedicated GRC team under a CISO, plus a risk committee</td>
</tr>
<tr>
<td><strong>Governance</strong></td>
<td>A Slack message: "Priya owns security"</td>
<td>Named owner, quarterly security steering meeting</td>
<td>Board risk committee, charters, delegated authority matrix</td>
</tr>
<tr>
<td><strong>Risk register</strong></td>
<td>A spreadsheet with 10 rows, and that's honestly fine</td>
<td>Spreadsheet or free platform (Eramba), with owners and review dates</td>
<td>Enterprise GRC platform tied into enterprise risk; top risks quantified</td>
</tr>
<tr>
<td><strong>Policies</strong></td>
<td>3–5 short ones people actually read</td>
<td>10–15 with owners and an annual review</td>
<td>Full hierarchy (policy → standard → procedure) with formal document control</td>
</tr>
<tr>
<td><strong>Compliance</strong></td>
<td>Questionnaires answered by hand</td>
<td>SOC 2 Type II and/or ISO 27001, evidence semi-automated</td>
<td>Multiple frameworks crosswalked, continuous monitoring, internal audit function</td>
</tr>
</tbody></table>
<p><strong>Where's Wayne?</strong> It's a 240-person company running a GRC program built for 20 people, minus the 20-person version's saving grace: at 20 people, everyone knows who owns what. Closing that gap is what this series is for.</p>
<hr />
<h2>Cheatsheet</h2>
<img src="https://cdn.hashnode.com/uploads/covers/6aa9ab6f5e60cef18e9a8e9b/67be51b1-039c-4304-8e00-8787d23e139a.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h2>The takeaway</h2>
<p>GRC isn't the paperwork that shows up after security work. It's the system that decides which security work happens, who owns it, and how you prove it happened. Wayne doesn't lack tools. It lacks decisions. By Friday it will have exactly one, written down, dated and confirmed, and that's a real start.</p>
<hr />
<p><em>// @18xBan</em></p>
]]></content:encoded></item></channel></rss>