<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Salaheldinaz</title><link>https://salaheldinaz.com/</link><description>OSINT · Intelligence · Cyber Security · Automation · Technologist · Investigator</description><language>en-US</language><lastBuildDate>Sun, 18 Oct 2026 07:59:35 +0000</lastBuildDate><atom:link href="https://salaheldinaz.com/tags/opsec/" rel="self" type="application/rss+xml"/><item><title>OPSEC for automation</title><link>https://salaheldinaz.com/blog/opsec-for-automation/</link><guid isPermaLink="true">https://salaheldinaz.com/blog/opsec-for-automation/</guid><pubDate>Sun, 18 Oct 2026 00:00:00 +0000</pubDate><description>You have automated the act of looking, so you have automated the act of being seen. What a server actually learns from a monitor that runs for a year, and why the fix is usually to slow down rather than to hide better.</description><category>osint</category><category>automation</category><category>opsec</category><category>methodology</category><content:encoded><![CDATA[<span class="hero-label not-prose">The Analyst&#39;s Stack · Foundations</span>

<p>Something happens about 3 weeks into running your first monitor. You stop thinking
about it. It runs, it messages you when something appears, and you forget it exists,
while it goes on knocking on somebody&rsquo;s server every 15 minutes for the next year.</p>
<p>That is the whole of this post in one sentence:</p>
<div class="callout callout-warning not-prose"><p class="callout-title">The line this post is built on</p><div class="callout-body"><strong>You have automated the act of looking, so you have automated the act of being
seen.</strong></div>
</div>

<p>Checking a source by hand has an OPSEC property <em>(operational security — the
habit of not handing an adversary information about what you are doing)</em> that
nobody designs in and everybody benefits from. It is irregular. You check twice
on Tuesday, not at all on Wednesday, 4 times on Thursday because something was
happening. The volume is small and the rhythm is human.</p>
<p>Automation removes all of that at once. What replaces it is regular, consistent and
indefinitely persistent — the combination that makes a thing easy to recognise and
easy to find again a year later in a log file.</p>
<p>That is not an argument against automating. It is an argument for knowing what you
emit, because most analysts I talk to have never thought about the outbound half.</p>
<h2 id="what-a-server-actually-sees">What a server actually sees</h2>
<p>Throw away the idea that you are identified by your IP address. Your IP is one field
in a larger picture, and it is the field that changes most easily, which is why people
fixate on it.</p>
<p>Here is what actually arrives at the other end when your script makes one request.</p>
<figure class="diagram not-prose">
  <img class="diagram-light" src="https://salaheldinaz.com/blog/opsec-for-automation/images/f5-fingerprint_light.svg" width="800" height="404" alt="Five separate signals — network, headers, TLS shape, timing, and session — feed into a vertical bus that converges on a single highlighted box labelled &#39;One client, the combination&#39;, which in turn leads to &#39;A profile that outlives the run&#39;." loading="lazy" decoding="async">
  <img class="diagram-dark" src="https://salaheldinaz.com/blog/opsec-for-automation/images/f5-fingerprint_dark.svg" width="800" height="404" alt="Five separate signals — network, headers, TLS shape, timing, and session — feed into a vertical bus that converges on a single highlighted box labelled &#39;One client, the combination&#39;, which in turn leads to &#39;A profile that outlives the run&#39;." loading="lazy" decoding="async">
  <figcaption class="post-caption"><strong>What the far end correlates</strong> — 5 of the 6 signals below; the sixth, what you never request, is the one people miss. No single row identifies you. The set of them, taken together and observed repeatedly, does.</figcaption>
</figure>

