<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Victor B Vieira</title>
    <description>The latest articles on DEV Community by Victor B Vieira (@bidu).</description>
    <link>https://dev.to/bidu</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4125001%2Ff29b66c5-b49e-4ab3-a5b1-b28e1bfb4e8c.png</url>
      <title>DEV Community: Victor B Vieira</title>
      <link>https://dev.to/bidu</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/bidu"/>
    <language>en</language>
    <item>
      <title>RBAC Is Not Enough for Sensitive Investigation Workflows</title>
      <dc:creator>Victor B Vieira</dc:creator>
      <pubDate>Sat, 24 Oct 2026 01:06:54 +0000</pubDate>
      <link>https://dev.to/bidu/rbac-is-not-enough-for-sensitive-investigation-workflows-1ce6</link>
      <guid>https://dev.to/bidu/rbac-is-not-enough-for-sensitive-investigation-workflows-1ce6</guid>
      <description>&lt;p&gt;At first, an anonymous reporting system looks like a fairly ordinary CRUD application.&lt;/p&gt;

&lt;p&gt;Someone submits a report.&lt;/p&gt;

&lt;p&gt;An administrator receives it.&lt;/p&gt;

&lt;p&gt;Evidence gets attached.&lt;/p&gt;

&lt;p&gt;The case moves through a few statuses.&lt;/p&gt;

&lt;p&gt;Eventually, someone closes it.&lt;/p&gt;

&lt;p&gt;You can model that with roles:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;reporter
investigator
admin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And permissions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;reporter      -&amp;gt; create report, read own case
investigator  -&amp;gt; read cases, add notes
admin         -&amp;gt; read everything, manage users
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Simple.&lt;/p&gt;

&lt;p&gt;Until the first sensitive case arrives.&lt;/p&gt;

&lt;p&gt;Imagine an employee reports their own manager.&lt;/p&gt;

&lt;p&gt;That manager happens to be an administrator.&lt;/p&gt;

&lt;p&gt;Should &lt;code&gt;admin&lt;/code&gt; still mean:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;can_read_all_cases = true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Obviously not.&lt;/p&gt;

&lt;p&gt;And that's where the authorization model starts getting much more interesting.&lt;/p&gt;

&lt;p&gt;The problem is no longer:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What role does this user have?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It becomes:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What is this user's relationship with this specific case?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Role-based access is only the first layer
&lt;/h2&gt;

&lt;p&gt;RBAC works well when permissions mostly follow organizational roles.&lt;/p&gt;

&lt;p&gt;A billing employee sees billing information.&lt;/p&gt;

&lt;p&gt;Support sees tickets.&lt;/p&gt;

&lt;p&gt;Administrators configure the system.&lt;/p&gt;

&lt;p&gt;Sensitive investigations introduce another dimension: &lt;strong&gt;context&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A user can be perfectly authorized to investigate reports in general and still be forbidden from accessing one particular report.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;can_access_case =
    has_investigator_role
    &amp;amp;&amp;amp; !is_subject_of_case
    &amp;amp;&amp;amp; !has_conflict_of_interest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is already different from standard RBAC.&lt;/p&gt;

&lt;p&gt;Now imagine another situation.&lt;/p&gt;

&lt;p&gt;An HR director is allowed to access investigations.&lt;/p&gt;

&lt;p&gt;But the case mentions their spouse.&lt;/p&gt;

&lt;p&gt;Or their direct report.&lt;/p&gt;

&lt;p&gt;Or their own department.&lt;/p&gt;

&lt;p&gt;The role didn't change.&lt;/p&gt;

&lt;p&gt;The context did.&lt;/p&gt;

&lt;p&gt;This is why systems that deal with sensitive cases often need some combination of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;role-based permissions;&lt;/li&gt;
&lt;li&gt;case-level permissions;&lt;/li&gt;
&lt;li&gt;explicit exclusions;&lt;/li&gt;
&lt;li&gt;conflict-of-interest rules;&lt;/li&gt;
&lt;li&gt;temporary assignments;&lt;/li&gt;
&lt;li&gt;auditable overrides.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;"Admin" stops being a sufficient security model very quickly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Case access and identity access should not be the same permission
&lt;/h2&gt;

&lt;p&gt;Another mistake is treating a report as one indivisible object.&lt;/p&gt;

&lt;p&gt;Suppose the report contains:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;case
├── allegation
├── attachments
├── reporter identity
├── internal notes
├── interviews
├── investigation findings
└── resolution
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Does everyone who can access the case need to see every field?&lt;/p&gt;

&lt;p&gt;Usually not.&lt;/p&gt;

&lt;p&gt;An investigator may need to read the allegation but not know the reporter's identity.&lt;/p&gt;

&lt;p&gt;Someone reviewing evidence may need attachments but not internal interview notes.&lt;/p&gt;

&lt;p&gt;A compliance lead may need to approve the final decision without seeing unnecessary personal information.&lt;/p&gt;

&lt;p&gt;So instead of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;can_view_case
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;you start needing permissions closer to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;can_view_report
can_view_reporter_identity
can_view_evidence
can_view_internal_notes
can_add_evidence
can_interview
can_assign_case
can_close_case
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This matters for privacy, but it also changes your domain model.&lt;/p&gt;

&lt;p&gt;The reporter's identity should not just be another field conveniently returned by:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET /cases/:id
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If identity deserves stronger protection, model it that way.&lt;/p&gt;

&lt;p&gt;Different storage.&lt;/p&gt;

&lt;p&gt;Different access policy.&lt;/p&gt;

&lt;p&gt;Different audit trail.&lt;/p&gt;

&lt;p&gt;Possibly different encryption boundaries.&lt;/p&gt;

&lt;p&gt;The data model should reflect the sensitivity model.&lt;/p&gt;

&lt;h2&gt;
  
  
  Anonymity is not a boolean
&lt;/h2&gt;

