Settings

Your files stay with you

Nothing you open or make in OBSCURA OS is kept by this site: the tools run in your browser and save to your own device. If the site went down tomorrow, or stopped for good, no file of yours would go with it, because none of them is here. There is nothing to export from us and nothing to get back.

What you do need to think about is the work kept inside your browser rather than in a file: drafts the Workspace keeps as you type, documents the applications keep on the device with their versions, and projects kept in the browser's own storage. A browser short of disk space can clear a site's storage, and clearing the site's data deletes it. Save to a file, or to a folder on your disk, for anything you mean to keep.

The free tools

The Workspace and the single tools are ordinary web pages. Once opened they work with no connection, and installed as an app they open offline too. They save Word, Excel, PowerPoint, PDF, Markdown, CSV and the other formats listed on file formats, which other programs open.

The formats, read from the code

Each note names the file in the code that writes the format, and the build checks the note against it: if the writer changes what it puts in the file, the build stops until the note says so. The checks also run the writers that run outside a browser and read back what they write.

OBSCURA Case: .obscase

What it is
A zip.
Standard inside
Zip; JSON and JSON Lines (one JSON record a line); plain UTF-8 text; each original file exactly as it was loaded; SHA-256.
Its own
The names of the fields in the JSON, which are Case's. Every case file carries a README.txt that explains them.
InsideWhat it holds
README.txtWhat every entry is, in plain words.
case.jsonThe case: its name, matter, custodians, tags, privilege names and domains, chronology and exhibits.
documents.jsonlOne record a document: its control number, family, custodian, metadata and hashes.
coding.jsonlThe tags and note each coded document was given, by whom and when.
batches.json, redactions.jsonlThe review batches, and each redaction as boxes on a page or ranges of text.
audit.jsonlThe audit trail, one entry a line, each holding the SHA-256 of the one before it.
text/The text taken from each document, named by control number.
natives/Each original file once, named by its SHA-256.
manifest.json, manifest.sha256Every entry with its size and SHA-256, and the manifest's own SHA-256.

Reading it without OBSCURA. Unzip it. sha256sum -c manifest.sha256 checks the manifest, and each entry's hash in manifest.json checks the rest. The originals in natives/ open in whatever opened them before. A production Case makes for the other side is also a zip, with standard Concordance (.dat) and Opticon (.opt) load files, TIFF or JPEG pages, text, the originals, and its own manifest.

Written by pro-code/case-share.js; the build checks this note against it.

The Photo Editor: .obphoto