<p><strong>The network you sit on.</strong> Not the address alone, but what kind of address it is:
residential, university, mobile carrier, or data centre. Which operator owns it
<em>(your IP belongs to an ASN — a numbered block of addresses assigned to one
organisation, and it is public which organisation that is)</em>. A request from a cloud
region is not anonymous. It is labelled &ldquo;automation&rdquo; before it says anything else.</p>
<p><strong>The exact set of headers you send, and the order you send them in.</strong> A request
carries a handful of small metadata lines: what you claim to be, what content types
you accept, what languages you prefer, what compression you understand. Real browsers
send a particular set, with particular values, in a particular sequence, and differ
from each other in stable, well-documented ways. Most programming libraries send a
shorter set in a different order. The order alone often separates &ldquo;a browser&rdquo; from
&ldquo;a program&rdquo; before anyone reads the values.</p>
<p><strong>The shape of your encrypted handshake.</strong> Before any of that is sent, your client
and the server negotiate an encrypted connection <em>(TLS — the layer that puts the &ldquo;s&rdquo;
in https)</em>. That negotiation happens in the clear and it is chatty: which encryption
methods you offer, in what order, which optional extensions you support, which you
leave out. The opening message is stable per client library and differs between
libraries and browsers. You cannot edit it from inside your script, because the layer
underneath produces it.</p>
<p><strong>What you do with cookies and redirects.</strong> A browser keeps the small identifiers a
site hands it and sends them back next time, follows a redirect, and picks up whatever
the new page sets. A script written by someone not thinking about it does neither, or
does one and not the other, and behaves like nothing that has ever had a human in
front of it.</p>
<p><strong>What you do not request.</strong> This is the one people miss entirely. Loading a page in
a browser produces dozens of follow-up requests: stylesheets, scripts, fonts, images,
an analytics call or two. A scraper asks for the document and leaves. From the
server&rsquo;s side that is a distinctive silence — the page was served and nothing was
ever drawn.</p>
<h2 id="a-fingerprint-is-a-combination">A fingerprint is a combination</h2>
<p>This is the part worth internalising, because it explains why so much amateur
counter-fingerprinting achieves nothing.</p>
<p>None of the signals above identifies you on its own. Millions of people share your
network operator. Thousands of tools send the same headers. The identification comes
from the <strong>joint</strong> pattern: this network, plus this header set in this order, plus
this handshake shape, plus this timing, plus this cookie behaviour, arriving together
and doing so again tomorrow.</p>
<p>Which means a change to one element usually changes nothing.</p>
<p>The classic version is the <code>User-Agent</code> <em>(the header where a client states what
software it is)</em>. Someone reads that scrapers get blocked, copies a real browser&rsquo;s
<code>User-Agent</code> string into their script, and reruns it. It fails in exactly the same
way. They are now a client that claims to be a browser, sends library headers in
library order, negotiates encryption like a scripting library, ignores cookies, and
never once asks for a stylesheet. The declared string is the only part that matches,
and the mismatch between it and everything else is <em>more</em> distinctive than the honest
original was.</p>
<div class="callout callout-default not-prose"><p class="callout-title">The general rule</p><div class="callout-body">Changing one element of a fingerprint rarely makes you less visible. It usually
makes you a new, smaller, and more interesting category: <strong>clients that are lying
about one specific thing.</strong> If you cannot change the whole picture coherently, you
are often better off not touching any of it.</div>
</div>

