# ARGUS-A07: Database Error Leakage in API Responses > **Rule Code:** `ARGUS-A07` > **Identifier:** `ERROR_LEAK` > **Severity:** `HIGH` (Information Disclosure & Reconnaissance Blocker) > **Category:** `Security & Data Integrity` > **Target Standards:** CWE-209 (Generation of Error Message Containing Sensitive Information), CWE-200 (Exposure of Sensitive Information), OWASP ASVS v4.0.3/v5.0 §V7.1.1, §V7.1.2 --- ## 1. Overview & Core Invariant Raw internal database error strings-specifically `*pgconn.PgError.Message`, `Detail`, `Hint`, `Where`, SQL query fragments, or unmapped `err.Error()` strings originating from database operations-**must never be exposed directly to external clients** across HTTP, gRPC, or WebSocket API responses. Database driver errors must be intercepted at repository or handler boundaries, logged to secure server-side telemetry alongside a public transaction trace ID (`public_tx_id`), and mapped to human-friendly, domain-level response envelopes. --- ## 2. Technical Grounding & PostgreSQL Engine Realities ### 2.1. Wire Protocol `ErrorResponse` (§54.7) & PII Leaks When an execution error occurs in PostgreSQL 18.x, the server returns an `ErrorResponse` packet containing deep diagnostic metadata: - **`'M'` (Message):** Primary error summary (e.g. `relation "accounts" does not exist` or `null value in column "email" violates not-null constraint`). - **`'D'` (Detail):** Specific row values that failed execution (e.g. `Key (national_id)=(3201012345670001) already exists in table "users"`). **This leaks raw citizen PII!** - **`'H'` (Hint) & `'n'` (Constraint Name):** Guidance and exact internal constraint identifiers (e.g. `fk_orders_customer_id`). ### 2.2. Reconnaissance & Enumeration Attacks (CWE-209) Attackers submit probing payloads to discover internal table schemas, existing identifiers, and private foreign key structures. Leaking database error messages enables attackers to map database topology without authorization. ```mermaid flowchart TD subgraph VULN ["Direct Error Leakage (VULNERABLE)"] direction TB Attacker1["Attacker: POST /api/register {id: 123}"] --> App1["Go Handler: http.Error(w, err.Error(), 500)"] App1 --> Leak1["Exposes: pq: duplicate key (users_national_id_key). Detail: (3201...) in users_master"] Leak1 --> Recon["PII Leaked & Schema Mapped (CWE-209)"] end subgraph SECURE ["Sanitized Domain Error (COMPLIANT)"] direction TB Attacker2["Attacker: POST /api/register {id: 123}"] --> App2["Go Handler: epimetheus.ToAppError(err)"] App2 --> Log["Server Logs Full Error + public_tx_id"] App2 --> SafeResp["HTTP 409: {'error': 'Resource already exists', 'tx_id': 'TX-01H...'}"] SafeResp --> Safe["Zero Leaks: PII & Schema Protected (SAFE)"] end ``` ### 2.3. Legitimate Exception: SQLSTATE Code Branching (`pgErr.Code`) Applications **are explicitly permitted** to inspect standard SQLSTATE codes (e.g. `"23505"` for `unique_violation`, `"23503"` for `foreign_key_violation`) using `errors.As(err, &pgErr)` and evaluating `pgErr.Code`. This enables safe domain branching as long as the raw message or detail strings are not passed to client sinks. --- ## 3. How Argus Detects Violations (Static Analysis Architecture) Argus combines AST selector validation with response sink data flow tracking: ```mermaid flowchart LR AST["Inspect Go Functions
(Exclude _test.go)"] --> Selectors{"Access to pgErr.Detail,
pgErr.Hint, or pgErr.Where?"} Selectors -->|Yes| ReportSel["Report HIGH Violation:
Forbidden Direct PgError Access"] Selectors -->|No| Sinks{"Is Call a Response Sink?
(http.Error, json.Encode, Write)"} Sinks -->|Yes| Flow{"Does Argument Contain
err.Error() or Tainted Variable?"} Flow -->|Yes| ReportSink["Report HIGH Violation:
Database Error Leaked to Client"] Flow -->|No| Pass["Pass (Safe Response / Masked Error)"] Sinks -->|No| Pass ``` 1. **Sensitive Field Access (`error_flow.go`):** Identifies selector expressions referencing `Detail`, `Hint`, or `Where` on `*pgconn.PgError` instances. 2. **Response Sink Recognition (`sinks.go`):** - `http.Error(w, errText, code)` - `response.ErrorJSON(w, code, errText)` - `w.Write([]byte(errText))` - `json.NewEncoder(w).Encode(...)` - `fmt.Fprintf(w, "%s", errText)` 3. **Data Flow Tracing (`error_flow.go`):** Detects both direct calls (`err.Error()`) and indirect local variables (`errStr := err.Error()`) emitted into response sinks. --- ## 4. Vulnerability & Risk Taxonomy | Failure Mode | Technical Impact | Risk Severity | | :----------------------------------- | :------------------------------------------------------------------------- | :------------ | | **Direct `err.Error()` to Client** | Exposes internal database error details, table names, and query fragments. | **HIGH** | | **Access to `pgErr.Detail`** | Leaks raw data values (PII, unique key contents) directly to attackers. | **CRITICAL** | | **Access to `pgErr.Hint` / `Where`** | Discloses server-side PL/pgSQL call stacks and engine hints. | **HIGH** | | **JSON Serializer Error Leakage** | Obfuscates error leaks inside JSON maps bypass basic string audits. | **HIGH** | --- ## 5. Non-Compliant Code Patterns (Bad Examples) ### Example 1: Passing `err.Error()` to `http.Error` ```go // VIOLATION: Emitting raw error string directly to HTTP response func HandleGetUser(w http.ResponseWriter, r *http.Request) { user, err := repo.FindByID(r.Context(), r.PathValue("id")) if err != nil { // Flagged: raw err.Error() passed directly to HTTP response http.Error(w, err.Error(), http.StatusInternalServerError) return } // ... } ``` ### Example 2: Emitting `pgErr.Detail` ```go // VIOLATION: Exposing raw database detail string containing PII func HandleCreateCitizen(w http.ResponseWriter, r *http.Request) { var pgErr *pgconn.PgError if err := service.Create(r.Context(), data); errors.As(err, &pgErr) { // Flagged: forbidden direct access to pgconn.PgError.Detail response.ErrorJSON(w, http.StatusBadRequest, pgErr.Detail) return } } ``` ### Example 3: Indirect Variable Assignment ```go // VIOLATION: Storing raw error in variable and emitting to sink func HandleUpdateProfile(w http.ResponseWriter, r *http.Request) { err := repo.Update(r.Context(), profile) if err != nil { errMsg := err.Error() // Tracked by error_flow http.Error(w, errMsg, http.StatusInternalServerError) return } } ``` --- ## 6. Compliant Implementation Patterns (Good Examples) ### Solution 1: Sanitized Domain Error Mapping (Standard) ```go // COMPLIANT: Internal logging with sanitized client response func HandleGetUser(w http.ResponseWriter, r *http.Request) { user, err := repo.FindByID(r.Context(), r.PathValue("id")) if err != nil { // Secure server-side telemetry logger.Error("failed retrieving user profile", "error", err, "tx_id", txID) // Sanitized human-friendly client response response.ErrorJSON(w, http.StatusInternalServerError, "Gagal memproses data pengguna") return } json.NewEncoder(w).Encode(user) } ``` ### Solution 2: SQLSTATE Code Branching (Safe PII Handling) ```go // COMPLIANT: Inspecting SQLSTATE code without leaking message text func HandleRegisterCitizen(w http.ResponseWriter, r *http.Request) { var pgErr *pgconn.PgError if err := service.Register(r.Context(), req); errors.As(err, &pgErr) { if pgErr.Code == "23505" { // unique_violation response.ErrorJSON(w, http.StatusConflict, "Data identitas sudah terdaftar di sistem") return } response.ErrorJSON(w, http.StatusInternalServerError, "Gagal mendaftarkan data pengguna") return } } ``` --- ## 7. How to Suppress (Ignore Directives) For internal debugging harnesses or developer test endpoints: ```go // argus:ignore ARGUS-A07 internal developer diagnostic endpoint http.Error(w, err.Error(), http.StatusInternalServerError) ``` Alternatively, use the identifier alias: ```go // argus:ignore ERROR_LEAK verified internal debug log exporter response.ErrorJSON(w, http.StatusInternalServerError, err.Error()) ``` --- ## 8. Configuration Reference (`.argus.yaml`) Enable or configure this rule in `.argus.yaml`: ```yaml rules: ARGUS-A07: enabled: true ```