-
Notifications
You must be signed in to change notification settings - Fork 0
ARGUS A07
Rule Code:
ARGUS-A07Identifier:ERROR_LEAKSeverity:HIGH(Information Disclosure & Reconnaissance Blocker) Category:Security & Data IntegrityTarget 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
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.
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 existornull 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).
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.
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
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.
Argus combines AST selector validation with response sink data flow tracking:
flowchart LR
AST["Inspect Go Functions<br/>(Exclude _test.go)"] --> Selectors{"Access to pgErr.Detail,<br/>pgErr.Hint, or pgErr.Where?"}
Selectors -->|Yes| ReportSel["Report HIGH Violation:<br/>Forbidden Direct PgError Access"]
Selectors -->|No| Sinks{"Is Call a Response Sink?<br/>(http.Error, json.Encode, Write)"}
Sinks -->|Yes| Flow{"Does Argument Contain<br/>err.Error() or Tainted Variable?"}
Flow -->|Yes| ReportSink["Report HIGH Violation:<br/>Database Error Leaked to Client"]
Flow -->|No| Pass["Pass (Safe Response / Masked Error)"]
Sinks -->|No| Pass
-
Sensitive Field Access (
error_flow.go): Identifies selector expressions referencingDetail,Hint, orWhereon*pgconn.PgErrorinstances. -
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)
-
Data Flow Tracing (
error_flow.go): Detects both direct calls (err.Error()) and indirect local variables (errStr := err.Error()) emitted into response sinks.
| 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 |
// 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
}
// ...
}// 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
}
}// 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
}
}// 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)
}// 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
}
}For internal debugging harnesses or developer test endpoints:
// argus:ignore ARGUS-A07 internal developer diagnostic endpoint
http.Error(w, err.Error(), http.StatusInternalServerError)Alternatively, use the identifier alias:
// argus:ignore ERROR_LEAK verified internal debug log exporter
response.ErrorJSON(w, http.StatusInternalServerError, err.Error())Enable or configure this rule in .argus.yaml:
rules:
ARGUS-A07:
enabled: true