Skip to content

Commit c782a04

Browse files
feat(audit): drop misleading client_ip from audit log
The pgwire socket address is the Fly edge proxy, not the real client, so the recorded value was misleading. Stop writing it (always None on both the deny path and the query-completed path), remove the field from the admin API response and React audit page, and add a DEPRECATED comment to the entity field. The DB column is kept for backward compatibility.
1 parent 4f62e71 commit c782a04

8 files changed

Lines changed: 9 additions & 11 deletions

File tree

CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ policy_assignment (id UUID v7, policy_id, data_source_id, user_id?, role_id?,
208208
admin_audit_log (id UUID v7, resource_type, resource_id, action, actor_id, changes JSON, created_at)
209209
query_audit_log (id UUID v7, user_id, username, data_source_id, datasource_name,
210210
original_query, rewritten_query, policies_applied JSON,
211-
execution_time_ms, client_ip, client_info, created_at)
211+
execution_time_ms, client_info, created_at)
212212
```
213213

214214
Catalog entity IDs (schemas, tables, columns) are deterministic UUID v5 fingerprints derived from their natural keys. Re-discovering the same upstream object always produces the same ID, so re-syncs are safe upserts.

admin-ui/src/api/audit.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ export interface AuditLogEntry {
2222
}
2323
}>
2424
execution_time_ms: number | null
25-
client_ip: string | null
2625
client_info: string | null
2726
created_at: string
2827
status: 'success' | 'error' | 'denied'

admin-ui/src/pages/QueryAuditPage.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -300,7 +300,6 @@ export function QueryAuditPage() {
300300
</div>
301301
)}
302302
<div className="flex gap-6 text-xs text-gray-500">
303-
{entry.client_ip && <span>IP: {entry.client_ip}</span>}
304303
{entry.client_info && <span>App: {entry.client_info}</span>}
305304
</div>
306305
</div>

proxy/CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ A single shared WASM runtime created once at startup in `main.rs` and passed to
9797

9898
**Column-level policies must be enforced at scan level**: All column-level policies (deny, mask, and any future types) MUST be enforced at the `TableScan` level (visibility-level for deny, `transform_up` Projection for mask) to prevent CTE/subquery alias bypass. `SubqueryAlias` and CTE nodes change the DFSchema qualifier from the real table name to the alias, causing top-level-only matching to miss. Top-level `apply_projection_qualified` is defense-in-depth only.
9999

100-
**Audit logging**: after each query, `PolicyHook` spawns a `tokio::spawn` task to insert a `query_audit_log` row asynchronously. The row captures `original_query`, `rewritten_query`, `policies_applied` (JSON with name+version snapshot including decision function results), `client_ip`, and `client_info` (application_name from pgwire startup params).
100+
**Audit logging**: after each query, `PolicyHook` spawns a `tokio::spawn` task to insert a `query_audit_log` row asynchronously. The row captures `original_query`, `rewritten_query`, `policies_applied` (JSON with name+version snapshot including decision function results), and `client_info` (application_name from pgwire startup params).
101101

102102
### Decision Functions in PolicyHook
103103

proxy/src/admin/audit_handlers.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,6 @@ pub async fn list_audit_logs(
7979
rewritten_query: m.rewritten_query,
8080
policies_applied,
8181
execution_time_ms: m.execution_time_ms,
82-
client_ip: m.client_ip,
8382
client_info: m.client_info,
8483
created_at: m.created_at,
8584
status: m.status,

proxy/src/admin/dto.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -892,7 +892,6 @@ pub struct AuditLogResponse {
892892
pub rewritten_query: Option<String>,
893893
pub policies_applied: serde_json::Value,
894894
pub execution_time_ms: Option<i64>,
895-
pub client_ip: Option<String>,
896895
pub client_info: Option<String>,
897896
pub created_at: chrono::NaiveDateTime,
898897
pub status: String,

proxy/src/entity/query_audit_log.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ pub struct Model {
1616
/// JSON array of {policy_id, version, name}
1717
pub policies_applied: String,
1818
pub execution_time_ms: Option<i64>,
19+
/// DEPRECATED — logically dropped 2026-04-26. Always written as `None`.
20+
/// The TCP peer address pgwire exposes is the Fly edge proxy, not the real
21+
/// client, so the value was misleading. Column kept for backward DB compat.
22+
/// Do NOT start writing it again without first parsing PROXY protocol v2 in
23+
/// the accept loop and gating it behind `BR_TRUST_PROXY_PROTOCOL`.
1924
pub client_ip: Option<String>,
2025
pub client_info: Option<String>,
2126
pub created_at: DateTime,

proxy/src/hooks/policy.rs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -793,7 +793,6 @@ impl PolicyHook {
793793
};
794794
let username = metadata.get("user").cloned().unwrap_or_default();
795795
let datasource = metadata.get("datasource").cloned().unwrap_or_default();
796-
let client_ip = Some(client.socket_addr().ip().to_string());
797796
let client_info = metadata.get("application_name").cloned();
798797

799798
let session = match self.get_session(user_id, &datasource).await {
@@ -816,7 +815,7 @@ impl PolicyHook {
816815
rewritten_query: sea_orm::Set(None),
817816
policies_applied: sea_orm::Set("[]".to_string()),
818817
execution_time_ms: sea_orm::Set(None),
819-
client_ip: sea_orm::Set(client_ip),
818+
client_ip: sea_orm::Set(None),
820819
client_info: sea_orm::Set(client_info),
821820
created_at: sea_orm::Set(now),
822821
status: sea_orm::Set("denied".to_string()),
@@ -2439,7 +2438,6 @@ impl QueryHook for PolicyHook {
24392438
};
24402439
let username = metadata.get("user").cloned().unwrap_or_default();
24412440
let datasource = metadata.get("datasource").cloned().unwrap_or_default();
2442-
let client_ip = Some(client.socket_addr().ip().to_string());
24432441
let client_info = metadata.get("application_name").cloned();
24442442

24452443
// Load session data
@@ -2668,7 +2666,6 @@ impl QueryHook for PolicyHook {
26682666
let audit_ds_name = session.datasource_name.clone();
26692667
let audit_orig_q = original_query;
26702668
let audit_policies = serde_json::to_string(&policies_applied).unwrap_or_default();
2671-
let audit_ip = client_ip;
26722669
let audit_info = client_info;
26732670
let audit_status_owned = audit_status.to_string();
26742671

@@ -2684,7 +2681,7 @@ impl QueryHook for PolicyHook {
26842681
rewritten_query: sea_orm::Set(audit_rewritten),
26852682
policies_applied: sea_orm::Set(audit_policies),
26862683
execution_time_ms: sea_orm::Set(Some(elapsed_ms)),
2687-
client_ip: sea_orm::Set(audit_ip),
2684+
client_ip: sea_orm::Set(None),
26882685
client_info: sea_orm::Set(audit_info),
26892686
created_at: sea_orm::Set(now),
26902687
status: sea_orm::Set(audit_status_owned),

0 commit comments

Comments
 (0)