npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

jupyter-trust

v1.0.0

Published

A utility for managing Jupyter Notebook trust in Node.js

Downloads

58

Readme

jupyter-trust

CI npm version

A utility for managing Jupyter Notebook trust in Node.js.

This package is available as both a CommonJS and ESM module.

Basic Example

import { JupyterTrust, create, check, sign, unsign } from 'jupyter-trust'

// using an instance
const trust = new JupyterTrust(database, secret)
const trust = await create() // or JupyterTrust.create(...)

await trust.sign('path/to/notebook.ipynb')
await trust.check(parsedNotebookObject)

// using the static methods
await sign('path/to/notebook.ipynb') // or JupyterTrust.sign(...)
await check(parseNotebookObject)
await unsign(parseNotebookObject)
await check('path/to/notebook.ipynb')

Reference

Exports

jupyter-trust

jupyter-trust/utils


JupyterTrust

The JupyterTrust class is used to manage the trust and signing of notebooks.

There are static methods on the class which can be used to create a temporary instance of JupyterTrust and run the corresponding method. If you are handling multiple operations, it is better to create an instance either with the constructor or the create method. All the static methods are also exported as named exports of jupyter-trust.

By default, all methods use your user level Jupyter trust database which is used by other apps like Jupyter Notebook and Jupyter Lab to store the trust of notebooks.

[!IMPORTANT] For all methods which take in notebook data, note that all numbers are treated like python floats, while bigint is treated like python's int. if you are parsing a notebook, you will need to parse all integers to BigInt, which you can do with the parse() utility. This affects how the numbers are serialized and therefore its signature.

class JupyterTrust {
    constructor(database: string, secret: string, algorithm?: string, opts?: SignatureStoreOptions)
}

Refer to JupyterTrustOptions and SignatureStoreOptions for more details on the parameters of the constructor.


create

static create(opts?: JupyterTrustOptions): Promise<JupyterTrust>

Convenience method to create a JupyterTrust instance, including using and loading all defaults. See JupyterTrustOptions for more information.


check

check(notebook: string | object): Promise<boolean>
static check(notebook: string | object, opts?: JupyterTrustOptions): Promise<boolean>

Checks if a notebook is trusted. Accepts either the path to the ipynb file or the notebook data.

This will also update the last seen timestamp of the signature (unless the store is readonly).

Returns true if notebook is trusted, or false if it is not signed.


filter

filter<T extends string | object>(notebooks: T[]): Promise<T[]>
filter<T>(objs: T[], accessor: FilterAccessor<T>): Promise<T[]>
static filter<T extends string | object>(notebooks: T[], opts?: JupyterTrustOptions): Promise<T[]>
static filter<T>(objs: T[], accessor: FilterAccessor<T>, opts?: JupyterTrustOptions): Promise<T[]>

type FilterAccessor<T> = (obj: T) => string | object | Promise<string | object>

Given a list of notebooks, this will return the ones which are trusted. Optionally, an accessor function can be passed which is used to access the notebook from some wrapper object (or however it can get a notebook), in which case the wrapper objects will be returned instead.

Like with the other methods, a string will be treated as the file path, while an object is treated as the notebook data.

import { glob } from 'glob'
import { filter } from 'jupyter-trust'

await filter(await glob('src/**/*.ipynb')) // string[]
await filter(await glob('src/**/*.ipynb', { withFileTypes: true }), (path) => path.fullpath()) // Path[]

sign

function sign(notebook: string | object): Promise<boolean>

Sign a notebook as trusted. Accepts either the path to the ipynb file or the notebook data.

Returns true if notebook was signed, or false if it has already been signed.


unsign

unsign(notebook: string | object): Promise<boolean>
static unsign(notebook: string | object, opts?: JupyterTrustOptions): Promise<boolean>

Unsign a notebook. Accepts either the path to the ipynb file or the notebook data.

Returns true if notebook was unsigned, or false if it was not previously signed.


digest

digest(nb: object): string
static digest(nb: object, options?: JupyterTrustOptions): Promise<string>
static digest(nb: object, options?: JupyterTrustOptions & { secret: string }): string

Used to manually compute the signature of a notebook. This only accepts the loaded notebook data as an object.

If no secret is provided in options for the static method, a Promise will be returned. The database field in JupyterTrustOptions is unused.

This generates a HMAC digest of the notebook using the secret and algorithm. You can recreate the behaviour of this function with help from serialize() and omitSignature() in jupyter-trust/utils

import { createHmac } from 'node:crypto'

import { omitSignature, serialize } from 'jupyter-trust/utils'

const hmac = createHmac('sha256', '<SECRET>')
for (const data of serialize(omitSignature(nb))) {
    hmac.update(data)
}
hmac.digest('hex')

There is also a generateSecret() utility.


readonly store: SignatureStore

The underlying SignatureStore used to manage signatures.


async close(): Promise<void>

Close the underlying store.


JupyterTrustOptions

