<?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: Tahir Almas</title>
    <description>The latest articles on DEV Community by Tahir Almas (@tahiralmas).</description>
    <link>https://dev.to/tahiralmas</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3911307%2F77d2f227-47d5-42ea-87e2-5d4abeb9b2cd.png</url>
      <title>DEV Community: Tahir Almas</title>
      <link>https://dev.to/tahiralmas</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tahiralmas"/>
    <language>en</language>
    <item>
      <title>One Leaked Key: What the Beacon CRM Software Breach Teaches</title>
      <dc:creator>Tahir Almas</dc:creator>
      <pubDate>Mon, 17 Aug 2026 04:56:41 +0000</pubDate>
      <link>https://dev.to/tahiralmas/one-leaked-key-what-the-beacon-crm-software-breach-teaches-131e</link>
      <guid>https://dev.to/tahiralmas/one-leaked-key-what-the-beacon-crm-software-breach-teaches-131e</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://www.ictcrm.com/crm-software-breach-leaked-cloud-key-logs/" rel="noopener noreferrer"&gt;ictcrm.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A UK CRM provider called Beacon confirmed this week that the breach affecting all of its 1,000-plus customers started with a single compromised AWS access key, likely exposed in publicly available JavaScript build artifacts. No zero-day, no phishing campaign, no insider. A credential that shipped to the browser along with the app.&lt;/p&gt;

&lt;p&gt;Beacon serves charities and nonprofits, so the records involved cover donors, supporters, volunteers and fundraising history. That is exactly the kind of data that is worthless to a bank and very valuable to anyone running a targeted scam. If you are responsible for CRM software anywhere, this incident is worth twenty minutes of your attention, because the mistake behind it is common and the second mistake is more common still.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually happened
&lt;/h2&gt;

&lt;p&gt;The earliest malicious activity Beacon can find is 27 July 2026, with data transfers happening across 27 and 28 July. The company disclosed in early August and published its root-cause finding in the week of 14 August.&lt;/p&gt;

&lt;p&gt;The attacker used the key to reach an AWS environment holding customer database backups and downloaded them. The backups were encrypted at rest, which sounds like the story should end there. It does not. Beacon acknowledges the attacker may have been able to decrypt them before taking them, because whatever could read the backups could also reach what was needed to open them.&lt;/p&gt;

&lt;p&gt;Exposed fields include names, phone numbers, email addresses and postal addresses. No bank details, sort codes or card numbers, because Beacon does not store them. The UK Charity Commission is monitoring the situation. No group has claimed the attack and the data has not surfaced publicly so far.&lt;/p&gt;

&lt;h2&gt;
  
  
  How a cloud key ends up on the public internet
&lt;/h2&gt;

&lt;p&gt;Almost nobody commits a secret to a public Git repository any more. Scanners catch it, GitHub emails you about it, and most teams have a pre-commit hook. The exposure moved somewhere else.&lt;/p&gt;

&lt;p&gt;The key never touches your repository. The build step publishes it for you.&lt;/p&gt;

&lt;p&gt;The path runs like this. The key sits in an environment file, correctly excluded from version control, and everyone feels fine about it. Then a front-end build runs. Bundlers inline environment variables into the output so the browser can read them, because that is what they are for. The bundler also writes a source map next to the minified file, which conveniently reverses the minification for anyone who fetches it.&lt;/p&gt;

&lt;p&gt;Now the key is a static asset on your CDN. Anyone can open developer tools, pull the bundle, and search it for the string AKIA. There are automated crawlers that do nothing else all day.&lt;/p&gt;

&lt;p&gt;The reason this keeps happening is that the mistake is invisible from the inside. Your repository is clean. Your secret scanner passes. Your env file is git-ignored. Nobody on the team thinks to check the compiled output, because the compiled output feels like a build product rather than a published document. It is a published document.&lt;/p&gt;

&lt;h3&gt;
  
  
  The cheap version of not being here
&lt;/h3&gt;

&lt;p&gt;Four things, none of which require a budget.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scan your own deployed bundle.&lt;/strong&gt; Fetch your production JavaScript the way an attacker would and grep it for credential patterns. Put it in CI so it runs on every deploy. This takes an afternoon to set up and would have caught this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stop shipping source maps to production.&lt;/strong&gt; They are a debugging convenience for your team and a decompiler for everyone else. Upload them to your error tracker and keep them off the public host.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Give the front end a short-lived token, not a permanent credential.&lt;/strong&gt; If the browser needs to talk to cloud storage, have your backend mint a scoped token that expires in minutes. A leaked token with a fifteen-minute life is an inconvenience. A leaked permanent key is this article.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Put an expiry on every key.&lt;/strong&gt; Rotation is dull and it is the only control that limits the damage of a leak you never noticed. If a credential has no expiry date, its blast radius is the rest of your company's life.&lt;/p&gt;

&lt;h2&gt;
  
  
  The second failure was the logging
&lt;/h2&gt;

&lt;p&gt;Here is the sentence from Beacon's statement that should worry you more than the key: specific objects, the exact destination of the downloads, and definitive attribution of which objects were accessed cannot be determined from available logs.&lt;/p&gt;

&lt;p&gt;Read that again. They could see that data moved. They could not see which records. So when they worked out what to tell customers, they had to fall back on transfer volume, and their assessment from volume is that the actor exported everything in the database.&lt;/p&gt;

&lt;p&gt;That may or may not be literally true. It does not matter, because when the logs cannot answer the question, you are obliged to assume the worst. Every customer gets the maximum version of the notification. The difference between "we can confirm 400 records were accessed" and "we must assume all of it" is not a technical difference. It is the difference between a contained incident and a company-defining one.&lt;/p&gt;

&lt;p&gt;Four questions arrive within hours of any breach. Decide now whether your logs can answer them.&lt;/p&gt;

&lt;p&gt;The questions that arrive on day one are always the same four.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Whose records were taken?&lt;/strong&gt; Answering this needs object-level access logging. In AWS terms that is S3 data events, which are separate from management events and are off by default because they cost money. The bill for turning them on is trivial next to the cost of not being able to answer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When did it start?&lt;/strong&gt; Beacon's earliest evidence is 27 July. If your log retention is shorter than an attacker's dwell time, your earliest evidence is simply the day your retention window opens, which tells you nothing. Ninety days is a floor, not a target.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Was this credential ever used normally?&lt;/strong&gt; A key used from a new country at three in the morning is the entire story, but only if you know what normal looked like for that key. That requires one credential per job. A single shared key used by six services has no baseline, because everything is normal for it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Did they read the data or just copy it?&lt;/strong&gt; Encryption at rest only helps if the decryption rights live on a different identity from the storage rights. If the same key opens the door and unlocks the box, encryption is documentation rather than defence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this is an argument for self-hosted CRM software
&lt;/h2&gt;

&lt;p&gt;Beacon's customers did nothing wrong. They picked a specialist provider serving their sector, which is normally the sensible choice. They had no way to audit whether a cloud key was sitting in a JavaScript bundle, no visibility into log retention, and no say in whether object-level logging was enabled. They found out about all of it from an email.&lt;/p&gt;

&lt;p&gt;That is the trade in hosted CRM software. You hand over the operational burden and you also hand over the ability to verify anything. It works right up until it does not, and the failure is shared across every customer at once.&lt;/p&gt;

&lt;p&gt;With a self-hosted deployment the calculation changes. You own the credentials, so you can rotate them. You own the logs, so you decide the retention. You can look at your own build output. Your breach, if you have one, is yours alone rather than an event affecting a thousand organisations simultaneously. We have written more on that trade in our piece on &lt;a href="https://www.ictcrm.com/self-hosted-crm-2026-data-sovereignty-privacy-cost/" rel="noopener noreferrer"&gt;self-hosted CRM in 2026&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This is not a claim that self-hosting is automatically safer. A neglected self-hosted server is worse than a well-run managed one. The point is narrower: self-hosting converts an invisible risk into a visible one. You can fix problems you can see.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.ictcrm.com/ictcrm-features/" rel="noopener noreferrer"&gt;ICTCRM&lt;/a&gt; is open source and self-hosted for that reason. You read the code, you place it on infrastructure you control, and you configure logging to whatever standard your regulator or your board expects rather than whatever your vendor decided was cost-effective. For anyone holding donor, patient or client records, that control tends to matter more than the convenience it costs. It matters most in regulated sectors, which is part of why &lt;a href="https://www.ictcrm.com/healthcare-fastest-growing-crm-vertical-self-hosted/" rel="noopener noreferrer"&gt;healthcare is the fastest-growing self-hosted CRM vertical&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to do this week
&lt;/h2&gt;

&lt;p&gt;If you run any CRM software, hosted or otherwise, three checks are worth doing before the weekend.&lt;/p&gt;

&lt;p&gt;Pull your own production JavaScript bundle and search it for credential patterns. Check whether your source maps are publicly fetchable. Then look at whether your object-level access logs are on and how long they are kept.&lt;/p&gt;

&lt;p&gt;If you use a hosted provider and cannot check those things yourself, ask them. The answers you get, and how quickly you get them, will tell you a lot. The strength of a vendor's security is usually visible in how comfortable they are being asked about it. We covered the related pattern of attackers simply logging in with valid credentials in &lt;a href="https://www.ictcrm.com/crm-software-breach-credentials-2026/" rel="noopener noreferrer"&gt;an earlier piece on CRM credential abuse&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What caused the Beacon CRM breach?
&lt;/h3&gt;

&lt;p&gt;A compromised AWS access key, which Beacon says was likely exposed in publicly available JavaScript build artifacts. The attacker used it to reach an environment holding customer database backups and downloaded them. The earliest malicious activity found was 27 July 2026.&lt;/p&gt;

&lt;h3&gt;
  
  
  Was the stolen data encrypted?
&lt;/h3&gt;

&lt;p&gt;The database backups were encrypted, but Beacon accepts that the attacker may have decrypted them before taking them. Encryption at rest only helps when the ability to decrypt sits on a separate identity from the ability to read storage.&lt;/p&gt;

&lt;h3&gt;
  
  
  How does an AWS key end up in public JavaScript?
&lt;/h3&gt;

&lt;p&gt;Front-end bundlers inline environment variables into the compiled output so the browser can read them, and they often write a source map alongside it. If a cloud credential is in that environment file, it becomes a static asset anyone can fetch. The repository stays clean the whole time, which is why the mistake is easy to miss.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why does the logging gap matter so much?
&lt;/h3&gt;

&lt;p&gt;Because when logs cannot show which specific records were accessed, the organisation has to assume everything was taken. Beacon's own assessment, based on transfer volume, is that all data in the database was exported. Better logging often turns a total-loss notification into a much narrower one.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does self-hosted CRM software prevent this kind of breach?
&lt;/h3&gt;

&lt;p&gt;It does not prevent mistakes, but it puts them within your reach. You control credential rotation, log retention and build pipeline, and you can inspect all three. With a hosted provider you have no way to verify any of it until you receive a notification email.&lt;/p&gt;

&lt;h3&gt;
  
  
  What should I check first?
&lt;/h3&gt;

&lt;p&gt;Fetch your deployed JavaScript bundle and search it for credential patterns, confirm your source maps are not publicly reachable, and verify that object-level access logging is enabled with retention longer than a realistic attacker dwell time. Those three take under a day and cover the failures in this incident.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.ictcrm.com/data-sovereignty-self-hosted-open-source-crm/" rel="noopener noreferrer"&gt;Data sovereignty and self-hosted open source CRM&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.ictcrm.com/crm-software-breach-credentials-2026/" rel="noopener noreferrer"&gt;When nobody hacks the CRM and just logs in instead&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.ictcrm.com/mcp-ai-agent-open-source-crm-agentic-sales/" rel="noopener noreferrer"&gt;MCP, AI agents and open source CRM&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.ictcrm.com/ictcrm-features/" rel="noopener noreferrer"&gt;ICTCRM features&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Details of the Beacon incident are drawn from the company's own disclosure and root-cause update, reported by SecurityWeek on 14 August 2026.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>AI Mental Health Support: What a New Audit Found in Long Conversations</title>
      <dc:creator>Tahir Almas</dc:creator>
      <pubDate>Mon, 17 Aug 2026 04:43:29 +0000</pubDate>
      <link>https://dev.to/tahiralmas/ai-mental-health-support-what-a-new-audit-found-in-long-conversations-2afo</link>
      <guid>https://dev.to/tahiralmas/ai-mental-health-support-what-a-new-audit-found-in-long-conversations-2afo</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://roshni.online/ai-mental-health-support-long-conversation-audit/" rel="noopener noreferrer"&gt;roshni.online&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A study published in Nature Medicine this month tested AI mental health support the way people actually use it, across a whole conversation rather than one question and one answer. The finding that matters: concerning replies were rare at the opening and became more likely as the chat went on. If you only test the first message, you miss almost everything worth testing.&lt;/p&gt;

&lt;p&gt;The work came out of UCL, the University of Oxford and the UK AI Security Institute. They built a framework called SIM-VAIL, ran 810 conversations across nine frontier models, and had clinicians and automated raters score more than 90,000 individual exchanges. It is the largest look so far at what these tools do over time rather than in a snapshot.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why single-reply testing misses the problem
&lt;/h2&gt;

&lt;p&gt;Most safety benchmarks work like an exam question. One prompt goes in, one reply comes out, and someone decides whether that reply was acceptable. It is a reasonable way to catch a model that will hand out a dangerous instruction on request. It is a poor way to catch anything that builds up.&lt;/p&gt;

&lt;p&gt;Dr. Matthew Nour, the study's senior author, put it plainly: many existing benchmarks assess a single message, while the risks in mental health conversations emerge gradually as the exchange develops. Nobody in distress opens with their hardest sentence. They circle it. They test the water with something small, see how it lands, and go a little further.&lt;/p&gt;

&lt;p&gt;Single-reply benchmarks test the safest end of the conversation. The study looked at the end where people actually are.&lt;/p&gt;

&lt;p&gt;So the researchers built simulated users with specific vulnerabilities, including depression, mania, psychosis, obsessive-compulsive patterns and insecure attachment. Each simulated user came in with an intent, such as getting the chatbot to agree with them, to downplay how bad things were, or to endorse an action that would make things worse. Thirty user profiles in total, held in conversation long enough for patterns to show.&lt;/p&gt;

&lt;h2&gt;
  
  
  The loop they named
&lt;/h2&gt;

&lt;p&gt;The clearest thing to come out of the study is a named pattern: the Vulnerability-Amplifying Interaction Loop, or VAIL. It describes what happens when a reply that looks supportive on its own quietly reinforces the exact thinking that caused the difficulty in the first place.&lt;/p&gt;

&lt;p&gt;Read any single turn in one of these loops and it sounds fine. Warm, respectful, non-judgemental. Read four turns and you can see the person being walked somewhere they should not go, with the model agreeing at every step.&lt;/p&gt;