&lt;p&gt;Developers are often tempted to model anonymity like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"anonymous"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But anonymity is not really a property of the report.&lt;/p&gt;

&lt;p&gt;It is a property of the entire system around the report.&lt;/p&gt;

&lt;p&gt;You can remove the name field and still collect:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;IP addresses;&lt;/li&gt;
&lt;li&gt;analytics identifiers;&lt;/li&gt;
&lt;li&gt;session metadata;&lt;/li&gt;
&lt;li&gt;authentication information;&lt;/li&gt;
&lt;li&gt;reverse-proxy logs;&lt;/li&gt;
&lt;li&gt;device fingerprints;&lt;/li&gt;
&lt;li&gt;email notifications containing sensitive context.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The UI may tell the user:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Your report is anonymous.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Meanwhile the infrastructure may know exactly where it came from.&lt;/p&gt;

&lt;p&gt;That doesn't necessarily mean those systems are malicious.&lt;/p&gt;

&lt;p&gt;It often means nobody modeled anonymity as an architectural requirement.&lt;/p&gt;

&lt;p&gt;For sensitive reporting, a better question is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Which pieces of information could allow us to identify this person, and do we actually need them?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's data minimization as an engineering decision, not just a privacy-policy sentence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Anonymous follow-up creates another design problem
&lt;/h2&gt;

&lt;p&gt;If you don't know who the reporter is, how do you ask them another question?&lt;/p&gt;

&lt;p&gt;You still need conversations like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Can you tell us approximately when this happened?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;or:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Do you still have the original file?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Traditional messaging assumes identity.&lt;/p&gt;

&lt;p&gt;Anonymous reporting can't.&lt;/p&gt;

&lt;p&gt;One pattern is to issue an opaque credential when the report is created:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;report_id
access_token
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The reporter stores that credential and uses it to return later.&lt;/p&gt;

&lt;p&gt;Something conceptually like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST /reports

201 Created

{
  "report_id": "RPT-8FH2K",
  "access_token": "&amp;lt;opaque-secret&amp;gt;"
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The token is not an account.&lt;/p&gt;

&lt;p&gt;It doesn't need an email address.&lt;/p&gt;

&lt;p&gt;It simply proves that whoever holds it is allowed to access the reporter-facing side of that specific case.&lt;/p&gt;

&lt;p&gt;That lets the system provide bidirectional communication without forcing identity.&lt;/p&gt;

&lt;p&gt;But then more questions appear:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Can the token be recovered?&lt;/li&gt;
&lt;li&gt;Can administrators see it?&lt;/li&gt;
&lt;li&gt;Should it be stored hashed?&lt;/li&gt;
&lt;li&gt;Can it be rotated?&lt;/li&gt;
&lt;li&gt;What happens if it is leaked?&lt;/li&gt;
&lt;li&gt;How long does it remain valid?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The apparently simple requirement "allow anonymous follow-up" becomes an authentication design problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Evidence needs history, not just storage
&lt;/h2&gt;

&lt;p&gt;Attachments are another deceptively simple feature.&lt;/p&gt;

&lt;p&gt;The product requirement may begin as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Users can upload evidence.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So you create:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;attachments
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and store files somewhere.&lt;/p&gt;

&lt;p&gt;But once those files may influence an investigation, the system needs to answer more questions.&lt;/p&gt;

&lt;p&gt;Where did this file come from?&lt;/p&gt;

&lt;p&gt;When was it uploaded?&lt;/p&gt;

&lt;p&gt;Was it replaced?&lt;/p&gt;

&lt;p&gt;Who downloaded it?&lt;/p&gt;

&lt;p&gt;Did anyone modify its metadata?&lt;/p&gt;

&lt;p&gt;Was it deleted?&lt;/p&gt;

&lt;p&gt;If it was deleted, why?&lt;/p&gt;

&lt;p&gt;A useful evidence record might look more like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Evidence
--------
id
case_id
original_filename
storage_key
content_hash
submitted_at
submitted_by
source_type
status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then every important operation becomes an event:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;EVIDENCE_UPLOADED
EVIDENCE_VIEWED
EVIDENCE_DOWNLOADED
EVIDENCE_MARKED_IRRELEVANT
EVIDENCE_RESTRICTED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice what is missing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;EVIDENCE_SILENTLY_DISAPPEARED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For sensitive systems, destructive CRUD operations deserve suspicion.&lt;/p&gt;

&lt;p&gt;Sometimes the better model is not:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;DELETE&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;evidence&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;but something closer to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;status = discarded
discard_reason = duplicate
discarded_by = investigator_42
discarded_at = ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The original remains protected according to retention rules, while the investigation history remains understandable.&lt;/p&gt;

&lt;p&gt;Whether you need immutable storage, hashing, signed records or stronger chain-of-custody controls depends on the system.&lt;/p&gt;

&lt;p&gt;But &lt;strong&gt;auditability should be designed before you need it&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Your audit log is security infrastructure
&lt;/h2&gt;

&lt;p&gt;Most applications have logs.&lt;/p&gt;

&lt;p&gt;That doesn't automatically mean they have an audit trail.&lt;/p&gt;

&lt;p&gt;An application log might say:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET /api/cases/381 200
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An investigation audit trail should answer something more useful:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;actor: user_827
action: viewed_reporter_identity
case: case_381
timestamp: ...
reason: assigned_investigator
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Those are different things.&lt;/p&gt;

&lt;p&gt;For highly sensitive operations, I want to know:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;who performed the action;&lt;/li&gt;
&lt;li&gt;which case it affected;&lt;/li&gt;
&lt;li&gt;what kind of data was accessed;&lt;/li&gt;
&lt;li&gt;when it happened;&lt;/li&gt;
&lt;li&gt;whether the action was authorized;&lt;/li&gt;
&lt;li&gt;when relevant, why it happened.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And the audit system itself needs protection.&lt;/p&gt;

&lt;p&gt;If administrators can rewrite the audit trail, it isn't much of an audit trail.&lt;/p&gt;

&lt;h2&gt;
  
  
  Investigation state is richer than open/closed
&lt;/h2&gt;

&lt;p&gt;Another place where domain complexity leaks into the software is case status.&lt;/p&gt;

&lt;p&gt;The naive model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;open
closed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;doesn't tell you much.&lt;/p&gt;

&lt;p&gt;So maybe you add:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;new
triage
investigating
resolved
closed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Better.&lt;/p&gt;

&lt;p&gt;But eventually you discover that &lt;strong&gt;workflow state and investigation outcome are different concepts&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A case might be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;workflow_status = closed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;while its outcome is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;outcome = inconclusive
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That matters because an inconclusive report is not the same thing as a false report.&lt;/p&gt;

&lt;p&gt;A more expressive model separates them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;workflow_status:
  new
  triage
  investigating
  awaiting_information
  decision_pending
  closed

outcome:
  substantiated
  partially_substantiated
  unsubstantiated
  inconclusive
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why care?&lt;/p&gt;

&lt;p&gt;Because software shapes behavior.&lt;/p&gt;

&lt;p&gt;If your UI forces investigators to choose only:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;true
false
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;you are embedding an oversimplified policy into the product.&lt;/p&gt;

&lt;p&gt;Your enum is making an organizational decision.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conflicts of interest belong in the domain model
&lt;/h2&gt;

&lt;p&gt;This is another thing I initially thought belonged entirely to company policy.&lt;/p&gt;

&lt;p&gt;It doesn't.&lt;/p&gt;

&lt;p&gt;Suppose Alice is normally an investigator.&lt;/p&gt;

&lt;p&gt;A report arrives involving Alice.&lt;/p&gt;

&lt;p&gt;The system should not rely on another administrator remembering:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Oh, we probably shouldn't assign that one to Alice.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The conflict should be representable.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CaseParticipant
---------------
case_id
user_id
relationship

relationships:
  reporter
  subject
  witness
  investigator
  reviewer
  conflicted
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now authorization can use domain information.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;can_access_case&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;case&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has_permission&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;investigate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has_conflict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_assigned_to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Real systems will be more complex.&lt;/p&gt;

&lt;p&gt;But the important point is this:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;conflict of interest stops being a note in a policy PDF and becomes something the application can enforce.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's a much stronger guarantee.&lt;/p&gt;

&lt;h2&gt;
  
  
  Overrides should be possible — and uncomfortable
&lt;/h2&gt;

&lt;p&gt;Eventually someone will need emergency access.&lt;/p&gt;

&lt;p&gt;Maybe every normal investigator is conflicted.&lt;/p&gt;

&lt;p&gt;Maybe a legal hold requires access.&lt;/p&gt;

&lt;p&gt;Maybe an incident needs escalation.&lt;/p&gt;

&lt;p&gt;Hard-coding "never" into authorization rules can make a system unusable.&lt;/p&gt;

&lt;p&gt;But the opposite is also dangerous:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;super_admin = god_mode
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A useful pattern is controlled break-glass access.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;override requires:
- elevated permission
- explicit reason
- re-authentication
- audit event
- optional second approval
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You are not making the action impossible.&lt;/p&gt;

&lt;p&gt;You are making it deliberate and visible.&lt;/p&gt;

&lt;p&gt;That difference matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sensitive workflows make generic admin panels dangerous
&lt;/h2&gt;

&lt;p&gt;A lot of SaaS products are built around the idea that the customer administrator owns everything.&lt;/p&gt;

&lt;p&gt;For many products, that's reasonable.&lt;/p&gt;

&lt;p&gt;For sensitive reporting systems, it can be exactly the wrong assumption.&lt;/p&gt;

&lt;p&gt;The person who bought the software may not be entitled to inspect every report.&lt;/p&gt;

&lt;p&gt;The company owner may be the subject of a report.&lt;/p&gt;

&lt;p&gt;The account administrator may work in the department being investigated.&lt;/p&gt;

&lt;p&gt;So tenant ownership and data access cannot automatically mean the same thing.&lt;/p&gt;

&lt;p&gt;This is one of those cases where traditional B2B SaaS assumptions start breaking.&lt;/p&gt;

&lt;p&gt;The customer owns the workspace.&lt;/p&gt;

&lt;p&gt;That does not necessarily mean every customer administrator should be able to read every piece of information inside it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The hardest requirements often appear after &lt;code&gt;POST /reports&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;When we first think about a reporting system, it's natural to focus on intake.&lt;/p&gt;

&lt;p&gt;The form.&lt;/p&gt;

&lt;p&gt;Validation.&lt;/p&gt;

&lt;p&gt;File uploads.&lt;/p&gt;

&lt;p&gt;Anonymous submission.&lt;/p&gt;

&lt;p&gt;Those are visible.&lt;/p&gt;

&lt;p&gt;They are easy to put on a roadmap.&lt;/p&gt;

&lt;p&gt;But most of the difficult engineering decisions happen afterward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;submit
  ↓
triage
  ↓
conflict detection
  ↓
assignment
  ↓
evidence collection
  ↓
restricted interviews
  ↓
decision
  ↓
follow-up
  ↓
retention
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every arrow hides authorization decisions.&lt;/p&gt;

&lt;p&gt;Every box contains potentially sensitive data.&lt;/p&gt;

&lt;p&gt;And every shortcut can affect whether users trust the system.&lt;/p&gt;

&lt;p&gt;That's why I no longer think of anonymous reporting as a form with a back office.&lt;/p&gt;

&lt;p&gt;It's a &lt;strong&gt;context-sensitive authorization system wrapped around an investigation workflow&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The form is just where the data enters.&lt;/p&gt;




&lt;h2&gt;
  
  
  A few questions I now ask much earlier
&lt;/h2&gt;

&lt;p&gt;If I were designing another sensitive workflow from scratch, I'd ask these before choosing the database schema:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can someone be authorized globally but forbidden from one specific case?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is identity access separate from case access?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can sensitive information disappear without leaving a trace?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can users communicate without revealing their identity?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can the system represent conflicts of interest?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What happens when normal authorization rules need an emergency override?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do our statuses reflect the real domain, or just make the UI convenient?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If those questions arrive late, changing the architecture becomes much more painful.&lt;/p&gt;

&lt;p&gt;If they arrive early, they shape a much better system.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;AI assistance disclosure: AI was used during drafting and editing. The technical concepts and product decisions were reviewed by the author before publication.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Conceptual illustration of a sensitive reporting workflow. AI-generated.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>backend</category>
      <category>security</category>
      <category>systemdesign</category>
    </item>
    <item>
      <title>When a Legal Requirement Turns Into an Authorization Problem</title>
      <dc:creator>Victor B Vieira</dc:creator>
      <pubDate>Fri, 02 Oct 2026 17:06:00 +0000</pubDate>
      <link>https://dev.to/bidu/when-a-legal-requirement-turns-into-an-authorization-problem-37c</link>
      <guid>https://dev.to/bidu/when-a-legal-requirement-turns-into-an-authorization-problem-37c</guid>
      <description>&lt;p&gt;At first, the requirement sounded almost trivial:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Employees need a way to submit anonymous reports.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Build a form. Store the report. Give HR an admin panel.&lt;/p&gt;

&lt;p&gt;Done.&lt;/p&gt;

&lt;p&gt;Except it wasn't.&lt;/p&gt;

&lt;p&gt;Once we started breaking the requirement down, a simple reporting form became an authorization problem, then a privacy problem, then a governance problem.&lt;/p&gt;

&lt;p&gt;And most of the difficult parts had very little to do with the form itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  The requirement
&lt;/h2&gt;

&lt;p&gt;In Brazil, Law No. 14,457/2022 introduced several requirements for companies that maintain a CIPA — the country's Internal Commission for Accident and Harassment Prevention.&lt;/p&gt;

&lt;p&gt;One of them is particularly interesting from a software perspective.&lt;/p&gt;

&lt;p&gt;Companies need procedures for receiving and following up on reports, investigating what happened, and potentially applying sanctions while &lt;strong&gt;guaranteeing the anonymity of the person submitting the report&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That's a legal requirement.&lt;/p&gt;

&lt;p&gt;But someone eventually has to turn it into software.&lt;/p&gt;

&lt;p&gt;And that's where the ambiguity starts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who is allowed to see a report?
&lt;/h2&gt;

&lt;p&gt;Imagine the first version of the application.&lt;/p&gt;

&lt;p&gt;You have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a public reporting form;&lt;/li&gt;
&lt;li&gt;an authenticated dashboard;&lt;/li&gt;
&lt;li&gt;attachments;&lt;/li&gt;
&lt;li&gt;comments;&lt;/li&gt;
&lt;li&gt;report statuses;&lt;/li&gt;
&lt;li&gt;an audit history.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pretty standard internal software.&lt;/p&gt;

&lt;p&gt;Then this report arrives:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;My manager has been harassing me.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now suppose that manager also has administrative access to the reporting system.&lt;/p&gt;

&lt;p&gt;Suddenly, the permission model is wrong.&lt;/p&gt;

&lt;p&gt;It doesn't matter that the reporter's name is hidden if the person being reported can open the case, see its contents, or infer where it came from.&lt;/p&gt;

&lt;p&gt;So we get a new rule:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Someone mentioned in a report should not be able to manage that report.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That sounds obvious after you hear it.&lt;/p&gt;

&lt;p&gt;It isn't necessarily obvious when you design the first database schema.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;admin&lt;/code&gt; stops being useful pretty quickly
&lt;/h2&gt;

&lt;p&gt;A lot of internal software begins with something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;user
admin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For this kind of system, that abstraction falls apart almost immediately.&lt;/p&gt;

&lt;p&gt;You might have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;HR;&lt;/li&gt;
&lt;li&gt;compliance;&lt;/li&gt;
&lt;li&gt;company directors;&lt;/li&gt;
&lt;li&gt;external investigators;&lt;/li&gt;
&lt;li&gt;organization administrators;&lt;/li&gt;
&lt;li&gt;members of the CIPA.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Different roles can see different things.&lt;/p&gt;

&lt;p&gt;But even that isn't enough.&lt;/p&gt;

&lt;p&gt;Because access can depend on the &lt;strong&gt;content of the report itself&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Alice may normally be allowed to see every report.&lt;/p&gt;

&lt;p&gt;Unless Alice is mentioned in this one.&lt;/p&gt;

&lt;p&gt;So authorization becomes something closer to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;can_access =
    has_permission
    &amp;amp;&amp;amp; !is_involved_in_report
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which means this is no longer just classic RBAC.&lt;/p&gt;

&lt;p&gt;The authorization decision now depends on context.&lt;/p&gt;

&lt;p&gt;And once you go down that path, a lot of other questions show up.&lt;/p&gt;

&lt;p&gt;Who determines that someone is involved?&lt;/p&gt;

&lt;p&gt;Can another administrator override the restriction?&lt;/p&gt;

&lt;p&gt;What happens if every person with the appropriate role is mentioned?&lt;/p&gt;

&lt;p&gt;Who can see that access was revoked?&lt;/p&gt;

&lt;p&gt;Should the database enforce this, or should the application?&lt;/p&gt;

&lt;p&gt;A requirement that originally sounded like "add an anonymous form" is now shaping the authorization model.&lt;/p&gt;

&lt;h2&gt;
  
  
  Anonymous to whom?
&lt;/h2&gt;

&lt;p&gt;"Anonymous" is another word that sounds simple until you have to define it.&lt;/p&gt;

&lt;p&gt;The form not asking for a name is one thing.&lt;/p&gt;

&lt;p&gt;The company administrator being unable to identify the reporter is another.&lt;/p&gt;

&lt;p&gt;The infrastructure itself not retaining data that could later be correlated with that person is something else entirely.&lt;/p&gt;

&lt;p&gt;A perfectly anonymous-looking form can still sit behind:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;IP logs;&lt;/li&gt;
&lt;li&gt;analytics scripts;&lt;/li&gt;
&lt;li&gt;authentication middleware;&lt;/li&gt;
&lt;li&gt;session identifiers;&lt;/li&gt;
&lt;li&gt;reverse proxy logs;&lt;/li&gt;
&lt;li&gt;third-party monitoring tools.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of those systems are inherently bad.&lt;/p&gt;

&lt;p&gt;Most are things we would add to a normal application without thinking much about them.&lt;/p&gt;

&lt;p&gt;But privacy-sensitive products force you to ask a different question:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do we actually need to collect this information?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Sometimes the safest data is simply data you never stored.&lt;/p&gt;

&lt;h2&gt;
  
  
  Then comes the messaging problem
&lt;/h2&gt;

&lt;p&gt;Reports are rarely complete.&lt;/p&gt;

&lt;p&gt;Someone submits something like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;My manager threatened me after the meeting.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For an investigation, that may not be enough.&lt;/p&gt;

&lt;p&gt;Which meeting?&lt;/p&gt;

&lt;p&gt;When?&lt;/p&gt;

&lt;p&gt;Was anyone else there?&lt;/p&gt;

&lt;p&gt;Is there a message or document related to it?&lt;/p&gt;

&lt;p&gt;Normally, the obvious solution is:&lt;/p&gt;

&lt;p&gt;Send the user an email.&lt;/p&gt;

&lt;p&gt;Except you don't know who the user is.&lt;/p&gt;

&lt;p&gt;And ideally you don't want to know.&lt;/p&gt;

&lt;p&gt;So the identity model needs to change.&lt;/p&gt;

&lt;p&gt;Instead of attaching the conversation to a person, you can attach it to the report itself.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;report_id
access_token
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The reporter keeps a credential.&lt;/p&gt;

&lt;p&gt;They can return later, see replies, provide additional information, and follow the status of the case.&lt;/p&gt;

&lt;p&gt;The investigator talks to the holder of that credential without knowing who that person is.&lt;/p&gt;

&lt;p&gt;Nothing particularly exotic is happening technically.&lt;/p&gt;

&lt;p&gt;But a domain requirement completely changes how a very ordinary feature — messaging — has to work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trust is another part of the architecture
&lt;/h2&gt;

&lt;p&gt;There's also a problem code alone can't solve.&lt;/p&gt;

&lt;p&gt;You can build everything correctly.&lt;/p&gt;

&lt;p&gt;You can avoid storing IP addresses.&lt;/p&gt;

&lt;p&gt;You can minimize logs.&lt;/p&gt;

&lt;p&gt;You can encrypt sensitive fields.&lt;/p&gt;

&lt;p&gt;You can implement contextual access policies.&lt;/p&gt;

&lt;p&gt;You can automatically remove someone from a case when they're involved in it.&lt;/p&gt;

&lt;p&gt;And an employee can still look at the &lt;strong&gt;Submit report&lt;/strong&gt; button and think:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Is this actually anonymous?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's rational.&lt;/p&gt;

&lt;p&gt;The person using the system isn't reading the source code.&lt;/p&gt;

&lt;p&gt;They're not inspecting proxy configuration.&lt;/p&gt;

&lt;p&gt;They're not reviewing database policies.&lt;/p&gt;

&lt;p&gt;They're being asked to trust the system.&lt;/p&gt;

&lt;p&gt;So technical decisions eventually become UX decisions.&lt;/p&gt;

&lt;p&gt;Compare:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Your report is anonymous.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;with:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;We do not ask for your identity or store your IP address.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The second statement is more specific.&lt;/p&gt;

&lt;p&gt;It explains something the system actually does.&lt;/p&gt;

&lt;p&gt;That sort of explainability matters more than I originally expected.&lt;/p&gt;

&lt;h2&gt;
  
  
  The law defines an outcome, not an architecture
&lt;/h2&gt;

&lt;p&gt;This is probably the part I find most interesting.&lt;/p&gt;

&lt;p&gt;Brazilian Law No. 14,457/2022 doesn't tell developers to use PostgreSQL.&lt;/p&gt;

&lt;p&gt;It doesn't say:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;use row-level security
don't log IP addresses
implement contextual RBAC
require two administrators
use magic-link authentication
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It defines obligations and expected outcomes.&lt;/p&gt;

&lt;p&gt;The implementation is left to the organization building the system.&lt;/p&gt;

&lt;p&gt;That happens a lot in B2B software.&lt;/p&gt;

&lt;p&gt;A requirement arrives as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;We need to comply with X.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You start decomposing it.&lt;/p&gt;

&lt;p&gt;And underneath that sentence you find decisions involving:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;architecture;&lt;/li&gt;
&lt;li&gt;permissions;&lt;/li&gt;
&lt;li&gt;security;&lt;/li&gt;
&lt;li&gt;privacy;&lt;/li&gt;
&lt;li&gt;data retention;&lt;/li&gt;
&lt;li&gt;UX;&lt;/li&gt;
&lt;li&gt;auditability;&lt;/li&gt;
&lt;li&gt;governance.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The regulation describes the destination.&lt;/p&gt;

&lt;p&gt;Engineering has to figure out the road.&lt;/p&gt;

&lt;h2&gt;
  
  
  The form is probably the least interesting part
&lt;/h2&gt;

&lt;p&gt;This:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;textarea&amp;gt;&amp;lt;/textarea&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;button&amp;gt;&lt;/span&gt;Submit report&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;is easy.&lt;/p&gt;

&lt;p&gt;The hard questions come afterward.&lt;/p&gt;

&lt;p&gt;Who receives the report?&lt;/p&gt;

&lt;p&gt;Who must never receive it?&lt;/p&gt;

&lt;p&gt;Who audits access?&lt;/p&gt;

&lt;p&gt;How do you communicate with someone you can't identify?&lt;/p&gt;

&lt;p&gt;What should you log?&lt;/p&gt;

&lt;p&gt;What should you intentionally &lt;strong&gt;not&lt;/strong&gt; log?&lt;/p&gt;

&lt;p&gt;How do you handle conflicts of interest?&lt;/p&gt;

&lt;p&gt;And how do you explain all of this to the user well enough that they trust the system?&lt;/p&gt;

&lt;p&gt;That's when something that originally looked like "an HR form" becomes a much more interesting engineering problem.&lt;/p&gt;

&lt;p&gt;I'm particularly curious about one part of this:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If access to a record depends on whether the current user is mentioned inside that record, where would you enforce that rule?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Application layer?&lt;/p&gt;

&lt;p&gt;Database policies?&lt;/p&gt;

&lt;p&gt;A dedicated authorization layer?&lt;/p&gt;

&lt;p&gt;Something else?&lt;/p&gt;




&lt;h3&gt;
  
  
  A note on this article
&lt;/h3&gt;

&lt;p&gt;I work as CTO and co-founder at Sigilo, where we build privacy-first reporting and organizational feedback systems.&lt;/p&gt;

&lt;p&gt;The examples and technical decisions discussed here come from problems we've encountered while designing these workflows.&lt;/p&gt;

&lt;p&gt;I used AI assistance to help structure and refine the English version of this article. The technical reasoning, product experience, and final review are my own.&lt;/p&gt;

</description>
      <category>security</category>
      <category>privacy</category>
      <category>architecture</category>
      <category>programming</category>
    </item>
    <item>
      <title>Anonymity Is Not a Checkbox: Lessons From Building a Whistleblowing System</title>
      <dc:creator>Victor B Vieira</dc:creator>
      <pubDate>Thu, 24 Sep 2026 18:06:17 +0000</pubDate>
      <link>https://dev.to/bidu/anonymity-is-not-a-checkbox-lessons-from-building-a-whistleblowing-system-14em</link>
      <guid>https://dev.to/bidu/anonymity-is-not-a-checkbox-lessons-from-building-a-whistleblowing-system-14em</guid>
      <description>&lt;p&gt;When we started building the whistleblowing channel at &lt;strong&gt;Sigilo Profissional&lt;/strong&gt;, anonymity seemed like one of the simpler requirements.&lt;/p&gt;

&lt;p&gt;Do not ask for a name.&lt;/p&gt;

&lt;p&gt;Do not require an email address.&lt;/p&gt;

&lt;p&gt;Do not force users to create an account.&lt;/p&gt;

&lt;p&gt;Done, right?&lt;/p&gt;

&lt;p&gt;Not exactly.&lt;/p&gt;

&lt;p&gt;The deeper we went into the architecture, the clearer it became that &lt;strong&gt;anonymity is not a UI feature. It is a system-wide constraint.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here are some of the problems we had to think about.&lt;/p&gt;




&lt;h3&gt;
  
  
  1. A Form Without a Name Can Still Identify Someone
&lt;/h3&gt;

&lt;p&gt;A reporting page can display:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"Your report is 100% anonymous."&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But the infrastructure behind it may still collect:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;IP addresses&lt;/li&gt;
&lt;li&gt;&lt;code&gt;X-Forwarded-For&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;session identifiers&lt;/li&gt;
&lt;li&gt;persistent cookies&lt;/li&gt;
&lt;li&gt;User-Agent data&lt;/li&gt;
&lt;li&gt;infrastructure logs&lt;/li&gt;
&lt;li&gt;precise timestamps&lt;/li&gt;
&lt;li&gt;analytics events&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The problem is not always one individual data point.&lt;/p&gt;

&lt;p&gt;It is correlation.&lt;/p&gt;

&lt;p&gt;A report submitted at &lt;code&gt;14:37&lt;/code&gt;, for example, may become much less anonymous if someone can compare that timestamp with corporate VPN, Wi-Fi, firewall, or internal access logs.&lt;/p&gt;

&lt;p&gt;This led us to a simple privacy principle:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;If identifiable data is not required to provide the feature, why store it?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;At Sigilo, whistleblowers do not need to create an account, provide a name, or enter an email address.&lt;/p&gt;

&lt;p&gt;We also do not store their IP address.&lt;/p&gt;

&lt;p&gt;Instead, reports can be accessed later through an independently generated protocol.&lt;/p&gt;

&lt;p&gt;Which creates another interesting problem.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. Continuity Does Not Require Identity
&lt;/h3&gt;

&lt;p&gt;After submitting a report, the whistleblower may need to return later.&lt;/p&gt;

&lt;p&gt;Maybe they want to check the investigation status.&lt;/p&gt;

&lt;p&gt;Maybe the compliance team needs additional information.&lt;/p&gt;

&lt;p&gt;Maybe they need to upload another document.&lt;/p&gt;

&lt;p&gt;In a traditional application, the solution would probably be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;email + password
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or a persistent authenticated session.&lt;/p&gt;

&lt;p&gt;But the question we actually need to answer is not:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"Who is this user?"&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"Is this person authorized to access this report?"&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Those are different problems.&lt;/p&gt;

&lt;p&gt;We can preserve continuity without establishing real-world identity.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;anonymous user
      ↓
random protocol
      ↓
specific report
      ↓
follow-up communication
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The system knows that someone has the correct credentials to access a specific case.&lt;/p&gt;

&lt;p&gt;It does not need to know who that person is.&lt;/p&gt;

&lt;p&gt;That distinction between &lt;strong&gt;identity and continuity&lt;/strong&gt; became one of the most interesting patterns in the product.&lt;/p&gt;




&lt;h3&gt;
  
  
  3. Uploaded Files Are Part of the Threat Model
&lt;/h3&gt;

&lt;p&gt;Removing IP logging still does not make a system anonymous by itself.&lt;/p&gt;

&lt;p&gt;Suppose someone uploads a &lt;code&gt;.docx&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That file may contain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;author information&lt;/li&gt;
&lt;li&gt;operating-system usernames&lt;/li&gt;
&lt;li&gt;creation dates&lt;/li&gt;
&lt;li&gt;software metadata&lt;/li&gt;
&lt;li&gt;revision history&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Images may contain EXIF metadata.&lt;/p&gt;

&lt;p&gt;Spreadsheets can contain document properties.&lt;/p&gt;

&lt;p&gt;Even screenshots may accidentally expose information the sender did not intend to share.&lt;/p&gt;

&lt;p&gt;So the threat model cannot stop at the HTTP request.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;User-generated files are part of the anonymity boundary too.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And there is another limitation no architecture can completely solve.&lt;/p&gt;

&lt;p&gt;Imagine a report containing:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"Yesterday at 2 PM, during a meeting attended only by my manager, John, and me..."&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The application can minimize technical traces.&lt;/p&gt;

&lt;p&gt;It cannot prevent contextual inference.&lt;/p&gt;

&lt;p&gt;Privacy engineering can reduce what the system leaks.&lt;/p&gt;

&lt;p&gt;It cannot remove information voluntarily provided by the human.&lt;/p&gt;




&lt;h3&gt;
  
  
  4. Conflict of Interest Becomes an Authorization Problem
&lt;/h3&gt;

&lt;p&gt;Another requirement that initially looked like a business rule quickly became an access-control problem.&lt;/p&gt;

&lt;p&gt;Imagine three managers responsible for reviewing reports.&lt;/p&gt;

&lt;p&gt;The first version of the workflow looks simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Report
   ↓
Responsible Group
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now imagine the report is about one of those three managers.&lt;/p&gt;

&lt;p&gt;That person should obviously not receive access to the case.&lt;/p&gt;

&lt;p&gt;So the workflow becomes closer to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Report
   ↓
Responsible Group
   ↓
Detect involved reviewers
   ↓
Exclude them from the case
   ↓
Fallback route if necessary
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In simplified pseudocode:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;can_access_report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reviewer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;report&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;reviewer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;report&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;involved_people&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;reviewer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;report&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assigned_reviewers&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But then more questions appear.&lt;/p&gt;

&lt;p&gt;What if every reviewer is involved?&lt;/p&gt;

&lt;p&gt;What if a reviewer joins the group after the case was created?&lt;/p&gt;

&lt;p&gt;Should access be retroactive?&lt;/p&gt;

&lt;p&gt;What happens when someone leaves the investigation team?&lt;/p&gt;

&lt;p&gt;How do we preserve an audit trail for every access change?&lt;/p&gt;

&lt;p&gt;A seemingly simple workflow quickly becomes a problem involving &lt;strong&gt;authorization, auditability, segregation of duties, and conflict-of-interest management&lt;/strong&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  5. Technical Anonymity Is Not Enough
&lt;/h3&gt;

&lt;p&gt;This is probably the part I find most interesting.&lt;/p&gt;

&lt;p&gt;We can strip IP addresses.&lt;/p&gt;

&lt;p&gt;Minimize logs.&lt;/p&gt;

&lt;p&gt;Use strong access controls.&lt;/p&gt;

&lt;p&gt;Avoid persistent identifiers.&lt;/p&gt;

&lt;p&gt;Protect tenant boundaries.&lt;/p&gt;

&lt;p&gt;But none of that automatically answers this question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Why should the employee believe us?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The person using the system may be reporting their own manager.&lt;/p&gt;

&lt;p&gt;Harassment.&lt;/p&gt;

&lt;p&gt;Fraud.&lt;/p&gt;

&lt;p&gt;Discrimination.&lt;/p&gt;

&lt;p&gt;Something that could affect their career.&lt;/p&gt;

&lt;p&gt;In that context, &lt;code&gt;"Trust us, your data is safe"&lt;/code&gt; is not enough.&lt;/p&gt;

&lt;p&gt;Some technical decisions become part of the product experience itself.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"We do not store your IP address."&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is an infrastructure decision.&lt;/p&gt;

&lt;p&gt;But it is also information that helps the whistleblower understand &lt;strong&gt;why&lt;/strong&gt; the system is designed to protect their anonymity.&lt;/p&gt;

&lt;p&gt;This changed how I think about privacy-sensitive software.&lt;/p&gt;

&lt;p&gt;The user's perception of safety is not separate from the architecture.&lt;/p&gt;

&lt;p&gt;It is part of whether the product works at all.&lt;/p&gt;




&lt;h3&gt;
  
  
  💬 Community Question: When Is a System Truly Anonymous?
&lt;/h3&gt;

&lt;p&gt;The biggest lesson for me has been that anonymity is not something you add at the end of development.&lt;/p&gt;

&lt;p&gt;It affects infrastructure, data collection, authentication, authorization, file handling, UX, and even internal business processes.&lt;/p&gt;

&lt;p&gt;So I would love to hear how other developers approach this:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;At what point do you think a software product can legitimately describe itself as anonymous?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And which layer of the stack do you consider the hardest to protect against indirect identification?&lt;/p&gt;

&lt;p&gt;Especially interested in edge cases from people working with privacy, security, or other sensitive-data systems.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Victor Vieira&lt;/strong&gt; | CTO &amp;amp; Co-founder @ &lt;a href="https://sigiloprofissional.com.br" rel="noopener noreferrer"&gt;Sigilo Profissional&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;🔗 &lt;a href="https://www.linkedin.com/in/victorbvieira/" rel="noopener noreferrer"&gt;LinkedIn: Victor Vieira&lt;/a&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>privacy</category>
      <category>architecture</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Designing a Privacy-First Architecture for Sensitive Data: Zero-IP Logging, Postgres RLS, and AI Safety</title>
      <dc:creator>Victor B Vieira</dc:creator>
      <pubDate>Mon, 14 Sep 2026 18:32:41 +0000</pubDate>
      <link>https://dev.to/bidu/designing-a-privacy-first-architecture-for-sensitive-data-zero-ip-logging-postgres-rls-and-ai-3g23</link>
      <guid>https://dev.to/bidu/designing-a-privacy-first-architecture-for-sensitive-data-zero-ip-logging-postgres-rls-and-ai-3g23</guid>
      <description>&lt;p&gt;When building software that handles sensitive human data (like workplace feedback, satisfaction surveys, and compliance reports), security and privacy cannot be treated as optional features or mere legal disclaimers. They must be embedded into the core system design.&lt;/p&gt;

&lt;p&gt;At &lt;strong&gt;Sigilo Profissional&lt;/strong&gt; (&lt;a href="https://sigiloprofissional.com.br" rel="noopener noreferrer"&gt;sigiloprofissional.com.br&lt;/a&gt;), a B2B SaaS platform focused on &lt;strong&gt;Workplace Climate Surveys, eNPS, and Whistleblowing Channels&lt;/strong&gt; compliant with &lt;strong&gt;ISO 37002&lt;/strong&gt; and &lt;strong&gt;GDPR / LGPD&lt;/strong&gt;, privacy is the fundamental prerequisite for psychological safety. If employees do not trust the platform's anonymity, engagement drops to zero.&lt;/p&gt;

&lt;p&gt;Here is a breakdown of the security architecture and privacy controls we implemented in our Python (FastAPI) and PostgreSQL stack.&lt;/p&gt;




&lt;h3&gt;
  
  
  1. Dual-Layer Multi-Tenancy (Application + PostgreSQL RLS)
&lt;/h3&gt;

&lt;p&gt;To prevent cross-tenant data leakage, we enforce tenant isolation at two independent layers:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Application Layer:&lt;/strong&gt; Every query filters by &lt;code&gt;firm_id&lt;/code&gt; within SQLAlchemy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database Engine Layer:&lt;/strong&gt; PostgreSQL &lt;strong&gt;Row Level Security (RLS)&lt;/strong&gt; is enabled on all tenant tables. The application middleware sets &lt;code&gt;SET LOCAL app.firm_id&lt;/code&gt; per database session.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Enforcing PostgreSQL Row Level Security (RLS)&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;tenant_complaints&lt;/span&gt; &lt;span class="n"&gt;ENABLE&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt; &lt;span class="k"&gt;LEVEL&lt;/span&gt; &lt;span class="k"&gt;SECURITY&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;POLICY&lt;/span&gt; &lt;span class="n"&gt;firm_isolation&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;tenant_complaints&lt;/span&gt;
  &lt;span class="k"&gt;USING&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;firm_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;current_setting&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'app.firm_id'&lt;/span&gt;&lt;span class="p"&gt;)::&lt;/span&gt;&lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Why both?&lt;/em&gt; Even if a bug or missing filter occurs in application code, the database engine natively blocks access to data outside the active tenant context.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. Zero-IP Logging Policy &amp;amp; Anonymous Protocol Tokens
