<?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: Jahnavi</title>
    <description>The latest articles on DEV Community by Jahnavi (@jahnavichalla19jaanu).</description>
    <link>https://dev.to/jahnavichalla19jaanu</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%2F4192237%2F79f49641-cf1c-4599-848d-89b42649048d.jpg</url>
      <title>DEV Community: Jahnavi</title>
      <link>https://dev.to/jahnavichalla19jaanu</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jahnavichalla19jaanu"/>
    <language>en</language>
    <item>
      <title>I Built a Map of the Doors I Forgot to Lock</title>
      <dc:creator>Jahnavi</dc:creator>
      <pubDate>Sun, 18 Oct 2026 14:13:35 +0000</pubDate>
      <link>https://dev.to/jahnavichalla19jaanu/i-built-a-map-of-the-doors-i-forgot-to-lock-4dhd</link>
      <guid>https://dev.to/jahnavichalla19jaanu/i-built-a-map-of-the-doors-i-forgot-to-lock-4dhd</guid>
      <description>&lt;p&gt;&lt;strong&gt;&lt;em&gt;A stranger, a pull request, and a bot that holds your keys&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Picture a normal night. You maintain a project on GitHub. Someone you have never met opens a pull request with a tiny change. You are asleep. But something else is awake: your GitHub Actions workflow, the little bot that builds and tests your code.&lt;br&gt;
That bot holds real keys. Tokens, cloud logins, sometimes a role that can deploy to production. Whether a stranger’s pull request can make it misuse those keys depends on a handful of settings that almost nobody double-checks.&lt;br&gt;
This is not a thought experiment. In December 2024, attackers compromised Ultralytics, a hugely popular Python package, and got a crypto miner into real releases. Security firms who investigated describe a pull request whose branch name carried a shell command, and a workflow that pasted that name straight into a command. GitHub’s own security team has a name for the wider pattern: a “pwn request”, a workflow that runs with trusted privileges on code from an untrusted pull request.&lt;/p&gt;

&lt;p&gt;What bothered me was not any single mistake. It was that every step looked reasonable on its own.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why the usual tools miss it
&lt;/h2&gt;

&lt;p&gt;Today’s tools look at one layer at a time:&lt;br&gt;
• Workflow linters read one YAML file and say “this line looks risky”. They can’t tell what the workflow’s cloud login can actually touch.&lt;br&gt;
• Cloud permission tools read IAM roles and say “this role is powerful”. They can’t tell whether a stranger on the internet can ever reach it.&lt;br&gt;
An attacker doesn’t think in layers. They think in routes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Stranger’s pull request
  → privileged trigger
  → workflow job
  → cloud role (through GitHub’s OIDC login)
  → a role that can pass an admin role
  → the customer database
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each hop might be rated “medium”. Together they are a breach. A building inspector who checks every lock can still miss the unlocked window that connects two corridors.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I built
&lt;/h2&gt;

&lt;p&gt;GraphGuard shows you your own repository the way an attacker would see it. It is a defensive tool for the people who own the repo.&lt;br&gt;
It reads your workflow files from GitHub and your AWS permissions from an exported IAM file, joins them into one graph, and answers four questions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Can a stranger reach anything valuable?&lt;/li&gt;
&lt;li&gt;Through which route?&lt;/li&gt;
&lt;li&gt;Which single fix closes the most routes?&lt;/li&gt;
&lt;li&gt;If I make that fix, does it really work?&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  Why a graph, and why FalkorDB
&lt;/h2&gt;

&lt;p&gt;Think of a pinboard with string between pins. “Starting from an untrusted trigger, follow the strings, however many hops it takes, and show me every route that ends at something sensitive” is natural there. In a table database, every hop needs another join, and you must guess the number of hops in advance.&lt;br&gt;
In FalkorDB the core question is one Cypher query (simplified here):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cypher"&gt;&lt;code&gt;&lt;span class="k"&gt;MATCH&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="py"&gt;t:&lt;/span&gt;&lt;span class="n"&gt;Trigger&lt;/span&gt; &lt;span class="ss"&gt;{&lt;/span&gt;&lt;span class="py"&gt;untrusted:&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="ss"&gt;})&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt;&lt;span class="ss"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;r.sensitive&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;
&lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;FalkorDB is a graph database that speaks the Redis protocol. It stores graphs as sparse matrices and follows connections with fast matrix math, and it ships graph algorithms such as PageRank and shortest path. It also comes with a browser on port 3000, so I can open the same graph my app is using and run queries by hand.&lt;br&gt;
My data model is small on purpose:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Trigger  -FIRES-&amp;gt;      Workflow -RUNS_JOB-&amp;gt; Job -CAN_READ-&amp;gt; Secret
Job      -ASSUMES-&amp;gt;    Role     -CAN_PASS_ROLE-&amp;gt; Role
Role     -CAN_ACCESS-&amp;gt; Resource (for example, customer-db)
Decision / Finding -ABOUT-&amp;gt; any node   (the agent’s memory)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you take FalkorDB out of GraphGuard, there is nothing left to run. The graph is not decoration, it is the engine. It does three jobs:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Path finding. Cypher walks from untrusted triggers to sensitive things.&lt;/li&gt;
&lt;li&gt;Choke points. It finds the single connection that sits on the most attack paths, and uses PageRank to show which nodes the graph leans on most.&lt;/li&gt;
&lt;li&gt;Throwaway graphs for “what if”. One FalkorDB instance can hold many separate graphs. To test a fix, GraphGuard copies the real graph into a temporary sim_ graph, removes one connection, re-runs the path search, compares before and after, then deletes the copy. Your real graph is never touched.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  How a scan works
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Read workflows + IAM export
  → build the graph in FalkorDB
  → run 12 detection rules
  → find attack paths
  → rank the fix-first choke point
  → simulate the fix on a throwaway graph
  → ask the agent questions
  → remember decisions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The 12 rules cover things like a pull_request_target workflow that checks out the pull request’s own code, user-controlled text pasted into a shell command, third-party actions that are not pinned to a fixed commit, write-all permissions, and cloud roles that can pass a more powerful role.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part I’m proudest of: it stays quiet when it should
