HIGH pii leakageecho godynamodb

Pii Leakage in Echo Go with Dynamodb

Pii Leakage in Echo Go with Dynamodb — how this specific combination creates or exposes the vulnerability

When building HTTP APIs in Go using the Echo framework and persisting data in DynamoDB, PII leakage commonly arises from a mismatch between application intent and data access patterns. Echo routes often deserialize JSON requests into Go structs that include fields for sensitive attributes such as email, phone, or national ID. If these structs are used directly as DynamoDB attribute values or keys without selective projection, sensitive fields can be written in full to the table. DynamoDB’s eventual-consistency model and flexible schema amplify this risk: conditional writes or batch operations may inadvertently expose PII across indexes or through unexpected item merges.

DynamoDB-specific issues surface when queries or scans retrieve more attributes than necessary. For example, using Scan without a filter or projection expression can return every attribute, including PII, to the application. In an Echo handler, if the response serializer does not explicitly omit sensitive fields, those fields can be included in API responses. Another vector is the use of DynamoDB Streams with Lambda integrations; if the consumer logs or mishandles stream records, PII can be exposed in logs or downstream systems. Weak access patterns in DynamoDB—such as relying on a Global Secondary Index (GSI) that includes PII in its key schema—can also surface sensitive data through query results that are cached or retained longer than needed.

Consider an Echo route that updates a user profile and writes to DynamoDB:

// Echo handler that writes request body directly to DynamoDB (risky)
c.Bind(&user)
av, err := attributevalue.MarshalMap(user)
if err != nil {
    c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
    return
}
input := &dynamodb.PutItemInput{
    TableName: aws.String("users"),
    Item:      av,
}
_, err = svc.PutItem(context.TODO(), input)
if err != nil {
    c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
    return
}

If the User struct contains fields like Email or SSN, and the application does not strip or encrypt them, the entire item is stored with PII. Later, a query or scan that returns ALL_ATTRIBUTES can expose these fields to any caller with read access, intentionally or unintentionally. Echo’s flexible parameter binding can also cause PII to be reflected in logs or error messages when binding fails, especially if request payloads include sensitive data and the framework’s debug mode is enabled.

SSR and misconfigured indexes compound these issues. A GSI that includes PII in its partition or sort key can propagate sensitive data into index queries. Without strict attribute selection on reads (e.g., using ProjectionExpression), consumers may retrieve full items or sensitive GSI keys. In multi-tenant scenarios, incorrect key design can allow one tenant’s PII to be accessible to another through weakly isolated queries, a pattern that aligns with BOLA/IDOR findings that middleBrick checks across DynamoDB workflows.

Dynamodb-Specific Remediation in Echo Go — concrete code fixes

Remediation centers on minimizing the data footprint stored in DynamoDB and controlling what is returned to Echo responses. Use selective field structures for persistence, avoid storing raw request bodies directly, and always limit returned attributes with projection expressions.

  • Separate persistence models from API models: define a UserInput for binding and a UserRecord for DynamoDB that excludes PII unless explicitly required.
  • Use attributevalue.MarshalMap only on filtered structs and prefer av.MarshalListOfMaps for partial updates.
  • Always specify ProjectionExpression on queries and scans, and use FilterExpression only for post-retrieval filtering (not security).
  • Encrypt PII at rest by storing only tokens or encrypted blobs; manage keys outside DynamoDB.
  • Audit index key schemas to ensure PII is not included in GSIs unless strictly necessary.

Secure Echo handler example with DynamoDB:

// Define request-specific model (bind only what you need)
type UserInput struct {
    UserID string `json:"userId" validate:"required"`
    Name   string `json:"name" validate:"required"`
    Email  string `json:"email" validate:"required,email"`
}

// Define a DynamoDB-specific model that omits or encrypts PII
type UserRecord struct {
    PK       string `json:"pk"`
    SK       string `json:"sk"`
    Name     string `json:"name"`
    EmailTok string `json:"email_tok"` // tokenized or encrypted
    Created  string `json:"created"`
}

// Echo handler: bind to input model, map to record, write with limited attributes
func UpdateProfile(c echo.Context) error {
    var in UserInput
    if err := c.Bind(&in); err != nil {
        return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid payload"})
    }
    // Transform to DynamoDB model (apply business rules, tokenization, encryption here)
    rec := UserRecord{
        PK:       "USER#" + in.UserID,
        SK:       "PROFILE",
        Name:     in.Name,
        EmailTok: tokenize(in.Email), // placeholder for your tokenization/encryption
        Created:  time.Now().UTC().Format(time.RFC3339),
    }
    av, err := attributevalue.MarshalMap(rec)
    if err != nil {
        return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
    }
    input := &dynamodb.PutItemInput{
        TableName:                 aws.String("users"),
        Item:                      av,
        ConditionExpression:       aws.String("attribute_not_exists(PK)"),
        ReturnConsumedCapacity:    aws.String("NONE"),
    }
    _, err = svc.PutItem(context.TODO(), input)
    if err != nil {
        return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
    }
    return c.NoContent(http.StatusOK)
}

Query with controlled attribute retrieval:

out, err := svc.Query(context.TODO(), &dynamodb.QueryInput{
    TableName:              aws.String("users"),
    KeyConditionExpression: aws.String("pk = :pk"),
    ExpressionAttributeValues: map[string]types.AttributeValue{
        ":pk": &types.AttributeValueMemberS{Value: "USER#123"},
    },
    ProjectionExpression: aws.String("pk, sk, name, email_tok"), // limit returned fields
})
if err != nil {
    return err
}
var items []UserRecord
if err := attributevalue.UnmarshalListOfMaps(out.Items, &items); err != nil {
    return err
}

For scans that must traverse the table, avoid returning PII by specifying ProjectionExpression and filtering on the server side where possible. If PII must be stored, ensure it is encrypted before writing and decrypted only in secure contexts, and avoid logging raw responses in Echo middleware or error handlers.

middleBrick scans can surface PII exposure by checking whether responses include sensitive attributes without projection, whether GSIs include PII in keys, and whether unauthenticated endpoints return items containing PII. These findings map to OWASP API Top 10 and compliance frameworks to guide remediation.

Related CWEs: dataExposure

CWE IDNameSeverity
CWE-200Exposure of Sensitive Information HIGH
CWE-209Error Information Disclosure MEDIUM
CWE-213Exposure of Sensitive Information Due to Incompatible Policies HIGH
CWE-215Insertion of Sensitive Information Into Debugging Code MEDIUM
CWE-312Cleartext Storage of Sensitive Information HIGH
CWE-359Exposure of Private Personal Information (PII) HIGH
CWE-522Insufficiently Protected Credentials CRITICAL
CWE-532Insertion of Sensitive Information into Log File MEDIUM
CWE-538Insertion of Sensitive Information into Externally-Accessible File HIGH
CWE-540Inclusion of Sensitive Information in Source Code HIGH

Frequently Asked Questions

How does Echo Go handle request binding that might inadvertently capture PII?
Echo binds JSON payloads into Go structs. If sensitive fields are present in the struct and are written to DynamoDB without filtering, PII can be stored and later exposed. Mitigate by using separate input structs that exclude or tokenize PII and by avoiding direct passthrough to persistence models.
Can middleBrick detect PII leakage in DynamoDB-backed APIs?
Yes. middleBrick runs checks across API behavior and spec-to-runtime mappings to identify whether responses or queries expose PII, whether indexes include sensitive attributes, and whether unauthenticated paths return items with sensitive data, providing prioritized remediation guidance.