&lt;/h3&gt;

&lt;p&gt;To ensure complete whistleblower anonymity:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Proxy-Level IP Stripping:&lt;/strong&gt; NGINX and edge proxies strip &lt;code&gt;X-Forwarded-For&lt;/code&gt; and client IP headers. No IP addresses are saved in NGINX logs, application memory, or database records.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cryptographic Tokens:&lt;/strong&gt; Anonymous reports are tracked strictly through random protocol tokens (e.g., &lt;code&gt;SGL-2026-7X3K&lt;/code&gt;). Whistleblowers can check updates using their protocol token without ever creating an account or storing session cookies.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  3. PII Anonymization for Responsible AI Features
&lt;/h3&gt;

&lt;p&gt;We use Large Language Models (LLMs) to assist employees in structuring clear reports and to summarize qualitative eNPS sentiment for management.&lt;/p&gt;

&lt;p&gt;To prevent sensitive Personally Identifiable Information (PII) from being sent to external AI models:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Requests pass through an intermediate LiteLLM proxy layer that sanitizes names, emails, phone numbers, and location metadata before dispatching prompts.&lt;/li&gt;
&lt;li&gt;AI outputs are strictly used for guidance and categorization, never for autonomous decision-making.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  4. Immutable Audit Trails
&lt;/h3&gt;

&lt;p&gt;Compliance frameworks (such as ISO 37002) require full auditability for compliance officers. We maintain immutable audit logs for administrative actions (e.g., status updates, report triage) using cryptographic hashing, ensuring complete transparency without compromising whistleblower anonymity.&lt;/p&gt;




&lt;h3&gt;
  
  
  💬 Community Question: What Are We Missing?
&lt;/h3&gt;

&lt;p&gt;As we continue scaling our engineering stack, we are reviewing our security roadmap.&lt;/p&gt;

&lt;p&gt;We would love feedback from the Dev.to community:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What automated query auditing or key rotation strategies do you recommend for multi-tenant Postgres RLS at scale?&lt;/li&gt;
&lt;li&gt;Have you implemented additional client-side payload encryption patterns that balance searchability with absolute zero-knowledge privacy?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Looking forward to your thoughts and suggestions in the comments!&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Victor Vieira&lt;/strong&gt; | CTO &amp;amp; Co-founder @ &lt;a href="https://sigiloprofissional.com.br" rel="noopener noreferrer"&gt;Sigilo Profissional&lt;/a&gt;&lt;br&gt;
🔗 &lt;a href="https://www.linkedin.com/in/victorbvieira/" rel="noopener noreferrer"&gt;LinkedIn: Victor Vieira&lt;/a&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>privacy</category>
      <category>python</category>
      <category>postgres</category>
    </item>
  </channel>
</rss>
