Skip to content

Commit 1919fbd

Browse files
authored
[codex] always tag audit event source (#32)
* fix: always tag audit event source * chore: address audit source review comments
1 parent 7afafbf commit 1919fbd

4 files changed

Lines changed: 53 additions & 9 deletions

File tree

docs/features/audit-log.mdx

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,8 @@ All audit events are JSON objects with a common structure:
6161
"actor": "alice@example.com",
6262
"provider": "google",
6363
"ip": "192.168.1.100",
64-
"success": true
64+
"success": true,
65+
"source": "web"
6566
}
6667
```
6768

@@ -71,6 +72,7 @@ All audit events are JSON objects with a common structure:
7172
| `ip` | Client IP address |
7273
| `success` | Whether login succeeded |
7374
| `error` | Error message (if failed) |
75+
| `source` | Event origin: currently `"web"` |
7476

7577
### auth.logout
7678

@@ -79,10 +81,16 @@ All audit events are JSON objects with a common structure:
7981
"type": "audit",
8082
"ts": "2024-01-15T10:35:00.000Z",
8183
"action": "auth.logout",
82-
"actor": "alice@example.com"
84+
"actor": "alice@example.com",
85+
"source": "web"
8386
}
8487
```
8588

89+
| Field | Description |
90+
|-------|-------------|
91+
| `actor` | User who logged out |
92+
| `source` | Event origin: currently `"web"` |
93+
8694
### sql.execute
8795

8896
```json
@@ -96,7 +104,8 @@ All audit events are JSON objects with a common structure:
96104
"sql": "SELECT * FROM users WHERE active = true",
97105
"success": true,
98106
"duration_ms": 45,
99-
"row_count": 150
107+
"row_count": 150,
108+
"source": "web"
100109
}
101110
```
102111

