Skip to content

feat(protect): add support for deeply nested protect schemas#155

Merged
calvinbrewer merged 6 commits intomainfrom
nested-encryption
Jun 11, 2025
Merged

feat(protect): add support for deeply nested protect schemas#155
calvinbrewer merged 6 commits intomainfrom
nested-encryption

Conversation

@calvinbrewer
Copy link
Contributor

@calvinbrewer calvinbrewer commented Jun 9, 2025

Added support for nested objects in Protect schemas, allowing you to encrypt and search on nested properties through the model interfaces. E.g.

import { csTable, csColumn, csValue } from "@cipherstash/protect";

export const protectedUsers = csTable("users", {
  email: csColumn("email").freeTextSearch().equality().orderAndRange(),
  profile: {
    name: csValue("profile.name"),
    address: {
      street: csValue("profile.address.street"),
      location: {
        coordinates: csValue("profile.address.location.coordinates"),
      },
    },
  },
});

When working with nested objects:

  • Each level can have its own encrypted fields
  • The maximum nesting depth is 3 levels
  • Null and undefined values are supported at any level
  • Optional nested objects are supported

@calvinbrewer
Copy link
Contributor Author

The encrypt config will look like the following and the i -> column field in the EQL payload will have the dot notation as the column name to continue protection against confused deputy attacks. @CDThomas Is this the correct way to handle this?

{
  v: 2,
  tables: {
    users: {
      email: [Object],
      address: [Object],
      'example.field': [Object],
      'example.nested.deeper': [Object]
    }
  }
}

@calvinbrewer calvinbrewer requested a review from CDThomas June 9, 2025 22:35
Copy link
Contributor

@CDThomas CDThomas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left a few minor comments, but LGTM otherwise.

Protect.js supports nested objects in your schema, allowing you to encrypt **but not search on** nested properties. You can define nested objects up to 3 levels deep.
This is useful for data stores that have less structured data, like NoSQL databases.

You can define nested objects by using the `csValue` function to define a value in a nested object. The value naming convention of the `csValue` function is a dot-separated string of the nested object path, e.g. `profile.name` or `profile.address.street`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The column/attribute name in the DB table can't contain a . since we're using it a delimiter, right? Definitely an edge case, but thought it was worth mentioning.

@CDThomas
Copy link
Contributor

The encrypt config will look like the following and the i -> column field in the EQL payload will have the dot notation as the column name to continue protection against confused deputy attacks. @CDThomas Is this the correct way to handle this?

{
  v: 2,
  tables: {
    users: {
      email: [Object],
      address: [Object],
      'example.field': [Object],
      'example.nested.deeper': [Object]
    }
  }
}

This makes sense to me @calvinbrewer. I think the main thing we want to do on the client at the moment is to only decrypt the specific fields that have been configured as encrypted (as opposed to blindly decrypting anything that looks like a ciphertext).

@calvinbrewer calvinbrewer merged commit c0cf180 into main Jun 11, 2025
1 check passed
@calvinbrewer calvinbrewer deleted the nested-encryption branch June 11, 2025 19:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants