<?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: Alexey Polyakov</title>
    <description>The latest articles on DEV Community by Alexey Polyakov (@alexey_polyakov_cfe2095e3).</description>
    <link>https://dev.to/alexey_polyakov_cfe2095e3</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%2F3977419%2F4ba77741-d2f9-4ae7-be8e-4683889ce5fc.jpg</url>
      <title>DEV Community: Alexey Polyakov</title>
      <link>https://dev.to/alexey_polyakov_cfe2095e3</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/alexey_polyakov_cfe2095e3"/>
    <language>en</language>
    <item>
      <title>Proof-of-Fix in GSC: An Architecture for Provable Vulnerability Remediation Verification</title>
      <dc:creator>Alexey Polyakov</dc:creator>
      <pubDate>Sun, 16 Aug 2026 02:12:08 +0000</pubDate>
      <link>https://dev.to/alexey_polyakov_cfe2095e3/proof-of-fix-in-gsc-an-architecture-for-provable-vulnerability-remediation-verification-29ck</link>
      <guid>https://dev.to/alexey_polyakov_cfe2095e3/proof-of-fix-in-gsc-an-architecture-for-provable-vulnerability-remediation-verification-29ck</guid>
      <description>&lt;h2&gt;
  
  
  Abstract
&lt;/h2&gt;

&lt;p&gt;GSC is a self-learning AppSec platform implementing the full pipeline&lt;br&gt;
&lt;code&gt;detect → prove → fix → verify → heal → predict&lt;/code&gt;. Its defining feature is&lt;br&gt;
&lt;strong&gt;Proof-of-Fix (PoF)&lt;/strong&gt;: a mechanism that does not &lt;em&gt;suggest&lt;/em&gt; a fix but &lt;strong&gt;proves&lt;/strong&gt;&lt;br&gt;
it. This paper describes the PoF architecture not as a list of modules but as&lt;br&gt;
&lt;strong&gt;three architectural contracts&lt;/strong&gt; — markers, isolation, and verification — that&lt;br&gt;
collapse into a single formula and define exactly what the &lt;code&gt;verified&lt;/code&gt; verdict&lt;br&gt;
guarantees. It also shows why competitors (Snyk CodeFix, Veracode Fix) stop at&lt;br&gt;
"assumption" while PoF reaches "fact".&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Keywords:&lt;/strong&gt; SAST, Proof-of-Fix, architectural contracts, PoC generation,&lt;br&gt;
sandbox isolation, fail-closed, verified remediation.&lt;/p&gt;


&lt;h2&gt;
  
  
  1. Introduction
&lt;/h2&gt;
&lt;h3&gt;
  
  
  1.1 The problem: "fixed" ≠ "proven"
&lt;/h3&gt;

&lt;p&gt;Classical SAST solves &lt;em&gt;detection&lt;/em&gt;: it locates a suspicious site and emits a&lt;br&gt;
finding. The rest is manual. A fundamental gap remains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The finding may be a false positive&lt;/strong&gt; — the pattern matched, no flaw exists.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The fix may be incomplete&lt;/strong&gt; — one vector closed, a neighboring one still open.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Closed" is not proven&lt;/strong&gt; — a tracker status is not proof of non-exploitation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result is expensive manual triage, a growing pile of unverified findings,&lt;br&gt;
alert fatigue, and — at the limit — a real vulnerability lost in the noise.&lt;/p&gt;
&lt;h3&gt;
  
  
  1.2 What GSC proposes
&lt;/h3&gt;

&lt;p&gt;GSC appends a &lt;em&gt;proof&lt;/em&gt; phase to detection: for every finding it generates a PoC&lt;br&gt;
(code that &lt;em&gt;exploits&lt;/em&gt; the flaw), generates a patch, &lt;strong&gt;runs the PoC against both&lt;br&gt;
code versions in a sandbox&lt;/strong&gt;, and records the verdict: exploitation happened&lt;br&gt;
before, and does not happen after. Only that exact match yields &lt;code&gt;verified&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  1.3 Why competitor patch generation is not enough
&lt;/h3&gt;

&lt;p&gt;To see the value of PoF, one principle difference suffices: competitors stop at&lt;br&gt;
patch &lt;em&gt;generation&lt;/em&gt;; PoF proceeds to &lt;em&gt;verification&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Snyk CodeFix&lt;/strong&gt; emits a fix as a diff but &lt;strong&gt;does not verify the diff actually&lt;br&gt;
eliminates the flaw&lt;/strong&gt;. For SQL injection it may propose a parameterized query&lt;br&gt;
&lt;code&gt;cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))&lt;/code&gt;. But if&lt;br&gt;
&lt;code&gt;user_id&lt;/code&gt; still arrives from an unsanitized source and is not taint-tracked end&lt;br&gt;
to end, the fix remains incomplete — CodeFix does not execute the code to find&lt;br&gt;
out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Veracode Fix&lt;/strong&gt; proposes point replacements (e.g. &lt;code&gt;eval(user_input)&lt;/code&gt; →&lt;br&gt;
&lt;code&gt;JSON.parse(user_input)&lt;/code&gt;) but &lt;strong&gt;does not post-verify&lt;/strong&gt; that &lt;code&gt;user_input&lt;/code&gt; is now&lt;br&gt;
valid and free of exploitable constructs (nested SSRF, ReDoS in the parser). No&lt;br&gt;
execution check is performed — the tool trusts the proposed replacement.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The key takeaway:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Patch generation is an &lt;strong&gt;assumption&lt;/strong&gt;. Verification through a repeated PoC is a&lt;br&gt;
&lt;strong&gt;fact&lt;/strong&gt;. GSC moves from assumption to fact: a patch counts as fixed only when&lt;br&gt;
the exploit that &lt;em&gt;reproduced&lt;/em&gt; the flaw &lt;em&gt;stops firing&lt;/em&gt; against the patched code.&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h2&gt;
  
  
  2. Proof-of-Fix as an architectural contract
&lt;/h2&gt;

&lt;p&gt;PoF reliability is defined not by a set of modules but by &lt;strong&gt;three architectural&lt;br&gt;
contracts&lt;/strong&gt;. They — not implementation syntax — are the essence of the&lt;br&gt;
architecture.&lt;/p&gt;
&lt;h3&gt;
  
  
  2.1 The marker contract
&lt;/h3&gt;

&lt;p&gt;A PoC must &lt;em&gt;prove&lt;/em&gt; exploitation by printing one of the success markers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;VULNERABLE | EXPLOITED | PWNED | LEAKED | SUCCESS | BREACH
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;PoC success is recorded &lt;strong&gt;only&lt;/strong&gt; when both conditions hold: &lt;code&gt;exit_code == 0&lt;/code&gt;&lt;br&gt;
&lt;strong&gt;and&lt;/strong&gt; a marker is present in stdout. A zero exit code alone is not enough —&lt;br&gt;
otherwise a PoC that merely "finished without error" would be wrongly treated as&lt;br&gt;
exploiting. This contract guards against false positives.&lt;/p&gt;
&lt;h3&gt;
  
  
  2.2 The isolation contract
&lt;/h3&gt;

&lt;p&gt;Every PoC execution happens in a sandbox with resource and network limits:&lt;br&gt;
&lt;code&gt;RLIMIT_CPU&lt;/code&gt;, &lt;code&gt;RLIMIT_AS&lt;/code&gt;, &lt;code&gt;--network none&lt;/code&gt;, &lt;code&gt;--read-only&lt;/code&gt;, all capabilities&lt;br&gt;
dropped (details in §4). The contract guarantees the PoC &lt;strong&gt;cannot&lt;/strong&gt; read host&lt;br&gt;
secrets, exhaust CI resources, or compromise the scanning environment.&lt;/p&gt;
&lt;h3&gt;
  
  
  2.3 The verification contract
&lt;/h3&gt;

&lt;p&gt;A fix is accepted as successful &lt;strong&gt;only&lt;/strong&gt; on the exact match:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;poc_before_success&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;   &lt;span class="n"&gt;AND&lt;/span&gt;   &lt;span class="n"&gt;poc_after_success&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Any deviation — incomplete fix or false-positive finding — renders the result&lt;br&gt;
unverified. This is the mathematical definition of &lt;code&gt;verified&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  2.4 The PoF formula
&lt;/h3&gt;

&lt;p&gt;The three contracts collapse into one formula:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;PoF_verified&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;poc_before_success&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
             &lt;span class="err"&gt;∧&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;poc_after_success&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
             &lt;span class="err"&gt;∧&lt;/span&gt; &lt;span class="nf"&gt;is_container_isolation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;before&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
             &lt;span class="err"&gt;∧&lt;/span&gt; &lt;span class="nf"&gt;is_container_isolation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;after&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The third and fourth conjuncts are the &lt;strong&gt;fail-closed gate&lt;/strong&gt;: &lt;code&gt;verified&lt;/code&gt; is&lt;br&gt;
admissible only when both stages ran in an OS-isolated container, not in a&lt;br&gt;
degraded mode. Consequence:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If PoF says &lt;code&gt;verified&lt;/code&gt; — the vulnerability was reproduced and removed in a&lt;br&gt;
controlled environment, and no third-party code could have influenced the&lt;br&gt;
result.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  3. System overview and PoF's place
&lt;/h2&gt;

&lt;h3&gt;
  
  
  3.1 The full GSC pipeline
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;detect ──► prove ──► fix ──► verify ──► heal ──► predict
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Phase&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;detect&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;static analysis, 41 detectors&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;prove&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;PoC generation, exploitation confirmation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;fix&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;patch generation (LLM)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;verify&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;run PoC against before/after in a sandbox&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;heal&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;auto-remediation in CI (self-healing)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;predict&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;predictive analytics over history&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Current core state: &lt;strong&gt;41 detectors&lt;/strong&gt; (37 registry + 4 standalone engines:&lt;br&gt;
Invariants GS028, Secrets GS029, SCA/OSV.dev GS030, IaC GS031), schema 32,&lt;br&gt;
109 modules, ~494K findings in a SQLite DB, 234 tests across 55 files. Figures&lt;br&gt;
are verified via &lt;code&gt;python3 gsc_meta.py&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.2 Data flow
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;finding + source
      │
      ▼