@@ -109,7 +118,7 @@ All audit events are JSON objects with a common structure:
109118
| `duration_ms` | Execution time in milliseconds |
110119
| `row_count` | Number of rows returned (optional) |
111120
| `error` | Error message (if failed) |
112-
| `source` | `"mcp"` when the query came from the [MCP Server](/features/mcp-server) (omitted otherwise) |
121+
| `source` | Query origin: `"web"` for the web app, `"mcp"` for the [MCP Server](/features/mcp-server) |
113122
| `tool` | MCP tool name that ran the query (only when `source` is `"mcp"`) |
114123
| `agent` | The [agent](/configuration/config#agents) id that ran the query (only when `source` is `"mcp"`). For a delegated agent the `actor` is the user it acts for; for a pure agent the `actor` is `agent:<id>` |
115124

@@ -125,7 +134,8 @@ All audit events are JSON objects with a common structure:
125134
"database": "postgres",
126135
"sql": "SELECT * FROM users WHERE active = true",
127136
"row_count": 150,
128-
"format": "csv"
137+
"format": "csv",
138+
"source": "web"
129139
}
130140
```
131141

@@ -136,6 +146,7 @@ All audit events are JSON objects with a common structure:
136146
| `sql` | SQL query that produced the exported data |
137147
| `row_count` | Number of rows exported |
138148
| `format` | Export format (`csv`) |
149+
| `source` | Export origin: currently `"web"` |
139150

140151
## Capturing Logs
141152

server/lib/audit.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,13 @@ interface AuthLoginEvent extends BaseEvent {
1212
provider: string
1313
ip: string
1414
success: boolean
15+
source: 'web'
1516
error?: string
1617
}
1718

1819
interface AuthLogoutEvent extends BaseEvent {
1920
action: 'auth.logout'
21+
source: 'web'
2022
}
2123

2224
interface SQLExecuteEvent extends BaseEvent {
@@ -28,8 +30,8 @@ interface SQLExecuteEvent extends BaseEvent {
2830
duration_ms: number
2931
row_count?: number
3032
error?: string
31-
/** Set to 'mcp' when the query originated from the MCP server */
32-
source?: 'mcp'
33+
/** Origin channel of the query: web app or MCP server */
34+
source: 'web' | 'mcp'
3335
/** MCP tool name (only when source is 'mcp') */
3436
tool?: string
3537
/** Agent id that ran the query, when source is 'mcp' (present for both pure and delegated agents) */
@@ -43,6 +45,7 @@ interface DataExportEvent extends BaseEvent {
4345
sql: string
4446
row_count: number
4547
format: string
48+
source: 'web'
4649
}
4750

4851
export type AuditEvent = AuthLoginEvent | AuthLogoutEvent | SQLExecuteEvent | DataExportEvent
@@ -96,6 +99,7 @@ export function auditLogin(actor: string, provider: string, ip: string, success:
9699
provider,
97100
ip,
98101
success,
102+
source: 'web',
99103
}
100104
if (error) event.error = error
101105
emit(event)
@@ -107,6 +111,7 @@ export function auditLogout(actor: string): void {
107111
ts: now(),
108112
action: 'auth.logout',
109113
actor,
114+
source: 'web',
110115
})
111116
}
112117

@@ -132,11 +137,11 @@ export function auditSQL(
132137
sql,
133138
success,
134139
duration_ms,
140+
source: opts?.source ?? 'web',
135141
}
136142
if (row_count !== undefined) event.row_count = row_count
137143
if (error) event.error = error
138144
if (opts) {
139-
event.source = opts.source
140145
event.tool = opts.tool
141146
event.agent = opts.agent
142147
}
@@ -161,6 +166,7 @@ export function auditExport(
161166
sql,
162167
row_count,
163168
format,
169+
source: 'web',
164170
})
165171
}
166172

src/pages/AuditLog.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ export default function AuditLog({ connectionId }: AuditLogProps) {
156156
<TableRow key={`${entry.timestamp}-${entry.action}-${entry.actor}-${idx}`}>
157157
<TableCell className="text-gray-600">{new Date(entry.timestamp).toLocaleString()}</TableCell>
158158
<TableCell>{entry.actor}</TableCell>
159-
<TableCell><ActionCell action={entry.action} /></TableCell>
159+
<TableCell><ActionCell action={entry.action} source={entry.source} /></TableCell>
160160
<TableCell><StatusBadge success={entry.success} /></TableCell>
161161
<TableCell>{entry.provider || '—'}</TableCell>
162162
<TableCell className="font-mono text-xs">{entry.ip || '—'}</TableCell>

tests/audit.test.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,34 @@ describe('audit event store', () => {
5656
const entries = listAuditEvents('prod', 10)
5757
expect(entries).toHaveLength(2)
5858
expect(entries[0].action).toBe('data.export')
59+
expect(entries[0].source).toBe('web')
5960
expect(entries[1].action).toBe('sql.execute')
61+
expect(entries[1].source).toBe('web')
62+
})
63+
64+
it('records source for web and MCP SQL events', () => {
65+
auditSQL('alice@example.com', 'prod', 'postgres', 'SELECT 1', true, 1, 1)
66+
auditSQL('agent:bot', 'prod', 'postgres', 'SELECT 2', true, 1, 1, undefined, {
67+
source: 'mcp',
68+
tool: 'query',
69+
agent: 'bot',
70+
})
71+
72+
const entries = listAuditEvents('prod', 10)
73+
expect(entries).toHaveLength(2)
74+
expect(entries[0].source).toBe('mcp')
75+
expect('tool' in entries[0] ? entries[0].tool : '').toBe('query')
76+
expect('agent' in entries[0] ? entries[0].agent : '').toBe('bot')
77+
expect(entries[1].source).toBe('web')
78+
})
79+
80+
it('records source for system auth events', () => {
81+
auditLogin('alice@example.com', 'password', '10.0.0.1', true)
82+
auditLogout('alice@example.com')
83+
84+
const system = listSystemAuditEvents(10)
85+
expect(system).toHaveLength(2)
86+
expect(system.every((event) => event.source === 'web')).toBe(true)
6087
})
6188

6289
it('applies the response limit', () => {

0 commit comments

Comments
 (0)