Skip to main content
  1. Posts/

From IOC Lists to Firewall Blocks: Building a Threat Feed Pipeline Without a Paid Subscription — Part 1

Pratik
Author
Pratik

This is my little digital scratchpad. Some days it’s about security tools, code, or systems I’m tinkering with. Some days it’s poems, emotions, or thoughts that refused to stay quiet. And sometimes it’s just photos I took while pretending I knew what I was doing.

Expect a mix of tech notes, life reflections, creative bursts, and the occasional anxious-mind ramble that somehow made sense at 2 AM.

Table of Contents

This started with a simple thought: we already trust community intelligence during investigation, so why are we not using it before the traffic reaches us?

No paid threat-intel subscription. No heavy SIEM licensing story. No fancy black-box appliance.

Just community feeds, a small publishing layer in Pramaan (Argusoft SaaS App), and proactive blocking at the firewall.

The result was not small. On traffic paths where Sophos ATP actually evaluates third-party threat feeds, the volume of threat items we had to triage dropped noticeably during the observation window. This was an operational before/after comparison on the same covered traffic path, not a formal benchmark, and it does not include the DNAT-forwarded path, which SFOS 21.5 does not evaluate for source-IP threat-feed matches in the same way as supported outgoing and firewall-destined traffic.

Introduction
#

Most security work starts after something has already made noise.

A suspicious source IP keeps touching SSH. A random URL shows up in proxy logs. A domain appears in malware telemetry. An analyst copies the IOC, checks reputation, adds it somewhere, and moves to the next thing.

That flow works, but it is reactive.

The question I had was simple:

If thousands of known-bad indicators are already published by the community every day, why should my firewall wait for them to knock first?

That question became the Threat Intel feed workflow inside Pramaan.

The goal was not to build another threat intelligence platform for the sake of having one more dashboard. The goal was much smaller and more useful:

  • collect community indicators,
  • normalize them,
  • split them by indicator type,
  • publish organization-specific feed URLs,
  • let the firewall poll them,
  • and keep enough logs to prove what happened.

It sounds like a text-file problem.

It was not.


Context: Where The Implementation Fits
#

This post is not about Pramaan as a product. That may be a separate post later.

This threat feed work came from a very practical challenge: we had community intelligence available, we had firewalls capable of consuming third-party feeds, and we had real noisy traffic touching Indian servers. But there was no clean middle layer that could normalize those indicators, publish them safely, keep organization boundaries, and still remain simple enough for a firewall to poll.

So I built a custom implementation inside Pramaan for this specific workflow.

Then we chose a real test case instead of keeping it theoretical. We used a firewall, Sophos XGS 2300, for the pilot and tested it on-premises, in a live working environment in India, against real traffic. The IPs in the validation were real public IPs observed against Indian servers, not lab-only placeholders.

That mattered.

Because a feed pipeline that works in a clean demo can still fail in the boring parts of production: firewall auth support, DNAT paths, stale indicators, feed expiry, polling behavior, and whether a “blocked” source is actually blocked on the path you care about.


The Problem With “Just Block These IPs”
#

At first, threat feeds look very simple.

One source gives IPs. Another gives domains. Another gives URLs. Some upstream sources include CIDR ranges. Pramaan canonicalizes and retains the original network, but it deliberately expands only IPv4 ranges from /24 through /32 into individual addresses. Broader IPv4 ranges and IPv6 CIDRs remain CIDR entries instead of being expanded into an impractically large number of addresses. Some include comments. Some include old entries. Some disappear for a while and come back later.

And then there is the real production question:

Should every organization receive every indicator?

The answer is no.

One bad entry in a block list can become a business outage. One stale indicator can waste analyst time. One missing allowlist can break access to something legitimate. This is why I did not want Pramaan to simply download feeds and throw them at the firewall.

The Community Feed Expiry Problem
#

This was the bigger reason a custom layer was needed.

Community feeds are living lists. That is their strength. It is also the part that makes naive blocking risky. In one sync cycle, Emerging Threats removed an IP from the feed. From the upstream feed’s point of view, that made sense: the list had moved on. But the next day, that same IP was still pinging our server.

That created a small but important question:

If a community feed stops listing an IP today, should the firewall immediately stop treating it as dangerous?

For now, my answer is no.

Pramaan does not remove an indicator the moment one upstream source drops it. If an IP is seen once, it stays in block mode for 30 days, and the clock refreshes whenever the same IP reappears in a future upstream sync. After the block window expires, it moves into monitor mode for another 30 days.