[PoC generation] ──► poc_code          (deterministic or LLM)
      │
      ▼
[Patch generation] ──► patch, up to MAX_ITERATIONS=3
      │
      ▼
[Sandbox]  PoC vs VULNERABLE code ──► poc_before_success
           PoC vs PATCHED code    ──► poc_after_success
      │
      ▼
[Fail-closed gate]  verification contract + OS-isolation
      │
      ▼
FixEvidence: verified | structural | syntax_only | failed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  4. Isolated execution
&lt;/h2&gt;

&lt;p&gt;Running a generated PoC is the &lt;strong&gt;critical security boundary&lt;/strong&gt;: a PoC is, by&lt;br&gt;
definition, malicious code. GSC addresses this with two-tier isolation.&lt;/p&gt;

&lt;h3&gt;
  
  
  4.1 Container isolation (primary path)
&lt;/h3&gt;

&lt;p&gt;The PoC runs in a disposable container (Docker or Podman) with a strict flag set:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Flag&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;th&gt;Guarantee&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--network none&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;egress deny&lt;/td&gt;
&lt;td&gt;the PoC cannot reach the network&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--read-only&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;read-only rootfs&lt;/td&gt;
&lt;td&gt;container filesystem is immutable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--cap-drop ALL&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;drop all capabilities&lt;/td&gt;
&lt;td&gt;no kernel privileges&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--security-opt no-new-privileges&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;no setuid&lt;/td&gt;
&lt;td&gt;no privilege escalation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--pids-limit 64&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;process limit&lt;/td&gt;
&lt;td&gt;no fork bomb&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;--memory 512m&lt;/code&gt; / &lt;code&gt;--cpus 1&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;resource caps&lt;/td&gt;
&lt;td&gt;no CPU/RAM exhaustion&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--user 65534:65534&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;nobody UID&lt;/td&gt;
&lt;td&gt;no root inside the container&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;--tmpfs /tmp&lt;/code&gt; + workdir mount&lt;/td&gt;
&lt;td&gt;only workspace writable&lt;/td&gt;
&lt;td&gt;writes confined to its own dir&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  4.2 rlimit fallback (degradation)
&lt;/h3&gt;

&lt;p&gt;Without a container runtime, the PoC runs as a subprocess with POSIX &lt;code&gt;rlimit&lt;/code&gt;&lt;br&gt;
(&lt;code&gt;RLIMIT_CPU&lt;/code&gt; 30 s, &lt;code&gt;RLIMIT_AS&lt;/code&gt; 512 MB, &lt;code&gt;RLIMIT_NPROC&lt;/code&gt; 64, &lt;code&gt;RLIMIT_FSIZE&lt;/code&gt; 16 MB,&lt;br&gt;
&lt;code&gt;RLIMIT_NOFILE&lt;/code&gt; 256). &lt;strong&gt;Important:&lt;/strong&gt; rlimit is a &lt;em&gt;degradation&lt;/em&gt;, not isolation; at&lt;br&gt;
the fail-closed gate a result produced under rlimit is &lt;strong&gt;never&lt;/strong&gt; labeled&lt;br&gt;
&lt;code&gt;verified&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  4.3 Environment isolation
&lt;/h3&gt;

&lt;p&gt;The PoC receives a whitelist of 9 environment variables instead of the full&lt;br&gt;
&lt;code&gt;os.environ&lt;/code&gt;, closing the host-secret leak (&lt;code&gt;DEEPSEEK_API_KEY&lt;/code&gt;, &lt;code&gt;GITHUB_TOKEN&lt;/code&gt;,&lt;br&gt;
&lt;code&gt;JWT_SECRET&lt;/code&gt;) into executed PoC code (finding DD-01 of an independent audit).&lt;/p&gt;




&lt;h2&gt;
  
  
  5. PoC and patch generation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  5.1 PoC: deterministic + LLM
&lt;/h3&gt;

&lt;p&gt;For common flaw classes (SQLi, command injection, IDOR, XSS, SSRF, open redirect,&lt;br&gt;
and via title keywords — SSTI/pickle/XXE/path traversal) the PoC is generated&lt;br&gt;
&lt;strong&gt;without an LLM&lt;/strong&gt;, from strict templates. For the rest — via an LLM (&lt;code&gt;DeepSeek&lt;/code&gt;,&lt;br&gt;
60 s timeout). Without &lt;code&gt;DEEPSEEK_API_KEY&lt;/code&gt; the system degrades to regex-only mode.&lt;br&gt;
Each execution is bounded: 30 s, output ≤ 4096 bytes, network neutralized by a&lt;br&gt;
&lt;code&gt;127.0.0.1:9&lt;/code&gt; discard-port proxy.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.2 Patch: minimality and atomicity
&lt;/h3&gt;

&lt;p&gt;The patch is generated as a set of edit-instructions (&lt;code&gt;find&lt;/code&gt;/&lt;code&gt;replace&lt;/code&gt; pairs)&lt;br&gt;
where &lt;code&gt;find&lt;/code&gt; must match the file text exactly. This yields two properties&lt;br&gt;
essential to reliability: the patch is &lt;strong&gt;minimal&lt;/strong&gt; (reviewable) and &lt;strong&gt;atomic&lt;/strong&gt; —&lt;br&gt;
an inexact match discards the attempt wholesale rather than applying a mangled&lt;br&gt;
patch. Up to 3 generation iterations and 6 edits per patch; a failed attempt is&lt;br&gt;
passed back into the LLM context for the next one.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Verification cycle and fail-closed gate
&lt;/h2&gt;

&lt;h3&gt;
  
  
  6.1 before/after PoC
&lt;/h3&gt;

&lt;p&gt;The proof core is a double run of one PoC:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Before:&lt;/strong&gt; the PoC against &lt;em&gt;vulnerable&lt;/em&gt; code → it &lt;strong&gt;must trigger&lt;/strong&gt;. If not,
likely a false positive; the verdict is rejected.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;After:&lt;/strong&gt; the PoC against &lt;em&gt;patched&lt;/em&gt; code → it &lt;strong&gt;must not trigger&lt;/strong&gt;. If it
still does, the fix is incomplete; the verdict is rejected.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  6.2 Fail-closed gate (GSC-001)
&lt;/h3&gt;

&lt;p&gt;The final check: &lt;code&gt;verified&lt;/code&gt; is admissible &lt;strong&gt;only&lt;/strong&gt; when both stages used&lt;br&gt;
OS-isolation. Otherwise the result is returned with an explicit reason, not a&lt;br&gt;
false "verified". Deliberate: better to honestly say "not proven" than to emit an&lt;br&gt;
unjustified signal.&lt;/p&gt;

&lt;h3&gt;
  
  
  6.3 Trust levels
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Level&lt;/th&gt;
&lt;th&gt;Criterion&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;verified&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;PoC triggered before, not after, both stages in a container&lt;/td&gt;
&lt;td&gt;proven&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;structural&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;detector stopped firing on patched code&lt;/td&gt;
&lt;td&gt;indirect&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;syntax_only&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;syntax intact, no proof&lt;/td&gt;
&lt;td&gt;weak&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;failed&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;could not generate/apply/verify&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  7. Security model and guarantees
&lt;/h2&gt;

&lt;h3&gt;
  
  
  7.1 Threat model
&lt;/h3&gt;

&lt;p&gt;The threat is &lt;strong&gt;hostile PoC code&lt;/strong&gt;. An attacker might try to read host secrets,&lt;br&gt;
write to the filesystem, reach the network (SSRF/exfil), exhaust resources, or&lt;br&gt;
escalate privileges.&lt;/p&gt;

&lt;h3&gt;
  
  
  7.2 Defense-in-depth
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Mechanism&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Network&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;--network none&lt;/code&gt; (container) / proxy &lt;code&gt;127.0.0.1:9&lt;/code&gt; (fallback)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Filesystem&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;--read-only&lt;/code&gt; rootfs, writes confined to workdir, &lt;code&gt;RLIMIT_FSIZE&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Privileges&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;--cap-drop ALL&lt;/code&gt;, &lt;code&gt;no-new-privileges&lt;/code&gt;, nobody UID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Resources&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;--pids-limit 64&lt;/code&gt;, &lt;code&gt;--memory 512m&lt;/code&gt;, &lt;code&gt;--cpus 1&lt;/code&gt;, &lt;code&gt;RLIMIT_AS&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Secrets&lt;/td&gt;
&lt;td&gt;env-whitelist (9 vars), never full &lt;code&gt;os.environ&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Trust&lt;/td&gt;
&lt;td&gt;fail-closed gate: no container → no &lt;code&gt;verified&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  7.3 The &lt;code&gt;verified&lt;/code&gt; guarantee
&lt;/h3&gt;

&lt;p&gt;Together the contracts (§2) yield the following guarantee: &lt;strong&gt;if PoF returns&lt;br&gt;
&lt;code&gt;verified&lt;/code&gt;, the vulnerability was factually reproduced by an exploit and&lt;br&gt;
factually removed, in an environment from which the generated code could neither&lt;br&gt;
read secrets nor influence the result&lt;/strong&gt;. This is PoF's real "product": trust&lt;br&gt;
grounded in evidence, not declarations.&lt;/p&gt;




