haldir 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
haldir-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,98 @@
1
+ Metadata-Version: 2.4
2
+ Name: haldir
3
+ Version: 0.1.0
4
+ Summary: The guardian layer for AI agents — identity, secrets, audit via MCP
5
+ Project-URL: Homepage, https://haldir.xyz
6
+ Project-URL: Repository, https://github.com/ExposureGuard/haldir
7
+ Project-URL: Documentation, https://haldir.xyz
8
+ Author-email: Sterling <sterling@getexposureguard.com>
9
+ License: MIT
10
+ Keywords: ai-agents,audit,compliance,governance,identity,mcp,model-context-protocol,secrets,security
11
+ Requires-Python: >=3.10
12
+ Requires-Dist: cryptography>=42.0.0
13
+ Requires-Dist: httpx>=0.27.0
14
+ Requires-Dist: mcp>=1.0.0
15
+ Requires-Dist: pydantic>=2.0.0
16
+ Description-Content-Type: text/markdown
17
+
18
+ <!-- mcp-name: io.github.ExposureGuard/haldir -->
19
+ # Haldir
20
+
21
+ **The guardian layer for AI agents.**
22
+
23
+ Haldir is an MCP server platform that gives AI agents identity, security, and accountability. Every agent action — browsing, paying, authenticating, calling APIs — flows through Haldir.
24
+
25
+ ## Products
26
+
27
+ | Product | What it does | MCP Tools |
28
+ |---|---|---|
29
+ | **Haldir Gate** | Agent identity, auth, permissions | `authenticate`, `check_permission`, `create_session`, `revoke_session` |
30
+ | **Haldir Vault** | Secrets, credentials, payment limits | `get_secret`, `store_secret`, `authorize_payment`, `check_budget` |
31
+ | **Haldir Watch** | Audit logs, compliance, cost tracking | `log_action`, `get_audit_trail`, `get_spend`, `flag_anomaly` |
32
+
33
+ ## Quick Start
34
+
35
+ ```bash
36
+ pip install haldir
37
+ ```
38
+
39
+ ```python
40
+ from haldir import Gate, Vault, Watch
41
+
42
+ # Initialize
43
+ gate = Gate(api_key="your-key")
44
+ vault = Vault(api_key="your-key")
45
+ watch = Watch(api_key="your-key")
46
+
47
+ # Authenticate an agent
48
+ session = gate.create_session(agent_id="my-agent", scopes=["read", "spend:50"])
49
+
50
+ # Get a secret safely
51
+ api_key = vault.get_secret("stripe_key", session=session)
52
+
53
+ # Every action is logged
54
+ watch.log_action(session=session, tool="stripe", action="charge", amount=29.99)
55
+ ```
56
+
57
+ ## MCP Server
58
+
59
+ ```json
60
+ {
61
+ "mcpServers": {
62
+ "haldir": {
63
+ "command": "haldir-mcp",
64
+ "env": {
65
+ "HALDIR_API_KEY": "your-key"
66
+ }
67
+ }
68
+ }
69
+ }
70
+ ```
71
+
72
+ ## Architecture
73
+
74
+ ```
75
+ Agent (Claude, GPT, etc.)
76
+
77
+
78
+ ┌─────────────────────────┐
79
+ │ Haldir Gate │ ← Identity + permissions
80
+ │ "Can this agent do X?" │
81
+ └────────┬────────────────┘
82
+
83
+ ┌────┴────┐
84
+ ▼ ▼
85
+ ┌────────┐ ┌────────┐
86
+ │ Vault │ │ Watch │
87
+ │secrets │ │ audit │
88
+ │payments│ │ costs │
89
+ └────────┘ └────────┘
90
+ │ │
91
+ ▼ ▼
92
+ External Storage
93
+ APIs (Postgres)
94
+ ```
95
+
96
+ ## License
97
+
98
+ MIT
haldir-0.1.0/README.md ADDED
@@ -0,0 +1,81 @@
1
+ <!-- mcp-name: io.github.ExposureGuard/haldir -->
2
+ # Haldir
3
+
4
+ **The guardian layer for AI agents.**
5
+
6
+ Haldir is an MCP server platform that gives AI agents identity, security, and accountability. Every agent action — browsing, paying, authenticating, calling APIs — flows through Haldir.
7
+
8
+ ## Products
9
+
10
+ | Product | What it does | MCP Tools |
11
+ |---|---|---|
12
+ | **Haldir Gate** | Agent identity, auth, permissions | `authenticate`, `check_permission`, `create_session`, `revoke_session` |
13
+ | **Haldir Vault** | Secrets, credentials, payment limits | `get_secret`, `store_secret`, `authorize_payment`, `check_budget` |
14
+ | **Haldir Watch** | Audit logs, compliance, cost tracking | `log_action`, `get_audit_trail`, `get_spend`, `flag_anomaly` |
15
+
16
+ ## Quick Start
17
+
18
+ ```bash
19
+ pip install haldir
20
+ ```
21
+
22
+ ```python
23
+ from haldir import Gate, Vault, Watch
24
+
25
+ # Initialize
26
+ gate = Gate(api_key="your-key")
27
+ vault = Vault(api_key="your-key")
28
+ watch = Watch(api_key="your-key")
29
+
30
+ # Authenticate an agent
31
+ session = gate.create_session(agent_id="my-agent", scopes=["read", "spend:50"])
32
+
33
+ # Get a secret safely
34
+ api_key = vault.get_secret("stripe_key", session=session)
35
+
36
+ # Every action is logged
37
+ watch.log_action(session=session, tool="stripe", action="charge", amount=29.99)
38
+ ```
39
+
40
+ ## MCP Server
41
+
42
+ ```json
43
+ {
44
+ "mcpServers": {
45
+ "haldir": {
46
+ "command": "haldir-mcp",
47
+ "env": {
48
+ "HALDIR_API_KEY": "your-key"
49
+ }
50
+ }
51
+ }
52
+ }
53
+ ```
54
+
55
+ ## Architecture
56
+
57
+ ```
58
+ Agent (Claude, GPT, etc.)
59
+
60
+
61
+ ┌─────────────────────────┐
62
+ │ Haldir Gate │ ← Identity + permissions
63
+ │ "Can this agent do X?" │
64
+ └────────┬────────────────┘
65
+
66
+ ┌────┴────┐
67
+ ▼ ▼
68
+ ┌────────┐ ┌────────┐
69
+ │ Vault │ │ Watch │
70
+ │secrets │ │ audit │
71
+ │payments│ │ costs │
72
+ └────────┘ └────────┘
73
+ │ │
74
+ ▼ ▼
75
+ External Storage
76
+ APIs (Postgres)
77
+ ```
78
+
79
+ ## License
80
+
81
+ MIT
@@ -0,0 +1,289 @@
1
+ """
2
+ Haldir Database — Multi-tenant persistence layer.
3
+
4
+ Supports SQLite (dev/small deployments) and PostgreSQL (production).
5
+ Every table has a tenant_id column for data isolation between API keys.
6
+
7
+ Set DATABASE_URL for Postgres: postgresql://user:pass@host/haldir
8
+ Otherwise falls back to SQLite at HALDIR_DB_PATH.
9
+ """
10
+
11
+ import os
12
+ import time
13
+ import sqlite3
14
+
15
+ DATABASE_URL = os.environ.get("DATABASE_URL", "")
16
+ DEFAULT_DB_PATH = os.environ.get("HALDIR_DB_PATH", "/data/haldir.db" if os.path.isdir("/data") else "haldir.db")
17
+
18
+ _pg_pool = None
19
+
20
+
21
+ def _is_postgres() -> bool:
22
+ return DATABASE_URL.startswith("postgres")
23
+
24
+
25
+ def get_db(db_path: str = DEFAULT_DB_PATH):
26
+ """Get a database connection. Returns SQLite or Postgres depending on config."""
27
+ if _is_postgres():
28
+ return _get_pg()
29
+ conn = sqlite3.connect(db_path, timeout=10)
30
+ conn.row_factory = sqlite3.Row
31
+ conn.execute("PRAGMA journal_mode=WAL")
32
+ conn.execute("PRAGMA foreign_keys=ON")
33
+ conn.execute("PRAGMA busy_timeout=5000")
34
+ return conn
35
+
36
+
37
+ def _get_pg():
38
+ """Get a PostgreSQL connection from the pool."""
39
+ global _pg_pool
40
+ import psycopg2
41
+ import psycopg2.extras
42
+
43
+ if _pg_pool is None:
44
+ from psycopg2 import pool
45
+ _pg_pool = pool.ThreadedConnectionPool(1, 10, DATABASE_URL)
46
+
47
+ conn = _pg_pool.getconn()
48
+ conn.autocommit = False
49
+ return PgConnectionWrapper(conn, _pg_pool)
50
+
51
+
52
+ class PgConnectionWrapper:
53
+ """Wraps psycopg2 connection to match sqlite3 interface."""
54
+
55
+ def __init__(self, conn, pool):
56
+ self._conn = conn
57
+ self._pool = pool
58
+ self._cursor = None
59
+
60
+ def execute(self, sql, params=None):
61
+ # Convert ? placeholders to %s for postgres
62
+ sql = sql.replace("?", "%s")
63
+ # Convert INTEGER PRIMARY KEY AUTOINCREMENT to SERIAL
64
+ sql = sql.replace("INTEGER PRIMARY KEY AUTOINCREMENT", "SERIAL PRIMARY KEY")
65
+ cursor = self._conn.cursor(cursor_factory=_DictCursor())
66
+ cursor.execute(sql, params or ())
67
+ self._cursor = cursor
68
+ return cursor
69
+
70
+ def executescript(self, sql):
71
+ # Convert SQLite syntax to Postgres
72
+ sql = sql.replace("INTEGER PRIMARY KEY AUTOINCREMENT", "SERIAL PRIMARY KEY")
73
+ sql = sql.replace("CREATE INDEX IF NOT EXISTS", "CREATE INDEX IF NOT EXISTS")
74
+ cursor = self._conn.cursor()
75
+ cursor.execute(sql)
76
+ self._conn.commit()
77
+ return cursor
78
+
79
+ def commit(self):
80
+ self._conn.commit()
81
+
82
+ def close(self):
83
+ self._pool.putconn(self._conn)
84
+
85
+ def fetchone(self):
86
+ if self._cursor:
87
+ return self._cursor.fetchone()
88
+ return None
89
+
90
+ def fetchall(self):
91
+ if self._cursor:
92
+ return self._cursor.fetchall()
93
+ return []
94
+
95
+
96
+ def _DictCursor():
97
+ import psycopg2.extras
98
+ return psycopg2.extras.RealDictCursor
99
+
100
+
101
+ def init_db(db_path: str = DEFAULT_DB_PATH):
102
+ """Create all tables if they don't exist."""
103
+ if _is_postgres():
104
+ _init_pg()
105
+ else:
106
+ _init_sqlite(db_path)
107
+
108
+
109
+ def _init_sqlite(db_path: str):
110
+ conn = sqlite3.connect(db_path, timeout=10)
111
+ conn.row_factory = sqlite3.Row
112
+ conn.execute("PRAGMA journal_mode=WAL")
113
+ conn.executescript(_SCHEMA_SQLITE)
114
+ conn.commit()
115
+ conn.close()
116
+
117
+
118
+ def _init_pg():
119
+ import psycopg2
120
+ conn = psycopg2.connect(DATABASE_URL)
121
+ cursor = conn.cursor()
122
+ cursor.execute(_SCHEMA_POSTGRES)
123
+ conn.commit()
124
+ conn.close()
125
+
126
+
127
+ # ── Schema ──
128
+
129
+ _SCHEMA_SQLITE = """
130
+ -- API keys
131
+ CREATE TABLE IF NOT EXISTS api_keys (
132
+ key_hash TEXT PRIMARY KEY,
133
+ key_prefix TEXT NOT NULL,
134
+ tenant_id TEXT NOT NULL DEFAULT '',
135
+ name TEXT NOT NULL DEFAULT '',
136
+ tier TEXT NOT NULL DEFAULT 'free',
137
+ created_at REAL NOT NULL,
138
+ last_used REAL NOT NULL DEFAULT 0,
139
+ revoked INTEGER NOT NULL DEFAULT 0
140
+ );
141
+
142
+ CREATE INDEX IF NOT EXISTS idx_keys_tenant ON api_keys(tenant_id);
143
+
144
+ -- Gate: agent policies
145
+ CREATE TABLE IF NOT EXISTS agents (
146
+ agent_id TEXT NOT NULL,
147
+ tenant_id TEXT NOT NULL DEFAULT '',
148
+ default_scopes TEXT NOT NULL DEFAULT '["read","browse"]',
149
+ max_spend REAL NOT NULL DEFAULT 0.0,
150
+ metadata TEXT NOT NULL DEFAULT '{}',
151
+ created_at REAL NOT NULL,
152
+ PRIMARY KEY (agent_id, tenant_id)
153
+ );
154
+
155
+ -- Gate: sessions
156
+ CREATE TABLE IF NOT EXISTS sessions (
157
+ session_id TEXT PRIMARY KEY,
158
+ tenant_id TEXT NOT NULL DEFAULT '',
159
+ agent_id TEXT NOT NULL,
160
+ scopes TEXT NOT NULL DEFAULT '[]',
161
+ spend_limit REAL NOT NULL DEFAULT 0.0,
162
+ spent REAL NOT NULL DEFAULT 0.0,
163
+ created_at REAL NOT NULL,
164
+ expires_at REAL NOT NULL DEFAULT 0.0,
165
+ revoked INTEGER NOT NULL DEFAULT 0,
166
+ metadata TEXT NOT NULL DEFAULT '{}'
167
+ );
168
+
169
+ CREATE INDEX IF NOT EXISTS idx_sessions_agent ON sessions(agent_id);
170
+ CREATE INDEX IF NOT EXISTS idx_sessions_tenant ON sessions(tenant_id);
171
+ CREATE INDEX IF NOT EXISTS idx_sessions_expires ON sessions(expires_at);
172
+
173
+ -- Vault: encrypted secrets
174
+ CREATE TABLE IF NOT EXISTS secrets (
175
+ name TEXT NOT NULL,
176
+ tenant_id TEXT NOT NULL DEFAULT '',
177
+ encrypted_value BLOB NOT NULL,
178
+ scope_required TEXT NOT NULL DEFAULT 'read',
179
+ created_at REAL NOT NULL,
180
+ last_accessed REAL NOT NULL DEFAULT 0.0,
181
+ access_count INTEGER NOT NULL DEFAULT 0,
182
+ metadata TEXT NOT NULL DEFAULT '{}',
183
+ PRIMARY KEY (name, tenant_id)
184
+ );
185
+
186
+ -- Vault: payment authorizations
187
+ CREATE TABLE IF NOT EXISTS payments (
188
+ authorization_id TEXT PRIMARY KEY,
189
+ tenant_id TEXT NOT NULL DEFAULT '',
190
+ session_id TEXT NOT NULL,
191
+ agent_id TEXT NOT NULL,
192
+ amount REAL NOT NULL,
193
+ currency TEXT NOT NULL DEFAULT 'USD',
194
+ description TEXT NOT NULL DEFAULT '',
195
+ remaining_budget REAL NOT NULL DEFAULT 0.0,
196
+ timestamp REAL NOT NULL
197
+ );
198
+
199
+ CREATE INDEX IF NOT EXISTS idx_payments_session ON payments(session_id);
200
+ CREATE INDEX IF NOT EXISTS idx_payments_tenant ON payments(tenant_id);
201
+
202
+ -- Watch: audit log
203
+ CREATE TABLE IF NOT EXISTS audit_log (
204
+ entry_id TEXT PRIMARY KEY,
205
+ tenant_id TEXT NOT NULL DEFAULT '',
206
+ session_id TEXT NOT NULL,
207
+ agent_id TEXT NOT NULL,
208
+ action TEXT NOT NULL,
209
+ tool TEXT NOT NULL DEFAULT '',
210
+ details TEXT NOT NULL DEFAULT '{}',
211
+ cost_usd REAL NOT NULL DEFAULT 0.0,
212
+ timestamp REAL NOT NULL,
213
+ flagged INTEGER NOT NULL DEFAULT 0,
214
+ flag_reason TEXT NOT NULL DEFAULT ''
215
+ );
216
+
217
+ CREATE INDEX IF NOT EXISTS idx_audit_session ON audit_log(session_id);
218
+ CREATE INDEX IF NOT EXISTS idx_audit_agent ON audit_log(agent_id);
219
+ CREATE INDEX IF NOT EXISTS idx_audit_tenant ON audit_log(tenant_id);
220
+ CREATE INDEX IF NOT EXISTS idx_audit_tool ON audit_log(tool);
221
+ CREATE INDEX IF NOT EXISTS idx_audit_timestamp ON audit_log(timestamp);
222
+ CREATE INDEX IF NOT EXISTS idx_audit_flagged ON audit_log(flagged);
223
+
224
+ -- Watch: anomaly rules
225
+ CREATE TABLE IF NOT EXISTS anomaly_rules (
226
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
227
+ tenant_id TEXT NOT NULL DEFAULT '',
228
+ rule_type TEXT NOT NULL,
229
+ threshold REAL NOT NULL,
230
+ reason TEXT NOT NULL DEFAULT '',
231
+ created_at REAL NOT NULL
232
+ );
233
+
234
+ -- Approvals
235
+ CREATE TABLE IF NOT EXISTS approval_requests (
236
+ request_id TEXT PRIMARY KEY,
237
+ tenant_id TEXT NOT NULL DEFAULT '',
238
+ session_id TEXT NOT NULL,
239
+ agent_id TEXT NOT NULL,
240
+ action TEXT NOT NULL,
241
+ tool TEXT NOT NULL DEFAULT '',
242
+ details TEXT NOT NULL DEFAULT '{}',
243
+ reason TEXT NOT NULL DEFAULT '',
244
+ amount REAL NOT NULL DEFAULT 0.0,
245
+ status TEXT NOT NULL DEFAULT 'pending',
246
+ created_at REAL NOT NULL,
247
+ expires_at REAL NOT NULL DEFAULT 0.0,
248
+ decided_at REAL NOT NULL DEFAULT 0.0,
249
+ decided_by TEXT NOT NULL DEFAULT '',
250
+ decision_note TEXT NOT NULL DEFAULT ''
251
+ );
252
+
253
+ CREATE INDEX IF NOT EXISTS idx_approvals_status ON approval_requests(status);
254
+ CREATE INDEX IF NOT EXISTS idx_approvals_tenant ON approval_requests(tenant_id);
255
+
256
+ -- Webhooks
257
+ CREATE TABLE IF NOT EXISTS webhooks (
258
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
259
+ tenant_id TEXT NOT NULL DEFAULT '',
260
+ url TEXT NOT NULL,
261
+ name TEXT NOT NULL DEFAULT '',
262
+ events TEXT NOT NULL DEFAULT '["all"]',
263
+ active INTEGER NOT NULL DEFAULT 1,
264
+ created_at REAL NOT NULL,
265
+ last_fired REAL NOT NULL DEFAULT 0,
266
+ fire_count INTEGER NOT NULL DEFAULT 0,
267
+ fail_count INTEGER NOT NULL DEFAULT 0
268
+ );
269
+
270
+ CREATE INDEX IF NOT EXISTS idx_webhooks_tenant ON webhooks(tenant_id);
271
+
272
+ -- Usage tracking (for billing)
273
+ CREATE TABLE IF NOT EXISTS usage (
274
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
275
+ tenant_id TEXT NOT NULL,
276
+ month TEXT NOT NULL,
277
+ action_count INTEGER NOT NULL DEFAULT 0,
278
+ secret_access_count INTEGER NOT NULL DEFAULT 0,
279
+ payment_count INTEGER NOT NULL DEFAULT 0,
280
+ total_spend_usd REAL NOT NULL DEFAULT 0.0,
281
+ UNIQUE(tenant_id, month)
282
+ );
283
+
284
+ CREATE INDEX IF NOT EXISTS idx_usage_tenant ON usage(tenant_id);
285
+ """
286
+
287
+ _SCHEMA_POSTGRES = _SCHEMA_SQLITE.replace(
288
+ "INTEGER PRIMARY KEY AUTOINCREMENT", "SERIAL PRIMARY KEY"
289
+ )
@@ -0,0 +1,9 @@
1
+ """
2
+ Haldir Gate — Agent identity, authentication, and permissions.
3
+
4
+ Every agent gets a session with scoped permissions. No session = no access.
5
+ """
6
+
7
+ from .gate import Gate, Session, Permission
8
+
9
+ __all__ = ["Gate", "Session", "Permission"]