flowchart TD
    A[Seen in source feed] --> B[Block mode for 30 days]
    B --> C{Seen again in an upstream sync?}
    C -- Yes --> D[Refresh the 30-day block window]
    D --> B
    C -- No --> E[Move to monitor mode for 30 more days]
    E --> F{Local hit while monitored?}
    F -- Yes --> G[Promote back to block mode]
    G --> B
    F -- No --> H[Expire if it stays quiet]

The 30-day window is a pragmatic baseline, not a proven optimum. As we gather more data on attacker infrastructure re-use, the policy will need tuning.

The point is the shape of the decision: community feeds should influence the block list, but they should not be the firewall’s only memory. If something was bad enough to enter the feed and it is still touching our infrastructure, it should not disappear from enforcement just because an upstream text file rotated.

This is where the monitor URL becomes useful. Once an IP ages out of the block feed, it stays visible through the monitor feed. If an IP in monitor mode hits us again, Pramaan should promote it back into block mode and reset the 30-day window. That promotion loop is still WIP.

Community feeds are not permanent truth. They are signals. The platform still needs memory, expiry, and a way to re-promote indicators when local telemetry proves they are still relevant.

The platform needed a little more discipline:

  • global feed sync should happen once on the schedule defined by each feed provider,
  • organizations should choose what they want,
  • allowlists should always win,
  • block and monitor feeds should be separate,
  • feed access should be logged,
  • and the firewall should consume plain URLs without caring how messy the backend is.
Threat intelligence is only useful when it is controlled. A raw IOC list connected directly to a firewall is not automation. It is risk with a cron job.

What Was Built
#

The first version was intentionally boring.

Pramaan maintains a list of source feeds. Each feed has a type, sync schedule, last run status, item count, and enabled state. Some upstream feeds contain individual IP addresses, while others also contain CIDR networks. During ingestion, Pramaan canonicalizes the network address and stores the original CIDR. For IPv4 prefixes from /24 through /32, it also creates the individual IP indicators that can be included in the Sophos-compatible ip.txt feed. IPv4 networks broader than /24 are kept only as CIDRs to avoid a large expansion, and IPv6 CIDRs are also preserved without expansion. Those retained network indicators are published separately for consumers or firewall workflows that support CIDR values. Some feeds contain domains, and others contain URLs.

In the current setup, the source catalog had 12 active threat feeds and 462,636 normalized indicators across IP/CIDR, domain, and URL types. These are ingestion-side classifications. The Sophos ip.txt output contains individual IPv4 indicators, including addresses produced from eligible /24 to /32 ranges; retained CIDRs are kept out of that feed:

MetricCount
Total feeds12
Active feeds12
Inactive feeds0
Total IP indicators333,257
Domain indicators19,075
URL indicators110,304
Total indicators462,636

The configured feeds were:

FeedUpstream indicator type
Abuse.ch Feodo Tracker (C2 IPs)IP address / CIDR
Abuse.ch Feodo Tracker (aggressive)IP address / CIDR
Abuse.ch URLhausURL
AbuseIPDB BlacklistIP address / CIDR
Etnetera AggressiveIP address / CIDR
URLhaus (plain text)URL
URLhaus DomainsDomain
blocklist.deIP address / CIDR
cybercrime trackerURL
Emerging Threats blocked IPsIP address / CIDR
Emerging Threats compromised IPIP address / CIDR
ThreatFox recent domainsDomain

Counts change with every upstream sync, so treat these as a snapshot from the sync window rather than a fixed inventory.

Pramaan threat intel source feed list showing active community feeds and indicator counts
Pramaan source feed catalog. Community feeds are synced, typed, counted, and tracked before they are ever published to a firewall.

The firewall did not need to know any of this internal structure.

It only needed URLs.

https://example.com/ThreatIntel/feeds/{organizationId}/block/ip.txt
https://example.com/ThreatIntel/feeds/{organizationId}/monitor/ip.txt
https://example.com/ThreatIntel/feeds/{organizationId}/block/cidr.txt
https://example.com/ThreatIntel/feeds/{organizationId}/monitor/cidr.txt
https://example.com/ThreatIntel/feeds/{organizationId}/block/domain.txt
https://example.com/ThreatIntel/feeds/{organizationId}/monitor/domain.txt
https://example.com/ThreatIntel/feeds/{organizationId}/block/url.txt
https://example.com/ThreatIntel/feeds/{organizationId}/monitor/url.txt

