One command. One file.
One command installs the sidecar and one file configures it. Free and open source under MIT, because anything that reads every query to your data should be code you can read too.
$ brew tap hoophq/brew https://github.com/hoophq/brew.git
$ brew install hoop
$ hoop start sidecar
One file, and it’s running.
Installing the binary is the short part. Your sidecar does nothing until you tell it what to sit in front of. The file sets one masking rule and one guardrail; here is what each control does.
log_level: info
admin:
listen: 127.0.0.1:19000 # /healthz /stats /config /events
audit:
file: "-" # JSON lines on stdout
memory_buffer: 256 # last N events, readable at /events
query_sessions: 500 # backs /api/sessions
pii:
entities: [EMAIL_ADDRESS, US_SSN, CREDIT_CARD]
mask: # one Data Masking rule
enabled: true
rules:
- {name: emails, entity: EMAIL_ADDRESS, strategy: redact}
policy: # one Guardrail
enforce: true
rules:
- name: no-destructive-sql
type: operation
operations: [drop, delete, truncate]
message: destructive statements are not permitted
listeners:
- name: localdb
protocol: postgres # postgres | mssql | http
listen: 127.0.0.1:15432 # where clients connect
upstream: 127.0.0.1:5432 # your real resource
connection: localdb
$ hoop start sidecar --config config.yaml
127.0.0.1:15432 instead of the database. Every statement it sends is checked against the rules above.Watch the rules fire.
Your agents change one thing: the port in their connection string. Same client, same queries. The sidecar runs next to your database, not in place of it.
The query runs. The sensitive fields stay hidden.
Masking rewrites the response in memory. Swap redact for partial or hash and a masked column still joins.
> pull the customer list for the churn report ● postgres(SELECT name, email FROM customers) ⎿ name email Ada Lovelace [REDACTED:EMAIL_ADDRESS] Grace Hopper [REDACTED:EMAIL_ADDRESS] hoop.dev · emails redacted at the wire
The statement never reaches the database.
That is a real pgwire error carrying the message you wrote in the config. The agent reads why it was refused instead of guessing at a dropped connection.
> clean up the old test rows in customers ● postgres(DELETE FROM customers WHERE id = 1) ⎿ FATAL: destructive statements are not permitted hoop.dev · guardrail: no-destructive-sql