<h2 id="a-fixed-interval-is-a-tell">A fixed interval is a tell</h2>
<p>Now the timing, which is where automation gives itself away most cheaply.</p>
<p>A job that runs at exactly :00, :15, :30 and :45 past every hour, forever, is not a
person. It does not matter how convincing your headers are; a request arriving every
900 seconds with a variance measured in milliseconds is a scheduler, and
anyone who plots request times against a source sees a straight line of dots the
moment they look.</p>
<p>The fix is small. <strong>Add jitter</strong> <em>(a random offset added to a scheduled time, so runs
land at slightly different moments)</em>. Instead of every 15 minutes, run every
15 minutes give or take 4.5.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">random</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">time</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">BASE_SECONDS</span> <span class="o">=</span> <span class="mi">900</span>          <span class="c1"># your nominal interval</span>
</span></span><span class="line"><span class="cl"><span class="n">JITTER_FRACTION</span> <span class="o">=</span> <span class="mf">0.3</span>       <span class="c1"># +/- 30% of it</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">sleep_until_next_run</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;&#34;&#34;Wait roughly BASE_SECONDS, never exactly BASE_SECONDS.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">spread</span> <span class="o">=</span> <span class="n">BASE_SECONDS</span> <span class="o">*</span> <span class="n">JITTER_FRACTION</span>
</span></span><span class="line"><span class="cl">    <span class="n">delay</span> <span class="o">=</span> <span class="n">BASE_SECONDS</span> <span class="o">+</span> <span class="n">random</span><span class="o">.</span><span class="n">uniform</span><span class="p">(</span><span class="o">-</span><span class="n">spread</span><span class="p">,</span> <span class="n">spread</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="n">delay</span><span class="p">)</span>
</span></span></code></pre></div><p>Three lines, and the straight line of dots becomes a cloud. The average rate is
unchanged: you collect the same amount, at an irregular cadence, which is what a human
doing the same job would produce anyway.</p>
<figure class="diagram not-prose">
  <img class="diagram-light" src="https://salaheldinaz.com/blog/opsec-for-automation/images/f5-jitter_light.svg" width="720" height="278" alt="Two timing strips stacked on the same 3.5-hour axis. The upper strip, labelled fixed interval, shows 15 requests as evenly spaced dots exactly 15 minutes apart. The lower strip, labelled with jitter, shows the same 15 requests scattered irregularly around the same average rate. Both are marked as example data." loading="lazy" decoding="async">
  <img class="diagram-dark" src="https://salaheldinaz.com/blog/opsec-for-automation/images/f5-jitter_dark.svg" width="720" height="278" alt="Two timing strips stacked on the same 3.5-hour axis. The upper strip, labelled fixed interval, shows 15 requests as evenly spaced dots exactly 15 minutes apart. The lower strip, labelled with jitter, shows the same 15 requests scattered irregularly around the same average rate. Both are marked as example data." loading="lazy" decoding="async">
  <figcaption class="post-caption"><strong>Two completely different things to look at in a log.</strong> The same number of requests, the same average rate. Numbers here are invented for the illustration.</figcaption>
</figure>

<p>There is a second reason to jitter, and it is the one I would lead with when asking a
colleague to do it. <strong>Fixed intervals synchronise.</strong> Run 4 monitors on a <code>*/15</code>
schedule and all 4 fire together, so the source gets all 4 requests in the same second,
96 times a day, instead of spread evenly. Jitter smooths that out. It is
the polite version of the same change, and a better argument to make to a team than
the OPSEC one, because it holds regardless of who is watching.</p>
<h2 id="the-rhythm-is-information-about-your-investigation">The rhythm is information about your investigation</h2>
<p>This is the part that goes beyond being identified as a bot, and it matters most in
this work.</p>
<p>Suppose you watch 40 sources at a slow, even rate for months. Then something
happens in a story you are working on, and you turn one of those 40 up to 10
times its usual rate for a week.</p>
<p>You have just announced an interest in that source, with a start date.</p>
<figure class="diagram not-prose">
  <img class="diagram-light" src="https://salaheldinaz.com/blog/opsec-for-automation/images/f5-surge_light.svg" width="760" height="300" alt="A bar chart of requests per day to one source over 6 weeks, with illustrative numbers. For 4 weeks every bar is the same low height, 96 a day, which is one request every 15 minutes. In week 5 every bar jumps to 960 a day, 10 times the usual rate, and the first day of the jump is marked as the start date. In week 6 the bars drop back to 96." loading="lazy" decoding="async">
  <img class="diagram-dark" src="https://salaheldinaz.com/blog/opsec-for-automation/images/f5-surge_dark.svg" width="760" height="300" alt="A bar chart of requests per day to one source over 6 weeks, with illustrative numbers. For 4 weeks every bar is the same low height, 96 a day, which is one request every 15 minutes. In week 5 every bar jumps to 960 a day, 10 times the usual rate, and the first day of the jump is marked as the start date. In week 6 the bars drop back to 96." loading="lazy" decoding="async">
  <figcaption class="post-caption"><strong>A week of attention, visible from the other side.</strong> The collection never said why it changed. It did not need to. Illustrative numbers.</figcaption>
