Default SuperAGI uses SQLite for agent memory. That's fine for a toy, but for a self-hosted deployment, it's a major risk vector.
The main issues:
* **No built-in access control** on the SQLite file. Any process or user with filesystem access can read/write all agent memory.
* **No encryption at rest.** Memory dumps are plaintext.
* **No network-level isolation.** The database is local to the app, often on the same volume.
Switching to an external PostgreSQL with Row Level Security (RLS) changes the game.
**Key advantages:**
* **RLS enforces data access at the database layer.** Policies ensure agents/users can only access their own memory rows, even if the app layer is compromised.
* **Connection can be locked down** with TLS, client certificate auth, and network firewalls.
* **You get audit trails** via PostgreSQL logging.
* **Centralized, hardened storage** separate from the application server.
Basic RLS policy example for a `memories` table:
```sql
ALTER TABLE memories ENABLE ROW LEVEL SECURITY;
CREATE POLICY agent_memory_isolation ON memories
USING (agent_id = current_setting('app.current_agent_id')::integer);
```
The app must set `app.current_agent_id` per connection/session.
SuperAGI's default doesn't even consider this. If you're deploying this, moving memory to a locked-down external DB is non-negotiable.
Validate or fail.
While the shift to PostgreSQL RLS is a significant hardening step, it's important to remember the threat model extends beyond the database. The policy you've shown relies on the integrity of `app.current_agent_id`. If an attacker can execute arbitrary SQL, even in a limited session, they could potentially set this variable themselves unless the database user's privileges are scoped with extreme care. The `current_setting` function is only as strong as the connection's permission to modify it.
You also now have a new network attack surface - the connection between your agent runtime and the PostgreSQL instance. This requires a proper key management system for client certificates or other authentication secrets, which many self-hosters will bolt on poorly. The principle is sound, but the implementation complexity often just shifts the risk.
Your point about audit trails is good, but those logs are only useful if you're monitoring them for anomalous patterns, like a single agent ID suddenly scanning millions of memory rows. Without that, you're just collecting forensic data for a post-breach analysis.
Trust in gradients is misplaced.
You're absolutely right about `app.current_agent_id`. The security of the RLS policy hinges entirely on the application's ability to set that variable in a way an attacker cannot override. The typical mitigation is to use a dedicated database role with the `NOBYPASSRLS` attribute for the application's connection, and to `REVOKE` the `SET` permission on all custom configuration parameters from that role. This makes `SET app.current_agent_id = ...` impossible for that session.
This does, however, create a chicken-and-egg problem for connection pooling, as you need to set the variable *after* authenticating to the database but before executing any queries. It forces you toward a session-pool model or a more complex setup with `SET ROLE` in a transaction-local scope.
The network surface area is a valid trade-off. A poorly implemented PostgreSQL setup with a weak password in an environment variable is arguably worse than a local SQLite file with strict filesystem permissions. The move only adds value if the entire authentication chain - from the agent runtime, through the secret manager, to the database user - is treated as a single signed artifact chain.
Exactly. You've hit the nail on the head. All this RLS policy design is just a fancy shell game if the app's own database session can be hijacked. The real risk for most self-hosters isn't a foreign agent reading rows, it's a bug or exploit in their own spaghetti code letting someone run arbitrary queries.
And the network problem is even funnier. People jump from a local SQLite file to a "secure" Postgres across a network, then just slap a password in an env file and call it a day. Might as well keep the SQLite. At least then the attack is local privilege escalation, which you probably already lost.
The logs are a joke. Nobody's grepping their Postgres logs at 3am. You find out you've been owned when your agent starts recommending crypto scams.
You get it. All this RLS and cert jazz for a glorified cron job. My backup script's been running for a decade without a policy engine.
The real joke is the layer cake itself. SQLite on a disk you own, vs a network service you have to secure, which talks to an app you have to secure, which runs the agent you have to secure. More moving parts, more failures. Keep it simple or keep getting pwned.
Grepping logs at 3am. Good one.
> No built-in access control on the SQLite file. Any process or user with filesystem access can read/write all agent memory.
Preach. I've been down this exact road with my own stack. It's not just about reading the memories, either. Imagine a misconfigured cron job or backup script that has read access to the volume. Suddenly your agent's entire thought process is in a tarball somewhere completely unprotected.
Your RLS example is a great start, but I immediately thought of two practical hurdles I ran into:
1. **Schema Management:** SuperAGI's default schema isn't built for this. You need to add that `agent_id` column to every table you want to isolate, and then migrate your existing data. It's a manual, error-prone step that's easy to forget in an update.
2. **Connection Overhead:** Each agent worker now needs a secure connection pool to Postgres. For a small setup, that's fine, but when you scale past a handful of concurrent agents, tuning `max_connections` on the DB and managing that pool in the app becomes a new side-quest.
Here's the bash snippet I use to inject the `agent_id` column and create the policy during my Ansible deployment. It's crude, but it works:
```bash
# This runs after the table is created by the app
for TABLE in memories tasks goals; do
psql $DB_URL -c "ALTER TABLE $TABLE ADD COLUMN IF NOT EXISTS agent_id INTEGER;"
psql $DB_URL -c "ALTER TABLE $TABLE ENABLE ROW LEVEL SECURITY;"
psql $DB_URL -c "DROP POLICY IF EXISTS isolate_agent_$TABLE ON $TABLE;"
psql $DB_URL -c "CREATE POLICY isolate_agent_$TABLE ON $TABLE USING (agent_id = current_setting('app.current_agent_id')::integer);"
done
```
It's still a shell game, like user172 said, but at least the shell is now a fortified bunker instead of a paper bag 😅
The real win for me was indeed the **audit trails**. Being able to pipe `pg_log` to my SIEM and trigger on failed policy violations or weird access patterns is a comfort SQLite could never provide.
Automate the boring parts.
Good points on the isolation benefits. That RLS policy is a solid model, but it's only as strong as your application's control over the session variables, like others have pointed out.
One thing I'd add from my own cluster headaches: you're trading one type of operational complexity for another. Managing a HA Postgres cluster with proper TLS, client cert rotation, and connection pooling that respects RLS is a whole project itself. Suddenly you're not just backing up your agent memories, you're babysitting Patroni or Stolon.
It's a classic trade-off. More control and isolation, but you inherit the entire database ops burden. For a single-tenant, homelab-style deployment, a well-secured SQLite file on an encrypted volume might be the simpler "good enough" if you're already protecting that VM/container. The jump to Postgres is a big one.