&lt;h2&gt;
  
  
  8. Current state and metrics
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Parameter&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Version&lt;/td&gt;
&lt;td&gt;1.3.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Detectors&lt;/td&gt;
&lt;td&gt;41 (37 registry + 4 standalone)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DB schema&lt;/td&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Modules&lt;/td&gt;
&lt;td&gt;109&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Findings in DB&lt;/td&gt;
&lt;td&gt;~494K&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tests&lt;/td&gt;
&lt;td&gt;234 (55 files)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PoF iterations&lt;/td&gt;
&lt;td&gt;up to 3, up to 6 edits per patch&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PoC timeout&lt;/td&gt;
&lt;td&gt;30 s, output ≤ 4096 bytes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  9. Limitations
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Sandbox language support:&lt;/strong&gt; &lt;code&gt;verify_fix&lt;/code&gt; runs Python PoCs only; other
languages return &lt;code&gt;verified=False&lt;/code&gt; with a reason.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Container-runtime dependency:&lt;/strong&gt; without docker/podman the top trust level
&lt;code&gt;verified&lt;/code&gt; is unreachable — a deliberate fail-closed choice.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LLM-dependent depth:&lt;/strong&gt; without &lt;code&gt;DEEPSEEK_API_KEY&lt;/code&gt;, PoC/patch are generated
only for templated classes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Single file per cycle:&lt;/strong&gt; PoF operates on a single file, not a whole
repository; cross-module exploit chains are outside the current cycle.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  10. Roadmap
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Runtime Validator (IAST-lite):&lt;/strong&gt; verify by actual runtime exploitation —
monkeypatch &lt;code&gt;subprocess&lt;/code&gt;/&lt;code&gt;requests&lt;/code&gt;/&lt;code&gt;open&lt;/code&gt; in the sandbox venv, then &lt;code&gt;strace&lt;/code&gt;
(requires &lt;code&gt;SYS_PTRACE&lt;/code&gt;), then Falco/Tetragon only in enterprise on-prem.
&lt;strong&gt;Never &lt;code&gt;--privileged&lt;/code&gt;&lt;/strong&gt; — it breaks isolation (F-05) and turns PoF from a
defense into an attack vector; any PoC requiring &lt;code&gt;--privileged&lt;/code&gt; is
automatically rejected.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JS/Go/Rust support&lt;/strong&gt; in &lt;code&gt;verify_fix&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PostgreSQL + RLS&lt;/strong&gt; for multi-tenant SaaS (tenant isolation, DD-09).&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  11. Conclusion
&lt;/h2&gt;

&lt;p&gt;Proof-of-Fix turns SAST from a "detector" into a "proven-remediation engine".&lt;br&gt;
The core idea is &lt;strong&gt;fail-closed&lt;/strong&gt;: the system would rather refuse to confirm a fix&lt;br&gt;
than emit an unjustified &lt;code&gt;verified&lt;/code&gt;. Architecturally this is expressed through&lt;br&gt;
three contracts (markers, isolation, verification) and one formula — not a list&lt;br&gt;
of modules. Combined with self-learning, the platform moves toward its goal:&lt;br&gt;
making security &lt;em&gt;verifiable&lt;/em&gt; rather than merely &lt;em&gt;declared&lt;/em&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Appendix A. Traceability (code ↔ claim map)
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Claim&lt;/th&gt;
&lt;th&gt;File:lines&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;41 detectors (37+4), schema 32, 109 modules&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;gsc_meta.py&lt;/code&gt; → &lt;code&gt;get_meta()&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Levels &lt;code&gt;verified/structural/syntax_only/failed&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;gsc_proofoffix.py:52&lt;/code&gt;, &lt;code&gt;_classify()&lt;/code&gt; ~476&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Exploitation markers&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gsc_pof_sandbox.py:21&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PoC timeout/limits&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gsc_proofoffix.py:31-34&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Network proxy stub&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gsc_proofoffix.py:37-43&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Container isolation flags&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gsc_pof_sandbox.py:230-244&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;rlimit limits&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gsc_pof_sandbox.py:122-153&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;env-whitelist (DD-01)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;gsc_pof_sandbox.py:36-40&lt;/code&gt;, &lt;code&gt;gsc_proofoffix.py:133+&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;before/after cycle + fail-closed (GSC-001)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gsc_pof_sandbox.py:319-362&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LLM call (DeepSeek)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gsc_proofoffix.py:205&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deterministic PoC&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;gsc_proofoffix.py:_generate_poc_code&lt;/code&gt; ~492&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;This document was generated from the actual code of &lt;code&gt;poliakarmai/gsc&lt;/code&gt; (branch&lt;br&gt;
&lt;code&gt;master&lt;/code&gt;). All figures were verified by running &lt;code&gt;python3 gsc_meta.py&lt;/code&gt; and reading&lt;br&gt;
the sources.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>cybersecurity</category>
      <category>python</category>
      <category>devsecops</category>
    </item>
    <item>
      <title>Clear the Lineup: XSS via innerHTML in cyberbro — Found and Fixed by GSC</title>
      <dc:creator>Alexey Polyakov</dc:creator>
      <pubDate>Wed, 12 Aug 2026 04:55:04 +0000</pubDate>
      <link>https://dev.to/alexey_polyakov_cfe2095e3/clear-the-lineup-xss-via-innerhtml-in-cyberbro-found-and-fixed-by-gsc-14mg</link>
      <guid>https://dev.to/alexey_polyakov_cfe2095e3/clear-the-lineup-xss-via-innerhtml-in-cyberbro-found-and-fixed-by-gsc-14mg</guid>
      <description>&lt;h2&gt;
  
  
  What I Fixed
&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;Cross-Site Scripting (XSS)&lt;/strong&gt; vulnerability in &lt;a href="https://github.com/stanfrbd/cyberbro" rel="noopener noreferrer"&gt;cyberbro&lt;/a&gt; (122 ⭐), an open-source OSINT platform. The search highlight feature used &lt;code&gt;.innerHTML&lt;/code&gt; with unsanitized user input, allowing reflected XSS in search results.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Found by:&lt;/strong&gt; &lt;a href="https://github.com/poliakarmai/gsc" rel="noopener noreferrer"&gt;GSC (Git Security Checker)&lt;/a&gt; — self-learning SAST scanner.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bug
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;File:&lt;/strong&gt; &lt;code&gt;src/views/SearchView.js&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The search highlight function took user search terms and injected them directly into the DOM via &lt;code&gt;.innerHTML&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="c1"&gt;// ❌ VULNERABLE: user input in innerHTML&lt;/span&gt;
&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;highlightSearchTerms&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;highlightSearchTerms()&lt;/code&gt; function wrapped matched terms in &lt;code&gt;&amp;lt;mark&amp;gt;&lt;/code&gt; tags — but if the search query itself contained HTML tags or JavaScript, they'd be rendered as live HTML:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Search query: &amp;lt;img src=x onerror=alert(document.cookie)&amp;gt;
→ Result: DOM XSS execution in victim's browser
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Fix
&lt;/h2&gt;

&lt;p&gt;Replace &lt;code&gt;.innerHTML&lt;/code&gt; with &lt;code&gt;.textContent&lt;/code&gt; + explicit &lt;code&gt;&amp;lt;mark&amp;gt;&lt;/code&gt; element creation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ✅ FIXED: safe DOM manipulation without HTML injection&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;highlighted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;highlightSearchTerms&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;query&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;temp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;span&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;temp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;highlighted&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Transfer marked nodes safely&lt;/span&gt;
&lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;firstChild&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;firstChild&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;temp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;firstChild&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;temp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;firstChild&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;PR:&lt;/strong&gt; &lt;a href="https://github.com/stanfrbd/cyberbro/pull/212" rel="noopener noreferrer"&gt;stanfrbd/cyberbro#212&lt;/a&gt; — &lt;strong&gt;✅ Merged&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Impact
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Severity:&lt;/strong&gt; MEDIUM (CVSS 5.4) — stored/reflected XSS, CWE-79, no-auth context&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Affected versions:&lt;/strong&gt; All versions before fix&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vector:&lt;/strong&gt; Search functionality — accessible to any unauthenticated user&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exploit:&lt;/strong&gt; One click by victim → attacker-controlled JavaScript execution → analysis data exfiltration, DOM manipulation&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why &lt;code&gt;.innerHTML&lt;/code&gt; is Dangerous
&lt;/h2&gt;

&lt;p&gt;The three golden rules of DOM security:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Never use &lt;code&gt;.innerHTML&lt;/code&gt; with user input.&lt;/strong&gt; Use &lt;code&gt;.textContent&lt;/code&gt; + create elements programmatically.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Even &lt;code&gt;.innerHTML&lt;/code&gt; with "sanitized" input is risky.&lt;/strong&gt; Sanitizers have bypasses (see mXSS attacks).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Defense-in-depth:&lt;/strong&gt; Content-Security-Policy + output encoding + safe APIs.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is OWASP Top 10 (A03:2021 — Injection) and CWE-79 (Cross-Site Scripting).&lt;/p&gt;

&lt;h2&gt;
  
  
  How GSC Found It
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/poliakarmai/gsc" rel="noopener noreferrer"&gt;GSC&lt;/a&gt; (Git Security Checker) is a self-learning SAST platform that detected this with its GS020 XSS detector. The detector uses pattern-based regex matching for DOM sinks (&lt;code&gt;innerHTML&lt;/code&gt;, &lt;code&gt;document.write&lt;/code&gt;, &lt;code&gt;dangerouslySetInnerHTML&lt;/code&gt;) and template injection patterns across 7+ languages.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gsc scan cyberbro/ &lt;span class="nt"&gt;--ci&lt;/span&gt; &lt;span class="nt"&gt;--json&lt;/span&gt;
&lt;span class="c"&gt;# → GS020 CRITICAL: "DOM XSS: .innerHTML assignment"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;GSC doesn't just find vulnerabilities — it proves them with auto-generated exploits, auto-generates verified fixes, and opens PRs. This cyberbro fix is one of &lt;a href="https://github.com/poliakarmai/gsc/blob/master/GSC_PRS.md" rel="noopener noreferrer"&gt;6 security PRs&lt;/a&gt; created by GSC.&lt;/p&gt;