</figure>

<p>Nobody had to read your notes. The shape of your collection carried the information
on its own. If the operator of that source is a party to what you are investigating,
you have told them somebody started paying close attention on a particular Tuesday,
and the gap between that and knowing who is smaller than it feels.</p>
<p>The same leak happens in other shapes:</p>
<ul>
<li><strong>A cold start on a new source.</strong> You have never touched it, then you fetch every
page on it in an afternoon. That is somebody who has just become interested, not an
ongoing background process.</li>
<li><strong>Collection that follows the news.</strong> A monitor that quickens within hours of a
public event correlates your activity to that event for anyone holding both halves.</li>
<li><strong>Collection that stops.</strong> Going quiet the week after you publish confirms that the
earlier activity was about the thing you published.</li>
</ul>
<p>The defence is not clever. Keep the baseline boring and keep it constant. If you can
tolerate the latency, build the surge into the schedule from the start rather than
introducing it when it matters.</p>
<h2 id="logged-in-sessions-are-the-sharpest-edge">Logged-in sessions are the sharpest edge</h2>
<p>Everything above concerns an anonymous request. Automating through an account is a
different category of exposure, and worth stating plainly because the convenience is
so tempting.</p>
<p><strong>Server logs are shallow. Account telemetry is deep.</strong> <em>(Telemetry here means the
activity data a platform records about a user, as opposed to the request data any web
server records about a connection.)</em> A platform that sees you as an unauthenticated
request has a line in a log. A platform that sees you as a logged-in account has a
history: every item you viewed and in what order, how long each view lasted, what you
searched for, what you saved, which device and which network each session came from,
and every other session that account has ever had. All of it tied to whatever identity
the account was created with — a phone number, an email address, a payment method, or
just a long enough history of logins from a network that is yours.</p>
<p>Three specific consequences.</p>
<p><strong>Automation does not look like use.</strong> Human browsing has pauses, misclicks, and
abandoned pages. Scripted browsing has none of those, and platforms have spent a decade
learning to spot the difference because it is how they catch commercial abuse. Your
research script meets detection built for somebody else and very good at its job.</p>
<p><strong>Some platforms tell the other side.</strong> Depending on the service, viewing a profile,
following an account, joining a channel, or opening a document can be visible to its
owner. Automating that turns one glance into hundreds of notifications on someone
else&rsquo;s screen.</p>
<p><strong>The account outlives the operation.</strong> A research account reused across 3
investigations links those 3 investigations to each other, permanently, in a
record you do not control and cannot inspect. That is the failure that does real
damage, and it happens through convenience rather than any technical mistake.</p>
<div class="callout callout-warning not-prose"><p class="callout-title">Before you automate anything behind a login</p><div class="callout-body">Ask what identity the account is attached to, whether the terms permit programmatic
access at all, what the platform shows the other side, and what else the account has
been used for. If any of those 4 answers is &ldquo;I do not know&rdquo;, that is the work to do
first.</div>
</div>

<h2 id="the-correct-fix-is-usually-to-slow-down">The correct fix is usually to slow down</h2>
<p>Most OPSEC reading gets you excited about hiding. Hiding is the fourth option, and
it is the expensive one.</p>
<figure class="diagram not-prose">
  <img class="diagram-light" src="https://salaheldinaz.com/blog/opsec-for-automation/images/f5-restraint_light.svg" width="776" height="190" alt="Four options in order, left to right: don&#39;t collect (asked once, read it), collect less (fewer sources, fewer fields), collect slower (jitter, and a wider gap) which is highlighted, and route around (the last resort, not the first)." loading="lazy" decoding="async">
  <img class="diagram-dark" src="https://salaheldinaz.com/blog/opsec-for-automation/images/f5-restraint_dark.svg" width="776" height="190" alt="Four options in order, left to right: don&#39;t collect (asked once, read it), collect less (fewer sources, fewer fields), collect slower (jitter, and a wider gap) which is highlighted, and route around (the last resort, not the first)." loading="lazy" decoding="async">
  <figcaption class="post-caption"><strong>Work left to right.</strong> Three of the 4 options cost nothing, and each one you take makes the next one less necessary.</figcaption>