&lt;p&gt;Each turn reads as kind. The problem only appears when you look at the sequence.&lt;/p&gt;

&lt;p&gt;Here is the shape of it. Someone says they do not really need their next appointment because they can manage on their own. The model says it makes sense to trust yourself, you know your situation best. That reply is not wrong in isolation. Plenty of people do know their own situation. But to someone whose illness is currently telling them they are fine, being agreed with reads as confirmation. So they go a step further and wonder aloud whether they should stop the medication too. And round it goes.&lt;/p&gt;

&lt;p&gt;The same loop runs differently depending on the vulnerability. With grandiose thinking, agreement inflates the plan. With obsessive checking, reassurance feeds the next question. With insecure attachment, the model's endless availability becomes something to lean on instead of a person. Same mechanism, different damage.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the numbers actually said
&lt;/h2&gt;

&lt;p&gt;Concerning behaviour was widespread across the models tested, which included systems from Anthropic, OpenAI, Google, xAI and Meta. It was also significantly less common in newer model versions than older ones, which is worth saying clearly because it is the encouraging half of the result. The direction of travel is right.&lt;/p&gt;

&lt;p&gt;Two other findings stood out. First, whether a model behaved safely depended heavily on the user's psychological context, not just on what was asked. The same question from two different simulated users produced meaningfully different risk. That is inconvenient for anyone hoping a fixed list of banned topics will solve this.&lt;/p&gt;

&lt;p&gt;Second, and this is the practical one: when the researchers replaced a single concerning response early in a conversation with a better one, the exchanges that followed were safer. The loop is not inevitable. It has a cheapest point of repair, and that point is early. Once a belief has been agreed with four times, you are no longer offering a perspective, you are arguing with something the conversation itself built.&lt;/p&gt;

&lt;p&gt;The automated scoring agreed substantially with the clinicians, which is what makes the framework usable at scale. The team has released an open SIM-VAIL Explorer so others can look at the conversations rather than take the summary on trust.&lt;/p&gt;

&lt;h2&gt;
  
  
  One caveat worth stating plainly
&lt;/h2&gt;

&lt;p&gt;This was an adversarial stress test. The simulated users were built to push. That is the correct way to find a failure mode, and it is not an estimate of how often this happens in ordinary use. Most conversations people have with these tools are not like the ones in this study, and reading the results as "AI mental health support is dangerous" gets the finding backwards.&lt;/p&gt;

&lt;p&gt;The honest reading is narrower and more useful. There is a specific pattern, it appears under specific conditions, it is worse in older models, and it is fixable early. That is a design problem with a known shape, which is much better news than a vague warning.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this changes for anyone building or using these tools
&lt;/h2&gt;

&lt;p&gt;If you build something that talks to people about their wellbeing, this study hands you a short list.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Test conversations, not messages.&lt;/strong&gt; Your safety evaluation should run twenty turns with a consistent simulated user, not twenty unrelated prompts. If your current testing is a spreadsheet of one-off questions, it is measuring the easiest part.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Treat agreement as a risk surface.&lt;/strong&gt; The failure here is not rudeness or a refusal. It is warmth applied without judgement. A model that never pushes back is not safe, it is agreeable, and those are different things.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Put the intervention early.&lt;/strong&gt; Since fixing one early turn improved everything after it, the value of catching drift on turn three is far higher than catching it on turn twelve. Build for the early catch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Know where the handoff is.&lt;/strong&gt; Every one of these loops ends somewhere a human should have taken over. The tool's job is to notice that point and say so, not to keep going because it can.&lt;/p&gt;

&lt;p&gt;If you use these tools yourself, the takeaway is simpler. An AI that agrees with everything you say is not confirming you are right. It is doing what it does. The moment you notice you have been agreed with several times in a row about something important, that is the moment to bring in someone who can disagree with you.&lt;/p&gt;

&lt;h2&gt;
  
  
  How we think about this at Roshni
&lt;/h2&gt;

&lt;p&gt;Our free AI assistant is live and it does real work. It answers questions at two in the morning when nothing else is open, it helps people put a difficult situation into words before they speak to anyone, and it takes the pressure off the first step. That first step is often the hardest part of getting &lt;a href="https://roshni.online/online-mental-health-support-how-it-helps/" rel="noopener noreferrer"&gt;mental health support&lt;/a&gt; at all.&lt;/p&gt;

&lt;p&gt;What it does not do is stand in for a qualified person. It is built to notice when a conversation needs someone human and to say so rather than keep talking. We have written before about &lt;a href="https://roshni.online/ai-assistant-knows-its-limits-human-handoff/" rel="noopener noreferrer"&gt;why knowing that limit is the feature&lt;/a&gt;, and this study is a good argument for that position: the risk is not in the AI saying something obviously wrong, it is in the AI being pleasant for slightly too long.&lt;/p&gt;

&lt;p&gt;The same logic applies on the legal side. Our &lt;a href="https://roshni.online/online-legal-consultation-trusted-guidance-from-home/" rel="noopener noreferrer"&gt;legal guidance&lt;/a&gt; works the same way, with the assistant helping you understand your situation and prepare your questions, and a qualified professional handling the part that carries real consequences.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Does this study mean AI mental health support is unsafe?
&lt;/h3&gt;

&lt;p&gt;No. It means a specific failure pattern exists and shows up more in long conversations than short ones. The study used deliberately adversarial simulated users to find that pattern, so the rates it reports are not what a typical user should expect. Newer models performed better than older ones.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the VAIL loop in simple terms?
&lt;/h3&gt;

&lt;p&gt;It is what happens when supportive replies accidentally reinforce the thinking behind someone's difficulty. The person says something shaped by their condition, the AI agrees warmly, the agreement feels like evidence they were right, and they go a step further. Each individual reply looks reasonable. The sequence does not.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why do safety problems appear later in a conversation?
&lt;/h3&gt;

&lt;p&gt;Because context accumulates. Early messages are usually general and easy to answer well. As the exchange continues, the model is working with everything already said, including its own earlier agreement. People also open up gradually, so the hardest material tends to arrive after trust has built.&lt;/p&gt;

&lt;h3&gt;
  
  
  How many conversations did the researchers test?
&lt;/h3&gt;

&lt;p&gt;810 conversations across nine frontier AI models and 30 simulated user profiles, producing more than 90,000 clinical ratings of individual exchanges. The automated scoring agreed substantially with human clinicians.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can this be fixed?
&lt;/h3&gt;

&lt;p&gt;The study found that replacing one concerning response early in a conversation made the following exchanges safer, so yes, and earlier is cheaper. It also found newer models already show significantly less of this behaviour, which suggests current training approaches are moving in the right direction.&lt;/p&gt;

&lt;h3&gt;
  
  
  Should I stop using an AI assistant for emotional support?
&lt;/h3&gt;

&lt;p&gt;Not necessarily. Use it for what it is good at, which is being available immediately, helping you organise your thoughts, and lowering the barrier to the first conversation. Bring in a qualified person for decisions that carry consequences, particularly anything involving treatment or medication.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://roshni.online/ai-mental-health-support-where-it-helps-2026/" rel="noopener noreferrer"&gt;Where AI mental health support actually helps in 2026&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://roshni.online/ai-chatbots-crisis-reach-real-support/" rel="noopener noreferrer"&gt;AI chatbots in a crisis and how people reach real support&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://roshni.online/ai-mental-health-support-regulation-2026/" rel="noopener noreferrer"&gt;How regulation is catching up with AI mental health tools&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://roshni.online/how-roshni-online-client-application-work/" rel="noopener noreferrer"&gt;How the Roshni client application works&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The study is "A clinically validated framework for auditing AI chatbot behavior in mental health interactions", published in Nature Medicine (DOI 10.1038/s41591-026-04577-2), led by researchers at UCL, the University of Oxford and the UK AI Security Institute.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>ethics</category>
      <category>llm</category>
      <category>mentalhealth</category>
    </item>
    <item>
      <title>AI Training Went Everywhere. Proof Did Not. What a Smart Online Exam Measures</title>
      <dc:creator>Tahir Almas</dc:creator>
      <pubDate>Sun, 16 Aug 2026 05:28:44 +0000</pubDate>
      <link>https://dev.to/tahiralmas/ai-training-went-everywhere-proof-did-not-what-a-smart-online-exam-measures-h68</link>
      <guid>https://dev.to/tahiralmas/ai-training-went-everywhere-proof-did-not-what-a-smart-online-exam-measures-h68</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://www.ictlms.net/ai-training-proof-smart-online-exam/" rel="noopener noreferrer"&gt;ictlms.net&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Most organizations can tell you exactly how many people completed AI training last quarter. Very few can tell you which of those people can now do the work. A smart online exam is what closes that distance, and the numbers coming out of 2026 suggest almost nobody has built one yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  The number that should be bothering your board
&lt;/h2&gt;

&lt;p&gt;IDC expects more than 90% of global enterprises to hit critical AI skills shortages by 2026, and puts the price of sustained skills gaps at up to $5.5 trillion in delayed products, quality problems, missed revenue and lost ground to competitors. That is not a training-department number. That is a business number.&lt;/p&gt;

&lt;p&gt;Sitting next to it is a stranger one. Around 94% of CEOs and HR chiefs name AI as their top in-demand skill, yet only about a third of leaders believe they have actually prepared their people. Only a third of organizations describe themselves as fully ready to adopt AI-driven ways of working, and only a third of employees report receiving any AI training at all in the past year.&lt;/p&gt;

&lt;p&gt;So the spending is happening and the confidence is not arriving with it. When you line the survey figures up, the shape of the problem gets obvious.&lt;br&gt;
Four percentages, one story. The fall from 82 to 18 happens entirely in the space where nobody is testing anything.&lt;br&gt;
Roughly 82% of enterprise leaders say their organization provides AI training and 68% report a dedicated program. Ask the employees who sat through it and only about 18% say it prepared them to work independently. Companies are spending in the region of $1,200 a head per year on this.&lt;/p&gt;

&lt;h2&gt;
  
  
  Completion is not capability, and everyone quietly knows it
&lt;/h2&gt;

&lt;p&gt;Here is the uncomfortable bit. The metric almost every organization reports upward is completion. Seats filled, modules finished, quiz passed, badge issued. It is easy to collect, it goes up and to the right, and it measures attendance.&lt;br&gt;
Same person, same course. One column answers whether they turned up, the other answers whether they can do the job.&lt;br&gt;
A ten question quiz at the end of a module tests whether someone remembers the slide from eleven minutes ago. That is recall, and recall decays fast. Six weeks later most of it is gone, and the completion record still says complete, forever.&lt;/p&gt;

&lt;p&gt;My honest view is that this is not laziness. Completion is measured because it is cheap, and capability is not measured because building a real assessment used to mean writing scenarios, marking them by hand, and finding humans with time to do it. That constraint has changed, which is exactly why the excuse has run out.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a smart online exam has to test if it is going to mean anything
&lt;/h2&gt;

&lt;p&gt;Testing AI skills with multiple choice is close to useless. The whole point of the skill is judgment under messy conditions, and you cannot get at judgment with four options and one right answer.&lt;/p&gt;

&lt;p&gt;The tests that predict real performance tend to do four things. They hand over a task rather than a question, using an actual document, dataset or customer message from the business. They let the candidate use AI tools during the exam, because banning the tool you are assessing them on makes no sense. They plant something wrong in the AI output and see whether it gets caught, since spotting a confident wrong answer is the skill that separates competent from dangerous. And they ask for the reasoning, not just the result.&lt;/p&gt;

&lt;p&gt;That last one is where &lt;a href="https://www.ictlms.net/ai-online-exam-software/" rel="noopener noreferrer"&gt;AI-assisted grading&lt;/a&gt; earns its place. Marking a few thousand written justifications by hand is what killed this idea in the past. A model can read them all, group the reasoning patterns, flag the weak ones, and hand a human a short pile to review instead of the whole stack.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep the proportions sane
&lt;/h2&gt;

&lt;p&gt;There is a real risk of overcorrecting here. An internal readiness check does not need the machinery of a licensing exam. If the result feeds a training plan, keep it light: open book, real tasks, no camera, no lockdown browser. Save the heavier controls for the cases where the certificate travels outside the company and carries weight with a customer or a regulator.&lt;/p&gt;

&lt;p&gt;We have argued before that &lt;a href="https://www.ictlms.net/ai-graded-exam-software-comparison/" rel="noopener noreferrer"&gt;AI should flag and humans should decide&lt;/a&gt;, and that applies just as much to grading as it does to integrity monitoring. An automated score that nobody can explain to the person who received it will not survive its first appeal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this fits with the systems you already run
&lt;/h2&gt;

&lt;p&gt;Nobody needs to replace their LMS to do this, and I would push back on any vendor who suggests it. Course delivery, enrolment and records are fine where they are. What is usually missing is the assessment layer that produces evidence.&lt;/p&gt;

&lt;p&gt;ICTExam is built to sit in that gap. It connects into an existing platform over &lt;a href="https://www.ictlms.net/support/lti-integration-guide/" rel="noopener noreferrer"&gt;LTI 1.3&lt;/a&gt;, so learners launch an exam from the course they are already in and results flow back without anyone exporting spreadsheets. If you want the full picture of what it does, the &lt;a href="https://www.ictlms.net/features/" rel="noopener noreferrer"&gt;feature list&lt;/a&gt; and the &lt;a href="https://www.ictlms.net/integrations/" rel="noopener noreferrer"&gt;integrations page&lt;/a&gt; cover it in more detail than a blog post should.&lt;/p&gt;

&lt;h2&gt;
  
  
  A 60 day version you can actually run
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pick one role, not the whole company.&lt;/strong&gt; Customer support, or analysts, or the sales team. A single role gives you a clean signal and a short argument.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write down four things that role should be able to do with AI.&lt;/strong&gt; If you cannot name four, the training probably had no target either.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build three tasks from real work.&lt;/strong&gt; Last month's actual tickets or documents, lightly anonymized. Invented scenarios produce invented results.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Salt one task with a plausible AI error.&lt;/strong&gt; A wrong figure, an invented policy, a citation that does not exist. This single item will tell you more than the other two combined.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run it open book with the tools allowed&lt;/strong&gt; and give people a time box rather than a lockdown.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Grade with AI, review a sample by hand.&lt;/strong&gt; Read every flagged script and a random 10% of the rest, then check whether the machine and the humans agreed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retest the same group at six months.&lt;/strong&gt; The retest is where you find out whether anything stuck, and it is the step everyone skips.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Run that once and you will have something no completion report can give you: a defensible statement about what a specific group of people can do, with the evidence attached.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Should employees be allowed to use AI during an AI skills exam?
&lt;/h3&gt;

&lt;p&gt;Yes, in almost every case. You are assessing how well they work with the tool, and taking it away tests something else entirely. The exception is a foundational test where you need to know they understand the underlying material without help.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is AI grading reliable enough for a workplace assessment?
&lt;/h3&gt;

&lt;p&gt;For structured, scenario-based answers with a clear rubric, it is good enough to do the first pass and it is dramatically more consistent than a tired human at 6pm. Keep a person reviewing flagged and borderline scripts. Fully automated grading with no human in the loop is not something we would recommend for anything that affects someone's job.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do we need proctoring for internal assessments?
&lt;/h3&gt;

&lt;p&gt;Usually not. If the result guides training rather than gating pay or promotion, cheating mostly harms the cheat and pollutes your data. Add controls when the stakes rise, and tell people clearly what is being monitored when you do.&lt;/p&gt;

&lt;h3&gt;
  
  
  How often should people be retested?
&lt;/h3&gt;

&lt;p&gt;Every six months works well for AI skills, mainly because the tools change so fast that a test from a year ago is measuring a product that no longer behaves the same way. Annual is the minimum I would defend.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can this run alongside Moodle or another LMS?
&lt;/h3&gt;

&lt;p&gt;Yes. LTI 1.3 is the standard route and it means learners never leave the platform they know. See the &lt;a href="https://www.ictlms.net/support/lti-integration-guide/" rel="noopener noreferrer"&gt;LTI integration guide&lt;/a&gt; for how the launch and grade passback work.&lt;/p&gt;

&lt;h3&gt;
  
  
  What size organization is this worth doing for?
&lt;/h3&gt;

&lt;p&gt;Below roughly 50 people a manager can usually judge capability by watching. Past that, informal judgment stops scaling and starts being wrong in ways nobody notices.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.ictlms.net/ai-online-exam-software/" rel="noopener noreferrer"&gt;AI online exam software: run secure, scalable assessments&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.ictlms.net/ai-graded-exam-software-comparison/" rel="noopener noreferrer"&gt;Top AI graded exam tools compared&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.ictlms.net/features/" rel="noopener noreferrer"&gt;ICTExam features&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.ictlms.net/ictclass/" rel="noopener noreferrer"&gt;ICTClass&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.ictlms.net/packages/" rel="noopener noreferrer"&gt;Packages and pricing&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you already run the training and just need the proof, the quickest way to see whether this fits is to put one real task in front of it. Take a look at the &lt;a href="https://www.ictlms.net/demo/" rel="noopener noreferrer"&gt;ICTExam demo&lt;/a&gt;, or start with &lt;a href="https://www.ictlms.net/" rel="noopener noreferrer"&gt;the platform overview&lt;/a&gt; if you are still scoping the problem.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Your Analog Fax Line Has 180 Days. Fax Server Software Is the Way Out</title>
      <dc:creator>Tahir Almas</dc:creator>
      <pubDate>Sun, 16 Aug 2026 05:26:00 +0000</pubDate>
      <link>https://dev.to/tahiralmas/your-analog-fax-line-has-180-days-fax-server-software-is-the-way-out-oi6</link>
      <guid>https://dev.to/tahiralmas/your-analog-fax-line-has-180-days-fax-server-software-is-the-way-out-oi6</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://www.ictfax.com/fax-server-software-copper-sunset/" rel="noopener noreferrer"&gt;ictfax.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;AT&amp;amp;T started permanently decommissioning copper in roughly 500 wire centers in June 2026. Business lines get a minimum of 180 days of notice, and then the dial tone stops for good. If a fax machine still sits on one of those lines, fax server software is the part of the answer most offices have not thought about yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the FCC actually changed back in March
&lt;/h2&gt;

&lt;p&gt;On 26 March 2026 the FCC unanimously adopted what it calls the Network and Services Modernization Order. The written version landed the next day, and it hit the Federal Register on 20 April.&lt;/p&gt;

&lt;p&gt;The order did not tell anyone to switch off copper. It removed most of the reasons a carrier had to move slowly. Network-change disclosure filings went away. A carrier still needs a Section 214 application to fully retire a service, but that application is now automatically granted after 31 days if nobody sustains an objection, and the same 31 days applies to every carrier rather than a longer clock for the big ones. State rules that conflicted with any of this were preempted.&lt;/p&gt;

&lt;p&gt;Your protection is the notice period, and that survived. Business customers get a minimum of 180 days. Residential customers get 90. That letter is the whole warning you receive, so whoever opens the post at your site is now part of your telecom strategy, which is a slightly alarming thing to write down.&lt;br&gt;
The dates worth putting in a calendar. The 180 day notice is the only warning most businesses will get.&lt;/p&gt;

&lt;h2&gt;
  
  
  The dates that matter on the AT&amp;amp;T footprint
&lt;/h2&gt;

&lt;p&gt;AT&amp;amp;T stopped taking orders for new copper lines on 15 October 2025, which was the quiet signal that everything after it was cleanup. June 2026 was the first real wave, around 500 wire centers, roughly a tenth of the footprint. When a wire center goes, every POTS line it serves goes with it.&lt;/p&gt;

&lt;p&gt;In July the company filed again, this time covering residential POTS, business POTS and some legacy wireline VoIP across parts of 55 wire centers in 13 states. If that clears, discontinuance can start as early as 16 October 2026. The company expects to be off copper across the vast majority of its footprint by the end of 2029.&lt;/p&gt;

&lt;p&gt;Don't plan around 2029. Plan around the month your wire center shows up in a filing, because that is the one that turns into a letter with a date on it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fax is the line nobody owns
&lt;/h2&gt;

&lt;p&gt;Picture a 40 person clinic that moved its phones to VoIP three years ago and felt good about it. What stayed behind on copper: the fire alarm dialer, the elevator phone, a postage meter, and the fax in the records room. None of those appear on the network diagram. They appear on a carrier invoice that goes to facilities, not IT.&lt;/p&gt;

&lt;p&gt;That split is why fax gets missed. The people who would notice the FCC news are not the people who receive the bill. And of that group, the fax line is the easiest one to move, because alarm and elevator circuits carry code and inspection requirements that fax simply does not. So fax should be first on the list, not last.&lt;/p&gt;

&lt;h2&gt;
  
  
  The quick swap that quietly loses pages
&lt;/h2&gt;

&lt;p&gt;The obvious fix is an ATA. Put a small box between the fax machine and your broadband, keep the machine, done by lunchtime. It works often enough that people trust it, and that is the problem.&lt;/p&gt;

&lt;p&gt;Fax tones are not speech, and the codecs and jitter buffers in a voice path were designed for speech. Drop a few packets in the middle of a page and the far end gets garbage. Error correction retransmits, which turns a 30 second fax into a four minute one, and then something times out. A single page test fax sails through. The 12 page referral packet at 4pm does not, and nobody finds out until someone phones asking where it went.&lt;/p&gt;

&lt;p&gt;If you are keeping a physical machine, insist on T.38 on both the ATA and the trunk, and test with a long document at fine resolution. A one page test proves almost nothing.&lt;br&gt;
The middle path is the one that bites, because the line still appears to work right up until a page goes missing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why fax server software ends the problem instead of moving it
&lt;/h2&gt;

&lt;p&gt;The other route is to stop having an analog line at all. Fax becomes an application on a server: staff send from email, from a browser, from a desktop print driver, or from another system over an API. The server talks T.38 to your SIP trunk, handles retries, and writes down what happened.&lt;/p&gt;

&lt;p&gt;That last part is the underrated bit. You get delivery receipts, per user and per department routing, inbound faxes landing in the right mailbox instead of a paper tray, and a searchable record of every page. &lt;a href="https://www.ictfax.com/ictfax-the-best-open-source-email-to-fax-server-software-for-businesses/" rel="noopener noreferrer"&gt;Sending a fax from email&lt;/a&gt; removes the walk to the machine, which sounds trivial until you count how many times a day it happens.&lt;/p&gt;

&lt;p&gt;ICTFax is our take on this. It is open source, &lt;a href="https://www.ictfax.com/freeswitch-based-fax-server/" rel="noopener noreferrer"&gt;built on FreeSWITCH&lt;/a&gt;, multi-tenant, and you host it yourself. There is a &lt;a href="https://ictfax.com/fax-rest-api-guide.html" rel="noopener noreferrer"&gt;REST API&lt;/a&gt; if you want your EHR or document system pushing faxes without a human in the loop. If you are already tearing out the line, changing the machine at the same time costs less than doing the job twice.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to do in the next 30 days
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pull the carrier bill, not the network diagram.&lt;/strong&gt; Analog lines hide from documentation but never from billing. Every line item you cannot explain is a line you need to explain.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Find out which wire center serves each address&lt;/strong&gt; and whether it appears in a filed discontinuance. Your carrier rep can tell you. Ask in writing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sort by what breaks.&lt;/strong&gt; Alarm and elevator lines have inspection rules attached. Fax usually does not, so move fax first and buy yourself time on the hard ones.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decide port or retire for each fax number.&lt;/strong&gt; Numbers printed on letterhead, in payer directories, or on referral forms need porting. Numbers three people remember can go.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test with 15 pages at fine resolution&lt;/strong&gt; to the partner or payer you fax most, not to the machine down the hall.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run both in parallel for two weeks&lt;/strong&gt; if the notice window still allows it. It usually does if you start early.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Porting takes longer than anyone plans for. If your 180 days is already half gone before you start the port request, you will spend the last month of it making phone calls you did not enjoy.&lt;/p&gt;

&lt;h2&gt;
  
  
  The compliance angle that shows up during an audit
&lt;/h2&gt;

&lt;p&gt;Misdirected faxes are a steady source of small breach reports in healthcare. A fax machine gives you a curled confirmation slip and nothing else. Six months later, when someone asks who sent what to which number, there is no answer in the building.&lt;/p&gt;

&lt;p&gt;A server gives you the sender, the document, the timestamp, the destination number, and whether the far end confirmed. That is not compliance on its own, and I would not claim otherwise. You still need access controls, encryption in transit on the &lt;a href="https://www.ictfax.com/implementing-secure-sip-communications-in-ictfax/" rel="noopener noreferrer"&gt;SIP side&lt;/a&gt;, and a signed agreement with anyone else who touches the traffic. What it gives you is the record, and the record is the thing you cannot reconstruct after the fact.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Does the FCC order ban fax?
&lt;/h3&gt;

&lt;p&gt;No. Nothing in the order says anything about fax. It changes how carriers retire copper facilities. Fax over IP is unaffected, and plenty of industries still run on it because their partners do.&lt;/p&gt;

&lt;h3&gt;
  
  
  Will I lose my fax number?
&lt;/h3&gt;

&lt;p&gt;Not if you port it. The number and the copper pair are separate things. Start the port before the line is disconnected, because porting from a dead number is a much worse conversation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I have to get rid of the fax machine?
&lt;/h3&gt;

&lt;p&gt;No, but you will need an ATA with working T.38 and a trunk that supports it. Keeping the machine is reasonable if staff genuinely feed paper into it all day. If most of what they fax started life as a PDF, the machine is a detour.&lt;/p&gt;

&lt;h3&gt;
  
  
  My carrier says my line is not affected. Am I fine?
&lt;/h3&gt;

&lt;p&gt;For now. Waves are being filed continuously, so not affected really means not in this particular filing. Ask which wire center serves you and set a reminder to check again in six months.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can fax server software run on our own servers?
&lt;/h3&gt;

&lt;p&gt;Yes. ICTFax is self-hosted, which is the usual requirement when the documents are patient records or legal files and the answer to where this data lives has to be a room you can point at.&lt;/p&gt;

&lt;h3&gt;
  
  
  How many trunk channels do we need?
&lt;/h3&gt;

&lt;p&gt;Count concurrent faxes at your busiest hour, not total volume. Most small offices never exceed two or three at once. Buying for peak monthly volume is how people end up paying for capacity that sits idle.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.ictfax.com/freeswitch-based-fax-server/" rel="noopener noreferrer"&gt;FreeSWITCH based fax server&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.ictfax.com/ictfax-whitelabel-and-multi-tenant-fax-server-software/" rel="noopener noreferrer"&gt;White label and multi-tenant fax server software&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.ictfax.com/user-guide/" rel="noopener noreferrer"&gt;ICTFax user guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.ictfax.com/packages-pricing/" rel="noopener noreferrer"&gt;Packages and pricing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.ictfax.com/implementing-secure-sip-communications-in-ictfax/" rel="noopener noreferrer"&gt;Implementing secure SIP communications in ICTFax&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your copper notice has already arrived, the fastest way to see whether this fits is to try it. Book a look at the &lt;a href="https://www.ictfax.com/demo/" rel="noopener noreferrer"&gt;ICTFax demo&lt;/a&gt;, or start from &lt;a href="https://www.ictfax.com/" rel="noopener noreferrer"&gt;the ICTFax overview&lt;/a&gt; if you are still working out what you actually need.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>We Released Two More Open Source Libraries: Streaming TTS for Calls and 21 CRM Connectors</title>
      <dc:creator>Tahir Almas</dc:creator>
      <pubDate>Thu, 13 Aug 2026 18:24:11 +0000</pubDate>
      <link>https://dev.to/tahiralmas/we-released-two-more-open-source-libraries-streaming-tts-for-calls-and-21-crm-connectors-29d8</link>
      <guid>https://dev.to/tahiralmas/we-released-two-more-open-source-libraries-streaming-tts-for-calls-and-21-crm-connectors-29d8</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://ictinnovations.com/open-source-piper-tts-server-php-crm-connectors/" rel="noopener noreferrer"&gt;ictinnovations.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;The short version&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;piper-tts-server&lt;/strong&gt; gives you paced, streaming Piper text to speech for real time voice, over Asterisk AudioSocket, RTP or a browser WebSocket. On PyPI now.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;php-crm-connectors&lt;/strong&gt; puts 21 CRMs behind a single PHP interface, so pushing a call outcome into Zoho or Salesforce is the same line of code. On Packagist now.&lt;/li&gt;
&lt;li&gt;Both came out of &lt;a href="https://www.ictcontact.com/" rel="noopener noreferrer"&gt;ICTContact&lt;/a&gt;, generalized so they no longer depend on any ICT product.&lt;/li&gt;
&lt;li&gt;MIT licensed, and both live under &lt;a href="https://github.com/ictinnovations" rel="noopener noreferrer"&gt;github.com/ictinnovations&lt;/a&gt; with the rest of our open source work.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We've open sourced two more libraries from our own stack. The first solves paced streaming text to speech on live phone calls, which is harder than it sounds. The second collapses 21 different CRM integrations into one PHP interface. Both are MIT licensed, both install from the package manager your ecosystem already uses, and neither needs any ICT product to run.&lt;/p&gt;

&lt;h2&gt;
  
  
  piper-tts-server: open source VoIP text to speech that survives a live call
&lt;/h2&gt;

&lt;p&gt;If you've tried bolting &lt;a href="https://github.com/OHF-Voice/piper1-gpl" rel="noopener noreferrer"&gt;Piper&lt;/a&gt; onto a phone call, you already know the symptoms. Dead air before the greeting. The first couple of words missing. Callers who only hear the tail end of every sentence. We hit all three building the AI voice agent in ICTContact, and the fixes turned out to be the same three things every time.&lt;/p&gt;

&lt;p&gt;Loading the voice per request is the obvious one, once you measure it. &lt;code&gt;PiperVoice.load()&lt;/code&gt; costs somewhere between 2.5 and 5.5 seconds on our hardware, so doing it per utterance means that much silence before the caller hears anything. The library caches voices process wide and reuses them.&lt;/p&gt;

&lt;p&gt;The second one bit us for a while before we understood it. Piper phonemizes through espeak-ng, and that C API isn't thread safe. Two calls synthesizing at the same moment produced audio that sounded like a bad radio. All synthesis now runs behind a single lock.&lt;/p&gt;

&lt;p&gt;The third is the one I'd never have guessed, and it's the reason this library exists at all rather than being a fifty line wrapper.&lt;/p&gt;

&lt;p&gt;Same sentence and same synthesis. The only thing that changes is when each frame is released.&lt;/p&gt;

&lt;p&gt;Real time transports forward each frame the instant it arrives. Push a whole utterance at once and you overrun the far end's jitter buffer, so the caller hears the end of the sentence and none of the start. The fix is to release exactly one frame per interval on a monotonic deadline. Here's the subtle part that took us longest: if synthesis stalls, that deadline goes stale, and the frames after the stall burst to catch up and clip the words right behind them. So the writer re-clamps the deadline to now on every single frame, not just at the start of a talk spurt.&lt;/p&gt;

&lt;p&gt;Defaults are 8 kHz slin16 at 20 ms frames, which is what telephony wants. Set one environment variable for 16 kHz if you're doing wideband. There's an HTTP service if you want one, but you can also import it as a library and keep HTTP out of the media path entirely, which is what we'd recommend for anything latency sensitive.&lt;/p&gt;

&lt;p&gt;One licensing note, because it matters and gets glossed over elsewhere. The package itself is MIT and ships no Piper code. Piper is an optional extra that's imported lazily, so the core installs with numpy alone. The maintained Piper release is GPL-3.0-or-later, so if you install the piper extra, that combined installation is subject to GPL terms. We'd rather say that plainly than let someone find out at their own legal review.&lt;/p&gt;

&lt;h2&gt;
  
  
  php-crm-connectors: one interface, 21 CRMs
&lt;/h2&gt;

&lt;p&gt;This one comes from a much less glamorous problem. Every contact center deployment wants its own CRM wired in, and after enough of them you're maintaining a heap of near identical integration classes that each do the same job in a slightly different dialect. ICTContact has 28 CRM modules in production. That's a lot of surface area for what is, honestly, one repeated task.&lt;/p&gt;

&lt;p&gt;Adding a CRM means implementing one interface, not teaching your application another SDK.&lt;/p&gt;

&lt;p&gt;The library is deliberately narrower than a general CRM SDK. The job it does is find a contact by phone or email, create it if it isn't there, then attach the call outcome as a note and an activity. That's a single call. If you don't want the campaign orchestration, the smaller methods are public too, so you can authenticate, look a contact up, create it and annotate it directly.&lt;/p&gt;

&lt;p&gt;Auth is where CRM vendors differ most and where integration code usually rots. OAuth2 with refresh tokens for Zoho, Salesforce, Dynamics 365, Creatio, Sugar and Suite. API keys or plain tokens for the rest. Your calling code doesn't have to know which, and access tokens cache through a pluggable store, so you can drop in Redis or a database instead of the in memory default.&lt;/p&gt;

&lt;p&gt;The design decision I expect to get argued about is that there's no PSR-18 HTTP client. Every request goes through a single &lt;code&gt;http()&lt;/code&gt; method on each connector. The upside is real: that seam is the only place I/O happens, so a connector is tested by overriding one method and the test suite never touches a live CRM. If you think the dependency earns its keep, the issue tracker is open.&lt;/p&gt;

&lt;p&gt;Covered so far: Zoho, Pipedrive, Salesforce, Dynamics 365, SugarCRM, SuiteCRM, Freshsales, Copper, Capsule, ActiveCampaign, Close, Keap, Zendesk Sell, monday, Streak, Vtiger, Apptivo, Agile, Creatio, Less Annoying and YetiForce. No HubSpot yet, which is the one people ask about most.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why we keep pulling pieces out of our products
&lt;/h2&gt;

&lt;p&gt;We've been shipping &lt;a href="https://www.ictinnovations.com/open-source-voip-software-top-ip-telephony-application" rel="noopener noreferrer"&gt;open source VoIP software&lt;/a&gt; since 2006, and the pattern that keeps proving itself is that the reusable part of a product is rarely the product. It's the layer underneath. Pacing frames correctly, or normalizing 21 CRM APIs, has nothing to do with what makes ICTContact worth buying, and both are things other teams shouldn't have to rediscover.&lt;/p&gt;

&lt;p&gt;There's a selfish reason too. Code that gets published gets read, and code that gets read gets better. The &lt;a href="https://www.ictinnovations.com/five-open-source-sip-libraries" rel="noopener noreferrer"&gt;SIP libraries we wrote about&lt;/a&gt; years ago are still doing the rounds because someone kept maintaining them in public. We'd like these two to age the same way.&lt;/p&gt;

&lt;p&gt;If you're building in this space, the wider trend is worth reading up on as well. Our take on &lt;a href="https://www.ictinnovations.com/beyond-sip-trunking-the-future-of-ai-powered-voice-services" rel="noopener noreferrer"&gt;AI powered voice services beyond SIP trunking&lt;/a&gt; covers where we think the on call AI layer is going, and piper-tts-server is one concrete piece of that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to get them
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;piper-tts-server&lt;/strong&gt;: &lt;code&gt;pip install "piper-tts-server[all]"&lt;/code&gt; from &lt;a href="https://pypi.org/project/piper-tts-server/" rel="noopener noreferrer"&gt;PyPI&lt;/a&gt;, source at &lt;a href="https://github.com/ictinnovations/piper-tts-server" rel="noopener noreferrer"&gt;github.com/ictinnovations/piper-tts-server&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;php-crm-connectors&lt;/strong&gt;: &lt;code&gt;composer require ictinnovations/php-crm-connectors&lt;/code&gt; from &lt;a href="https://packagist.org/packages/ictinnovations/php-crm-connectors" rel="noopener noreferrer"&gt;Packagist&lt;/a&gt;, source at &lt;a href="https://github.com/ictinnovations/php-crm-connectors" rel="noopener noreferrer"&gt;github.com/ictinnovations/php-crm-connectors&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every open source project we release goes through GitHub first. The full list, products and libraries both, now lives on our &lt;a href="https://www.ictinnovations.com/projects/" rel="noopener noreferrer"&gt;open source projects page&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Do I need an ICT product to use either library?&lt;/strong&gt;&lt;br&gt;
No. Both were stripped of anything ICT specific before release. piper-tts-server needs a Piper voice file, and php-crm-connectors needs PHP 7.4 or newer with curl and json. That's it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is piper-tts-server tied to Asterisk?&lt;/strong&gt;&lt;br&gt;
No, though that's where we use it. It emits raw paced frames, so anything that consumes 20 ms slin16 works, including RTP legs and browser WebSockets. The AudioSocket example in the repo is just the wiring we know best.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does the GPL licence of Piper affect my project?&lt;/strong&gt;&lt;br&gt;
Only if you install the optional piper extra. The package itself is MIT and contains no Piper source, and the core installs with numpy alone. Install the extra and your combined installation falls under GPL-3.0 terms.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I add a CRM that isn't in the list?&lt;/strong&gt;&lt;br&gt;
Yes, and there's a porting guide in the repo docs. You implement one interface and one http method. Pull requests are welcome, and HubSpot is the obvious gap if someone wants a first contribution.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Are these maintained or dumped over the wall?&lt;/strong&gt;&lt;br&gt;
Maintained. Both are in production inside our own products, which is the only maintenance promise worth anything. Issues and pull requests come to the same team that ships ICTContact.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.ictinnovations.com/projects/" rel="noopener noreferrer"&gt;Open source ICT projects&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.ictinnovations.com/open-source-voip-software-top-ip-telephony-application" rel="noopener noreferrer"&gt;Open source VoIP software and top IP telephony applications&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.ictinnovations.com/five-open-source-sip-libraries" rel="noopener noreferrer"&gt;Five open source SIP libraries&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.ictinnovations.com/beyond-sip-trunking-the-future-of-ai-powered-voice-services" rel="noopener noreferrer"&gt;Beyond SIP trunking: the future of AI powered voice services&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.ictcontact.com/" rel="noopener noreferrer"&gt;ICTContact, the platform both libraries came from&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Building something with either library and hit a wall? Open an issue on the repo, or &lt;a href="https://service.ictinnovations.com/submitticket.php" rel="noopener noreferrer"&gt;send us a ticket&lt;/a&gt; if it's about running them alongside an ICT product.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>The EU Now Requires Your Live Support Software to Admit It's AI</title>
      <dc:creator>Tahir Almas</dc:creator>
      <pubDate>Wed, 12 Aug 2026 10:05:28 +0000</pubDate>
      <link>https://dev.to/tahiralmas/the-eu-now-requires-your-live-support-software-to-admit-its-ai-21ii</link>
      <guid>https://dev.to/tahiralmas/the-eu-now-requires-your-live-support-software-to-admit-its-ai-21ii</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://ictdesk.net/blog/eu-ai-act-article-50-live-support-software/" rel="noopener noreferrer"&gt;ictdesk.net&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;On 2 August 2026, Article 50 of the EU AI Act became applicable. If your live support software answers customers with an AI system, you now have to tell them, and you have to tell them at the first interaction rather than somewhere in your terms. This is one of the shortest obligations in the whole regulation and one of the easiest to satisfy. It's also the one most chat widgets currently fail, because the disclosure exists on a settings page nobody reads instead of in the conversation.&lt;/p&gt;

&lt;p&gt;The fix is roughly a sentence of copy and a label on your transcripts. The cost of skipping it is set out in Article 99. So this is worth twenty minutes of your afternoon.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the text actually says
&lt;/h2&gt;

&lt;p&gt;Article 50(1) puts the duty on providers of AI systems intended to interact directly with people: design them so the person is informed they're dealing with an AI system, unless that's obvious to a reasonably well-informed and observant person given the circumstances.&lt;/p&gt;

&lt;p&gt;Article 50(5) adds the timing. The information has to be provided at the latest at the time of the first interaction or exposure. Not on request. Not after the fact. First interaction.&lt;/p&gt;

&lt;p&gt;That's the whole thing as it applies to a support chat. There's no template wording mandated, no logo you have to display, no filing with anyone. You just have to say it, clearly, at the top.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The Act phases in over three years. Chatbot transparency is the milestone that landed this month, which is why it's suddenly everyone's problem.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The exemption people over-read
&lt;/h2&gt;

&lt;p&gt;That "unless it's obvious" clause is doing a lot of work in a lot of internal legal memos right now, and I think most of them are reading it too generously.&lt;/p&gt;

&lt;p&gt;The test isn't whether a technically literate person could infer it. It's whether it's obvious to a reasonably well-informed and observant person in the circumstances. A widget labelled "Chat with us" that opens with "Hi, how can I help today?" and then writes three fluent paragraphs about your refund policy is not obviously a machine. In 2020 it might have been. That's precisely what changed.&lt;/p&gt;

&lt;p&gt;If your bot is a decision tree that offers four buttons and nothing else, you have a decent argument. If it generates prose, you don't, and betting a fine on that reading to save one line of text is a strange trade.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it costs to get wrong
&lt;/h2&gt;

&lt;p&gt;Article 99(4)(g) covers non-compliance with the transparency obligations: administrative fines up to 15 million euro or up to 3 percent of total worldwide annual turnover for the preceding financial year, whichever is higher.&lt;/p&gt;

&lt;p&gt;Realistically, a first enforcement action against a mid-sized SaaS company over a missing chat disclosure is not going to land at 15 million. Regulators have discretion and Article 99 tells them to weigh the nature and gravity of the infringement. But the ceiling exists, and the underlying point is that the EU has classified this as a transparency failure rather than a UX quibble. Once a rule has a fine attached, "we were going to get to it" stops being an answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three places disclosure has to appear
&lt;/h2&gt;

&lt;p&gt;Most teams do the first one and miss the other two.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The middle one is where most implementations break. A visitor who doesn't notice the handoff has been told once, at the start, and then quietly told nothing.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The first reply.&lt;/strong&gt; Before the visitor types anything worth reading, the assistant identifies itself. Something like "Hi, I'm an AI assistant. I can hand you to a person any time you ask." That second sentence isn't required by Article 50, but it turns a legal notice into something the customer is glad to read, which is a better outcome than technical compliance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The handoff.&lt;/strong&gt; When a human agent takes the conversation, say so, with the agent's name. When the conversation goes back to the assistant, say that too. This is the gap in most deployments: the widget discloses once at session start and then switches silently between an AI and a person for the next twenty minutes. The visitor was informed at first interaction and misinformed for the rest of it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The transcript.&lt;/strong&gt; Whatever you store, and whatever you email the customer afterwards, keeps the same labels the live chat had. If a regulator or a customer ever asks how you complied, the transcript is the evidence. A policy page saying you disclose is a claim. A transcript with "AI assistant" on four lines and "Sana, agent" on the next four is a record.&lt;/p&gt;

&lt;h2&gt;
  
  
  How ICTDesk handles it
&lt;/h2&gt;

&lt;p&gt;Our &lt;a href="https://ictdesk.net/features/" rel="noopener noreferrer"&gt;AI agent chat&lt;/a&gt; identifies itself in its opening message and marks the switch in both directions when a conversation moves between the assistant and a human agent. Those labels persist into the stored ticket, so the ticket history shows who said what without anyone having to reconstruct it later.&lt;/p&gt;

&lt;p&gt;Being straight about the limits: the opening line is editable, which means you can edit the disclosure out of it. That's deliberate, because you might be running the widget in a jurisdiction with different wording requirements, or you might want to phrase it in your own voice. But if you're serving EU visitors, don't remove it. If you've customised that greeting at any point in the last year, go and look at what it says now.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bit that isn't about compliance
&lt;/h2&gt;

&lt;p&gt;There's a version of this article that stops at the fine. Here's the part I think matters more.&lt;/p&gt;

&lt;p&gt;Customers work out they're talking to a bot within about two exchanges. They always have. What they resent isn't the bot, it's the two minutes spent trying to establish what they're dealing with, and the feeling that a company hoped they wouldn't notice. Disclosure removes that. It also lets you write a better bot, because an assistant that has admitted what it is can say "I don't have access to your billing record, let me get someone who does" without breaking character.&lt;/p&gt;

&lt;p&gt;The teams I've seen do this well tend to report that their handoff rates go down, not up, which is the opposite of what people expect when they first add the line. My read is that clear framing sets a realistic expectation and people stop testing the bot to see if it's real.&lt;/p&gt;

&lt;h2&gt;
  
  
  A short checklist
&lt;/h2&gt;

&lt;p&gt;Open your own widget as a visitor, in an incognito window, and check four things. Does the first message say it's AI? When you ask for a human, does the transition announce itself? Does the emailed transcript keep the labels? And if you run more than one widget, on a docs site or a pricing page or in-app, does every one of them do all three?&lt;/p&gt;

&lt;p&gt;That last one catches people. The main site widget gets configured carefully and the one embedded in the help centre inherits a default greeting from 2024.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Does this apply if my company isn't in the EU?
&lt;/h3&gt;

&lt;p&gt;The Act reaches providers and deployers placing AI systems on the EU market or whose output is used in the EU, so a US-based SaaS with EU customers is in scope. Geo-gating the disclosure to EU IPs is technically possible and, in my view, a bad idea: you'd be maintaining two behaviours to avoid writing one sentence, and the sentence is good for every visitor anyway.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is a notice in our privacy policy enough?
&lt;/h3&gt;

&lt;p&gt;No. Article 50(5) requires the information at the latest at the time of the first interaction. A privacy policy is neither the first interaction nor something anyone reads. The disclosure has to be in the chat.&lt;/p&gt;

&lt;h3&gt;
  
  
  What about AI that only helps the agent, like reply suggestions?
&lt;/h3&gt;

&lt;p&gt;Article 50(1) covers systems intended to interact directly with natural persons. If a human agent reviews and sends every message, the customer is interacting with the agent. The situation gets murkier when suggestions are auto-sent without review, and if that's your setup, treat it as an AI interaction and disclose.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do we need to disclose on every message, or just once?
&lt;/h3&gt;

&lt;p&gt;Once at the start satisfies the timing rule. But disclose again at every change of speaker, because a session that silently alternates between AI and human leaves the customer without accurate information for most of it. The regulation sets a floor. Handoff labelling is what makes the floor honest.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is there mandatory wording we have to use?
&lt;/h3&gt;

&lt;p&gt;No. Article 50 requires the person be informed, clearly and distinguishably, and leaves the words to you. Plain language beats legal language here. "I'm an AI assistant" does the job better than a formal notice most people skim past.&lt;/p&gt;

&lt;h3&gt;
  
  
  What about voice bots on the phone?
&lt;/h3&gt;

&lt;p&gt;Same obligation, same timing. The AI has to identify itself in its opening turn, before it asks the caller anything. Voice makes the "obvious to a reasonable person" exemption even harder to rely on, since current synthesis is good enough that callers regularly can't tell.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://ictdesk.net/what-is-help-desk-software/" rel="noopener noreferrer"&gt;What is help desk software&lt;/a&gt; for how the chat layer sits inside the wider support stack&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://ictdesk.net/how-to-add-live-chat-to-your-website/" rel="noopener noreferrer"&gt;How to add live chat to your website&lt;/a&gt; if you're setting the widget up for the first time and want the greeting right from the start&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://ictdesk.net/gdpr/" rel="noopener noreferrer"&gt;Our GDPR notes&lt;/a&gt; for the data-protection side, which is a separate obligation from the AI Act&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
    </item>
    <item>
      <title>Your PBX Software Is Only as Safe as the Ports You Left Open</title>
      <dc:creator>Tahir Almas</dc:creator>
      <pubDate>Wed, 12 Aug 2026 10:02:05 +0000</pubDate>
      <link>https://dev.to/tahiralmas/your-pbx-software-is-only-as-safe-as-the-ports-you-left-open-5di8</link>
      <guid>https://dev.to/tahiralmas/your-pbx-software-is-only-as-safe-as-the-ports-you-left-open-5di8</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://ictpbx.com/pbx-software-ports-network-exposure/" rel="noopener noreferrer"&gt;ictpbx.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A PBX in default configuration listens on somewhere between eight and fifteen network ports. Three of them have to face the internet for calls to work. The rest are there for your own convenience, and every one you publish is a listener that answers strangers. In June 2026 the FreeSWITCH project disclosed six advisories in a single day, and five of them needed no credentials at all. Whether that was a patch window or an incident came down to one thing: which ports were reachable.&lt;/p&gt;

&lt;p&gt;This is not a hardening checklist you run once. It's a question you should be able to answer in thirty seconds about any switch you operate, and most people can't.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three ports you actually have to publish
&lt;/h2&gt;

&lt;p&gt;Start from what the job requires rather than from what the installer opened.&lt;/p&gt;

&lt;p&gt;SIP signalling has to be reachable, because carriers and handsets need somewhere to send an INVITE. That's 5060 on UDP and TCP, 5061 for TLS, and often 5080 for a second profile handling external traffic. RTP media has to be reachable, and that's a wide UDP range, commonly 16384 to 32768, because every concurrent call leg needs its own pair. And your tenants need a web panel, which is 443.&lt;/p&gt;

&lt;p&gt;That's it. Everything else on the box is there because someone found it handy during install.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The public column is small on purpose. Everything on the right has a reason to exist and no reason to be reachable from Bulgaria.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The three public ports still need work. SIP on 5060 gets scanned constantly, so you want fail2ban watching for registration failures, per-carrier IP allow lists on your trunk profile, TLS where the far end supports it, and a hard per-tenant concurrent call cap so a compromised extension can't run up forty thousand dollars overnight while you sleep. We wrote about the money side of that in our notes on &lt;a href="https://ictpbx.com/fraud-credit-controls/" rel="noopener noreferrer"&gt;fraud and credit controls&lt;/a&gt;. But those three are the cost of being a phone system. The others are optional, and treating them as optional is the whole point.&lt;/p&gt;

&lt;h2&gt;
  
  
  What June 2026 actually looked like
&lt;/h2&gt;

&lt;p&gt;On 3 June the FreeSWITCH project published six advisories at once. ICTPBX is built on FreeSWITCH, so we read these the way you'd read a letter from your landlord. Two were rated critical, and both were pre-authentication heap buffer overflows.&lt;/p&gt;

&lt;p&gt;CVE-2026-49841 sits in mod_verto, in the code that reads an HTTP POST body. CVE-2026-49840 sits in libesl, in the code that parses a Content-Length header. Neither one asks who you are before it starts parsing. That's what pre-auth means, and it's the only word in a CVE description that reliably predicts how bad your week is about to be.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Sorted by the column that matters. Severity ratings are useful for prioritising a patch queue. The login column tells you whether you can sleep before you finish patching.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The rest of the cluster fills in the picture. CVE-2026-49847 is a stack overflow in the bundled cJSON parser triggered by deeply nested JSON, which matters anywhere the switch parses JSON it didn't write. CVE-2026-49842 turns mod_verto's speed-test frames into a bandwidth amplifier, so your uplink pays for someone else's flood. Two medium ones, CVE-2026-49848 and CVE-2026-49843, cover variable injection and evicting another user's session by guessing its identifier. And three weeks earlier, CVE-2026-45771 landed a denial of service in SIP PUBLISH through XML entity expansion.&lt;/p&gt;

&lt;p&gt;Here's the part worth sitting with. Of those seven, exactly one is on a port you have no choice about. The SIP one. Everything else arrives over 8021 or 8082, and neither of those has any business being open to the internet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Event Socket is a root shell with a friendlier name
&lt;/h2&gt;

&lt;p&gt;Port 8021 is the FreeSWITCH Event Socket. It exists so your application layer can control the switch: originate calls, hang them up, read channel variables, subscribe to events. In ICTPBX that's how ICTCore drives the platform, and it's a good design. The problem is what the protocol assumes about who's connecting.&lt;/p&gt;

&lt;p&gt;The default password in a stock event_socket.conf.xml is ClueCon. It's been ClueCon for over a decade. Anyone scanning port 8021 tries it first, and if it works they don't need an exploit at all, because ESL by design lets you run any API command the switch supports. There is no privilege tier inside ESL. Getting in is the whole game.&lt;/p&gt;

&lt;p&gt;So the mitigation isn't a stronger password, though obviously change it. The mitigation is that 8021 binds to 127.0.0.1, or to a VPN interface if your application layer runs on a different box. If your app server and your switch are the same machine, there is no argument for binding that socket to anything else, and the ACL in event_socket.conf.xml should say loopback.auto on top of that. Two locks on a door nobody should be able to find. Our &lt;a href="https://ictpbx.com/ictpbx-docs/system-admin/ictpbx-freeswitch-config/" rel="noopener noreferrer"&gt;FreeSWITCH configuration notes&lt;/a&gt; cover where those files live in a standard install.&lt;/p&gt;

&lt;p&gt;I'd go further. If you're running a multi-tenant platform and you can't say from memory what 8021 binds to, stop reading and go check. It takes one command and it's the highest-value thirty seconds in this article.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verto is a parser you probably aren't using
&lt;/h2&gt;

&lt;p&gt;Ports 8081 and 8082 carry Verto, the WebSocket signalling protocol FreeSWITCH uses for browser-based calling. Four of the June advisories are in mod_verto, which is not a coincidence. It's a WebSocket endpoint that parses attacker-controlled framing before it authenticates anything, which is a large surface by construction.&lt;/p&gt;

&lt;p&gt;The useful question is whether you're using it. A lot of PBX deployments have Verto listening because the module loads by default, not because anyone dialled through a browser. If nobody in your platform makes WebRTC calls, unload the module. A parser that isn't running has no vulnerabilities, which is the only security guarantee that never expires.&lt;/p&gt;

&lt;p&gt;If you do use WebRTC, put Verto behind a reverse proxy that terminates TLS and enforces origin checks, and don't expose the raw port. That's a real bit of work rather than a config toggle, and it's worth budgeting for properly rather than doing it the week a CVE drops.&lt;/p&gt;

&lt;h2&gt;
  
  
  The two you'd never publish deliberately
&lt;/h2&gt;

&lt;p&gt;Nobody sets out to expose MySQL on 3306 or PostgreSQL on 5432. It happens because a cloud provider's default security group is permissive, or because someone opened it during a migration and the rule outlived the migration by two years. That database holds your tenant list, your CDRs and your credentials, so it's the single worst thing on the box to leave reachable.&lt;/p&gt;

&lt;p&gt;The other one is the super admin panel. In a multi-tenant platform, tenant logins and platform-operator logins are two different risk profiles wearing the same login form. If they share a hostname and a port, then every credential-stuffing run aimed at your tenants is also aimed at you. Split them: tenant panel on the public 443, admin panel on a separate host that only answers from your VPN. It costs one more DNS record. The &lt;a href="https://ictpbx.com/ictpbx-docs/system-admin/ictpbx-architecture/" rel="noopener noreferrer"&gt;ICTPBX architecture overview&lt;/a&gt; shows where that split sits relative to the rest of the stack.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to actually check, in about five minutes
&lt;/h2&gt;

&lt;p&gt;Do not audit this from inside the machine. &lt;code&gt;ss -tlnp&lt;/code&gt; tells you what's listening, which is a different question from what's reachable, and the gap between those two answers is where incidents live. Firewall rules, cloud security groups and NAT all sit in between.&lt;/p&gt;

&lt;p&gt;Run the scan from outside, against your own public IP:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nmap &lt;span class="nt"&gt;-Pn&lt;/span&gt; &lt;span class="nt"&gt;-sS&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 1-10000 &amp;lt;your-public-ip&amp;gt;
nmap &lt;span class="nt"&gt;-Pn&lt;/span&gt; &lt;span class="nt"&gt;-sU&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 5060,5061 &amp;lt;your-public-ip&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then compare the output to the left-hand column of the first diagram. SIP, RTP and 443 are expected. Anything else that answers is a finding, and 8021 answering is an emergency.&lt;/p&gt;

&lt;p&gt;Two caveats so you trust the result. Scan from a network that isn't yours, because your office IP may be in an allow list you forgot about, and a cheap VPS for an hour is the honest test. And only scan infrastructure you operate, which shouldn't need saying but does.&lt;/p&gt;

&lt;p&gt;If you're standing up a new box, our &lt;a href="https://ictpbx.com/ictpbx-docs/system-admin/ictpbx-installation/" rel="noopener noreferrer"&gt;installation guide&lt;/a&gt; is the right place to bake this in, because retrofitting a bind address on a live platform means a restart and a restart means dropped registrations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this beats patching faster
&lt;/h2&gt;

&lt;p&gt;Patching is necessary and you should do it. But patch velocity is a race you're running against people who read the same advisory you did and don't have a change window. On a multi-tenant platform, a restart is a customer-visible event, so realistically you're patching within days rather than hours.&lt;/p&gt;

&lt;p&gt;Network exposure is the thing you control completely and in advance. Bind ESL to loopback once and the next libesl CVE, whenever it lands, is a scheduled maintenance item instead of a phone call at 2am. That asymmetry is the entire argument. You can't predict which parser breaks next. You can decide today who gets to send it bytes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Can I just put the whole PBX behind a VPN?
&lt;/h3&gt;

&lt;p&gt;Not the whole thing. SIP and RTP have to reach carriers and remote handsets, and a VPN in front of media introduces latency and MTU problems that show up as one-way audio. Put the management plane behind the VPN and leave the call path public but tightly rate limited. That's the split the first diagram draws.&lt;/p&gt;

&lt;h3&gt;
  
  
  What if my application server is on a different machine from the switch?
&lt;/h3&gt;

&lt;p&gt;Then ESL can't bind to loopback, and you have two decent options. Put both machines on a private network segment and bind 8021 to that interface, or run a WireGuard tunnel between them and bind to the tunnel. What you should not do is open 8021 to the public internet with a source-IP firewall rule as the only control, because IP allow lists are one misapplied cloud config away from being no control at all.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is changing the ClueCon password enough?
&lt;/h3&gt;

&lt;p&gt;It's the first thing to do and it isn't enough. CVE-2026-49840 is in the Content-Length parsing, which happens before authentication, so a strong password doesn't help against it. Password strength protects you from guessing. Binding to loopback protects you from the parser.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I know if mod_verto is even loaded?
&lt;/h3&gt;

&lt;p&gt;Run &lt;code&gt;fs_cli -x "module_exists mod_verto"&lt;/code&gt;, or check whether verto.conf.xml is enabled in your modules configuration. If it's loaded and you're not making browser calls, unloading it removes four of the June advisories from your exposure in one step.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does this apply to Asterisk-based systems too?
&lt;/h3&gt;

&lt;p&gt;The specific CVEs don't, since they're FreeSWITCH modules. The principle does, exactly. Asterisk's equivalent of ESL is AMI on port 5038, and it has the same property: getting in is the whole game, so it belongs on loopback or a VPN. Substitute the port numbers and the diagram still holds.&lt;/p&gt;

&lt;h3&gt;
  
  
  We're hosted. Isn't this our provider's job?
&lt;/h3&gt;

&lt;p&gt;Ask them which of these ports are open on your instance and see how fast you get a specific answer. A good provider will have the scan output. If the reply is general reassurance rather than a port list, run the nmap yourself, because it's your tenant data either way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://ictpbx.com/what-is-ip-pbx/" rel="noopener noreferrer"&gt;What is an IP PBX&lt;/a&gt; if you want the ground-level explanation of what all these ports are doing&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://ictpbx.com/fraud-credit-controls/" rel="noopener noreferrer"&gt;Fraud and credit controls&lt;/a&gt; for the per-tenant call caps mentioned above&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://ictpbx.com/ictpbx-docs/system-admin/ictpbx-architecture/" rel="noopener noreferrer"&gt;ICTPBX architecture&lt;/a&gt; for how the management plane and call path separate in practice&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>cybersecurity</category>
      <category>infosec</category>
      <category>networking</category>
      <category>security</category>
    </item>
    <item>
      <title>ICTFax Now Runs From One Docker Command</title>
      <dc:creator>Tahir Almas</dc:creator>
      <pubDate>Mon, 10 Aug 2026 03:07:10 +0000</pubDate>
      <link>https://dev.to/tahiralmas/ictfax-now-runs-from-one-docker-command-33p3</link>
      <guid>https://dev.to/tahiralmas/ictfax-now-runs-from-one-docker-command-33p3</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://www.ictfax.org/ictfax-official-docker-image/" rel="noopener noreferrer"&gt;ictfax.org&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You can now run a full ICTFax server with a single command. Official images are published to Docker Hub and GitHub Container Registry, rebuilt from source on every commit, and they carry the whole stack: Rocky Linux 8, FreeSWITCH 1.10.12 with T.38, PHP, MariaDB, the fax toolchain and the web dashboard.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; ictfax &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 8080:80 &lt;span class="nt"&gt;-p&lt;/span&gt; 8443:443 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 5060:5060/udp &lt;span class="nt"&gt;-p&lt;/span&gt; 5060:5060/tcp &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 16384-16484:16384-16484/udp &lt;span class="se"&gt;\&lt;/span&gt;
  ictinnovations/ictfax:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Give it a minute or two. The container provisions its database, loads the schema, generates a TLS certificate and starts FreeSWITCH before the dashboard comes up on &lt;code&gt;http://localhost:8080/&lt;/code&gt;. Sign in with &lt;code&gt;admin@ictcore.org&lt;/code&gt; and &lt;code&gt;helloAdmin&lt;/code&gt;, then change it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this took a while
&lt;/h2&gt;

&lt;p&gt;ICTFax has always been installable. The &lt;a href="https://www.ictfax.org/ictfax-installation-guide/" rel="noopener noreferrer"&gt;installation guide&lt;/a&gt; works, and plenty of people have followed it. What it asks of you is a couple of hours and a willingness to debug somebody else's repository setup on a machine you have not decided to keep yet.&lt;/p&gt;

&lt;p&gt;That is a bad first impression for a project you are only evaluating. Most people who want to know whether an open source fax server suits them want to send one fax and look at the result. Making that a two hour commitment loses the ones who would have stayed.&lt;/p&gt;

&lt;p&gt;So the image exists to shorten the distance between curiosity and a working fax. It is not a replacement for a considered production install, though it is close enough that some people will run it in production anyway, and we have built it so that is a reasonable thing to do.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is inside
&lt;/h2&gt;

&lt;p&gt;The interesting parts are the ones you would otherwise assemble by hand.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;FreeSWITCH 1.10.12&lt;/strong&gt; with &lt;code&gt;mod_spandsp&lt;/code&gt;, so T.38 and G.711 fax both work out of the box. The packages come from Fedora Copr, which is HTTPS and GPG signed, rather than from a mirror whose signing key nobody publishes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The fax toolchain.&lt;/strong&gt; &lt;code&gt;tiff2pdf&lt;/code&gt;, &lt;code&gt;tiffcp&lt;/code&gt;, Ghostscript, ImageMagick and sox. ICTFax shells out to these for document conversion, and on a bare Rocky 8 box half of them live in a repository that ships disabled.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;MariaDB, provisioned on first boot.&lt;/strong&gt; Point &lt;code&gt;DB_HOST&lt;/code&gt; at your own server and the bundled one drops out of supervisord completely, so you are not paying for a database you do not use.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;A TLS certificate generated at runtime.&lt;/strong&gt; Baking a private key into a public image would hand every user the same key, so the entrypoint makes one on first start.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Useful environment variables: &lt;code&gt;ICTCORE_HOST&lt;/code&gt; for the hostname the dashboard advertises, the &lt;code&gt;DB_*&lt;/code&gt; set for an external database, and &lt;code&gt;FS_ESL_PASSWORD&lt;/code&gt; for the FreeSWITCH event socket. Worth mounting: &lt;code&gt;/usr/ictcore/data&lt;/code&gt; for received faxes, &lt;code&gt;/usr/ictcore/log&lt;/code&gt;, and &lt;code&gt;/var/lib/mysql&lt;/code&gt; if you keep the bundled database.&lt;/p&gt;

&lt;h2&gt;
  
  
  The build tests itself
&lt;/h2&gt;

&lt;p&gt;Every push runs the image through a smoke test before anything reaches a registry. The container has to boot, the dashboard has to answer, Angular deep links have to resolve, the REST API has to respond without a PHP fatal, &lt;code&gt;mod_spandsp&lt;/code&gt; has to be loaded in FreeSWITCH, and the schema has to be present in the database. Any failure stops the run before the publish step.&lt;/p&gt;

&lt;p&gt;That gate turned out to be worth more than the image. Building it meant running a clean ICTFax install on a fresh machine over and over, which is something almost nobody does, and it surfaced four real bugs in the first-install path. Invalid DEFAULT clauses in one of the schema files, a trigger that got created twice, seed files loading in the wrong order, and a database connection string that glued the host and port together in a way the mysqli extension has never accepted.&lt;/p&gt;

&lt;p&gt;All four are fixed upstream in ICTCore, so they are fixed for people installing from packages too, not just for container users. That is the part we did not expect and the reason the test gate stays.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the images live
&lt;/h2&gt;

&lt;p&gt;Both registries carry the same build:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;docker pull ictinnovations/ictfax:latest&lt;/code&gt; from Docker Hub&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;docker pull ghcr.io/ictinnovations/ictfax:latest&lt;/code&gt; from GitHub Container Registry&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Two sibling images went out at the same time. &lt;code&gt;ictinnovations/ictcore&lt;/code&gt; is the framework on its own, useful if you are building against the REST API and do not need a fax dashboard. &lt;code&gt;ictinnovations/ictdialer&lt;/code&gt; is the voice and fax broadcasting product on the same stack.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we would still do by hand
&lt;/h2&gt;

&lt;p&gt;Media matters more than signalling with fax. Publish the RTP range, or you will get a call that connects and a fax that never completes, which is the classic first run complaint and looks like a software fault when it is a port mapping.&lt;/p&gt;

&lt;p&gt;SIP trunk configuration is still yours. The image gives you a working FreeSWITCH, not a working carrier relationship, and T.38 negotiation with a given provider is the part that takes the afternoon. The &lt;a href="https://www.ictfax.org/technologies/" rel="noopener noreferrer"&gt;technologies page&lt;/a&gt; covers what ICTFax expects from a trunk.&lt;/p&gt;

&lt;p&gt;And if you are running this for real, put a reverse proxy with a certificate you trust in front of it rather than relying on the self-signed one the entrypoint generates.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Does this replace the manual installation?
&lt;/h3&gt;

&lt;p&gt;No. The &lt;a href="https://www.ictfax.org/ictfax-installation-guide/" rel="noopener noreferrer"&gt;installation guide&lt;/a&gt; is still the right path if you want ICTFax on a host you already run and manage with your own tooling. The image is the fastest way to get a working system to look at.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I use my own database?
&lt;/h3&gt;

&lt;p&gt;Yes. Set &lt;code&gt;DB_HOST&lt;/code&gt;, &lt;code&gt;DB_PORT&lt;/code&gt;, &lt;code&gt;DB_NAME&lt;/code&gt;, &lt;code&gt;DB_USER&lt;/code&gt; and &lt;code&gt;DB_PASS&lt;/code&gt;. The bundled MariaDB is removed from supervisord automatically when &lt;code&gt;DB_HOST&lt;/code&gt; points somewhere else.&lt;/p&gt;

&lt;h3&gt;
  
  
  Which ports do I need to publish?
&lt;/h3&gt;

&lt;p&gt;80 and 443 for the dashboard and REST API, 5060 on TCP and UDP for SIP, and the 16384 to 16484 UDP range for media. Skipping the media range is the most common reason a first fax fails.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is T.38 supported?
&lt;/h3&gt;

&lt;p&gt;Yes. &lt;code&gt;mod_spandsp&lt;/code&gt; ships in the image and the build refuses to publish if it is not loaded. G.711 pass-through faxing works as well.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I persist faxes across restarts?
&lt;/h3&gt;

&lt;p&gt;Mount &lt;code&gt;/usr/ictcore/data&lt;/code&gt;. Add &lt;code&gt;/var/lib/mysql&lt;/code&gt; if you are using the bundled database and &lt;code&gt;/usr/ictcore/log&lt;/code&gt; if you want logs on the host.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is the image free to use?
&lt;/h3&gt;

&lt;p&gt;Yes. ICTFax is open source under GPL-3.0 and the images carry no additional restrictions. &lt;a href="https://www.ictfax.org/professional-support-ictfax/" rel="noopener noreferrer"&gt;Professional support&lt;/a&gt; is available separately if you want it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it and tell us what breaks
&lt;/h2&gt;

&lt;p&gt;The images are new. They pass their own tests on a clean runner, which is not the same as working on every network anyone will point them at. If you hit something, the &lt;a href="https://www.ictfax.org/support/" rel="noopener noreferrer"&gt;support page&lt;/a&gt; has the places to raise it, and a bug report from a container is easier for us to reproduce than one from a bespoke install.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>linux</category>
      <category>software</category>
    </item>
    <item>
      <title>Comments Close Today on the FCC Rule That Redefines an A-Level Call</title>
      <dc:creator>Tahir Almas</dc:creator>
      <pubDate>Mon, 10 Aug 2026 03:01:50 +0000</pubDate>
      <link>https://dev.to/tahiralmas/comments-close-today-on-the-fcc-rule-that-redefines-an-a-level-call-1gpk</link>
      <guid>https://dev.to/tahiralmas/comments-close-today-on-the-fcc-rule-that-redefines-an-a-level-call-1gpk</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://www.ictcontact.com/fcc-kyup-attestation-rules-contact-centers/" rel="noopener noreferrer"&gt;ictcontact.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The FCC's Know-Your-Upstream-Provider proposal stops taking comments today, 10 August 2026. If it becomes a rule, three things change for outbound teams: attestation levels get written into the regulations instead of living in an industry document, "improper attestation" becomes a defined offence with a defined remedy, and every provider in the chain has to vet the one feeding it traffic.&lt;/p&gt;

&lt;p&gt;None of that binds you tomorrow. Rulemakings take months. But the record closing today is the record the eventual order gets built from, and the direction of travel is already clear enough to plan against.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the FCC actually proposed
&lt;/h2&gt;

&lt;p&gt;The item is a Further Notice of Proposed Rulemaking, adopted as FCC 26-32 and running in WC Docket No. 17-97 alongside CG Docket No. 17-59. It landed in the Federal Register on 9 July 2026. Comments are due today. Reply comments run until 8 September 2026.&lt;/p&gt;

&lt;p&gt;Four groups of proposals matter if you run campaigns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Baseline KYUP obligations.&lt;/strong&gt; Providers would have to collect specific information about whoever hands them traffic, review it, verify it, monitor it over time, and take action when something is wrong. Today "know your upstream provider" is more of an expectation than a checklist. The proposal turns it into one.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;A stronger Governance Authority.&lt;/strong&gt; The body that issues STIR/SHAKEN certificates would get wider vetting, enforcement and reporting duties, so a provider that abuses a certificate can lose it rather than simply being asked to behave.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Codified attestation levels.&lt;/strong&gt; A, B and C would be defined in the rules. So would improper attestation. The FCC also asks what mechanisms should count as valid for verifying that a number really belongs to the customer using it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Fewer gaps.&lt;/strong&gt; Provider definitions get refined, existing exemptions get revisited, providers serving end users would have to assign an attestation, and calls would have to carry their authentication information all the way through instead of losing it at a handoff.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why the attestation piece is the one to read
&lt;/h2&gt;

&lt;p&gt;Most contact center teams treat attestation as somebody else's job. Your carrier signs the call, you see A on a spot check, everyone moves on. That works right now because the definitions live in ATIS documentation and enforcement is uneven.&lt;/p&gt;

&lt;p&gt;Codifying the levels changes the incentives on the carrier side. Once "improper attestation" is a defined thing with consequences attached, a carrier that has been signing A on numbers it never really verified has a reason to stop. The likely outcome is not that your calls get blocked. It is that your carrier starts asking you for proof it never asked for before, and downgrades you to B while it waits.&lt;/p&gt;

&lt;p&gt;B is not fatal. It also is not free. Analytics engines weigh attestation alongside complaint rates and call patterns, and a downgrade you did not plan for shows up as a drop in answer rate a week later, with no obvious cause in your own reporting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Five things worth checking this month
&lt;/h2&gt;

&lt;p&gt;You do not need a compliance project for this. You need answers to questions you can ask by email.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;What attestation does each of your numbers get, and from which provider?&lt;/strong&gt; Teams with more than one carrier often find the answer differs by route and nobody had noticed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;How does your carrier establish that a number is yours?&lt;/strong&gt; A letter of authorisation, a porting record and a verbal confirmation are not equally defensible, and the FCC is asking exactly this question.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Who is upstream of your carrier?&lt;/strong&gt; If your traffic passes through a wholesale layer before it reaches a tier-1, the KYUP duties land on that layer, and its answer becomes your problem.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Do you keep your own records of number ownership?&lt;/strong&gt; When a carrier tightens its process, the customers who can produce documentation the same day keep their A. The rest wait.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Can you see attestation in your own reporting?&lt;/strong&gt; If the only way to check is to call a mobile and look at the screen, you will find out about a downgrade late.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where the software side comes in
&lt;/h2&gt;

&lt;p&gt;Two habits make a carrier tightening its process survivable rather than disruptive.&lt;/p&gt;

&lt;p&gt;The first is clean number-to-campaign mapping. If your &lt;a href="https://www.ictcontact.com/what-is-contact-center-software/" rel="noopener noreferrer"&gt;contact center software&lt;/a&gt; can tell you which numbers ran which campaigns over the last quarter, you can answer a carrier questionnaire in an afternoon. If that lives in a spreadsheet somebody maintains by hand, you cannot.&lt;/p&gt;

&lt;p&gt;The second is watching answer rate per caller ID rather than per campaign. Attestation problems are number-level. A campaign-level average hides a single downgraded number until the quarter looks bad. Per-number reporting in your &lt;a href="https://www.ictcontact.com/user-guide/reports-guide-cdr-live-statistics-agent-reports/" rel="noopener noreferrer"&gt;CDR and live statistics&lt;/a&gt; turns that into something you spot in days.&lt;/p&gt;

