<?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: Sagar Ghag</title>
    <description>The latest articles on DEV Community by Sagar Ghag (@ghagsagar23).</description>
    <link>https://dev.to/ghagsagar23</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%2F4016716%2F3b117c4e-7c91-4107-903e-a8468d8bdcfb.png</url>
      <title>DEV Community: Sagar Ghag</title>
      <link>https://dev.to/ghagsagar23</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ghagsagar23"/>
    <language>en</language>
    <item>
      <title>I Built ai_guardrails: Local-First Safety for Dart and Flutter AI Apps</title>
      <dc:creator>Sagar Ghag</dc:creator>
      <pubDate>Fri, 11 Sep 2026 12:12:50 +0000</pubDate>
      <link>https://dev.to/ghagsagar23/i-built-aiguardrails-local-first-safety-for-dart-and-flutter-ai-apps-2jc1</link>
      <guid>https://dev.to/ghagsagar23/i-built-aiguardrails-local-first-safety-for-dart-and-flutter-ai-apps-2jc1</guid>
      <description>&lt;p&gt;AI features in Dart and Flutter apps usually start with a provider call. That is also where a few uncomfortable questions begin:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does a user prompt contain an email address, card number, or API key?&lt;/li&gt;
&lt;li&gt;Is retrieved RAG content trying to override the system prompt?&lt;/li&gt;
&lt;li&gt;Is streamed output about to leak a secret or produce unsafe tool-call arguments?&lt;/li&gt;
&lt;li&gt;Can we add these protections without sending user data to another service?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I built &lt;a href="https://pub.dev/packages/ai_guardrails" rel="noopener noreferrer"&gt;&lt;code&gt;ai_guardrails&lt;/code&gt;&lt;/a&gt; to make those checks a small, local layer around any LLM integration.&lt;/p&gt;

&lt;p&gt;It is a pure-Dart, provider-agnostic package for Dart and Flutter. It has no runtime dependencies and does not make network calls by itself. Use it with OpenAI, Gemini, Anthropic, a local model, or your own HTTP gateway.&lt;/p&gt;

&lt;h2&gt;
  
  
  The basic idea
&lt;/h2&gt;

&lt;p&gt;Create a scanner chain for input and output, then wrap the call you already have:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:ai_guardrails/ai_guardrails.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;guard&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;AiGuard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nl"&gt;inputScanners:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="n"&gt;PiiScanner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;action:&lt;/span&gt; &lt;span class="n"&gt;GuardAction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;redact&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;SecretScanner&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;PromptInjectionScanner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;threshold:&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;InvisibleTextScanner&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="nl"&gt;outputScanners:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="n"&gt;RepetitionScanner&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;SchemaValidator&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="s"&gt;'type'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'object'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s"&gt;'required'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'answer'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="s"&gt;'properties'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;'answer'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;'type'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'string'&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;outcome&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;guard&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nl"&gt;input:&lt;/span&gt; &lt;span class="n"&gt;userMessage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;llmCall:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sanitizedInput&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;myLlm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;complete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sanitizedInput&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;outcome&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;blocked&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Blocked at &lt;/span&gt;&lt;span class="si"&gt;${outcome.blockedStage}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;${outcome.blockReason}&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;outcome&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Redacting scanners run in order, so the model receives the final sanitized input. If an input scanner blocks, the provider call never happens.&lt;/p&gt;

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

&lt;p&gt;&lt;code&gt;ai_guardrails&lt;/code&gt; includes local heuristic scanners for common LLM-risk surfaces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PII detection and redaction across US, EU, India, Brazil, Mexico, Japan, South Korea, Canada, and Australia&lt;/li&gt;
&lt;li&gt;Secret detection for API keys, tokens, JWTs, and private-key blocks&lt;/li&gt;
&lt;li&gt;Prompt-injection, Unicode-smuggling, suspicious URL, and padding-attack detection&lt;/li&gt;
&lt;li&gt;Generated-code, SQL, HTML, JSON, URL, numeric-range, and choice validation&lt;/li&gt;
&lt;li&gt;Tool-call validation and tool-output scanning for agentic workflows&lt;/li&gt;
&lt;li&gt;RAG retrieval scanning, streaming response scanning, multi-turn escalation, and declarative conversation flows&lt;/li&gt;
&lt;li&gt;Policy profiles, JSON configuration, audit logs, metrics, OpenTelemetry-compatible tracing, benchmarking, and red-team probing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The package also supports optional async scanners. Semantic checks such as fact checking, topic safety, hallucination checking, and embedding grounding use callbacks you provide, so the core package stays provider-neutral.&lt;/p&gt;