</figure>

<p><strong>Do you need to collect this at all?</strong> The first post in this series has <a href="https://salaheldinaz.com/blog/what-automation-means-for-an-osint-analyst/">a whole
section on when not to automate</a>,
and its first item is that a question asked once should be answered by hand. Opening
the page yourself leaves a footprint of exactly one request.</p>
<p><strong>Do you need this much of it?</strong> Fewer sources, fewer fields, fewer pages per run.
The most common thing I see is a monitor fetching a full page every cycle when a much
smaller resource — a feed, an index, a single endpoint — answers the same question.
That is fewer requests, less bandwidth for the source, and less of you in their logs.</p>
<p><strong>Do you need it this fast?</strong> This is the one worth pushing on, because the answer is
almost always no and the interval was set by feel rather than by need. A 15-minute interval
and a 6-hour one feel very different to build and are often identical in outcome, because
the thing you are watching updates twice a week.</p>
<p>Ask what actually breaks if you find out 4 hours later. If the honest answer is
&ldquo;nothing&rdquo;, you have cut your exposure by roughly an order of magnitude without
configuring anything.</p>
<p>Only when those 3 are exhausted does routing make sense. A later post in this
series covers reaching sources that block you outright, which is a different problem
with a different answer.</p>
<div class="callout callout-info not-prose"><p class="callout-title">One line to take into that later post</p><div class="callout-body"><strong>Changing your exit IP is not permission to send personal data anywhere.</strong> Routing
changes which address a request appears to come from. It changes nothing about what
you may collect, what you may transmit, or who is on the other end receiving it.
Those are separate questions with separate answers, and a proxy touches none of them.</div>
</div>

<h2 id="being-legible-on-purpose">Being legible on purpose</h2>
<p>One strategy runs opposite to all of this, and for a lot of research it is the right
one.</p>
<p>In <a href="https://salaheldinaz.com/blog/building-your-first-automation-workflow/">the first workflow post</a> the
example client identified itself honestly: a tool name, a version, and a contact
address. That is not an oversight in an OPSEC post. If you are collecting from a
public source, at a modest rate, for work you would be willing to describe, then being
obviously a research tool with a way to reach you beats being an unexplained pattern.
Unexplained patterns get blocked. Identified ones sometimes get a reply, and
occasionally get an export.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">HEADERS</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;User-Agent&#34;</span><span class="p">:</span> <span class="s2">&#34;research-monitor/1.0 (analyst@example.test)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>The 2 strategies do not mix. Blending in and declaring yourself are both coherent;
declaring yourself while also trying to look like a browser is neither, and it is
where most people end up by accident. Pick one per source and write down why.</p>
<p>Here is what that looks like on a real run: a small echo server listening on 127.0.0.1,
receiving one request from a plain Python script and then one from a real browser opening
the same address.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">$ python3 echo_server.py
</span></span><span class="line"><span class="cl">listening on 127.0.0.1:8765
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">--- request 1 from 127.0.0.1 ---
</span></span><span class="line"><span class="cl">GET / HTTP/1.1
</span></span><span class="line"><span class="cl">Accept-Encoding: identity
</span></span><span class="line"><span class="cl">Host: 127.0.0.1:8765
</span></span><span class="line"><span class="cl">User-Agent: Python-urllib/3.14
</span></span><span class="line"><span class="cl">Connection: close
</span></span><span class="line"><span class="cl">(4 headers)
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">--- request 2 from 127.0.0.1 ---
</span></span><span class="line"><span class="cl">GET / HTTP/1.1
</span></span><span class="line"><span class="cl">Host: 127.0.0.1:8765
</span></span><span class="line"><span class="cl">Connection: keep-alive
</span></span><span class="line"><span class="cl">sec-ch-ua: &#34;Brave&#34;;v=&#34;149&#34;, &#34;Not_A Brand&#34;;v=&#34;8&#34;, &#34;Chromium&#34;;v=&#34;149&#34;
</span></span><span class="line"><span class="cl">sec-ch-ua-mobile: ?0
</span></span><span class="line"><span class="cl">sec-ch-ua-platform: &#34;Linux&#34;
</span></span><span class="line"><span class="cl">Upgrade-Insecure-Requests: 1
</span></span><span class="line"><span class="cl">User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36
</span></span><span class="line"><span class="cl">Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8
</span></span><span class="line"><span class="cl">Sec-GPC: 1
</span></span><span class="line"><span class="cl">Sec-Fetch-Site: none
</span></span><span class="line"><span class="cl">Sec-Fetch-Mode: navigate
</span></span><span class="line"><span class="cl">Sec-Fetch-Dest: document
</span></span><span class="line"><span class="cl">Accept-Encoding: gzip, deflate, br, zstd
</span></span><span class="line"><span class="cl">Accept-Language: en-US,en;q=0.9
</span></span><span class="line"><span class="cl">(14 headers)
</span></span></code></pre></div><div class="post-caption not-prose"><strong>Look at what your own tools send.</strong> Run an echo server on your own loopback address.
Loopback is the only safe way to do this exercise: never send your real fingerprint
to a third-party fingerprinting service to find out what it looks like, because that
request cannot be taken back, and it lands in somebody else&rsquo;s database. Real output,
both requests made on loopback: 4 headers from the script, 14 from the browser, and
not even <code>Host</code> in the same place. Two values were altered before publishing — the
browser version and the language list — so the capture is not the capturing machine&rsquo;s
own fingerprint; every header name, the count and the order are as captured.</div>