&lt;h2&gt;
  
  
  Credits
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Scanner:&lt;/strong&gt; &lt;a href="https://github.com/poliakarmai/gsc" rel="noopener noreferrer"&gt;GSC — Git Security Checker&lt;/a&gt; by @poliakarmai&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Repository:&lt;/strong&gt; &lt;a href="https://github.com/stanfrbd/cyberbro" rel="noopener noreferrer"&gt;stanfrbd/cyberbro&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Maintainer:&lt;/strong&gt; @stanfrbd — thank you for the quick review and merge!&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>bugsmash</category>
      <category>security</category>
      <category>xss</category>
      <category>javascript</category>
    </item>
    <item>
      <title>I Built an AI-Native Trading Engine in Python. 5 Months Later, Here's What Changed</title>
      <dc:creator>Alexey Polyakov</dc:creator>
      <pubDate>Wed, 17 Jun 2026 10:44:34 +0000</pubDate>
      <link>https://dev.to/alexey_polyakov_cfe2095e3/i-built-an-ai-native-trading-engine-in-python-5-months-later-heres-what-changed-317</link>
      <guid>https://dev.to/alexey_polyakov_cfe2095e3/i-built-an-ai-native-trading-engine-in-python-5-months-later-heres-what-changed-317</guid>
      <description>&lt;h1&gt;
  
  
  I Built an AI-Native Trading Engine in Python. 5 Months Later, Here's What Changed
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;9 strategies → 12. ML scoring, backtesting, partial take-profit, Telegram bot that survived a 3-echelon audit. Open source, MIT, trading real money.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Why I Built This (And Kept Building)
&lt;/h2&gt;

&lt;p&gt;Trading bots come in two flavors: black-box SaaS at $50/month, or GitHub scripts that crash at 3 AM. I wanted neither.&lt;/p&gt;

&lt;p&gt;Five months ago I shipped v1 of &lt;strong&gt;bybit-ws&lt;/strong&gt; — an AI-native trading engine for Bybit futures. The core loop was simple: scan Bollinger Bands daily, score every signal across 8 metrics, enter when the score passes 5.5/10. It worked. It made money. And then I kept building.&lt;/p&gt;

&lt;p&gt;This is the v2 story: machine learning on real trade data, a backtesting engine that runs against historical klines, partial take-profit logic, and a Telegram bot that survived 14 production bugs found by three AI agents in parallel.&lt;/p&gt;




&lt;h2&gt;
  
  
  Architecture: What 5 Months of Iteration Looks Like
&lt;/h2&gt;

&lt;p&gt;v1 was clean. v2 is battle-tested.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    ┌──────────────────────────┐
                    │      Hermes Agent          │
                    │  Voice/chat orchestrator   │
                    └───────────┬────────────────┘
                                │ MCP / REST (port 8766)
                    ┌───────────▼────────────────┐
                    │     bybit-ws (systemd)      │
                    │                             │
                    │  main.py — 30s light cycle  │
                    │         — 120s heavy cycle  │
                    │         — 480s x10 cycle    │
                    │                             │
                    │  ┌─ auto_sl.py              │
                    │  ├─ trailing_sl.py           │
                    │  ├─ trailing_sl_x10.py   ★   │
                    │  ├─ partial_tp.py        ★   │
                    │  ├─ funding_rotation.py  ★   │
                    │  ├─ ml_scorer.py         ★   │
                    │  ├─ backtest.py          ★   │
                    │  ├─ gridsignal_scanner.py    │
                    │  ├─ pump_detect.py           │
                    │  ├─ rpc.py (JSON-RPC)        │
                    │  └─ state_db.py (SQLite)     │
                    └───────────┬────────────────┘
                                │ WebSocket
                    ┌───────────▼────────────────┐
                    │       Bybit API v5          │
                    └────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;★ = new in v2&lt;/p&gt;

&lt;p&gt;Three cycles instead of two. The new &lt;strong&gt;x10 cycle&lt;/strong&gt; (every 8 minutes) handles trailing stops for high-leverage positions without touching the 3x positions. The heavy cycle (every 2 minutes, down from 7) now includes partial take-profit and funding rotation checks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;State is SQLite, period.&lt;/strong&gt; v1 had a mix of in-memory dicts and JSON snapshots. v2 uses SQLite with WAL mode as the single source of truth. 8 tables, atomic UPDATEs with WHERE clauses, zero race conditions. JSON snapshots are backup-only.&lt;/p&gt;




&lt;h2&gt;
  
  
  12 Strategies (Up from 9)
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Strategy&lt;/th&gt;
&lt;th&gt;Lev&lt;/th&gt;
&lt;th&gt;Timeframe&lt;/th&gt;
&lt;th&gt;What's New&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Bollinger Grid LONG&lt;/td&gt;
&lt;td&gt;3x&lt;/td&gt;
&lt;td&gt;Daily&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bollinger Grid SHORT&lt;/td&gt;
&lt;td&gt;3x&lt;/td&gt;
&lt;td&gt;Daily&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Junk Short&lt;/td&gt;
&lt;td&gt;3x&lt;/td&gt;
&lt;td&gt;Daily&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SL Re-entry&lt;/td&gt;
&lt;td&gt;3x&lt;/td&gt;
&lt;td&gt;Daily&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DCA Ladder&lt;/td&gt;
&lt;td&gt;3x&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BB Scalping&lt;/td&gt;
&lt;td&gt;10x&lt;/td&gt;
&lt;td&gt;M5&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mean Reversion&lt;/td&gt;
&lt;td&gt;10x&lt;/td&gt;
&lt;td&gt;Daily&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Funding Momentum&lt;/td&gt;
&lt;td&gt;10x&lt;/td&gt;
&lt;td&gt;Daily&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ATR Risk Sizing&lt;/td&gt;
&lt;td&gt;layer&lt;/td&gt;
&lt;td&gt;15m&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Partial TP&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;★&lt;/td&gt;
&lt;td&gt;dynamic&lt;/td&gt;
&lt;td&gt;20→50% scale-out, no numpy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Trailing SL x10&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;★&lt;/td&gt;
&lt;td&gt;x10 only&lt;/td&gt;
&lt;td&gt;Tight trailing on high-lev positions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Funding Rotation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;★&lt;/td&gt;
&lt;td&gt;auto&lt;/td&gt;
&lt;td&gt;Closes positions before negative funding hits&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The golden rule still applies: every entry passes through &lt;strong&gt;scoring across 8+ metrics&lt;/strong&gt; with a 5.5/10 threshold. But now there's a new layer on top.&lt;/p&gt;




&lt;h2&gt;
  
  
  ML Scoring: When Heuristics Aren't Enough
&lt;/h2&gt;

&lt;p&gt;After 5 months, the system had logged &lt;strong&gt;282 real signals&lt;/strong&gt; with outcomes — entry price, exit price, PnL, whether it hit take-profit or stop-loss. That's a dataset.&lt;/p&gt;

&lt;p&gt;I trained a &lt;strong&gt;RandomForest classifier&lt;/strong&gt; on it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;features&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;score&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;price_vs_lower&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;price_vs_upper&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;volume_24h&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;funding_rate&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;rsi_14&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;bb_squeeze&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;consecutive_down_days&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;target&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;is_profitable&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;  &lt;span class="c1"&gt;# 1 if PnL &amp;gt; 0, else 0
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Results:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;F1 score: 0.69&lt;/strong&gt; on a 262-signal test set&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Top features:&lt;/strong&gt; score (0.31 weight), price_vs_lower (0.22), RSI (0.15)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Combined scoring:&lt;/strong&gt; 70% ML + 30% heuristic. If they disagree, ML wins.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The model runs every heavy cycle, re-scores open positions, and can veto new entries. It's not a black box — feature importance is logged, so I can see &lt;em&gt;why&lt;/em&gt; it made a decision.&lt;/p&gt;

&lt;p&gt;Is F1=0.69 "AI that prints money"? No. It's a filter that catches bad entries the heuristic would miss. In backtesting, the ML layer improved average PnL per trade by 18% just by rejecting the bottom quartile of signals.&lt;/p&gt;




&lt;h2&gt;
  
  
  Backtesting: Walk-Forward on Real Klines
&lt;/h2&gt;

&lt;p&gt;You can't trust a strategy you haven't backtested. I built a walk-forward engine that pulls historical klines from Bybit's REST API and replays them day by day:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Each day: scan → score → simulate entry → track PnL
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;day&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;trading_days&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;klines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fetch_klines&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;day&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start_ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;signals&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;scan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;klines&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;strategy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;bollinger_grid&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;sig&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;signals&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;trade&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;simulate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;klines&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;day&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;day&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;trade&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tested on BTC, ETH, SUI, and ADA with Daily signals:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Symbol&lt;/th&gt;
&lt;th&gt;Win Rate&lt;/th&gt;
&lt;th&gt;Avg PnL&lt;/th&gt;
&lt;th&gt;Best Trade&lt;/th&gt;
&lt;th&gt;Worst Trade&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SUIUSDT&lt;/td&gt;
&lt;td&gt;42.9%&lt;/td&gt;
&lt;td&gt;+6.56%&lt;/td&gt;
&lt;td&gt;+27.3%&lt;/td&gt;
&lt;td&gt;−12.1%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ADAUSDT&lt;/td&gt;
&lt;td&gt;38.5%&lt;/td&gt;
&lt;td&gt;+4.82%&lt;/td&gt;
&lt;td&gt;+19.4%&lt;/td&gt;
&lt;td&gt;−11.7%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BTCUSDT&lt;/td&gt;
&lt;td&gt;35.7%&lt;/td&gt;
&lt;td&gt;+3.11%&lt;/td&gt;
&lt;td&gt;+14.2%&lt;/td&gt;
&lt;td&gt;−9.3%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ETHUSDT&lt;/td&gt;
&lt;td&gt;33.3%&lt;/td&gt;
&lt;td&gt;+1.95%&lt;/td&gt;
&lt;td&gt;+11.8%&lt;/td&gt;
&lt;td&gt;−10.5%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Not every strategy is a winner. The backtest exposed that ETH is borderline — win rate barely above random, average PnL close to fees. That's valuable information. I'd rather know from backtesting than from a blown account.&lt;/p&gt;