&lt;/h2&gt;

&lt;p&gt;I expected the first real repository I scanned, pandas, to light up. It uses pull_request_target, a trigger people rightly worry about. GraphGuard found zero attack paths.&lt;br&gt;
My first thought was that I had a bug. Then I read their workflows. They pin the checkout to a fixed commit, give the workflow no permissions by default, and only fetch trusted scripts. They never run the pull request’s code. GraphGuard only connects an untrusted trigger to a workflow when that workflow actually runs untrusted code.&lt;br&gt;
A risky doorbell is not an open door. A security tool that cries wolf gets muted, so I would rather it said “clean” when a repo is clean.&lt;/p&gt;

&lt;h2&gt;
  
  
  An agent that is only allowed to quote the graph
&lt;/h2&gt;

&lt;p&gt;You can ask GraphGuard plain questions like “Can an untrusted PR reach the database?” The agent does not free-write an answer. It chooses a graph tool (find paths, blast radius, shortest path, most depended-on, simulate a fix, recall a memory), runs the query, and answers from the rows it got back.&lt;br&gt;
Every answer ends in one of four honest outcomes: answered with a path, from memory, not in graph, or unknown node. Ask about a service that does not exist, like ghost-service, and it says so instead of inventing one. Names in an answer are checked against what the tools actually returned, and an answer with a made-up name is thrown away and rebuilt from the real rows.&lt;br&gt;
To be upfront: by default the agent is a deterministic router that picks tools from your question, so GraphGuard works with no API key at all. If someone sets a key, an LLM picks the tools instead. Either way, the graph supplies the facts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Memory that lives in the graph
&lt;/h2&gt;

&lt;p&gt;Security work is full of decisions: “we accept this risk until the end of the month, owner Priya, because the deploy is being rebuilt.” GraphGuard stores those as Decision and Finding nodes linked to the node they are about. They are kept in the graph, not in a chat window, so they come back after a rescan, and an accepted risk that has expired shows up again.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three things that broke (and what they taught me)
&lt;/h2&gt;

&lt;p&gt;• The app would not start. Spring found two constructors in one class and didn’t know which to use. My tests passed because they built objects by hand. Lesson: add a test that boots the real wiring.&lt;br&gt;
• A scan froze forever. Downloading each workflow file from a second host stalled on my network. I moved to a single API host, added timeouts, and made the error say what actually went wrong.&lt;br&gt;
• A fake database showed up in a real scan. The demo’s customer-db appeared inside my scan of pandas. Sample data must never leak into real results, so a scan of a real repo now starts with no cloud data unless you upload an IAM file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it yourself
&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/jahnavichalla19-Jaanu/graphguard.git
&lt;span class="nb"&gt;cd &lt;/span&gt;graphguard
docker compose up &lt;span class="nt"&gt;--build&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open localhost:8080 for GraphGuard and localhost:3000 for the FalkorDB browser. No API key is needed. Click Load Demo Graph, then Find Attack Paths, then ask the agent about the database. After that, scan one of your own public repositories.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it can’t do (yet)
&lt;/h2&gt;

&lt;p&gt;• It shows where to look. A path means a risky route exists in the setup. It does not prove someone could exploit it today.&lt;br&gt;
• Cloud permissions come from an exported IAM file. Live AWS scanning is not implemented.&lt;br&gt;
• It has 12 rules, not every CI/CD weakness.&lt;br&gt;
• The API has no login, so run it on your own machine.&lt;br&gt;
• The graph drawing library loads from the internet, so the picture needs a connection.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I’d build next
&lt;/h2&gt;

&lt;p&gt;Tests against a real FalkorDB in CI, API authentication, SARIF export so findings appear in GitHub’s code scanning, a bigger evaluation set for the agent, and exposing the graph tools through MCP so other agents can use them.&lt;/p&gt;

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

&lt;p&gt;I built GraphGuard during the hackathon, using AI coding assistants for code, review and documentation [edit this line to describe exactly how you used them]. I directed the design, ran and debugged everything, and I can explain the graph model, the queries and the decisions behind them. The data is public workflow files and a made-up IAM sample, nothing private.&lt;br&gt;
If you take one thing from this post, let it be this: the most useful security answer is not a list of 400 warnings. It is “fix this one thing first”, and proof that it works.&lt;br&gt;
Code: &lt;a href="https://github.com/jahnavichalla19-Jaanu/graphguard" rel="noopener noreferrer"&gt;https://github.com/jahnavichalla19-Jaanu/graphguard&lt;/a&gt;· Demo: &lt;a href="https://youtu.be/-uLW4-E6fdc" rel="noopener noreferrer"&gt;https://youtu.be/-uLW4-E6fdc&lt;/a&gt;&lt;/p&gt;

</description>
      <category>programming</category>
      <category>docker</category>
      <category>agents</category>
    </item>
  </channel>
</rss>
