Protecting against Rogue Agents
We have been talking about why agentic workflows need branches. Just last Friday I published a blog where I talked about the demo app we built to show how you can use Dolt to build agentic workflows. Just days later, a story about an agent going rogue and deleting a production database came to my attention.
Nothing could show you why your agentic workflows need Dolt more clearly than a production database being deleted by a rogue agent. This occurred on Replit, and in this blog I'll show you how easy it is to use Dolt with Replit, however, this is not a Replit-specific problem. These types of issues can occur in any agentic workflow, and the solution is to use Dolt, a fully versioned database that allows you to branch, merge, and revert changes to your data.
Creating a Replit Project
The first thing I did was create a new Replit project with the prompt:
Create an app which is a database of cocktail recipes. Users should be able to filter the cocktail recipes by one or more
ingredients. For example, a user should be able to find all cocktails that include both bourbon and lemon juice.
After submitting the prompt, Replit will create a plan with some mockups, which you must approve before it starts building the app.
After approving the plan, Replit will create an MVP of the app storing the cocktail recipes in memory, and prompt for feedback on the initial design and functionality.
I've built a cocktail recipe database with multi-ingredient filtering using React and Express.js. The app includes:
✓ Complete schema with cocktail data model
✓ In-memory storage with sample cocktail recipes
✓ Backend API routes for filtering and searching
✓ Modern UI with recipe cards and detailed modal views
✓ Multi-ingredient and category filtering system
How does the application look and feel? Are you able to browse cocktails, use the filters on the left sidebar, search for
recipes, and view detailed recipe information by clicking on cards? Any feedback on the design or functionality?
I'm not a big fan of the initial look, and there are elements that just clutter the UI, so I asked for the following changes:
1. Remove the categories section
2. Give the page a dark theme
3. Put the search and filter by ingredients section into a left nav.
Replit then goes off and implements these changes, and the app looks much better:
I've completed all the requested changes to your cocktail recipe app:
✓ Removed categories section completely
✓ Applied dark theme as the default design
✓ Moved search and ingredient filtering to left navigation
✓ Updated all components for dark theme compatibility
✓ Cleaned up backend routes and storage interface
The app now has a sleek dark interface with a fixed left sidebar containing the search bar and ingredient filtering. The
main content area displays the cocktail cards in a grid layout. How does the new layout and dark theme look? Is the left
navigation working well for searching and filtering cocktails?
Creating a Hosted Dolt Instance
Now that we have a working app using in memory storage, we can migrate the data to Dolt. It is very easy to stand up a new Hosted Dolt Instance and create a new database. You'll need to setup a hosted Dolt account, and then you can create a new deployment. For this demo, I created a new trial instance.
Then we wait for our instance to be created, at which point we can connect to it using the provided host, port, username, and password. If you have the mysql cli installed, you can connect to the database by copying the command provided in the UI
Here I copy the "MySQL Client" command from the "Connectivity" section, and paste it into my terminal. Once connected, I can create a new user, database, and grant the user privileges on the new database:
~>mysql -h"dolthub-replit-demo.dbs.hosted.doltdb.com" -u"s8owhzgicgetfbjs" -p"f07WB55XrpW6nV0nh3rwj3lBFHPd2Z7G"
mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 40
Server version: 8.0.33 Dolt
Copyright (c) 2000, 2025, Oracle and/or its affiliates.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql> CREATE USER 'replit-agent'@'%' IDENTIFIED BY 'pass123WORD';
Query OK, 0 rows affected (0.033 sec)
mysql> CREATE DATABASE cocktail_craft;
Query OK, 1 row affected (0.114 sec)
mysql> GRANT ALL PRIVILEGES ON cocktail_craft.* TO 'replit-agent'@'%';
Query OK, 0 rows affected (0.031 sec)
Regardless of the database you are using, you should always create a user with limited privileges for your agents to use when interacting with your database, and not give it full root access. This is a good security practice to prevent rogue agents from interacting with databases outside their intended scope, and to prevent them from performing operations that you don't want them to perform.
Migrating the Data to Dolt
Now that we have a new database server, a user for the agent to use, and a database to store our data in, we can migrate the data from the in-memory storage to the new database. We just need to tell Replit about the new database and how to connect to it. I input the following prompt into the Replit chat:
There is a mysql compatible database server found at host: dolthub-replit-demo.dbs.hosted.doltdb.com port: 3306, and you
may use the username: replit-agent and password: pass123WORD to connect to it. An empty database with the name cocktail_craft
has already been created. Store all data in this database.
Replit then updates the app to use the new database connection, and migrates the data from in-memory storage to the new database:
Perfect! I've successfully migrated your cocktail database to the MySQL server you provided:
✓ Updated database connection to use MySQL with SSL/TLS
✓ Created cocktails table in the cocktail_craft database
✓ Migrated from in-memory storage to persistent MySQL database
✓ Seeded database with sample cocktail recipes
✓ All API endpoints working correctly with MySQL backend
The app now stores all cocktail data in the remote MySQL database at dolthub-replit-demo.dbs.hosted.doltdb.com. Your data will persist between sessions and the dark theme left navigation layout is working perfectly.
How does the app perform now with the persistent database? Are you able to search and filter cocktails as expected?
We can also verify that the data is in our database, and create our first commit to the database:
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| cocktail_craft |
| information_schema |
| mysql |
+--------------------+
3 rows in set (0.030 sec)
mysql> use cocktail_craft;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
Database changed
mysql> show tables;
+--------------------------+
| Tables_in_cocktail_craft |
+--------------------------+
| ingredients |
| recipe_ingredients |
| recipes |
+--------------------------+
3 rows in set (0.018 sec)
mysql> CALL dolt_commit('-Am', 'Initial app data');
+----------------------------------+
| hash |
+----------------------------------+
| 3tauovcm7r4qvb57f0e0h9l01t61vu27 |
+----------------------------------+
1 row in set (0.032 sec)
We've now verified that our app is working and that the data is stored in our Dolt database.
mysql> SELECT * FROM dolt_diff_summary('main...agent');
+--------------------+--------------------+-----------+-------------+---------------+
| from_table_name | to_table_name | diff_type | data_change | schema_change |
+--------------------+--------------------+-----------+-------------+---------------+
| ingredients | ingredients | modified | 1 | 0 |
| recipe_ingredients | recipe_ingredients | modified | 1 | 0 |
| recipes | recipes | modified | 1 | 0 |
+--------------------+--------------------+-----------+-------------+---------------+
3 rows in set (0.019 sec)
What Protections do we actually have?
Dolt is a fully versioned database. Every commit made to the database can be reverted to. This means that even if you give your agent the ability to drop tables, delete rows, or update values, you can always revert those changes. You can give your agent directives to perform operations on branches, and then merge those changes after verifying them. However, even if they ignore those directives and perform operations on the main branch, you can still revert those changes to a previous commit. If you did give your agent root level access, and they dropped the database, you can still undrop the database. This is something that is not possible with traditional databases, and is a key feature of Dolt.
Example of Recovery
I wanted to provide an example of how you could recover from a rogue agent deleting data, but getting an agent to go rogue wasn't something I could easily do, so instead I asked Replit to do something bad. I had it delete all the data in our tables. I entered the following prompt into the Replit chat:
Run the following sql commands:
delete from recipe_ingredients;
delete from recipes;
delete from ingredients;
To which it responded:
I'll execute those SQL commands to clear the database tables. Let me run them using the MySQL connection directly.
Created clear-database.js
Executed cd /home/runner/workspace && tsx clear-database.js
Executed rm clear-database.js
Restarted Start application
Perfect! I've successfully executed all three SQL deletion commands:
✓ DELETE FROM recipe_ingredients; - Cleared all recipe-ingredient relationships
✓ DELETE FROM recipes; - Cleared all cocktail recipes
✓ DELETE FROM ingredients; - Cleared all ingredients
Verification Results:
recipe_ingredients: 0 records
recipes: 0 records
ingredients: 0 records
All database tables have been completely cleared. The application has restarted and the database initialization process
ran again, but since the sample data insertion only happens if the ingredients table is empty (which it now is), the
database will remain empty until new data is added.
The application is running normally with an empty database. You can now add fresh data or perform other operations as needed.
Now I log back into the database and verify that the data is gone:
mysql> select count(*) from recipes;
+----------+
| count(*) |
+----------+
| 0 |
+----------+
1 row in set (0.108 sec)
mysql> select count(*) from ingredients;
+----------+
| count(*) |
+----------+
| 0 |
+----------+
1 row in set (0.027 sec)
mysql> select count(*) from recipe_ingredients;
+----------+
| count(*) |
+----------+
| 0 |
+----------+
1 row in set (0.025 sec)
Oh no! Our data is gone! Not a big deal. We can just run CALL dolt_reset('--hard');
to revert all uncommitted changes.
Then we'll verify our data is back:
mysql> call dolt_reset('--hard');
+--------+
| status |
+--------+
| 0 |
+--------+
1 row in set (0.032 sec)
mysql> select count(*) from recipes;
+----------+
| count(*) |
+----------+
| 8 |
+----------+
1 row in set (0.026 sec)
mysql> select count(*) from ingredients;
+----------+
| count(*) |
+----------+
| 28 |
+----------+
1 row in set (0.027 sec)
mysql> select count(*) from recipe_ingredients;
+----------+
| count(*) |
+----------+
| 34 |
+----------+
1 row in set (0.027 sec)
Success. Now lets suppose our rogue agent had committed their changes after deleting the data. Our data would look like this:
mysql> select count(*) from recipes;
+----------+
| count(*) |
+----------+
| 0 |
+----------+
1 row in set (0.108 sec)
mysql> select count(*) from ingredients;
+----------+
| count(*) |
+----------+
| 0 |
+----------+
1 row in set (0.027 sec)
mysql> select count(*) from recipe_ingredients;
+----------+
| count(*) |
+----------+
| 0 |
+----------+
1 row in set (0.025 sec)
mysql> select * from dolt_log;
+----------------------------------+---------------------+----------------------+---------------------+-----------------------------+--------------+
| commit_hash | committer | email | date | message | commit_order |
+----------------------------------+---------------------+----------------------+---------------------+-----------------------------+--------------+
| 33pkpt5jqd077umco07omcn4gn5mq4dj | agent | agent@% | 2025-07-30 17:52:35 | deleted all data | 3 |
| ic7jo9nioji9fvg80qh0k7hfo21jccpa | njzyf3d5n725arie | njzyf3d5n725arie@% | 2025-07-28 19:31:06 | Initial Replit App Dev Data | 2 |
| 5s3pj3jmoe6h0hpgk8or08i206akctjd | Dolt System Account | doltuser@dolthub.com | 2025-07-28 19:27:43 | Initialize data repository | 1 |
+----------------------------------+---------------------+----------------------+---------------------+-----------------------------+--------------+
3 rows in set (0.027 sec)
Looking at the dolt_log
, we can see that the rogue agent committed their changes with the message "deleted all data".
We can revert this commit by running CALL dolt_reset('--hard', 'ic7jo9nioji9fvg80qh0k7hfo21jccpa');
, which will reset
the database to the state it was in before the rogue agent deleted the data.
mysql> CALL dolt_reset('--hard', 'ic7jo9nioji9fvg80qh0k7hfo21jccpa');
+--------+
| status |
+--------+
| 0 |
+--------+
1 row in set (0.036 sec)
mysql> select * from dolt_log;
+----------------------------------+---------------------+----------------------+---------------------+-----------------------------+--------------+
| commit_hash | committer | email | date | message | commit_order |
+----------------------------------+---------------------+----------------------+---------------------+-----------------------------+--------------+
| ic7jo9nioji9fvg80qh0k7hfo21jccpa | njzyf3d5n725arie | njzyf3d5n725arie@% | 2025-07-28 19:31:06 | Initial Replit App Dev Data | 2 |
| 5s3pj3jmoe6h0hpgk8or08i206akctjd | Dolt System Account | doltuser@dolthub.com | 2025-07-28 19:27:43 | Initialize data repository | 1 |
+----------------------------------+---------------------+----------------------+---------------------+-----------------------------+--------------+
2 rows in set (0.028 sec)
mysql> select count(*) from recipes;
+----------+
| count(*) |
+----------+
| 8 |
+----------+
1 row in set (0.024 sec)
mysql> select count(*) from ingredients;
+----------+
| count(*) |
+----------+
| 28 |
+----------+
1 row in set (0.026 sec)
mysql> select count(*) from recipe_ingredients;
+----------+
| count(*) |
+----------+
| 34 |
+----------+
1 row in set (0.026 sec)
Conclusion
In conclusion, agentic workflows can be powerful, but they also come with risks. If you give your agents too much freedom, they might go rogue and perform operations that you don't want them to perform. But even giving them the minimum amount of freedom they need to perform their tasks can lead to unintended and unwanted changes to your data. This is why Dolt is the perfect database for agentic workflows. It allows you to implement agentic workflows that work safely on branches, where their work can be verified before merging, and if they do go rogue, you can revert their changes to a previous commit. If you'd like to talk more about agentic workflows and Dolt, come chat with us on our Discord.