&lt;h2&gt;
  
  
  Risk Management: The Boring Stuff That Saves Accounts
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Partial Take-Profit
&lt;/h3&gt;

&lt;p&gt;Most bots close a position all at once. Partial TP scales out gradually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Dynamic split: 20% at first TP → up to 50% at final TP
&lt;/span&gt;&lt;span class="n"&gt;tp_levels&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;calculate_partial_tp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;entry_price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mark_price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;unrealized_pnl_pct&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# First fill: 20% of position, SL moves to breakeven
# Second fill: 30% more, trailing SL activates
# 50% left rides with trailing stop
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No numpy. Pure &lt;code&gt;statistics&lt;/code&gt; module. Works on any Python 3.11+ without extra dependencies.&lt;/p&gt;

&lt;h3&gt;
  
  
  Trailing Stop for x10
&lt;/h3&gt;

&lt;p&gt;High-leverage positions move fast. The new &lt;code&gt;trailing_sl_x10&lt;/code&gt; module runs every heavy cycle, but &lt;strong&gt;only for positions with leverage ≥10&lt;/strong&gt;. It tightens the stop-loss when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;W-BB drops below 25% (price approaching lower band for LONG)&lt;/li&gt;
&lt;li&gt;PnL exceeds 15% (lock in profits)&lt;/li&gt;
&lt;li&gt;Update threshold: 0.5% from current mark price (avoids SL churn)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Auto Funding Rotation
&lt;/h3&gt;

&lt;p&gt;Negative funding rates eat your margin. The rotation module checks every heavy cycle:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;If any open position has funding rate below −0.01%: flag it&lt;/li&gt;
&lt;li&gt;If a better alternative exists (positive funding + BB signal): close current, open new&lt;/li&gt;
&lt;li&gt;Order matters: &lt;strong&gt;open first, then close&lt;/strong&gt; — never be out of the market during rotation&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  The Telegram Bot That Survived Production
&lt;/h2&gt;

&lt;p&gt;The @Gridbolbot (formerly @GridSignalBot) is a 2,153-line Telegram bot that scans markets, sends alerts, and lets users execute trades inline. Five months in, it went silent.&lt;/p&gt;

&lt;p&gt;Not "slow" — dead silent. But the logs were clean, the process was running, systemd showed &lt;code&gt;active&lt;/code&gt;. Classic Heisenbug.&lt;/p&gt;

&lt;p&gt;I ran a &lt;strong&gt;3-echelon AI audit&lt;/strong&gt; — three agents in parallel, each with a different focus:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Source-Driven:&lt;/strong&gt; code vs official documentation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security:&lt;/strong&gt; secrets, CVEs, command injection&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Adversarial:&lt;/strong&gt; race conditions, blocking calls, logic bugs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Four minutes later: &lt;strong&gt;14 findings&lt;/strong&gt;. Five CRITICAL. Highlights:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;_valid_symbol()&lt;/code&gt; — called in 3 places, &lt;strong&gt;defined in zero&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Nine blocking &lt;code&gt;subprocess.run()&lt;/code&gt; calls inside async handlers&lt;/li&gt;
&lt;li&gt;Race condition in daily scan limit (&lt;code&gt;UPDATE ... SET +1&lt;/code&gt; without WHERE check)&lt;/li&gt;
&lt;li&gt;Ghost buttons in the keyboard with no handlers&lt;/li&gt;
&lt;li&gt;SQLite without WAL mode, database sitting in &lt;code&gt;.gitignore&lt;/code&gt; blind spot&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;After fixes: &lt;strong&gt;45/45 smoke tests green&lt;/strong&gt;, bot responds instantly. Full story in the &lt;a href="https://dev.to/alexey_polyakov_cfe2095e3/i-built-an-ai-native-trading-engine-in-python-heres-how-it-works-54p0"&gt;separate article&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Testing: 45 Tests, Zero External Services
&lt;/h2&gt;

&lt;p&gt;Every fix, every feature, every release — the smoke test suite runs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@pytest.mark.asyncio&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_scan_button_does_not_block_event_loop&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Verify scan handler yields control back to event loop.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;patch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;subprocess.run&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;mock_run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;mock_run&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;return_value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;CompletedProcess&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
        &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;monotonic&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;cmd_scan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;monotonic&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_race_condition_scan_limit&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Double-tap must not exceed daily limit.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;UPDATE users SET scans_today = 9 WHERE id = 1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;cmd_scan&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;   &lt;span class="c1"&gt;# 10th — pass
&lt;/span&gt;    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;pytest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raises&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RateLimitExceeded&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;cmd_scan&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;  &lt;span class="c1"&gt;# 11th — blocked
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tools: &lt;code&gt;pytest&lt;/code&gt;, &lt;code&gt;pytest-asyncio&lt;/code&gt;, &lt;code&gt;unittest.mock&lt;/code&gt;, SQLite &lt;code&gt;:memory:&lt;/code&gt; databases. No external services — pure deterministic tests in under 2 seconds.&lt;/p&gt;

&lt;p&gt;45 tests. 0 failures. CI green.&lt;/p&gt;




&lt;h2&gt;
  
  
  Numbers That Matter
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;v1 (Jan 2026)&lt;/th&gt;
&lt;th&gt;v2 (Jun 2026)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Strategies&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Codebase&lt;/td&gt;
&lt;td&gt;2,100 lines&lt;/td&gt;
&lt;td&gt;4,600+ lines&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Test coverage&lt;/td&gt;
&lt;td&gt;0 tests&lt;/td&gt;
&lt;td&gt;45 smoke tests&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ML layer&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;RandomForest F1=0.69&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Backtesting&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;Walk-forward on REST klines&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Risk management&lt;/td&gt;
&lt;td&gt;Basic SL&lt;/td&gt;
&lt;td&gt;Partial TP + trailing x10 + funding rotation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Telegram bot&lt;/td&gt;
&lt;td&gt;Working, untested&lt;/td&gt;
&lt;td&gt;45/45 tests, 14 bugs fixed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deployment&lt;/td&gt;
&lt;td&gt;Manual&lt;/td&gt;
&lt;td&gt;systemd + white-label script&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Monitoring&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;Prometheus /metrics + daily health alerts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Memory&lt;/td&gt;
&lt;td&gt;~200 MB&lt;/td&gt;
&lt;td&gt;~23.5 MB (SQLite beats in-memory dicts)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The memory drop isn't a typo. Moving from Python dicts to SQLite cut RAM by 88%.&lt;/p&gt;




&lt;h2&gt;
  
  
  Roadmap: Phase 4
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;🔜 &lt;strong&gt;ATR-based risk sizing&lt;/strong&gt; — position size from volatility, not fixed&lt;/li&gt;
&lt;li&gt;🔜 &lt;strong&gt;Multi-timeframe confluence&lt;/strong&gt; — D/W/M agreement required for entries&lt;/li&gt;
&lt;li&gt;🔜 &lt;strong&gt;Grafana dashboard&lt;/strong&gt; — real-time PnL, drawdown, position heatmap&lt;/li&gt;
&lt;li&gt;🔜 &lt;strong&gt;Telegram Mini App&lt;/strong&gt; — dashboard right in Telegram&lt;/li&gt;
&lt;li&gt;🔮 &lt;strong&gt;OKX/Binance support&lt;/strong&gt; — same strategies, more liquidity&lt;/li&gt;
&lt;/ul&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;git clone https://github.com/poliakarmai/bybit-ws
&lt;span class="nb"&gt;cd &lt;/span&gt;bybit-ws
&lt;span class="nb"&gt;cp &lt;/span&gt;config.example.yaml config.yaml
&lt;span class="c"&gt;# Insert your Bybit API keys&lt;/span&gt;
pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
python &lt;span class="nt"&gt;-m&lt;/span&gt; bybit-ws
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or deploy as a systemd service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo cp &lt;/span&gt;bybit-ws.service /etc/systemd/system/
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="nt"&gt;--now&lt;/span&gt; bybit-ws
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/poliakarmai/bybit-ws" rel="noopener noreferrer"&gt;poliakarmai/bybit-ws&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Bot:&lt;/strong&gt; &lt;a href="https://t.me/Gridbolbot" rel="noopener noreferrer"&gt;@Gridbolbot&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;License:&lt;/strong&gt; MIT&lt;/p&gt;




&lt;p&gt;&lt;em&gt;The author is a trader and AI engineer. Writes about trading infrastructure, multi-agent systems, and the boring risk management that actually saves accounts.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>cryptocurrency</category>
      <category>trading</category>
      <category>ai</category>
    </item>
    <item>
      <title>Bybit-WS: AI-Native Trading Engine — как я построил автономного трейдингового агента за 8 месяцев</title>
      <dc:creator>Alexey Polyakov</dc:creator>
      <pubDate>Thu, 11 Jun 2026 14:18:29 +0000</pubDate>
      <link>https://dev.to/alexey_polyakov_cfe2095e3/bybit-ws-ai-native-trading-engine-kak-ia-postroil-avtonomnogho-trieidinghovogho-aghienta-za-8-miesiatsiev-47kg</link>
      <guid>https://dev.to/alexey_polyakov_cfe2095e3/bybit-ws-ai-native-trading-engine-kak-ia-postroil-avtonomnogho-trieidinghovogho-aghienta-za-8-miesiatsiev-47kg</guid>
      <description>&lt;h1&gt;
  
  
  Bybit-WS: AI-Native Trading Engine — как я построил автономного трейдингового агента за 8 месяцев
&lt;/h1&gt;

&lt;h2&gt;
  
  
  О чём речь
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;bybit-ws&lt;/strong&gt; — это не очередной торговый бот. Это движок, спроектированный для AI-агентов. Он даёт твоему Claude Code, Codex или Cursor REST API, MCP-сервер и Python SDK — и агент сам сканирует рынок, входит в позиции, управляет рисками. Без человека в цикле.&lt;/p&gt;

