Nisaba runs on an operator's own machine and holds credentials for live selling accounts. A vulnerability here is not abstract: it can read marketplace OAuth tokens, write to real inventory, or expose a company's catalog to a peer that should not have it. Please report anything you find.
Nisaba has not cut a tagged release yet. Only the master branch is supported;
fixes land there.
| Version | Supported |
|---|---|
master |
✅ |
| Anything older | ❌ |
Do not open a public issue for a security problem.
Use GitHub's private vulnerability reporting on this repository: go to the Security tab → Report a vulnerability. That opens a private advisory visible only to you and the maintainers.
Please include:
- what an attacker can do, and what they need first (local access? a malicious vendor plugin? a peer they control? a compromised marketplace response?)
- the affected crate or file
- reproduction steps, or a proof-of-concept plugin/payload
- the commit you tested against
You should get an acknowledgement within a week. Please give us a chance to ship a fix before disclosing publicly.
The parts of Nisaba that carry real security weight:
- Credential handling. Marketplace OAuth tokens and refresh tokens, the
Squarespace API key, XMR Bazaar session cookies, the P2P company secret, and
any vendor-plugin config field marked
secret: truebelong in the OS keyring. Anything that writes one of those toconfig.toml, to a log line, to an export, or into an error message shown in the UI is a vulnerability. - The vendor plugin sandbox (
crates/vendor-runtime). Vendor plugins are third-party TypeScript executed in an embedded Deno runtime, and theop_nisaba_*ops are the whole security boundary. A plugin reaching the filesystem, spawning a process, or making network requests outsideop_nisaba_fetchis a sandbox escape — report it.
The vendor plugin surface frozen for 1.0. Every plugin's metadata must declare
api_version: 1; at install the host refuses any plugin whose API major it does
not support, naming both versions so the user knows a plugin build made for
this host's API is what is needed. Bumping the major is a breaking change by
design — it is what keeps a v2 plugin from silently misbehaving on a v1 host.
The frozen v1 surface is:
- Metadata:
name,version,api_version,description,config_fields(each withkey,label,required,secret,placeholder),category,icon,allowed_hosts. - Entry points: an exported
metadataobject literal (read before any run, with no network access) and an exported asyncfetchListings(config, { since, cursor }).sinceis the UTC timestamp of the last successful sync (nullon the first run) andcursorthe opaque cursor the plugin returned last time. It returns either the whole catalog asVendorListing[](plugins that ignore the second argument keep these full-pull semantics, and the host replaces the cached catalog) or a{ changes, deleted, cursor }delta, which the host merges into the cached catalog:changesare upserted byvendor_item_id,deletedids are removed, untouched rows keep their storedfetched_at. The host records the returned cursor for the next run; a re-install starts from a full pull. - The
Nisabahost object:fetch(only toallowed_hosts; redirects are followed only to listed hosts),sleep(the only way to wait — there are no timers),emitBatch(progress batches; the arrayfetchListingsreturns is the result),store(get/set/delete: a per-plugin key–value store for cursors, ETags and last-seen timestamps — 1 MiB total per plugin, keys at most 256 bytes, string values only; scoped to plugin + company in the company database and cleared on uninstall), andlog(trace/debug/info/warn/error;console.*writes there too). VendorListing:vendor_item_id,title,price(exact decimal text, e.g."19.99"— a JSON number is still accepted from pre-freeze plugins),currency(required whenever a price is present),quantity(null= unknown stock,0= out of stock — never coerce one into the other),sku,images(string[], most representative first; the pre-freeze singleimage_urlis still accepted and folded intoimages[0]),url,upc,brand,mpn,weight({ value, unit }),dimensions({ length, width, height, unit }),extras(free-form),group_key,variant_attributes. Onlyvendor_item_idandtitleare required.- Sandbox limits per run: 30 minutes wall-clock, a 1 GiB V8 heap, 256 MiB
of listing output, 64 MiB per
fetchresponse body.
The same surface is written down in plugins/template/index.ts, the starting
point for third-party plugins.
- The P2P layer (
crates/p2p). Payloads between installs are AES-GCM encrypted and carried over a Tor onion service. Anything that lets an unauthorized peer read or write a company's catalog, or that weakens the encryption or the onion service's addressing, counts. - Platform adapters. Marketplace responses are untrusted input. A crafted API response or scraped HTML page that causes Nisaba to write the wrong quantity or price to a different listing is a vulnerability, not a bug — it costs the operator real stock.
- An operator choosing to run a malicious vendor plugin and granting it hosts explicitly. Plugins are third-party code by design; the sandbox is the boundary, and the trust decision at install time is the operator's.
- Findings that require an attacker who already has the operator's OS user account. Nisaba trusts the local user and the OS keyring.
- Rate limits, quotas, or terms-of-service issues with a marketplace API.
If you are contributing:
- never commit a
config.toml, a*.db, an OAuth token, or a.harcapture —.gitignorecovers these, keep it that way - never add a real credential to a test fixture, even an expired one
- never log a token, a cookie, or the company secret, at any
tracinglevel