<?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: Sven Schuchardt</title>
    <description>The latest articles on DEV Community by Sven Schuchardt (@sven_schuchardt_0aa51663a).</description>
    <link>https://dev.to/sven_schuchardt_0aa51663a</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3407996%2Ffaba4f7a-c083-4f98-9f6c-e8be76fc17b2.jpg</url>
      <title>DEV Community: Sven Schuchardt</title>
      <link>https://dev.to/sven_schuchardt_0aa51663a</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sven_schuchardt_0aa51663a"/>
    <language>en</language>
    <item>
      <title>How to Compute Zero Trust Effectiveness: Four Metrics That Survive a Breach</title>
      <dc:creator>Sven Schuchardt</dc:creator>
      <pubDate>Wed, 29 Apr 2026 16:29:10 +0000</pubDate>
      <link>https://dev.to/sven_schuchardt_0aa51663a/how-to-compute-zero-trust-effectiveness-four-metrics-that-survive-a-breach-1ghg</link>
      <guid>https://dev.to/sven_schuchardt_0aa51663a/how-to-compute-zero-trust-effectiveness-four-metrics-that-survive-a-breach-1ghg</guid>
      <description>&lt;p&gt;Three hops captures the realistic post-compromise reach inside a typical enterprise environment. If your IAM tooling does not expose a graph, the practical substitute is "count of distinct resources the identity has permission to read or modify within 60 minutes of session start, assuming no MFA step-up triggers."&lt;/p&gt;

&lt;h3&gt;
  
  
  What good looks like
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Privileged human identity:&lt;/strong&gt; under 50 reachable resources, zero crown-jewel data classes without step-up&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Standard human identity:&lt;/strong&gt; under 200 reachable resources, no production data without explicit grant&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Service account:&lt;/strong&gt; scoped to a single namespace or workload — under 10 reachable resources is normal, over 100 is a problem&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Report this metric per identity &lt;em&gt;class&lt;/em&gt;, not as a single org-wide average. The average hides the outliers, and the outliers are what get exploited.&lt;/p&gt;

&lt;h2&gt;
  
  
  Metric 2: Lateral-movement time-to-detect
&lt;/h2&gt;

&lt;p&gt;Lateral-movement TTD is the median time between an attacker's first action on a compromised host and the moment your SOC opens a case for the second host. Every Zero Trust programme implicitly claims to reduce this number. Most never measure it.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to compute it
&lt;/h3&gt;

&lt;p&gt;The easiest source is your EDR plus your SIEM. You need two timestamps per simulated or real lateral-movement event:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Microsoft Sentinel / KQL — adapt to Splunk / Elastic / Chronicle
let lateralEvents = SecurityAlert
  | where AlertName has_any ("Pass-the-hash", "Suspicious WMI", "RDP from unusual host", "Service account used from new asset")
  | project firstHopTime = TimeGenerated, firstHost = CompromisedEntity, alertId = SystemAlertId;
let secondHopAlerts = SecurityAlert
  | where AlertName has_any ("Suspicious lateral connection", "Credential reuse on new host")
  | project secondHopTime = TimeGenerated, secondHost = CompromisedEntity, correlationId = SystemAlertId;
lateralEvents
  | join kind=inner (secondHopAlerts) on $left.alertId == $right.correlationId
  | extend ttd_minutes = datetime_diff('minute', secondHopTime, firstHopTime)
  | summarize p50 = percentile(ttd_minutes, 50), p90 = percentile(ttd_minutes, 90)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you are not running purple-team exercises that produce real lateral-movement signal, your TTD is technically infinite — and that is the metric you should report. Quarterly attack simulations are the cheapest way to populate this number honestly.&lt;/p&gt;

&lt;h3&gt;
  
  
  What good looks like
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Mature programme:&lt;/strong&gt; p50 under 10 minutes, p90 under 30 minutes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Functional programme:&lt;/strong&gt; p50 under 60 minutes, p90 under 4 hours&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Untested programme:&lt;/strong&gt; unknown — and "unknown" is a board-grade red flag&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;a href="https://www.ibm.com/reports/data-breach" rel="noopener noreferrer"&gt;IBM 2025 Cost of a Data Breach Report&lt;/a&gt; shows breaches contained in under 200 days cost $1.14M less on average than slower ones. Lateral-movement TTD is the leading indicator that determines containment time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Metric 3: Service-account scope drift
&lt;/h2&gt;

&lt;p&gt;Human identities have managers, review cycles, and offboarding. Service accounts and machine identities have none of these by default — and they outnumber human identities &lt;a href="https://biztechbridge.com/insights/zero-trust-service-account-triage" rel="noopener noreferrer"&gt;roughly 82 to 1 in a typical enterprise&lt;/a&gt;. Scope drift measures how their permissions change quarter over quarter without explicit human approval.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to compute it
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Compare snapshot of service-account permissions across two points in time&lt;/span&gt;
&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;current_perms&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;identity_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;permission&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;granted_at&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;iam_permissions_snapshot&lt;/span&gt;
  &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;snapshot_date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;CURRENT_DATE&lt;/span&gt;
    &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;identity_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'service_account'&lt;/span&gt;
