Biscuits

Biscuits are a self-describing authorization key for SXT resources.

Biscuits are a cryptographically secure decentralized authorization key that allows the holder of a table's private key to define and distribute authorizations to anyone, in a way that is both safe and organizationally manageable.

You can think of biscuits as a "self-describing authorization key" that grant specific access to a Space and Time table or view.

How Biscuits Work

Biscuits are created locally (client) with the table's Private key, and verified on the network (server) with the table's Public key.

The process of creating a biscuit looks like:

  1. Create an ED25519 public/private keypair

  2. Create the desired table, with the Public key from #1 added to the WITH statement (required)

  3. Submit the final CREATE TABLE... statement to the Space and Time network to be created, again including the Public key.

  4. With the same Private key from #1, create a biscuit with the appropriate constraints and authorizations.

  5. The user submits a query to the network, and includes a biscuit.

  6. The table verifies the biscuit instructions, filters and capabilities using it's Public key, which must match the corresponding Private key from step #4

📘

The ED25519 keypair created for a table and biscuit are encoded in HEX, whereas the ED25519 keypair created for Users are encoded as BASE64. This is done to reinforce the fact that table keypairs and user keypairs are completely seperate. Also, HEX is SQL-Safe, whereas Base64 is not.


How to Define a Biscuit

To create a biscuit, you need a "datalog" - text that defines the authorization granted and to whom, in terms of filter "checks" and "capabilities." This datalog text is then signed by the private key that matches the table's public key.

Capabilities

Capabilities are always structured as:sxt:capability("<operation>", "<resource>");

Operations are: dql_select, dml_insert, dml_update, dml_delete, ddl_create, ddl_drop

Resources are objects in the Space and Time network, most commonly Tables and Views. It is also possible to use the wildcard character * to represent ALL capabilities and/or resources (as long as the resource's public key matches).

A few examples:

sxt:capability("dql_select", "mySchema.myTable");  
sxt:capability("dml_insert", "mySchema.myTable");  
sxt:capability("dml_update", "mySchema.myTable");  
sxt:capability("dml_delete", "mySchema.myTable");
sxt:capability("*", "mySchema.myTable");
sxt:capability("dql_select", "*");
sxt:capability("*", "*");

"Wildcard" biscuits are fine for development, but should rarely (if ever) be used in production.

Checks

Checks are structured like IF statements, and allow standard operations like equality, inequality, greater than, less than, etc. Currently you can write checks on:

  • Timestamp
  • UserID logged in
  • Subscription associated with the UserID

A few examples:

check if sxt:user("MyuserId");   
check if sxt:subscription("<Users_Subcription>");
check if time($time), $time <= 2024-07-01T12:00:00Z; 

Each line is evaluated alone, meaning they behave like AND operations. If you would like an OR operation (i.e., UserABC OR Subscription123) add them together on one line:

check if sxt:user("Chuck") or sxt:subscription("abc123_example_subscription");  
check if time($time), $time <= 2024-07-01T13:00:00Z or $time >= 2024-07-01T12:00:00Z;

📘

Time format must be ISO8601 compliant, as seen above.

A full combined biscuit might look something like:

sxt:capability("dql_select", "mySchema.myTable");  
sxt:capability("dml_insert", "mySchema.myTable");  
check if sxt:user("Chuck") or sxt:subscription("abc123_example_subscription");  
check if time($time), $time <= 2025-07-01T12:00:00Z; 

Once you have the datalog (text above) and table's private key, you can put them together into a biscuit!

The SXT CLI does much of this work for you, and is a far easier approach. To see biscuit creation in action, check out this recipe!


How to Create a Biscuit with the CLI

The easiest way to create new biscuits is to use the SXT CLI. You can create a biscuit for a resource (table or view) before or after creating the resource - it doesn't matter. You only need the private key and one capability to create a biscuit.

Confirm prerequisites: SXTCLI 0.0.6 or higher is installed and working, and you have loaded your table's public and private keys into environment variables RESOURCE_PUBLIC_KEY and RESOURCE_PRIVATE_KEY.

To test:

# display character length of private key, and full public key:
Demo~$ echo -n $RESOURCE_PRIVATE_KEY | wc -m
      64

Demo~$ echo $RESOURCE_PUBLIC_KEY
5CA5D53A2F58227676063C8187CEB171962B86909CF59205CBDCEEE98E2BDB69


# display version of sxtcli, and help menu:
Demo~$ sxtcli version
Version: 0.0.6


Demo~$ sxtcli help
Usage: <main class> [COMMAND]
Commands:
  help          Display help information about the specified command.
  authenticate  Perform platform authentication commands.
  biscuit       Perform biscuit commands.
  discover      Perform resource discovery commands.
  sql           Perform SQL commands.
  sql-support   Perform supporting SQL commands.
  subscription  Perform subscription commands.
  activity      Perform activity retrieval commands.
  version       Displays the version information

Build the biscuit: for the example biscuit below, we'll assume:

  • the table: mySchema.myTable
  • granting: select and insert permissions only
  • restricted only to: UserID Alice in the SubscriptionID abc123456789def
  • expiring end of year, 2024

This set of criteria would look like:

sxtcli biscuit generate table \
  --privateKey=$RESOURCE_PRIVATE_KEY \
  --resources="mySchema.myTable" \
  --operations=SELECT,INSERT \
  --users="Alice" \
  --subscriptions="abc123456789def" \
  --expires="2024-12-31T23:59:59Z"

The above SXTCLI command will create the biscuit, and print the full list of capabilities and checks, as well as the biscuit key at the bottom. Given the above sample input, the expected output would be:

Biscuit content:
 Facts:
  sxt:capability("dml_insert","myschema.mytable");
  sxt:capability("dql_select","myschema.mytable");
 Checks:
  check if sxt:user("Alice");
  check if sxt:subscription("abc123456789def");
  check if time($time), $time <= "2024-12-31T23:59:59Z";
Biscuit:
EtwCCvEBCg5zeHQ...

To confirm that the biscuit key contains the correct information, you can visit BiscuitSec.org and paste the above Biscuit key into the "Encoded token" textbox on the main page, and it will display the contents:

📘

It is possible to create one biscuit that is good for many tables, but requires all tables be created with the same public key, so they can all be verify by the biscuit created with the same private key.


Best Practices for Biscuits

  • Treat biscuits like the sensitive keys they are! Do not let them get out into the wild, there is no way to revoke, except to recreate your table with a brand new keypair.
  • Always add a check if sxt:subscription("<Your_Subcription>"); so that the biscuit will only work if the user belongs to your SxT subscription.
    • Allows admins to quickly remove biscuit access by removing the UserID from the SxT subscription. Thus, biscuits will self-adjust for new hires and terminations.
    • It also reduces risk if the biscuit is accidently lost / picked up by a malicious actor - it will only work for them if they are ALSO in your SxT organization.
  • Force key rotation by setting expiration dates on all biscuits.
  • It is also possible to create ephemerial biscuits - i.e., if you have an automated process that has access to the table's private key, it can create a biscuit with an expiration that aligns with the ACCESS_TOKEN timeout, and only for the current expected operations. i.e., unlike the private key, biscuits can be created JIT and need not be persistent.

See this page for a deep dive on biscuits.