&lt;p&gt;Neither of these is a compliance feature. They are ordinary operational hygiene that happens to be what regulators are moving toward.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we would not do
&lt;/h2&gt;

&lt;p&gt;Do not buy anything on the strength of an FNPRM. The comment cycle has not closed on reply comments yet, the order will differ from the proposal, and vendors selling KYUP compliance today are selling a guess.&lt;/p&gt;

&lt;p&gt;Do not assume this only touches US traffic either. The FCC explicitly asks about foreign-originated calls, and the pattern it is describing, pushing verification back along the chain, is the same one &lt;a href="https://www.ictcontact.com/glossary-terms/" rel="noopener noreferrer"&gt;branded calling programmes&lt;/a&gt; in other markets have already adopted.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Does anything change for my calls today?
&lt;/h3&gt;

&lt;p&gt;No. This is a proposal. The comment deadline closing today affects who gets to shape the rule, not what your carrier does this week.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can we still file a comment?
&lt;/h3&gt;

&lt;p&gt;Comments are due on or before 10 August 2026 in WC Docket No. 17-97. Reply comments stay open until 8 September 2026, so there is a second window if you miss the first.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the difference between A, B and C attestation?
&lt;/h3&gt;

&lt;p&gt;A means the provider knows the customer and knows they have the right to use that number. B means it knows the customer but not the number. C means it is passing the call along without either. The proposal would write those definitions into the rules rather than leaving them to industry documentation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Will B attestation get our calls blocked?
&lt;/h3&gt;

&lt;p&gt;Not on its own. Analytics engines weigh attestation together with complaint rates, call duration patterns and volume. B narrows your margin rather than ending the call.&lt;/p&gt;

&lt;h3&gt;
  
  
  We use a wholesale provider. Does KYUP apply to us?
&lt;/h3&gt;

&lt;p&gt;The obligations as proposed sit with providers rather than with calling parties. The practical effect reaches you anyway, because the provider carrying your traffic has to be able to answer for it.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do we prove a number belongs to us?
&lt;/h3&gt;

&lt;p&gt;Keep porting records, letters of authorisation and provisioning confirmations somewhere you can retrieve them by number. That is the evidence a carrier will ask for, and the FCC is currently taking comment on which forms of it should count.&lt;/p&gt;

&lt;h2&gt;
  
  
  The short version
&lt;/h2&gt;

&lt;p&gt;The FCC is moving verification away from a single signature at the edge and spreading it across the whole call path. For contact centers the practical consequence is not a new compliance burden, it is a carrier that starts asking harder questions. Teams that can answer with records rather than assurances will keep their attestation. Teams that cannot will spend a quarter finding out why answer rates slipped.&lt;/p&gt;

&lt;p&gt;If you want to talk through how your numbers and campaigns are mapped, &lt;a href="https://service.ictvision.net/contact.php" rel="noopener noreferrer"&gt;open a ticket&lt;/a&gt; and we will take a look.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>We Open Sourced an Asterisk AI Voice Agent and the AudioSocket Library Under It</title>
      <dc:creator>Tahir Almas</dc:creator>
      <pubDate>Sun, 09 Aug 2026 14:57:01 +0000</pubDate>
      <link>https://dev.to/tahiralmas/we-open-sourced-an-asterisk-ai-voice-agent-and-the-audiosocket-library-under-it-1f0h</link>
      <guid>https://dev.to/tahiralmas/we-open-sourced-an-asterisk-ai-voice-agent-and-the-audiosocket-library-under-it-1f0h</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://ictinnovations.com/asterisk-ai-voice-agent-audiosocket-open-source/" rel="noopener noreferrer"&gt;ictinnovations.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We've released two open source projects today. &lt;strong&gt;asterisk-ai-voice-agent&lt;/strong&gt; is a self-hosted Python sidecar that turns an Asterisk call into a spoken conversation with an LLM. &lt;strong&gt;asterisk-audiosocket&lt;/strong&gt; is the protocol layer underneath it, packaged on its own for Node. Both live on GitHub, and you run them on your own hardware with your own API keys.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the voice agent actually does
&lt;/h2&gt;

&lt;p&gt;You point an Asterisk dialplan at it with a single &lt;code&gt;AudioSocket()&lt;/code&gt; line. From there the sidecar takes the raw call audio, works out when the caller has stopped speaking, transcribes what they said, sends it to a language model, synthesises the reply, and pushes that reply back down the same socket as audio.&lt;/p&gt;

&lt;p&gt;The pieces you can pick:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Speech in.&lt;/strong&gt; OpenAI Whisper or ElevenLabs Scribe. WebRTC voice activity detection decides when a turn has ended, so the agent isn't waiting on a fixed timer.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The reasoning.&lt;/strong&gt; Anthropic Claude, streamed token by token, so synthesis can start before the full answer exists.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Speech out.&lt;/strong&gt; Piper runs locally on CPU, or ElevenLabs if you want the cloud voice. Piper means the whole loop can run with no text to speech bill at all.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Tools.&lt;/strong&gt; When the model decides to transfer a call, book a callback, look something up in your CRM, or check a do not call list, the sidecar POSTs that decision to a webhook you own. Your code does the work. The model just asks.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Barge-in is in there too. Start talking over the agent and the queued speech gets dropped mid-sentence, the way a person stops when you interrupt them. Without that, an AI caller feels like a recorded message, and people hang up on recorded messages.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pacing bug that eats your first week
&lt;/h2&gt;

&lt;p&gt;Here's the part that catches everyone, and the reason we split the protocol layer into its own package.&lt;/p&gt;

&lt;p&gt;Your text to speech engine hands you a finished sentence. Say 2.4 seconds of audio, which at 8 kHz slin16 works out to 120 frames of 320 bytes. The obvious thing to do is write all of it to the socket and move on.&lt;/p&gt;

&lt;p&gt;Don't. Asterisk's &lt;code&gt;app_audiosocket&lt;/code&gt; forwards every frame to the channel the instant it arrives. It does not buffer on your behalf. So 120 frames land on the caller's jitter buffer in a few milliseconds, that buffer holds a small fraction of them, and the rest are thrown away. The caller hears the tail of your sentence and nothing else.&lt;/p&gt;

&lt;p&gt;The fix is to meter the audio to the frame clock: one 320 byte frame, then wait until the next 20 millisecond deadline. The subtlety is that the deadline has to be re-clamped on every frame. If synthesis stalls for half a second, a naive writer will burst to catch up and you're back where you started. Both projects ship this writer already built, so you don't have to rediscover it.&lt;/p&gt;

&lt;h2&gt;
  
  
  asterisk-audiosocket on its own
&lt;/h2&gt;

&lt;p&gt;If you'd rather build your agent in Node, or you just want a clean AudioSocket implementation without any AI attached, &lt;code&gt;npm install asterisk-audiosocket&lt;/code&gt; gets you the protocol on its own. TypeScript types, no runtime dependencies, the framed slin16 codec, the paced &lt;code&gt;play()&lt;/code&gt; writer described above, and barge-in support.&lt;/p&gt;

&lt;p&gt;It joins the two clients we &lt;a href="https://ictinnovations.com/asterisk-ami-freeswitch-esl-nodejs-clients/" rel="noopener noreferrer"&gt;published for Asterisk AMI and FreeSWITCH ESL&lt;/a&gt;. Those three cover most of what you need to drive a PBX from Node: manager interface, event socket, and now live media. The AMI client is the one you'd reach for to implement a transfer tool from the voice agent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why we built it this way
&lt;/h2&gt;

&lt;p&gt;Plenty of hosted voice AI products will happily take your call audio. That's fine until you look at where the recording goes, what the per-minute cost does at volume, or what happens to your deployment when the vendor changes a model.&lt;/p&gt;

&lt;p&gt;This runs inside your stack. Your PBX, your keys, your prompts, your logs. The only traffic leaving the box is the provider calls you explicitly configured, and with Piper doing synthesis locally there's one fewer of those.&lt;/p&gt;

&lt;p&gt;It also follows on from what we've been building elsewhere. &lt;a href="https://ictinnovations.com/pbx-mcp-open-source-mcp-server-asterisk-freeswitch/" rel="noopener noreferrer"&gt;pbx-mcp gives AI assistants a read-only window into Asterisk and FreeSWITCH&lt;/a&gt;, and we've written before about &lt;a href="https://ictinnovations.com/ai-default-voip-feature-open-source-stack/" rel="noopener noreferrer"&gt;AI becoming a default feature rather than an add-on&lt;/a&gt; in the open source VoIP stack. The voice agent is the same idea pointed at the live call instead of the console.&lt;/p&gt;

&lt;h2&gt;
  
  
  Being straight about v0.1.0
&lt;/h2&gt;

&lt;p&gt;This is a first tagged release. The protocol handling is covered by tests, CI imports every module on every push, and the pacing behaviour is the part we're most confident in because it's the part we got wrong first. What it hasn't had yet is a long soak on production traffic. Treat it as something to run in a lab, read, and tell us about, not something to point your main queue at on Monday.&lt;/p&gt;

&lt;p&gt;Two practical notes before you start. Ports 9091 and 9092 should stay off the public internet, since neither speaks any authentication. And each concurrent call uses roughly 150 MB during synthesis bursts, so the concurrency cap in the config exists for a reason.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to get it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/ictinnovations/asterisk-ai-voice-agent" rel="noopener noreferrer"&gt;github.com/ictinnovations/asterisk-ai-voice-agent&lt;/a&gt; (Python sidecar)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/ictinnovations/asterisk-audiosocket" rel="noopener noreferrer"&gt;github.com/ictinnovations/asterisk-audiosocket&lt;/a&gt; and &lt;a href="https://www.npmjs.com/package/asterisk-audiosocket" rel="noopener noreferrer"&gt;npm&lt;/a&gt; (Node library)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both are MIT licensed. Issues and pull requests are open, and if you get it running on something interesting we'd like to hear about it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Questions people asked us first
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Do I need a specific Asterisk version?
&lt;/h3&gt;

&lt;p&gt;You need one with &lt;code&gt;app_audiosocket&lt;/code&gt;, which means Asterisk 16 or newer. It's a single dialplan line, so there's no patching and no module to compile yourself.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I swap Claude for a different model?
&lt;/h3&gt;

&lt;p&gt;Yes. Claude is what ships and what's tested, but the LLM sits behind one small module interface. Implementing that interface against another provider, including a local model, is documented in PORTING.md in the repo. The same applies to the speech engines.&lt;/p&gt;

&lt;h3&gt;
  
  
  How much does a call cost to run?
&lt;/h3&gt;

&lt;p&gt;It depends entirely on which providers you pick. With Piper handling synthesis locally you're only paying for transcription and the model tokens. If you switch text to speech to ElevenLabs, that becomes the largest line item on a chatty call.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does this work with FreeSWITCH?
&lt;/h3&gt;

&lt;p&gt;Not directly, because AudioSocket is an Asterisk application. FreeSWITCH has its own media paths, and our &lt;a href="https://ictinnovations.com/asterisk-ami-freeswitch-esl-nodejs-clients/" rel="noopener noreferrer"&gt;ESL client&lt;/a&gt; is the starting point there. Porting the agent's audio layer is on the list, not done.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is this the same thing as the AI voice agent in ICTContact?
&lt;/h3&gt;

&lt;p&gt;No. ICTContact ships its own AI Personas feature as part of the product, with the campaign management, reporting, and multi-tenant pieces around it. These releases are the plumbing, published on their own for people who want to build something themselves. If you'd rather have the finished product, &lt;a href="https://service.ictinnovations.com/contact.php" rel="noopener noreferrer"&gt;open a ticket and ask us&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  What's the roadmap?
&lt;/h3&gt;

&lt;p&gt;Wider provider coverage, a longer soak on real traffic, and better numbers on latency per stage so you can see where a slow turn is actually going. Feature requests on GitHub carry real weight this early.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Two Zero Dependency Node.js Clients for Asterisk AMI and FreeSWITCH ESL</title>
      <dc:creator>Tahir Almas</dc:creator>
      <pubDate>Sun, 09 Aug 2026 13:14:25 +0000</pubDate>
      <link>https://dev.to/tahiralmas/two-zero-dependency-nodejs-clients-for-asterisk-ami-and-freeswitch-esl-1fa3</link>
      <guid>https://dev.to/tahiralmas/two-zero-dependency-nodejs-clients-for-asterisk-ami-and-freeswitch-esl-1fa3</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://ictinnovations.com/asterisk-ami-freeswitch-esl-nodejs-clients/" rel="noopener noreferrer"&gt;ictinnovations.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We published two npm packages today. &lt;strong&gt;asterisk-ami-node&lt;/strong&gt; speaks the Asterisk Manager Interface, &lt;strong&gt;freeswitch-esl-node&lt;/strong&gt; speaks the FreeSWITCH Event Socket, and neither one pulls a third party dependency into your project. Both are MIT licensed, ship TypeScript types, work from ESM and CommonJS, and need nothing newer than Node 18.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why we published them
&lt;/h2&gt;

&lt;p&gt;The Asterisk side of npm has gone quiet. &lt;code&gt;asterisk-ami-client&lt;/code&gt; last shipped a release in 2018. &lt;code&gt;asterisk-ami-connector&lt;/code&gt; last shipped in 2016. Between them they still pull around 24,000 downloads a month, which tells you people keep reaching for a Node AMI client and keep landing on code nobody has touched in seven years. That is not a criticism of the authors. Maintaining something for free is hard and stopping is allowed. It is just a gap.&lt;/p&gt;

&lt;p&gt;The FreeSWITCH side is healthier. &lt;code&gt;modesl&lt;/code&gt;, &lt;code&gt;esl&lt;/code&gt; and &lt;code&gt;drachtio-modesl&lt;/code&gt; are all in real use and one of them saw a release this year. We're not filling a hole there, we're offering an alternative, so pick on the merits rather than on the release date.&lt;/p&gt;

&lt;p&gt;Both clients already existed. They were sitting inside &lt;a href="https://ictinnovations.com/pbx-mcp-open-source-mcp-server-asterisk-freeswitch/" rel="noopener noreferrer"&gt;pbx-mcp&lt;/a&gt;, the MCP server we released that gives an AI assistant a read-only window into both platforms. If all you want is to talk to a switch from your own Node code, dragging an MCP SDK along for the ride makes no sense. So we pulled the protocol layer out and shipped it on its own.&lt;/p&gt;