&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="n"&gt;baseline_perms&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;identity_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;permission&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;iam_permissions_snapshot&lt;/span&gt;
  &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;snapshot_date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;CURRENT_DATE&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="s1"&gt;'90 days'&lt;/span&gt;
    &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;identity_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'service_account'&lt;/span&gt;
&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="n"&gt;drift&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;identity_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;permission&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;granted_at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;CASE&lt;/span&gt;
      &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;change_approvals&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;
                   &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;identity_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;identity_id&lt;/span&gt;
                     &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;permission&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;permission&lt;/span&gt;
                     &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;approved_at&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;granted_at&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="s1"&gt;'7 days'&lt;/span&gt;
                                            &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;granted_at&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="s1"&gt;'approved'&lt;/span&gt;
      &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="s1"&gt;'unapproved'&lt;/span&gt;
    &lt;span class="k"&gt;END&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;approval_status&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;current_perms&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;
  &lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;baseline_perms&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;identity_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;identity_id&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;permission&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;permission&lt;/span&gt;
  &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;permission&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;  &lt;span class="c1"&gt;-- new permission since baseline&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;approval_status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;new_perms&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;drift&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;approval_status&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The number you report is the count of unapproved new permissions per quarter, plus the top ten service accounts that gained the most scope.&lt;/p&gt;

&lt;h3&gt;
  
  
  What good looks like
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Quarterly unapproved drift: &lt;strong&gt;under 5%&lt;/strong&gt; of total permission changes&lt;/li&gt;
&lt;li&gt;Zero service accounts in the top-ten that touch crown-jewel data classes&lt;/li&gt;
&lt;li&gt;Every "approved" entry traces to a ticket or change record&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Anything above 15% unapproved drift means your IAM hygiene has decayed, regardless of how many controls you have deployed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Metric 4: Exception age
&lt;/h2&gt;

&lt;p&gt;Every Zero Trust programme accumulates exceptions: the legacy app that cannot do MFA, the build server that needs a static credential, the compliance carve-out for a specific business unit. These are unavoidable. What is not unavoidable is letting them age.&lt;/p&gt;

&lt;p&gt;Exception age is the median number of days an active policy exception has been in production.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to compute it
&lt;/h3&gt;

&lt;p&gt;The exception register is your source of truth. It needs three fields per entry: opened date, business owner, and committed remediation date. The query is trivial:&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;SELECT&lt;/span&gt;
  &lt;span class="n"&gt;exception_category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;active_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;PERCENTILE_CONT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;WITHIN&lt;/span&gt; &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;DATE_PART&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'day'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;opened_at&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;p50_age_days&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;PERCENTILE_CONT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;WITHIN&lt;/span&gt; &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;DATE_PART&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'day'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;opened_at&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;p90_age_days&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;FILTER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;remediation_committed_at&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;overdue_count&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;zt_exceptions&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'active'&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;exception_category&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;p90_age_days&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you do not have an exception register, that is the metric you should report: "number of policy exceptions tracked: zero — and we know that is wrong."&lt;/p&gt;

&lt;h3&gt;
  
  
  What good looks like
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Median exception age: &lt;strong&gt;under 90 days&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;p90 exception age: under 180 days&lt;/li&gt;
&lt;li&gt;Overdue (past committed remediation date): zero&lt;/li&gt;
&lt;li&gt;Every entry has a named human owner, not a team distribution list&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The most uncomfortable version of this metric is the &lt;em&gt;expired&lt;/em&gt; exception count — exceptions whose stated business justification is no longer true but which remain in production because nobody owns the cleanup. Surface that number deliberately.&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting the four metrics together
&lt;/h2&gt;

&lt;p&gt;The four metrics tell a coherent story when reported together:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Pattern&lt;/th&gt;
&lt;th&gt;Diagnosis&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Blast radius high, TTD low&lt;/td&gt;
&lt;td&gt;Detection is fast but identity scope is too broad. Tighten least-privilege.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Blast radius low, TTD high&lt;/td&gt;
&lt;td&gt;Containment is structurally sound but observability is weak. Invest in EDR + UEBA.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Drift high, exception age low&lt;/td&gt;
&lt;td&gt;New permissions outpace cleanup. Tighten IAM change control.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Drift low, exception age high&lt;/td&gt;
&lt;td&gt;Stable IAM, but the exception register is a parking lot. Force re-justification quarterly.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;All four red&lt;/td&gt;
&lt;td&gt;The programme is doing activity work. Stop deploying and start measuring.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Notice none of these four metrics are coverage percentages. None of them go up just because you bought a tool. Every one of them requires a human to make a decision about whether the current number is acceptable — which is the entire point.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to put on the board slide
&lt;/h2&gt;