The hostname and path above are representative. The real Pramaan endpoints use a different host and organization-scoped path, but the structure is the same: one simple text file per indicator type and mode. The CIDR output is separate because not every feed consumer accepts network ranges; the Sophos configuration in this pilot used the individual-address ip.txt feed. That small URL shape mattered.

Sophos XGS can consume third-party threat feeds through external URLs. So instead of forcing the firewall into Pramaan’s world, Pramaan published feeds in the shape the firewall already understood.

Pramaan organization publishing page showing block and monitor feed URLs for IP, domain, and URL indicators
Organization-specific block and monitor URLs. The firewall gets simple text feeds. Pramaan handles selection, normalization, and publishing behind the scenes.

Firewall Consumption
#

In Sophos, the feed configuration becomes very straightforward. In SFOS 21.5, this is configured under Active threat response → Third-party threat feeds. For each feed, the administrator selects the action, indicator type, external URL, authentication method, and polling interval. The mental model is simple: Sophos polls an external feed URL, classifies it by indicator type, and applies the selected action.

Sophos third-party threat feed edit dialog configured with a Pramaan IP block feed URL
Sophos consuming the Pramaan IP block feed as a third-party threat feed. No custom firewall plugin, no paid feed connector.

The important part is that the firewall action still remains explicit. Pramaan publishes the feed. Sophos decides how to enforce it.

That separation kept the MVP clean.


The First Useful Signal
#

After configuring the feed on the firewall, the useful signal showed up in the Sophos Active Threat Response logs: source IPs from the Pramaan-backed block feed were being matched and dropped.

Sophos Active Threat Response log viewer showing PramaanIPBlock entries with dropped source IPs
Sophos Active Threat Response logs showing PramaanIPBlock hits. The firewall matched the source IPs from the published feed and dropped the traffic.

That proved the feed was not just configured. It was being enforced by the firewall.

I also checked one of the dropped source IPs, 81.16.152.2, in VirusTotal. The result gave a second point of validation: 9 out of 91 security vendors flagged the IP as malicious, with detections such as malicious, malware, phishing, and suspicious.

VirusTotal detection page showing 9 out of 91 security vendors flagged IP address 81.16.152.2 as malicious
VirusTotal corroboration for one blocked source IP. Sophos showed the drop, and VirusTotal showed that the same IP had malicious reputation across multiple vendors.

No dramatic alert. No incident bridge. Just known-bad sources being handled by the firewall before they became analyst work.


The Dashboard Told the Better Story
#

The numbers made the effect visible.

Sophos showed the individual drops, but the better operational picture came after those logs were collected in Wazuh. Over a 24-hour window, the SIEM view showed:

MetricValue
Total ATP blocks496
Countries seen14
Unique source IPs169

The dashboard made the pattern easier to understand than raw firewall logs. It showed blocks over time, the spread across countries, and the top Pramaan-blocked source IPs. One source, 81.16.152.2, dominated the list with 216 blocks in the 24-hour window.

Wazuh Pramaan Threat Feed Dashboard showing 496 ATP blocks, 14 countries, 169 unique source IPs, ATP blocks over time, and top Pramaan blocked source IPs
Wazuh dashboard built from Sophos logs over the last 24 hours. The SIEM view made the ATP block volume, source distribution, and top blocked IPs easier to explain.

A second Wazuh view broke the same activity down by source country and protocol. That helped separate the story from a single noisy IP and showed that the blocked traffic was spread across multiple locations and protocols.

Wazuh dashboard showing ATP blocks by country and ATP protocol breakdown with ICMP and UDP traffic
Country and protocol breakdown from Wazuh. The feed was not only blocking one source; it was reducing noisy traffic across countries and protocols.

This is where the reduction mattered.

The point was not that every blocked packet was a confirmed compromise attempt. The point was that a large amount of known-bad, low-value, noisy traffic never reached the normal triage flow.

That is what proactive blocking should do.

It should reduce the background radiation.

Using only public community feeds, proactive firewall blocking intercepted hundreds of known-bad traffic attempts on the covered paths before they entered the normal analyst triage flow. That is a good trade: less noise, no feed subscription cost, and a workflow that can still be audited.

The DNAT Blind Spot We Found During Validation
#

Update: After upgrading the firewall to SFOS 22, we validated Drop remote source match events on DNAT-forwarded traffic using the same Pramaan-backed feed. The expanded validation and Wazuh results are covered in Part Two.

Once the feed was active, I checked whether third-party threat-feed enforcement applied to all traffic paths, not just the obvious outbound cases.

On SFOS 21.5, the answer was more nuanced than a simple yes or no. It also raised a fair question:

If Sophos threat feeds do not reliably cover DNAT-forwarded traffic, what are they actually blocking inbound? Without a DNAT rule, would the traffic not hit the final deny rule anyway? Does that make threat feeds redundant?

For a DNAT-heavy inbound setup, that observation is mostly right. If there is no DNAT rule, the final deny rule blocks the traffic anyway. If there is a bare DNAT rule, SFOS 21.5 may forward the traffic without applying third-party threat-feed enforcement on that path.

That does not make the feature useless. It means the value is concentrated elsewhere: outbound LAN-to-WAN traffic, firewall-owned WAN services such as User Portal, WebAdmin, and SSL VPN, and routed or VPN-style flows where the relevant security checks apply. The strongest value in our validation was on those covered paths, not on bare inbound port forwarding.

The blind spot was traditional inbound DNAT or port-forward traffic from WAN to LAN. In our validation, the feed was active and the indicator existed, but the bare DNAT path did not produce the same threat-feed enforcement behavior as the covered paths. Sophos guidance suggests attaching core security engines such as IPS, Web Protection, or AV to the inbound rule can affect enforcement on that path, but that specific configuration was not tested in our validation.

The Sophos Firewall v22 release note indicates this area changes in v22, where inbound forwarded traffic such as WAF and DNAT can be identified and matched with third-party threat feeds. We have not validated that v22 behavior in our environment yet.

Sophos Firewall v22 release note highlighting support for identifying and matching inbound forwarded traffic such as WAF and DNAT with third-party threat feeds
Sophos’s v22 release note calls out support for matching inbound forwarded traffic, including WAF and DNAT, with third-party threat feeds. This is documented by Sophos, but it is still pending validation in our environment.

The practical conclusion was simple: third-party threat feeds are useful, but they are not a replacement for inbound firewall-rule design. For DNAT-published services on SFOS 21.5, a conventional drop rule using an IP host group is still the more reliable inbound control. We used a separately maintained IP block group populated through the Sophos REST API with known-bad CIDR ranges from sources like Emerging Threats. That gave partial coverage, but it required separate maintenance, missed Pramaan’s normalization, and did not get the same refresh behavior.

If your organization publishes services through DNAT rules on SFOS 21.5, validate that your threat-feed blocking is actually evaluating source IPs on that specific rule. Do not assume coverage just because the feed works for outbound traffic or firewall-local services. Test the DNAT path explicitly, especially if the rule is a bare port forward without IPS, Web Protection, or AV enabled.

Why This Needed No Paid Threat-Intelligence Feed Subscription
#

The expensive part of threat intelligence is not always the data.

There are excellent paid feeds, and they have their place. But for many teams, there is already meaningful value in public community intelligence if it is handled properly.

This setup did not need a paid threat-intel subscription because it used:

  • community feeds,
  • scheduled sync jobs,
  • simple normalization,
  • organization-level publishing,
  • and firewall-native third-party feed polling.

The cost was engineering time and infrastructure, not license or feed subscription cost.

This does not mean the complete firewall deployment is license-free. Sophos third-party threat feeds require the relevant Sophos firewall licensing. The point is narrower: Pramaan did not require an additional commercial threat-intelligence data subscription.

That distinction matters for small teams.

Not every organization can buy another security product. But many can use what is already available, make it safer, and integrate it into existing controls.


The Authentication Limitation Nobody Talks About
#

This was one of the quieter lessons from the implementation.

Threat-feed publishing is not only limited by what the platform can generate. It is also limited by what the firewall can consume.

Sophos XGS gives a few useful options for third-party feeds:

  • no authentication,
  • basic authentication,
  • API key in a header,
  • API key in query parameters,
  • server certificate validation,
  • polling interval control.

That is good enough for many simple feed providers. But it is still not the same as being a full HTTP integration client.

Many firewalls do not support richer provider authentication patterns such as OAuth client credentials, signed requests, mTLS client certificates, custom token refresh flows, HMAC signatures, or complex multi-step API calls. Even when a threat intelligence provider exposes a good API, the firewall may only understand:

“Give me a URL that returns one indicator per line.”

That limitation shaped the design.

Pramaan can be richer on the ingestion side. It can fetch upstream feeds with configured headers, URL templates, GET/POST methods, multiple content formats, compression handling, parsing, normalization, and secret placeholders. But on the publishing side, the feed still has to become something firewall-friendly: a plain text URL that Sophos can poll reliably.

So Pramaan acts as the translation layer.

Upstream, it can deal with messy providers.

Downstream, it speaks the simple language the firewall understands.

This is also why feed authentication needs to be designed carefully. The easiest firewall-compatible option is often a long, obscure URL or an API key in a header/query parameter. That works operationally, but it should be treated like a secret, rotated, logged safely, and never exposed in screenshots or access logs.

In the current MVP, the published Pramaan feed URLs are intentionally simple for firewall compatibility. A stronger production version should add enforced API-key validation on published feed endpoints, key rotation, per-organization feed keys, and clear access logs that store only key prefixes, never full secrets.


The Design Decisions That Mattered
#

Global Sync Once
#

If ten organizations use the same feed, the platform should not download it ten times.

Pramaan syncs source feeds centrally, stores normalized indicators, and then creates organization-specific views from that data.

This keeps the system cheaper, simpler, and easier to debug.

Organization-Specific Publishing
#

Every organization can have its own feed URLs and its own feed selection.

This matters because threat appetite is not universal. One organization may want aggressive blocking. Another may want a shorter block window or stricter source selection. Another may need to exclude specific business-critical endpoints.

Allowlist Wins
#

This rule is non-negotiable.

If an indicator exists in a source feed and an organization allowlists it, the published feed should not include it.

False positives are not just technical mistakes. They break trust. Once the firewall blocks something important because the platform ignored an allowlist, people stop trusting automation.

During the pilot, we did not hit a single false-positive outage, largely because the allowlist workflow was built before enforcement went live. But one clean pilot does not prove the workflow is perfect. The real test of a block feed is how quickly and safely you can undo a mistake.

Plain Text Output
#

The published feed is intentionally simple.

One indicator per line.

No complex API contract. No custom agent. No firewall-side parsing logic.

The less the firewall has to understand, the more reliable the integration becomes.

Access Logs
#

Every feed request should leave a trail:

  • organization,
  • indicator type,
  • request path,
  • status code,
  • indicator count,
  • client IP,
  • user agent,
  • forwarded-for metadata.

But it should not log secrets.

Authorization headers and full API keys do not belong in logs. A defensive service should not become a credential leak.


What I Would Improve Next
#

This version proved the value, but there are three things that matter most right now.

SFOS 22.0 Upgrade for Full DNAT Coverage
#

The DNAT blind spot is the most concrete gap in the current setup. Sophos documents inbound forwarded traffic support in v22, as noted earlier, but the upgrade still needs to be tested against the same DNAT path before we treat that coverage as proven.

I would still treat it like any other security control: upgrade, test the same source IP again, and confirm the log path. The manual IP block group can then be retired or kept as a secondary layer. Post-upgrade numbers should also reflect broader coverage, which will make the before/after comparison more honest.

Better Expiry Handling
#

The 30-day block and 30-day monitor windows are a deliberate first guess, not a final policy. The next version should make expiry more explicit: source-level defaults, indicator-type defaults, local-hit extensions, and clear reasons for why an indicator is still blocked or only monitored.

Confidence-Based Promotion
#

Right now, an indicator can age from block to monitor, but the reverse trip is manual. The next step is automatic promotion: if an IP in monitor mode hits our infrastructure again, Pramaan should move it back to block mode and reset the 30-day window.

Other Ideas
#

A few longer-term experiments are worth mentioning but are not urgent:

  • Wazuh correlation. If Wazuh sees failed SSH activity and the source is already in a Pramaan block feed, that alert should carry more context.
  • India-specific signals. A small honeypot in the network could capture India-relevant scanning patterns and feed them back into Pramaan as a local community-style feed. It might be useful, noisy, or both.
  • False-positive workflow. Allowlist entries need owner, reason, expiry, and review history. Otherwise every exception becomes permanent.

What Stayed With Me
#

This feature reminded me that security does not always need to be expensive to be useful.

Sometimes the better question is not:

Which product should we buy?

Sometimes it is:

What useful signal is already available, and how do we make it safe enough to act on?

Community feeds are not perfect. They can be noisy. They can go stale. They can contain mistakes. But with the right controls around them, they can still remove a surprising amount of useless traffic before it becomes operational noise.

The DNAT finding reinforced something else: enabling a feed and validating a feed are two different things. The reduction was real in the covered paths, but it only applied where ATP evaluation actually fired. Knowing where coverage ends is just as important as knowing where it works.

For me, the important lesson was this:

Feeds are easy.

Safe feeds are engineering.

Knowing your coverage boundaries is the rest of the job.

And when all of that works, the result is beautifully quiet.

Disclosure note: Screenshots and values in this post should be treated as sanitized operational examples. Feed URL patterns use a placeholder host to avoid exposing real endpoints.

Related