&lt;p&gt;Код: &lt;a href="https://github.com/poliakarmai/bybit-ws" rel="noopener noreferrer"&gt;github.com/poliakarmai/bybit-ws&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Торгует на Bybit с марта 2025. С июня 2026 — в Open Source. MIT.&lt;/p&gt;

&lt;h2&gt;
  
  
  Почему не freqtrade / hummingbot?
&lt;/h2&gt;

&lt;p&gt;Есть два класса трейдинговых инструментов:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Фреймворки&lt;/th&gt;
&lt;th&gt;bybit-ws&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Пишешь стратегию сам&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8 готовых стратегий из коробки&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MCP-сервер для AI-агентов&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Один YAML-конфиг&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Нет базы данных&lt;/td&gt;
&lt;td&gt;❌ (SQLite)&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;freqtrade и hummingbot — фреймворки. Ты &lt;strong&gt;кодишь&lt;/strong&gt; стратегию. bybit-ws — готовый движок. Ты настраиваешь YAML, запускаешь, подключаешь AI-агента.&lt;/p&gt;

&lt;p&gt;Фреймворки хороши для исследователей. bybit-ws — для тех, кому нужно работать здесь и сейчас.&lt;/p&gt;

&lt;h2&gt;
  
  
  Архитектура
&lt;/h2&gt;

&lt;p&gt;Два слоя:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Интерфейсы (как AI-агенты общаются):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AI Agent (Claude Code / Codex / Cursor / Hermes)
    ├── REST API (port 8766) — scan, enter, close, positions
    ├── MCP Server — scan_market, get_positions, get_metrics
    └── Python SDK — from bybit_ws_sdk import Monitor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Движок (30-секундный цикл, systemd или Docker):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;7 стратегий + DCA overlay&lt;/li&gt;
&lt;li&gt;Динамический сайзинг позиций (% от депозита × conviction score)&lt;/li&gt;
&lt;li&gt;Автоматический SL/TP через trading-stop&lt;/li&gt;
&lt;li&gt;X10 safety pack: ATR-валидация, дневные лимиты убытков, кулдауны&lt;/li&gt;
&lt;li&gt;Корреляционная матрица, фандинг-трекер, классификатор режима рынка&lt;/li&gt;
&lt;li&gt;SVG-дашборд и Telegram-бот @Gridbolbot&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  7 стратегий
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Стратегия&lt;/th&gt;
&lt;th&gt;Плечо&lt;/th&gt;
&lt;th&gt;TF&lt;/th&gt;
&lt;th&gt;Триггер входа&lt;/th&gt;
&lt;th&gt;SL&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;BB Grid LONG&lt;/td&gt;
&lt;td&gt;3x&lt;/td&gt;
&lt;td&gt;D&lt;/td&gt;
&lt;td&gt;BB &amp;lt; 25%, score ≥ 5.5&lt;/td&gt;
&lt;td&gt;−7%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;BB Grid SHORT&lt;/td&gt;
&lt;td&gt;3x&lt;/td&gt;
&lt;td&gt;D&lt;/td&gt;
&lt;td&gt;BB &amp;gt; 85%, Tier A/B&lt;/td&gt;
&lt;td&gt;+5-7%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Junk SHORT&lt;/td&gt;
&lt;td&gt;3x&lt;/td&gt;
&lt;td&gt;D&lt;/td&gt;
&lt;td&gt;Pump ≥ 80%&lt;/td&gt;
&lt;td&gt;−15%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;SL Re-entry&lt;/td&gt;
&lt;td&gt;3x&lt;/td&gt;
&lt;td&gt;D&lt;/td&gt;
&lt;td&gt;Лесенка после SL&lt;/td&gt;
&lt;td&gt;−7%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;BB Scalp M5 ⚡&lt;/td&gt;
&lt;td&gt;10x&lt;/td&gt;
&lt;td&gt;M5&lt;/td&gt;
&lt;td&gt;Касание полосы + RSI&lt;/td&gt;
&lt;td&gt;3%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Mean Revert ⚡&lt;/td&gt;
&lt;td&gt;10x&lt;/td&gt;
&lt;td&gt;D&lt;/td&gt;
&lt;td&gt;BB% &amp;lt; 5% или &amp;gt; 95%&lt;/td&gt;
&lt;td&gt;5%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;Funding Momentum ⚡&lt;/td&gt;
&lt;td&gt;10x&lt;/td&gt;
&lt;td&gt;D&lt;/td&gt;
&lt;td&gt;Funding ±0.1% + тренд&lt;/td&gt;
&lt;td&gt;4%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;⚡ — x10 плечо, ликвидация при ~10% движении. High risk.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;DCA overlay&lt;/strong&gt; — работает поверх всех стратегий: докупает на −5/−10/−15% от входа (до 2 добавок).&lt;/p&gt;

&lt;h2&gt;
  
  
  Как AI-агент торгует через bybit-ws
&lt;/h2&gt;

&lt;p&gt;Никакого ручного кликанья. Агент сам принимает решение:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;bybit_ws_sdk&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Monitor&lt;/span&gt;

&lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Monitor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://localhost:8766&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your-rpc-token&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Шаг 1: сканируем рынок
&lt;/span&gt;&lt;span class="n"&gt;signals&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;long&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;signals&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;signals&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;score&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mf"&gt;7.0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Шаг 2: превью без исполнения (dry-run)
&lt;/span&gt;        &lt;span class="n"&gt;preview&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;symbol&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Buy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;qty&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;confirm&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Маржа: $&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;preview&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;margin&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;, Ликв: $&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;preview&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;liq_price&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Шаг 3: вход с SL и TP
&lt;/span&gt;        &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;symbol&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Buy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;qty&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="n"&gt;sl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;preview&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sl_suggested&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="n"&gt;tp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;preview&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tp_suggested&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="n"&gt;confirm&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;И всё. Дальше bybit-ws сам следит за позицией: SL, TP, авто-закрытие при деградации BB, DCA на откатах.&lt;/p&gt;

&lt;h2&gt;
  
  
  Risk Management
&lt;/h2&gt;