&lt;p&gt;Translate the four metrics into the only sentence the board cares about:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"If an attacker compromises one identity tomorrow, the blast radius is &lt;strong&gt;N systems&lt;/strong&gt; containing &lt;strong&gt;C crown-jewel data classes&lt;/strong&gt;, our median time to detect a second hop is &lt;strong&gt;T minutes&lt;/strong&gt;, and we currently carry &lt;strong&gt;E policy exceptions&lt;/strong&gt; with a median age of &lt;strong&gt;A days&lt;/strong&gt;."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That single sentence is the dashboard. Everything else — the rings, the percentages, the heatmaps — is supporting evidence. If you cannot answer it from your current tooling in under five minutes, the gap is not a tooling gap. It is a measurement-discipline gap, and no amount of additional Zero Trust deployment will close it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;Zero Trust is a security discipline that lives or dies by what you measure. Activity metrics make the programme look healthy in year one and vanish in year two when the breach happens anyway. Effectiveness metrics are uglier, harder to compute, and they survive contact with reality.&lt;/p&gt;

&lt;p&gt;Pick the four. Compute them honestly. Report the awkward numbers alongside the impressive ones. The CISOs getting real budget in 2026 are the ones whose dashboards make leadership uncomfortable on purpose — because uncomfortable numbers are the only ones a board can act on.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://biztechbridge.com/insights/computing-zero-trust-effectiveness-metrics" rel="noopener noreferrer"&gt;biztechbridge.com&lt;/a&gt;. For the strategic framing of these metrics in board reporting, see &lt;a href="https://biztechbridge.com/insights/zero-trust-measurement-dashboard" rel="noopener noreferrer"&gt;Measuring Zero Trust: The Dashboard Your Board Wants to See&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>zerotrust</category>
      <category>security</category>
      <category>devops</category>
      <category>sre</category>
    </item>
    <item>
      <title>How to Measure Voluntary Adoption of Your Internal Developer Platform</title>
      <dc:creator>Sven Schuchardt</dc:creator>
      <pubDate>Mon, 27 Apr 2026 06:44:45 +0000</pubDate>
      <link>https://dev.to/sven_schuchardt_0aa51663a/how-to-measure-voluntary-adoption-of-your-internal-developer-platform-22dm</link>
      <guid>https://dev.to/sven_schuchardt_0aa51663a/how-to-measure-voluntary-adoption-of-your-internal-developer-platform-22dm</guid>
      <description>&lt;p&gt;If your platform team only tracks "services onboarded" or "deployments per week," you are measuring compliance, not value. The single metric that predicts whether your Internal Developer Platform (IDP) will deliver return on investment is &lt;strong&gt;voluntary adoption rate of the golden path&lt;/strong&gt; — the percentage of new work that chooses the paved road when an off-road option still exists.&lt;/p&gt;

&lt;p&gt;This article shows three ways to measure it concretely, using Backstage, GitHub, Argo CD, and Prometheus. It is the technical companion to the broader &lt;a href="https://dev.to/insights/platform-engineering-business-impact"&gt;Platform Engineering business case&lt;/a&gt; — that piece argues &lt;em&gt;why&lt;/em&gt; voluntary adoption matters; this one shows &lt;em&gt;how&lt;/em&gt; to compute it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why activity metrics mislead platform teams
&lt;/h2&gt;

&lt;p&gt;Most platform dashboards report on activity:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Number of templates run&lt;/li&gt;
&lt;li&gt;Services in the catalog&lt;/li&gt;
&lt;li&gt;Pipeline executions per day&lt;/li&gt;
&lt;li&gt;Onboarded teams&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These numbers go up regardless of whether developers actually like the platform. A mandated platform produces the same activity graph as a beloved one — until attrition spikes and the post-mortem reveals that nobody was self-serving anything; they were filing tickets to comply with a policy.&lt;/p&gt;

&lt;p&gt;Voluntary adoption asks a harder, more honest question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;When developers had a real choice, what did they pick?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If the answer trends toward the golden path over time, the platform is genuinely removing friction. If it trends away — or if there was never an off-road option to reject — you do not have signal. You have theatre.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three measurement layers
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;What it measures&lt;/th&gt;
&lt;th&gt;Source&lt;/th&gt;
&lt;th&gt;Cadence&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1. Path-of-least-resistance rate&lt;/td&gt;
&lt;td&gt;% of new services created via the golden-path template vs. ad-hoc&lt;/td&gt;
&lt;td&gt;Backstage Scaffolder + GitHub repo creation events&lt;/td&gt;
&lt;td&gt;Weekly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2. Stickiness rate&lt;/td&gt;
&lt;td&gt;% of services still on the golden path 90 days after creation&lt;/td&gt;
&lt;td&gt;Catalog metadata + drift detection&lt;/td&gt;
&lt;td&gt;Monthly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3. Re-entry rate&lt;/td&gt;
&lt;td&gt;% of legacy services voluntarily migrating onto the platform without a mandate&lt;/td&gt;
&lt;td&gt;Catalog + GitOps PR activity&lt;/td&gt;
&lt;td&gt;Quarterly&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;You want all three trending up. Activity metrics — deploys, builds, pipeline runs — are downstream of these and noisy on their own.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 1: path-of-least-resistance rate
&lt;/h2&gt;