interface JupyterTrustOptions extends SignatureStoreOptions
  • database: string (optional)

    The path to the SQLite database.

    The default database locations are:

    ~/.local/share/jupyter/nbsignatures.db  # Linux
    ~/Library/Jupyter/nbsignatures.db       # OS X
    %APPDATA%/jupyter/nbsignatures.db       # Windows

    If the default does not work, try running jupyter --paths and look for a nbsignatures.db file in the listed data paths. (Not all the paths listed by jupyter --paths may necessarily exist.)

  • secret: string (optional)

    The secret used to sign notebooks. By default, Jupyter uses a 1024 byte secret encoded as base64 stored in a notebook_secret file in the same directory as the database. You can specify this directory with the dataDir option to load the secret instead.

  • dataDir: string (optional)

    The data directory containing the database and secret.

    If the default does not work, try running jupyter --paths and look for a directory in the listed data paths which has the nbsignatures.db and notebook_secret files. (Not all the paths listed by jupyter --paths may necessarily exist.)

  • algorithm: string (optional)

    The algorithm used to sign notebooks. The default is sha256. Any algorithm which is supported by node:crypto should work.

All the options in SignatureStoreOptions can also be passed in.


SignatureStore

Class to interface more directly with the database storing notebook signatures.

class SignatureStore {
    constructor(filename: string, options?: SignatureStoreOptions)
}

async ready(): Promise<void>

Optionally wait for the database to be ready. If the database fails to open, it will throw.


async close(): Promise<void>

Close the database.


static async init(db: sqlite3.Database): Promise<boolean>

Utility function to initialize the database table and index. Returns true if it did anything. This is called automatically by SignatureStore when opening the database. This should only be necessary if you are directly working with the node sqlite3 library.


async check(signature: string, algorithm: string): Promise<boolean>

Checks if the signature with the specified algorithm exists in the database. If found (and the store is not readonly), this will update the last seen value of the signature.


async store(signature: string, algorithm: string): Promise<boolean>

Store a signature and its algorithm in the database. Returns true if a new signature was stored, or false if it was already in the database.


async remove(signature: string, algorithm: string): Promise<boolean>

Remove a signature from the database. Returns true if the signature was removed, or false if it was not in the database.


async cull(): Promise<boolean>

Removes the oldest signatures from the database until the size of the database is 75% of the cacheSize option.

Returns true if signatures were removed, false otherwise.


async count(): Promise<number>

Returns the number of signatures stored in the database.


SignatureStoreOptions

interface SignatureStoreOptions
  • readonly: boolean (default false)

    Disable any writing to the database. This will cause an error on any write operations.

  • create: boolean (default false)

    Create the database file if it does not exist. Note that as long as the database is not readonly, it will try to initialize the database even if create is false.

  • cull: boolean (default true)

    Whether to automatically run cull() on the store whenever the number of signatures exceeds the cacheSize.

  • cacheSize: number (default 65535)

    The size limit of the database used when culling the database.


parse

parse(text: string): any

Parse a JSON string. This parses all integer strings to BigInt to match the bahaviour of numbers in Python. In effect, the Javascript number type is used to represent the Python float, while bigint represents int. This also allows parsing large integer values without any loss of precision.

1234 // bigint
1234.0 // number
1.234e3 // number
1e3 // number

serialize

serialize(obj: unknown): Generator<string>

Generator function used in generating the notebook's signature. The generator yields the string representation of a single object key or (primitive) value at a time. The serialization of primitive values should exactly match the str() function in Python. This treats all numbers like Python floats and bigint like int. The function is intended to be called on objects which are parsed from JSON using parse().

Object keys are sorted to ensure the output is stable.

For example,

serialize({
    key1: [21, 'str'],
    other: 0.00001,
    int: 1n,
})

will yield the following

int
1
key1
21.0
str
other
1e-5

omitSignature

omitSignature(obj: unknown): unknown

Returns a copy of the input object without the metadata.signature property. The object is only copied if it is valid and has a metadata object with a signature property.


generateSecret

generateSecret(): Promise<string>

Asynchronously enerates a secure 1024 byte secret using node:crypto which can be used to sign notebooks. The result is encoded as base64.

If you need a synchronous version, you can also use randomBytes from node:crypto.

import { randomBytes } from 'node:crypto'

randomBytes(1024).toString('base64')

defaultDataDir

defaultDataDir(): string

Returns the default data directory used by Jupyter. If the environment variable JUPYTER_DATA_DIR exists it will be used. Otherwise the result depends on your operating system.


defaultSecret

defaultSecret(dataDir?: string, refresh?: boolean): Promise<string>

Returns the default secret used for signing notebooks.

This reads the notebook_secret file in dataDir (defaults to defaultDataDir()). If no dataDir is specified, it may return a cached value. Set refresh to true to force it to re-read the secret file.


Development

After cloning the repository, run:

npm install

For the tests to work, you will need to setup python and run:

pip install -r requirements.txt

Building:

npm run build

Testing:

npm run lint
npm run format:check
npm test
npm run coverage # run testing with coverage

Release:

npx changeset

Versioning and releasing are handled with changesets.