&lt;p&gt;Потому что без защиты на фьючерсах делать нечего:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Динамический сайзинг&lt;/strong&gt;: депозит × risk% / max_positions × conviction score. Больше уверенность → больше позиция&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Drawdown guard&lt;/strong&gt;: алерт + опциональный emergency close при −15% от пика депозита&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Дневной лимит убытков&lt;/strong&gt;: стоп торговли при −$N за день&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Корреляционный блок&lt;/strong&gt;: запрет входа если ≥2 позиции коррелируют &amp;gt;0.8&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;X10 limits&lt;/strong&gt;: макс 3 убыточных x10 сделки → кулдаун 24 часа&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Каскадная защита&lt;/strong&gt;: market-close если цена в 2× ближе к ликвидации чем SL&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Безопасность
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;API-ключи через переменные окружения (&lt;code&gt;${BYBIT_API_KEY}&lt;/code&gt;), никогда в коде&lt;/li&gt;
&lt;li&gt;RPC auth: Bearer token на все write-эндпоинты (&lt;code&gt;/enter&lt;/code&gt;, &lt;code&gt;/close&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Bind 127.0.0.1 по умолчанию — не светим наружу&lt;/li&gt;
&lt;li&gt;chmod 600 на конфиг&lt;/li&gt;
&lt;li&gt;Рекомендация: IP whitelist на стороне Bybit&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Запуск за 5 минут
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/poliakarmai/bybit-ws.git
&lt;span class="nb"&gt;cd &lt;/span&gt;bybit-ws
pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;                    &lt;span class="c"&gt;# ставит bybit-ws + зависимости&lt;/span&gt;
&lt;span class="nb"&gt;cp &lt;/span&gt;config.example.yaml ~/.config/bybit-ws/config.yaml
&lt;span class="c"&gt;# Вставь API-ключи и НАЧНИ С ТЕСТНЕТА:&lt;/span&gt;
&lt;span class="c"&gt;#   api.base_url: "https://api-testnet.bybit.com"&lt;/span&gt;
bybit-ws daemon
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Всё. Демон запущен, REST API на порту 8766. Здоровье: &lt;code&gt;curl localhost:8766/health&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Что под капотом
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Python 3.11+&lt;/strong&gt;, 8500+ строк, 37 модулей&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Скоринг&lt;/strong&gt;: 9 метрик (Tier, BB Daily, Volume, Days Falling, Weekly/Monthly BB, Funding, Volatility, Bounce Quality, RSI)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Режимы рынка&lt;/strong&gt;: классификатор определяет тренд/рэндж/волатильность и адаптирует параметры&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Фандинг-трекер&lt;/strong&gt;: исторические ставки, прогноз, фильтр входа при минусовом фандинге&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SVG-дашборд&lt;/strong&gt;: винрейт, маржа, фандинг, корреляции — обновляется раз в 5 минут&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Telegram-бот @Gridbolbot&lt;/strong&gt;: живые сигналы, алерты, утренняя сводка&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Production Track Record
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Самоотчёт с продакшена. Не аудировано. Прошлые результаты ≠ будущие.&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Аптайм&lt;/strong&gt;: 99.7% (30 дней)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Сделок&lt;/strong&gt;: 847 за 30 дней&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Винрейт (LONG)&lt;/strong&gt;: ~68%&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Среднее удержание&lt;/strong&gt;: 14 часов&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Roadmap
&lt;/h2&gt;

&lt;p&gt;Следующие шаги:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;WebSocket миграция&lt;/strong&gt; — замена REST-поллинга на реальный поток&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Бэктестинг&lt;/strong&gt; — прогон стратегий на исторических данных&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-exchange&lt;/strong&gt; — Binance, OKX&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Веб-дашборд&lt;/strong&gt; — реальный UI вместо SVG&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;PR'ы приветствуются. См. &lt;a href="https://github.com/poliakarmai/bybit-ws#-contributing" rel="noopener noreferrer"&gt;CONTRIBUTING&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Итог
&lt;/h2&gt;

&lt;p&gt;Я построил этот движок чтобы мой AI-агент (Hermes) мог торговать автономно. Получилось так, что он подходит любому агенту — Claude Code, Codex, Cursor. 8 месяцев разработки, из них 6 в проде на реальных деньгах.&lt;/p&gt;

&lt;p&gt;Если ты:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AI-разработчик&lt;/strong&gt; и хочешь чтобы твой агент торговал&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Квант-трейдер&lt;/strong&gt; которому нужен API для Bollinger Grid&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Крипто-энтузиаст&lt;/strong&gt; который хочет 24/7 мониторинг без кодинга&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;— клонируй, ставь тестнет, пробуй. Бесплатно. MIT.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub&lt;/strong&gt;: &lt;a href="https://github.com/poliakarmai/bybit-ws" rel="noopener noreferrer"&gt;github.com/poliakarmai/bybit-ws&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Telegram-бот&lt;/strong&gt;: &lt;a href="https://t.me/Gridbolbot" rel="noopener noreferrer"&gt;@Gridbolbot&lt;/a&gt; — демо, 10 сканов/день бесплатно&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Канал&lt;/strong&gt;: &lt;a href="https://t.me/criptapolyaka" rel="noopener noreferrer"&gt;@criptapolyaka&lt;/a&gt; — сигналы и разбор рынка&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bybit&lt;/strong&gt;: &lt;a href="https://www.bybit.com/invite?ref=DQ0EAQ&amp;amp;medium=referral&amp;amp;utm_campaign=evergreen" rel="noopener noreferrer"&gt;Регистрация с $30 бонусом&lt;/a&gt; — поддерживает проект&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;⚠️ &lt;strong&gt;Дисклеймер&lt;/strong&gt;: Торговля фьючерсами с плечом — высокий риск. Можно потерять весь депозит. Это не финансовая рекомендация. Начинайте с тестнета.&lt;/p&gt;

</description>
      <category>python</category>
      <category>crypto</category>
      <category>trading</category>
      <category>ai</category>
    </item>
    <item>
      <title>How I Fixed a Silent Telegram Bot Using a 3-Echelon AI Audit (14 Bugs Found)</title>
      <dc:creator>Alexey Polyakov</dc:creator>
      <pubDate>Wed, 10 Jun 2026 10:04:06 +0000</pubDate>
      <link>https://dev.to/alexey_polyakov_cfe2095e3/i-built-an-ai-native-trading-engine-in-python-heres-how-it-works-54p0</link>
      <guid>https://dev.to/alexey_polyakov_cfe2095e3/i-built-an-ai-native-trading-engine-in-python-heres-how-it-works-54p0</guid>
      <description>&lt;h1&gt;
  
  
  How I Fixed a Silent Telegram Bot Using a 3-Echelon AI Audit (14 Bugs Found)
&lt;/h1&gt;

&lt;p&gt;My Telegram bot stopped responding.&lt;/p&gt;

&lt;p&gt;Not "responding slowly" — just dead silent. You tap the "Scan" button and nothing comes back. The process is running, memory's fine, logs are clean. Classic Heisenbug: it breaks when you're not watching, works when you are.&lt;/p&gt;

&lt;p&gt;I'm an engineer with a background in industrial safety (pipeline diagnostics, corrosion monitoring), but for the past six months I've been deep in AI agents and trading infrastructure. Here's what I learned: debugging with AI isn't just "ask ChatGPT to fix the error." It's a systematic approach.&lt;/p&gt;

&lt;p&gt;Let me show you how three AI agents scanned 2,153 lines of code in parallel, found 14 bugs of varying nastiness, and brought the bot back to life.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bot That Went Silent
&lt;/h2&gt;

&lt;p&gt;Context first. The bot is called GridSignal — a trading tool for Bybit futures. It scans the market using Bollinger Bands, generates entry signals, sends alerts. 2,153 lines of Python, a bunch of dependencies, its own SQLite database, subprocess calls to the Bybit CLI. Your typical "grown-up" Telegram bot.&lt;/p&gt;

&lt;p&gt;The problem surfaced after adding a new feature: funding rate rotation. I plugged a call to &lt;code&gt;funding_rotation.py&lt;/code&gt; into the "Rotation" button handler, and the bot went down. Not immediately — first it just got sluggish, then stopped responding entirely.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Silent Killer: Async Exceptions Without Logging
&lt;/h3&gt;

&lt;p&gt;The real kicker: logs were spotless. The process showed &lt;code&gt;active (running)&lt;/code&gt; in systemd. I spent an hour guessing — "maybe Telegram API is having issues? network? self-healing?" — until I ran the audit.&lt;/p&gt;

&lt;p&gt;Here's why the logs were clean: asyncio has a nasty habit of &lt;strong&gt;swallowing exceptions silently&lt;/strong&gt;. If a coroutine raises inside a handler that doesn't have its own try/except, and nobody's awaiting it, the exception gets logged to the event loop's default handler — which, by default, prints to stderr and nothing else. Your logging framework never sees it.&lt;/p&gt;

&lt;p&gt;The fix is a global exception handler that plugs into your logging pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;loop&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_event_loop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;loop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_exception_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;loop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Global asyncio error: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;message&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;exc_info&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;exception&lt;/span&gt;&lt;span class="sh"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this, every swallowed exception shows up in your logs — no more Heisenbugs hiding in the shadows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three Echelons
&lt;/h2&gt;

&lt;p&gt;I run my own AI agent platform called Hermes. It can spawn multiple auditors in parallel, each with a different focus. The architecture looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                 ┌─────────────┐
                 │  Your Code  │
                 └──────┬──────┘
          ┌─────────────┼─────────────┐
          ▼             ▼             ▼
  ┌──────────────┐ ┌──────────┐ ┌──────────────┐
  │Source-Driven │ │ Security  │ │ Adversarial  │
  │              │ │           │ │              │
  │ API docs     │ │ Secrets &amp;amp; │ │ Race cond.   │
  │ vs code      │ │ CVEs      │ │ Logic bugs   │
  └──────┬───────┘ └─────┬─────┘ └──────┬───────┘
         └───────────────┼──────────────┘
                         ▼
              ┌──────────────────┐
              │  Consolidated    │
              │  14 Findings     │
              └──────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Source-Driven:&lt;/strong&gt; cross-references code against official documentation. Finds API calls that don't exist, parameters that aren't supported, hallucinations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security:&lt;/strong&gt; hunts for secrets in code, holes in &lt;code&gt;.gitignore&lt;/code&gt;, command injection, CVEs in dependencies.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Adversarial:&lt;/strong&gt; finds fatal bugs in business logic. Race conditions, blocking calls, resource leaks, edge cases.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Why three instead of one? Because they have different blind spots. A security auditor will catch &lt;code&gt;subprocess.run&lt;/code&gt; with f-strings beautifully, but won't notice that &lt;code&gt;_valid_symbol()&lt;/code&gt; is never defined anywhere. Source-driven will find doc mismatches, but will miss a race condition in scan limits. Adversarial will spot 10 sequential subprocess calls hanging the event loop for 100 seconds — but it doesn't care about CVEs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prompts That Drove the Audit
&lt;/h3&gt;

&lt;p&gt;What exactly did I ask the agents? Here are the actual prompts that anyone can use with ChatGPT, Claude, or Cursor:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Adversarial echelon prompt:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Analyze this Telegram bot handler code. Find every place where a race condition is possible — if the user taps the button twice within 100ms, or two users call this function simultaneously. Also flag all blocking calls (subprocess.run, time.sleep, file I/O) inside async handlers. For each finding, suggest an atomic fix. Prioritize by severity."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Source-Driven echelon prompt:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Cross-reference every API call in this code against official python-telegram-bot documentation and Bybit API v5 docs. Flag: (1) parameters not documented, (2) deprecated methods, (3) return values used incorrectly. Include links to the relevant docs for each finding."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Three agents, parallel execution. Four minutes later, I had the consolidated report: 14 findings. Five CRITICAL, four HIGH.&lt;/p&gt;

&lt;h2&gt;
  
  
  What We Found
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. The Function That Doesn't Exist
&lt;/h3&gt;

&lt;p&gt;The bot calls &lt;code&gt;_valid_symbol(symbol)&lt;/code&gt; in three places: during &lt;code&gt;/alert&lt;/code&gt;, during background alert checking, and during inline queries (&lt;code&gt;@Gridbolbot BTCUSDT&lt;/code&gt;). But the function is never defined. Not in the main file, not in any import, not in any adjacent module.&lt;/p&gt;

&lt;p&gt;When a user tapped &lt;code&gt;/alert BTCUSDT&lt;/code&gt;, the bot crashed with &lt;code&gt;NameError: name '_valid_symbol' is not defined&lt;/code&gt;. The handler died, no alert was set, and I sat there scratching my head at the clean logs.&lt;/p&gt;

&lt;p&gt;The Source-Driven echelon found this. Interestingly, the Security echelon also found &lt;code&gt;_valid_symbol&lt;/code&gt; — but from a different angle: "symbol validation function undefined, potential command injection via subprocess if it existed."&lt;/p&gt;

&lt;p&gt;Two echelons, two different reasons to worry about the same non-existent function.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Nine Blocking Calls in Async
&lt;/h3&gt;

&lt;p&gt;The Adversarial echelon walked through every handler and built a table. The worst offender: &lt;code&gt;cmd_fear&lt;/code&gt;. The "Fear" button made 10 sequential &lt;code&gt;subprocess.run(['bybit', 'bb', ...])&lt;/code&gt; calls. Each with a 10-second timeout. Ten coins, ten calls, 100 seconds of event-loop blockage.&lt;/p&gt;

&lt;p&gt;Async Python doesn't forgive this. While &lt;code&gt;cmd_fear&lt;/code&gt; waits for Bybit's response on the tenth coin, every other user sees "typing..." and gets nothing. If two people hit buttons at the same time — the bot just freezes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# BLOCKS the entire event loop for up to 100 seconds
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;symbol&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;symbols&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;bybit&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;bb&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;capture_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;parse_output&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;After:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Event loop stays free — other users can still interact
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;symbol&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;symbols&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;result&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;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_thread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;bybit&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;bb&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;capture_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;parse_output&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three lines changed, event loop freed.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Race Condition in Scan Limits
&lt;/h3&gt;

&lt;p&gt;The bot has a limit: 10 scans per user per day. Abuse protection.&lt;/p&gt;

&lt;p&gt;But &lt;code&gt;update_scan_count()&lt;/code&gt; blindly did &lt;code&gt;UPDATE users SET scans_today=scans_today+1&lt;/code&gt; without checking the current value. If a user managed to fire &lt;code&gt;/scan&lt;/code&gt; twice in one event-loop tick (fast double tap), both requests saw &lt;code&gt;scans_today=0&lt;/code&gt; and both passed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Two concurrent taps → both see scans_today=0 → both pass
&lt;/span&gt;&lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;UPDATE users SET scans_today = scans_today + 1 WHERE user_id = ?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&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;&lt;strong&gt;After — atomic check-and-increment:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;UPDATE users SET scans_today = scans_today + 1
       WHERE user_id = ? AND scans_today &amp;lt; 10&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&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="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rowcount&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;RateLimitExceeded&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Daily scan limit reached&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No more race. The database itself enforces the constraint.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Ghost Buttons and Duplicates
&lt;/h3&gt;

&lt;p&gt;The "📊 LONG" and "📉 SHORT" buttons were in the keyboard, but nobody handled the press. Users tapped them — zero response. The Adversarial echelon flagged it: "buttons exist in &lt;code&gt;MAIN_KEYBOARD&lt;/code&gt;, no handler implemented."&lt;/p&gt;

&lt;p&gt;We also found a &lt;code&gt;cmd_top&lt;/code&gt; duplicate: the command was registered as a handler twice, and inside &lt;code&gt;cmd_stats&lt;/code&gt; there was another &lt;code&gt;cmd_top&lt;/code&gt; (leaderboard) silently overwriting the original (top gainers/losers). Split into separate functions, cleaned up.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. SQLite Without WAL, and the Database Sitting in Git
&lt;/h3&gt;

&lt;p&gt;The Security echelon found that &lt;code&gt;.gitignore&lt;/code&gt; didn't exclude &lt;code&gt;data/*.db&lt;/code&gt; — SQLite files with user data could accidentally fly into the repository. They hadn't, but they could have.&lt;/p&gt;

&lt;p&gt;Another echelon noticed: &lt;code&gt;sqlite3.connect()&lt;/code&gt; without &lt;code&gt;check_same_thread=False&lt;/code&gt; and without &lt;code&gt;PRAGMA journal_mode=WAL&lt;/code&gt;. Two concurrent users and you get &lt;code&gt;SQLITE_BUSY: database is locked&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;conn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sqlite3&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;data/bot.db&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;After:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;conn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sqlite3&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;data/bot.db&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;check_same_thread&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;PRAGMA journal_mode=WAL;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two extra parameters, zero more locking errors.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Temp File Leak
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;/chart&lt;/code&gt; command generates a chart image, saves it temporarily, sends it to the user, and deletes it. Simple — unless the chart generation raises an exception.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;mpf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;plot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;savefig&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;chart.png&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;chart.png&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;rb&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reply_photo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unlink&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;chart.png&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Never runs if mpf.plot() raises!
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;After:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;tempfile&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;NamedTemporaryFile&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;NamedTemporaryFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;suffix&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.png&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;delete&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;mpf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;plot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;savefig&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reply_photo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;rb&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="c1"&gt;# File is auto-deleted by the context manager — even on exceptions
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;tempfile.NamedTemporaryFile&lt;/code&gt; guarantees cleanup. The OS handles it, not your error-prone manual &lt;code&gt;os.unlink()&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Else We Found (Less Dramatic)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;parse_mode='Markdown'&lt;/code&gt; instead of &lt;code&gt;MarkdownV2&lt;/code&gt; in three places. Old mode, deprecated. Doesn't break things, but it's an eyesore.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;requirements.txt&lt;/code&gt; incomplete — three libraries missing. Deploy on a clean machine and you won't know what's missing.&lt;/li&gt;
&lt;li&gt;Bare &lt;code&gt;import re&lt;/code&gt; missing, even though &lt;code&gt;_valid_symbol()&lt;/code&gt; (once we actually wrote it) used &lt;code&gt;re.fullmatch()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Twenty-one &lt;code&gt;except:&lt;/code&gt; clauses without exception type — swallowing KeyboardInterrupt and SystemExit along with real errors.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Testing: How 45/45 Smoke Tests Passed Green
&lt;/h2&gt;

&lt;p&gt;After every fix, I ran the smoke test suite. Here's the setup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# pytest with asyncio support, async fixtures, mocks
# test_smoke.py
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pytest&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;unittest.mock&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AsyncMock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;patch&lt;/span&gt;

&lt;span class="nd"&gt;@pytest.mark.asyncio&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_scan_button_does_not_block_event_loop&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Verify scan handler yields control back to event loop.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;patch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;subprocess.run&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;mock_run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;mock_run&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;return_value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;CompletedProcess&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="n"&gt;returncode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;{}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;monotonic&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;cmd_scan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;elapsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;monotonic&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;
        &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;elapsed&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;  &lt;span class="c1"&gt;# Should return fast — actual work is in to_thread()
&lt;/span&gt;
&lt;span class="nd"&gt;@pytest.mark.asyncio&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_race_condition_scan_limit&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Double-tap scan must not exceed daily limit.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;UPDATE users SET scans_today = 9 WHERE user_id = 1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;cmd_scan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# 10th scan — should pass
&lt;/span&gt;    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;pytest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raises&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RateLimitExceeded&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;cmd_scan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# 11th — blocked
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tools used: &lt;code&gt;pytest&lt;/code&gt;, &lt;code&gt;pytest-asyncio&lt;/code&gt;, &lt;code&gt;unittest.mock&lt;/code&gt; for Telegram API calls, &lt;code&gt;sqlite3&lt;/code&gt; with &lt;code&gt;:memory:&lt;/code&gt; databases for isolation. No external services touched — pure deterministic tests that run in under 2 seconds.&lt;/p&gt;

&lt;p&gt;45 tests, 0 failures. CI green.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;One.&lt;/strong&gt; A single auditor is dangerous. It will confidently say "all clear" because it's looking from one angle. Three agents with different focuses cover each other's blind spots.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Two.&lt;/strong&gt; Blocking calls in async handlers aren't a "fix later" thing. They make your bot look dead in production. &lt;code&gt;subprocess.run()&lt;/code&gt; inside async is a red flag. Always.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Three.&lt;/strong&gt; You can't spot a race condition by eyeballing it — especially in code you wrote yourself. You need an adversarial echelon that actively asks "what if they tap twice, fast?"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Four.&lt;/strong&gt; Buttons in a keyboard without handlers are negligence you never notice because you never use those buttons. Users do.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Five.&lt;/strong&gt; Type annotations double your auditor's accuracy. An AI agent that sees &lt;code&gt;def handler(update: Update, context: ContextTypes.DEFAULT_TYPE)&lt;/code&gt; makes 30–40% fewer false positives than one seeing &lt;code&gt;def handler(update, context)&lt;/code&gt;. Add type hints — your tools get smarter.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bonus: What Else to Check in Your Telegram Bot
&lt;/h2&gt;

&lt;p&gt;Beyond the five bugs above, here's a checklist of things that bite bots in production. Run through these — each one takes 2 minutes to verify:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ &lt;strong&gt;Rate Limiting (429 Too Many Requests).&lt;/strong&gt; Does your bot handle Telegram API backpressure? Use aiogram's built-in retry middleware or add exponential backoff: &lt;code&gt;await asyncio.sleep(min(2 ** attempt, 60))&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Type Hints.&lt;/strong&gt; Run &lt;code&gt;mypy --strict&lt;/code&gt; on your handlers. AI auditors get 30–40% more accurate with typed code — the model understands variable context better.&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Global asyncio exception handler.&lt;/strong&gt; Add the &lt;code&gt;loop.set_exception_handler()&lt;/code&gt; snippet from above. Silent exceptions are the #1 cause of "it works on my machine" Heisenbugs.&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Atomic database operations.&lt;/strong&gt; Any counter, limit, or state transition that can be triggered by concurrent users needs &lt;code&gt;WHERE ... AND current_state &amp;lt; limit&lt;/code&gt; — never two-step read-then-write.&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Temporary file cleanup.&lt;/strong&gt; Replace all manual &lt;code&gt;os.unlink()&lt;/code&gt; with &lt;code&gt;tempfile.NamedTemporaryFile&lt;/code&gt; or &lt;code&gt;try/finally&lt;/code&gt;. One exception and your disk fills up.&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Dependency completeness.&lt;/strong&gt; Run &lt;code&gt;pip freeze &amp;gt; requirements.txt&lt;/code&gt; from a clean venv, not your dev environment. Three missing libraries is typical.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;p&gt;After all fixes: 0 CRITICAL, 45/45 smoke tests green, bot responds instantly. Audit time: 4 minutes. Manual debugging time: 1 hour.&lt;/p&gt;

&lt;p&gt;I'm not saying AI audits replace code review. But as a first line of defense, they're terrifyingly effective — especially when your project crosses 2,000 lines and keeping it all in your head is simply not realistic.&lt;/p&gt;

&lt;p&gt;Got a Telegram bot that "sometimes lags"? Try running the three-echelon approach with the prompts above. Chances are, you'll find a couple of your own &lt;code&gt;_valid_symbol&lt;/code&gt;s.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;The author is a trader and AI engineer. Writes about trading bot infrastructure, multi-agent systems, and practical production debugging.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>telegram</category>
      <category>ai</category>
      <category>debugging</category>
    </item>
  </channel>
</rss>