&lt;p&gt;The Backstage Scaffolder logs every template execution. Cross-reference that against all new repositories created in your GitHub organisation during the same window. The ratio between the two is your weekly voluntary adoption rate for new work.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Pseudocode against your data warehouse&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="n"&gt;date_trunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'week'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;week&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;FILTER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'backstage_scaffolder'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;golden_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;FILTER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'manual_repo'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;          &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;off_road&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;FILTER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'backstage_scaffolder'&lt;/span&gt;&lt;span class="p"&gt;)::&lt;/span&gt;&lt;span class="nb"&gt;numeric&lt;/span&gt;
    &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="k"&gt;NULLIF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;voluntary_adoption_pct&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;new_services&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Healthy trend: 60% → 80%+ over six months. Stalled below 40%? Your golden path is not actually the easiest path. Find out why before adding more features. The most common culprits are template rigidity, slow scaffold time, and missing escape hatches for legitimate edge cases.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 2: stickiness rate
&lt;/h2&gt;

&lt;p&gt;Services that get scaffolded from a template often drift off the paved road within weeks. Teams bypass the CI pipeline, stop publishing to the catalog, hand-edit the Helm chart instead of using the platform-provided one. Stickiness measures how many services are still genuinely platform-managed 90 days after creation.&lt;/p&gt;

&lt;p&gt;Detect drift with a periodic reconciliation job and an annotation contract:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Argo CD Application — every service should carry these annotations&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;argoproj.io/v1alpha1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Application&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;annotations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;platform.bz/golden-path-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2.4"&lt;/span&gt;
    &lt;span class="na"&gt;platform.bz/last-reconciled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2026-04-24T08:12:00Z"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then ask: of services scaffolded 90 days ago, how many still carry a current &lt;code&gt;golden-path-version&lt;/code&gt; annotation and pass policy admission without exemption?&lt;/p&gt;

&lt;p&gt;That ratio is your stickiness rate. Sub-70% means your golden path is too rigid — developers leave because the road does not bend where it needs to. The fix is rarely more enforcement; it is usually more flexibility within the paved road, so teams do not need to step off it for legitimate variations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 3: re-entry rate
&lt;/h2&gt;