&lt;h2&gt;
  
  
  Talking to Asterisk
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;AmiClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;listRows&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;asterisk-ami-node&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;ami&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;AmiClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;127.0.0.1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;admin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;secret&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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ami&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&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;reply&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ami&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;action&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;CoreShowChannels&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="k"&gt;for &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;row&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nf"&gt;listRows&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;CoreShowChannel&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="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;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Channel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CallerIDNum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;ami&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The part that's easy to get wrong by hand is the boundary between an action reply and the event stream. A list action such as &lt;code&gt;CoreShowChannels&lt;/code&gt; answers with a success line, then one event per channel, then a completion event. Those per-channel events look exactly like unsolicited events, so a naive client hands them to your &lt;code&gt;Hangup&lt;/code&gt; and &lt;code&gt;Newchannel&lt;/code&gt; listeners as well as to the caller who asked for them. This client keeps them apart. Rows raised by your own action go back to you and never reach your event handlers.&lt;/p&gt;

&lt;p&gt;CLI output works too. Asterisk 14 and later return it as a repeated &lt;code&gt;Output&lt;/code&gt; header, which &lt;code&gt;command()&lt;/code&gt; folds into one string:&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="s2"&gt;`const out = await ami.command("pjsip show endpoints");`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;TLS, a keepalive that notices a dead socket on an idle session, and auto-reconnect with backoff are all there as options. All three are off until you ask.&lt;/p&gt;

&lt;h2&gt;
  
  
  Talking to FreeSWITCH
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;EslClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;freeswitch-esl-node&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;esl&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;EslClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;127.0.0.1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ClueCon&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;CHANNEL_ANSWER&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;CHANNEL_HANGUP_COMPLETE&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="nx"&gt;esl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;CHANNEL_ANSWER&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="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;answered&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Unique-ID&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]));&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;esl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;esl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;api&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sofia status&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Long jobs get their own path. &lt;code&gt;api()&lt;/code&gt; holds the socket until the switch answers, which on an originate can be most of a minute with nothing else getting through. &lt;code&gt;bgapi()&lt;/code&gt; hands the work to FreeSWITCH, then resolves later when the matching &lt;code&gt;BACKGROUND_JOB&lt;/code&gt; event arrives, correlated by a job ID the client generates for you. That event gets consumed rather than passed on, so it won't turn up in your own handlers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two bugs that shaped the code
&lt;/h2&gt;

&lt;p&gt;Both packages carry fixes for problems we hit for real while building pbx-mcp. They're worth describing, because if you write your own client you'll meet both.&lt;/p&gt;

&lt;h3&gt;
  
  
  The one that lies to you
&lt;/h3&gt;

&lt;p&gt;An ESL reply is a header block, a blank line, then a body whose size is declared in &lt;code&gt;Content-Length&lt;/code&gt;. The obvious implementation emits a frame the moment it sees that blank line. It works perfectly until you run &lt;code&gt;show channels&lt;/code&gt; on a busy switch, the body spans several TCP segments, and you get a confidently truncated answer with no error attached to it.&lt;/p&gt;

&lt;p&gt;Twelve channels looks exactly like twelve channels whether or not there were really ninety. This client reads the declared length and waits for the whole body, counted in bytes rather than characters so a multibyte caller ID can't shift the boundary. There's a test that feeds a 400 row response through in 500 byte chunks.&lt;/p&gt;

&lt;h3&gt;
  
  
  The one that leaks
&lt;/h3&gt;

&lt;p&gt;On the AMI side, the internal handler that watches for an action's reply originally answered a yes or no question: is this message mine? That can't express the state you actually need, which is "this row belongs to my action but I'm not finished yet". So mid-list rows came back as "not mine" and got re-emitted into the public event stream. A test caught it. In production it would have shown up as duplicate channel events that nobody could trace.&lt;/p&gt;

&lt;p&gt;The fix was to give the handler three answers instead of two: ignored, claimed, or done. Small change, and the class of bug it removes is the kind you spend a day chasing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Line breaks are rejected, not stripped
&lt;/h2&gt;

&lt;p&gt;AMI has no escaping. A value carrying a carriage return or newline ends the current action early, and everything after it gets read as a second action. A channel name built from user input can turn an &lt;code&gt;Originate&lt;/code&gt; into a &lt;code&gt;Command&lt;/code&gt;.&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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ami&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;action&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Originate&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;Channel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;PJSIP/1&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="s2"&gt;Action: Command&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;// AmiError: Refusing to send AMI field "Channel":&lt;/span&gt;
&lt;span class="c1"&gt;// line breaks in a value can inject a second action&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Throwing is deliberate. Quietly stripping the break would send an action you never wrote, and you'd have no way to know. The ESL client applies the same rule to command text.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install them
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;asterisk-ami-node
npm &lt;span class="nb"&gt;install &lt;/span&gt;freeswitch-esl-node
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing new goes on the PBX. On Asterisk you need a manager account in &lt;code&gt;manager.conf&lt;/code&gt;. On FreeSWITCH the event socket is already running with the default password. Keep ports 5038 and 8021 bound to localhost and reach them over a tunnel or a private network, because anyone who can open those ports owns the switch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the code lives
&lt;/h2&gt;

&lt;p&gt;Both repositories are public: &lt;a href="https://github.com/ictinnovations/asterisk-ami-node" rel="noopener noreferrer"&gt;asterisk-ami-node&lt;/a&gt; and &lt;a href="https://github.com/ictinnovations/freeswitch-esl-node" rel="noopener noreferrer"&gt;freeswitch-esl-node&lt;/a&gt;. The packages are on npm under the same names. Each README covers PBX side setup, the full option table and worked examples.&lt;/p&gt;

&lt;p&gt;They join the rest of our open source work, which you can browse on our &lt;a href="https://ictinnovations.com/projects/" rel="noopener noreferrer"&gt;projects page&lt;/a&gt;. The same protocol groundwork sits underneath &lt;a href="https://github.com/ictinnovations/ictcore" rel="noopener noreferrer"&gt;ICTCore&lt;/a&gt; and the products built on it, including &lt;a href="https://ictpbx.com" rel="noopener noreferrer"&gt;ICTPBX&lt;/a&gt; and &lt;a href="https://ictcontact.com" rel="noopener noreferrer"&gt;ICTContact&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Do these replace asterisk-ami-client or modesl?
&lt;/h3&gt;

&lt;p&gt;They're not drop-in replacements. The APIs differ, so switching means a small rewrite of your call sites. If your current client works, there's no urgency. If you're starting fresh, or you're stuck on something the old package won't fix, these are maintained and the issue tracker is open.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I need pbx-mcp to use them?
&lt;/h3&gt;

&lt;p&gt;No. They're standalone packages with no knowledge of MCP. pbx-mcp is one consumer of the same protocol work, not a requirement.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do they work with CommonJS?
&lt;/h3&gt;

&lt;p&gt;Yes. Both ship an ESM build and a CommonJS build, so &lt;code&gt;import&lt;/code&gt; and &lt;code&gt;require&lt;/code&gt; both resolve to the right one automatically. TypeScript declarations come with each build.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does the Asterisk client support TLS?
&lt;/h3&gt;

&lt;p&gt;Yes. Set &lt;code&gt;tls: true&lt;/code&gt; and the socket is wrapped, which matches &lt;code&gt;tlsenable=yes&lt;/code&gt; on the Asterisk side. Certificate verification is a separate option, off by default because self-signed certificates on internal switches are common.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does the FreeSWITCH client do outbound mode?
&lt;/h3&gt;

&lt;p&gt;Not yet. Inbound mode is implemented, which is what you want for monitoring and control. Outbound mode, where FreeSWITCH connects out to you from the dialplan, is a different shape of problem. Open an issue if you need it and we'll look at it.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I test my code without a PBX?
&lt;/h3&gt;

&lt;p&gt;Both packages ship their own test suites that run against mock TCP servers, so you can read those for a pattern. Sixteen tests on the Asterisk side, twenty on the FreeSWITCH side, and no live switch needed for any of them.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>We Released pbx-mcp: One AI Tool Surface for Asterisk and FreeSWITCH</title>
      <dc:creator>Tahir Almas</dc:creator>
      <pubDate>Sun, 09 Aug 2026 11:26:10 +0000</pubDate>
      <link>https://dev.to/tahiralmas/we-released-pbx-mcp-one-ai-tool-surface-for-asterisk-and-freeswitch-2bn</link>
      <guid>https://dev.to/tahiralmas/we-released-pbx-mcp-one-ai-tool-surface-for-asterisk-and-freeswitch-2bn</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://ictinnovations.com/pbx-mcp-open-source-mcp-server-asterisk-freeswitch/" rel="noopener noreferrer"&gt;ictinnovations.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;pbx-mcp is an open source MCP server that gives an AI assistant one tool surface for both Asterisk and FreeSWITCH. You ask a plain question, it runs the right command against whichever switch you configured, and hands back a readable answer. It is MIT licensed, installs with a single npx command, and is read-only until you say otherwise.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why we built it
&lt;/h2&gt;

&lt;p&gt;If you run both platforms, you carry two vocabularies for the same job. Checking whether an extension is registered means &lt;code&gt;pjsip show endpoints&lt;/code&gt; on Asterisk and &lt;code&gt;sofia status profile internal reg&lt;/code&gt; on FreeSWITCH. Neither is hard. Both are one more thing to recall at 2am on a switch you last touched eight months ago.&lt;/p&gt;

&lt;p&gt;MCP, the Model Context Protocol, is the open standard for connecting AI assistants to real systems. pbx-mcp speaks it, so an assistant can ask your PBX questions directly rather than guessing from documentation.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does
&lt;/h2&gt;

&lt;p&gt;The useful part is not single lookups. It is chained ones. Ask why outbound calls to one country are failing and the assistant checks the switch is alive, checks whether the trunk registered upstream, lists recent channels to see how far calls get, and pulls the dialplan for that route. Four commands across two syntaxes, in the right order, without you holding the sequence in your head.&lt;/p&gt;

&lt;p&gt;Ten read-only tools ship in the default mode. On Asterisk you get switch status, active channels, PJSIP endpoint state with a chan_sip fallback, dialplan lookups and a guarded CLI passthrough. On FreeSWITCH you get status, channels, sofia profile and gateway status, SIP registrations and a guarded API passthrough. Call control, meaning originate and hangup, brings the total to fourteen but stays switched off by default.&lt;/p&gt;

&lt;h2&gt;
  
  
  Safety came first
&lt;/h2&gt;

&lt;p&gt;A PBX is not a scratch pad. A reload drops every registration on a profile, and an originate on a live trunk is a real call with a real bill attached. So the guards are layered:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Read-only by default.&lt;/strong&gt; CLI and API passthroughs check every command against an allow list.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Word level scanning.&lt;/strong&gt; On FreeSWITCH, &lt;code&gt;sofia&lt;/code&gt; is allowed but &lt;code&gt;sofia profile internal restart&lt;/code&gt; is not. Every word gets checked, so a prefix match cannot wave a state change through.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Write tools are not registered at all&lt;/strong&gt; unless you set &lt;code&gt;PBX_MCP_ALLOW_WRITE=true&lt;/code&gt;. A model cannot call a tool it cannot see, so this is not a refusal it might argue around. The tool simply does not exist.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Header injection is blocked.&lt;/strong&gt; AMI is newline delimited, so every field is checked for smuggled line breaks.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Output is clamped&lt;/strong&gt; at 20,000 characters, because one channel listing on a busy switch would otherwise fill the whole context window.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How it is built
&lt;/h2&gt;

&lt;p&gt;Two protocol clients written from scratch, with no third party networking dependencies. AMI is a line protocol on TCP 5038 with ActionID correlation. ESL is TCP 8021 with a password handshake and Content-Length framed bodies. Both are framed text over TCP, so hand writing them keeps the install small and the behaviour predictable. The only runtime dependencies are the MCP SDK and Zod.&lt;/p&gt;

&lt;p&gt;That groundwork is not new to us. The same AMI and ESL handling sits underneath &lt;a href="https://github.com/ictinnovations/ictcore" rel="noopener noreferrer"&gt;ICTCore&lt;/a&gt;, the engine behind our telephony products.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="sb"&gt;`&lt;/span&gt;npx &lt;span class="nt"&gt;-y&lt;/span&gt; pbx-mcp&lt;span class="sb"&gt;`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then point your MCP client at it and set the host, username and password for whichever switch you run. Configure only Asterisk and you never see a FreeSWITCH tool, and the reverse holds too.&lt;/p&gt;

&lt;p&gt;One thing catches most people. pbx-mcp runs on your machine, not on the PBX, so your machine needs to reach port 5038 or 8021 on the phone system. Most installs bind those to localhost. An SSH tunnel is the quick answer, and you should not open those ports to the internet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to get it
&lt;/h2&gt;

&lt;p&gt;The code is on &lt;a href="https://github.com/ictinnovations/pbx-mcp" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; and the package is on &lt;a href="https://www.npmjs.com/package/pbx-mcp" rel="noopener noreferrer"&gt;npm&lt;/a&gt;. The &lt;a href="https://github.com/ictinnovations/pbx-mcp/blob/main/docs/USER-GUIDE.md" rel="noopener noreferrer"&gt;user guide&lt;/a&gt; covers PBX side setup for both platforms, with troubleshooting keyed to the actual error strings you will hit.&lt;/p&gt;

&lt;p&gt;It joins the rest of our open source work, which you can browse on our &lt;a href="https://ictinnovations.com/projects/" rel="noopener noreferrer"&gt;projects page&lt;/a&gt;. If you want a commercially supported PBX built on the same foundations, look at &lt;a href="https://ictpbx.com" rel="noopener noreferrer"&gt;ICTPBX&lt;/a&gt;, and for outbound and contact centre work there is &lt;a href="https://ictcontact.com" rel="noopener noreferrer"&gt;ICTContact&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Does pbx-mcp work with Asterisk and FreeSWITCH at the same time?
&lt;/h3&gt;

&lt;p&gt;Yes. Set the environment variables for both and it registers tools for both. Set only one and it registers only that side, so an Asterisk shop never sees FreeSWITCH tools in the list.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can the AI change my PBX configuration?
&lt;/h3&gt;

&lt;p&gt;No. It cannot edit configuration files at all. In the default read-only mode it cannot even place or end a call, because those tools are not registered. You have to opt in with an environment variable before they appear.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Asterisk versions are supported?
&lt;/h3&gt;

&lt;p&gt;Anything with a working Manager Interface. The endpoint tool asks PJSIP first and falls back to chan_sip peers on its own, so older boxes still answer without you changing any settings.&lt;/p&gt;

&lt;h3&gt;
  
  
  Which AI assistants can use it?
&lt;/h3&gt;

&lt;p&gt;Any MCP client. Claude Desktop is the common one and the guide has a copy and paste config for it, but the protocol is open and other clients work the same way.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is it really free?
&lt;/h3&gt;

&lt;p&gt;Yes, MIT licensed. Use it commercially, fork it, ship it inside your own product. Issues and pull requests are welcome on GitHub.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is missing right now?
&lt;/h3&gt;

&lt;p&gt;Queue statistics, call detail record lookups and conference room state are the obvious gaps. If one of those would help you, open an issue and tell us which, because we would rather build the one people ask for than guess.&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