What it is
A zip, with a different name.
Standard inside
Zip; JSON; every layer's pixels as a PNG, at full size; lookup tables as the .cube text they came in as.
Its own
How the layers are put together: blend modes (named as the browser's canvas names them, such as source-over and multiply), adjustment layers, effects and editable text are described in document.json in the editor's own terms. A program that does not know them gets every layer's pixels, and has to stack them itself.
InsideWhat it holds
document.jsonThe picture's size and resolution, and each layer bottom to top: its name, position, opacity, blend mode, mask, and the file holding its pixels.
layers/1.png, layers/2.png, …Each layer's pixels, in the order document.json lists them; 1 is the bottom.
masks/, smart/, frames/A layer's mask, a smart object's source, a frame's picture, numbered like the layer they belong to.
patterns/, alphas/, luts/, selection.pngPatterns, saved selections and lookup tables the picture uses, and the selection at the time it was saved.

Reading it without OBSCURA. Rename it to .zip, or open it with any zip program. Each layer is an ordinary PNG; stacked bottom to top at the position document.json gives, with its opacity, they make the picture. To keep a picture any program opens, export a PNG, JPEG, WebP, TIFF or PSD from the editor as well.

Written by pro-code/photo-editor-file.js; the build checks this note against it.

Foundry (the Engineering Suite's 2D drawings): .foundry

What it is
One JSON text file.
Standard inside
JSON in UTF-8. Pictures traced over are inside it as data: URLs.
Its own
The drawing's shapes are Foundry's own records: lines, polylines, arcs, circles, splines, text, dimensions, hatches, blocks, sheets and viewports, with points as numbers in the drawing's units. Nothing but Foundry draws them from this file.
InsideWhat it holds
formatAlways obscura-foundry.
drawingThe drawing: its name, units, precision, layers (name, colour, line type and weight), and ents, every shape with its type, layer and points; its blocks and sheets.
assetsThe pictures the drawing uses, by id.

Reading it without OBSCURA. Any text editor or JSON reader opens it. To keep a drawing any CAD program opens, export DXF (AutoCAD's exchange format, version 2000) from Foundry as well, or PDF and SVG for a picture of it.

Written by pro-code/foundry-io.js; the build checks this note against it.

The Engineering Suite (3D parts and assemblies): .json (a project file)

What it is
One JSON text file.
Standard inside
JSON in UTF-8.
Its own
The part is kept as its history: sketches, and the features made from them (extrusions, revolves, holes, fillets and the rest), with their numbers and the variables they use. It is the suite's own parametric description; no other CAD program rebuilds a solid from it.
InsideWhat it holds
format, version, savedobscura-engineering, 1, and when it was saved.
docThe part or assembly: its name, material, variables and features, the history in order; an assembly also holds the parts in it.

Reading it without OBSCURA. Any JSON reader opens it. To keep the shape itself readable by any CAD program, export STEP (a faceted solid), 3MF, STL, OBJ or glTF from the suite as well; a drawing of the part exports as DXF or PDF.

Written by pro-code/eng-io.js; the build checks this note against it.

OBSCURA Roots: .oroots

What it is
A zip archive.
Standard inside
The tree exports as GEDCOM 5.5.1 or 7.0, the genealogy standard every family history program and site reads, and as GEDZIP (GEDCOM 7.0 with its pictures in one zip). Pictures and documents inside the project are kept byte for byte, in their own formats.
Its own
tree.json, the whole tree as Roots holds it: the GEDCOM it was read from, with every tag Roots does not show kept on its record, and what GEDCOM has no place for: DNA matches, the research log and to-do lists, and faces marked in pictures.
InsideWhat it holds
manifest.jsonoroots, its version, when it was saved and how many people are in it.
tree.jsonThe tree: people, families, sources, repositories, notes and places, in JSON.
media/Each picture or document, under its record's ID, as it was added.

Reading it without OBSCURA. Any zip tool opens it and any JSON reader reads tree.json. To keep the tree readable by every genealogy program, export GEDCOM from Roots as well.

Written by pro-code/roots-store.js; the build checks this note against it.

OBSCURA Vector: .ovector

What it is
A zip, with the mimetype first and uncompressed, as an EPUB or an OpenDocument file has it.
Standard inside
Zip, JSON in UTF-8, every picture as the PNG or JPEG it was, and preview.svg: the first artboard as an ordinary SVG file that any browser or drawing program opens.
Its own
The shape of the JSON: artboards, layers, and each object with its geometry and style. Paths are lists of anchor points with their two handles; rectangles, ellipses, polygons and stars keep their sizes and a matrix; text keeps its face, size and spacing; parts of an imported SVG that Vector does not edit are kept as the SVG they were.
InsideWhat it holds
mimetypeapplication/x-obscura-vector.
document.jsonThe document: format is obscura-vector, then artboards, layers with everything in them, the swatches, guides and grid.
media/Each picture placed in the document, named by its id in document.json.
preview.svgThe first artboard, written as Vector exports SVG.

Reading it without OBSCURA. Unzip it and open preview.svg in any browser, or read document.json in any JSON reader. To keep every artboard in a standard file, export SVG for each from Vector, and PDF for print.

Written by pro-code/vector-out.js; the build checks this note against it.

OBSCURA Erase: .oerase

What it is
The line OBSCURA-ERASE, a four-byte length and a JSON header, then a zip sealed with AES-256-GCM under a key made from the file’s own passphrase with Argon2id (or PBKDF2-SHA-256 at 600,000 rounds where Argon2 cannot run); the header holds the parameters.
Standard inside
AES-256-GCM, Argon2id, zip and JSON in UTF-8; each request’s letters as PDF; each file that came back as the bytes it arrived as.
Its own
The shape of the JSON: the person’s details, and each request with its organisation, law, right, the text it was asked with, and its timeline of dated events.
InsideWhat it holds
erase.jsonformat is obscura-erase-data; index holds me, requests and settings.
letters/One PDF a request: the request letter and the follow-ups its timeline called for when it was exported.
files/What each organisation sent back, in a folder named by the file’s id.

Reading it without OBSCURA. With the passphrase, the header gives the key derivation’s parameters and the rest is one AES-GCM message: any tool with Argon2id and AES-GCM opens it, and the zip inside opens anywhere. The PDFs read in any PDF reader.

Written by pro-code/erase-store.js; the build checks this note against it.

OBSCURA Umbra: a folder

What it is
Not a file of its own: a project is a folder of ordinary files.
Standard inside
Everything. A folder you opened is read and written in place, so the files on your disk are the project. A project kept in the browser exports as an ordinary zip, with its git history (a normal .git folder) if you ask.
Its own
Nothing.
InsideWhat it holds
The project's filesAs you wrote them.
.gitThe version history, in git's own format, which any git reads.

Reading it without OBSCURA. Open the folder, or unzip the export, in any editor. A project kept only in the browser is lost if the browser's data is cleared, so export a zip now and then.

Written by pro-code/umbra-fs.js; the build checks this note against it.

OBSCURA Books: .obooks

What it is
A zip.
Standard inside
Zip; JSON; the receipts and bills as the pictures or PDFs they were; SHA-256.
Its own
The shape of the JSON: the company, its chart, contacts, documents, bank lines and journals, as Books keeps them.
InsideWhat it holds
company.jsonobscura-books, version 1: the company's details and VAT scheme, accounts (the chart), contacts, docs (invoices, credit notes, quotes and bills, line by line), bank (each statement line and how it was explained), journals, rules, reconciliations, posted VAT returns and closed years. Every amount is a whole number of pence (or the currency's smallest unit).
attachments/Each receipt or supplier's PDF, under its id and its own name.
manifest.jsonobscura-books-file: when it was written, and the path, size and SHA-256 of every other part, checked when Books opens the file.

Reading it without OBSCURA. Unzip it. company.json opens in any JSON reader or spreadsheet's import; the attachments are ordinary files. Books also exports every journal line, document and bank line as CSV for an accountant's own software.

Written by pro-code/books-store.js; the build checks this note against it.

OBSCURA Keep: .okeep

What it is
One file: the line OBSCURA-KEEP, a JSON header, then each sealed part as a name and its bytes. A package left for one person is the same, with a format of obscura-keep-package.
Standard inside
JSON; base64; Argon2id (or PBKDF2-SHA-256, as the header says); AES-256-GCM; HKDF-SHA-256.
Its own
The way the parts are strung together, and the names of the fields inside the index, which are Keep's. Every part is sealed, so nothing in it reads without the passphrase.
InsideWhat it holds
The headerThe Keep's id; kdf, the passphrase's salt and Argon2id parameters; wraps.pass, the 32-byte vault key sealed under the key the passphrase gives (and wraps.recovery, under the recovery key's).
index.binEvery record, login, code and note, the letter and the settings, as JSON, sealed: a version byte (1), a 12-byte IV, then AES-256-GCM ciphertext and tag. The key is HKDF-SHA-256 of the vault key, salt obscura-keep| and the id, info obscura-keep v1 data; the additional data names the part.
b-<id>.bin, t-<id>.binEach document's bytes, and its thumbnail, sealed the same way.

Reading it without OBSCURA. The Python below, with the cryptography package (version 45 or later), opens one with its passphrase and writes the index and every document into a folder. A package somebody left you also opens at /keep-open, with no account.

import base64, getpass, json, os, struct, sys
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from cryptography.hazmat.primitives.kdf.argon2 import Argon2id
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC

data, out = open(sys.argv[1], "rb").read(), sys.argv[2]
assert data.startswith(b"OBSCURA-KEEP\n"), "not a Keep file"
at = 13
n, = struct.unpack(">I", data[at:at + 4]); at += 4
header = json.loads(data[at:at + n]); at += n
parts = {}
while True:
    nl, = struct.unpack(">H", data[at:at + 2]); at += 2
    if not nl: break
    name = data[at:at + nl].decode(); at += nl
    ln, = struct.unpack(">I", data[at:at + 4]); at += 4
    parts[name] = data[at:at + ln]; at += ln
kid, k = header["id"], header["kdf"]
salt = base64.b64decode(k["salt"])
secret = (sys.argv[3] if len(sys.argv) > 3 else getpass.getpass("Passphrase: ")).encode()
if k["alg"] == "argon2id":
    kek = Argon2id(salt=salt, length=32, iterations=k["t"], lanes=k["p"], memory_cost=k["m"]).derive(secret)
else:
    kek = PBKDF2HMAC(hashes.SHA256(), 32, salt, k["i"]).derive(secret)
def unseal(key, sealed, aad):
    assert sealed[0] == 1
    return AESGCM(key).decrypt(sealed[1:13], sealed[13:], aad.encode())
vault = unseal(kek, base64.b64decode(header["wraps"]["pass"]), "obscura-keep|v1|wrap|pass|" + kid)
key = HKDF(hashes.SHA256(), 32, ("obscura-keep|" + kid).encode(), b"obscura-keep v1 data").derive(vault)
index = json.loads(unseal(key, parts["index.bin"], "obscura-keep|v1|index|%s|index" % kid))
os.makedirs(out, exist_ok=True)
json.dump(index, open(os.path.join(out, "index.json"), "w"), indent=1, ensure_ascii=False)
for item in index["items"].values():
    for f in item.get("files", []):
        body = unseal(key, parts["b-%s.bin" % f["id"]], "obscura-keep|v1|blob|%s|%s" % (kid, f["id"]))
        open(os.path.join(out, f["id"] + " " + f["name"].replace("/", "-")), "wb").write(body)
print(len(index["items"]), "items written to", out)

Written by pro-code/keep-store.js; the build checks this note against it.

OBSCURA Research: .oresearch (a project file)

What it is
A zip archive.
Standard inside
Zip, holding JSON in UTF-8, the pictures as PNG, JPEG, GIF or WebP files and the recordings as the audio and video files they were. The data also save as SPSS .sav, Stata .dta, CSV and Excel .xlsx, the output as Word and PDF, the codebook as CSV and Word, transcripts as WebVTT, and the coding as a REFI-QDA project, all standard files.
Its own
The project's description in project.json: the data set by column with each variable's label, value labels, missing values and measure; the sources' text; the codes, the coded passages (a code, a source and the characters from and to), memos, annotations and cases; the output viewer's tables; and the syntax log.
InsideWhat it holds
project.jsonformat (obscura-research), version 1, the name, data, qual, output and log.
images/Each picture a coded source holds, as it was imported.
media/Each recording a coded source holds, as it was imported.

Reading it without OBSCURA. Unzip it and open project.json in any JSON reader; the coded passages are character offsets into each source's text. To take the data elsewhere, save them as .sav, CSV or Excel from the File menu.

Written by pro-code/research-project.js; the build checks this note against it.

OBSCURA Write: .owrite

What it is
A zip, with the mimetype first and uncompressed, as an EPUB or an OpenDocument file has it.
Standard inside
Zip, JSON in UTF-8, and the pictures and PDFs in the research as the files they were.
Its own
The shape of the JSON: the binder as items with their children, and the bible, labels, targets, writing history, comments and submissions. The text of each document is a small subset of HTML (paragraphs, emphasis, footnotes and notes as spans); a screenplay's scene marks each paragraph with its element.
InsideWhat it holds
mimetypeapplication/x-obscura-write.
project.jsonThe project: format is obscura-write, items the binder, each with its title, synopsis, status, labels, target, story date, snapshots and body.
media/Each picture or PDF added to the research, named by its id in project.json.

Reading it without OBSCURA. Unzip it and read project.json in any JSON reader; every document's text is there as HTML a browser shows. To have the writing itself in a standard file, compile to Word, EPUB, Markdown or plain text, or a screenplay to Final Draft or Fountain.

Written by pro-code/write-out.js; the build checks this note against it.

OBSCURA Authorship: .oauthor (a record of how a work was made)

What it is
A zip of ordinary files.
Standard inside
Zip, JSON lines, JSON, Markdown, HTML, PDF, Word (.docx), PNG, RFC 3161 timestamp tokens (DER) and an ECDSA P-256 signature. The work itself is in its own Markdown, and as HTML, PDF and Word beside it.
Its own
The record (chain.jsonl): blocks of events chained by SHA-256, laid out in full at the top of oauthor.js, which is free code anyone can read and run.
InsideWhat it holds
chain.jsonl, reveal.jsonThe record, and the words of pastes, links and footnotes kept beside it under salted SHA-256s.
timestamps/, manifest.json, manifest.sig, device.jsonThe timestamp tokens, every file's SHA-256, and the device key's signature over that list with its public half.
document/, summary.pdfThe work, and the figures with a timeline.

Reading it without OBSCURA. Unzip it with anything. /verify-authorship checks it, free; the work in document/ opens in any word processor or browser.

Written by pro-code/authorship-export.js; the build checks this note against it.

OBSCURA Sound: .osound

What it is
A zip, with the mimetype first and uncompressed, as an EPUB or an OpenDocument file has it.
Standard inside
Zip, JSON in UTF-8, and the audio as the files it came in as (an MP3 stays an MP3, a FLAC a FLAC), or as 32-bit float WAV for audio made in Sound: a recording, or the result of an effect.
Its own
The shape of the JSON: the tracks with their gain, pan, mute and solo; each clip's place on the timeline in samples, the stretch of its file it plays, its gain and its fades; the markers; and a noise profile if one was learned.
InsideWhat it holds
mimetypeapplication/x-obscura-sound.
project.jsonThe project: format is obscura-sound, rate the sample rate every position is counted in, tracks with their clips, markers in seconds, and sources, each naming its file under audio/.
audio/Each recording, named by its id in project.json.

Reading it without OBSCURA. Unzip it: every recording in audio/ plays in any audio player or editor. To have the mix itself in a standard file, export it to WAV, which any editor opens; project.json says where each clip goes if the edit is to be rebuilt elsewhere.

Written by pro-code/sound-store.js; the build checks this note against it.

PDF Studio: standard files only

What it is
PDF Studio has no file format of its own.
Standard inside
It writes PDF; comments as XFDF; form data as FDF, XFDF or JSON; measurements as CSV; Word, Excel and PowerPoint files; certificates as PKCS#12 (.p12).
Its own
Nothing.

Reading it without OBSCURA. Whatever reads PDF and those formats: Acrobat, a browser, LibreOffice, Microsoft Office.

Written by pro-code/pdfs-comments-ui.js; the build checks this note against it.

The Sovereign Suite: .sovereign (a backup)

What it is
Sealed one of two ways: with a passphrase, as a small JSON envelope, or to age public keys, as a standard age file.
Standard inside
JSON; base64; PBKDF2-SHA256 and AES-256-GCM; age (age-encryption.org/v1).
Its own
The names of the registers inside, which are the Suite's. Each record carries its SHA-256.
InsideWhat it holds
format, method, kdf, ciphersovereign-backup, passphrase, and how it was sealed, in words.
sealedBase64 of 16 bytes of salt, 12 of IV, and the AES-256-GCM ciphertext. The key is PBKDF2-SHA256 of the passphrase with that salt, 310,000 iterations, 32 bytes.
Inside, once openedJSON: sovereign-backup-payload, the workspace's name, when it was made, every record by name (items), and each record's SHA-256 (sha256).

Reading it without OBSCURA. One sealed to age keys opens with the age program: age -d -i your-key.txt backup.sovereign. One sealed with a passphrase opens with this Python, which needs the cryptography package, and checks every record's hash as it goes:

import base64, hashlib, json, sys
from cryptography.hazmat.primitives.ciphers.aead import AESGCM

envelope = json.load(open(sys.argv[1]))
assert envelope["format"] == "sovereign-backup" and envelope["method"] == "passphrase"
blob = base64.b64decode(envelope["sealed"])
salt, iv, sealed = blob[:16], blob[16:28], blob[28:]
key = hashlib.pbkdf2_hmac("sha256", sys.argv[2].encode(), salt, 310000, 32)
payload = json.loads(AESGCM(key).decrypt(iv, sealed, None))
for name, value in payload["items"].items():
    assert hashlib.sha256(value.encode()).hexdigest() == payload["sha256"][name]
json.dump(payload, sys.stdout, indent=1)

Written by pro-code/sovereign-svc-data.js; the build checks this note against it.

What is not offered

  • No escrow of the paid applications' code. There is no copy held by a third party, to be released if the business stops, and no offline package of the paid code a customer can keep past the date of their key.
  • No written disaster recovery plan beyond this page and incident response. The site is static files and a few small routes on Cloudflare, rebuilt from the repository on every change; the records the server keeps are listed on Trust, and there is no separate backup of them beyond what Cloudflare keeps. A Team or Business admin can download the record of seat changes at any time from the seats page.
  • No service level agreement. Status shows what the scheduled checks have recorded.

Questions about any of this: [email protected].