&lt;p&gt;The hardest test, and the most informative one. Of services that existed &lt;em&gt;before&lt;/em&gt; the platform, how many have voluntarily migrated onto it in the last quarter — without a top-down mandate forcing the move?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Prometheus — services emitting platform-managed telemetry that were not managed 90 days ago
sum(
  count by (service) (
    platform_managed_service_info{managed="true"}
    unless on(service)
    platform_managed_service_info{managed="true"} offset 90d
  )
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the return-on-investment signal a CFO can defend in a budget review. A voluntary re-entry means the platform earned the developer's trust enough that they ported a working production service onto it — at their own initiative, on their own schedule, against the inertia of "if it works, do not touch it."&lt;/p&gt;

&lt;p&gt;If you are seeing zero quarterly re-entries despite a healthy Layer 1 number, the migration cost is too high. Build a one-command migration template for the most common service shape and watch the rate move.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reading the three numbers together
&lt;/h2&gt;

&lt;p&gt;Each layer in isolation lies. Read them as a system:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Pattern&lt;/th&gt;
&lt;th&gt;Diagnosis&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;L1 high, L2 low&lt;/td&gt;
&lt;td&gt;Templates are good; the runtime experience drifts. Invest in policy automation and reconciliation.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;L1 low, L2 high&lt;/td&gt;
&lt;td&gt;The few who adopt love it; discoverability is broken. Invest in developer experience and template marketing, not features.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;L1 high, L3 zero&lt;/td&gt;
&lt;td&gt;New work uses the platform; old work will not migrate. Build a migration template.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;All three flat&lt;/td&gt;
&lt;td&gt;You probably have a mandate hiding the truth. Remove it for one team and remeasure honestly.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The one anti-pattern to avoid
&lt;/h2&gt;

&lt;p&gt;Do not report voluntary adoption as a single headline percentage to leadership without the three layers underneath. A 92% headline with 30% stickiness is materially worse than a 60% headline with 85% stickiness — the first is a compliance illusion that will collapse when the mandate lifts; the second is a working product with room to grow.&lt;/p&gt;

&lt;p&gt;Platform engineering is a product discipline. Measure it the way a product team measures retention, not the way an ops team measures uptime.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;The mandate question is not ideological — it is a measurement question. If you can prove voluntary adoption is rising, you do not need a mandate. If you cannot measure it, no mandate will save the platform when budget season arrives and the CFO asks what changed.&lt;/p&gt;

&lt;p&gt;What is your platform's voluntary adoption rate right now? And, more importantly: does anyone on your team know how to compute it?&lt;/p&gt;

&lt;p&gt;For the broader strategic case behind this metric — Forrester ROI numbers, developer attrition costs, and why mandated adoption destroys returns — see the &lt;a href="https://biztechbridge.com/insights/platform-engineering-business-impact" rel="noopener noreferrer"&gt;Platform Engineering business case&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;For the reference architecture and tooling choices that make these measurements possible, see the &lt;a href="https://biztechbridge.com/insights/platform-engineering-technology" rel="noopener noreferrer"&gt;Platform Engineering technology deep dive&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>backstage</category>
      <category>devops</category>
      <category>productivity</category>
      <category>devex</category>
    </item>
    <item>
      <title>Zero Trust is Not a Security Tool — It’s a Software Design Problem</title>
      <dc:creator>Sven Schuchardt</dc:creator>
      <pubDate>Sun, 19 Apr 2026 17:47:27 +0000</pubDate>
      <link>https://dev.to/sven_schuchardt_0aa51663a/zero-trust-is-not-a-security-tool-its-a-software-design-problem-25mh</link>
      <guid>https://dev.to/sven_schuchardt_0aa51663a/zero-trust-is-not-a-security-tool-its-a-software-design-problem-25mh</guid>
      <description>&lt;p&gt;Most Zero Trust discussions focus on tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ZTNA
&lt;/li&gt;
&lt;li&gt;micro-segmentation
&lt;/li&gt;
&lt;li&gt;identity providers
&lt;/li&gt;
&lt;li&gt;SASE platforms
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s useful.&lt;/p&gt;

&lt;p&gt;But it misses the point.&lt;/p&gt;




&lt;h2&gt;
  
  
  The real problem
&lt;/h2&gt;

&lt;p&gt;Modern systems don’t behave like the architectures Zero Trust was originally designed to fix.&lt;/p&gt;

&lt;p&gt;Today, most traffic is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;encrypted
&lt;/li&gt;
&lt;li&gt;service-to-service
&lt;/li&gt;
&lt;li&gt;happening inside your system
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Which means:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The “trusted internal network” assumption is already broken.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And yet, many implementations still rely on it.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Zero Trust actually requires
&lt;/h2&gt;

&lt;p&gt;Zero Trust Architecture is often described as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Never trust, always verify”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That sounds simple.&lt;/p&gt;

&lt;p&gt;But the technical implication is not.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;every request must be authenticated&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;every request must be authorized&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;continuously, not just at login&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s not a network change.&lt;/p&gt;

&lt;p&gt;That’s a &lt;strong&gt;system design change&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where most implementations break
&lt;/h2&gt;

&lt;p&gt;In practice, the failure point is rarely the edge.&lt;/p&gt;

&lt;p&gt;It’s inside the system:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;internal APIs don’t enforce authentication
&lt;/li&gt;
&lt;li&gt;service-to-service calls rely on network trust
&lt;/li&gt;
&lt;li&gt;authorization logic is inconsistent
&lt;/li&gt;
&lt;li&gt;policies are not version-controlled
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In other words:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;We removed the perimeter… but kept the assumptions.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The shift most teams underestimate
&lt;/h2&gt;

&lt;p&gt;To make Zero Trust work, three things need to change:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Identity is no longer just for users
&lt;/h3&gt;

&lt;p&gt;Workloads need identity too.&lt;/p&gt;

&lt;p&gt;Patterns like SPIFFE/SPIRE provide:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;short-lived identities
&lt;/li&gt;
&lt;li&gt;tied to workload, not IP
&lt;/li&gt;
&lt;li&gt;automatically rotated
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without this, mTLS becomes operationally painful or inconsistent.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. Authorization becomes per-request
&lt;/h3&gt;

&lt;p&gt;Checking access at login is not enough.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;request-level validation
&lt;/li&gt;
&lt;li&gt;resource-level authorization
&lt;/li&gt;
&lt;li&gt;context-aware decisions
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is why patterns like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;API gateways
&lt;/li&gt;
&lt;li&gt;service mesh policies
&lt;/li&gt;
&lt;li&gt;policy-as-code (e.g. OPA)
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;become critical.&lt;/p&gt;




&lt;h3&gt;
  
  
  3. Security moves into the delivery pipeline
&lt;/h3&gt;

&lt;p&gt;If policies only exist at runtime, you are already too late.&lt;/p&gt;

&lt;p&gt;Teams that push Zero Trust controls into CI/CD:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;catch violations earlier
&lt;/li&gt;
&lt;li&gt;reduce production incidents significantly
&lt;/li&gt;
&lt;li&gt;avoid breaking changes at enforcement time
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The uncomfortable takeaway
&lt;/h2&gt;

&lt;p&gt;Zero Trust is not something you “implement” with a tool.&lt;/p&gt;

&lt;p&gt;It’s something you &lt;strong&gt;design into your system&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If your architecture still assumes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;trusted internal networks
&lt;/li&gt;
&lt;li&gt;static roles
&lt;/li&gt;
&lt;li&gt;one-time authentication
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;then adding Zero Trust tooling will mostly add complexity.&lt;/p&gt;

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




&lt;h2&gt;
  
  
  What to do instead
&lt;/h2&gt;

&lt;p&gt;Start with a different question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Where does implicit trust still exist in our system?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You’ll usually find it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;between services
&lt;/li&gt;
&lt;li&gt;in internal APIs
&lt;/li&gt;
&lt;li&gt;in long-lived credentials
&lt;/li&gt;
&lt;li&gt;in developer workflows
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s your real attack surface.&lt;/p&gt;




&lt;h2&gt;
  
  
  If you want to go deeper
&lt;/h2&gt;

&lt;p&gt;I wrote a full breakdown of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;how NIST SP 800-207 maps to real systems
&lt;/li&gt;
&lt;li&gt;mTLS and workload identity
&lt;/li&gt;
&lt;li&gt;SPIFFE/SPIRE, OPA, and secrets management
&lt;/li&gt;
&lt;li&gt;what actually changes for development teams
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👉 &lt;a href="https://biztechbridge.com/insights/zero-trust-architecture-technology" rel="noopener noreferrer"&gt;https://biztechbridge.com/insights/zero-trust-architecture-technology&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Final thought
&lt;/h2&gt;

&lt;p&gt;Zero Trust is often sold as a security upgrade.&lt;/p&gt;

&lt;p&gt;In reality, it’s closer to a &lt;strong&gt;paradigm shift in how systems make decisions&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;And that shift is still underestimated in most implementations.&lt;/p&gt;

</description>
      <category>security</category>
      <category>cloud</category>
      <category>devops</category>
      <category>platformengineering</category>
    </item>
    <item>
      <title>JWT Explained: What's Actually Inside a JSON Web Token</title>
      <dc:creator>Sven Schuchardt</dc:creator>
      <pubDate>Fri, 10 Apr 2026 17:09:14 +0000</pubDate>
      <link>https://dev.to/sven_schuchardt_0aa51663a/jwt-explained-whats-actually-inside-a-json-web-token-3o0d</link>
      <guid>https://dev.to/sven_schuchardt_0aa51663a/jwt-explained-whats-actually-inside-a-json-web-token-3o0d</guid>
      <description>&lt;p&gt;You're integrating an API and you get back a token that starts with &lt;code&gt;eyJ&lt;/code&gt;. You paste it somewhere and suddenly you can read your user's email address, their user ID, and an expiry timestamp. No decryption key needed. How? And if anyone can read it, is that secure?&lt;/p&gt;

&lt;p&gt;JWTs look encrypted but aren't. That tension — readable but trustworthy — is the whole point. Understanding it takes about five minutes, and it changes how you think about auth tokens for good.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is a JWT?
&lt;/h2&gt;

&lt;p&gt;A JSON Web Token is three base64url-encoded strings joined by dots:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;header.payload.signature
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Take a real minimal example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyXzEyMyIsImVtYWlsIjoidXNlckBleGFtcGxlLmNvbSIsImV4cCI6MTcxMjcwMDAwMH0.signature
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each part can be decoded in a browser console right now — no keys, no secrets, no libraries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Manually decode the payload (works in any browser console)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyXzEyMyIsImVtYWlsIjoidXNlckBleGFtcGxlLmNvbSIsImV4cCI6MTcxMjcwMDAwMH0.signature&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;atob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/-/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;+&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/_/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// { sub: "user_123", email: "user@example.com", exp: 1712700000 }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Part 1 — Header:&lt;/strong&gt; Contains &lt;code&gt;alg&lt;/code&gt; (the signing algorithm, e.g. &lt;code&gt;HS256&lt;/code&gt; or &lt;code&gt;RS256&lt;/code&gt;) and &lt;code&gt;typ&lt;/code&gt; (always &lt;code&gt;"JWT"&lt;/code&gt;). Decoded, it looks like &lt;code&gt;{ "alg": "HS256", "typ": "JWT" }&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Part 2 — Payload:&lt;/strong&gt; The claims — data statements about the user or token. These are just JSON key-value pairs. Standard claim names are short by convention (&lt;code&gt;sub&lt;/code&gt;, &lt;code&gt;exp&lt;/code&gt;, &lt;code&gt;iat&lt;/code&gt;) but the values can be anything. Custom claims like &lt;code&gt;role&lt;/code&gt; or &lt;code&gt;org_id&lt;/code&gt; are perfectly valid.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Part 3 — Signature:&lt;/strong&gt; An HMAC or RSA hash of &lt;code&gt;base64url(header) + "." + base64url(payload)&lt;/code&gt;, computed using a secret known only to the issuer. This is the part that makes the token trustworthy — not readability, but tamper-evidence.&lt;/p&gt;

&lt;p&gt;The key insight: &lt;strong&gt;JWTs are signed, not encrypted.&lt;/strong&gt; The payload is readable by anyone who has the token. Only the issuer can produce a valid signature.&lt;/p&gt;

&lt;h2&gt;
  
  
  Standard Claims
&lt;/h2&gt;

&lt;p&gt;The JWT spec defines a set of registered claim names. You don't have to use them, but you should — they're understood by every JWT library.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Claim&lt;/th&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sub&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Subject&lt;/td&gt;
&lt;td&gt;User identifier (user ID, email, etc.)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;iss&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Issuer&lt;/td&gt;
&lt;td&gt;Who created the token (your auth server)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;aud&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Audience&lt;/td&gt;
&lt;td&gt;Who the token is intended for&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;exp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Expiration&lt;/td&gt;
&lt;td&gt;Unix timestamp when token expires&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;iat&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Issued at&lt;/td&gt;
&lt;td&gt;Unix timestamp when token was created&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;nbf&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Not before&lt;/td&gt;
&lt;td&gt;Token not valid before this timestamp&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;jti&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;JWT ID&lt;/td&gt;
&lt;td&gt;Unique token identifier (for revocation)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;exp&lt;/code&gt; and &lt;code&gt;iat&lt;/code&gt; are Unix timestamps — seconds since January 1 1970. An &lt;code&gt;exp&lt;/code&gt; of &lt;code&gt;1712700000&lt;/code&gt; means the token expires at a specific calendar date and time. Paste any JWT into our &lt;a href="https://biztechbridge.com/tools/jwt-decoder" rel="noopener noreferrer"&gt;JWT decoder tool&lt;/a&gt; to see the header, payload, and claims broken out — without sending the token to any server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it Works — and Where it Doesn't
&lt;/h2&gt;

&lt;p&gt;The signature prevents tampering. If you change even one byte of the payload, the signature becomes invalid. The server verifies by re-computing the signature with its own secret and comparing. If they match, the payload hasn't been touched since the issuer signed it.&lt;/p&gt;

&lt;p&gt;But the payload is public. Everyone who holds the token can read it. That means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Never put passwords, credit card numbers, or API secrets in a JWT payload.&lt;/li&gt;
&lt;li&gt;Never put anything you wouldn't put in a cookie you're okay with users reading.&lt;/li&gt;
&lt;li&gt;Session tokens and user IDs are fine. Sensitive personal data should stay server-side.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A common mistake in early JWT implementations: accepting a token as proof of identity without verifying the signature. A token that decodes to &lt;code&gt;{ "sub": "admin" }&lt;/code&gt; proves nothing on its own — the signature is what proves it came from your auth server. Always verify server-side before trusting any claim.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://jwt.io/introduction" rel="noopener noreferrer"&gt;JWT.io Introduction&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc7519" rel="noopener noreferrer"&gt;RFC 7519 — JSON Web Token&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/sven-divico/biztechbridge-tools/tree/main/tools/jwt-decoder" rel="noopener noreferrer"&gt;biztechbridge-tools/jwt-decoder&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>jwt</category>
      <category>security</category>
      <category>webdev</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Unix Timestamps Explained: What Every Developer Should Know</title>
      <dc:creator>Sven Schuchardt</dc:creator>
      <pubDate>Thu, 09 Apr 2026 19:30:59 +0000</pubDate>
      <link>https://dev.to/sven_schuchardt_0aa51663a/unix-timestamps-explained-what-every-developer-should-know-890</link>
      <guid>https://dev.to/sven_schuchardt_0aa51663a/unix-timestamps-explained-what-every-developer-should-know-890</guid>
      <description>&lt;p&gt;You're tailing a log file and you see this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[1712700000] ERROR: connection timeout
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What is &lt;code&gt;1712700000&lt;/code&gt;? Is it a bug? A timestamp? A version number? If you've ever stared at a number like that and felt unsure, this article is for you.&lt;/p&gt;

&lt;p&gt;By the end you'll know exactly what Unix timestamps are, why every serious API uses them, and how to convert them instantly without memorising any formula.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Is a Unix Timestamp?
&lt;/h2&gt;

&lt;p&gt;A Unix timestamp (also called an epoch timestamp) is simply the number of &lt;strong&gt;seconds that have elapsed since January 1, 1970, 00:00:00 UTC&lt;/strong&gt; — a moment arbitrarily chosen as the starting point of computer time, known as the Unix epoch.&lt;/p&gt;

&lt;p&gt;That's it. No timezones, no daylight saving adjustments, no locale quirks. Just a single integer that means the same thing on every machine on the planet.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;1712700000&lt;/code&gt; translates to &lt;strong&gt;April 9, 2024, 20:00:00 UTC&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why 1970?
&lt;/h3&gt;

&lt;p&gt;The Unix operating system was developed in the early 1970s. The designers needed a fixed reference point that was recent enough to keep numbers small but old enough to cover any historical dates they cared about. January 1, 1970 was a clean, round choice that stuck — and 50+ years later the entire industry still uses it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Seconds vs Milliseconds — the most common gotcha
&lt;/h3&gt;

&lt;p&gt;Two variants exist and they will burn you if you mix them up:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Format&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;th&gt;Used by&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Seconds (Unix time)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1712700000&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Most Unix APIs, databases, server logs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Milliseconds&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1712700000000&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;JavaScript's &lt;code&gt;Date.now()&lt;/code&gt;, Java, many web APIs&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A 13-digit number is almost always milliseconds. A 10-digit number is almost always seconds. When in doubt, check the API docs.&lt;/p&gt;

&lt;h3&gt;
  
  
  The 2038 problem (a quick aside)
&lt;/h3&gt;

&lt;p&gt;32-bit systems store Unix timestamps as a signed integer, which maxes out on &lt;strong&gt;January 19, 2038&lt;/strong&gt;. Most modern systems use 64-bit integers (which won't overflow until the year 292 billion), but if you're working with embedded systems or legacy C code, it's worth knowing.&lt;/p&gt;




&lt;h2&gt;
  
  
  Converting Epoch to a Human-Readable Date in JavaScript
&lt;/h2&gt;

&lt;p&gt;No library needed. JavaScript's built-in &lt;code&gt;Date&lt;/code&gt; handles it in one line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// If your timestamp is in seconds, multiply by 1000 first&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1712700000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ts&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="c1"&gt;// → "2024-04-09T20:00:00.000Z"&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLocaleString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en-US&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;timeZone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;America/New_York&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;
&lt;span class="c1"&gt;// → "4/9/2024, 4:00:00 PM"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Going the other way — current time as epoch — is even simpler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nowInSeconds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nowInSeconds&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// → 1712700000 (approximately)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  A debug helper worth bookmarking
&lt;/h3&gt;

&lt;p&gt;When you're debugging logs, paste this into your browser console:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fromEpoch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Handle both seconds and milliseconds automatically&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ms&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ts&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;e12&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;ts&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ts&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nf"&gt;fromEpoch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1712700000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;    &lt;span class="c1"&gt;// → "2024-04-09T20:00:00.000Z"&lt;/span&gt;
&lt;span class="nf"&gt;fromEpoch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1712700000000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// → "2024-04-09T20:00:00.000Z"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Try It Without Writing Any Code
&lt;/h2&gt;

&lt;p&gt;If you just need a quick conversion — during debugging, code review, or reading API docs — paste any timestamp into our free &lt;a href="https://biztechbridge.com/tools/epoch-converter" rel="noopener noreferrer"&gt;epoch converter tool&lt;/a&gt;. It handles both seconds and milliseconds, supports timezone selection, and works entirely in your browser. No data is ever sent to a server.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why APIs Use Unix Time (and Not ISO Strings)
&lt;/h2&gt;

&lt;p&gt;You'll notice that Stripe, GitHub, Slack, and virtually every major API returns timestamps as integers, not formatted date strings. There are good reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No timezone ambiguity&lt;/strong&gt; — &lt;code&gt;1712700000&lt;/code&gt; is the same moment everywhere; &lt;code&gt;"2024-04-09 20:00:00"&lt;/code&gt; is meaningless without a timezone&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Easy arithmetic&lt;/strong&gt; — want to check if something happened in the last 24 hours? &lt;code&gt;now - ts &amp;lt; 86400&lt;/code&gt;. Try doing that with ISO strings.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compact&lt;/strong&gt; — 10 digits vs 24 characters for ISO 8601&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No parsing edge cases&lt;/strong&gt; — no locale formats, no AM/PM, no separator variations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The tradeoff is readability — which is exactly why tools and debug helpers exist.&lt;/p&gt;




&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A Unix timestamp is seconds since January 1, 1970 UTC&lt;/li&gt;
&lt;li&gt;10 digits = seconds, 13 digits = milliseconds — multiply by 1000 before passing to &lt;code&gt;new Date()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;APIs use Unix time because it's unambiguous, compact, and arithmetically convenient&lt;/li&gt;
&lt;li&gt;Convert any timestamp instantly with our &lt;a href="https://biztechbridge.com/tools/epoch-converter" rel="noopener noreferrer"&gt;epoch converter tool&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Further Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/now" rel="noopener noreferrer"&gt;MDN: Date.now()&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Unix_time" rel="noopener noreferrer"&gt;Unix time — Wikipedia&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;The full React source for the converter is open on GitHub: &lt;a href="https://github.com/sven-divico/biztechbridge-tools/tree/main/tools/epoch-converter" rel="noopener noreferrer"&gt;biztechbridge-tools/epoch-converter&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>beginners</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