&lt;h2&gt;
  
  
  PII without sacrificing a natural response
&lt;/h2&gt;

&lt;p&gt;For many chat experiences, redacting PII before it leaves the device is the safe choice—but users still expect a natural response. &lt;code&gt;ai_guardrails&lt;/code&gt; supports a round trip for that case:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;guard&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;AiGuard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nl"&gt;inputScanners:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;PiiScanner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;action:&lt;/span&gt; &lt;span class="n"&gt;GuardAction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;redact&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;outcome&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;guard&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nl"&gt;input:&lt;/span&gt; &lt;span class="s"&gt;'Email alice@example.com about the release.'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;llmCall:&lt;/span&gt; &lt;span class="n"&gt;myLlm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;complete&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// The model sees: "Email [EMAIL_1] about the release."&lt;/span&gt;
&lt;span class="c1"&gt;// outcome.output can restore the placeholder for the app.&lt;/span&gt;
&lt;span class="c1"&gt;// outcome.rawOutput preserves the model's pre-rehydration response.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That behavior is deliberate and should be used only where returning the original value to the user is appropriate. The redaction map and raw output remain available when an application needs tighter control.&lt;/p&gt;

&lt;h2&gt;
  
  
  Designed for the pipeline, not just a single prompt
&lt;/h2&gt;

&lt;p&gt;LLM safety does not end at a chat textbox. The package has first-class stages for the inputs an application assembles around a model call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Drop poisoned or unsafe RAG chunks before prompt assembly.&lt;/span&gt;
&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;retrieval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;guard&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;runRetrievalStage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;retrievedChunks&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Scan untrusted tool results before returning them to the model.&lt;/span&gt;
&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;toolResults&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;guard&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;runToolOutputStage&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="n"&gt;ToolOutput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;toolName:&lt;/span&gt; &lt;span class="s"&gt;'search'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;content:&lt;/span&gt; &lt;span class="n"&gt;untrustedSearchResult&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For streaming applications, &lt;code&gt;StreamingAiGuard&lt;/code&gt; scans complete response segments as they arrive and ends the stream when a scanner blocks. For whole-document rules such as JSON schema validation, run a final full-output pass after the stream completes.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this package is—and is not
&lt;/h2&gt;

&lt;p&gt;The built-in local scanners are deterministic heuristics. They are intentionally fast, offline, and inexpensive, but they do not understand every meaning, evasion, or cultural context. Treat them as defense in depth, not as a compliance certification or a substitute for application authorization.&lt;/p&gt;

&lt;p&gt;For semantic checks, the package exposes callback boundaries rather than embedding a provider SDK. Your app decides whether to use an on-device model, a private endpoint, or a cloud model—and owns the associated privacy and cost choices.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dart pub add ai_guardrails
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The package is open source under Apache-2.0. The README includes provider examples, configuration-driven policies, and a full scanner reference.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://pub.dev/packages/ai_guardrails" rel="noopener noreferrer"&gt;Package on pub.dev&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/GhagSagar23/ai_guardrails" rel="noopener noreferrer"&gt;Source code and issue tracker&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I would especially love feedback from teams building Flutter copilots, RAG search, local-model experiences, and agentic tools. What safety checks are you currently reimplementing in every Dart project?&lt;/p&gt;

</description>
      <category>dart</category>
      <category>flutter</category>
      <category>ai</category>
      <category>security</category>
    </item>
  </channel>
</rss>