<div class="limitation not-prose"><p class="limitation-title">What none of this fixes</p><div class="limitation-body"><p>This post is about the signals your collection emits, and whole categories sit
outside it.</p>
<p>It does not address <strong>what you are legally allowed to collect</strong>. Jitter is not
consent, and a polite request rate does not make personal data lawful to accumulate.
That question comes before the technical one, and the first post in this series says
so at more length.</p>
<p>It does not address <strong>what happens to material after you collect it</strong>. Where the store
lives, who can read it, what leaves your machine when a step calls a hosted model —
real exposure, none of it on the diagram above.</p>
<p>It does not make you <strong>anonymous</strong>. Nothing here does. Reducing and irregularising your
footprint raises the cost of noticing you; it does not take you off the network. If
your threat model genuinely requires that nobody can attribute your collection, the
answer is not a better scraper.</p>
<p>And it does not survive <strong>one careless run</strong>. Months of disciplined pacing are undone
by a single afternoon of pulling an entire site as fast as it will answer, because that
afternoon is the one that trips an alerting threshold somewhere.</p>
</div>
</div>

<h2 id="the-habit">The habit</h2>
<p>Add one line to the notes you keep for each monitor, next to what it collects and
where it stores things:</p>
<blockquote>
<p><strong>What does this look like from the other side?</strong></p>
</blockquote>
<p>Answer it in a sentence. <em>A data-centre address, library headers, every 15 minutes
give or take 4.5, no account, identifies itself.</em> You will not always like the answer,
and writing it down is what makes it changeable. An emission you have described is a
decision; an emission you have never described is a habit that has been running since
March.</p>
<p>That closes the foundations: <a href="https://salaheldinaz.com/blog/what-automation-means-for-an-osint-analyst/">what automation
means</a>, <a href="https://salaheldinaz.com/blog/building-your-first-automation-workflow/">building the first
one</a>, <a href="https://salaheldinaz.com/blog/where-ai-actually-fits-in-osint/">where AI
fits</a>, <a href="https://salaheldinaz.com/blog/ai-dos-and-donts-for-investigators/">the do&rsquo;s and
don&rsquo;ts</a>, and this. The methods half, later in the series, goes
deeper, starting with how to build all of it without writing code.</p>
]]></content:encoded></item></channel></rss>